1use std::collections::HashMap;
26use std::path::PathBuf;
27use std::sync::Arc;
28
29use anyhow::{Context, Result};
30use tokio::sync::RwLock;
31
32use async_trait::async_trait;
33
34use crate::ast::{Arg, Command, Expr, FileTestOp, Stmt, StringPart, TestExpr, ToolDef, Value, BinaryOp};
35use crate::backend::{BackendError, KernelBackend};
36use kaish_glob::glob_match;
37use crate::dispatch::{CommandDispatcher, PipelinePosition};
38use crate::interpreter::{apply_output_format, eval_expr, expand_tilde, json_to_value, value_to_bool, value_to_string, ControlFlow, ExecResult, Scope};
39use crate::parser::parse;
40use crate::scheduler::{drain_to_stream, is_bool_type, schema_param_lookup, stderr_stream, BoundedStream, JobManager, PipelineRunner, StderrReceiver, DEFAULT_STREAM_MAX_SIZE};
41use crate::tools::{extract_output_format, register_builtins, resolve_in_path, ExecContext, ToolArgs, ToolRegistry};
42use crate::validator::{Severity, Validator};
43use crate::vfs::{BuiltinFs, JobFs, LocalFs, MemoryFs, VfsRouter};
44
45#[derive(Debug, Clone)]
52pub enum VfsMountMode {
53 Passthrough,
62
63 Sandboxed {
78 root: Option<PathBuf>,
81 },
82
83 NoLocal,
93}
94
95impl Default for VfsMountMode {
96 fn default() -> Self {
97 VfsMountMode::Sandboxed { root: None }
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct KernelConfig {
104 pub name: String,
106
107 pub vfs_mode: VfsMountMode,
109
110 pub cwd: PathBuf,
112
113 pub skip_validation: bool,
119
120 pub interactive: bool,
125
126 pub ignore_config: crate::ignore_config::IgnoreConfig,
128
129 pub output_limit: crate::output_limit::OutputLimitConfig,
131
132 pub allow_external_commands: bool,
142
143 pub latch_enabled: bool,
148
149 pub trash_enabled: bool,
155
156 pub nonce_store: Option<crate::nonce::NonceStore>,
162}
163
164fn default_sandbox_root() -> PathBuf {
166 std::env::var("HOME")
167 .map(PathBuf::from)
168 .unwrap_or_else(|_| PathBuf::from("/"))
169}
170
171impl Default for KernelConfig {
172 fn default() -> Self {
173 let home = default_sandbox_root();
174 Self {
175 name: "default".to_string(),
176 vfs_mode: VfsMountMode::Sandboxed { root: None },
177 cwd: home,
178 skip_validation: false,
179 interactive: false,
180 ignore_config: crate::ignore_config::IgnoreConfig::none(),
181 output_limit: crate::output_limit::OutputLimitConfig::none(),
182 allow_external_commands: true,
183 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
184 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
185 nonce_store: None,
186 }
187 }
188}
189
190impl KernelConfig {
191 pub fn transient() -> Self {
193 let home = default_sandbox_root();
194 Self {
195 name: "transient".to_string(),
196 vfs_mode: VfsMountMode::Sandboxed { root: None },
197 cwd: home,
198 skip_validation: false,
199 interactive: false,
200 ignore_config: crate::ignore_config::IgnoreConfig::none(),
201 output_limit: crate::output_limit::OutputLimitConfig::none(),
202 allow_external_commands: true,
203 latch_enabled: false,
204 trash_enabled: false,
205 nonce_store: None,
206 }
207 }
208
209 pub fn named(name: &str) -> Self {
211 let home = default_sandbox_root();
212 Self {
213 name: name.to_string(),
214 vfs_mode: VfsMountMode::Sandboxed { root: None },
215 cwd: home,
216 skip_validation: false,
217 interactive: false,
218 ignore_config: crate::ignore_config::IgnoreConfig::none(),
219 output_limit: crate::output_limit::OutputLimitConfig::none(),
220 allow_external_commands: true,
221 latch_enabled: false,
222 trash_enabled: false,
223 nonce_store: None,
224 }
225 }
226
227 pub fn repl() -> Self {
232 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
233 Self {
234 name: "repl".to_string(),
235 vfs_mode: VfsMountMode::Passthrough,
236 cwd,
237 skip_validation: false,
238 interactive: false,
239 ignore_config: crate::ignore_config::IgnoreConfig::none(),
240 output_limit: crate::output_limit::OutputLimitConfig::none(),
241 allow_external_commands: true,
242 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
243 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
244 nonce_store: None,
245 }
246 }
247
248 pub fn mcp() -> Self {
255 let home = default_sandbox_root();
256 Self {
257 name: "mcp".to_string(),
258 vfs_mode: VfsMountMode::Sandboxed { root: None },
259 cwd: home,
260 skip_validation: false,
261 interactive: false,
262 ignore_config: crate::ignore_config::IgnoreConfig::mcp(),
263 output_limit: crate::output_limit::OutputLimitConfig::mcp(),
264 allow_external_commands: true,
265 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
266 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
267 nonce_store: None,
268 }
269 }
270
271 pub fn mcp_with_root(root: PathBuf) -> Self {
275 Self {
276 name: "mcp".to_string(),
277 vfs_mode: VfsMountMode::Sandboxed { root: Some(root.clone()) },
278 cwd: root,
279 skip_validation: false,
280 interactive: false,
281 ignore_config: crate::ignore_config::IgnoreConfig::mcp(),
282 output_limit: crate::output_limit::OutputLimitConfig::mcp(),
283 allow_external_commands: true,
284 latch_enabled: std::env::var("KAISH_LATCH").is_ok_and(|v| v == "1"),
285 trash_enabled: std::env::var("KAISH_TRASH").is_ok_and(|v| v == "1"),
286 nonce_store: None,
287 }
288 }
289
290 pub fn isolated() -> Self {
295 Self {
296 name: "isolated".to_string(),
297 vfs_mode: VfsMountMode::NoLocal,
298 cwd: PathBuf::from("/"),
299 skip_validation: false,
300 interactive: false,
301 ignore_config: crate::ignore_config::IgnoreConfig::none(),
302 output_limit: crate::output_limit::OutputLimitConfig::none(),
303 allow_external_commands: false,
304 latch_enabled: false,
305 trash_enabled: false,
306 nonce_store: None,
307 }
308 }
309
310 pub fn with_vfs_mode(mut self, mode: VfsMountMode) -> Self {
312 self.vfs_mode = mode;
313 self
314 }
315
316 pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
318 self.cwd = cwd;
319 self
320 }
321
322 pub fn with_skip_validation(mut self, skip: bool) -> Self {
324 self.skip_validation = skip;
325 self
326 }
327
328 pub fn with_interactive(mut self, interactive: bool) -> Self {
330 self.interactive = interactive;
331 self
332 }
333
334 pub fn with_ignore_config(mut self, config: crate::ignore_config::IgnoreConfig) -> Self {
336 self.ignore_config = config;
337 self
338 }
339
340 pub fn with_output_limit(mut self, config: crate::output_limit::OutputLimitConfig) -> Self {
342 self.output_limit = config;
343 self
344 }
345
346 pub fn with_allow_external_commands(mut self, allow: bool) -> Self {
352 self.allow_external_commands = allow;
353 self
354 }
355
356 pub fn with_latch(mut self, enabled: bool) -> Self {
358 self.latch_enabled = enabled;
359 self
360 }
361
362 pub fn with_trash(mut self, enabled: bool) -> Self {
364 self.trash_enabled = enabled;
365 self
366 }
367
368 pub fn with_nonce_store(mut self, store: crate::nonce::NonceStore) -> Self {
373 self.nonce_store = Some(store);
374 self
375 }
376}
377
378pub struct Kernel {
383 name: String,
385 scope: RwLock<Scope>,
387 tools: Arc<ToolRegistry>,
389 user_tools: RwLock<HashMap<String, ToolDef>>,
391 vfs: Arc<VfsRouter>,
393 jobs: Arc<JobManager>,
395 runner: PipelineRunner,
397 exec_ctx: RwLock<ExecContext>,
399 skip_validation: bool,
401 interactive: bool,
403 allow_external_commands: bool,
405 stderr_receiver: tokio::sync::Mutex<StderrReceiver>,
410 cancel_token: std::sync::Mutex<tokio_util::sync::CancellationToken>,
416 #[cfg(unix)]
418 terminal_state: Option<Arc<crate::terminal::TerminalState>>,
419}
420
421impl Kernel {
422 pub fn new(config: KernelConfig) -> Result<Self> {
424 let mut vfs = Self::setup_vfs(&config);
425 let jobs = Arc::new(JobManager::new());
426
427 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
429
430 Self::assemble(config, vfs, jobs, |_| {}, |vfs_ref, tools| {
431 ExecContext::with_vfs_and_tools(vfs_ref.clone(), tools.clone())
432 })
433 }
434
435 fn setup_vfs(config: &KernelConfig) -> VfsRouter {
437 let mut vfs = VfsRouter::new();
438
439 match &config.vfs_mode {
440 VfsMountMode::Passthrough => {
441 vfs.mount("/", LocalFs::new(PathBuf::from("/")));
443 vfs.mount("/v", MemoryFs::new());
445 }
446 VfsMountMode::Sandboxed { root } => {
447 vfs.mount("/", MemoryFs::new());
449 vfs.mount("/v", MemoryFs::new());
450
451 vfs.mount("/tmp", LocalFs::new(PathBuf::from("/tmp")));
453
454 let runtime = crate::paths::xdg_runtime_dir();
456 if runtime.exists() {
457 let runtime_str = runtime.to_string_lossy().to_string();
458 vfs.mount(&runtime_str, LocalFs::new(runtime));
459 }
460
461 let local_root = root.clone().unwrap_or_else(|| {
463 std::env::var("HOME")
464 .map(PathBuf::from)
465 .unwrap_or_else(|_| PathBuf::from("/"))
466 });
467
468 let mount_point = local_root.to_string_lossy().to_string();
472 vfs.mount(&mount_point, LocalFs::new(local_root));
473 }
474 VfsMountMode::NoLocal => {
475 vfs.mount("/", MemoryFs::new());
477 vfs.mount("/tmp", MemoryFs::new());
478 vfs.mount("/v", MemoryFs::new());
479 }
480 }
481
482 vfs
483 }
484
485 pub fn transient() -> Result<Self> {
487 Self::new(KernelConfig::transient())
488 }
489
490 pub fn with_backend(
524 backend: Arc<dyn KernelBackend>,
525 config: KernelConfig,
526 configure_vfs: impl FnOnce(&mut VfsRouter),
527 configure_tools: impl FnOnce(&mut ToolRegistry),
528 ) -> Result<Self> {
529 use crate::backend::VirtualOverlayBackend;
530
531 let mut vfs = VfsRouter::new();
532 let jobs = Arc::new(JobManager::new());
533
534 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
535 vfs.mount("/v/blobs", MemoryFs::new());
536
537 configure_vfs(&mut vfs);
539
540 Self::assemble(config, vfs, jobs, configure_tools, |vfs_arc: &Arc<VfsRouter>, _: &Arc<ToolRegistry>| {
541 let overlay: Arc<dyn KernelBackend> =
542 Arc::new(VirtualOverlayBackend::new(backend, vfs_arc.clone()));
543 ExecContext::with_backend(overlay)
544 })
545 }
546
547 fn assemble(
553 config: KernelConfig,
554 mut vfs: VfsRouter,
555 jobs: Arc<JobManager>,
556 configure_tools: impl FnOnce(&mut ToolRegistry),
557 make_ctx: impl FnOnce(&Arc<VfsRouter>, &Arc<ToolRegistry>) -> ExecContext,
558 ) -> Result<Self> {
559 let KernelConfig { name, cwd, skip_validation, interactive, ignore_config, output_limit, allow_external_commands, latch_enabled, trash_enabled, nonce_store, .. } = config;
560
561 let mut tools = ToolRegistry::new();
562 register_builtins(&mut tools);
563 configure_tools(&mut tools);
564 let tools = Arc::new(tools);
565
566 vfs.mount("/v/bin", BuiltinFs::new(tools.clone()));
568
569 let vfs = Arc::new(vfs);
570
571 let runner = PipelineRunner::new(tools.clone());
572
573 let (stderr_writer, stderr_receiver) = stderr_stream();
574
575 let mut exec_ctx = make_ctx(&vfs, &tools);
576 exec_ctx.set_cwd(cwd);
577 exec_ctx.set_job_manager(jobs.clone());
578 exec_ctx.set_tool_schemas(tools.schemas());
579 exec_ctx.set_tools(tools.clone());
580 exec_ctx.stderr = Some(stderr_writer);
581 exec_ctx.ignore_config = ignore_config;
582 exec_ctx.output_limit = output_limit;
583 exec_ctx.allow_external_commands = allow_external_commands;
584 if let Some(store) = nonce_store {
585 exec_ctx.nonce_store = store;
586 }
587
588 Ok(Self {
589 name,
590 scope: RwLock::new({
591 let mut scope = Scope::new();
592 if let Ok(home) = std::env::var("HOME") {
593 scope.set("HOME", Value::String(home));
594 }
595 scope.set_latch_enabled(latch_enabled);
596 scope.set_trash_enabled(trash_enabled);
597 scope
598 }),
599 tools,
600 user_tools: RwLock::new(HashMap::new()),
601 vfs,
602 jobs,
603 runner,
604 exec_ctx: RwLock::new(exec_ctx),
605 skip_validation,
606 interactive,
607 allow_external_commands,
608 stderr_receiver: tokio::sync::Mutex::new(stderr_receiver),
609 cancel_token: std::sync::Mutex::new(tokio_util::sync::CancellationToken::new()),
610 #[cfg(unix)]
611 terminal_state: None,
612 })
613 }
614
615 pub fn name(&self) -> &str {
617 &self.name
618 }
619
620 #[cfg(unix)]
625 pub fn init_terminal(&mut self) {
626 if !self.interactive {
627 return;
628 }
629 match crate::terminal::TerminalState::init() {
630 Ok(state) => {
631 let state = Arc::new(state);
632 self.terminal_state = Some(state.clone());
633 self.exec_ctx.get_mut().terminal_state = Some(state);
635 tracing::debug!("terminal job control initialized");
636 }
637 Err(e) => {
638 tracing::warn!("failed to initialize terminal job control: {}", e);
639 }
640 }
641 }
642
643 pub fn cancel(&self) {
649 #[allow(clippy::expect_used)]
650 let token = self.cancel_token.lock().expect("cancel_token poisoned");
651 token.cancel();
652 }
653
654 pub fn is_cancelled(&self) -> bool {
656 #[allow(clippy::expect_used)]
657 let token = self.cancel_token.lock().expect("cancel_token poisoned");
658 token.is_cancelled()
659 }
660
661 fn reset_cancel(&self) -> tokio_util::sync::CancellationToken {
663 #[allow(clippy::expect_used)]
664 let mut token = self.cancel_token.lock().expect("cancel_token poisoned");
665 if token.is_cancelled() {
666 *token = tokio_util::sync::CancellationToken::new();
667 }
668 token.clone()
669 }
670
671 pub async fn execute(&self, input: &str) -> Result<ExecResult> {
675 self.execute_streaming(input, &mut |_| {}).await
676 }
677
678 #[tracing::instrument(level = "info", skip(self, on_output), fields(input_len = input.len()))]
687 pub async fn execute_streaming(
688 &self,
689 input: &str,
690 on_output: &mut dyn FnMut(&ExecResult),
691 ) -> Result<ExecResult> {
692 let program = parse(input).map_err(|errors| {
693 let msg = errors
694 .iter()
695 .map(|e| e.to_string())
696 .collect::<Vec<_>>()
697 .join("; ");
698 anyhow::anyhow!("parse error: {}", msg)
699 })?;
700
701 {
703 let scope = self.scope.read().await;
704 if scope.show_ast() {
705 let output = format!("{:#?}\n", program);
706 return Ok(ExecResult::with_output(crate::interpreter::OutputData::text(output)));
707 }
708 }
709
710 if !self.skip_validation {
712 let user_tools = self.user_tools.read().await;
713 let validator = Validator::new(&self.tools, &user_tools);
714 let issues = validator.validate(&program);
715
716 let errors: Vec<_> = issues
718 .iter()
719 .filter(|i| i.severity == Severity::Error)
720 .collect();
721
722 if !errors.is_empty() {
723 let error_msg = errors
724 .iter()
725 .map(|e| e.format(input))
726 .collect::<Vec<_>>()
727 .join("\n");
728 return Err(anyhow::anyhow!("validation failed:\n{}", error_msg));
729 }
730
731 for warning in issues.iter().filter(|i| i.severity == Severity::Warning) {
733 tracing::trace!("validation: {}", warning.format(input));
734 }
735 }
736
737 let mut result = ExecResult::success("");
738
739 let cancel = self.reset_cancel();
741
742 for stmt in program.statements {
743 if matches!(stmt, Stmt::Empty) {
744 continue;
745 }
746
747 if cancel.is_cancelled() {
749 result.code = 130;
750 return Ok(result);
751 }
752
753 let flow = self.execute_stmt_flow(&stmt).await?;
754
755 let drained_stderr = {
759 let mut receiver = self.stderr_receiver.lock().await;
760 receiver.drain_lossy()
761 };
762
763 match flow {
764 ControlFlow::Normal(mut r) => {
765 if !drained_stderr.is_empty() {
766 if !r.err.is_empty() && !r.err.ends_with('\n') {
767 r.err.push('\n');
768 }
769 let combined = format!("{}{}", drained_stderr, r.err);
771 r.err = combined;
772 }
773 on_output(&r);
774 let last_output = r.output.clone();
778 accumulate_result(&mut result, &r);
779 result.output = last_output;
780 }
781 ControlFlow::Exit { code } => {
782 if !drained_stderr.is_empty() {
783 result.err.push_str(&drained_stderr);
784 }
785 result.code = code;
786 return Ok(result);
787 }
788 ControlFlow::Return { mut value } => {
789 if !drained_stderr.is_empty() {
790 value.err = format!("{}{}", drained_stderr, value.err);
791 }
792 on_output(&value);
793 result = value;
794 }
795 ControlFlow::Break { result: mut r, .. } | ControlFlow::Continue { result: mut r, .. } => {
796 if !drained_stderr.is_empty() {
797 r.err = format!("{}{}", drained_stderr, r.err);
798 }
799 on_output(&r);
800 result = r;
801 }
802 }
803 }
804
805 Ok(result)
806 }
807
808 fn execute_stmt_flow<'a>(
810 &'a self,
811 stmt: &'a Stmt,
812 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ControlFlow>> + Send + 'a>> {
813 use tracing::Instrument;
814 let span = tracing::debug_span!("execute_stmt_flow", stmt_type = %stmt.kind_name());
815 Box::pin(async move {
816 match stmt {
817 Stmt::Assignment(assign) => {
818 let value = self.eval_expr_async(&assign.value).await
820 .context("failed to evaluate assignment")?;
821 let mut scope = self.scope.write().await;
822 if assign.local {
823 scope.set(&assign.name, value.clone());
825 } else {
826 scope.set_global(&assign.name, value.clone());
828 }
829 drop(scope);
830
831 Ok(ControlFlow::ok(ExecResult::success("")))
833 }
834 Stmt::Command(cmd) => {
835 let pipeline = crate::ast::Pipeline {
838 commands: vec![cmd.clone()],
839 background: false,
840 };
841 let result = self.execute_pipeline(&pipeline).await?;
842 self.update_last_result(&result).await;
843
844 if !result.ok() {
846 let scope = self.scope.read().await;
847 if scope.error_exit_enabled() {
848 return Ok(ControlFlow::exit_code(result.code));
849 }
850 }
851
852 Ok(ControlFlow::ok(result))
853 }
854 Stmt::Pipeline(pipeline) => {
855 let result = self.execute_pipeline(pipeline).await?;
856 self.update_last_result(&result).await;
857
858 if !result.ok() {
860 let scope = self.scope.read().await;
861 if scope.error_exit_enabled() {
862 return Ok(ControlFlow::exit_code(result.code));
863 }
864 }
865
866 Ok(ControlFlow::ok(result))
867 }
868 Stmt::If(if_stmt) => {
869 let cond_value = self.eval_expr_async(&if_stmt.condition).await?;
871
872 let branch = if is_truthy(&cond_value) {
873 &if_stmt.then_branch
874 } else {
875 if_stmt.else_branch.as_deref().unwrap_or(&[])
876 };
877
878 let mut result = ExecResult::success("");
879 for stmt in branch {
880 let flow = self.execute_stmt_flow(stmt).await?;
881 match flow {
882 ControlFlow::Normal(r) => {
883 accumulate_result(&mut result, &r);
884 self.drain_stderr_into(&mut result).await;
885 }
886 other => {
887 self.drain_stderr_into(&mut result).await;
888 return Ok(other);
889 }
890 }
891 }
892 Ok(ControlFlow::ok(result))
893 }
894 Stmt::For(for_loop) => {
895 let mut items: Vec<Value> = Vec::new();
898 for item_expr in &for_loop.items {
899 let item = self.eval_expr_async(item_expr).await?;
900 match &item {
902 Value::Json(serde_json::Value::Array(arr)) => {
904 for elem in arr {
905 items.push(json_to_value(elem.clone()));
906 }
907 }
908 Value::String(_) => {
911 items.push(item);
912 }
913 _ => items.push(item),
915 }
916 }
917
918 let mut result = ExecResult::success("");
919 {
920 let mut scope = self.scope.write().await;
921 scope.push_frame();
922 }
923
924 'outer: for item in items {
925 if self.is_cancelled() {
927 let mut scope = self.scope.write().await;
928 scope.pop_frame();
929 result.code = 130;
930 return Ok(ControlFlow::ok(result));
931 }
932 {
933 let mut scope = self.scope.write().await;
934 scope.set(&for_loop.variable, item);
935 }
936 for stmt in &for_loop.body {
937 let mut flow = self.execute_stmt_flow(stmt).await?;
938 self.drain_stderr_into(&mut result).await;
939 match &mut flow {
940 ControlFlow::Normal(r) => {
941 accumulate_result(&mut result, r);
942 if !r.ok() {
943 let scope = self.scope.read().await;
944 if scope.error_exit_enabled() {
945 drop(scope);
946 let mut scope = self.scope.write().await;
947 scope.pop_frame();
948 return Ok(ControlFlow::exit_code(r.code));
949 }
950 }
951 }
952 ControlFlow::Break { .. } => {
953 if flow.decrement_level() {
954 break 'outer;
955 }
956 let mut scope = self.scope.write().await;
957 scope.pop_frame();
958 return Ok(flow);
959 }
960 ControlFlow::Continue { .. } => {
961 if flow.decrement_level() {
962 continue 'outer;
963 }
964 let mut scope = self.scope.write().await;
965 scope.pop_frame();
966 return Ok(flow);
967 }
968 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
969 let mut scope = self.scope.write().await;
970 scope.pop_frame();
971 return Ok(flow);
972 }
973 }
974 }
975 }
976
977 {
978 let mut scope = self.scope.write().await;
979 scope.pop_frame();
980 }
981 Ok(ControlFlow::ok(result))
982 }
983 Stmt::While(while_loop) => {
984 let mut result = ExecResult::success("");
985
986 'outer: loop {
987 if self.is_cancelled() {
990 result.code = 130;
991 return Ok(ControlFlow::ok(result));
992 }
993
994 let cond_value = self.eval_expr_async(&while_loop.condition).await?;
995
996 if !is_truthy(&cond_value) {
997 break;
998 }
999
1000 for stmt in &while_loop.body {
1002 let mut flow = self.execute_stmt_flow(stmt).await?;
1003 self.drain_stderr_into(&mut result).await;
1004 match &mut flow {
1005 ControlFlow::Normal(r) => {
1006 accumulate_result(&mut result, r);
1007 if !r.ok() {
1008 let scope = self.scope.read().await;
1009 if scope.error_exit_enabled() {
1010 return Ok(ControlFlow::exit_code(r.code));
1011 }
1012 }
1013 }
1014 ControlFlow::Break { .. } => {
1015 if flow.decrement_level() {
1016 break 'outer;
1017 }
1018 return Ok(flow);
1019 }
1020 ControlFlow::Continue { .. } => {
1021 if flow.decrement_level() {
1022 continue 'outer;
1023 }
1024 return Ok(flow);
1025 }
1026 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
1027 return Ok(flow);
1028 }
1029 }
1030 }
1031 }
1032
1033 Ok(ControlFlow::ok(result))
1034 }
1035 Stmt::Case(case_stmt) => {
1036 let match_value = {
1038 let value = self.eval_expr_async(&case_stmt.expr).await?;
1039 value_to_string(&value)
1040 };
1041
1042 for branch in &case_stmt.branches {
1044 let matched = branch.patterns.iter().any(|pattern| {
1045 glob_match(pattern, &match_value)
1046 });
1047
1048 if matched {
1049 let mut result = ExecResult::success("");
1051 for stmt in &branch.body {
1052 let flow = self.execute_stmt_flow(stmt).await?;
1053 match flow {
1054 ControlFlow::Normal(r) => {
1055 accumulate_result(&mut result, &r);
1056 self.drain_stderr_into(&mut result).await;
1057 }
1058 other => {
1059 self.drain_stderr_into(&mut result).await;
1060 return Ok(other);
1061 }
1062 }
1063 }
1064 return Ok(ControlFlow::ok(result));
1065 }
1066 }
1067
1068 Ok(ControlFlow::ok(ExecResult::success("")))
1070 }
1071 Stmt::Break(levels) => {
1072 Ok(ControlFlow::break_n(levels.unwrap_or(1)))
1073 }
1074 Stmt::Continue(levels) => {
1075 Ok(ControlFlow::continue_n(levels.unwrap_or(1)))
1076 }
1077 Stmt::Return(expr) => {
1078 let result = if let Some(e) = expr {
1081 let val = self.eval_expr_async(e).await?;
1082 let code = match val {
1084 Value::Int(n) => n,
1085 Value::Bool(b) => if b { 0 } else { 1 },
1086 _ => 0,
1087 };
1088 ExecResult {
1089 code,
1090 out: String::new(),
1091 err: String::new(),
1092 data: None,
1093 output: None,
1094 did_spill: false,
1095 original_code: None,
1096 }
1097 } else {
1098 ExecResult::success("")
1099 };
1100 Ok(ControlFlow::return_value(result))
1101 }
1102 Stmt::Exit(expr) => {
1103 let code = if let Some(e) = expr {
1104 let val = self.eval_expr_async(e).await?;
1105 match val {
1106 Value::Int(n) => n,
1107 _ => 0,
1108 }
1109 } else {
1110 0
1111 };
1112 Ok(ControlFlow::exit_code(code))
1113 }
1114 Stmt::ToolDef(tool_def) => {
1115 let mut user_tools = self.user_tools.write().await;
1116 user_tools.insert(tool_def.name.clone(), tool_def.clone());
1117 Ok(ControlFlow::ok(ExecResult::success("")))
1118 }
1119 Stmt::AndChain { left, right } => {
1120 {
1123 let mut scope = self.scope.write().await;
1124 scope.suppress_errexit();
1125 }
1126 let left_flow = self.execute_stmt_flow(left).await?;
1127 {
1128 let mut scope = self.scope.write().await;
1129 scope.unsuppress_errexit();
1130 }
1131 match left_flow {
1132 ControlFlow::Normal(mut left_result) => {
1133 self.drain_stderr_into(&mut left_result).await;
1134 self.update_last_result(&left_result).await;
1135 if left_result.ok() {
1136 let right_flow = self.execute_stmt_flow(right).await?;
1137 match right_flow {
1138 ControlFlow::Normal(mut right_result) => {
1139 self.drain_stderr_into(&mut right_result).await;
1140 self.update_last_result(&right_result).await;
1141 let mut combined = left_result;
1142 accumulate_result(&mut combined, &right_result);
1143 Ok(ControlFlow::ok(combined))
1144 }
1145 other => Ok(other),
1146 }
1147 } else {
1148 Ok(ControlFlow::ok(left_result))
1149 }
1150 }
1151 _ => Ok(left_flow),
1152 }
1153 }
1154 Stmt::OrChain { left, right } => {
1155 {
1158 let mut scope = self.scope.write().await;
1159 scope.suppress_errexit();
1160 }
1161 let left_flow = self.execute_stmt_flow(left).await?;
1162 {
1163 let mut scope = self.scope.write().await;
1164 scope.unsuppress_errexit();
1165 }
1166 match left_flow {
1167 ControlFlow::Normal(mut left_result) => {
1168 self.drain_stderr_into(&mut left_result).await;
1169 self.update_last_result(&left_result).await;
1170 if !left_result.ok() {
1171 let right_flow = self.execute_stmt_flow(right).await?;
1172 match right_flow {
1173 ControlFlow::Normal(mut right_result) => {
1174 self.drain_stderr_into(&mut right_result).await;
1175 self.update_last_result(&right_result).await;
1176 let mut combined = left_result;
1177 accumulate_result(&mut combined, &right_result);
1178 Ok(ControlFlow::ok(combined))
1179 }
1180 other => Ok(other),
1181 }
1182 } else {
1183 Ok(ControlFlow::ok(left_result))
1184 }
1185 }
1186 _ => Ok(left_flow), }
1188 }
1189 Stmt::Test(test_expr) => {
1190 let is_true = self.eval_test_async(test_expr).await?;
1191 if is_true {
1192 Ok(ControlFlow::ok(ExecResult::success("")))
1193 } else {
1194 Ok(ControlFlow::ok(ExecResult::failure(1, "")))
1195 }
1196 }
1197 Stmt::Empty => Ok(ControlFlow::ok(ExecResult::success(""))),
1198 }
1199 }.instrument(span))
1200 }
1201
1202 #[tracing::instrument(level = "debug", skip(self, pipeline), fields(background = pipeline.background, command_count = pipeline.commands.len()))]
1204 async fn execute_pipeline(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
1205 if pipeline.commands.is_empty() {
1206 return Ok(ExecResult::success(""));
1207 }
1208
1209 if pipeline.background {
1211 return self.execute_background(pipeline).await;
1212 }
1213
1214 let mut ctx = {
1222 let ec = self.exec_ctx.read().await;
1223 let scope = self.scope.read().await;
1224 ExecContext {
1225 backend: ec.backend.clone(),
1226 scope: scope.clone(),
1227 cwd: ec.cwd.clone(),
1228 prev_cwd: ec.prev_cwd.clone(),
1229 stdin: None,
1230 stdin_data: None,
1231 pipe_stdin: None,
1232 pipe_stdout: None,
1233 stderr: ec.stderr.clone(),
1234 tool_schemas: ec.tool_schemas.clone(),
1235 tools: ec.tools.clone(),
1236 job_manager: ec.job_manager.clone(),
1237 pipeline_position: PipelinePosition::Only,
1238 interactive: self.interactive,
1239 aliases: ec.aliases.clone(),
1240 ignore_config: ec.ignore_config.clone(),
1241 output_limit: ec.output_limit.clone(),
1242 allow_external_commands: self.allow_external_commands,
1243 nonce_store: ec.nonce_store.clone(),
1244 #[cfg(unix)]
1245 terminal_state: ec.terminal_state.clone(),
1246 }
1247 }; let mut result = self.runner.run(&pipeline.commands, &mut ctx, self).await;
1250
1251 if ctx.output_limit.is_enabled() {
1253 let _ = crate::output_limit::spill_if_needed(&mut result, &ctx.output_limit).await;
1254 }
1255
1256 if result.did_spill {
1259 result.original_code = Some(result.code);
1260 result.code = 3;
1261 }
1262
1263 {
1265 let mut ec = self.exec_ctx.write().await;
1266 ec.cwd = ctx.cwd.clone();
1267 ec.prev_cwd = ctx.prev_cwd.clone();
1268 ec.aliases = ctx.aliases.clone();
1269 ec.ignore_config = ctx.ignore_config.clone();
1270 ec.output_limit = ctx.output_limit.clone();
1271 }
1272 {
1273 let mut scope = self.scope.write().await;
1274 *scope = ctx.scope.clone();
1275 }
1276
1277 Ok(result)
1278 }
1279
1280 #[tracing::instrument(level = "debug", skip(self, pipeline), fields(command_count = pipeline.commands.len()))]
1288 async fn execute_background(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
1289 use tokio::sync::oneshot;
1290
1291 let command_str = self.format_pipeline(pipeline);
1293
1294 let stdout = Arc::new(BoundedStream::default_size());
1296 let stderr = Arc::new(BoundedStream::default_size());
1297
1298 let (tx, rx) = oneshot::channel();
1300
1301 let job_id = self.jobs.register_with_streams(
1303 command_str.clone(),
1304 rx,
1305 stdout.clone(),
1306 stderr.clone(),
1307 ).await;
1308
1309 let runner = self.runner.clone();
1311 let commands = pipeline.commands.clone();
1312 let backend = {
1313 let ctx = self.exec_ctx.read().await;
1314 ctx.backend.clone()
1315 };
1316 let scope = {
1317 let scope = self.scope.read().await;
1318 scope.clone()
1319 };
1320 let cwd = {
1321 let ctx = self.exec_ctx.read().await;
1322 ctx.cwd.clone()
1323 };
1324 let tools = self.tools.clone();
1325 let tool_schemas = self.tools.schemas();
1326 let allow_ext = self.allow_external_commands;
1327
1328 tokio::spawn(async move {
1330 let mut bg_ctx = ExecContext::with_backend(backend);
1333 bg_ctx.scope = scope;
1334 bg_ctx.cwd = cwd;
1335 bg_ctx.set_tools(tools.clone());
1336 bg_ctx.set_tool_schemas(tool_schemas);
1337 bg_ctx.allow_external_commands = allow_ext;
1338
1339 let dispatcher = crate::dispatch::BackendDispatcher::new(tools);
1342
1343 let result = runner.run(&commands, &mut bg_ctx, &dispatcher).await;
1345
1346 let text = result.text_out();
1348 if !text.is_empty() {
1349 stdout.write(text.as_bytes()).await;
1350 }
1351 if !result.err.is_empty() {
1352 stderr.write(result.err.as_bytes()).await;
1353 }
1354
1355 stdout.close().await;
1357 stderr.close().await;
1358
1359 let _ = tx.send(result);
1361 });
1362
1363 Ok(ExecResult::success(format!("[{}]", job_id)))
1364 }
1365
1366 fn format_pipeline(&self, pipeline: &crate::ast::Pipeline) -> String {
1368 pipeline.commands
1369 .iter()
1370 .map(|cmd| {
1371 let mut parts = vec![cmd.name.clone()];
1372 for arg in &cmd.args {
1373 match arg {
1374 Arg::Positional(expr) => {
1375 parts.push(self.format_expr(expr));
1376 }
1377 Arg::Named { key, value } => {
1378 parts.push(format!("{}={}", key, self.format_expr(value)));
1379 }
1380 Arg::ShortFlag(name) => {
1381 parts.push(format!("-{}", name));
1382 }
1383 Arg::LongFlag(name) => {
1384 parts.push(format!("--{}", name));
1385 }
1386 Arg::DoubleDash => {
1387 parts.push("--".to_string());
1388 }
1389 }
1390 }
1391 parts.join(" ")
1392 })
1393 .collect::<Vec<_>>()
1394 .join(" | ")
1395 }
1396
1397 fn format_expr(&self, expr: &Expr) -> String {
1399 match expr {
1400 Expr::Literal(Value::String(s)) => {
1401 if s.contains(' ') || s.contains('"') {
1402 format!("'{}'", s.replace('\'', "\\'"))
1403 } else {
1404 s.clone()
1405 }
1406 }
1407 Expr::Literal(Value::Int(i)) => i.to_string(),
1408 Expr::Literal(Value::Float(f)) => f.to_string(),
1409 Expr::Literal(Value::Bool(b)) => b.to_string(),
1410 Expr::Literal(Value::Null) => "null".to_string(),
1411 Expr::VarRef(path) => {
1412 let name = path.segments.iter()
1413 .map(|seg| match seg {
1414 crate::ast::VarSegment::Field(f) => f.clone(),
1415 })
1416 .collect::<Vec<_>>()
1417 .join(".");
1418 format!("${{{}}}", name)
1419 }
1420 Expr::Interpolated(_) => "\"...\"".to_string(),
1421 _ => "...".to_string(),
1422 }
1423 }
1424
1425 async fn execute_command(&self, name: &str, args: &[Arg]) -> Result<ExecResult> {
1427 self.execute_command_depth(name, args, 0).await
1428 }
1429
1430 #[tracing::instrument(level = "info", skip(self, args, alias_depth), fields(command = %name), err)]
1431 async fn execute_command_depth(&self, name: &str, args: &[Arg], alias_depth: u8) -> Result<ExecResult> {
1432 match name {
1434 "true" => return Ok(ExecResult::success("")),
1435 "false" => return Ok(ExecResult::failure(1, "")),
1436 "source" | "." => return self.execute_source(args).await,
1437 _ => {}
1438 }
1439
1440 if alias_depth < 10 {
1442 let alias_value = {
1443 let ctx = self.exec_ctx.read().await;
1444 ctx.aliases.get(name).cloned()
1445 };
1446 if let Some(alias_val) = alias_value {
1447 let parts: Vec<&str> = alias_val.split_whitespace().collect();
1449 if let Some((alias_cmd, alias_args)) = parts.split_first() {
1450 let mut new_args: Vec<Arg> = alias_args
1451 .iter()
1452 .map(|a| Arg::Positional(Expr::Literal(Value::String(a.to_string()))))
1453 .collect();
1454 new_args.extend_from_slice(args);
1455 return Box::pin(self.execute_command_depth(alias_cmd, &new_args, alias_depth + 1)).await;
1456 }
1457 }
1458 }
1459
1460 if let Some(builtin_name) = name.strip_prefix("/v/bin/") {
1462 return match self.tools.get(builtin_name) {
1463 Some(_) => Box::pin(self.execute_command_depth(builtin_name, args, alias_depth)).await,
1464 None => Ok(ExecResult::failure(127, format!("command not found: {}", name))),
1465 };
1466 }
1467
1468 {
1470 let user_tools = self.user_tools.read().await;
1471 if let Some(tool_def) = user_tools.get(name) {
1472 let tool_def = tool_def.clone();
1473 drop(user_tools);
1474 return self.execute_user_tool(tool_def, args).await;
1475 }
1476 }
1477
1478 let tool = match self.tools.get(name) {
1480 Some(t) => t,
1481 None => {
1482 if let Some(result) = self.try_execute_script(name, args).await? {
1484 return Ok(result);
1485 }
1486 if let Some(result) = self.try_execute_external(name, args).await? {
1488 return Ok(result);
1489 }
1490
1491 let backend = self.exec_ctx.read().await.backend.clone();
1496 let tool_schema = backend.get_tool(name).await.ok().flatten().map(|t| {
1497 let mut s = t.schema;
1498 s.map_positionals = true;
1499 s
1500 });
1501 let tool_args = self.build_args_async(args, tool_schema.as_ref()).await?;
1502 let mut ctx = self.exec_ctx.write().await;
1503 {
1504 let scope = self.scope.read().await;
1505 ctx.scope = scope.clone();
1506 }
1507 let backend = ctx.backend.clone();
1508 match backend.call_tool(name, tool_args, &mut ctx).await {
1509 Ok(tool_result) => {
1510 let mut scope = self.scope.write().await;
1511 *scope = ctx.scope.clone();
1512 let mut exec = ExecResult::from_output(
1513 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1514 );
1515 exec.output = tool_result.output;
1516 return Ok(exec);
1517 }
1518 Err(BackendError::ToolNotFound(_)) => {
1519 }
1521 Err(e) => {
1522 tracing::debug!("backend error for {name}: {e}");
1525 }
1526 }
1527
1528 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1529 }
1530 };
1531
1532 let schema = tool.schema();
1534 let mut tool_args = self.build_args_async(args, Some(&schema)).await?;
1535 let output_format = extract_output_format(&mut tool_args, Some(&schema));
1536
1537 let schema_claims = |flag: &str| -> bool {
1539 let bare = flag.trim_start_matches('-');
1540 schema.params.iter().any(|p| p.matches_flag(flag) || p.matches_flag(bare))
1541 };
1542 let wants_help =
1543 (tool_args.flags.contains("help") && !schema_claims("help"))
1544 || (tool_args.flags.contains("h") && !schema_claims("-h"));
1545 if wants_help {
1546 let help_topic = crate::help::HelpTopic::Tool(name.to_string());
1547 let ctx = self.exec_ctx.read().await;
1548 let content = crate::help::get_help(&help_topic, &ctx.tool_schemas);
1549 return Ok(ExecResult::with_output(crate::interpreter::OutputData::text(content)));
1550 }
1551
1552 let mut ctx = self.exec_ctx.write().await;
1554 {
1555 let scope = self.scope.read().await;
1556 ctx.scope = scope.clone();
1557 }
1558
1559 let result = tool.execute(tool_args, &mut ctx).await;
1560
1561 {
1563 let mut scope = self.scope.write().await;
1564 *scope = ctx.scope.clone();
1565 }
1566
1567 let result = match output_format {
1568 Some(format) => apply_output_format(result, format),
1569 None => result,
1570 };
1571
1572 Ok(result)
1573 }
1574
1575 async fn build_args_async(&self, args: &[Arg], schema: Option<&crate::tools::ToolSchema>) -> Result<ToolArgs> {
1579 let mut tool_args = ToolArgs::new();
1580 let param_lookup = schema.map(schema_param_lookup).unwrap_or_default();
1581
1582 let mut consumed: std::collections::HashSet<usize> = std::collections::HashSet::new();
1584 let mut past_double_dash = false;
1585
1586 let positional_indices: Vec<usize> = args.iter().enumerate()
1588 .filter_map(|(i, a)| matches!(a, Arg::Positional(_)).then_some(i))
1589 .collect();
1590
1591 let mut i = 0;
1592 while i < args.len() {
1593 match &args[i] {
1594 Arg::DoubleDash => {
1595 past_double_dash = true;
1596 }
1597 Arg::Positional(expr) => {
1598 if !consumed.contains(&i) {
1599 let value = self.eval_expr_async(expr).await?;
1600 let value = apply_tilde_expansion(value);
1601 tool_args.positional.push(value);
1602 }
1603 }
1604 Arg::Named { key, value } => {
1605 let val = self.eval_expr_async(value).await?;
1606 let val = apply_tilde_expansion(val);
1607 tool_args.named.insert(key.clone(), val);
1608 }
1609 Arg::ShortFlag(name) => {
1610 if past_double_dash {
1611 tool_args.positional.push(Value::String(format!("-{name}")));
1612 } else if name.len() == 1 {
1613 let flag_name = name.as_str();
1614 let lookup = param_lookup.get(flag_name);
1615 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1616
1617 if is_bool {
1618 tool_args.flags.insert(flag_name.to_string());
1619 } else {
1620 let canonical = lookup.map(|(name, _)| *name).unwrap_or(flag_name);
1622 let next_pos = positional_indices.iter()
1623 .find(|idx| **idx > i && !consumed.contains(idx));
1624
1625 if let Some(&pos_idx) = next_pos {
1626 if let Arg::Positional(expr) = &args[pos_idx] {
1627 let value = self.eval_expr_async(expr).await?;
1628 let value = apply_tilde_expansion(value);
1629 tool_args.named.insert(canonical.to_string(), value);
1630 consumed.insert(pos_idx);
1631 }
1632 } else {
1633 tool_args.flags.insert(flag_name.to_string());
1634 }
1635 }
1636 } else if let Some(&(canonical, typ)) = param_lookup.get(name.as_str()) {
1637 if is_bool_type(typ) {
1639 tool_args.flags.insert(canonical.to_string());
1640 } else {
1641 let next_pos = positional_indices.iter()
1642 .find(|idx| **idx > i && !consumed.contains(idx));
1643 if let Some(&pos_idx) = next_pos {
1644 if let Arg::Positional(expr) = &args[pos_idx] {
1645 let value = self.eval_expr_async(expr).await?;
1646 let value = apply_tilde_expansion(value);
1647 tool_args.named.insert(canonical.to_string(), value);
1648 consumed.insert(pos_idx);
1649 }
1650 } else {
1651 tool_args.flags.insert(name.clone());
1652 }
1653 }
1654 } else {
1655 for c in name.chars() {
1657 tool_args.flags.insert(c.to_string());
1658 }
1659 }
1660 }
1661 Arg::LongFlag(name) => {
1662 if past_double_dash {
1663 tool_args.positional.push(Value::String(format!("--{name}")));
1664 } else {
1665 let lookup = param_lookup.get(name.as_str());
1666 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1667
1668 if is_bool {
1669 tool_args.flags.insert(name.clone());
1670 } else {
1671 let canonical = lookup.map(|(name, _)| *name).unwrap_or(name.as_str());
1672 let next_pos = positional_indices.iter()
1673 .find(|idx| **idx > i && !consumed.contains(idx));
1674
1675 if let Some(&pos_idx) = next_pos {
1676 if let Arg::Positional(expr) = &args[pos_idx] {
1677 let value = self.eval_expr_async(expr).await?;
1678 let value = apply_tilde_expansion(value);
1679 tool_args.named.insert(canonical.to_string(), value);
1680 consumed.insert(pos_idx);
1681 }
1682 } else {
1683 tool_args.flags.insert(name.clone());
1684 }
1685 }
1686 }
1687 }
1688 }
1689 i += 1;
1690 }
1691
1692 if let Some(schema) = schema.filter(|s| s.map_positionals) {
1697 let pre_dash_count = if past_double_dash {
1698 let dash_pos = args.iter().position(|a| matches!(a, Arg::DoubleDash)).unwrap_or(args.len());
1699 positional_indices.iter()
1700 .filter(|idx| **idx < dash_pos && !consumed.contains(idx))
1701 .count()
1702 } else {
1703 tool_args.positional.len()
1704 };
1705
1706 let mut remaining = Vec::new();
1707 let mut positional_iter = tool_args.positional.drain(..).enumerate();
1708
1709 for param in &schema.params {
1710 if tool_args.named.contains_key(¶m.name) || tool_args.flags.contains(¶m.name) {
1711 continue;
1712 }
1713 if is_bool_type(¶m.param_type) {
1714 continue;
1715 }
1716 loop {
1717 match positional_iter.next() {
1718 Some((idx, val)) if idx < pre_dash_count => {
1719 tool_args.named.insert(param.name.clone(), val);
1720 break;
1721 }
1722 Some((_, val)) => {
1723 remaining.push(val);
1724 }
1725 None => break,
1726 }
1727 }
1728 }
1729
1730 remaining.extend(positional_iter.map(|(_, v)| v));
1731 tool_args.positional = remaining;
1732 }
1733
1734 Ok(tool_args)
1735 }
1736
1737 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1747 let mut argv = Vec::new();
1748 for arg in args {
1749 match arg {
1750 Arg::Positional(expr) => {
1751 let value = self.eval_expr_async(expr).await?;
1752 let value = apply_tilde_expansion(value);
1753 argv.push(value_to_string(&value));
1754 }
1755 Arg::Named { key, value } => {
1756 let val = self.eval_expr_async(value).await?;
1757 let val = apply_tilde_expansion(val);
1758 argv.push(format!("{}={}", key, value_to_string(&val)));
1759 }
1760 Arg::ShortFlag(name) => {
1761 argv.push(format!("-{}", name));
1763 }
1764 Arg::LongFlag(name) => {
1765 argv.push(format!("--{}", name));
1767 }
1768 Arg::DoubleDash => {
1769 argv.push("--".to_string());
1771 }
1772 }
1773 }
1774 Ok(argv)
1775 }
1776
1777 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send + 'a>> {
1782 Box::pin(async move {
1783 match expr {
1784 Expr::Literal(value) => Ok(value.clone()),
1785 Expr::VarRef(path) => {
1786 let scope = self.scope.read().await;
1787 scope.resolve_path(path)
1788 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1789 }
1790 Expr::Interpolated(parts) => {
1791 let mut result = String::new();
1792 for part in parts {
1793 result.push_str(&self.eval_string_part_async(part).await?);
1794 }
1795 Ok(Value::String(result))
1796 }
1797 Expr::BinaryOp { left, op, right } => {
1798 match op {
1799 BinaryOp::And => {
1800 let left_val = self.eval_expr_async(left).await?;
1801 if !is_truthy(&left_val) {
1802 return Ok(left_val);
1803 }
1804 self.eval_expr_async(right).await
1805 }
1806 BinaryOp::Or => {
1807 let left_val = self.eval_expr_async(left).await?;
1808 if is_truthy(&left_val) {
1809 return Ok(left_val);
1810 }
1811 self.eval_expr_async(right).await
1812 }
1813 _ => {
1814 let left_val = self.eval_expr_async(left).await?;
1816 let right_val = self.eval_expr_async(right).await?;
1817 let resolved = Expr::BinaryOp {
1818 left: Box::new(Expr::Literal(left_val)),
1819 op: *op,
1820 right: Box::new(Expr::Literal(right_val)),
1821 };
1822 let mut scope = self.scope.write().await;
1823 eval_expr(&resolved, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1824 }
1825 }
1826 }
1827 Expr::CommandSubst(pipeline) => {
1828 let saved_scope = { self.scope.read().await.clone() };
1831 let saved_cwd = {
1832 let ec = self.exec_ctx.read().await;
1833 (ec.cwd.clone(), ec.prev_cwd.clone())
1834 };
1835
1836 let run_result = self.execute_pipeline(pipeline).await;
1838
1839 {
1841 let mut scope = self.scope.write().await;
1842 *scope = saved_scope;
1843 if let Ok(ref r) = run_result {
1844 scope.set_last_result(r.clone());
1845 }
1846 }
1847 {
1848 let mut ec = self.exec_ctx.write().await;
1849 ec.cwd = saved_cwd.0;
1850 ec.prev_cwd = saved_cwd.1;
1851 }
1852
1853 let result = run_result?;
1855
1856 if let Some(data) = &result.data {
1858 Ok(data.clone())
1859 } else if let Some(ref output) = result.output {
1860 if output.is_flat() && !output.is_simple_text() && !output.root.is_empty() {
1862 let items: Vec<serde_json::Value> = output.root.iter()
1863 .map(|n| serde_json::Value::String(n.display_name().to_string()))
1864 .collect();
1865 Ok(Value::Json(serde_json::Value::Array(items)))
1866 } else {
1867 Ok(Value::String(result.text_out().trim_end().to_string()))
1868 }
1869 } else {
1870 Ok(Value::String(result.text_out().trim_end().to_string()))
1872 }
1873 }
1874 Expr::Test(test_expr) => {
1875 Ok(Value::Bool(self.eval_test_async(test_expr).await?))
1876 }
1877 Expr::Positional(n) => {
1878 let scope = self.scope.read().await;
1879 match scope.get_positional(*n) {
1880 Some(s) => Ok(Value::String(s.to_string())),
1881 None => Ok(Value::String(String::new())),
1882 }
1883 }
1884 Expr::AllArgs => {
1885 let scope = self.scope.read().await;
1886 Ok(Value::String(scope.all_args().join(" ")))
1887 }
1888 Expr::ArgCount => {
1889 let scope = self.scope.read().await;
1890 Ok(Value::Int(scope.arg_count() as i64))
1891 }
1892 Expr::VarLength(name) => {
1893 let scope = self.scope.read().await;
1894 match scope.get(name) {
1895 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1896 None => Ok(Value::Int(0)),
1897 }
1898 }
1899 Expr::VarWithDefault { name, default } => {
1900 let scope = self.scope.read().await;
1901 let use_default = match scope.get(name) {
1902 Some(value) => value_to_string(value).is_empty(),
1903 None => true,
1904 };
1905 drop(scope); if use_default {
1907 self.eval_string_parts_async(default).await.map(Value::String)
1909 } else {
1910 let scope = self.scope.read().await;
1911 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1912 }
1913 }
1914 Expr::Arithmetic(expr_str) => {
1915 let scope = self.scope.read().await;
1916 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1917 .map(Value::Int)
1918 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1919 }
1920 Expr::Command(cmd) => {
1921 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1923 Ok(Value::Bool(result.code == 0))
1924 }
1925 Expr::LastExitCode => {
1926 let scope = self.scope.read().await;
1927 Ok(Value::Int(scope.last_result().code))
1928 }
1929 Expr::CurrentPid => {
1930 let scope = self.scope.read().await;
1931 Ok(Value::Int(scope.pid() as i64))
1932 }
1933 }
1934 })
1935 }
1936
1937 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1939 Box::pin(async move {
1940 let mut result = String::new();
1941 for part in parts {
1942 result.push_str(&self.eval_string_part_async(part).await?);
1943 }
1944 Ok(result)
1945 })
1946 }
1947
1948 fn eval_test_async<'a>(&'a self, test_expr: &'a TestExpr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<bool>> + Send + 'a>> {
1952 Box::pin(async move {
1953 match test_expr {
1954 TestExpr::FileTest { op, path } => {
1955 let path_value = self.eval_expr_async(path).await?;
1956 let path_str = value_to_string(&path_value);
1957 let backend = self.exec_ctx.read().await.backend.clone();
1958 let entry = backend.stat(std::path::Path::new(&path_str)).await.ok();
1959 Ok(match op {
1960 FileTestOp::Exists => entry.is_some(),
1961 FileTestOp::IsFile => entry.as_ref().is_some_and(|e| e.is_file()),
1962 FileTestOp::IsDir => entry.as_ref().is_some_and(|e| e.is_dir()),
1963 FileTestOp::Readable => entry.is_some(),
1964 FileTestOp::Writable => entry.as_ref().is_some_and(|e| {
1965 e.permissions.is_none_or(|p| p & 0o222 != 0)
1966 }),
1967 FileTestOp::Executable => entry.as_ref().is_some_and(|e| {
1968 e.permissions.is_some_and(|p| p & 0o111 != 0)
1969 }),
1970 })
1971 }
1972 TestExpr::StringTest { op, value } => {
1973 let val = self.eval_expr_async(value).await?;
1974 let s = value_to_string(&val);
1975 Ok(match op {
1976 crate::ast::StringTestOp::IsEmpty => s.is_empty(),
1977 crate::ast::StringTestOp::IsNonEmpty => !s.is_empty(),
1978 })
1979 }
1980 TestExpr::Comparison { left, op, right } => {
1981 let left_val = self.eval_expr_async(left).await?;
1983 let right_val = self.eval_expr_async(right).await?;
1984 let resolved = TestExpr::Comparison {
1985 left: Box::new(Expr::Literal(left_val)),
1986 op: *op,
1987 right: Box::new(Expr::Literal(right_val)),
1988 };
1989 let expr = Expr::Test(Box::new(resolved));
1990 let mut scope = self.scope.write().await;
1991 let value = eval_expr(&expr, &mut scope)
1992 .map_err(|e| anyhow::anyhow!("{}", e))?;
1993 Ok(value_to_bool(&value))
1994 }
1995 TestExpr::And { left, right } => {
1996 if !self.eval_test_async(left).await? {
1997 Ok(false)
1998 } else {
1999 self.eval_test_async(right).await
2000 }
2001 }
2002 TestExpr::Or { left, right } => {
2003 if self.eval_test_async(left).await? {
2004 Ok(true)
2005 } else {
2006 self.eval_test_async(right).await
2007 }
2008 }
2009 TestExpr::Not { expr } => {
2010 Ok(!self.eval_test_async(expr).await?)
2011 }
2012 }
2013 })
2014 }
2015
2016 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
2017 Box::pin(async move {
2018 match part {
2019 StringPart::Literal(s) => Ok(s.clone()),
2020 StringPart::Var(path) => {
2021 let scope = self.scope.read().await;
2022 match scope.resolve_path(path) {
2023 Some(value) => Ok(value_to_string(&value)),
2024 None => Ok(String::new()), }
2026 }
2027 StringPart::VarWithDefault { name, default } => {
2028 let scope = self.scope.read().await;
2029 let use_default = match scope.get(name) {
2030 Some(value) => value_to_string(value).is_empty(),
2031 None => true,
2032 };
2033 drop(scope); if use_default {
2035 self.eval_string_parts_async(default).await
2037 } else {
2038 let scope = self.scope.read().await;
2039 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
2040 }
2041 }
2042 StringPart::VarLength(name) => {
2043 let scope = self.scope.read().await;
2044 match scope.get(name) {
2045 Some(value) => Ok(value_to_string(value).len().to_string()),
2046 None => Ok("0".to_string()),
2047 }
2048 }
2049 StringPart::Positional(n) => {
2050 let scope = self.scope.read().await;
2051 match scope.get_positional(*n) {
2052 Some(s) => Ok(s.to_string()),
2053 None => Ok(String::new()),
2054 }
2055 }
2056 StringPart::AllArgs => {
2057 let scope = self.scope.read().await;
2058 Ok(scope.all_args().join(" "))
2059 }
2060 StringPart::ArgCount => {
2061 let scope = self.scope.read().await;
2062 Ok(scope.arg_count().to_string())
2063 }
2064 StringPart::Arithmetic(expr) => {
2065 let scope = self.scope.read().await;
2066 match crate::arithmetic::eval_arithmetic(expr, &scope) {
2067 Ok(value) => Ok(value.to_string()),
2068 Err(_) => Ok(String::new()),
2069 }
2070 }
2071 StringPart::CommandSubst(pipeline) => {
2072 let saved_scope = { self.scope.read().await.clone() };
2075 let saved_cwd = {
2076 let ec = self.exec_ctx.read().await;
2077 (ec.cwd.clone(), ec.prev_cwd.clone())
2078 };
2079
2080 let run_result = self.execute_pipeline(pipeline).await;
2082
2083 {
2085 let mut scope = self.scope.write().await;
2086 *scope = saved_scope;
2087 if let Ok(ref r) = run_result {
2088 scope.set_last_result(r.clone());
2089 }
2090 }
2091 {
2092 let mut ec = self.exec_ctx.write().await;
2093 ec.cwd = saved_cwd.0;
2094 ec.prev_cwd = saved_cwd.1;
2095 }
2096
2097 let result = run_result?;
2099
2100 Ok(result.text_out().trim_end_matches('\n').to_string())
2101 }
2102 StringPart::LastExitCode => {
2103 let scope = self.scope.read().await;
2104 Ok(scope.last_result().code.to_string())
2105 }
2106 StringPart::CurrentPid => {
2107 let scope = self.scope.read().await;
2108 Ok(scope.pid().to_string())
2109 }
2110 }
2111 })
2112 }
2113
2114 async fn update_last_result(&self, result: &ExecResult) {
2116 let mut scope = self.scope.write().await;
2117 scope.set_last_result(result.clone());
2118 }
2119
2120 async fn drain_stderr_into(&self, result: &mut ExecResult) {
2126 let drained = {
2127 let mut receiver = self.stderr_receiver.lock().await;
2128 receiver.drain_lossy()
2129 };
2130 if !drained.is_empty() {
2131 if !result.err.is_empty() && !result.err.ends_with('\n') {
2132 result.err.push('\n');
2133 }
2134 result.err.push_str(&drained);
2135 }
2136 }
2137
2138 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
2144 let tool_args = self.build_args_async(args, None).await?;
2146
2147 {
2149 let mut scope = self.scope.write().await;
2150 scope.push_frame();
2151 }
2152
2153 let saved_positional = {
2155 let mut scope = self.scope.write().await;
2156 let saved = scope.save_positional();
2157
2158 let positional_args: Vec<String> = tool_args.positional
2160 .iter()
2161 .map(value_to_string)
2162 .collect();
2163 scope.set_positional(&def.name, positional_args);
2164
2165 saved
2166 };
2167
2168 let mut accumulated_out = String::new();
2171 let mut accumulated_err = String::new();
2172 let mut last_code = 0i64;
2173 let mut last_data: Option<Value> = None;
2174
2175 let mut exec_error: Option<anyhow::Error> = None;
2177 let mut exit_code: Option<i64> = None;
2178
2179 for stmt in &def.body {
2180 match self.execute_stmt_flow(stmt).await {
2181 Ok(flow) => {
2182 let drained = {
2184 let mut receiver = self.stderr_receiver.lock().await;
2185 receiver.drain_lossy()
2186 };
2187 if !drained.is_empty() {
2188 accumulated_err.push_str(&drained);
2189 }
2190
2191 match flow {
2192 ControlFlow::Normal(r) => {
2193 accumulated_out.push_str(&r.out);
2194 accumulated_err.push_str(&r.err);
2195 last_code = r.code;
2196 last_data = r.data;
2197 }
2198 ControlFlow::Return { value } => {
2199 accumulated_out.push_str(&value.out);
2200 accumulated_err.push_str(&value.err);
2201 last_code = value.code;
2202 last_data = value.data;
2203 break;
2204 }
2205 ControlFlow::Exit { code } => {
2206 exit_code = Some(code);
2207 break;
2208 }
2209 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
2210 accumulated_out.push_str(&r.out);
2211 accumulated_err.push_str(&r.err);
2212 last_code = r.code;
2213 last_data = r.data;
2214 }
2215 }
2216 }
2217 Err(e) => {
2218 exec_error = Some(e);
2219 break;
2220 }
2221 }
2222 }
2223
2224 {
2226 let mut scope = self.scope.write().await;
2227 scope.pop_frame();
2228 scope.set_positional(saved_positional.0, saved_positional.1);
2229 }
2230
2231 if let Some(e) = exec_error {
2233 return Err(e);
2234 }
2235 if let Some(code) = exit_code {
2236 return Ok(ExecResult {
2237 code,
2238 out: accumulated_out,
2239 err: accumulated_err,
2240 data: last_data,
2241 output: None,
2242 did_spill: false,
2243 original_code: None,
2244 });
2245 }
2246
2247 Ok(ExecResult {
2248 code: last_code,
2249 out: accumulated_out,
2250 err: accumulated_err,
2251 data: last_data,
2252 output: None,
2253 did_spill: false,
2254 original_code: None,
2255 })
2256 }
2257
2258 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
2263 let tool_args = self.build_args_async(args, None).await?;
2265 let path = match tool_args.positional.first() {
2266 Some(Value::String(s)) => s.clone(),
2267 Some(v) => value_to_string(v),
2268 None => {
2269 return Ok(ExecResult::failure(1, "source: missing filename"));
2270 }
2271 };
2272
2273 let full_path = {
2275 let ctx = self.exec_ctx.read().await;
2276 if path.starts_with('/') {
2277 std::path::PathBuf::from(&path)
2278 } else {
2279 ctx.cwd.join(&path)
2280 }
2281 };
2282
2283 let content = {
2285 let ctx = self.exec_ctx.read().await;
2286 match ctx.backend.read(&full_path, None).await {
2287 Ok(bytes) => {
2288 String::from_utf8(bytes).map_err(|e| {
2289 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
2290 })?
2291 }
2292 Err(e) => {
2293 return Ok(ExecResult::failure(
2294 1,
2295 format!("source: {}: {}", path, e),
2296 ));
2297 }
2298 }
2299 };
2300
2301 let program = match crate::parser::parse(&content) {
2303 Ok(p) => p,
2304 Err(errors) => {
2305 let msg = errors
2306 .iter()
2307 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
2308 .collect::<Vec<_>>()
2309 .join("\n");
2310 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
2311 }
2312 };
2313
2314 let mut result = ExecResult::success("");
2316 for stmt in program.statements {
2317 if matches!(stmt, crate::ast::Stmt::Empty) {
2318 continue;
2319 }
2320
2321 match self.execute_stmt_flow(&stmt).await {
2322 Ok(flow) => {
2323 self.drain_stderr_into(&mut result).await;
2324 match flow {
2325 ControlFlow::Normal(r) => {
2326 result = r.clone();
2327 self.update_last_result(&r).await;
2328 }
2329 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
2330 return Err(anyhow::anyhow!(
2331 "source: {}: unexpected break/continue outside loop",
2332 path
2333 ));
2334 }
2335 ControlFlow::Return { value } => {
2336 return Ok(value);
2337 }
2338 ControlFlow::Exit { code } => {
2339 result.code = code;
2340 return Ok(result);
2341 }
2342 }
2343 }
2344 Err(e) => {
2345 return Err(e.context(format!("source: {}", path)));
2346 }
2347 }
2348 }
2349
2350 Ok(result)
2351 }
2352
2353 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
2358 let path_value = {
2360 let scope = self.scope.read().await;
2361 scope
2362 .get("PATH")
2363 .map(value_to_string)
2364 .unwrap_or_else(|| "/bin".to_string())
2365 };
2366
2367 for dir in path_value.split(':') {
2369 if dir.is_empty() {
2370 continue;
2371 }
2372
2373 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
2375
2376 let exists = {
2378 let ctx = self.exec_ctx.read().await;
2379 ctx.backend.exists(&script_path).await
2380 };
2381
2382 if !exists {
2383 continue;
2384 }
2385
2386 let content = {
2388 let ctx = self.exec_ctx.read().await;
2389 match ctx.backend.read(&script_path, None).await {
2390 Ok(bytes) => match String::from_utf8(bytes) {
2391 Ok(s) => s,
2392 Err(e) => {
2393 return Ok(Some(ExecResult::failure(
2394 1,
2395 format!("{}: invalid UTF-8: {}", script_path.display(), e),
2396 )));
2397 }
2398 },
2399 Err(e) => {
2400 return Ok(Some(ExecResult::failure(
2401 1,
2402 format!("{}: {}", script_path.display(), e),
2403 )));
2404 }
2405 }
2406 };
2407
2408 let program = match crate::parser::parse(&content) {
2410 Ok(p) => p,
2411 Err(errors) => {
2412 let msg = errors
2413 .iter()
2414 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
2415 .collect::<Vec<_>>()
2416 .join("\n");
2417 return Ok(Some(ExecResult::failure(1, msg)));
2418 }
2419 };
2420
2421 let tool_args = self.build_args_async(args, None).await?;
2423
2424 let mut isolated_scope = Scope::new();
2426
2427 let positional_args: Vec<String> = tool_args.positional
2429 .iter()
2430 .map(value_to_string)
2431 .collect();
2432 isolated_scope.set_positional(name, positional_args);
2433
2434 let original_scope = {
2436 let mut scope = self.scope.write().await;
2437 std::mem::replace(&mut *scope, isolated_scope)
2438 };
2439
2440 let mut result = ExecResult::success("");
2442 let mut exec_error: Option<anyhow::Error> = None;
2443 let mut exit_code: Option<i64> = None;
2444
2445 for stmt in program.statements {
2446 if matches!(stmt, crate::ast::Stmt::Empty) {
2447 continue;
2448 }
2449
2450 match self.execute_stmt_flow(&stmt).await {
2451 Ok(flow) => {
2452 match flow {
2453 ControlFlow::Normal(r) => result = r,
2454 ControlFlow::Return { value } => {
2455 result = value;
2456 break;
2457 }
2458 ControlFlow::Exit { code } => {
2459 exit_code = Some(code);
2460 break;
2461 }
2462 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
2463 result = r;
2464 }
2465 }
2466 }
2467 Err(e) => {
2468 exec_error = Some(e);
2469 break;
2470 }
2471 }
2472 }
2473
2474 {
2476 let mut scope = self.scope.write().await;
2477 *scope = original_scope;
2478 }
2479
2480 if let Some(e) = exec_error {
2482 return Err(e.context(format!("script: {}", script_path.display())));
2483 }
2484 if let Some(code) = exit_code {
2485 result.code = code;
2486 return Ok(Some(result));
2487 }
2488
2489 return Ok(Some(result));
2490 }
2491
2492 Ok(None)
2494 }
2495
2496 #[tracing::instrument(level = "debug", skip(self, args), fields(command = %name))]
2510 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
2511 if !self.allow_external_commands {
2512 return Ok(None);
2513 }
2514
2515 let real_cwd = {
2520 let ctx = self.exec_ctx.read().await;
2521 match ctx.backend.resolve_real_path(&ctx.cwd) {
2522 Some(p) => p,
2523 None => return Ok(None),
2524 }
2525 };
2526
2527 let executable = if name.contains('/') {
2528 let resolved = if std::path::Path::new(name).is_absolute() {
2530 std::path::PathBuf::from(name)
2531 } else {
2532 real_cwd.join(name)
2533 };
2534 if !resolved.exists() {
2535 return Ok(Some(ExecResult::failure(
2536 127,
2537 format!("{}: No such file or directory", name),
2538 )));
2539 }
2540 if !resolved.is_file() {
2541 return Ok(Some(ExecResult::failure(
2542 126,
2543 format!("{}: Is a directory", name),
2544 )));
2545 }
2546 #[cfg(unix)]
2547 {
2548 use std::os::unix::fs::PermissionsExt;
2549 let mode = std::fs::metadata(&resolved)
2550 .map(|m| m.permissions().mode())
2551 .unwrap_or(0);
2552 if mode & 0o111 == 0 {
2553 return Ok(Some(ExecResult::failure(
2554 126,
2555 format!("{}: Permission denied", name),
2556 )));
2557 }
2558 }
2559 resolved.to_string_lossy().into_owned()
2560 } else {
2561 let path_var = {
2563 let scope = self.scope.read().await;
2564 scope
2565 .get("PATH")
2566 .map(value_to_string)
2567 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
2568 };
2569
2570 match resolve_in_path(name, &path_var) {
2572 Some(path) => path,
2573 None => return Ok(None), }
2575 };
2576
2577 tracing::debug!(executable = %executable, "resolved external command");
2578
2579 let argv = self.build_args_flat(args).await?;
2581
2582 let stdin_data = {
2584 let mut ctx = self.exec_ctx.write().await;
2585 ctx.take_stdin()
2586 };
2587
2588 use tokio::process::Command;
2590
2591 let mut cmd = Command::new(&executable);
2592 cmd.args(&argv);
2593 cmd.current_dir(&real_cwd);
2594
2595 cmd.stdin(if stdin_data.is_some() {
2597 std::process::Stdio::piped()
2598 } else if self.interactive {
2599 std::process::Stdio::inherit()
2600 } else {
2601 std::process::Stdio::null()
2602 });
2603
2604 let pipeline_position = {
2608 let ctx = self.exec_ctx.read().await;
2609 ctx.pipeline_position
2610 };
2611 let inherit_output = self.interactive
2612 && matches!(pipeline_position, PipelinePosition::Only | PipelinePosition::Last);
2613
2614 if inherit_output {
2615 cmd.stdout(std::process::Stdio::inherit());
2616 cmd.stderr(std::process::Stdio::inherit());
2617 } else {
2618 cmd.stdout(std::process::Stdio::piped());
2619 cmd.stderr(std::process::Stdio::piped());
2620 }
2621
2622 #[cfg(unix)]
2626 if self.terminal_state.is_some() && inherit_output {
2627 #[allow(unsafe_code)]
2629 unsafe {
2630 cmd.pre_exec(|| {
2631 nix::unistd::setpgid(nix::unistd::Pid::from_raw(0), nix::unistd::Pid::from_raw(0))
2633 .map_err(|e| std::io::Error::from_raw_os_error(e as i32))?;
2634 use nix::libc::{sigaction, SIGTSTP, SIGTTOU, SIGTTIN, SIGINT, SIG_DFL};
2636 let mut sa: nix::libc::sigaction = std::mem::zeroed();
2637 sa.sa_sigaction = SIG_DFL;
2638 sigaction(SIGTSTP, &sa, std::ptr::null_mut());
2639 sigaction(SIGTTOU, &sa, std::ptr::null_mut());
2640 sigaction(SIGTTIN, &sa, std::ptr::null_mut());
2641 sigaction(SIGINT, &sa, std::ptr::null_mut());
2642 Ok(())
2643 });
2644 }
2645 }
2646
2647 let mut child = match cmd.spawn() {
2649 Ok(child) => child,
2650 Err(e) => {
2651 return Ok(Some(ExecResult::failure(
2652 127,
2653 format!("{}: {}", name, e),
2654 )));
2655 }
2656 };
2657
2658 if let Some(data) = stdin_data
2660 && let Some(mut stdin) = child.stdin.take()
2661 {
2662 use tokio::io::AsyncWriteExt;
2663 if let Err(e) = stdin.write_all(data.as_bytes()).await {
2664 return Ok(Some(ExecResult::failure(
2665 1,
2666 format!("{}: failed to write stdin: {}", name, e),
2667 )));
2668 }
2669 }
2671
2672 if inherit_output {
2673 #[cfg(unix)]
2675 if let Some(ref term) = self.terminal_state {
2676 let child_id = child.id().unwrap_or(0);
2677 let pid = nix::unistd::Pid::from_raw(child_id as i32);
2678 let pgid = pid; if let Err(e) = term.give_terminal_to(pgid) {
2682 tracing::warn!("failed to give terminal to child: {}", e);
2683 }
2684
2685 let term_clone = term.clone();
2686 let cmd_name = name.to_string();
2687 let cmd_display = format!("{} {}", name, argv.join(" "));
2688 let jobs = self.jobs.clone();
2689
2690 let code = tokio::task::block_in_place(move || {
2691 let result = term_clone.wait_for_foreground(pid);
2692
2693 if let Err(e) = term_clone.reclaim_terminal() {
2695 tracing::warn!("failed to reclaim terminal: {}", e);
2696 }
2697
2698 match result {
2699 crate::terminal::WaitResult::Exited(code) => code as i64,
2700 crate::terminal::WaitResult::Signaled(sig) => 128 + sig as i64,
2701 crate::terminal::WaitResult::Stopped(_sig) => {
2702 let rt = tokio::runtime::Handle::current();
2704 let job_id = rt.block_on(jobs.register_stopped(
2705 cmd_display,
2706 child_id,
2707 child_id, ));
2709 eprintln!("\n[{}]+ Stopped\t{}", job_id, cmd_name);
2710 148 }
2712 }
2713 });
2714
2715 return Ok(Some(ExecResult::from_output(code, String::new(), String::new())));
2716 }
2717
2718 let status = match child.wait().await {
2720 Ok(s) => s,
2721 Err(e) => {
2722 return Ok(Some(ExecResult::failure(
2723 1,
2724 format!("{}: failed to wait: {}", name, e),
2725 )));
2726 }
2727 };
2728
2729 let code = status.code().unwrap_or_else(|| {
2730 #[cfg(unix)]
2731 {
2732 use std::os::unix::process::ExitStatusExt;
2733 128 + status.signal().unwrap_or(0)
2734 }
2735 #[cfg(not(unix))]
2736 {
2737 -1
2738 }
2739 }) as i64;
2740
2741 Ok(Some(ExecResult::from_output(code, String::new(), String::new())))
2743 } else {
2744 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2746 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2747
2748 let stdout_pipe = child.stdout.take();
2749 let stderr_pipe = child.stderr.take();
2750
2751 let stdout_clone = stdout_stream.clone();
2752 let stderr_clone = stderr_stream.clone();
2753
2754 let stdout_task = stdout_pipe.map(|pipe| {
2755 tokio::spawn(async move {
2756 drain_to_stream(pipe, stdout_clone).await;
2757 })
2758 });
2759
2760 let stderr_task = stderr_pipe.map(|pipe| {
2761 tokio::spawn(async move {
2762 drain_to_stream(pipe, stderr_clone).await;
2763 })
2764 });
2765
2766 let status = match child.wait().await {
2767 Ok(s) => s,
2768 Err(e) => {
2769 return Ok(Some(ExecResult::failure(
2770 1,
2771 format!("{}: failed to wait: {}", name, e),
2772 )));
2773 }
2774 };
2775
2776 if let Some(task) = stdout_task {
2777 let _ = task.await;
2779 }
2780 if let Some(task) = stderr_task {
2781 let _ = task.await;
2782 }
2783
2784 let code = status.code().unwrap_or_else(|| {
2785 #[cfg(unix)]
2786 {
2787 use std::os::unix::process::ExitStatusExt;
2788 128 + status.signal().unwrap_or(0)
2789 }
2790 #[cfg(not(unix))]
2791 {
2792 -1
2793 }
2794 }) as i64;
2795
2796 let stdout = stdout_stream.read_string().await;
2797 let stderr = stderr_stream.read_string().await;
2798
2799 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
2800 }
2801 }
2802
2803 pub async fn get_var(&self, name: &str) -> Option<Value> {
2807 let scope = self.scope.read().await;
2808 scope.get(name).cloned()
2809 }
2810
2811 #[cfg(test)]
2813 pub async fn error_exit_enabled(&self) -> bool {
2814 let scope = self.scope.read().await;
2815 scope.error_exit_enabled()
2816 }
2817
2818 pub async fn set_var(&self, name: &str, value: Value) {
2820 let mut scope = self.scope.write().await;
2821 scope.set(name.to_string(), value);
2822 }
2823
2824 pub async fn set_positional(&self, script_name: impl Into<String>, args: Vec<String>) {
2826 let mut scope = self.scope.write().await;
2827 scope.set_positional(script_name, args);
2828 }
2829
2830 pub async fn list_vars(&self) -> Vec<(String, Value)> {
2832 let scope = self.scope.read().await;
2833 scope.all()
2834 }
2835
2836 pub async fn cwd(&self) -> PathBuf {
2840 self.exec_ctx.read().await.cwd.clone()
2841 }
2842
2843 pub async fn set_cwd(&self, path: PathBuf) {
2845 let mut ctx = self.exec_ctx.write().await;
2846 ctx.set_cwd(path);
2847 }
2848
2849 pub async fn last_result(&self) -> ExecResult {
2853 let scope = self.scope.read().await;
2854 scope.last_result().clone()
2855 }
2856
2857 pub async fn has_function(&self, name: &str) -> bool {
2861 self.user_tools.read().await.contains_key(name)
2862 }
2863
2864 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
2866 self.tools.schemas()
2867 }
2868
2869 pub fn jobs(&self) -> Arc<JobManager> {
2873 self.jobs.clone()
2874 }
2875
2876 pub fn vfs(&self) -> Arc<VfsRouter> {
2880 self.vfs.clone()
2881 }
2882
2883 pub async fn reset(&self) -> Result<()> {
2890 {
2891 let mut scope = self.scope.write().await;
2892 *scope = Scope::new();
2893 }
2894 {
2895 let mut ctx = self.exec_ctx.write().await;
2896 ctx.cwd = PathBuf::from("/");
2897 }
2898 Ok(())
2899 }
2900
2901 pub async fn shutdown(self) -> Result<()> {
2903 self.jobs.wait_all().await;
2905 Ok(())
2906 }
2907
2908 async fn dispatch_command(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2919 {
2921 let mut scope = self.scope.write().await;
2922 *scope = ctx.scope.clone();
2923 }
2924 {
2925 let mut ec = self.exec_ctx.write().await;
2926 ec.cwd = ctx.cwd.clone();
2927 ec.prev_cwd = ctx.prev_cwd.clone();
2928 ec.stdin = ctx.stdin.take();
2929 ec.stdin_data = ctx.stdin_data.take();
2930 ec.aliases = ctx.aliases.clone();
2931 ec.ignore_config = ctx.ignore_config.clone();
2932 ec.output_limit = ctx.output_limit.clone();
2933 ec.pipeline_position = ctx.pipeline_position;
2934 }
2935
2936 let result = self.execute_command(&cmd.name, &cmd.args).await?;
2938
2939 {
2941 let scope = self.scope.read().await;
2942 ctx.scope = scope.clone();
2943 }
2944 {
2945 let ec = self.exec_ctx.read().await;
2946 ctx.cwd = ec.cwd.clone();
2947 ctx.prev_cwd = ec.prev_cwd.clone();
2948 ctx.aliases = ec.aliases.clone();
2949 ctx.ignore_config = ec.ignore_config.clone();
2950 ctx.output_limit = ec.output_limit.clone();
2951 }
2952
2953 Ok(result)
2954 }
2955}
2956
2957#[async_trait]
2958impl CommandDispatcher for Kernel {
2959 async fn dispatch(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2965 self.dispatch_command(cmd, ctx).await
2966 }
2967}
2968
2969fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2975 if accumulated.out.is_empty() {
2979 if let Some(ref output) = accumulated.output {
2980 accumulated.out = output.to_canonical_string();
2981 accumulated.output = None;
2982 }
2983 }
2984 let new_text = new.text_out();
2985 if !accumulated.out.is_empty() && !new_text.is_empty() && !accumulated.out.ends_with('\n') {
2986 accumulated.out.push('\n');
2987 }
2988 accumulated.out.push_str(&new_text);
2989 if !accumulated.err.is_empty() && !new.err.is_empty() && !accumulated.err.ends_with('\n') {
2990 accumulated.err.push('\n');
2991 }
2992 accumulated.err.push_str(&new.err);
2993 accumulated.code = new.code;
2994 accumulated.data = new.data.clone();
2995}
2996
2997fn is_truthy(value: &Value) -> bool {
2999 match value {
3000 Value::Null => false,
3001 Value::Bool(b) => *b,
3002 Value::Int(i) => *i != 0,
3003 Value::Float(f) => *f != 0.0,
3004 Value::String(s) => !s.is_empty(),
3005 Value::Json(json) => match json {
3006 serde_json::Value::Null => false,
3007 serde_json::Value::Array(arr) => !arr.is_empty(),
3008 serde_json::Value::Object(obj) => !obj.is_empty(),
3009 serde_json::Value::Bool(b) => *b,
3010 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
3011 serde_json::Value::String(s) => !s.is_empty(),
3012 },
3013 Value::Blob(_) => true, }
3015}
3016
3017fn apply_tilde_expansion(value: Value) -> Value {
3021 match value {
3022 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
3023 _ => value,
3024 }
3025}
3026
3027#[cfg(test)]
3028mod tests {
3029 use super::*;
3030
3031 #[tokio::test]
3032 async fn test_kernel_transient() {
3033 let kernel = Kernel::transient().expect("failed to create kernel");
3034 assert_eq!(kernel.name(), "transient");
3035 }
3036
3037 #[tokio::test]
3038 async fn test_kernel_execute_echo() {
3039 let kernel = Kernel::transient().expect("failed to create kernel");
3040 let result = kernel.execute("echo hello").await.expect("execution failed");
3041 assert!(result.ok());
3042 assert_eq!(result.out.trim(), "hello");
3043 }
3044
3045 #[tokio::test]
3046 async fn test_multiple_statements_accumulate_output() {
3047 let kernel = Kernel::transient().expect("failed to create kernel");
3048 let result = kernel
3049 .execute("echo one\necho two\necho three")
3050 .await
3051 .expect("execution failed");
3052 assert!(result.ok());
3053 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
3055 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
3056 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
3057 }
3058
3059 #[tokio::test]
3060 async fn test_and_chain_accumulates_output() {
3061 let kernel = Kernel::transient().expect("failed to create kernel");
3062 let result = kernel
3063 .execute("echo first && echo second")
3064 .await
3065 .expect("execution failed");
3066 assert!(result.ok());
3067 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
3068 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
3069 }
3070
3071 #[tokio::test]
3072 async fn test_for_loop_accumulates_output() {
3073 let kernel = Kernel::transient().expect("failed to create kernel");
3074 let result = kernel
3075 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
3076 .await
3077 .expect("execution failed");
3078 assert!(result.ok());
3079 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
3080 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
3081 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
3082 }
3083
3084 #[tokio::test]
3085 async fn test_while_loop_accumulates_output() {
3086 let kernel = Kernel::transient().expect("failed to create kernel");
3087 let result = kernel
3088 .execute(r#"
3089 N=3
3090 while [[ ${N} -gt 0 ]]; do
3091 echo "N=${N}"
3092 N=$((N - 1))
3093 done
3094 "#)
3095 .await
3096 .expect("execution failed");
3097 assert!(result.ok());
3098 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
3099 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
3100 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
3101 }
3102
3103 #[tokio::test]
3104 async fn test_kernel_set_var() {
3105 let kernel = Kernel::transient().expect("failed to create kernel");
3106
3107 kernel.execute("X=42").await.expect("set failed");
3108
3109 let value = kernel.get_var("X").await;
3110 assert_eq!(value, Some(Value::Int(42)));
3111 }
3112
3113 #[tokio::test]
3114 async fn test_kernel_var_expansion() {
3115 let kernel = Kernel::transient().expect("failed to create kernel");
3116
3117 kernel.execute("NAME=\"world\"").await.expect("set failed");
3118 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
3119
3120 assert!(result.ok());
3121 assert_eq!(result.out.trim(), "hello world");
3122 }
3123
3124 #[tokio::test]
3125 async fn test_kernel_last_result() {
3126 let kernel = Kernel::transient().expect("failed to create kernel");
3127
3128 kernel.execute("echo test").await.expect("echo failed");
3129
3130 let last = kernel.last_result().await;
3131 assert!(last.ok());
3132 assert_eq!(last.out.trim(), "test");
3133 }
3134
3135 #[tokio::test]
3136 async fn test_kernel_tool_not_found() {
3137 let kernel = Kernel::transient().expect("failed to create kernel");
3138
3139 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
3140 assert!(!result.ok());
3141 assert_eq!(result.code, 127);
3142 assert!(result.err.contains("command not found"));
3143 }
3144
3145 #[tokio::test]
3146 async fn test_external_command_true() {
3147 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
3149
3150 let result = kernel.execute("true").await.expect("execution failed");
3152 assert!(result.ok(), "true should succeed: {:?}", result);
3154 }
3155
3156 #[tokio::test]
3157 async fn test_external_command_basic() {
3158 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
3160
3161 let path_var = std::env::var("PATH").unwrap_or_default();
3166 eprintln!("System PATH: {}", path_var);
3167
3168 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
3170
3171 let result = kernel.execute("uname").await.expect("execution failed");
3174 eprintln!("uname result: {:?}", result);
3175 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
3177 }
3178
3179 #[tokio::test]
3180 async fn test_kernel_reset() {
3181 let kernel = Kernel::transient().expect("failed to create kernel");
3182
3183 kernel.execute("X=1").await.expect("set failed");
3184 assert!(kernel.get_var("X").await.is_some());
3185
3186 kernel.reset().await.expect("reset failed");
3187 assert!(kernel.get_var("X").await.is_none());
3188 }
3189
3190 #[tokio::test]
3191 async fn test_kernel_cwd() {
3192 let kernel = Kernel::transient().expect("failed to create kernel");
3193
3194 let cwd = kernel.cwd().await;
3196 let home = std::env::var("HOME")
3197 .map(PathBuf::from)
3198 .unwrap_or_else(|_| PathBuf::from("/"));
3199 assert_eq!(cwd, home);
3200
3201 kernel.set_cwd(PathBuf::from("/tmp")).await;
3202 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
3203 }
3204
3205 #[tokio::test]
3206 async fn test_kernel_list_vars() {
3207 let kernel = Kernel::transient().expect("failed to create kernel");
3208
3209 kernel.execute("A=1").await.ok();
3210 kernel.execute("B=2").await.ok();
3211
3212 let vars = kernel.list_vars().await;
3213 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
3214 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
3215 }
3216
3217 #[tokio::test]
3218 async fn test_is_truthy() {
3219 assert!(!is_truthy(&Value::Null));
3220 assert!(!is_truthy(&Value::Bool(false)));
3221 assert!(is_truthy(&Value::Bool(true)));
3222 assert!(!is_truthy(&Value::Int(0)));
3223 assert!(is_truthy(&Value::Int(1)));
3224 assert!(!is_truthy(&Value::String("".into())));
3225 assert!(is_truthy(&Value::String("x".into())));
3226 }
3227
3228 #[tokio::test]
3229 async fn test_jq_in_pipeline() {
3230 let kernel = Kernel::transient().expect("failed to create kernel");
3231 let result = kernel
3233 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
3234 .await
3235 .expect("execution failed");
3236 assert!(result.ok(), "jq pipeline failed: {}", result.err);
3237 assert_eq!(result.out.trim(), "Alice");
3238 }
3239
3240 #[tokio::test]
3241 async fn test_user_defined_tool() {
3242 let kernel = Kernel::transient().expect("failed to create kernel");
3243
3244 kernel
3246 .execute(r#"greet() { echo "Hello, $1!" }"#)
3247 .await
3248 .expect("function definition failed");
3249
3250 let result = kernel
3252 .execute(r#"greet "World""#)
3253 .await
3254 .expect("function call failed");
3255
3256 assert!(result.ok(), "greet failed: {}", result.err);
3257 assert_eq!(result.out.trim(), "Hello, World!");
3258 }
3259
3260 #[tokio::test]
3261 async fn test_user_tool_positional_args() {
3262 let kernel = Kernel::transient().expect("failed to create kernel");
3263
3264 kernel
3266 .execute(r#"greet() { echo "Hi $1" }"#)
3267 .await
3268 .expect("function definition failed");
3269
3270 let result = kernel
3272 .execute(r#"greet "Amy""#)
3273 .await
3274 .expect("function call failed");
3275
3276 assert!(result.ok(), "greet failed: {}", result.err);
3277 assert_eq!(result.out.trim(), "Hi Amy");
3278 }
3279
3280 #[tokio::test]
3281 async fn test_function_shared_scope() {
3282 let kernel = Kernel::transient().expect("failed to create kernel");
3283
3284 kernel
3286 .execute(r#"SECRET="hidden""#)
3287 .await
3288 .expect("set failed");
3289
3290 kernel
3292 .execute(r#"access_parent() {
3293 echo "${SECRET}"
3294 SECRET="modified"
3295 }"#)
3296 .await
3297 .expect("function definition failed");
3298
3299 let result = kernel.execute("access_parent").await.expect("function call failed");
3301
3302 assert!(
3304 result.out.contains("hidden"),
3305 "Function should access parent scope, got: {}",
3306 result.out
3307 );
3308
3309 let secret = kernel.get_var("SECRET").await;
3311 assert_eq!(
3312 secret,
3313 Some(Value::String("modified".into())),
3314 "Function should modify parent scope"
3315 );
3316 }
3317
3318 #[tokio::test]
3319 async fn test_exec_builtin() {
3320 let kernel = Kernel::transient().expect("failed to create kernel");
3321 let result = kernel
3323 .execute(r#"exec command="/bin/echo" argv="hello world""#)
3324 .await
3325 .expect("exec failed");
3326
3327 assert!(result.ok(), "exec failed: {}", result.err);
3328 assert_eq!(result.out.trim(), "hello world");
3329 }
3330
3331 #[tokio::test]
3332 async fn test_while_false_never_runs() {
3333 let kernel = Kernel::transient().expect("failed to create kernel");
3334
3335 let result = kernel
3337 .execute(r#"
3338 while false; do
3339 echo "should not run"
3340 done
3341 "#)
3342 .await
3343 .expect("while false failed");
3344
3345 assert!(result.ok());
3346 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
3347 }
3348
3349 #[tokio::test]
3350 async fn test_while_string_comparison() {
3351 let kernel = Kernel::transient().expect("failed to create kernel");
3352
3353 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
3355
3356 let result = kernel
3359 .execute(r#"
3360 while [[ ${FLAG} == "go" ]]; do
3361 FLAG="stop"
3362 echo "running"
3363 done
3364 "#)
3365 .await
3366 .expect("while with string cmp failed");
3367
3368 assert!(result.ok());
3369 assert!(result.out.contains("running"), "should have run once: {}", result.out);
3370
3371 let flag = kernel.get_var("FLAG").await;
3373 assert_eq!(flag, Some(Value::String("stop".into())));
3374 }
3375
3376 #[tokio::test]
3377 async fn test_while_numeric_comparison() {
3378 let kernel = Kernel::transient().expect("failed to create kernel");
3379
3380 kernel.execute("N=5").await.expect("set failed");
3382
3383 let result = kernel
3385 .execute(r#"
3386 while [[ ${N} -gt 3 ]]; do
3387 N=3
3388 echo "N was greater"
3389 done
3390 "#)
3391 .await
3392 .expect("while with > failed");
3393
3394 assert!(result.ok());
3395 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
3396 }
3397
3398 #[tokio::test]
3399 async fn test_break_in_while_loop() {
3400 let kernel = Kernel::transient().expect("failed to create kernel");
3401
3402 let result = kernel
3403 .execute(r#"
3404 I=0
3405 while true; do
3406 I=1
3407 echo "before break"
3408 break
3409 echo "after break"
3410 done
3411 "#)
3412 .await
3413 .expect("while with break failed");
3414
3415 assert!(result.ok());
3416 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
3417 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
3418
3419 let i = kernel.get_var("I").await;
3421 assert_eq!(i, Some(Value::Int(1)));
3422 }
3423
3424 #[tokio::test]
3425 async fn test_continue_in_while_loop() {
3426 let kernel = Kernel::transient().expect("failed to create kernel");
3427
3428 let result = kernel
3433 .execute(r#"
3434 STATE="start"
3435 AFTER_CONTINUE="no"
3436 while [[ ${STATE} != "done" ]]; do
3437 if [[ ${STATE} == "start" ]]; then
3438 STATE="middle"
3439 continue
3440 AFTER_CONTINUE="yes"
3441 fi
3442 if [[ ${STATE} == "middle" ]]; then
3443 STATE="done"
3444 fi
3445 done
3446 "#)
3447 .await
3448 .expect("while with continue failed");
3449
3450 assert!(result.ok());
3451
3452 let state = kernel.get_var("STATE").await;
3454 assert_eq!(state, Some(Value::String("done".into())));
3455
3456 let after = kernel.get_var("AFTER_CONTINUE").await;
3458 assert_eq!(after, Some(Value::String("no".into())));
3459 }
3460
3461 #[tokio::test]
3462 async fn test_break_with_level() {
3463 let kernel = Kernel::transient().expect("failed to create kernel");
3464
3465 let result = kernel
3470 .execute(r#"
3471 OUTER=0
3472 while true; do
3473 OUTER=1
3474 for X in "1 2"; do
3475 break 2
3476 done
3477 OUTER=2
3478 done
3479 "#)
3480 .await
3481 .expect("nested break failed");
3482
3483 assert!(result.ok());
3484
3485 let outer = kernel.get_var("OUTER").await;
3487 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
3488 }
3489
3490 #[tokio::test]
3491 async fn test_return_from_tool() {
3492 let kernel = Kernel::transient().expect("failed to create kernel");
3493
3494 kernel
3496 .execute(r#"early_return() {
3497 if [[ $1 == 1 ]]; then
3498 return 42
3499 fi
3500 echo "not returned"
3501 }"#)
3502 .await
3503 .expect("function definition failed");
3504
3505 let result = kernel
3508 .execute("early_return 1")
3509 .await
3510 .expect("function call failed");
3511
3512 assert_eq!(result.code, 42);
3514 assert!(result.out.is_empty());
3516 }
3517
3518 #[tokio::test]
3519 async fn test_return_without_value() {
3520 let kernel = Kernel::transient().expect("failed to create kernel");
3521
3522 kernel
3524 .execute(r#"early_exit() {
3525 if [[ $1 == "stop" ]]; then
3526 return
3527 fi
3528 echo "continued"
3529 }"#)
3530 .await
3531 .expect("function definition failed");
3532
3533 let result = kernel
3535 .execute(r#"early_exit "stop""#)
3536 .await
3537 .expect("function call failed");
3538
3539 assert!(result.ok());
3540 assert!(result.out.is_empty() || result.out.trim().is_empty());
3541 }
3542
3543 #[tokio::test]
3544 async fn test_exit_stops_execution() {
3545 let kernel = Kernel::transient().expect("failed to create kernel");
3546
3547 kernel
3549 .execute(r#"
3550 BEFORE="yes"
3551 exit 0
3552 AFTER="yes"
3553 "#)
3554 .await
3555 .expect("execution failed");
3556
3557 let before = kernel.get_var("BEFORE").await;
3559 assert_eq!(before, Some(Value::String("yes".into())));
3560
3561 let after = kernel.get_var("AFTER").await;
3562 assert!(after.is_none(), "AFTER should not be set after exit");
3563 }
3564
3565 #[tokio::test]
3566 async fn test_exit_with_code() {
3567 let kernel = Kernel::transient().expect("failed to create kernel");
3568
3569 let result = kernel
3571 .execute("exit 42")
3572 .await
3573 .expect("exit failed");
3574
3575 assert_eq!(result.code, 42);
3576 assert!(result.out.is_empty(), "exit should not produce stdout");
3577 }
3578
3579 #[tokio::test]
3580 async fn test_set_e_stops_on_failure() {
3581 let kernel = Kernel::transient().expect("failed to create kernel");
3582
3583 kernel.execute("set -e").await.expect("set -e failed");
3585
3586 kernel
3588 .execute(r#"
3589 STEP1="done"
3590 false
3591 STEP2="done"
3592 "#)
3593 .await
3594 .expect("execution failed");
3595
3596 let step1 = kernel.get_var("STEP1").await;
3598 assert_eq!(step1, Some(Value::String("done".into())));
3599
3600 let step2 = kernel.get_var("STEP2").await;
3601 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
3602 }
3603
3604 #[tokio::test]
3605 async fn test_set_plus_e_disables_error_exit() {
3606 let kernel = Kernel::transient().expect("failed to create kernel");
3607
3608 kernel.execute("set -e").await.expect("set -e failed");
3610 kernel.execute("set +e").await.expect("set +e failed");
3611
3612 kernel
3614 .execute(r#"
3615 STEP1="done"
3616 false
3617 STEP2="done"
3618 "#)
3619 .await
3620 .expect("execution failed");
3621
3622 let step1 = kernel.get_var("STEP1").await;
3624 assert_eq!(step1, Some(Value::String("done".into())));
3625
3626 let step2 = kernel.get_var("STEP2").await;
3627 assert_eq!(step2, Some(Value::String("done".into())));
3628 }
3629
3630 #[tokio::test]
3631 async fn test_set_ignores_unknown_options() {
3632 let kernel = Kernel::transient().expect("failed to create kernel");
3633
3634 let result = kernel
3636 .execute("set -e -u -o pipefail")
3637 .await
3638 .expect("set with unknown options failed");
3639
3640 assert!(result.ok(), "set should succeed with unknown options");
3641
3642 kernel
3644 .execute(r#"
3645 BEFORE="yes"
3646 false
3647 AFTER="yes"
3648 "#)
3649 .await
3650 .ok();
3651
3652 let after = kernel.get_var("AFTER").await;
3653 assert!(after.is_none(), "-e should be enabled despite unknown options");
3654 }
3655
3656 #[tokio::test]
3657 async fn test_set_no_args_shows_settings() {
3658 let kernel = Kernel::transient().expect("failed to create kernel");
3659
3660 kernel.execute("set -e").await.expect("set -e failed");
3662
3663 let result = kernel.execute("set").await.expect("set failed");
3665
3666 assert!(result.ok());
3667 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
3668 }
3669
3670 #[tokio::test]
3671 async fn test_set_e_in_pipeline() {
3672 let kernel = Kernel::transient().expect("failed to create kernel");
3673
3674 kernel.execute("set -e").await.expect("set -e failed");
3675
3676 kernel
3678 .execute(r#"
3679 BEFORE="yes"
3680 false | cat
3681 AFTER="yes"
3682 "#)
3683 .await
3684 .ok();
3685
3686 let before = kernel.get_var("BEFORE").await;
3687 assert_eq!(before, Some(Value::String("yes".into())));
3688
3689 }
3694
3695 #[tokio::test]
3696 async fn test_set_e_with_and_chain() {
3697 let kernel = Kernel::transient().expect("failed to create kernel");
3698
3699 kernel.execute("set -e").await.expect("set -e failed");
3700
3701 kernel
3704 .execute(r#"
3705 RESULT="initial"
3706 false && RESULT="chained"
3707 RESULT="continued"
3708 "#)
3709 .await
3710 .ok();
3711
3712 let result = kernel.get_var("RESULT").await;
3715 assert!(result.is_some(), "RESULT should be set");
3718 }
3719
3720 #[tokio::test]
3721 async fn test_set_e_exits_in_for_loop() {
3722 let kernel = Kernel::transient().expect("failed to create kernel");
3723
3724 kernel.execute("set -e").await.expect("set -e failed");
3725
3726 kernel
3727 .execute(r#"
3728 REACHED="no"
3729 for x in 1 2 3; do
3730 false
3731 REACHED="yes"
3732 done
3733 "#)
3734 .await
3735 .ok();
3736
3737 let reached = kernel.get_var("REACHED").await;
3739 assert_eq!(reached, Some(Value::String("no".into())),
3740 "set -e should exit on failure in for loop body");
3741 }
3742
3743 #[tokio::test]
3744 async fn test_for_loop_continues_without_set_e() {
3745 let kernel = Kernel::transient().expect("failed to create kernel");
3746
3747 kernel
3749 .execute(r#"
3750 COUNT=0
3751 for x in 1 2 3; do
3752 false
3753 COUNT=$((COUNT + 1))
3754 done
3755 "#)
3756 .await
3757 .ok();
3758
3759 let count = kernel.get_var("COUNT").await;
3760 let count_val = match &count {
3762 Some(Value::Int(n)) => *n,
3763 Some(Value::String(s)) => s.parse().unwrap_or(-1),
3764 _ => -1,
3765 };
3766 assert_eq!(count_val, 3,
3767 "without set -e, loop should complete all iterations (got {:?})", count);
3768 }
3769
3770 #[tokio::test]
3775 async fn test_source_sets_variables() {
3776 let kernel = Kernel::transient().expect("failed to create kernel");
3777
3778 kernel
3780 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
3781 .await
3782 .expect("write failed");
3783
3784 let result = kernel
3786 .execute(r#"source "/test.kai""#)
3787 .await
3788 .expect("source failed");
3789
3790 assert!(result.ok(), "source should succeed");
3791
3792 let foo = kernel.get_var("FOO").await;
3794 assert_eq!(foo, Some(Value::String("bar".into())));
3795 }
3796
3797 #[tokio::test]
3798 async fn test_source_with_dot_alias() {
3799 let kernel = Kernel::transient().expect("failed to create kernel");
3800
3801 kernel
3803 .execute(r#"write "/vars.kai" 'X=42'"#)
3804 .await
3805 .expect("write failed");
3806
3807 let result = kernel
3809 .execute(r#". "/vars.kai""#)
3810 .await
3811 .expect(". failed");
3812
3813 assert!(result.ok(), ". should succeed");
3814
3815 let x = kernel.get_var("X").await;
3817 assert_eq!(x, Some(Value::Int(42)));
3818 }
3819
3820 #[tokio::test]
3821 async fn test_source_not_found() {
3822 let kernel = Kernel::transient().expect("failed to create kernel");
3823
3824 let result = kernel
3826 .execute(r#"source "/nonexistent.kai""#)
3827 .await
3828 .expect("source should not fail with error");
3829
3830 assert!(!result.ok(), "source of non-existent file should fail");
3831 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
3832 }
3833
3834 #[tokio::test]
3835 async fn test_source_missing_filename() {
3836 let kernel = Kernel::transient().expect("failed to create kernel");
3837
3838 let result = kernel
3840 .execute("source")
3841 .await
3842 .expect("source should not fail with error");
3843
3844 assert!(!result.ok(), "source without filename should fail");
3845 assert!(result.err.contains("missing filename"), "error should mention missing filename");
3846 }
3847
3848 #[tokio::test]
3849 async fn test_source_executes_multiple_statements() {
3850 let kernel = Kernel::transient().expect("failed to create kernel");
3851
3852 kernel
3854 .execute(r#"write "/multi.kai" 'A=1
3855B=2
3856C=3'"#)
3857 .await
3858 .expect("write failed");
3859
3860 kernel
3862 .execute(r#"source "/multi.kai""#)
3863 .await
3864 .expect("source failed");
3865
3866 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
3868 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
3869 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
3870 }
3871
3872 #[tokio::test]
3873 async fn test_source_can_define_functions() {
3874 let kernel = Kernel::transient().expect("failed to create kernel");
3875
3876 kernel
3878 .execute(r#"write "/functions.kai" 'greet() {
3879 echo "Hello, $1!"
3880}'"#)
3881 .await
3882 .expect("write failed");
3883
3884 kernel
3886 .execute(r#"source "/functions.kai""#)
3887 .await
3888 .expect("source failed");
3889
3890 let result = kernel
3892 .execute(r#"greet "World""#)
3893 .await
3894 .expect("greet failed");
3895
3896 assert!(result.ok());
3897 assert!(result.out.contains("Hello, World!"));
3898 }
3899
3900 #[tokio::test]
3901 async fn test_source_inherits_error_exit() {
3902 let kernel = Kernel::transient().expect("failed to create kernel");
3903
3904 kernel.execute("set -e").await.expect("set -e failed");
3906
3907 kernel
3909 .execute(r#"write "/fail.kai" 'BEFORE="yes"
3910false
3911AFTER="yes"'"#)
3912 .await
3913 .expect("write failed");
3914
3915 kernel
3917 .execute(r#"source "/fail.kai""#)
3918 .await
3919 .ok();
3920
3921 let before = kernel.get_var("BEFORE").await;
3923 assert_eq!(before, Some(Value::String("yes".into())));
3924
3925 }
3928
3929 #[tokio::test]
3934 async fn test_set_e_and_chain_left_fails() {
3935 let kernel = Kernel::transient().expect("failed to create kernel");
3937 kernel.execute("set -e").await.expect("set -e failed");
3938
3939 kernel
3940 .execute("false && echo hi; REACHED=1")
3941 .await
3942 .expect("execution failed");
3943
3944 let reached = kernel.get_var("REACHED").await;
3945 assert_eq!(
3946 reached,
3947 Some(Value::Int(1)),
3948 "set -e should not trigger on left side of &&"
3949 );
3950 }
3951
3952 #[tokio::test]
3953 async fn test_set_e_and_chain_right_fails() {
3954 let kernel = Kernel::transient().expect("failed to create kernel");
3956 kernel.execute("set -e").await.expect("set -e failed");
3957
3958 kernel
3959 .execute("true && false; REACHED=1")
3960 .await
3961 .expect("execution failed");
3962
3963 let reached = kernel.get_var("REACHED").await;
3964 assert!(
3965 reached.is_none(),
3966 "set -e should trigger when right side of && fails"
3967 );
3968 }
3969
3970 #[tokio::test]
3971 async fn test_set_e_or_chain_recovers() {
3972 let kernel = Kernel::transient().expect("failed to create kernel");
3974 kernel.execute("set -e").await.expect("set -e failed");
3975
3976 kernel
3977 .execute("false || echo recovered; REACHED=1")
3978 .await
3979 .expect("execution failed");
3980
3981 let reached = kernel.get_var("REACHED").await;
3982 assert_eq!(
3983 reached,
3984 Some(Value::Int(1)),
3985 "set -e should not trigger when || recovers the failure"
3986 );
3987 }
3988
3989 #[tokio::test]
3990 async fn test_set_e_or_chain_both_fail() {
3991 let kernel = Kernel::transient().expect("failed to create kernel");
3993 kernel.execute("set -e").await.expect("set -e failed");
3994
3995 kernel
3996 .execute("false || false; REACHED=1")
3997 .await
3998 .expect("execution failed");
3999
4000 let reached = kernel.get_var("REACHED").await;
4001 assert!(
4002 reached.is_none(),
4003 "set -e should trigger when || chain ultimately fails"
4004 );
4005 }
4006
4007 fn schedule_cancel(kernel: &Arc<Kernel>, delay: std::time::Duration) {
4014 let k = Arc::clone(kernel);
4015 std::thread::spawn(move || {
4016 std::thread::sleep(delay);
4017 k.cancel();
4018 });
4019 }
4020
4021 #[tokio::test]
4022 async fn test_cancel_interrupts_for_loop() {
4023 let kernel = Arc::new(Kernel::transient().expect("failed to create kernel"));
4024
4025 schedule_cancel(&kernel, std::time::Duration::from_millis(10));
4027
4028 let result = kernel
4029 .execute("for i in $(seq 1 100000); do X=$i; done")
4030 .await
4031 .expect("execute failed");
4032
4033 assert_eq!(result.code, 130, "cancelled execution should exit with code 130");
4034
4035 let x = kernel.get_var("X").await;
4037 if let Some(Value::Int(n)) = x {
4038 assert!(n < 100000, "loop should have been interrupted before finishing, got X={n}");
4039 }
4040 }
4041
4042 #[tokio::test]
4043 async fn test_cancel_interrupts_while_loop() {
4044 let kernel = Arc::new(Kernel::transient().expect("failed to create kernel"));
4045 kernel.execute("COUNT=0").await.expect("init failed");
4046
4047 schedule_cancel(&kernel, std::time::Duration::from_millis(10));
4048
4049 let result = kernel
4050 .execute("while true; do COUNT=$((COUNT + 1)); done")
4051 .await
4052 .expect("execute failed");
4053
4054 assert_eq!(result.code, 130);
4055
4056 let count = kernel.get_var("COUNT").await;
4057 if let Some(Value::Int(n)) = count {
4058 assert!(n > 0, "loop should have run at least once");
4059 }
4060 }
4061
4062 #[tokio::test]
4063 async fn test_reset_after_cancel() {
4064 let kernel = Kernel::transient().expect("failed to create kernel");
4066 kernel.cancel(); let result = kernel.execute("echo hello").await.expect("execute failed");
4069 assert!(result.ok(), "execute after cancel should succeed");
4070 assert_eq!(result.out.trim(), "hello");
4071 }
4072
4073 #[tokio::test]
4074 async fn test_cancel_interrupts_statement_sequence() {
4075 let kernel = Arc::new(Kernel::transient().expect("failed to create kernel"));
4076
4077 schedule_cancel(&kernel, std::time::Duration::from_millis(50));
4079
4080 let result = kernel
4081 .execute("STEP=1; sleep 5; STEP=2; sleep 5; STEP=3")
4082 .await
4083 .expect("execute failed");
4084
4085 assert_eq!(result.code, 130);
4086
4087 let step = kernel.get_var("STEP").await;
4089 assert_eq!(step, Some(Value::Int(1)), "cancel should stop before STEP=2");
4090 }
4091
4092 #[tokio::test]
4097 async fn test_case_simple_match() {
4098 let kernel = Kernel::transient().expect("failed to create kernel");
4099
4100 let result = kernel
4101 .execute(r#"
4102 case "hello" in
4103 hello) echo "matched hello" ;;
4104 world) echo "matched world" ;;
4105 esac
4106 "#)
4107 .await
4108 .expect("case failed");
4109
4110 assert!(result.ok());
4111 assert_eq!(result.out.trim(), "matched hello");
4112 }
4113
4114 #[tokio::test]
4115 async fn test_case_wildcard_match() {
4116 let kernel = Kernel::transient().expect("failed to create kernel");
4117
4118 let result = kernel
4119 .execute(r#"
4120 case "main.rs" in
4121 "*.py") echo "Python" ;;
4122 "*.rs") echo "Rust" ;;
4123 "*") echo "Unknown" ;;
4124 esac
4125 "#)
4126 .await
4127 .expect("case failed");
4128
4129 assert!(result.ok());
4130 assert_eq!(result.out.trim(), "Rust");
4131 }
4132
4133 #[tokio::test]
4134 async fn test_case_default_match() {
4135 let kernel = Kernel::transient().expect("failed to create kernel");
4136
4137 let result = kernel
4138 .execute(r#"
4139 case "unknown.xyz" in
4140 "*.py") echo "Python" ;;
4141 "*.rs") echo "Rust" ;;
4142 "*") echo "Default" ;;
4143 esac
4144 "#)
4145 .await
4146 .expect("case failed");
4147
4148 assert!(result.ok());
4149 assert_eq!(result.out.trim(), "Default");
4150 }
4151
4152 #[tokio::test]
4153 async fn test_case_no_match() {
4154 let kernel = Kernel::transient().expect("failed to create kernel");
4155
4156 let result = kernel
4158 .execute(r#"
4159 case "nope" in
4160 "yes") echo "yes" ;;
4161 "no") echo "no" ;;
4162 esac
4163 "#)
4164 .await
4165 .expect("case failed");
4166
4167 assert!(result.ok());
4168 assert!(result.out.is_empty(), "no match should produce empty output");
4169 }
4170
4171 #[tokio::test]
4172 async fn test_case_with_variable() {
4173 let kernel = Kernel::transient().expect("failed to create kernel");
4174
4175 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
4176
4177 let result = kernel
4178 .execute(r#"
4179 case ${LANG} in
4180 python) echo "snake" ;;
4181 rust) echo "crab" ;;
4182 go) echo "gopher" ;;
4183 esac
4184 "#)
4185 .await
4186 .expect("case failed");
4187
4188 assert!(result.ok());
4189 assert_eq!(result.out.trim(), "crab");
4190 }
4191
4192 #[tokio::test]
4193 async fn test_case_multiple_patterns() {
4194 let kernel = Kernel::transient().expect("failed to create kernel");
4195
4196 let result = kernel
4197 .execute(r#"
4198 case "yes" in
4199 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
4200 "n"|"no"|"N"|"NO") echo "negative" ;;
4201 esac
4202 "#)
4203 .await
4204 .expect("case failed");
4205
4206 assert!(result.ok());
4207 assert_eq!(result.out.trim(), "affirmative");
4208 }
4209
4210 #[tokio::test]
4211 async fn test_case_glob_question_mark() {
4212 let kernel = Kernel::transient().expect("failed to create kernel");
4213
4214 let result = kernel
4215 .execute(r#"
4216 case "test1" in
4217 "test?") echo "matched test?" ;;
4218 "*") echo "default" ;;
4219 esac
4220 "#)
4221 .await
4222 .expect("case failed");
4223
4224 assert!(result.ok());
4225 assert_eq!(result.out.trim(), "matched test?");
4226 }
4227
4228 #[tokio::test]
4229 async fn test_case_char_class() {
4230 let kernel = Kernel::transient().expect("failed to create kernel");
4231
4232 let result = kernel
4233 .execute(r#"
4234 case "Yes" in
4235 "[Yy]*") echo "yes-like" ;;
4236 "[Nn]*") echo "no-like" ;;
4237 esac
4238 "#)
4239 .await
4240 .expect("case failed");
4241
4242 assert!(result.ok());
4243 assert_eq!(result.out.trim(), "yes-like");
4244 }
4245
4246 #[tokio::test]
4251 async fn test_cat_from_pipeline() {
4252 let kernel = Kernel::transient().expect("failed to create kernel");
4253
4254 let result = kernel
4255 .execute(r#"echo "piped text" | cat"#)
4256 .await
4257 .expect("cat pipeline failed");
4258
4259 assert!(result.ok(), "cat failed: {}", result.err);
4260 assert_eq!(result.out.trim(), "piped text");
4261 }
4262
4263 #[tokio::test]
4264 async fn test_cat_from_pipeline_multiline() {
4265 let kernel = Kernel::transient().expect("failed to create kernel");
4266
4267 let result = kernel
4268 .execute(r#"echo "line1\nline2" | cat -n"#)
4269 .await
4270 .expect("cat pipeline failed");
4271
4272 assert!(result.ok(), "cat failed: {}", result.err);
4273 assert!(result.out.contains("1\t"), "output: {}", result.out);
4274 }
4275
4276 #[tokio::test]
4281 async fn test_heredoc_basic() {
4282 let kernel = Kernel::transient().expect("failed to create kernel");
4283
4284 let result = kernel
4285 .execute("cat <<EOF\nhello\nEOF")
4286 .await
4287 .expect("heredoc failed");
4288
4289 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
4290 assert_eq!(result.out.trim(), "hello");
4291 }
4292
4293 #[tokio::test]
4294 async fn test_arithmetic_in_string() {
4295 let kernel = Kernel::transient().expect("failed to create kernel");
4296
4297 let result = kernel
4298 .execute(r#"echo "result: $((1 + 2))""#)
4299 .await
4300 .expect("arithmetic in string failed");
4301
4302 assert!(result.ok(), "echo failed: {}", result.err);
4303 assert_eq!(result.out.trim(), "result: 3");
4304 }
4305
4306 #[tokio::test]
4307 async fn test_heredoc_multiline() {
4308 let kernel = Kernel::transient().expect("failed to create kernel");
4309
4310 let result = kernel
4311 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
4312 .await
4313 .expect("heredoc failed");
4314
4315 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
4316 assert!(result.out.contains("line1"), "output: {}", result.out);
4317 assert!(result.out.contains("line2"), "output: {}", result.out);
4318 assert!(result.out.contains("line3"), "output: {}", result.out);
4319 }
4320
4321 #[tokio::test]
4322 async fn test_heredoc_variable_expansion() {
4323 let kernel = Kernel::transient().expect("failed to create kernel");
4325
4326 kernel.execute("GREETING=hello").await.expect("set var");
4327
4328 let result = kernel
4329 .execute("cat <<EOF\n$GREETING world\nEOF")
4330 .await
4331 .expect("heredoc expansion failed");
4332
4333 assert!(result.ok(), "heredoc expansion failed: {}", result.err);
4334 assert_eq!(result.out.trim(), "hello world");
4335 }
4336
4337 #[tokio::test]
4338 async fn test_heredoc_quoted_no_expansion() {
4339 let kernel = Kernel::transient().expect("failed to create kernel");
4341
4342 kernel.execute("GREETING=hello").await.expect("set var");
4343
4344 let result = kernel
4345 .execute("cat <<'EOF'\n$GREETING world\nEOF")
4346 .await
4347 .expect("quoted heredoc failed");
4348
4349 assert!(result.ok(), "quoted heredoc failed: {}", result.err);
4350 assert_eq!(result.out.trim(), "$GREETING world");
4351 }
4352
4353 #[tokio::test]
4354 async fn test_heredoc_default_value_expansion() {
4355 let kernel = Kernel::transient().expect("failed to create kernel");
4357
4358 let result = kernel
4359 .execute("cat <<EOF\n${UNSET:-fallback}\nEOF")
4360 .await
4361 .expect("heredoc default expansion failed");
4362
4363 assert!(result.ok(), "heredoc default expansion failed: {}", result.err);
4364 assert_eq!(result.out.trim(), "fallback");
4365 }
4366
4367 #[tokio::test]
4372 async fn test_read_from_pipeline() {
4373 let kernel = Kernel::transient().expect("failed to create kernel");
4374
4375 let result = kernel
4377 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
4378 .await
4379 .expect("read pipeline failed");
4380
4381 assert!(result.ok(), "read failed: {}", result.err);
4382 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
4383 }
4384
4385 #[tokio::test]
4386 async fn test_read_multiple_vars_from_pipeline() {
4387 let kernel = Kernel::transient().expect("failed to create kernel");
4388
4389 let result = kernel
4390 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
4391 .await
4392 .expect("read pipeline failed");
4393
4394 assert!(result.ok(), "read failed: {}", result.err);
4395 assert!(result.out.contains("John is 42"), "output: {}", result.out);
4396 }
4397
4398 #[tokio::test]
4403 async fn test_posix_function_with_positional_params() {
4404 let kernel = Kernel::transient().expect("failed to create kernel");
4405
4406 kernel
4408 .execute(r#"greet() { echo "Hello, $1!" }"#)
4409 .await
4410 .expect("function definition failed");
4411
4412 let result = kernel
4414 .execute(r#"greet "Amy""#)
4415 .await
4416 .expect("function call failed");
4417
4418 assert!(result.ok(), "greet failed: {}", result.err);
4419 assert_eq!(result.out.trim(), "Hello, Amy!");
4420 }
4421
4422 #[tokio::test]
4423 async fn test_posix_function_multiple_args() {
4424 let kernel = Kernel::transient().expect("failed to create kernel");
4425
4426 kernel
4428 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
4429 .await
4430 .expect("function definition failed");
4431
4432 let result = kernel
4434 .execute(r#"add_greeting "Hello" "World""#)
4435 .await
4436 .expect("function call failed");
4437
4438 assert!(result.ok(), "function failed: {}", result.err);
4439 assert_eq!(result.out.trim(), "Hello World!");
4440 }
4441
4442 #[tokio::test]
4443 async fn test_bash_function_with_positional_params() {
4444 let kernel = Kernel::transient().expect("failed to create kernel");
4445
4446 kernel
4448 .execute(r#"function greet { echo "Hi $1" }"#)
4449 .await
4450 .expect("function definition failed");
4451
4452 let result = kernel
4454 .execute(r#"greet "Bob""#)
4455 .await
4456 .expect("function call failed");
4457
4458 assert!(result.ok(), "greet failed: {}", result.err);
4459 assert_eq!(result.out.trim(), "Hi Bob");
4460 }
4461
4462 #[tokio::test]
4463 async fn test_shell_function_with_all_args() {
4464 let kernel = Kernel::transient().expect("failed to create kernel");
4465
4466 kernel
4468 .execute(r#"echo_all() { echo "args: $@" }"#)
4469 .await
4470 .expect("function definition failed");
4471
4472 let result = kernel
4474 .execute(r#"echo_all "a" "b" "c""#)
4475 .await
4476 .expect("function call failed");
4477
4478 assert!(result.ok(), "function failed: {}", result.err);
4479 assert_eq!(result.out.trim(), "args: a b c");
4480 }
4481
4482 #[tokio::test]
4483 async fn test_shell_function_with_arg_count() {
4484 let kernel = Kernel::transient().expect("failed to create kernel");
4485
4486 kernel
4488 .execute(r#"count_args() { echo "count: $#" }"#)
4489 .await
4490 .expect("function definition failed");
4491
4492 let result = kernel
4494 .execute(r#"count_args "x" "y" "z""#)
4495 .await
4496 .expect("function call failed");
4497
4498 assert!(result.ok(), "function failed: {}", result.err);
4499 assert_eq!(result.out.trim(), "count: 3");
4500 }
4501
4502 #[tokio::test]
4503 async fn test_shell_function_shared_scope() {
4504 let kernel = Kernel::transient().expect("failed to create kernel");
4505
4506 kernel
4508 .execute(r#"PARENT_VAR="visible""#)
4509 .await
4510 .expect("set failed");
4511
4512 kernel
4514 .execute(r#"modify_parent() {
4515 echo "saw: ${PARENT_VAR}"
4516 PARENT_VAR="changed by function"
4517 }"#)
4518 .await
4519 .expect("function definition failed");
4520
4521 let result = kernel.execute("modify_parent").await.expect("function failed");
4523
4524 assert!(
4525 result.out.contains("visible"),
4526 "Shell function should access parent scope, got: {}",
4527 result.out
4528 );
4529
4530 let var = kernel.get_var("PARENT_VAR").await;
4532 assert_eq!(
4533 var,
4534 Some(Value::String("changed by function".into())),
4535 "Shell function should modify parent scope"
4536 );
4537 }
4538
4539 #[tokio::test]
4544 async fn test_script_execution_from_path() {
4545 let kernel = Kernel::transient().expect("failed to create kernel");
4546
4547 kernel.execute(r#"mkdir "/bin""#).await.ok();
4549 kernel
4550 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
4551 .await
4552 .expect("write script failed");
4553
4554 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
4556
4557 let result = kernel
4559 .execute("hello")
4560 .await
4561 .expect("script execution failed");
4562
4563 assert!(result.ok(), "script failed: {}", result.err);
4564 assert_eq!(result.out.trim(), "Hello from script!");
4565 }
4566
4567 #[tokio::test]
4568 async fn test_script_with_args() {
4569 let kernel = Kernel::transient().expect("failed to create kernel");
4570
4571 kernel.execute(r#"mkdir "/bin""#).await.ok();
4573 kernel
4574 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
4575 .await
4576 .expect("write script failed");
4577
4578 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
4580
4581 let result = kernel
4583 .execute(r#"greet "World""#)
4584 .await
4585 .expect("script execution failed");
4586
4587 assert!(result.ok(), "script failed: {}", result.err);
4588 assert_eq!(result.out.trim(), "Hello, World!");
4589 }
4590
4591 #[tokio::test]
4592 async fn test_script_not_found() {
4593 let kernel = Kernel::transient().expect("failed to create kernel");
4594
4595 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
4597
4598 let result = kernel
4600 .execute("noscript")
4601 .await
4602 .expect("execution failed");
4603
4604 assert!(!result.ok(), "should fail with command not found");
4605 assert_eq!(result.code, 127);
4606 assert!(result.err.contains("command not found"));
4607 }
4608
4609 #[tokio::test]
4610 async fn test_script_path_search_order() {
4611 let kernel = Kernel::transient().expect("failed to create kernel");
4612
4613 kernel.execute(r#"mkdir "/first""#).await.ok();
4616 kernel.execute(r#"mkdir "/second""#).await.ok();
4617 kernel
4618 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
4619 .await
4620 .expect("write failed");
4621 kernel
4622 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
4623 .await
4624 .expect("write failed");
4625
4626 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
4628
4629 let result = kernel
4631 .execute("myscript")
4632 .await
4633 .expect("script execution failed");
4634
4635 assert!(result.ok(), "script failed: {}", result.err);
4636 assert_eq!(result.out.trim(), "from first");
4637 }
4638
4639 #[tokio::test]
4644 async fn test_last_exit_code_success() {
4645 let kernel = Kernel::transient().expect("failed to create kernel");
4646
4647 let result = kernel.execute("true; echo $?").await.expect("execution failed");
4649 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
4650 }
4651
4652 #[tokio::test]
4653 async fn test_last_exit_code_failure() {
4654 let kernel = Kernel::transient().expect("failed to create kernel");
4655
4656 let result = kernel.execute("false; echo $?").await.expect("execution failed");
4658 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
4659 }
4660
4661 #[tokio::test]
4662 async fn test_current_pid() {
4663 let kernel = Kernel::transient().expect("failed to create kernel");
4664
4665 let result = kernel.execute("echo $$").await.expect("execution failed");
4666 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
4668 assert!(pid > 0, "PID should be positive");
4669 }
4670
4671 #[tokio::test]
4672 async fn test_unset_variable_expands_to_empty() {
4673 let kernel = Kernel::transient().expect("failed to create kernel");
4674
4675 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
4677 assert_eq!(result.out.trim(), "prefix::suffix");
4678 }
4679
4680 #[tokio::test]
4681 async fn test_eq_ne_operators() {
4682 let kernel = Kernel::transient().expect("failed to create kernel");
4683
4684 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
4686 assert_eq!(result.out.trim(), "eq works");
4687
4688 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
4690 assert_eq!(result.out.trim(), "ne works");
4691
4692 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
4694 assert_eq!(result.out.trim(), "correct");
4695 }
4696
4697 #[tokio::test]
4698 async fn test_escaped_dollar_in_string() {
4699 let kernel = Kernel::transient().expect("failed to create kernel");
4700
4701 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
4703 assert_eq!(result.out.trim(), "$100");
4704 }
4705
4706 #[tokio::test]
4707 async fn test_special_vars_in_interpolation() {
4708 let kernel = Kernel::transient().expect("failed to create kernel");
4709
4710 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
4712 assert_eq!(result.out.trim(), "exit: 0");
4713
4714 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
4716 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
4717 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
4718 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
4719 }
4720
4721 #[tokio::test]
4726 async fn test_command_subst_assignment() {
4727 let kernel = Kernel::transient().expect("failed to create kernel");
4728
4729 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
4731 assert_eq!(result.out.trim(), "hello");
4732 }
4733
4734 #[tokio::test]
4735 async fn test_command_subst_with_args() {
4736 let kernel = Kernel::transient().expect("failed to create kernel");
4737
4738 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
4740 assert_eq!(result.out.trim(), "a b c");
4741 }
4742
4743 #[tokio::test]
4744 async fn test_command_subst_nested_vars() {
4745 let kernel = Kernel::transient().expect("failed to create kernel");
4746
4747 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
4749 assert_eq!(result.out.trim(), "hello world");
4750 }
4751
4752 #[tokio::test]
4753 async fn test_background_job_basic() {
4754 use std::time::Duration;
4755
4756 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
4757
4758 let result = kernel.execute("echo hello &").await.expect("execution failed");
4760 assert!(result.ok(), "background command should succeed: {}", result.err);
4761 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
4762
4763 tokio::time::sleep(Duration::from_millis(100)).await;
4765
4766 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
4768 assert!(status.ok(), "status should succeed: {}", status.err);
4769 assert!(
4770 status.out.contains("done:") || status.out.contains("running"),
4771 "should have valid status: {}",
4772 status.out
4773 );
4774
4775 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
4777 assert!(stdout.ok());
4778 assert!(stdout.out.contains("hello"));
4779 }
4780
4781 #[tokio::test]
4782 async fn test_heredoc_piped_to_command() {
4783 let kernel = Kernel::transient().expect("kernel");
4785 let result = kernel.execute("cat <<EOF | cat\nhello world\nEOF").await.expect("exec");
4786 assert!(result.ok(), "heredoc | cat failed: {}", result.err);
4787 assert_eq!(result.out.trim(), "hello world");
4788 }
4789
4790 #[tokio::test]
4791 async fn test_for_loop_glob_iterates() {
4792 let kernel = Kernel::transient().expect("kernel");
4794 let dir = format!("/tmp/kaish_test_glob_{}", std::process::id());
4795 kernel.execute(&format!("mkdir -p {dir}")).await.unwrap();
4796 kernel.execute(&format!("echo a > {dir}/a.txt")).await.unwrap();
4797 kernel.execute(&format!("echo b > {dir}/b.txt")).await.unwrap();
4798 let result = kernel.execute(&format!(r#"
4799 N=0
4800 for F in $(glob "{dir}/*.txt"); do
4801 N=$((N + 1))
4802 done
4803 echo $N
4804 "#)).await.unwrap();
4805 assert!(result.ok(), "for glob failed: {}", result.err);
4806 assert_eq!(result.out.trim(), "2", "Should iterate 2 files, got: {}", result.out);
4807 kernel.execute(&format!("rm {dir}/a.txt")).await.unwrap();
4808 kernel.execute(&format!("rm {dir}/b.txt")).await.unwrap();
4809 }
4810
4811 #[tokio::test]
4812 async fn test_command_subst_echo_not_iterable() {
4813 let kernel = Kernel::transient().expect("kernel");
4815 let result = kernel.execute(r#"
4816 N=0
4817 for X in $(echo "a b c"); do N=$((N + 1)); done
4818 echo $N
4819 "#).await.unwrap();
4820 assert!(result.ok());
4821 assert_eq!(result.out.trim(), "1", "echo should be one item: {}", result.out);
4822 }
4823
4824 #[test]
4827 fn test_accumulate_no_double_newlines() {
4828 let mut acc = ExecResult::success("line1\n");
4830 let new = ExecResult::success("line2\n");
4831 accumulate_result(&mut acc, &new);
4832 assert_eq!(acc.out, "line1\nline2\n");
4833 assert!(!acc.out.contains("\n\n"), "should not have double newlines: {:?}", acc.out);
4834 }
4835
4836 #[test]
4837 fn test_accumulate_adds_separator_when_needed() {
4838 let mut acc = ExecResult::success("line1");
4840 let new = ExecResult::success("line2");
4841 accumulate_result(&mut acc, &new);
4842 assert_eq!(acc.out, "line1\nline2");
4843 }
4844
4845 #[test]
4846 fn test_accumulate_empty_into_nonempty() {
4847 let mut acc = ExecResult::success("");
4848 let new = ExecResult::success("hello\n");
4849 accumulate_result(&mut acc, &new);
4850 assert_eq!(acc.out, "hello\n");
4851 }
4852
4853 #[test]
4854 fn test_accumulate_nonempty_into_empty() {
4855 let mut acc = ExecResult::success("hello\n");
4856 let new = ExecResult::success("");
4857 accumulate_result(&mut acc, &new);
4858 assert_eq!(acc.out, "hello\n");
4859 }
4860
4861 #[test]
4862 fn test_accumulate_stderr_no_double_newlines() {
4863 let mut acc = ExecResult::failure(1, "err1\n");
4864 let new = ExecResult::failure(1, "err2\n");
4865 accumulate_result(&mut acc, &new);
4866 assert!(!acc.err.contains("\n\n"), "stderr should not have double newlines: {:?}", acc.err);
4867 }
4868
4869 #[tokio::test]
4870 async fn test_multiple_echo_no_blank_lines() {
4871 let kernel = Kernel::transient().expect("kernel");
4872 let result = kernel
4873 .execute("echo one\necho two\necho three")
4874 .await
4875 .expect("execution failed");
4876 assert!(result.ok());
4877 assert_eq!(result.out, "one\ntwo\nthree\n");
4878 }
4879
4880 #[tokio::test]
4881 async fn test_for_loop_no_blank_lines() {
4882 let kernel = Kernel::transient().expect("kernel");
4883 let result = kernel
4884 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
4885 .await
4886 .expect("execution failed");
4887 assert!(result.ok());
4888 assert_eq!(result.out, "item: a\nitem: b\nitem: c\n");
4889 }
4890
4891 #[tokio::test]
4892 async fn test_for_command_subst_no_blank_lines() {
4893 let kernel = Kernel::transient().expect("kernel");
4894 let result = kernel
4895 .execute(r#"for N in $(seq 1 3); do echo "n=${N}"; done"#)
4896 .await
4897 .expect("execution failed");
4898 assert!(result.ok());
4899 assert_eq!(result.out, "n=1\nn=2\nn=3\n");
4900 }
4901
4902}