1use std::collections::HashMap;
26use std::path::PathBuf;
27use std::sync::Arc;
28
29use anyhow::{Context, Result};
30use tokio::sync::RwLock;
31
32use crate::ast::{Arg, Expr, Stmt, StringPart, ToolDef, Value, BinaryOp};
33use crate::backend::{BackendError, KernelBackend};
34use kaish_glob::glob_match;
35use crate::interpreter::{apply_output_format, eval_expr, expand_tilde, json_to_value, value_to_string, ControlFlow, ExecResult, Scope};
36use crate::parser::parse;
37use crate::scheduler::{drain_to_stream, BoundedStream, JobManager, PipelineRunner, DEFAULT_STREAM_MAX_SIZE};
38use crate::tools::{extract_output_format, register_builtins, resolve_in_path, ExecContext, ToolArgs, ToolRegistry};
39use crate::validator::{Severity, Validator};
40use crate::vfs::{JobFs, LocalFs, MemoryFs, VfsRouter};
41
42#[derive(Debug, Clone)]
49pub enum VfsMountMode {
50 Passthrough,
60
61 Sandboxed {
74 root: Option<PathBuf>,
77 },
78
79 NoLocal,
90}
91
92impl Default for VfsMountMode {
93 fn default() -> Self {
94 VfsMountMode::Sandboxed { root: None }
95 }
96}
97
98#[derive(Debug, Clone)]
100pub struct KernelConfig {
101 pub name: String,
103
104 pub vfs_mode: VfsMountMode,
106
107 pub cwd: PathBuf,
109
110 pub skip_validation: bool,
116
117 pub interactive: bool,
122}
123
124fn default_sandbox_root() -> PathBuf {
126 std::env::var("HOME")
127 .map(PathBuf::from)
128 .unwrap_or_else(|_| PathBuf::from("/"))
129}
130
131impl Default for KernelConfig {
132 fn default() -> Self {
133 let home = default_sandbox_root();
134 Self {
135 name: "default".to_string(),
136 vfs_mode: VfsMountMode::Sandboxed { root: None },
137 cwd: home,
138 skip_validation: false,
139 interactive: false,
140 }
141 }
142}
143
144impl KernelConfig {
145 pub fn transient() -> Self {
147 let home = default_sandbox_root();
148 Self {
149 name: "transient".to_string(),
150 vfs_mode: VfsMountMode::Sandboxed { root: None },
151 cwd: home,
152 skip_validation: false,
153 interactive: false,
154 }
155 }
156
157 pub fn named(name: &str) -> Self {
159 let home = default_sandbox_root();
160 Self {
161 name: name.to_string(),
162 vfs_mode: VfsMountMode::Sandboxed { root: None },
163 cwd: home,
164 skip_validation: false,
165 interactive: false,
166 }
167 }
168
169 pub fn repl() -> Self {
174 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
175 Self {
176 name: "repl".to_string(),
177 vfs_mode: VfsMountMode::Passthrough,
178 cwd,
179 skip_validation: false,
180 interactive: false,
181 }
182 }
183
184 pub fn mcp() -> Self {
189 let home = default_sandbox_root();
190 Self {
191 name: "mcp".to_string(),
192 vfs_mode: VfsMountMode::Sandboxed { root: None },
193 cwd: home,
194 skip_validation: false,
195 interactive: false,
196 }
197 }
198
199 pub fn mcp_with_root(root: PathBuf) -> Self {
203 Self {
204 name: "mcp".to_string(),
205 vfs_mode: VfsMountMode::Sandboxed { root: Some(root.clone()) },
206 cwd: root,
207 skip_validation: false,
208 interactive: false,
209 }
210 }
211
212 pub fn isolated() -> Self {
216 Self {
217 name: "isolated".to_string(),
218 vfs_mode: VfsMountMode::NoLocal,
219 cwd: PathBuf::from("/"),
220 skip_validation: false,
221 interactive: false,
222 }
223 }
224
225 pub fn with_vfs_mode(mut self, mode: VfsMountMode) -> Self {
227 self.vfs_mode = mode;
228 self
229 }
230
231 pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
233 self.cwd = cwd;
234 self
235 }
236
237 pub fn with_skip_validation(mut self, skip: bool) -> Self {
239 self.skip_validation = skip;
240 self
241 }
242
243 pub fn with_interactive(mut self, interactive: bool) -> Self {
245 self.interactive = interactive;
246 self
247 }
248}
249
250pub struct Kernel {
255 name: String,
257 scope: RwLock<Scope>,
259 tools: Arc<ToolRegistry>,
261 user_tools: RwLock<HashMap<String, ToolDef>>,
263 vfs: Arc<VfsRouter>,
265 jobs: Arc<JobManager>,
267 runner: PipelineRunner,
269 exec_ctx: RwLock<ExecContext>,
271 skip_validation: bool,
273 interactive: bool,
275}
276
277impl Kernel {
278 pub fn new(config: KernelConfig) -> Result<Self> {
280 let mut vfs = Self::setup_vfs(&config);
281 let jobs = Arc::new(JobManager::new());
282
283 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
285
286 let vfs = Arc::new(vfs);
287
288 let mut tools = ToolRegistry::new();
290 register_builtins(&mut tools);
291 let tools = Arc::new(tools);
292
293 let runner = PipelineRunner::new(tools.clone());
295
296 let scope = Scope::new();
297 let cwd = config.cwd;
298
299 let mut exec_ctx = ExecContext::with_vfs_and_tools(vfs.clone(), tools.clone());
301 exec_ctx.set_cwd(cwd);
302 exec_ctx.set_job_manager(jobs.clone());
303 exec_ctx.set_tool_schemas(tools.schemas());
304
305 Ok(Self {
306 name: config.name,
307 scope: RwLock::new(scope),
308 tools,
309 user_tools: RwLock::new(HashMap::new()),
310 vfs,
311 jobs,
312 runner,
313 exec_ctx: RwLock::new(exec_ctx),
314 skip_validation: config.skip_validation,
315 interactive: config.interactive,
316 })
317 }
318
319 fn setup_vfs(config: &KernelConfig) -> VfsRouter {
321 let mut vfs = VfsRouter::new();
322
323 match &config.vfs_mode {
324 VfsMountMode::Passthrough => {
325 vfs.mount("/", LocalFs::new(PathBuf::from("/")));
327 vfs.mount("/v", MemoryFs::new());
329 vfs.mount("/scratch", MemoryFs::new());
330 }
331 VfsMountMode::Sandboxed { root } => {
332 vfs.mount("/", MemoryFs::new());
334 vfs.mount("/v", MemoryFs::new());
335 vfs.mount("/scratch", MemoryFs::new());
336
337 vfs.mount("/tmp", LocalFs::new(PathBuf::from("/tmp")));
339
340 let local_root = root.clone().unwrap_or_else(|| {
342 std::env::var("HOME")
343 .map(PathBuf::from)
344 .unwrap_or_else(|_| PathBuf::from("/"))
345 });
346
347 let mount_point = local_root.to_string_lossy().to_string();
351 vfs.mount(&mount_point, LocalFs::new(local_root));
352 }
353 VfsMountMode::NoLocal => {
354 vfs.mount("/", MemoryFs::new());
356 vfs.mount("/tmp", MemoryFs::new());
357 vfs.mount("/v", MemoryFs::new());
358 vfs.mount("/scratch", MemoryFs::new());
359 }
360 }
361
362 vfs
363 }
364
365 pub fn transient() -> Result<Self> {
367 Self::new(KernelConfig::transient())
368 }
369
370 pub fn with_backend(backend: Arc<dyn KernelBackend>, config: KernelConfig) -> Result<Self> {
379 let mut vfs = Self::setup_vfs(&config);
381 let jobs = Arc::new(JobManager::new());
382
383 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
385
386 let vfs = Arc::new(vfs);
387
388 let mut tools = ToolRegistry::new();
390 register_builtins(&mut tools);
391 let tools = Arc::new(tools);
392
393 let runner = PipelineRunner::new(tools.clone());
395
396 let scope = Scope::new();
397 let cwd = config.cwd;
398
399 let mut exec_ctx = ExecContext::with_backend(backend);
401 exec_ctx.set_cwd(cwd);
402 exec_ctx.set_job_manager(jobs.clone());
403 exec_ctx.set_tool_schemas(tools.schemas());
404 exec_ctx.set_tools(tools.clone());
405
406 Ok(Self {
407 name: config.name,
408 scope: RwLock::new(scope),
409 tools,
410 user_tools: RwLock::new(HashMap::new()),
411 vfs,
412 jobs,
413 runner,
414 exec_ctx: RwLock::new(exec_ctx),
415 skip_validation: config.skip_validation,
416 interactive: config.interactive,
417 })
418 }
419
420 pub fn with_backend_and_virtual_paths(
440 backend: Arc<dyn KernelBackend>,
441 config: KernelConfig,
442 ) -> Result<Self> {
443 use crate::backend::VirtualOverlayBackend;
444
445 let mut vfs = VfsRouter::new();
447 let jobs = Arc::new(JobManager::new());
448
449 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
451 vfs.mount("/v/blobs", MemoryFs::new());
453 vfs.mount("/v/scratch", MemoryFs::new());
455
456 let vfs = Arc::new(vfs);
457
458 let overlay: Arc<dyn KernelBackend> = Arc::new(VirtualOverlayBackend::new(backend, vfs.clone()));
460
461 let mut tools = ToolRegistry::new();
463 register_builtins(&mut tools);
464 let tools = Arc::new(tools);
465
466 let runner = PipelineRunner::new(tools.clone());
468
469 let scope = Scope::new();
470 let cwd = config.cwd;
471
472 let mut exec_ctx = ExecContext::with_backend(overlay);
474 exec_ctx.set_cwd(cwd);
475 exec_ctx.set_job_manager(jobs.clone());
476 exec_ctx.set_tool_schemas(tools.schemas());
477 exec_ctx.set_tools(tools.clone());
478
479 Ok(Self {
480 name: config.name,
481 scope: RwLock::new(scope),
482 tools,
483 user_tools: RwLock::new(HashMap::new()),
484 vfs,
485 jobs,
486 runner,
487 exec_ctx: RwLock::new(exec_ctx),
488 skip_validation: config.skip_validation,
489 interactive: config.interactive,
490 })
491 }
492
493 pub fn name(&self) -> &str {
495 &self.name
496 }
497
498 pub async fn execute(&self, input: &str) -> Result<ExecResult> {
502 self.execute_streaming(input, &mut |_| {}).await
503 }
504
505 pub async fn execute_streaming(
514 &self,
515 input: &str,
516 on_output: &mut dyn FnMut(&ExecResult),
517 ) -> Result<ExecResult> {
518 let program = parse(input).map_err(|errors| {
519 let msg = errors
520 .iter()
521 .map(|e| e.to_string())
522 .collect::<Vec<_>>()
523 .join("; ");
524 anyhow::anyhow!("parse error: {}", msg)
525 })?;
526
527 if !self.skip_validation {
529 let user_tools = self.user_tools.read().await;
530 let validator = Validator::new(&self.tools, &user_tools);
531 let issues = validator.validate(&program);
532
533 let errors: Vec<_> = issues
535 .iter()
536 .filter(|i| i.severity == Severity::Error)
537 .collect();
538
539 if !errors.is_empty() {
540 let error_msg = errors
541 .iter()
542 .map(|e| e.format(input))
543 .collect::<Vec<_>>()
544 .join("\n");
545 return Err(anyhow::anyhow!("validation failed:\n{}", error_msg));
546 }
547
548 for warning in issues.iter().filter(|i| i.severity == Severity::Warning) {
550 tracing::trace!("validation: {}", warning.format(input));
551 }
552 }
553
554 let mut result = ExecResult::success("");
555
556 for stmt in program.statements {
557 if matches!(stmt, Stmt::Empty) {
558 continue;
559 }
560 let flow = self.execute_stmt_flow(&stmt).await?;
561 match flow {
562 ControlFlow::Normal(r) => {
563 on_output(&r);
564 accumulate_result(&mut result, &r);
565 }
566 ControlFlow::Exit { code } => {
567 let exit_result = ExecResult::success(code.to_string());
568 return Ok(exit_result);
569 }
570 ControlFlow::Return { value } => {
571 on_output(&value);
572 result = value;
573 }
574 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
575 on_output(&r);
576 result = r;
577 }
578 }
579 }
580
581 Ok(result)
582 }
583
584 fn execute_stmt_flow<'a>(
586 &'a self,
587 stmt: &'a Stmt,
588 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ControlFlow>> + 'a>> {
589 Box::pin(async move {
590 match stmt {
591 Stmt::Assignment(assign) => {
592 let value = self.eval_expr_async(&assign.value).await
594 .context("failed to evaluate assignment")?;
595 let mut scope = self.scope.write().await;
596 if assign.local {
597 scope.set(&assign.name, value.clone());
599 } else {
600 scope.set_global(&assign.name, value.clone());
602 }
603 drop(scope);
604
605 Ok(ControlFlow::ok(ExecResult::success("")))
607 }
608 Stmt::Command(cmd) => {
609 let result = self.execute_command(&cmd.name, &cmd.args).await?;
610 self.update_last_result(&result).await;
611
612 if !result.ok() {
614 let scope = self.scope.read().await;
615 if scope.error_exit_enabled() {
616 return Ok(ControlFlow::exit_code(result.code));
617 }
618 }
619
620 Ok(ControlFlow::ok(result))
621 }
622 Stmt::Pipeline(pipeline) => {
623 let result = self.execute_pipeline(pipeline).await?;
624 self.update_last_result(&result).await;
625
626 if !result.ok() {
628 let scope = self.scope.read().await;
629 if scope.error_exit_enabled() {
630 return Ok(ControlFlow::exit_code(result.code));
631 }
632 }
633
634 Ok(ControlFlow::ok(result))
635 }
636 Stmt::If(if_stmt) => {
637 let cond_value = self.eval_expr_async(&if_stmt.condition).await?;
639
640 let branch = if is_truthy(&cond_value) {
641 &if_stmt.then_branch
642 } else {
643 if_stmt.else_branch.as_deref().unwrap_or(&[])
644 };
645
646 let mut flow = ControlFlow::ok(ExecResult::success(""));
647 for stmt in branch {
648 flow = self.execute_stmt_flow(stmt).await?;
649 if !flow.is_normal() {
650 return Ok(flow);
651 }
652 }
653 Ok(flow)
654 }
655 Stmt::For(for_loop) => {
656 let mut items: Vec<Value> = Vec::new();
659 for item_expr in &for_loop.items {
660 let item = self.eval_expr_async(item_expr).await?;
661 match &item {
663 Value::Json(serde_json::Value::Array(arr)) => {
665 for elem in arr {
666 items.push(json_to_value(elem.clone()));
667 }
668 }
669 Value::String(_) => {
672 items.push(item);
673 }
674 _ => items.push(item),
676 }
677 }
678
679 let mut result = ExecResult::success("");
680 {
681 let mut scope = self.scope.write().await;
682 scope.push_frame();
683 }
684
685 'outer: for item in items {
686 {
687 let mut scope = self.scope.write().await;
688 scope.set(&for_loop.variable, item);
689 }
690 for stmt in &for_loop.body {
691 let mut flow = self.execute_stmt_flow(stmt).await?;
692 match &mut flow {
693 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
694 ControlFlow::Break { .. } => {
695 if flow.decrement_level() {
696 break 'outer;
698 }
699 let mut scope = self.scope.write().await;
701 scope.pop_frame();
702 return Ok(flow);
703 }
704 ControlFlow::Continue { .. } => {
705 if flow.decrement_level() {
706 continue 'outer;
708 }
709 let mut scope = self.scope.write().await;
711 scope.pop_frame();
712 return Ok(flow);
713 }
714 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
715 let mut scope = self.scope.write().await;
716 scope.pop_frame();
717 return Ok(flow);
718 }
719 }
720 }
721 }
722
723 {
724 let mut scope = self.scope.write().await;
725 scope.pop_frame();
726 }
727 Ok(ControlFlow::ok(result))
728 }
729 Stmt::While(while_loop) => {
730 let mut result = ExecResult::success("");
731
732 'outer: loop {
733 let cond_value = self.eval_expr_async(&while_loop.condition).await?;
735
736 if !is_truthy(&cond_value) {
737 break;
738 }
739
740 for stmt in &while_loop.body {
742 let mut flow = self.execute_stmt_flow(stmt).await?;
743 match &mut flow {
744 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
745 ControlFlow::Break { .. } => {
746 if flow.decrement_level() {
747 break 'outer;
749 }
750 return Ok(flow);
752 }
753 ControlFlow::Continue { .. } => {
754 if flow.decrement_level() {
755 continue 'outer;
757 }
758 return Ok(flow);
760 }
761 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
762 return Ok(flow);
763 }
764 }
765 }
766 }
767
768 Ok(ControlFlow::ok(result))
769 }
770 Stmt::Case(case_stmt) => {
771 let match_value = {
773 let mut scope = self.scope.write().await;
774 let value = eval_expr(&case_stmt.expr, &mut scope)?;
775 value_to_string(&value)
776 };
777
778 for branch in &case_stmt.branches {
780 let matched = branch.patterns.iter().any(|pattern| {
781 glob_match(pattern, &match_value)
782 });
783
784 if matched {
785 let mut result = ControlFlow::ok(ExecResult::success(""));
787 for stmt in &branch.body {
788 result = self.execute_stmt_flow(stmt).await?;
789 if !result.is_normal() {
790 return Ok(result);
791 }
792 }
793 return Ok(result);
794 }
795 }
796
797 Ok(ControlFlow::ok(ExecResult::success("")))
799 }
800 Stmt::Break(levels) => {
801 Ok(ControlFlow::break_n(levels.unwrap_or(1)))
802 }
803 Stmt::Continue(levels) => {
804 Ok(ControlFlow::continue_n(levels.unwrap_or(1)))
805 }
806 Stmt::Return(expr) => {
807 let result = if let Some(e) = expr {
810 let mut scope = self.scope.write().await;
811 let val = eval_expr(e, &mut scope)?;
812 let code = match val {
814 Value::Int(n) => n,
815 Value::Bool(b) => if b { 0 } else { 1 },
816 _ => 0,
817 };
818 ExecResult {
819 code,
820 out: String::new(),
821 err: String::new(),
822 data: None,
823 output: None,
824 }
825 } else {
826 ExecResult::success("")
827 };
828 Ok(ControlFlow::return_value(result))
829 }
830 Stmt::Exit(expr) => {
831 let code = if let Some(e) = expr {
832 let mut scope = self.scope.write().await;
833 let val = eval_expr(e, &mut scope)?;
834 match val {
835 Value::Int(n) => n,
836 _ => 0,
837 }
838 } else {
839 0
840 };
841 Ok(ControlFlow::exit_code(code))
842 }
843 Stmt::ToolDef(tool_def) => {
844 let mut user_tools = self.user_tools.write().await;
845 user_tools.insert(tool_def.name.clone(), tool_def.clone());
846 Ok(ControlFlow::ok(ExecResult::success("")))
847 }
848 Stmt::AndChain { left, right } => {
849 let left_flow = self.execute_stmt_flow(left).await?;
851 match left_flow {
852 ControlFlow::Normal(left_result) => {
853 self.update_last_result(&left_result).await;
854 if left_result.ok() {
855 let right_flow = self.execute_stmt_flow(right).await?;
856 match right_flow {
857 ControlFlow::Normal(right_result) => {
858 self.update_last_result(&right_result).await;
859 let mut combined = left_result;
861 accumulate_result(&mut combined, &right_result);
862 Ok(ControlFlow::ok(combined))
863 }
864 other => Ok(other), }
866 } else {
867 Ok(ControlFlow::ok(left_result))
868 }
869 }
870 _ => Ok(left_flow), }
872 }
873 Stmt::OrChain { left, right } => {
874 let left_flow = self.execute_stmt_flow(left).await?;
876 match left_flow {
877 ControlFlow::Normal(left_result) => {
878 self.update_last_result(&left_result).await;
879 if !left_result.ok() {
880 let right_flow = self.execute_stmt_flow(right).await?;
881 match right_flow {
882 ControlFlow::Normal(right_result) => {
883 self.update_last_result(&right_result).await;
884 let mut combined = left_result;
886 accumulate_result(&mut combined, &right_result);
887 Ok(ControlFlow::ok(combined))
888 }
889 other => Ok(other), }
891 } else {
892 Ok(ControlFlow::ok(left_result))
893 }
894 }
895 _ => Ok(left_flow), }
897 }
898 Stmt::Test(test_expr) => {
899 let expr = crate::ast::Expr::Test(Box::new(test_expr.clone()));
901 let mut scope = self.scope.write().await;
902 let value = eval_expr(&expr, &mut scope)?;
903 drop(scope);
904 let is_true = match value {
905 crate::ast::Value::Bool(b) => b,
906 _ => false,
907 };
908 if is_true {
909 Ok(ControlFlow::ok(ExecResult::success("")))
910 } else {
911 Ok(ControlFlow::ok(ExecResult::failure(1, "")))
912 }
913 }
914 Stmt::Empty => Ok(ControlFlow::ok(ExecResult::success(""))),
915 }
916 })
917 }
918
919 async fn execute_pipeline(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
921 if pipeline.commands.is_empty() {
922 return Ok(ExecResult::success(""));
923 }
924
925 if pipeline.background {
927 return self.execute_background(pipeline).await;
928 }
929
930 if pipeline.commands.len() == 1 && pipeline.commands[0].redirects.is_empty() {
932 let cmd = &pipeline.commands[0];
933 return self.execute_command(&cmd.name, &cmd.args).await;
934 }
935
936 let mut ctx = self.exec_ctx.write().await;
938 {
939 let scope = self.scope.read().await;
940 ctx.scope = scope.clone();
941 }
942
943 let result = self.runner.run(&pipeline.commands, &mut ctx).await;
944
945 {
947 let mut scope = self.scope.write().await;
948 *scope = ctx.scope.clone();
949 }
950
951 Ok(result)
952 }
953
954 async fn execute_background(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
962 use tokio::sync::oneshot;
963
964 let command_str = self.format_pipeline(pipeline);
966
967 let stdout = Arc::new(BoundedStream::default_size());
969 let stderr = Arc::new(BoundedStream::default_size());
970
971 let (tx, rx) = oneshot::channel();
973
974 let job_id = self.jobs.register_with_streams(
976 command_str.clone(),
977 rx,
978 stdout.clone(),
979 stderr.clone(),
980 ).await;
981
982 let runner = self.runner.clone();
984 let commands = pipeline.commands.clone();
985 let backend = {
986 let ctx = self.exec_ctx.read().await;
987 ctx.backend.clone()
988 };
989 let scope = {
990 let scope = self.scope.read().await;
991 scope.clone()
992 };
993 let cwd = {
994 let ctx = self.exec_ctx.read().await;
995 ctx.cwd.clone()
996 };
997 let tools = self.tools.clone();
998 let tool_schemas = self.tools.schemas();
999
1000 tokio::spawn(async move {
1002 let mut bg_ctx = ExecContext::with_backend(backend);
1005 bg_ctx.scope = scope;
1006 bg_ctx.cwd = cwd;
1007 bg_ctx.set_tools(tools);
1008 bg_ctx.set_tool_schemas(tool_schemas);
1009
1010 let result = runner.run(&commands, &mut bg_ctx).await;
1012
1013 if !result.out.is_empty() {
1015 stdout.write(result.out.as_bytes()).await;
1016 }
1017 if !result.err.is_empty() {
1018 stderr.write(result.err.as_bytes()).await;
1019 }
1020
1021 stdout.close().await;
1023 stderr.close().await;
1024
1025 let _ = tx.send(result);
1027 });
1028
1029 Ok(ExecResult::success(format!("[{}]", job_id)))
1030 }
1031
1032 fn format_pipeline(&self, pipeline: &crate::ast::Pipeline) -> String {
1034 pipeline.commands
1035 .iter()
1036 .map(|cmd| {
1037 let mut parts = vec![cmd.name.clone()];
1038 for arg in &cmd.args {
1039 match arg {
1040 Arg::Positional(expr) => {
1041 parts.push(self.format_expr(expr));
1042 }
1043 Arg::Named { key, value } => {
1044 parts.push(format!("{}={}", key, self.format_expr(value)));
1045 }
1046 Arg::ShortFlag(name) => {
1047 parts.push(format!("-{}", name));
1048 }
1049 Arg::LongFlag(name) => {
1050 parts.push(format!("--{}", name));
1051 }
1052 Arg::DoubleDash => {
1053 parts.push("--".to_string());
1054 }
1055 }
1056 }
1057 parts.join(" ")
1058 })
1059 .collect::<Vec<_>>()
1060 .join(" | ")
1061 }
1062
1063 fn format_expr(&self, expr: &Expr) -> String {
1065 match expr {
1066 Expr::Literal(Value::String(s)) => {
1067 if s.contains(' ') || s.contains('"') {
1068 format!("'{}'", s.replace('\'', "\\'"))
1069 } else {
1070 s.clone()
1071 }
1072 }
1073 Expr::Literal(Value::Int(i)) => i.to_string(),
1074 Expr::Literal(Value::Float(f)) => f.to_string(),
1075 Expr::Literal(Value::Bool(b)) => b.to_string(),
1076 Expr::Literal(Value::Null) => "null".to_string(),
1077 Expr::VarRef(path) => {
1078 let name = path.segments.iter()
1079 .map(|seg| match seg {
1080 crate::ast::VarSegment::Field(f) => f.clone(),
1081 })
1082 .collect::<Vec<_>>()
1083 .join(".");
1084 format!("${{{}}}", name)
1085 }
1086 Expr::Interpolated(_) => "\"...\"".to_string(),
1087 _ => "...".to_string(),
1088 }
1089 }
1090
1091 async fn execute_command(&self, name: &str, args: &[Arg]) -> Result<ExecResult> {
1093 match name {
1095 "true" => return Ok(ExecResult::success("")),
1096 "false" => return Ok(ExecResult::failure(1, "")),
1097 "source" | "." => return self.execute_source(args).await,
1098 _ => {}
1099 }
1100
1101 {
1103 let user_tools = self.user_tools.read().await;
1104 if let Some(tool_def) = user_tools.get(name) {
1105 let tool_def = tool_def.clone();
1106 drop(user_tools);
1107 return self.execute_user_tool(tool_def, args).await;
1108 }
1109 }
1110
1111 let tool = match self.tools.get(name) {
1113 Some(t) => t,
1114 None => {
1115 if let Some(result) = self.try_execute_script(name, args).await? {
1117 return Ok(result);
1118 }
1119 if let Some(result) = self.try_execute_external(name, args).await? {
1121 return Ok(result);
1122 }
1123
1124 let tool_args = self.build_args_async(args).await?;
1126 let mut ctx = self.exec_ctx.write().await;
1127 {
1128 let scope = self.scope.read().await;
1129 ctx.scope = scope.clone();
1130 }
1131 let backend = ctx.backend.clone();
1132 match backend.call_tool(name, tool_args, &mut ctx).await {
1133 Ok(tool_result) => {
1134 let mut scope = self.scope.write().await;
1135 *scope = ctx.scope.clone();
1136 let mut exec = ExecResult::from_output(
1137 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1138 );
1139 exec.output = tool_result.output;
1140 return Ok(exec);
1141 }
1142 Err(BackendError::ToolNotFound(_)) => {
1143 }
1145 Err(e) => {
1146 return Ok(ExecResult::failure(1, e.to_string()));
1147 }
1148 }
1149
1150 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1151 }
1152 };
1153
1154 let mut tool_args = self.build_args_async(args).await?;
1156 let output_format = extract_output_format(&mut tool_args, Some(&tool.schema()));
1157
1158 let mut ctx = self.exec_ctx.write().await;
1160 {
1161 let scope = self.scope.read().await;
1162 ctx.scope = scope.clone();
1163 }
1164
1165 let result = tool.execute(tool_args, &mut ctx).await;
1166
1167 {
1169 let mut scope = self.scope.write().await;
1170 *scope = ctx.scope.clone();
1171 }
1172
1173 let result = match output_format {
1174 Some(format) => apply_output_format(result, format),
1175 None => result,
1176 };
1177
1178 Ok(result)
1179 }
1180
1181 async fn build_args_async(&self, args: &[Arg]) -> Result<ToolArgs> {
1185 let mut tool_args = ToolArgs::new();
1186
1187 for arg in args {
1188 match arg {
1189 Arg::Positional(expr) => {
1190 let value = self.eval_expr_async(expr).await?;
1191 let value = apply_tilde_expansion(value);
1193 tool_args.positional.push(value);
1194 }
1195 Arg::Named { key, value } => {
1196 let val = self.eval_expr_async(value).await?;
1197 let val = apply_tilde_expansion(val);
1199 tool_args.named.insert(key.clone(), val);
1200 }
1201 Arg::ShortFlag(name) => {
1202 for c in name.chars() {
1203 tool_args.flags.insert(c.to_string());
1204 }
1205 }
1206 Arg::LongFlag(name) => {
1207 tool_args.flags.insert(name.clone());
1208 }
1209 Arg::DoubleDash => {
1210 }
1213 }
1214 }
1215
1216 Ok(tool_args)
1217 }
1218
1219 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1229 let mut argv = Vec::new();
1230 for arg in args {
1231 match arg {
1232 Arg::Positional(expr) => {
1233 let value = self.eval_expr_async(expr).await?;
1234 let value = apply_tilde_expansion(value);
1235 argv.push(value_to_string(&value));
1236 }
1237 Arg::Named { key, value } => {
1238 let val = self.eval_expr_async(value).await?;
1239 let val = apply_tilde_expansion(val);
1240 argv.push(format!("{}={}", key, value_to_string(&val)));
1241 }
1242 Arg::ShortFlag(name) => {
1243 argv.push(format!("-{}", name));
1245 }
1246 Arg::LongFlag(name) => {
1247 argv.push(format!("--{}", name));
1249 }
1250 Arg::DoubleDash => {
1251 argv.push("--".to_string());
1253 }
1254 }
1255 }
1256 Ok(argv)
1257 }
1258
1259 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + 'a>> {
1264 Box::pin(async move {
1265 match expr {
1266 Expr::Literal(value) => Ok(value.clone()),
1267 Expr::VarRef(path) => {
1268 let scope = self.scope.read().await;
1269 scope.resolve_path(path)
1270 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1271 }
1272 Expr::Interpolated(parts) => {
1273 let mut result = String::new();
1274 for part in parts {
1275 result.push_str(&self.eval_string_part_async(part).await?);
1276 }
1277 Ok(Value::String(result))
1278 }
1279 Expr::BinaryOp { left, op, right } => {
1280 match op {
1281 BinaryOp::And => {
1282 let left_val = self.eval_expr_async(left).await?;
1283 if !is_truthy(&left_val) {
1284 return Ok(left_val);
1285 }
1286 self.eval_expr_async(right).await
1287 }
1288 BinaryOp::Or => {
1289 let left_val = self.eval_expr_async(left).await?;
1290 if is_truthy(&left_val) {
1291 return Ok(left_val);
1292 }
1293 self.eval_expr_async(right).await
1294 }
1295 _ => {
1296 let mut scope = self.scope.write().await;
1298 eval_expr(expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1299 }
1300 }
1301 }
1302 Expr::CommandSubst(pipeline) => {
1303 let result = self.execute_pipeline(pipeline).await?;
1305 self.update_last_result(&result).await;
1306 if let Some(data) = &result.data {
1308 Ok(data.clone())
1309 } else {
1310 Ok(Value::String(result.out.trim_end().to_string()))
1312 }
1313 }
1314 Expr::Test(test_expr) => {
1315 let expr = Expr::Test(test_expr.clone());
1317 let mut scope = self.scope.write().await;
1318 eval_expr(&expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1319 }
1320 Expr::Positional(n) => {
1321 let scope = self.scope.read().await;
1322 match scope.get_positional(*n) {
1323 Some(s) => Ok(Value::String(s.to_string())),
1324 None => Ok(Value::String(String::new())),
1325 }
1326 }
1327 Expr::AllArgs => {
1328 let scope = self.scope.read().await;
1329 Ok(Value::String(scope.all_args().join(" ")))
1330 }
1331 Expr::ArgCount => {
1332 let scope = self.scope.read().await;
1333 Ok(Value::Int(scope.arg_count() as i64))
1334 }
1335 Expr::VarLength(name) => {
1336 let scope = self.scope.read().await;
1337 match scope.get(name) {
1338 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1339 None => Ok(Value::Int(0)),
1340 }
1341 }
1342 Expr::VarWithDefault { name, default } => {
1343 let scope = self.scope.read().await;
1344 let use_default = match scope.get(name) {
1345 Some(value) => value_to_string(value).is_empty(),
1346 None => true,
1347 };
1348 drop(scope); if use_default {
1350 self.eval_string_parts_async(default).await.map(Value::String)
1352 } else {
1353 let scope = self.scope.read().await;
1354 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1355 }
1356 }
1357 Expr::Arithmetic(expr_str) => {
1358 let scope = self.scope.read().await;
1359 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1360 .map(Value::Int)
1361 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1362 }
1363 Expr::Command(cmd) => {
1364 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1366 Ok(Value::Bool(result.code == 0))
1367 }
1368 Expr::LastExitCode => {
1369 let scope = self.scope.read().await;
1370 Ok(Value::Int(scope.last_result().code))
1371 }
1372 Expr::CurrentPid => {
1373 let scope = self.scope.read().await;
1374 Ok(Value::Int(scope.pid() as i64))
1375 }
1376 }
1377 })
1378 }
1379
1380 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + 'a>> {
1382 Box::pin(async move {
1383 let mut result = String::new();
1384 for part in parts {
1385 result.push_str(&self.eval_string_part_async(part).await?);
1386 }
1387 Ok(result)
1388 })
1389 }
1390
1391 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + 'a>> {
1393 Box::pin(async move {
1394 match part {
1395 StringPart::Literal(s) => Ok(s.clone()),
1396 StringPart::Var(path) => {
1397 let scope = self.scope.read().await;
1398 match scope.resolve_path(path) {
1399 Some(value) => Ok(value_to_string(&value)),
1400 None => Ok(String::new()), }
1402 }
1403 StringPart::VarWithDefault { name, default } => {
1404 let scope = self.scope.read().await;
1405 let use_default = match scope.get(name) {
1406 Some(value) => value_to_string(value).is_empty(),
1407 None => true,
1408 };
1409 drop(scope); if use_default {
1411 self.eval_string_parts_async(default).await
1413 } else {
1414 let scope = self.scope.read().await;
1415 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
1416 }
1417 }
1418 StringPart::VarLength(name) => {
1419 let scope = self.scope.read().await;
1420 match scope.get(name) {
1421 Some(value) => Ok(value_to_string(value).len().to_string()),
1422 None => Ok("0".to_string()),
1423 }
1424 }
1425 StringPart::Positional(n) => {
1426 let scope = self.scope.read().await;
1427 match scope.get_positional(*n) {
1428 Some(s) => Ok(s.to_string()),
1429 None => Ok(String::new()),
1430 }
1431 }
1432 StringPart::AllArgs => {
1433 let scope = self.scope.read().await;
1434 Ok(scope.all_args().join(" "))
1435 }
1436 StringPart::ArgCount => {
1437 let scope = self.scope.read().await;
1438 Ok(scope.arg_count().to_string())
1439 }
1440 StringPart::Arithmetic(expr) => {
1441 let scope = self.scope.read().await;
1442 match crate::arithmetic::eval_arithmetic(expr, &scope) {
1443 Ok(value) => Ok(value.to_string()),
1444 Err(_) => Ok(String::new()),
1445 }
1446 }
1447 StringPart::CommandSubst(pipeline) => {
1448 let result = self.execute_pipeline(pipeline).await?;
1450 Ok(result.out.trim_end_matches('\n').to_string())
1451 }
1452 StringPart::LastExitCode => {
1453 let scope = self.scope.read().await;
1454 Ok(scope.last_result().code.to_string())
1455 }
1456 StringPart::CurrentPid => {
1457 let scope = self.scope.read().await;
1458 Ok(scope.pid().to_string())
1459 }
1460 }
1461 })
1462 }
1463
1464 async fn update_last_result(&self, result: &ExecResult) {
1466 let mut scope = self.scope.write().await;
1467 scope.set_last_result(result.clone());
1468 }
1469
1470 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
1476 let tool_args = self.build_args_async(args).await?;
1478
1479 {
1481 let mut scope = self.scope.write().await;
1482 scope.push_frame();
1483 }
1484
1485 let saved_positional = {
1487 let mut scope = self.scope.write().await;
1488 let saved = scope.save_positional();
1489
1490 let positional_args: Vec<String> = tool_args.positional
1492 .iter()
1493 .map(value_to_string)
1494 .collect();
1495 scope.set_positional(&def.name, positional_args);
1496
1497 saved
1498 };
1499
1500 let mut accumulated_out = String::new();
1503 let mut accumulated_err = String::new();
1504 let mut last_code = 0i64;
1505 let mut last_data: Option<Value> = None;
1506
1507 for stmt in &def.body {
1508 match self.execute_stmt_flow(stmt).await {
1509 Ok(flow) => {
1510 match flow {
1511 ControlFlow::Normal(r) => {
1512 accumulated_out.push_str(&r.out);
1513 accumulated_err.push_str(&r.err);
1514 last_code = r.code;
1515 last_data = r.data;
1516 }
1517 ControlFlow::Return { value } => {
1518 accumulated_out.push_str(&value.out);
1520 accumulated_err.push_str(&value.err);
1521 last_code = value.code;
1522 last_data = value.data;
1523 break;
1524 }
1525 ControlFlow::Exit { code } => {
1526 let mut scope = self.scope.write().await;
1528 scope.pop_frame();
1529 scope.set_positional(saved_positional.0.clone(), saved_positional.1.clone());
1530 return Ok(ExecResult::failure(code, "exit"));
1531 }
1532 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1533 accumulated_out.push_str(&r.out);
1535 accumulated_err.push_str(&r.err);
1536 last_code = r.code;
1537 last_data = r.data;
1538 }
1539 }
1540 }
1541 Err(e) => {
1542 let mut scope = self.scope.write().await;
1544 scope.pop_frame();
1545 scope.set_positional(saved_positional.0.clone(), saved_positional.1.clone());
1546 return Err(e);
1547 }
1548 }
1549 }
1550
1551 let result = ExecResult {
1552 code: last_code,
1553 out: accumulated_out,
1554 err: accumulated_err,
1555 data: last_data,
1556 output: None,
1557 };
1558
1559 {
1561 let mut scope = self.scope.write().await;
1562 scope.pop_frame();
1563 scope.set_positional(saved_positional.0, saved_positional.1);
1564 }
1565
1566 Ok(result)
1568 }
1569
1570 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
1575 let tool_args = self.build_args_async(args).await?;
1577 let path = match tool_args.positional.first() {
1578 Some(Value::String(s)) => s.clone(),
1579 Some(v) => value_to_string(v),
1580 None => {
1581 return Ok(ExecResult::failure(1, "source: missing filename"));
1582 }
1583 };
1584
1585 let full_path = {
1587 let ctx = self.exec_ctx.read().await;
1588 if path.starts_with('/') {
1589 std::path::PathBuf::from(&path)
1590 } else {
1591 ctx.cwd.join(&path)
1592 }
1593 };
1594
1595 let content = {
1597 let ctx = self.exec_ctx.read().await;
1598 match ctx.backend.read(&full_path, None).await {
1599 Ok(bytes) => {
1600 String::from_utf8(bytes).map_err(|e| {
1601 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
1602 })?
1603 }
1604 Err(e) => {
1605 return Ok(ExecResult::failure(
1606 1,
1607 format!("source: {}: {}", path, e),
1608 ));
1609 }
1610 }
1611 };
1612
1613 let program = match crate::parser::parse(&content) {
1615 Ok(p) => p,
1616 Err(errors) => {
1617 let msg = errors
1618 .iter()
1619 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
1620 .collect::<Vec<_>>()
1621 .join("\n");
1622 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
1623 }
1624 };
1625
1626 let mut result = ExecResult::success("");
1628 for stmt in program.statements {
1629 if matches!(stmt, crate::ast::Stmt::Empty) {
1630 continue;
1631 }
1632
1633 match self.execute_stmt_flow(&stmt).await {
1634 Ok(flow) => {
1635 match flow {
1636 ControlFlow::Normal(r) => {
1637 result = r.clone();
1638 self.update_last_result(&r).await;
1639 }
1640 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
1641 return Err(anyhow::anyhow!(
1643 "source: {}: unexpected break/continue outside loop",
1644 path
1645 ));
1646 }
1647 ControlFlow::Return { value } => {
1648 return Ok(value);
1650 }
1651 ControlFlow::Exit { code } => {
1652 return Ok(ExecResult::failure(code, "exit"));
1654 }
1655 }
1656 }
1657 Err(e) => {
1658 return Err(e.context(format!("source: {}", path)));
1659 }
1660 }
1661 }
1662
1663 Ok(result)
1664 }
1665
1666 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1671 let path_value = {
1673 let scope = self.scope.read().await;
1674 scope
1675 .get("PATH")
1676 .map(value_to_string)
1677 .unwrap_or_else(|| "/bin".to_string())
1678 };
1679
1680 for dir in path_value.split(':') {
1682 if dir.is_empty() {
1683 continue;
1684 }
1685
1686 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
1688
1689 let exists = {
1691 let ctx = self.exec_ctx.read().await;
1692 ctx.backend.exists(&script_path).await
1693 };
1694
1695 if !exists {
1696 continue;
1697 }
1698
1699 let content = {
1701 let ctx = self.exec_ctx.read().await;
1702 match ctx.backend.read(&script_path, None).await {
1703 Ok(bytes) => match String::from_utf8(bytes) {
1704 Ok(s) => s,
1705 Err(e) => {
1706 return Ok(Some(ExecResult::failure(
1707 1,
1708 format!("{}: invalid UTF-8: {}", script_path.display(), e),
1709 )));
1710 }
1711 },
1712 Err(e) => {
1713 return Ok(Some(ExecResult::failure(
1714 1,
1715 format!("{}: {}", script_path.display(), e),
1716 )));
1717 }
1718 }
1719 };
1720
1721 let program = match crate::parser::parse(&content) {
1723 Ok(p) => p,
1724 Err(errors) => {
1725 let msg = errors
1726 .iter()
1727 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
1728 .collect::<Vec<_>>()
1729 .join("\n");
1730 return Ok(Some(ExecResult::failure(1, msg)));
1731 }
1732 };
1733
1734 let tool_args = self.build_args_async(args).await?;
1736
1737 let mut isolated_scope = Scope::new();
1739
1740 let positional_args: Vec<String> = tool_args.positional
1742 .iter()
1743 .map(value_to_string)
1744 .collect();
1745 isolated_scope.set_positional(name, positional_args);
1746
1747 let original_scope = {
1749 let mut scope = self.scope.write().await;
1750 std::mem::replace(&mut *scope, isolated_scope)
1751 };
1752
1753 let mut result = ExecResult::success("");
1755 for stmt in program.statements {
1756 if matches!(stmt, crate::ast::Stmt::Empty) {
1757 continue;
1758 }
1759
1760 match self.execute_stmt_flow(&stmt).await {
1761 Ok(flow) => {
1762 match flow {
1763 ControlFlow::Normal(r) => result = r,
1764 ControlFlow::Return { value } => {
1765 result = value;
1766 break;
1767 }
1768 ControlFlow::Exit { code } => {
1769 let mut scope = self.scope.write().await;
1771 *scope = original_scope;
1772 return Ok(Some(ExecResult::failure(code, "exit")));
1773 }
1774 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1775 result = r;
1776 }
1777 }
1778 }
1779 Err(e) => {
1780 let mut scope = self.scope.write().await;
1782 *scope = original_scope;
1783 return Err(e.context(format!("script: {}", script_path.display())));
1784 }
1785 }
1786 }
1787
1788 {
1790 let mut scope = self.scope.write().await;
1791 *scope = original_scope;
1792 }
1793
1794 return Ok(Some(result));
1795 }
1796
1797 Ok(None)
1799 }
1800
1801 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1815 if name.contains('/') {
1817 return Ok(None);
1818 }
1819
1820 let path_var = {
1822 let scope = self.scope.read().await;
1823 scope
1824 .get("PATH")
1825 .map(value_to_string)
1826 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
1827 };
1828
1829 let executable = match resolve_in_path(name, &path_var) {
1831 Some(path) => path,
1832 None => return Ok(None), };
1834
1835 let real_cwd = {
1837 let ctx = self.exec_ctx.read().await;
1838 match ctx.backend.resolve_real_path(&ctx.cwd) {
1839 Some(p) => p,
1840 None => {
1841 return Ok(Some(ExecResult::failure(
1842 1,
1843 format!(
1844 "{}: cannot run external command from virtual directory '{}'",
1845 name,
1846 ctx.cwd.display()
1847 ),
1848 )));
1849 }
1850 }
1851 };
1852
1853 let argv = self.build_args_flat(args).await?;
1855
1856 let stdin_data = {
1858 let mut ctx = self.exec_ctx.write().await;
1859 ctx.take_stdin()
1860 };
1861
1862 use tokio::process::Command;
1864
1865 let mut cmd = Command::new(&executable);
1866 cmd.args(&argv);
1867 cmd.current_dir(&real_cwd);
1868
1869 cmd.stdin(if stdin_data.is_some() {
1871 std::process::Stdio::piped()
1872 } else {
1873 std::process::Stdio::null()
1874 });
1875
1876 let inherit_output = self.interactive && stdin_data.is_none();
1879
1880 if inherit_output {
1881 cmd.stdout(std::process::Stdio::inherit());
1882 cmd.stderr(std::process::Stdio::inherit());
1883 } else {
1884 cmd.stdout(std::process::Stdio::piped());
1885 cmd.stderr(std::process::Stdio::piped());
1886 }
1887
1888 let mut child = match cmd.spawn() {
1890 Ok(child) => child,
1891 Err(e) => {
1892 return Ok(Some(ExecResult::failure(
1893 127,
1894 format!("{}: {}", name, e),
1895 )));
1896 }
1897 };
1898
1899 if let Some(data) = stdin_data
1901 && let Some(mut stdin) = child.stdin.take()
1902 {
1903 use tokio::io::AsyncWriteExt;
1904 if let Err(e) = stdin.write_all(data.as_bytes()).await {
1905 return Ok(Some(ExecResult::failure(
1906 1,
1907 format!("{}: failed to write stdin: {}", name, e),
1908 )));
1909 }
1910 }
1912
1913 if inherit_output {
1914 let status = match child.wait().await {
1916 Ok(s) => s,
1917 Err(e) => {
1918 return Ok(Some(ExecResult::failure(
1919 1,
1920 format!("{}: failed to wait: {}", name, e),
1921 )));
1922 }
1923 };
1924
1925 let code = status.code().unwrap_or_else(|| {
1926 #[cfg(unix)]
1927 {
1928 use std::os::unix::process::ExitStatusExt;
1929 128 + status.signal().unwrap_or(0)
1930 }
1931 #[cfg(not(unix))]
1932 {
1933 -1
1934 }
1935 }) as i64;
1936
1937 Ok(Some(ExecResult::from_output(code, String::new(), String::new())))
1939 } else {
1940 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
1942 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
1943
1944 let stdout_pipe = child.stdout.take();
1945 let stderr_pipe = child.stderr.take();
1946
1947 let stdout_clone = stdout_stream.clone();
1948 let stderr_clone = stderr_stream.clone();
1949
1950 let stdout_task = stdout_pipe.map(|pipe| {
1951 tokio::spawn(async move {
1952 drain_to_stream(pipe, stdout_clone).await;
1953 })
1954 });
1955
1956 let stderr_task = stderr_pipe.map(|pipe| {
1957 tokio::spawn(async move {
1958 drain_to_stream(pipe, stderr_clone).await;
1959 })
1960 });
1961
1962 let status = match child.wait().await {
1963 Ok(s) => s,
1964 Err(e) => {
1965 return Ok(Some(ExecResult::failure(
1966 1,
1967 format!("{}: failed to wait: {}", name, e),
1968 )));
1969 }
1970 };
1971
1972 if let Some(task) = stdout_task {
1973 let _ = task.await;
1975 }
1976 if let Some(task) = stderr_task {
1977 let _ = task.await;
1978 }
1979
1980 let code = status.code().unwrap_or_else(|| {
1981 #[cfg(unix)]
1982 {
1983 use std::os::unix::process::ExitStatusExt;
1984 128 + status.signal().unwrap_or(0)
1985 }
1986 #[cfg(not(unix))]
1987 {
1988 -1
1989 }
1990 }) as i64;
1991
1992 let stdout = stdout_stream.read_string().await;
1993 let stderr = stderr_stream.read_string().await;
1994
1995 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
1996 }
1997 }
1998
1999 pub async fn get_var(&self, name: &str) -> Option<Value> {
2003 let scope = self.scope.read().await;
2004 scope.get(name).cloned()
2005 }
2006
2007 #[cfg(test)]
2009 pub async fn error_exit_enabled(&self) -> bool {
2010 let scope = self.scope.read().await;
2011 scope.error_exit_enabled()
2012 }
2013
2014 pub async fn set_var(&self, name: &str, value: Value) {
2016 let mut scope = self.scope.write().await;
2017 scope.set(name.to_string(), value);
2018 }
2019
2020 pub async fn list_vars(&self) -> Vec<(String, Value)> {
2022 let scope = self.scope.read().await;
2023 scope.all()
2024 }
2025
2026 pub async fn cwd(&self) -> PathBuf {
2030 self.exec_ctx.read().await.cwd.clone()
2031 }
2032
2033 pub async fn set_cwd(&self, path: PathBuf) {
2035 let mut ctx = self.exec_ctx.write().await;
2036 ctx.set_cwd(path);
2037 }
2038
2039 pub async fn last_result(&self) -> ExecResult {
2043 let scope = self.scope.read().await;
2044 scope.last_result().clone()
2045 }
2046
2047 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
2051 self.tools.schemas()
2052 }
2053
2054 pub fn jobs(&self) -> Arc<JobManager> {
2058 self.jobs.clone()
2059 }
2060
2061 pub fn vfs(&self) -> Arc<VfsRouter> {
2065 self.vfs.clone()
2066 }
2067
2068 pub async fn reset(&self) -> Result<()> {
2075 {
2076 let mut scope = self.scope.write().await;
2077 *scope = Scope::new();
2078 }
2079 {
2080 let mut ctx = self.exec_ctx.write().await;
2081 ctx.cwd = PathBuf::from("/");
2082 }
2083 Ok(())
2084 }
2085
2086 pub async fn shutdown(self) -> Result<()> {
2088 self.jobs.wait_all().await;
2090 Ok(())
2091 }
2092}
2093
2094fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2100 if !accumulated.out.is_empty() && !new.out.is_empty() {
2101 accumulated.out.push('\n');
2102 }
2103 accumulated.out.push_str(&new.out);
2104 if !accumulated.err.is_empty() && !new.err.is_empty() {
2105 accumulated.err.push('\n');
2106 }
2107 accumulated.err.push_str(&new.err);
2108 accumulated.code = new.code;
2109 accumulated.data = new.data.clone();
2110}
2111
2112fn is_truthy(value: &Value) -> bool {
2114 match value {
2115 Value::Null => false,
2116 Value::Bool(b) => *b,
2117 Value::Int(i) => *i != 0,
2118 Value::Float(f) => *f != 0.0,
2119 Value::String(s) => !s.is_empty(),
2120 Value::Json(json) => match json {
2121 serde_json::Value::Null => false,
2122 serde_json::Value::Array(arr) => !arr.is_empty(),
2123 serde_json::Value::Object(obj) => !obj.is_empty(),
2124 serde_json::Value::Bool(b) => *b,
2125 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
2126 serde_json::Value::String(s) => !s.is_empty(),
2127 },
2128 Value::Blob(_) => true, }
2130}
2131
2132fn apply_tilde_expansion(value: Value) -> Value {
2136 match value {
2137 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
2138 _ => value,
2139 }
2140}
2141
2142#[cfg(test)]
2143mod tests {
2144 use super::*;
2145
2146 #[tokio::test]
2147 async fn test_kernel_transient() {
2148 let kernel = Kernel::transient().expect("failed to create kernel");
2149 assert_eq!(kernel.name(), "transient");
2150 }
2151
2152 #[tokio::test]
2153 async fn test_kernel_execute_echo() {
2154 let kernel = Kernel::transient().expect("failed to create kernel");
2155 let result = kernel.execute("echo hello").await.expect("execution failed");
2156 assert!(result.ok());
2157 assert_eq!(result.out.trim(), "hello");
2158 }
2159
2160 #[tokio::test]
2161 async fn test_multiple_statements_accumulate_output() {
2162 let kernel = Kernel::transient().expect("failed to create kernel");
2163 let result = kernel
2164 .execute("echo one\necho two\necho three")
2165 .await
2166 .expect("execution failed");
2167 assert!(result.ok());
2168 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
2170 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
2171 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
2172 }
2173
2174 #[tokio::test]
2175 async fn test_and_chain_accumulates_output() {
2176 let kernel = Kernel::transient().expect("failed to create kernel");
2177 let result = kernel
2178 .execute("echo first && echo second")
2179 .await
2180 .expect("execution failed");
2181 assert!(result.ok());
2182 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
2183 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
2184 }
2185
2186 #[tokio::test]
2187 async fn test_for_loop_accumulates_output() {
2188 let kernel = Kernel::transient().expect("failed to create kernel");
2189 let result = kernel
2190 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
2191 .await
2192 .expect("execution failed");
2193 assert!(result.ok());
2194 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
2195 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
2196 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
2197 }
2198
2199 #[tokio::test]
2200 async fn test_while_loop_accumulates_output() {
2201 let kernel = Kernel::transient().expect("failed to create kernel");
2202 let result = kernel
2203 .execute(r#"
2204 N=3
2205 while [[ ${N} -gt 0 ]]; do
2206 echo "N=${N}"
2207 N=$((N - 1))
2208 done
2209 "#)
2210 .await
2211 .expect("execution failed");
2212 assert!(result.ok());
2213 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
2214 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
2215 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
2216 }
2217
2218 #[tokio::test]
2219 async fn test_kernel_set_var() {
2220 let kernel = Kernel::transient().expect("failed to create kernel");
2221
2222 kernel.execute("X=42").await.expect("set failed");
2223
2224 let value = kernel.get_var("X").await;
2225 assert_eq!(value, Some(Value::Int(42)));
2226 }
2227
2228 #[tokio::test]
2229 async fn test_kernel_var_expansion() {
2230 let kernel = Kernel::transient().expect("failed to create kernel");
2231
2232 kernel.execute("NAME=\"world\"").await.expect("set failed");
2233 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
2234
2235 assert!(result.ok());
2236 assert_eq!(result.out.trim(), "hello world");
2237 }
2238
2239 #[tokio::test]
2240 async fn test_kernel_last_result() {
2241 let kernel = Kernel::transient().expect("failed to create kernel");
2242
2243 kernel.execute("echo test").await.expect("echo failed");
2244
2245 let last = kernel.last_result().await;
2246 assert!(last.ok());
2247 assert_eq!(last.out.trim(), "test");
2248 }
2249
2250 #[tokio::test]
2251 async fn test_kernel_tool_not_found() {
2252 let kernel = Kernel::transient().expect("failed to create kernel");
2253
2254 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
2255 assert!(!result.ok());
2256 assert_eq!(result.code, 127);
2257 assert!(result.err.contains("command not found"));
2258 }
2259
2260 #[tokio::test]
2261 async fn test_external_command_true() {
2262 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2264
2265 let result = kernel.execute("true").await.expect("execution failed");
2267 assert!(result.ok(), "true should succeed: {:?}", result);
2269 }
2270
2271 #[tokio::test]
2272 async fn test_external_command_basic() {
2273 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2275
2276 let path_var = std::env::var("PATH").unwrap_or_default();
2281 eprintln!("System PATH: {}", path_var);
2282
2283 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
2285
2286 let result = kernel.execute("uname").await.expect("execution failed");
2289 eprintln!("uname result: {:?}", result);
2290 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
2292 }
2293
2294 #[tokio::test]
2295 async fn test_kernel_reset() {
2296 let kernel = Kernel::transient().expect("failed to create kernel");
2297
2298 kernel.execute("X=1").await.expect("set failed");
2299 assert!(kernel.get_var("X").await.is_some());
2300
2301 kernel.reset().await.expect("reset failed");
2302 assert!(kernel.get_var("X").await.is_none());
2303 }
2304
2305 #[tokio::test]
2306 async fn test_kernel_cwd() {
2307 let kernel = Kernel::transient().expect("failed to create kernel");
2308
2309 let cwd = kernel.cwd().await;
2311 let home = std::env::var("HOME")
2312 .map(PathBuf::from)
2313 .unwrap_or_else(|_| PathBuf::from("/"));
2314 assert_eq!(cwd, home);
2315
2316 kernel.set_cwd(PathBuf::from("/tmp")).await;
2317 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
2318 }
2319
2320 #[tokio::test]
2321 async fn test_kernel_list_vars() {
2322 let kernel = Kernel::transient().expect("failed to create kernel");
2323
2324 kernel.execute("A=1").await.ok();
2325 kernel.execute("B=2").await.ok();
2326
2327 let vars = kernel.list_vars().await;
2328 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
2329 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
2330 }
2331
2332 #[tokio::test]
2333 async fn test_is_truthy() {
2334 assert!(!is_truthy(&Value::Null));
2335 assert!(!is_truthy(&Value::Bool(false)));
2336 assert!(is_truthy(&Value::Bool(true)));
2337 assert!(!is_truthy(&Value::Int(0)));
2338 assert!(is_truthy(&Value::Int(1)));
2339 assert!(!is_truthy(&Value::String("".into())));
2340 assert!(is_truthy(&Value::String("x".into())));
2341 }
2342
2343 #[tokio::test]
2344 async fn test_jq_in_pipeline() {
2345 let kernel = Kernel::transient().expect("failed to create kernel");
2346 let result = kernel
2348 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
2349 .await
2350 .expect("execution failed");
2351 assert!(result.ok(), "jq pipeline failed: {}", result.err);
2352 assert_eq!(result.out.trim(), "Alice");
2353 }
2354
2355 #[tokio::test]
2356 async fn test_user_defined_tool() {
2357 let kernel = Kernel::transient().expect("failed to create kernel");
2358
2359 kernel
2361 .execute(r#"greet() { echo "Hello, $1!" }"#)
2362 .await
2363 .expect("function definition failed");
2364
2365 let result = kernel
2367 .execute(r#"greet "World""#)
2368 .await
2369 .expect("function call failed");
2370
2371 assert!(result.ok(), "greet failed: {}", result.err);
2372 assert_eq!(result.out.trim(), "Hello, World!");
2373 }
2374
2375 #[tokio::test]
2376 async fn test_user_tool_positional_args() {
2377 let kernel = Kernel::transient().expect("failed to create kernel");
2378
2379 kernel
2381 .execute(r#"greet() { echo "Hi $1" }"#)
2382 .await
2383 .expect("function definition failed");
2384
2385 let result = kernel
2387 .execute(r#"greet "Amy""#)
2388 .await
2389 .expect("function call failed");
2390
2391 assert!(result.ok(), "greet failed: {}", result.err);
2392 assert_eq!(result.out.trim(), "Hi Amy");
2393 }
2394
2395 #[tokio::test]
2396 async fn test_function_shared_scope() {
2397 let kernel = Kernel::transient().expect("failed to create kernel");
2398
2399 kernel
2401 .execute(r#"SECRET="hidden""#)
2402 .await
2403 .expect("set failed");
2404
2405 kernel
2407 .execute(r#"access_parent() {
2408 echo "${SECRET}"
2409 SECRET="modified"
2410 }"#)
2411 .await
2412 .expect("function definition failed");
2413
2414 let result = kernel.execute("access_parent").await.expect("function call failed");
2416
2417 assert!(
2419 result.out.contains("hidden"),
2420 "Function should access parent scope, got: {}",
2421 result.out
2422 );
2423
2424 let secret = kernel.get_var("SECRET").await;
2426 assert_eq!(
2427 secret,
2428 Some(Value::String("modified".into())),
2429 "Function should modify parent scope"
2430 );
2431 }
2432
2433 #[tokio::test]
2434 async fn test_exec_builtin() {
2435 let kernel = Kernel::transient().expect("failed to create kernel");
2436 let result = kernel
2438 .execute(r#"exec command="/bin/echo" argv="hello world""#)
2439 .await
2440 .expect("exec failed");
2441
2442 assert!(result.ok(), "exec failed: {}", result.err);
2443 assert_eq!(result.out.trim(), "hello world");
2444 }
2445
2446 #[tokio::test]
2447 async fn test_while_false_never_runs() {
2448 let kernel = Kernel::transient().expect("failed to create kernel");
2449
2450 let result = kernel
2452 .execute(r#"
2453 while false; do
2454 echo "should not run"
2455 done
2456 "#)
2457 .await
2458 .expect("while false failed");
2459
2460 assert!(result.ok());
2461 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
2462 }
2463
2464 #[tokio::test]
2465 async fn test_while_string_comparison() {
2466 let kernel = Kernel::transient().expect("failed to create kernel");
2467
2468 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
2470
2471 let result = kernel
2474 .execute(r#"
2475 while [[ ${FLAG} == "go" ]]; do
2476 FLAG="stop"
2477 echo "running"
2478 done
2479 "#)
2480 .await
2481 .expect("while with string cmp failed");
2482
2483 assert!(result.ok());
2484 assert!(result.out.contains("running"), "should have run once: {}", result.out);
2485
2486 let flag = kernel.get_var("FLAG").await;
2488 assert_eq!(flag, Some(Value::String("stop".into())));
2489 }
2490
2491 #[tokio::test]
2492 async fn test_while_numeric_comparison() {
2493 let kernel = Kernel::transient().expect("failed to create kernel");
2494
2495 kernel.execute("N=5").await.expect("set failed");
2497
2498 let result = kernel
2500 .execute(r#"
2501 while [[ ${N} -gt 3 ]]; do
2502 N=3
2503 echo "N was greater"
2504 done
2505 "#)
2506 .await
2507 .expect("while with > failed");
2508
2509 assert!(result.ok());
2510 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
2511 }
2512
2513 #[tokio::test]
2514 async fn test_break_in_while_loop() {
2515 let kernel = Kernel::transient().expect("failed to create kernel");
2516
2517 let result = kernel
2518 .execute(r#"
2519 I=0
2520 while true; do
2521 I=1
2522 echo "before break"
2523 break
2524 echo "after break"
2525 done
2526 "#)
2527 .await
2528 .expect("while with break failed");
2529
2530 assert!(result.ok());
2531 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
2532 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
2533
2534 let i = kernel.get_var("I").await;
2536 assert_eq!(i, Some(Value::Int(1)));
2537 }
2538
2539 #[tokio::test]
2540 async fn test_continue_in_while_loop() {
2541 let kernel = Kernel::transient().expect("failed to create kernel");
2542
2543 let result = kernel
2548 .execute(r#"
2549 STATE="start"
2550 AFTER_CONTINUE="no"
2551 while [[ ${STATE} != "done" ]]; do
2552 if [[ ${STATE} == "start" ]]; then
2553 STATE="middle"
2554 continue
2555 AFTER_CONTINUE="yes"
2556 fi
2557 if [[ ${STATE} == "middle" ]]; then
2558 STATE="done"
2559 fi
2560 done
2561 "#)
2562 .await
2563 .expect("while with continue failed");
2564
2565 assert!(result.ok());
2566
2567 let state = kernel.get_var("STATE").await;
2569 assert_eq!(state, Some(Value::String("done".into())));
2570
2571 let after = kernel.get_var("AFTER_CONTINUE").await;
2573 assert_eq!(after, Some(Value::String("no".into())));
2574 }
2575
2576 #[tokio::test]
2577 async fn test_break_with_level() {
2578 let kernel = Kernel::transient().expect("failed to create kernel");
2579
2580 let result = kernel
2585 .execute(r#"
2586 OUTER=0
2587 while true; do
2588 OUTER=1
2589 for X in "1 2"; do
2590 break 2
2591 done
2592 OUTER=2
2593 done
2594 "#)
2595 .await
2596 .expect("nested break failed");
2597
2598 assert!(result.ok());
2599
2600 let outer = kernel.get_var("OUTER").await;
2602 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
2603 }
2604
2605 #[tokio::test]
2606 async fn test_return_from_tool() {
2607 let kernel = Kernel::transient().expect("failed to create kernel");
2608
2609 kernel
2611 .execute(r#"early_return() {
2612 if [[ $1 == 1 ]]; then
2613 return 42
2614 fi
2615 echo "not returned"
2616 }"#)
2617 .await
2618 .expect("function definition failed");
2619
2620 let result = kernel
2623 .execute("early_return 1")
2624 .await
2625 .expect("function call failed");
2626
2627 assert_eq!(result.code, 42);
2629 assert!(result.out.is_empty());
2631 }
2632
2633 #[tokio::test]
2634 async fn test_return_without_value() {
2635 let kernel = Kernel::transient().expect("failed to create kernel");
2636
2637 kernel
2639 .execute(r#"early_exit() {
2640 if [[ $1 == "stop" ]]; then
2641 return
2642 fi
2643 echo "continued"
2644 }"#)
2645 .await
2646 .expect("function definition failed");
2647
2648 let result = kernel
2650 .execute(r#"early_exit "stop""#)
2651 .await
2652 .expect("function call failed");
2653
2654 assert!(result.ok());
2655 assert!(result.out.is_empty() || result.out.trim().is_empty());
2656 }
2657
2658 #[tokio::test]
2659 async fn test_exit_stops_execution() {
2660 let kernel = Kernel::transient().expect("failed to create kernel");
2661
2662 kernel
2664 .execute(r#"
2665 BEFORE="yes"
2666 exit 0
2667 AFTER="yes"
2668 "#)
2669 .await
2670 .expect("execution failed");
2671
2672 let before = kernel.get_var("BEFORE").await;
2674 assert_eq!(before, Some(Value::String("yes".into())));
2675
2676 let after = kernel.get_var("AFTER").await;
2677 assert!(after.is_none(), "AFTER should not be set after exit");
2678 }
2679
2680 #[tokio::test]
2681 async fn test_exit_with_code() {
2682 let kernel = Kernel::transient().expect("failed to create kernel");
2683
2684 let result = kernel
2686 .execute("exit 42")
2687 .await
2688 .expect("exit failed");
2689
2690 assert_eq!(result.out, "42");
2692 }
2693
2694 #[tokio::test]
2695 async fn test_set_e_stops_on_failure() {
2696 let kernel = Kernel::transient().expect("failed to create kernel");
2697
2698 kernel.execute("set -e").await.expect("set -e failed");
2700
2701 kernel
2703 .execute(r#"
2704 STEP1="done"
2705 false
2706 STEP2="done"
2707 "#)
2708 .await
2709 .expect("execution failed");
2710
2711 let step1 = kernel.get_var("STEP1").await;
2713 assert_eq!(step1, Some(Value::String("done".into())));
2714
2715 let step2 = kernel.get_var("STEP2").await;
2716 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
2717 }
2718
2719 #[tokio::test]
2720 async fn test_set_plus_e_disables_error_exit() {
2721 let kernel = Kernel::transient().expect("failed to create kernel");
2722
2723 kernel.execute("set -e").await.expect("set -e failed");
2725 kernel.execute("set +e").await.expect("set +e failed");
2726
2727 kernel
2729 .execute(r#"
2730 STEP1="done"
2731 false
2732 STEP2="done"
2733 "#)
2734 .await
2735 .expect("execution failed");
2736
2737 let step1 = kernel.get_var("STEP1").await;
2739 assert_eq!(step1, Some(Value::String("done".into())));
2740
2741 let step2 = kernel.get_var("STEP2").await;
2742 assert_eq!(step2, Some(Value::String("done".into())));
2743 }
2744
2745 #[tokio::test]
2746 async fn test_set_ignores_unknown_options() {
2747 let kernel = Kernel::transient().expect("failed to create kernel");
2748
2749 let result = kernel
2751 .execute("set -e -u -o pipefail")
2752 .await
2753 .expect("set with unknown options failed");
2754
2755 assert!(result.ok(), "set should succeed with unknown options");
2756
2757 kernel
2759 .execute(r#"
2760 BEFORE="yes"
2761 false
2762 AFTER="yes"
2763 "#)
2764 .await
2765 .ok();
2766
2767 let after = kernel.get_var("AFTER").await;
2768 assert!(after.is_none(), "-e should be enabled despite unknown options");
2769 }
2770
2771 #[tokio::test]
2772 async fn test_set_no_args_shows_settings() {
2773 let kernel = Kernel::transient().expect("failed to create kernel");
2774
2775 kernel.execute("set -e").await.expect("set -e failed");
2777
2778 let result = kernel.execute("set").await.expect("set failed");
2780
2781 assert!(result.ok());
2782 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
2783 }
2784
2785 #[tokio::test]
2786 async fn test_set_e_in_pipeline() {
2787 let kernel = Kernel::transient().expect("failed to create kernel");
2788
2789 kernel.execute("set -e").await.expect("set -e failed");
2790
2791 kernel
2793 .execute(r#"
2794 BEFORE="yes"
2795 false | cat
2796 AFTER="yes"
2797 "#)
2798 .await
2799 .ok();
2800
2801 let before = kernel.get_var("BEFORE").await;
2802 assert_eq!(before, Some(Value::String("yes".into())));
2803
2804 }
2809
2810 #[tokio::test]
2811 async fn test_set_e_with_and_chain() {
2812 let kernel = Kernel::transient().expect("failed to create kernel");
2813
2814 kernel.execute("set -e").await.expect("set -e failed");
2815
2816 kernel
2819 .execute(r#"
2820 RESULT="initial"
2821 false && RESULT="chained"
2822 RESULT="continued"
2823 "#)
2824 .await
2825 .ok();
2826
2827 let result = kernel.get_var("RESULT").await;
2830 assert!(result.is_some(), "RESULT should be set");
2833 }
2834
2835 #[tokio::test]
2840 async fn test_source_sets_variables() {
2841 let kernel = Kernel::transient().expect("failed to create kernel");
2842
2843 kernel
2845 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
2846 .await
2847 .expect("write failed");
2848
2849 let result = kernel
2851 .execute(r#"source "/test.kai""#)
2852 .await
2853 .expect("source failed");
2854
2855 assert!(result.ok(), "source should succeed");
2856
2857 let foo = kernel.get_var("FOO").await;
2859 assert_eq!(foo, Some(Value::String("bar".into())));
2860 }
2861
2862 #[tokio::test]
2863 async fn test_source_with_dot_alias() {
2864 let kernel = Kernel::transient().expect("failed to create kernel");
2865
2866 kernel
2868 .execute(r#"write "/vars.kai" 'X=42'"#)
2869 .await
2870 .expect("write failed");
2871
2872 let result = kernel
2874 .execute(r#". "/vars.kai""#)
2875 .await
2876 .expect(". failed");
2877
2878 assert!(result.ok(), ". should succeed");
2879
2880 let x = kernel.get_var("X").await;
2882 assert_eq!(x, Some(Value::Int(42)));
2883 }
2884
2885 #[tokio::test]
2886 async fn test_source_not_found() {
2887 let kernel = Kernel::transient().expect("failed to create kernel");
2888
2889 let result = kernel
2891 .execute(r#"source "/nonexistent.kai""#)
2892 .await
2893 .expect("source should not fail with error");
2894
2895 assert!(!result.ok(), "source of non-existent file should fail");
2896 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
2897 }
2898
2899 #[tokio::test]
2900 async fn test_source_missing_filename() {
2901 let kernel = Kernel::transient().expect("failed to create kernel");
2902
2903 let result = kernel
2905 .execute("source")
2906 .await
2907 .expect("source should not fail with error");
2908
2909 assert!(!result.ok(), "source without filename should fail");
2910 assert!(result.err.contains("missing filename"), "error should mention missing filename");
2911 }
2912
2913 #[tokio::test]
2914 async fn test_source_executes_multiple_statements() {
2915 let kernel = Kernel::transient().expect("failed to create kernel");
2916
2917 kernel
2919 .execute(r#"write "/multi.kai" 'A=1
2920B=2
2921C=3'"#)
2922 .await
2923 .expect("write failed");
2924
2925 kernel
2927 .execute(r#"source "/multi.kai""#)
2928 .await
2929 .expect("source failed");
2930
2931 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
2933 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
2934 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
2935 }
2936
2937 #[tokio::test]
2938 async fn test_source_can_define_functions() {
2939 let kernel = Kernel::transient().expect("failed to create kernel");
2940
2941 kernel
2943 .execute(r#"write "/functions.kai" 'greet() {
2944 echo "Hello, $1!"
2945}'"#)
2946 .await
2947 .expect("write failed");
2948
2949 kernel
2951 .execute(r#"source "/functions.kai""#)
2952 .await
2953 .expect("source failed");
2954
2955 let result = kernel
2957 .execute(r#"greet "World""#)
2958 .await
2959 .expect("greet failed");
2960
2961 assert!(result.ok());
2962 assert!(result.out.contains("Hello, World!"));
2963 }
2964
2965 #[tokio::test]
2966 async fn test_source_inherits_error_exit() {
2967 let kernel = Kernel::transient().expect("failed to create kernel");
2968
2969 kernel.execute("set -e").await.expect("set -e failed");
2971
2972 kernel
2974 .execute(r#"write "/fail.kai" 'BEFORE="yes"
2975false
2976AFTER="yes"'"#)
2977 .await
2978 .expect("write failed");
2979
2980 kernel
2982 .execute(r#"source "/fail.kai""#)
2983 .await
2984 .ok();
2985
2986 let before = kernel.get_var("BEFORE").await;
2988 assert_eq!(before, Some(Value::String("yes".into())));
2989
2990 }
2993
2994 #[tokio::test]
2999 async fn test_case_simple_match() {
3000 let kernel = Kernel::transient().expect("failed to create kernel");
3001
3002 let result = kernel
3003 .execute(r#"
3004 case "hello" in
3005 hello) echo "matched hello" ;;
3006 world) echo "matched world" ;;
3007 esac
3008 "#)
3009 .await
3010 .expect("case failed");
3011
3012 assert!(result.ok());
3013 assert_eq!(result.out.trim(), "matched hello");
3014 }
3015
3016 #[tokio::test]
3017 async fn test_case_wildcard_match() {
3018 let kernel = Kernel::transient().expect("failed to create kernel");
3019
3020 let result = kernel
3021 .execute(r#"
3022 case "main.rs" in
3023 "*.py") echo "Python" ;;
3024 "*.rs") echo "Rust" ;;
3025 "*") echo "Unknown" ;;
3026 esac
3027 "#)
3028 .await
3029 .expect("case failed");
3030
3031 assert!(result.ok());
3032 assert_eq!(result.out.trim(), "Rust");
3033 }
3034
3035 #[tokio::test]
3036 async fn test_case_default_match() {
3037 let kernel = Kernel::transient().expect("failed to create kernel");
3038
3039 let result = kernel
3040 .execute(r#"
3041 case "unknown.xyz" in
3042 "*.py") echo "Python" ;;
3043 "*.rs") echo "Rust" ;;
3044 "*") echo "Default" ;;
3045 esac
3046 "#)
3047 .await
3048 .expect("case failed");
3049
3050 assert!(result.ok());
3051 assert_eq!(result.out.trim(), "Default");
3052 }
3053
3054 #[tokio::test]
3055 async fn test_case_no_match() {
3056 let kernel = Kernel::transient().expect("failed to create kernel");
3057
3058 let result = kernel
3060 .execute(r#"
3061 case "nope" in
3062 "yes") echo "yes" ;;
3063 "no") echo "no" ;;
3064 esac
3065 "#)
3066 .await
3067 .expect("case failed");
3068
3069 assert!(result.ok());
3070 assert!(result.out.is_empty(), "no match should produce empty output");
3071 }
3072
3073 #[tokio::test]
3074 async fn test_case_with_variable() {
3075 let kernel = Kernel::transient().expect("failed to create kernel");
3076
3077 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
3078
3079 let result = kernel
3080 .execute(r#"
3081 case ${LANG} in
3082 python) echo "snake" ;;
3083 rust) echo "crab" ;;
3084 go) echo "gopher" ;;
3085 esac
3086 "#)
3087 .await
3088 .expect("case failed");
3089
3090 assert!(result.ok());
3091 assert_eq!(result.out.trim(), "crab");
3092 }
3093
3094 #[tokio::test]
3095 async fn test_case_multiple_patterns() {
3096 let kernel = Kernel::transient().expect("failed to create kernel");
3097
3098 let result = kernel
3099 .execute(r#"
3100 case "yes" in
3101 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
3102 "n"|"no"|"N"|"NO") echo "negative" ;;
3103 esac
3104 "#)
3105 .await
3106 .expect("case failed");
3107
3108 assert!(result.ok());
3109 assert_eq!(result.out.trim(), "affirmative");
3110 }
3111
3112 #[tokio::test]
3113 async fn test_case_glob_question_mark() {
3114 let kernel = Kernel::transient().expect("failed to create kernel");
3115
3116 let result = kernel
3117 .execute(r#"
3118 case "test1" in
3119 "test?") echo "matched test?" ;;
3120 "*") echo "default" ;;
3121 esac
3122 "#)
3123 .await
3124 .expect("case failed");
3125
3126 assert!(result.ok());
3127 assert_eq!(result.out.trim(), "matched test?");
3128 }
3129
3130 #[tokio::test]
3131 async fn test_case_char_class() {
3132 let kernel = Kernel::transient().expect("failed to create kernel");
3133
3134 let result = kernel
3135 .execute(r#"
3136 case "Yes" in
3137 "[Yy]*") echo "yes-like" ;;
3138 "[Nn]*") echo "no-like" ;;
3139 esac
3140 "#)
3141 .await
3142 .expect("case failed");
3143
3144 assert!(result.ok());
3145 assert_eq!(result.out.trim(), "yes-like");
3146 }
3147
3148 #[tokio::test]
3153 async fn test_cat_from_pipeline() {
3154 let kernel = Kernel::transient().expect("failed to create kernel");
3155
3156 let result = kernel
3157 .execute(r#"echo "piped text" | cat"#)
3158 .await
3159 .expect("cat pipeline failed");
3160
3161 assert!(result.ok(), "cat failed: {}", result.err);
3162 assert_eq!(result.out.trim(), "piped text");
3163 }
3164
3165 #[tokio::test]
3166 async fn test_cat_from_pipeline_multiline() {
3167 let kernel = Kernel::transient().expect("failed to create kernel");
3168
3169 let result = kernel
3170 .execute(r#"echo "line1\nline2" | cat -n"#)
3171 .await
3172 .expect("cat pipeline failed");
3173
3174 assert!(result.ok(), "cat failed: {}", result.err);
3175 assert!(result.out.contains("1\t"), "output: {}", result.out);
3176 }
3177
3178 #[tokio::test]
3183 async fn test_heredoc_basic() {
3184 let kernel = Kernel::transient().expect("failed to create kernel");
3185
3186 let result = kernel
3187 .execute("cat <<EOF\nhello\nEOF")
3188 .await
3189 .expect("heredoc failed");
3190
3191 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3192 assert_eq!(result.out.trim(), "hello");
3193 }
3194
3195 #[tokio::test]
3196 async fn test_arithmetic_in_string() {
3197 let kernel = Kernel::transient().expect("failed to create kernel");
3198
3199 let result = kernel
3200 .execute(r#"echo "result: $((1 + 2))""#)
3201 .await
3202 .expect("arithmetic in string failed");
3203
3204 assert!(result.ok(), "echo failed: {}", result.err);
3205 assert_eq!(result.out.trim(), "result: 3");
3206 }
3207
3208 #[tokio::test]
3209 async fn test_heredoc_multiline() {
3210 let kernel = Kernel::transient().expect("failed to create kernel");
3211
3212 let result = kernel
3213 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
3214 .await
3215 .expect("heredoc failed");
3216
3217 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3218 assert!(result.out.contains("line1"), "output: {}", result.out);
3219 assert!(result.out.contains("line2"), "output: {}", result.out);
3220 assert!(result.out.contains("line3"), "output: {}", result.out);
3221 }
3222
3223 #[tokio::test]
3228 async fn test_read_from_pipeline() {
3229 let kernel = Kernel::transient().expect("failed to create kernel");
3230
3231 let result = kernel
3233 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
3234 .await
3235 .expect("read pipeline failed");
3236
3237 assert!(result.ok(), "read failed: {}", result.err);
3238 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
3239 }
3240
3241 #[tokio::test]
3242 async fn test_read_multiple_vars_from_pipeline() {
3243 let kernel = Kernel::transient().expect("failed to create kernel");
3244
3245 let result = kernel
3246 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
3247 .await
3248 .expect("read pipeline failed");
3249
3250 assert!(result.ok(), "read failed: {}", result.err);
3251 assert!(result.out.contains("John is 42"), "output: {}", result.out);
3252 }
3253
3254 #[tokio::test]
3259 async fn test_posix_function_with_positional_params() {
3260 let kernel = Kernel::transient().expect("failed to create kernel");
3261
3262 kernel
3264 .execute(r#"greet() { echo "Hello, $1!" }"#)
3265 .await
3266 .expect("function definition failed");
3267
3268 let result = kernel
3270 .execute(r#"greet "Amy""#)
3271 .await
3272 .expect("function call failed");
3273
3274 assert!(result.ok(), "greet failed: {}", result.err);
3275 assert_eq!(result.out.trim(), "Hello, Amy!");
3276 }
3277
3278 #[tokio::test]
3279 async fn test_posix_function_multiple_args() {
3280 let kernel = Kernel::transient().expect("failed to create kernel");
3281
3282 kernel
3284 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
3285 .await
3286 .expect("function definition failed");
3287
3288 let result = kernel
3290 .execute(r#"add_greeting "Hello" "World""#)
3291 .await
3292 .expect("function call failed");
3293
3294 assert!(result.ok(), "function failed: {}", result.err);
3295 assert_eq!(result.out.trim(), "Hello World!");
3296 }
3297
3298 #[tokio::test]
3299 async fn test_bash_function_with_positional_params() {
3300 let kernel = Kernel::transient().expect("failed to create kernel");
3301
3302 kernel
3304 .execute(r#"function greet { echo "Hi $1" }"#)
3305 .await
3306 .expect("function definition failed");
3307
3308 let result = kernel
3310 .execute(r#"greet "Bob""#)
3311 .await
3312 .expect("function call failed");
3313
3314 assert!(result.ok(), "greet failed: {}", result.err);
3315 assert_eq!(result.out.trim(), "Hi Bob");
3316 }
3317
3318 #[tokio::test]
3319 async fn test_shell_function_with_all_args() {
3320 let kernel = Kernel::transient().expect("failed to create kernel");
3321
3322 kernel
3324 .execute(r#"echo_all() { echo "args: $@" }"#)
3325 .await
3326 .expect("function definition failed");
3327
3328 let result = kernel
3330 .execute(r#"echo_all "a" "b" "c""#)
3331 .await
3332 .expect("function call failed");
3333
3334 assert!(result.ok(), "function failed: {}", result.err);
3335 assert_eq!(result.out.trim(), "args: a b c");
3336 }
3337
3338 #[tokio::test]
3339 async fn test_shell_function_with_arg_count() {
3340 let kernel = Kernel::transient().expect("failed to create kernel");
3341
3342 kernel
3344 .execute(r#"count_args() { echo "count: $#" }"#)
3345 .await
3346 .expect("function definition failed");
3347
3348 let result = kernel
3350 .execute(r#"count_args "x" "y" "z""#)
3351 .await
3352 .expect("function call failed");
3353
3354 assert!(result.ok(), "function failed: {}", result.err);
3355 assert_eq!(result.out.trim(), "count: 3");
3356 }
3357
3358 #[tokio::test]
3359 async fn test_shell_function_shared_scope() {
3360 let kernel = Kernel::transient().expect("failed to create kernel");
3361
3362 kernel
3364 .execute(r#"PARENT_VAR="visible""#)
3365 .await
3366 .expect("set failed");
3367
3368 kernel
3370 .execute(r#"modify_parent() {
3371 echo "saw: ${PARENT_VAR}"
3372 PARENT_VAR="changed by function"
3373 }"#)
3374 .await
3375 .expect("function definition failed");
3376
3377 let result = kernel.execute("modify_parent").await.expect("function failed");
3379
3380 assert!(
3381 result.out.contains("visible"),
3382 "Shell function should access parent scope, got: {}",
3383 result.out
3384 );
3385
3386 let var = kernel.get_var("PARENT_VAR").await;
3388 assert_eq!(
3389 var,
3390 Some(Value::String("changed by function".into())),
3391 "Shell function should modify parent scope"
3392 );
3393 }
3394
3395 #[tokio::test]
3400 async fn test_script_execution_from_path() {
3401 let kernel = Kernel::transient().expect("failed to create kernel");
3402
3403 kernel.execute(r#"mkdir "/bin""#).await.ok();
3405 kernel
3406 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
3407 .await
3408 .expect("write script failed");
3409
3410 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3412
3413 let result = kernel
3415 .execute("hello")
3416 .await
3417 .expect("script execution failed");
3418
3419 assert!(result.ok(), "script failed: {}", result.err);
3420 assert_eq!(result.out.trim(), "Hello from script!");
3421 }
3422
3423 #[tokio::test]
3424 async fn test_script_with_args() {
3425 let kernel = Kernel::transient().expect("failed to create kernel");
3426
3427 kernel.execute(r#"mkdir "/bin""#).await.ok();
3429 kernel
3430 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
3431 .await
3432 .expect("write script failed");
3433
3434 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3436
3437 let result = kernel
3439 .execute(r#"greet "World""#)
3440 .await
3441 .expect("script execution failed");
3442
3443 assert!(result.ok(), "script failed: {}", result.err);
3444 assert_eq!(result.out.trim(), "Hello, World!");
3445 }
3446
3447 #[tokio::test]
3448 async fn test_script_not_found() {
3449 let kernel = Kernel::transient().expect("failed to create kernel");
3450
3451 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
3453
3454 let result = kernel
3456 .execute("noscript")
3457 .await
3458 .expect("execution failed");
3459
3460 assert!(!result.ok(), "should fail with command not found");
3461 assert_eq!(result.code, 127);
3462 assert!(result.err.contains("command not found"));
3463 }
3464
3465 #[tokio::test]
3466 async fn test_script_path_search_order() {
3467 let kernel = Kernel::transient().expect("failed to create kernel");
3468
3469 kernel.execute(r#"mkdir "/first""#).await.ok();
3472 kernel.execute(r#"mkdir "/second""#).await.ok();
3473 kernel
3474 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
3475 .await
3476 .expect("write failed");
3477 kernel
3478 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
3479 .await
3480 .expect("write failed");
3481
3482 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
3484
3485 let result = kernel
3487 .execute("myscript")
3488 .await
3489 .expect("script execution failed");
3490
3491 assert!(result.ok(), "script failed: {}", result.err);
3492 assert_eq!(result.out.trim(), "from first");
3493 }
3494
3495 #[tokio::test]
3500 async fn test_last_exit_code_success() {
3501 let kernel = Kernel::transient().expect("failed to create kernel");
3502
3503 let result = kernel.execute("true; echo $?").await.expect("execution failed");
3505 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
3506 }
3507
3508 #[tokio::test]
3509 async fn test_last_exit_code_failure() {
3510 let kernel = Kernel::transient().expect("failed to create kernel");
3511
3512 let result = kernel.execute("false; echo $?").await.expect("execution failed");
3514 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
3515 }
3516
3517 #[tokio::test]
3518 async fn test_current_pid() {
3519 let kernel = Kernel::transient().expect("failed to create kernel");
3520
3521 let result = kernel.execute("echo $$").await.expect("execution failed");
3522 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
3524 assert!(pid > 0, "PID should be positive");
3525 }
3526
3527 #[tokio::test]
3528 async fn test_unset_variable_expands_to_empty() {
3529 let kernel = Kernel::transient().expect("failed to create kernel");
3530
3531 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
3533 assert_eq!(result.out.trim(), "prefix::suffix");
3534 }
3535
3536 #[tokio::test]
3537 async fn test_eq_ne_operators() {
3538 let kernel = Kernel::transient().expect("failed to create kernel");
3539
3540 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
3542 assert_eq!(result.out.trim(), "eq works");
3543
3544 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
3546 assert_eq!(result.out.trim(), "ne works");
3547
3548 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
3550 assert_eq!(result.out.trim(), "correct");
3551 }
3552
3553 #[tokio::test]
3554 async fn test_escaped_dollar_in_string() {
3555 let kernel = Kernel::transient().expect("failed to create kernel");
3556
3557 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
3559 assert_eq!(result.out.trim(), "$100");
3560 }
3561
3562 #[tokio::test]
3563 async fn test_special_vars_in_interpolation() {
3564 let kernel = Kernel::transient().expect("failed to create kernel");
3565
3566 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
3568 assert_eq!(result.out.trim(), "exit: 0");
3569
3570 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
3572 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
3573 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
3574 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
3575 }
3576
3577 #[tokio::test]
3582 async fn test_command_subst_assignment() {
3583 let kernel = Kernel::transient().expect("failed to create kernel");
3584
3585 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
3587 assert_eq!(result.out.trim(), "hello");
3588 }
3589
3590 #[tokio::test]
3591 async fn test_command_subst_with_args() {
3592 let kernel = Kernel::transient().expect("failed to create kernel");
3593
3594 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
3596 assert_eq!(result.out.trim(), "a b c");
3597 }
3598
3599 #[tokio::test]
3600 async fn test_command_subst_nested_vars() {
3601 let kernel = Kernel::transient().expect("failed to create kernel");
3602
3603 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
3605 assert_eq!(result.out.trim(), "hello world");
3606 }
3607
3608 #[tokio::test]
3609 async fn test_background_job_basic() {
3610 use std::time::Duration;
3611
3612 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
3613
3614 let result = kernel.execute("echo hello &").await.expect("execution failed");
3616 assert!(result.ok(), "background command should succeed: {}", result.err);
3617 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
3618
3619 tokio::time::sleep(Duration::from_millis(100)).await;
3621
3622 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
3624 assert!(status.ok(), "status should succeed: {}", status.err);
3625 assert!(
3626 status.out.contains("done:") || status.out.contains("running"),
3627 "should have valid status: {}",
3628 status.out
3629 );
3630
3631 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
3633 assert!(stdout.ok());
3634 assert!(stdout.out.contains("hello"));
3635 }
3636
3637}