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, Stmt, StringPart, 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_string, ControlFlow, ExecResult, Scope};
39use crate::parser::parse;
40use crate::scheduler::{drain_to_stream, is_bool_type, schema_param_lookup, BoundedStream, JobManager, PipelineRunner, 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::{JobFs, LocalFs, MemoryFs, VfsRouter};
44
45#[derive(Debug, Clone)]
52pub enum VfsMountMode {
53 Passthrough,
62
63 Sandboxed {
75 root: Option<PathBuf>,
78 },
79
80 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 Self::assemble(config, vfs, jobs, |vfs_ref, tools| {
289 ExecContext::with_vfs_and_tools(vfs_ref.clone(), tools.clone())
290 })
291 }
292
293 fn setup_vfs(config: &KernelConfig) -> VfsRouter {
295 let mut vfs = VfsRouter::new();
296
297 match &config.vfs_mode {
298 VfsMountMode::Passthrough => {
299 vfs.mount("/", LocalFs::new(PathBuf::from("/")));
301 vfs.mount("/v", MemoryFs::new());
303 }
304 VfsMountMode::Sandboxed { root } => {
305 vfs.mount("/", MemoryFs::new());
307 vfs.mount("/v", MemoryFs::new());
308
309 vfs.mount("/tmp", LocalFs::new(PathBuf::from("/tmp")));
311
312 let local_root = root.clone().unwrap_or_else(|| {
314 std::env::var("HOME")
315 .map(PathBuf::from)
316 .unwrap_or_else(|_| PathBuf::from("/"))
317 });
318
319 let mount_point = local_root.to_string_lossy().to_string();
323 vfs.mount(&mount_point, LocalFs::new(local_root));
324 }
325 VfsMountMode::NoLocal => {
326 vfs.mount("/", MemoryFs::new());
328 vfs.mount("/tmp", MemoryFs::new());
329 vfs.mount("/v", MemoryFs::new());
330 }
331 }
332
333 vfs
334 }
335
336 pub fn transient() -> Result<Self> {
338 Self::new(KernelConfig::transient())
339 }
340
341 pub fn with_backend(
370 backend: Arc<dyn KernelBackend>,
371 config: KernelConfig,
372 configure_vfs: impl FnOnce(&mut VfsRouter),
373 ) -> Result<Self> {
374 use crate::backend::VirtualOverlayBackend;
375
376 let mut vfs = VfsRouter::new();
377 let jobs = Arc::new(JobManager::new());
378
379 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
380 vfs.mount("/v/blobs", MemoryFs::new());
381
382 configure_vfs(&mut vfs);
384
385 let vfs = Arc::new(vfs);
386
387 let overlay: Arc<dyn KernelBackend> =
388 Arc::new(VirtualOverlayBackend::new(backend, vfs.clone()));
389
390 Self::assemble(config, vfs, jobs, |_: &Arc<VfsRouter>, _: &Arc<ToolRegistry>| {
391 ExecContext::with_backend(overlay)
392 })
393 }
394
395 fn assemble(
401 config: KernelConfig,
402 vfs: Arc<VfsRouter>,
403 jobs: Arc<JobManager>,
404 make_ctx: impl FnOnce(&Arc<VfsRouter>, &Arc<ToolRegistry>) -> ExecContext,
405 ) -> Result<Self> {
406 let KernelConfig { name, cwd, skip_validation, interactive, .. } = config;
407
408 let mut tools = ToolRegistry::new();
409 register_builtins(&mut tools);
410 let tools = Arc::new(tools);
411
412 let runner = PipelineRunner::new(tools.clone());
413
414 let mut exec_ctx = make_ctx(&vfs, &tools);
415 exec_ctx.set_cwd(cwd);
416 exec_ctx.set_job_manager(jobs.clone());
417 exec_ctx.set_tool_schemas(tools.schemas());
418 exec_ctx.set_tools(tools.clone());
419
420 Ok(Self {
421 name,
422 scope: RwLock::new(Scope::new()),
423 tools,
424 user_tools: RwLock::new(HashMap::new()),
425 vfs,
426 jobs,
427 runner,
428 exec_ctx: RwLock::new(exec_ctx),
429 skip_validation,
430 interactive,
431 })
432 }
433
434 pub fn name(&self) -> &str {
436 &self.name
437 }
438
439 pub async fn execute(&self, input: &str) -> Result<ExecResult> {
443 self.execute_streaming(input, &mut |_| {}).await
444 }
445
446 pub async fn execute_streaming(
455 &self,
456 input: &str,
457 on_output: &mut dyn FnMut(&ExecResult),
458 ) -> Result<ExecResult> {
459 let program = parse(input).map_err(|errors| {
460 let msg = errors
461 .iter()
462 .map(|e| e.to_string())
463 .collect::<Vec<_>>()
464 .join("; ");
465 anyhow::anyhow!("parse error: {}", msg)
466 })?;
467
468 if !self.skip_validation {
470 let user_tools = self.user_tools.read().await;
471 let validator = Validator::new(&self.tools, &user_tools);
472 let issues = validator.validate(&program);
473
474 let errors: Vec<_> = issues
476 .iter()
477 .filter(|i| i.severity == Severity::Error)
478 .collect();
479
480 if !errors.is_empty() {
481 let error_msg = errors
482 .iter()
483 .map(|e| e.format(input))
484 .collect::<Vec<_>>()
485 .join("\n");
486 return Err(anyhow::anyhow!("validation failed:\n{}", error_msg));
487 }
488
489 for warning in issues.iter().filter(|i| i.severity == Severity::Warning) {
491 tracing::trace!("validation: {}", warning.format(input));
492 }
493 }
494
495 let mut result = ExecResult::success("");
496
497 for stmt in program.statements {
498 if matches!(stmt, Stmt::Empty) {
499 continue;
500 }
501 let flow = self.execute_stmt_flow(&stmt).await?;
502 match flow {
503 ControlFlow::Normal(r) => {
504 on_output(&r);
505 let last_output = r.output.clone();
509 accumulate_result(&mut result, &r);
510 result.output = last_output;
511 }
512 ControlFlow::Exit { code } => {
513 let exit_result = ExecResult::success(code.to_string());
514 return Ok(exit_result);
515 }
516 ControlFlow::Return { value } => {
517 on_output(&value);
518 result = value;
519 }
520 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
521 on_output(&r);
522 result = r;
523 }
524 }
525 }
526
527 Ok(result)
528 }
529
530 fn execute_stmt_flow<'a>(
532 &'a self,
533 stmt: &'a Stmt,
534 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ControlFlow>> + Send + 'a>> {
535 Box::pin(async move {
536 match stmt {
537 Stmt::Assignment(assign) => {
538 let value = self.eval_expr_async(&assign.value).await
540 .context("failed to evaluate assignment")?;
541 let mut scope = self.scope.write().await;
542 if assign.local {
543 scope.set(&assign.name, value.clone());
545 } else {
546 scope.set_global(&assign.name, value.clone());
548 }
549 drop(scope);
550
551 Ok(ControlFlow::ok(ExecResult::success("")))
553 }
554 Stmt::Command(cmd) => {
555 let pipeline = crate::ast::Pipeline {
558 commands: vec![cmd.clone()],
559 background: false,
560 };
561 let result = self.execute_pipeline(&pipeline).await?;
562 self.update_last_result(&result).await;
563
564 if !result.ok() {
566 let scope = self.scope.read().await;
567 if scope.error_exit_enabled() {
568 return Ok(ControlFlow::exit_code(result.code));
569 }
570 }
571
572 Ok(ControlFlow::ok(result))
573 }
574 Stmt::Pipeline(pipeline) => {
575 let result = self.execute_pipeline(pipeline).await?;
576 self.update_last_result(&result).await;
577
578 if !result.ok() {
580 let scope = self.scope.read().await;
581 if scope.error_exit_enabled() {
582 return Ok(ControlFlow::exit_code(result.code));
583 }
584 }
585
586 Ok(ControlFlow::ok(result))
587 }
588 Stmt::If(if_stmt) => {
589 let cond_value = self.eval_expr_async(&if_stmt.condition).await?;
591
592 let branch = if is_truthy(&cond_value) {
593 &if_stmt.then_branch
594 } else {
595 if_stmt.else_branch.as_deref().unwrap_or(&[])
596 };
597
598 let mut flow = ControlFlow::ok(ExecResult::success(""));
599 for stmt in branch {
600 flow = self.execute_stmt_flow(stmt).await?;
601 if !flow.is_normal() {
602 return Ok(flow);
603 }
604 }
605 Ok(flow)
606 }
607 Stmt::For(for_loop) => {
608 let mut items: Vec<Value> = Vec::new();
611 for item_expr in &for_loop.items {
612 let item = self.eval_expr_async(item_expr).await?;
613 match &item {
615 Value::Json(serde_json::Value::Array(arr)) => {
617 for elem in arr {
618 items.push(json_to_value(elem.clone()));
619 }
620 }
621 Value::String(_) => {
624 items.push(item);
625 }
626 _ => items.push(item),
628 }
629 }
630
631 let mut result = ExecResult::success("");
632 {
633 let mut scope = self.scope.write().await;
634 scope.push_frame();
635 }
636
637 'outer: for item in items {
638 {
639 let mut scope = self.scope.write().await;
640 scope.set(&for_loop.variable, item);
641 }
642 for stmt in &for_loop.body {
643 let mut flow = self.execute_stmt_flow(stmt).await?;
644 match &mut flow {
645 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
646 ControlFlow::Break { .. } => {
647 if flow.decrement_level() {
648 break 'outer;
650 }
651 let mut scope = self.scope.write().await;
653 scope.pop_frame();
654 return Ok(flow);
655 }
656 ControlFlow::Continue { .. } => {
657 if flow.decrement_level() {
658 continue 'outer;
660 }
661 let mut scope = self.scope.write().await;
663 scope.pop_frame();
664 return Ok(flow);
665 }
666 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
667 let mut scope = self.scope.write().await;
668 scope.pop_frame();
669 return Ok(flow);
670 }
671 }
672 }
673 }
674
675 {
676 let mut scope = self.scope.write().await;
677 scope.pop_frame();
678 }
679 Ok(ControlFlow::ok(result))
680 }
681 Stmt::While(while_loop) => {
682 let mut result = ExecResult::success("");
683
684 'outer: loop {
685 let cond_value = self.eval_expr_async(&while_loop.condition).await?;
687
688 if !is_truthy(&cond_value) {
689 break;
690 }
691
692 for stmt in &while_loop.body {
694 let mut flow = self.execute_stmt_flow(stmt).await?;
695 match &mut flow {
696 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
697 ControlFlow::Break { .. } => {
698 if flow.decrement_level() {
699 break 'outer;
701 }
702 return Ok(flow);
704 }
705 ControlFlow::Continue { .. } => {
706 if flow.decrement_level() {
707 continue 'outer;
709 }
710 return Ok(flow);
712 }
713 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
714 return Ok(flow);
715 }
716 }
717 }
718 }
719
720 Ok(ControlFlow::ok(result))
721 }
722 Stmt::Case(case_stmt) => {
723 let match_value = {
725 let mut scope = self.scope.write().await;
726 let value = eval_expr(&case_stmt.expr, &mut scope)?;
727 value_to_string(&value)
728 };
729
730 for branch in &case_stmt.branches {
732 let matched = branch.patterns.iter().any(|pattern| {
733 glob_match(pattern, &match_value)
734 });
735
736 if matched {
737 let mut result = ControlFlow::ok(ExecResult::success(""));
739 for stmt in &branch.body {
740 result = self.execute_stmt_flow(stmt).await?;
741 if !result.is_normal() {
742 return Ok(result);
743 }
744 }
745 return Ok(result);
746 }
747 }
748
749 Ok(ControlFlow::ok(ExecResult::success("")))
751 }
752 Stmt::Break(levels) => {
753 Ok(ControlFlow::break_n(levels.unwrap_or(1)))
754 }
755 Stmt::Continue(levels) => {
756 Ok(ControlFlow::continue_n(levels.unwrap_or(1)))
757 }
758 Stmt::Return(expr) => {
759 let result = if let Some(e) = expr {
762 let mut scope = self.scope.write().await;
763 let val = eval_expr(e, &mut scope)?;
764 let code = match val {
766 Value::Int(n) => n,
767 Value::Bool(b) => if b { 0 } else { 1 },
768 _ => 0,
769 };
770 ExecResult {
771 code,
772 out: String::new(),
773 err: String::new(),
774 data: None,
775 output: None,
776 }
777 } else {
778 ExecResult::success("")
779 };
780 Ok(ControlFlow::return_value(result))
781 }
782 Stmt::Exit(expr) => {
783 let code = if let Some(e) = expr {
784 let mut scope = self.scope.write().await;
785 let val = eval_expr(e, &mut scope)?;
786 match val {
787 Value::Int(n) => n,
788 _ => 0,
789 }
790 } else {
791 0
792 };
793 Ok(ControlFlow::exit_code(code))
794 }
795 Stmt::ToolDef(tool_def) => {
796 let mut user_tools = self.user_tools.write().await;
797 user_tools.insert(tool_def.name.clone(), tool_def.clone());
798 Ok(ControlFlow::ok(ExecResult::success("")))
799 }
800 Stmt::AndChain { left, right } => {
801 let left_flow = self.execute_stmt_flow(left).await?;
803 match left_flow {
804 ControlFlow::Normal(left_result) => {
805 self.update_last_result(&left_result).await;
806 if left_result.ok() {
807 let right_flow = self.execute_stmt_flow(right).await?;
808 match right_flow {
809 ControlFlow::Normal(right_result) => {
810 self.update_last_result(&right_result).await;
811 let mut combined = left_result;
813 accumulate_result(&mut combined, &right_result);
814 Ok(ControlFlow::ok(combined))
815 }
816 other => Ok(other), }
818 } else {
819 Ok(ControlFlow::ok(left_result))
820 }
821 }
822 _ => Ok(left_flow), }
824 }
825 Stmt::OrChain { left, right } => {
826 let left_flow = self.execute_stmt_flow(left).await?;
828 match left_flow {
829 ControlFlow::Normal(left_result) => {
830 self.update_last_result(&left_result).await;
831 if !left_result.ok() {
832 let right_flow = self.execute_stmt_flow(right).await?;
833 match right_flow {
834 ControlFlow::Normal(right_result) => {
835 self.update_last_result(&right_result).await;
836 let mut combined = left_result;
838 accumulate_result(&mut combined, &right_result);
839 Ok(ControlFlow::ok(combined))
840 }
841 other => Ok(other), }
843 } else {
844 Ok(ControlFlow::ok(left_result))
845 }
846 }
847 _ => Ok(left_flow), }
849 }
850 Stmt::Test(test_expr) => {
851 let expr = crate::ast::Expr::Test(Box::new(test_expr.clone()));
853 let mut scope = self.scope.write().await;
854 let value = eval_expr(&expr, &mut scope)?;
855 drop(scope);
856 let is_true = match value {
857 crate::ast::Value::Bool(b) => b,
858 _ => false,
859 };
860 if is_true {
861 Ok(ControlFlow::ok(ExecResult::success("")))
862 } else {
863 Ok(ControlFlow::ok(ExecResult::failure(1, "")))
864 }
865 }
866 Stmt::Empty => Ok(ControlFlow::ok(ExecResult::success(""))),
867 }
868 })
869 }
870
871 async fn execute_pipeline(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
873 if pipeline.commands.is_empty() {
874 return Ok(ExecResult::success(""));
875 }
876
877 if pipeline.background {
879 return self.execute_background(pipeline).await;
880 }
881
882 let mut ctx = {
890 let ec = self.exec_ctx.read().await;
891 let scope = self.scope.read().await;
892 ExecContext {
893 backend: ec.backend.clone(),
894 scope: scope.clone(),
895 cwd: ec.cwd.clone(),
896 prev_cwd: ec.prev_cwd.clone(),
897 stdin: None,
898 stdin_data: None,
899 tool_schemas: ec.tool_schemas.clone(),
900 tools: ec.tools.clone(),
901 job_manager: ec.job_manager.clone(),
902 pipeline_position: PipelinePosition::Only,
903 }
904 }; let result = self.runner.run(&pipeline.commands, &mut ctx, self).await;
907
908 {
910 let mut ec = self.exec_ctx.write().await;
911 ec.cwd = ctx.cwd.clone();
912 ec.prev_cwd = ctx.prev_cwd.clone();
913 }
914 {
915 let mut scope = self.scope.write().await;
916 *scope = ctx.scope.clone();
917 }
918
919 Ok(result)
920 }
921
922 async fn execute_background(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
930 use tokio::sync::oneshot;
931
932 let command_str = self.format_pipeline(pipeline);
934
935 let stdout = Arc::new(BoundedStream::default_size());
937 let stderr = Arc::new(BoundedStream::default_size());
938
939 let (tx, rx) = oneshot::channel();
941
942 let job_id = self.jobs.register_with_streams(
944 command_str.clone(),
945 rx,
946 stdout.clone(),
947 stderr.clone(),
948 ).await;
949
950 let runner = self.runner.clone();
952 let commands = pipeline.commands.clone();
953 let backend = {
954 let ctx = self.exec_ctx.read().await;
955 ctx.backend.clone()
956 };
957 let scope = {
958 let scope = self.scope.read().await;
959 scope.clone()
960 };
961 let cwd = {
962 let ctx = self.exec_ctx.read().await;
963 ctx.cwd.clone()
964 };
965 let tools = self.tools.clone();
966 let tool_schemas = self.tools.schemas();
967
968 tokio::spawn(async move {
970 let mut bg_ctx = ExecContext::with_backend(backend);
973 bg_ctx.scope = scope;
974 bg_ctx.cwd = cwd;
975 bg_ctx.set_tools(tools.clone());
976 bg_ctx.set_tool_schemas(tool_schemas);
977
978 let dispatcher = crate::dispatch::BackendDispatcher::new(tools);
981
982 let result = runner.run(&commands, &mut bg_ctx, &dispatcher).await;
984
985 if !result.out.is_empty() {
987 stdout.write(result.out.as_bytes()).await;
988 }
989 if !result.err.is_empty() {
990 stderr.write(result.err.as_bytes()).await;
991 }
992
993 stdout.close().await;
995 stderr.close().await;
996
997 let _ = tx.send(result);
999 });
1000
1001 Ok(ExecResult::success(format!("[{}]", job_id)))
1002 }
1003
1004 fn format_pipeline(&self, pipeline: &crate::ast::Pipeline) -> String {
1006 pipeline.commands
1007 .iter()
1008 .map(|cmd| {
1009 let mut parts = vec![cmd.name.clone()];
1010 for arg in &cmd.args {
1011 match arg {
1012 Arg::Positional(expr) => {
1013 parts.push(self.format_expr(expr));
1014 }
1015 Arg::Named { key, value } => {
1016 parts.push(format!("{}={}", key, self.format_expr(value)));
1017 }
1018 Arg::ShortFlag(name) => {
1019 parts.push(format!("-{}", name));
1020 }
1021 Arg::LongFlag(name) => {
1022 parts.push(format!("--{}", name));
1023 }
1024 Arg::DoubleDash => {
1025 parts.push("--".to_string());
1026 }
1027 }
1028 }
1029 parts.join(" ")
1030 })
1031 .collect::<Vec<_>>()
1032 .join(" | ")
1033 }
1034
1035 fn format_expr(&self, expr: &Expr) -> String {
1037 match expr {
1038 Expr::Literal(Value::String(s)) => {
1039 if s.contains(' ') || s.contains('"') {
1040 format!("'{}'", s.replace('\'', "\\'"))
1041 } else {
1042 s.clone()
1043 }
1044 }
1045 Expr::Literal(Value::Int(i)) => i.to_string(),
1046 Expr::Literal(Value::Float(f)) => f.to_string(),
1047 Expr::Literal(Value::Bool(b)) => b.to_string(),
1048 Expr::Literal(Value::Null) => "null".to_string(),
1049 Expr::VarRef(path) => {
1050 let name = path.segments.iter()
1051 .map(|seg| match seg {
1052 crate::ast::VarSegment::Field(f) => f.clone(),
1053 })
1054 .collect::<Vec<_>>()
1055 .join(".");
1056 format!("${{{}}}", name)
1057 }
1058 Expr::Interpolated(_) => "\"...\"".to_string(),
1059 _ => "...".to_string(),
1060 }
1061 }
1062
1063 async fn execute_command(&self, name: &str, args: &[Arg]) -> Result<ExecResult> {
1065 match name {
1067 "true" => return Ok(ExecResult::success("")),
1068 "false" => return Ok(ExecResult::failure(1, "")),
1069 "source" | "." => return self.execute_source(args).await,
1070 _ => {}
1071 }
1072
1073 {
1075 let user_tools = self.user_tools.read().await;
1076 if let Some(tool_def) = user_tools.get(name) {
1077 let tool_def = tool_def.clone();
1078 drop(user_tools);
1079 return self.execute_user_tool(tool_def, args).await;
1080 }
1081 }
1082
1083 let tool = match self.tools.get(name) {
1085 Some(t) => t,
1086 None => {
1087 if let Some(result) = self.try_execute_script(name, args).await? {
1089 return Ok(result);
1090 }
1091 if let Some(result) = self.try_execute_external(name, args).await? {
1093 return Ok(result);
1094 }
1095
1096 let tool_args = self.build_args_async(args, None).await?;
1098 let mut ctx = self.exec_ctx.write().await;
1099 {
1100 let scope = self.scope.read().await;
1101 ctx.scope = scope.clone();
1102 }
1103 let backend = ctx.backend.clone();
1104 match backend.call_tool(name, tool_args, &mut ctx).await {
1105 Ok(tool_result) => {
1106 let mut scope = self.scope.write().await;
1107 *scope = ctx.scope.clone();
1108 let mut exec = ExecResult::from_output(
1109 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1110 );
1111 exec.output = tool_result.output;
1112 return Ok(exec);
1113 }
1114 Err(BackendError::ToolNotFound(_)) => {
1115 }
1117 Err(e) => {
1118 return Ok(ExecResult::failure(1, e.to_string()));
1119 }
1120 }
1121
1122 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1123 }
1124 };
1125
1126 let schema = tool.schema();
1128 let mut tool_args = self.build_args_async(args, Some(&schema)).await?;
1129 let output_format = extract_output_format(&mut tool_args, Some(&schema));
1130
1131 let mut ctx = self.exec_ctx.write().await;
1133 {
1134 let scope = self.scope.read().await;
1135 ctx.scope = scope.clone();
1136 }
1137
1138 let result = tool.execute(tool_args, &mut ctx).await;
1139
1140 {
1142 let mut scope = self.scope.write().await;
1143 *scope = ctx.scope.clone();
1144 }
1145
1146 let result = match output_format {
1147 Some(format) => apply_output_format(result, format),
1148 None => result,
1149 };
1150
1151 Ok(result)
1152 }
1153
1154 async fn build_args_async(&self, args: &[Arg], schema: Option<&crate::tools::ToolSchema>) -> Result<ToolArgs> {
1158 let mut tool_args = ToolArgs::new();
1159 let param_lookup = schema.map(schema_param_lookup).unwrap_or_default();
1160
1161 let mut consumed: std::collections::HashSet<usize> = std::collections::HashSet::new();
1163 let mut past_double_dash = false;
1164
1165 let positional_indices: Vec<usize> = args.iter().enumerate()
1167 .filter_map(|(i, a)| matches!(a, Arg::Positional(_)).then_some(i))
1168 .collect();
1169
1170 let mut i = 0;
1171 while i < args.len() {
1172 match &args[i] {
1173 Arg::DoubleDash => {
1174 past_double_dash = true;
1175 }
1176 Arg::Positional(expr) => {
1177 if !consumed.contains(&i) {
1178 let value = self.eval_expr_async(expr).await?;
1179 let value = apply_tilde_expansion(value);
1180 tool_args.positional.push(value);
1181 }
1182 }
1183 Arg::Named { key, value } => {
1184 let val = self.eval_expr_async(value).await?;
1185 let val = apply_tilde_expansion(val);
1186 tool_args.named.insert(key.clone(), val);
1187 }
1188 Arg::ShortFlag(name) => {
1189 if past_double_dash {
1190 tool_args.positional.push(Value::String(format!("-{name}")));
1191 } else if name.len() == 1 {
1192 let flag_name = name.as_str();
1193 let lookup = param_lookup.get(flag_name);
1194 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1195
1196 if is_bool {
1197 tool_args.flags.insert(flag_name.to_string());
1198 } else {
1199 let canonical = lookup.map(|(name, _)| *name).unwrap_or(flag_name);
1201 let next_pos = positional_indices.iter()
1202 .find(|idx| **idx > i && !consumed.contains(idx));
1203
1204 if let Some(&pos_idx) = next_pos {
1205 if let Arg::Positional(expr) = &args[pos_idx] {
1206 let value = self.eval_expr_async(expr).await?;
1207 let value = apply_tilde_expansion(value);
1208 tool_args.named.insert(canonical.to_string(), value);
1209 consumed.insert(pos_idx);
1210 }
1211 } else {
1212 tool_args.flags.insert(flag_name.to_string());
1213 }
1214 }
1215 } else {
1216 for c in name.chars() {
1218 tool_args.flags.insert(c.to_string());
1219 }
1220 }
1221 }
1222 Arg::LongFlag(name) => {
1223 if past_double_dash {
1224 tool_args.positional.push(Value::String(format!("--{name}")));
1225 } else {
1226 let lookup = param_lookup.get(name.as_str());
1227 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1228
1229 if is_bool {
1230 tool_args.flags.insert(name.clone());
1231 } else {
1232 let canonical = lookup.map(|(name, _)| *name).unwrap_or(name.as_str());
1233 let next_pos = positional_indices.iter()
1234 .find(|idx| **idx > i && !consumed.contains(idx));
1235
1236 if let Some(&pos_idx) = next_pos {
1237 if let Arg::Positional(expr) = &args[pos_idx] {
1238 let value = self.eval_expr_async(expr).await?;
1239 let value = apply_tilde_expansion(value);
1240 tool_args.named.insert(canonical.to_string(), value);
1241 consumed.insert(pos_idx);
1242 }
1243 } else {
1244 tool_args.flags.insert(name.clone());
1245 }
1246 }
1247 }
1248 }
1249 }
1250 i += 1;
1251 }
1252
1253 Ok(tool_args)
1254 }
1255
1256 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1266 let mut argv = Vec::new();
1267 for arg in args {
1268 match arg {
1269 Arg::Positional(expr) => {
1270 let value = self.eval_expr_async(expr).await?;
1271 let value = apply_tilde_expansion(value);
1272 argv.push(value_to_string(&value));
1273 }
1274 Arg::Named { key, value } => {
1275 let val = self.eval_expr_async(value).await?;
1276 let val = apply_tilde_expansion(val);
1277 argv.push(format!("{}={}", key, value_to_string(&val)));
1278 }
1279 Arg::ShortFlag(name) => {
1280 argv.push(format!("-{}", name));
1282 }
1283 Arg::LongFlag(name) => {
1284 argv.push(format!("--{}", name));
1286 }
1287 Arg::DoubleDash => {
1288 argv.push("--".to_string());
1290 }
1291 }
1292 }
1293 Ok(argv)
1294 }
1295
1296 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send + 'a>> {
1301 Box::pin(async move {
1302 match expr {
1303 Expr::Literal(value) => Ok(value.clone()),
1304 Expr::VarRef(path) => {
1305 let scope = self.scope.read().await;
1306 scope.resolve_path(path)
1307 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1308 }
1309 Expr::Interpolated(parts) => {
1310 let mut result = String::new();
1311 for part in parts {
1312 result.push_str(&self.eval_string_part_async(part).await?);
1313 }
1314 Ok(Value::String(result))
1315 }
1316 Expr::BinaryOp { left, op, right } => {
1317 match op {
1318 BinaryOp::And => {
1319 let left_val = self.eval_expr_async(left).await?;
1320 if !is_truthy(&left_val) {
1321 return Ok(left_val);
1322 }
1323 self.eval_expr_async(right).await
1324 }
1325 BinaryOp::Or => {
1326 let left_val = self.eval_expr_async(left).await?;
1327 if is_truthy(&left_val) {
1328 return Ok(left_val);
1329 }
1330 self.eval_expr_async(right).await
1331 }
1332 _ => {
1333 let mut scope = self.scope.write().await;
1335 eval_expr(expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1336 }
1337 }
1338 }
1339 Expr::CommandSubst(pipeline) => {
1340 let saved_scope = { self.scope.read().await.clone() };
1343 let saved_cwd = {
1344 let ec = self.exec_ctx.read().await;
1345 (ec.cwd.clone(), ec.prev_cwd.clone())
1346 };
1347
1348 let run_result = self.execute_pipeline(pipeline).await;
1350
1351 {
1353 let mut scope = self.scope.write().await;
1354 *scope = saved_scope;
1355 if let Ok(ref r) = run_result {
1356 scope.set_last_result(r.clone());
1357 }
1358 }
1359 {
1360 let mut ec = self.exec_ctx.write().await;
1361 ec.cwd = saved_cwd.0;
1362 ec.prev_cwd = saved_cwd.1;
1363 }
1364
1365 let result = run_result?;
1367
1368 if let Some(data) = &result.data {
1370 Ok(data.clone())
1371 } else {
1372 Ok(Value::String(result.out.trim_end().to_string()))
1374 }
1375 }
1376 Expr::Test(test_expr) => {
1377 let expr = Expr::Test(test_expr.clone());
1379 let mut scope = self.scope.write().await;
1380 eval_expr(&expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1381 }
1382 Expr::Positional(n) => {
1383 let scope = self.scope.read().await;
1384 match scope.get_positional(*n) {
1385 Some(s) => Ok(Value::String(s.to_string())),
1386 None => Ok(Value::String(String::new())),
1387 }
1388 }
1389 Expr::AllArgs => {
1390 let scope = self.scope.read().await;
1391 Ok(Value::String(scope.all_args().join(" ")))
1392 }
1393 Expr::ArgCount => {
1394 let scope = self.scope.read().await;
1395 Ok(Value::Int(scope.arg_count() as i64))
1396 }
1397 Expr::VarLength(name) => {
1398 let scope = self.scope.read().await;
1399 match scope.get(name) {
1400 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1401 None => Ok(Value::Int(0)),
1402 }
1403 }
1404 Expr::VarWithDefault { name, default } => {
1405 let scope = self.scope.read().await;
1406 let use_default = match scope.get(name) {
1407 Some(value) => value_to_string(value).is_empty(),
1408 None => true,
1409 };
1410 drop(scope); if use_default {
1412 self.eval_string_parts_async(default).await.map(Value::String)
1414 } else {
1415 let scope = self.scope.read().await;
1416 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1417 }
1418 }
1419 Expr::Arithmetic(expr_str) => {
1420 let scope = self.scope.read().await;
1421 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1422 .map(Value::Int)
1423 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1424 }
1425 Expr::Command(cmd) => {
1426 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1428 Ok(Value::Bool(result.code == 0))
1429 }
1430 Expr::LastExitCode => {
1431 let scope = self.scope.read().await;
1432 Ok(Value::Int(scope.last_result().code))
1433 }
1434 Expr::CurrentPid => {
1435 let scope = self.scope.read().await;
1436 Ok(Value::Int(scope.pid() as i64))
1437 }
1438 }
1439 })
1440 }
1441
1442 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1444 Box::pin(async move {
1445 let mut result = String::new();
1446 for part in parts {
1447 result.push_str(&self.eval_string_part_async(part).await?);
1448 }
1449 Ok(result)
1450 })
1451 }
1452
1453 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1455 Box::pin(async move {
1456 match part {
1457 StringPart::Literal(s) => Ok(s.clone()),
1458 StringPart::Var(path) => {
1459 let scope = self.scope.read().await;
1460 match scope.resolve_path(path) {
1461 Some(value) => Ok(value_to_string(&value)),
1462 None => Ok(String::new()), }
1464 }
1465 StringPart::VarWithDefault { name, default } => {
1466 let scope = self.scope.read().await;
1467 let use_default = match scope.get(name) {
1468 Some(value) => value_to_string(value).is_empty(),
1469 None => true,
1470 };
1471 drop(scope); if use_default {
1473 self.eval_string_parts_async(default).await
1475 } else {
1476 let scope = self.scope.read().await;
1477 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
1478 }
1479 }
1480 StringPart::VarLength(name) => {
1481 let scope = self.scope.read().await;
1482 match scope.get(name) {
1483 Some(value) => Ok(value_to_string(value).len().to_string()),
1484 None => Ok("0".to_string()),
1485 }
1486 }
1487 StringPart::Positional(n) => {
1488 let scope = self.scope.read().await;
1489 match scope.get_positional(*n) {
1490 Some(s) => Ok(s.to_string()),
1491 None => Ok(String::new()),
1492 }
1493 }
1494 StringPart::AllArgs => {
1495 let scope = self.scope.read().await;
1496 Ok(scope.all_args().join(" "))
1497 }
1498 StringPart::ArgCount => {
1499 let scope = self.scope.read().await;
1500 Ok(scope.arg_count().to_string())
1501 }
1502 StringPart::Arithmetic(expr) => {
1503 let scope = self.scope.read().await;
1504 match crate::arithmetic::eval_arithmetic(expr, &scope) {
1505 Ok(value) => Ok(value.to_string()),
1506 Err(_) => Ok(String::new()),
1507 }
1508 }
1509 StringPart::CommandSubst(pipeline) => {
1510 let saved_scope = { self.scope.read().await.clone() };
1513 let saved_cwd = {
1514 let ec = self.exec_ctx.read().await;
1515 (ec.cwd.clone(), ec.prev_cwd.clone())
1516 };
1517
1518 let run_result = self.execute_pipeline(pipeline).await;
1520
1521 {
1523 let mut scope = self.scope.write().await;
1524 *scope = saved_scope;
1525 if let Ok(ref r) = run_result {
1526 scope.set_last_result(r.clone());
1527 }
1528 }
1529 {
1530 let mut ec = self.exec_ctx.write().await;
1531 ec.cwd = saved_cwd.0;
1532 ec.prev_cwd = saved_cwd.1;
1533 }
1534
1535 let result = run_result?;
1537
1538 Ok(result.out.trim_end_matches('\n').to_string())
1539 }
1540 StringPart::LastExitCode => {
1541 let scope = self.scope.read().await;
1542 Ok(scope.last_result().code.to_string())
1543 }
1544 StringPart::CurrentPid => {
1545 let scope = self.scope.read().await;
1546 Ok(scope.pid().to_string())
1547 }
1548 }
1549 })
1550 }
1551
1552 async fn update_last_result(&self, result: &ExecResult) {
1554 let mut scope = self.scope.write().await;
1555 scope.set_last_result(result.clone());
1556 }
1557
1558 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
1564 let tool_args = self.build_args_async(args, None).await?;
1566
1567 {
1569 let mut scope = self.scope.write().await;
1570 scope.push_frame();
1571 }
1572
1573 let saved_positional = {
1575 let mut scope = self.scope.write().await;
1576 let saved = scope.save_positional();
1577
1578 let positional_args: Vec<String> = tool_args.positional
1580 .iter()
1581 .map(value_to_string)
1582 .collect();
1583 scope.set_positional(&def.name, positional_args);
1584
1585 saved
1586 };
1587
1588 let mut accumulated_out = String::new();
1591 let mut accumulated_err = String::new();
1592 let mut last_code = 0i64;
1593 let mut last_data: Option<Value> = None;
1594
1595 let mut exec_error: Option<anyhow::Error> = None;
1597 let mut exit_code: Option<i64> = None;
1598
1599 for stmt in &def.body {
1600 match self.execute_stmt_flow(stmt).await {
1601 Ok(flow) => {
1602 match flow {
1603 ControlFlow::Normal(r) => {
1604 accumulated_out.push_str(&r.out);
1605 accumulated_err.push_str(&r.err);
1606 last_code = r.code;
1607 last_data = r.data;
1608 }
1609 ControlFlow::Return { value } => {
1610 accumulated_out.push_str(&value.out);
1611 accumulated_err.push_str(&value.err);
1612 last_code = value.code;
1613 last_data = value.data;
1614 break;
1615 }
1616 ControlFlow::Exit { code } => {
1617 exit_code = Some(code);
1618 break;
1619 }
1620 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1621 accumulated_out.push_str(&r.out);
1622 accumulated_err.push_str(&r.err);
1623 last_code = r.code;
1624 last_data = r.data;
1625 }
1626 }
1627 }
1628 Err(e) => {
1629 exec_error = Some(e);
1630 break;
1631 }
1632 }
1633 }
1634
1635 {
1637 let mut scope = self.scope.write().await;
1638 scope.pop_frame();
1639 scope.set_positional(saved_positional.0, saved_positional.1);
1640 }
1641
1642 if let Some(e) = exec_error {
1644 return Err(e);
1645 }
1646 if let Some(code) = exit_code {
1647 return Ok(ExecResult::failure(code, "exit"));
1648 }
1649
1650 Ok(ExecResult {
1651 code: last_code,
1652 out: accumulated_out,
1653 err: accumulated_err,
1654 data: last_data,
1655 output: None,
1656 })
1657 }
1658
1659 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
1664 let tool_args = self.build_args_async(args, None).await?;
1666 let path = match tool_args.positional.first() {
1667 Some(Value::String(s)) => s.clone(),
1668 Some(v) => value_to_string(v),
1669 None => {
1670 return Ok(ExecResult::failure(1, "source: missing filename"));
1671 }
1672 };
1673
1674 let full_path = {
1676 let ctx = self.exec_ctx.read().await;
1677 if path.starts_with('/') {
1678 std::path::PathBuf::from(&path)
1679 } else {
1680 ctx.cwd.join(&path)
1681 }
1682 };
1683
1684 let content = {
1686 let ctx = self.exec_ctx.read().await;
1687 match ctx.backend.read(&full_path, None).await {
1688 Ok(bytes) => {
1689 String::from_utf8(bytes).map_err(|e| {
1690 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
1691 })?
1692 }
1693 Err(e) => {
1694 return Ok(ExecResult::failure(
1695 1,
1696 format!("source: {}: {}", path, e),
1697 ));
1698 }
1699 }
1700 };
1701
1702 let program = match crate::parser::parse(&content) {
1704 Ok(p) => p,
1705 Err(errors) => {
1706 let msg = errors
1707 .iter()
1708 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
1709 .collect::<Vec<_>>()
1710 .join("\n");
1711 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
1712 }
1713 };
1714
1715 let mut result = ExecResult::success("");
1717 for stmt in program.statements {
1718 if matches!(stmt, crate::ast::Stmt::Empty) {
1719 continue;
1720 }
1721
1722 match self.execute_stmt_flow(&stmt).await {
1723 Ok(flow) => {
1724 match flow {
1725 ControlFlow::Normal(r) => {
1726 result = r.clone();
1727 self.update_last_result(&r).await;
1728 }
1729 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
1730 return Err(anyhow::anyhow!(
1732 "source: {}: unexpected break/continue outside loop",
1733 path
1734 ));
1735 }
1736 ControlFlow::Return { value } => {
1737 return Ok(value);
1739 }
1740 ControlFlow::Exit { code } => {
1741 return Ok(ExecResult::failure(code, "exit"));
1743 }
1744 }
1745 }
1746 Err(e) => {
1747 return Err(e.context(format!("source: {}", path)));
1748 }
1749 }
1750 }
1751
1752 Ok(result)
1753 }
1754
1755 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1760 let path_value = {
1762 let scope = self.scope.read().await;
1763 scope
1764 .get("PATH")
1765 .map(value_to_string)
1766 .unwrap_or_else(|| "/bin".to_string())
1767 };
1768
1769 for dir in path_value.split(':') {
1771 if dir.is_empty() {
1772 continue;
1773 }
1774
1775 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
1777
1778 let exists = {
1780 let ctx = self.exec_ctx.read().await;
1781 ctx.backend.exists(&script_path).await
1782 };
1783
1784 if !exists {
1785 continue;
1786 }
1787
1788 let content = {
1790 let ctx = self.exec_ctx.read().await;
1791 match ctx.backend.read(&script_path, None).await {
1792 Ok(bytes) => match String::from_utf8(bytes) {
1793 Ok(s) => s,
1794 Err(e) => {
1795 return Ok(Some(ExecResult::failure(
1796 1,
1797 format!("{}: invalid UTF-8: {}", script_path.display(), e),
1798 )));
1799 }
1800 },
1801 Err(e) => {
1802 return Ok(Some(ExecResult::failure(
1803 1,
1804 format!("{}: {}", script_path.display(), e),
1805 )));
1806 }
1807 }
1808 };
1809
1810 let program = match crate::parser::parse(&content) {
1812 Ok(p) => p,
1813 Err(errors) => {
1814 let msg = errors
1815 .iter()
1816 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
1817 .collect::<Vec<_>>()
1818 .join("\n");
1819 return Ok(Some(ExecResult::failure(1, msg)));
1820 }
1821 };
1822
1823 let tool_args = self.build_args_async(args, None).await?;
1825
1826 let mut isolated_scope = Scope::new();
1828
1829 let positional_args: Vec<String> = tool_args.positional
1831 .iter()
1832 .map(value_to_string)
1833 .collect();
1834 isolated_scope.set_positional(name, positional_args);
1835
1836 let original_scope = {
1838 let mut scope = self.scope.write().await;
1839 std::mem::replace(&mut *scope, isolated_scope)
1840 };
1841
1842 let mut result = ExecResult::success("");
1844 let mut exec_error: Option<anyhow::Error> = None;
1845 let mut exit_code: Option<i64> = None;
1846
1847 for stmt in program.statements {
1848 if matches!(stmt, crate::ast::Stmt::Empty) {
1849 continue;
1850 }
1851
1852 match self.execute_stmt_flow(&stmt).await {
1853 Ok(flow) => {
1854 match flow {
1855 ControlFlow::Normal(r) => result = r,
1856 ControlFlow::Return { value } => {
1857 result = value;
1858 break;
1859 }
1860 ControlFlow::Exit { code } => {
1861 exit_code = Some(code);
1862 break;
1863 }
1864 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1865 result = r;
1866 }
1867 }
1868 }
1869 Err(e) => {
1870 exec_error = Some(e);
1871 break;
1872 }
1873 }
1874 }
1875
1876 {
1878 let mut scope = self.scope.write().await;
1879 *scope = original_scope;
1880 }
1881
1882 if let Some(e) = exec_error {
1884 return Err(e.context(format!("script: {}", script_path.display())));
1885 }
1886 if let Some(code) = exit_code {
1887 return Ok(Some(ExecResult::failure(code, "exit")));
1888 }
1889
1890 return Ok(Some(result));
1891 }
1892
1893 Ok(None)
1895 }
1896
1897 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1911 if name.contains('/') {
1913 return Ok(None);
1914 }
1915
1916 let path_var = {
1918 let scope = self.scope.read().await;
1919 scope
1920 .get("PATH")
1921 .map(value_to_string)
1922 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
1923 };
1924
1925 let executable = match resolve_in_path(name, &path_var) {
1927 Some(path) => path,
1928 None => return Ok(None), };
1930
1931 let real_cwd = {
1933 let ctx = self.exec_ctx.read().await;
1934 match ctx.backend.resolve_real_path(&ctx.cwd) {
1935 Some(p) => p,
1936 None => {
1937 return Ok(Some(ExecResult::failure(
1938 1,
1939 format!(
1940 "{}: cannot run external command from virtual directory '{}'",
1941 name,
1942 ctx.cwd.display()
1943 ),
1944 )));
1945 }
1946 }
1947 };
1948
1949 let argv = self.build_args_flat(args).await?;
1951
1952 let stdin_data = {
1954 let mut ctx = self.exec_ctx.write().await;
1955 ctx.take_stdin()
1956 };
1957
1958 use tokio::process::Command;
1960
1961 let mut cmd = Command::new(&executable);
1962 cmd.args(&argv);
1963 cmd.current_dir(&real_cwd);
1964
1965 cmd.stdin(if stdin_data.is_some() {
1967 std::process::Stdio::piped()
1968 } else {
1969 std::process::Stdio::null()
1970 });
1971
1972 let inherit_output = self.interactive && stdin_data.is_none();
1975
1976 if inherit_output {
1977 cmd.stdout(std::process::Stdio::inherit());
1978 cmd.stderr(std::process::Stdio::inherit());
1979 } else {
1980 cmd.stdout(std::process::Stdio::piped());
1981 cmd.stderr(std::process::Stdio::piped());
1982 }
1983
1984 let mut child = match cmd.spawn() {
1986 Ok(child) => child,
1987 Err(e) => {
1988 return Ok(Some(ExecResult::failure(
1989 127,
1990 format!("{}: {}", name, e),
1991 )));
1992 }
1993 };
1994
1995 if let Some(data) = stdin_data
1997 && let Some(mut stdin) = child.stdin.take()
1998 {
1999 use tokio::io::AsyncWriteExt;
2000 if let Err(e) = stdin.write_all(data.as_bytes()).await {
2001 return Ok(Some(ExecResult::failure(
2002 1,
2003 format!("{}: failed to write stdin: {}", name, e),
2004 )));
2005 }
2006 }
2008
2009 if inherit_output {
2010 let status = match child.wait().await {
2012 Ok(s) => s,
2013 Err(e) => {
2014 return Ok(Some(ExecResult::failure(
2015 1,
2016 format!("{}: failed to wait: {}", name, e),
2017 )));
2018 }
2019 };
2020
2021 let code = status.code().unwrap_or_else(|| {
2022 #[cfg(unix)]
2023 {
2024 use std::os::unix::process::ExitStatusExt;
2025 128 + status.signal().unwrap_or(0)
2026 }
2027 #[cfg(not(unix))]
2028 {
2029 -1
2030 }
2031 }) as i64;
2032
2033 Ok(Some(ExecResult::from_output(code, String::new(), String::new())))
2035 } else {
2036 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2038 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2039
2040 let stdout_pipe = child.stdout.take();
2041 let stderr_pipe = child.stderr.take();
2042
2043 let stdout_clone = stdout_stream.clone();
2044 let stderr_clone = stderr_stream.clone();
2045
2046 let stdout_task = stdout_pipe.map(|pipe| {
2047 tokio::spawn(async move {
2048 drain_to_stream(pipe, stdout_clone).await;
2049 })
2050 });
2051
2052 let stderr_task = stderr_pipe.map(|pipe| {
2053 tokio::spawn(async move {
2054 drain_to_stream(pipe, stderr_clone).await;
2055 })
2056 });
2057
2058 let status = match child.wait().await {
2059 Ok(s) => s,
2060 Err(e) => {
2061 return Ok(Some(ExecResult::failure(
2062 1,
2063 format!("{}: failed to wait: {}", name, e),
2064 )));
2065 }
2066 };
2067
2068 if let Some(task) = stdout_task {
2069 let _ = task.await;
2071 }
2072 if let Some(task) = stderr_task {
2073 let _ = task.await;
2074 }
2075
2076 let code = status.code().unwrap_or_else(|| {
2077 #[cfg(unix)]
2078 {
2079 use std::os::unix::process::ExitStatusExt;
2080 128 + status.signal().unwrap_or(0)
2081 }
2082 #[cfg(not(unix))]
2083 {
2084 -1
2085 }
2086 }) as i64;
2087
2088 let stdout = stdout_stream.read_string().await;
2089 let stderr = stderr_stream.read_string().await;
2090
2091 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
2092 }
2093 }
2094
2095 pub async fn get_var(&self, name: &str) -> Option<Value> {
2099 let scope = self.scope.read().await;
2100 scope.get(name).cloned()
2101 }
2102
2103 #[cfg(test)]
2105 pub async fn error_exit_enabled(&self) -> bool {
2106 let scope = self.scope.read().await;
2107 scope.error_exit_enabled()
2108 }
2109
2110 pub async fn set_var(&self, name: &str, value: Value) {
2112 let mut scope = self.scope.write().await;
2113 scope.set(name.to_string(), value);
2114 }
2115
2116 pub async fn list_vars(&self) -> Vec<(String, Value)> {
2118 let scope = self.scope.read().await;
2119 scope.all()
2120 }
2121
2122 pub async fn cwd(&self) -> PathBuf {
2126 self.exec_ctx.read().await.cwd.clone()
2127 }
2128
2129 pub async fn set_cwd(&self, path: PathBuf) {
2131 let mut ctx = self.exec_ctx.write().await;
2132 ctx.set_cwd(path);
2133 }
2134
2135 pub async fn last_result(&self) -> ExecResult {
2139 let scope = self.scope.read().await;
2140 scope.last_result().clone()
2141 }
2142
2143 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
2147 self.tools.schemas()
2148 }
2149
2150 pub fn jobs(&self) -> Arc<JobManager> {
2154 self.jobs.clone()
2155 }
2156
2157 pub fn vfs(&self) -> Arc<VfsRouter> {
2161 self.vfs.clone()
2162 }
2163
2164 pub async fn reset(&self) -> Result<()> {
2171 {
2172 let mut scope = self.scope.write().await;
2173 *scope = Scope::new();
2174 }
2175 {
2176 let mut ctx = self.exec_ctx.write().await;
2177 ctx.cwd = PathBuf::from("/");
2178 }
2179 Ok(())
2180 }
2181
2182 pub async fn shutdown(self) -> Result<()> {
2184 self.jobs.wait_all().await;
2186 Ok(())
2187 }
2188
2189 async fn dispatch_command(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2200 {
2202 let mut scope = self.scope.write().await;
2203 *scope = ctx.scope.clone();
2204 }
2205 {
2206 let mut ec = self.exec_ctx.write().await;
2207 ec.cwd = ctx.cwd.clone();
2208 ec.prev_cwd = ctx.prev_cwd.clone();
2209 ec.stdin = ctx.stdin.take();
2210 ec.stdin_data = ctx.stdin_data.take();
2211 }
2212
2213 let result = self.execute_command(&cmd.name, &cmd.args).await?;
2215
2216 {
2218 let scope = self.scope.read().await;
2219 ctx.scope = scope.clone();
2220 }
2221 {
2222 let ec = self.exec_ctx.read().await;
2223 ctx.cwd = ec.cwd.clone();
2224 ctx.prev_cwd = ec.prev_cwd.clone();
2225 }
2226
2227 Ok(result)
2228 }
2229}
2230
2231#[async_trait]
2232impl CommandDispatcher for Kernel {
2233 async fn dispatch(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2239 self.dispatch_command(cmd, ctx).await
2240 }
2241}
2242
2243fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2249 if !accumulated.out.is_empty() && !new.out.is_empty() {
2250 accumulated.out.push('\n');
2251 }
2252 accumulated.out.push_str(&new.out);
2253 if !accumulated.err.is_empty() && !new.err.is_empty() {
2254 accumulated.err.push('\n');
2255 }
2256 accumulated.err.push_str(&new.err);
2257 accumulated.code = new.code;
2258 accumulated.data = new.data.clone();
2259}
2260
2261fn is_truthy(value: &Value) -> bool {
2263 match value {
2264 Value::Null => false,
2265 Value::Bool(b) => *b,
2266 Value::Int(i) => *i != 0,
2267 Value::Float(f) => *f != 0.0,
2268 Value::String(s) => !s.is_empty(),
2269 Value::Json(json) => match json {
2270 serde_json::Value::Null => false,
2271 serde_json::Value::Array(arr) => !arr.is_empty(),
2272 serde_json::Value::Object(obj) => !obj.is_empty(),
2273 serde_json::Value::Bool(b) => *b,
2274 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
2275 serde_json::Value::String(s) => !s.is_empty(),
2276 },
2277 Value::Blob(_) => true, }
2279}
2280
2281fn apply_tilde_expansion(value: Value) -> Value {
2285 match value {
2286 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
2287 _ => value,
2288 }
2289}
2290
2291#[cfg(test)]
2292mod tests {
2293 use super::*;
2294
2295 #[tokio::test]
2296 async fn test_kernel_transient() {
2297 let kernel = Kernel::transient().expect("failed to create kernel");
2298 assert_eq!(kernel.name(), "transient");
2299 }
2300
2301 #[tokio::test]
2302 async fn test_kernel_execute_echo() {
2303 let kernel = Kernel::transient().expect("failed to create kernel");
2304 let result = kernel.execute("echo hello").await.expect("execution failed");
2305 assert!(result.ok());
2306 assert_eq!(result.out.trim(), "hello");
2307 }
2308
2309 #[tokio::test]
2310 async fn test_multiple_statements_accumulate_output() {
2311 let kernel = Kernel::transient().expect("failed to create kernel");
2312 let result = kernel
2313 .execute("echo one\necho two\necho three")
2314 .await
2315 .expect("execution failed");
2316 assert!(result.ok());
2317 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
2319 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
2320 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
2321 }
2322
2323 #[tokio::test]
2324 async fn test_and_chain_accumulates_output() {
2325 let kernel = Kernel::transient().expect("failed to create kernel");
2326 let result = kernel
2327 .execute("echo first && echo second")
2328 .await
2329 .expect("execution failed");
2330 assert!(result.ok());
2331 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
2332 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
2333 }
2334
2335 #[tokio::test]
2336 async fn test_for_loop_accumulates_output() {
2337 let kernel = Kernel::transient().expect("failed to create kernel");
2338 let result = kernel
2339 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
2340 .await
2341 .expect("execution failed");
2342 assert!(result.ok());
2343 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
2344 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
2345 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
2346 }
2347
2348 #[tokio::test]
2349 async fn test_while_loop_accumulates_output() {
2350 let kernel = Kernel::transient().expect("failed to create kernel");
2351 let result = kernel
2352 .execute(r#"
2353 N=3
2354 while [[ ${N} -gt 0 ]]; do
2355 echo "N=${N}"
2356 N=$((N - 1))
2357 done
2358 "#)
2359 .await
2360 .expect("execution failed");
2361 assert!(result.ok());
2362 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
2363 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
2364 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
2365 }
2366
2367 #[tokio::test]
2368 async fn test_kernel_set_var() {
2369 let kernel = Kernel::transient().expect("failed to create kernel");
2370
2371 kernel.execute("X=42").await.expect("set failed");
2372
2373 let value = kernel.get_var("X").await;
2374 assert_eq!(value, Some(Value::Int(42)));
2375 }
2376
2377 #[tokio::test]
2378 async fn test_kernel_var_expansion() {
2379 let kernel = Kernel::transient().expect("failed to create kernel");
2380
2381 kernel.execute("NAME=\"world\"").await.expect("set failed");
2382 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
2383
2384 assert!(result.ok());
2385 assert_eq!(result.out.trim(), "hello world");
2386 }
2387
2388 #[tokio::test]
2389 async fn test_kernel_last_result() {
2390 let kernel = Kernel::transient().expect("failed to create kernel");
2391
2392 kernel.execute("echo test").await.expect("echo failed");
2393
2394 let last = kernel.last_result().await;
2395 assert!(last.ok());
2396 assert_eq!(last.out.trim(), "test");
2397 }
2398
2399 #[tokio::test]
2400 async fn test_kernel_tool_not_found() {
2401 let kernel = Kernel::transient().expect("failed to create kernel");
2402
2403 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
2404 assert!(!result.ok());
2405 assert_eq!(result.code, 127);
2406 assert!(result.err.contains("command not found"));
2407 }
2408
2409 #[tokio::test]
2410 async fn test_external_command_true() {
2411 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2413
2414 let result = kernel.execute("true").await.expect("execution failed");
2416 assert!(result.ok(), "true should succeed: {:?}", result);
2418 }
2419
2420 #[tokio::test]
2421 async fn test_external_command_basic() {
2422 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2424
2425 let path_var = std::env::var("PATH").unwrap_or_default();
2430 eprintln!("System PATH: {}", path_var);
2431
2432 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
2434
2435 let result = kernel.execute("uname").await.expect("execution failed");
2438 eprintln!("uname result: {:?}", result);
2439 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
2441 }
2442
2443 #[tokio::test]
2444 async fn test_kernel_reset() {
2445 let kernel = Kernel::transient().expect("failed to create kernel");
2446
2447 kernel.execute("X=1").await.expect("set failed");
2448 assert!(kernel.get_var("X").await.is_some());
2449
2450 kernel.reset().await.expect("reset failed");
2451 assert!(kernel.get_var("X").await.is_none());
2452 }
2453
2454 #[tokio::test]
2455 async fn test_kernel_cwd() {
2456 let kernel = Kernel::transient().expect("failed to create kernel");
2457
2458 let cwd = kernel.cwd().await;
2460 let home = std::env::var("HOME")
2461 .map(PathBuf::from)
2462 .unwrap_or_else(|_| PathBuf::from("/"));
2463 assert_eq!(cwd, home);
2464
2465 kernel.set_cwd(PathBuf::from("/tmp")).await;
2466 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
2467 }
2468
2469 #[tokio::test]
2470 async fn test_kernel_list_vars() {
2471 let kernel = Kernel::transient().expect("failed to create kernel");
2472
2473 kernel.execute("A=1").await.ok();
2474 kernel.execute("B=2").await.ok();
2475
2476 let vars = kernel.list_vars().await;
2477 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
2478 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
2479 }
2480
2481 #[tokio::test]
2482 async fn test_is_truthy() {
2483 assert!(!is_truthy(&Value::Null));
2484 assert!(!is_truthy(&Value::Bool(false)));
2485 assert!(is_truthy(&Value::Bool(true)));
2486 assert!(!is_truthy(&Value::Int(0)));
2487 assert!(is_truthy(&Value::Int(1)));
2488 assert!(!is_truthy(&Value::String("".into())));
2489 assert!(is_truthy(&Value::String("x".into())));
2490 }
2491
2492 #[tokio::test]
2493 async fn test_jq_in_pipeline() {
2494 let kernel = Kernel::transient().expect("failed to create kernel");
2495 let result = kernel
2497 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
2498 .await
2499 .expect("execution failed");
2500 assert!(result.ok(), "jq pipeline failed: {}", result.err);
2501 assert_eq!(result.out.trim(), "Alice");
2502 }
2503
2504 #[tokio::test]
2505 async fn test_user_defined_tool() {
2506 let kernel = Kernel::transient().expect("failed to create kernel");
2507
2508 kernel
2510 .execute(r#"greet() { echo "Hello, $1!" }"#)
2511 .await
2512 .expect("function definition failed");
2513
2514 let result = kernel
2516 .execute(r#"greet "World""#)
2517 .await
2518 .expect("function call failed");
2519
2520 assert!(result.ok(), "greet failed: {}", result.err);
2521 assert_eq!(result.out.trim(), "Hello, World!");
2522 }
2523
2524 #[tokio::test]
2525 async fn test_user_tool_positional_args() {
2526 let kernel = Kernel::transient().expect("failed to create kernel");
2527
2528 kernel
2530 .execute(r#"greet() { echo "Hi $1" }"#)
2531 .await
2532 .expect("function definition failed");
2533
2534 let result = kernel
2536 .execute(r#"greet "Amy""#)
2537 .await
2538 .expect("function call failed");
2539
2540 assert!(result.ok(), "greet failed: {}", result.err);
2541 assert_eq!(result.out.trim(), "Hi Amy");
2542 }
2543
2544 #[tokio::test]
2545 async fn test_function_shared_scope() {
2546 let kernel = Kernel::transient().expect("failed to create kernel");
2547
2548 kernel
2550 .execute(r#"SECRET="hidden""#)
2551 .await
2552 .expect("set failed");
2553
2554 kernel
2556 .execute(r#"access_parent() {
2557 echo "${SECRET}"
2558 SECRET="modified"
2559 }"#)
2560 .await
2561 .expect("function definition failed");
2562
2563 let result = kernel.execute("access_parent").await.expect("function call failed");
2565
2566 assert!(
2568 result.out.contains("hidden"),
2569 "Function should access parent scope, got: {}",
2570 result.out
2571 );
2572
2573 let secret = kernel.get_var("SECRET").await;
2575 assert_eq!(
2576 secret,
2577 Some(Value::String("modified".into())),
2578 "Function should modify parent scope"
2579 );
2580 }
2581
2582 #[tokio::test]
2583 async fn test_exec_builtin() {
2584 let kernel = Kernel::transient().expect("failed to create kernel");
2585 let result = kernel
2587 .execute(r#"exec command="/bin/echo" argv="hello world""#)
2588 .await
2589 .expect("exec failed");
2590
2591 assert!(result.ok(), "exec failed: {}", result.err);
2592 assert_eq!(result.out.trim(), "hello world");
2593 }
2594
2595 #[tokio::test]
2596 async fn test_while_false_never_runs() {
2597 let kernel = Kernel::transient().expect("failed to create kernel");
2598
2599 let result = kernel
2601 .execute(r#"
2602 while false; do
2603 echo "should not run"
2604 done
2605 "#)
2606 .await
2607 .expect("while false failed");
2608
2609 assert!(result.ok());
2610 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
2611 }
2612
2613 #[tokio::test]
2614 async fn test_while_string_comparison() {
2615 let kernel = Kernel::transient().expect("failed to create kernel");
2616
2617 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
2619
2620 let result = kernel
2623 .execute(r#"
2624 while [[ ${FLAG} == "go" ]]; do
2625 FLAG="stop"
2626 echo "running"
2627 done
2628 "#)
2629 .await
2630 .expect("while with string cmp failed");
2631
2632 assert!(result.ok());
2633 assert!(result.out.contains("running"), "should have run once: {}", result.out);
2634
2635 let flag = kernel.get_var("FLAG").await;
2637 assert_eq!(flag, Some(Value::String("stop".into())));
2638 }
2639
2640 #[tokio::test]
2641 async fn test_while_numeric_comparison() {
2642 let kernel = Kernel::transient().expect("failed to create kernel");
2643
2644 kernel.execute("N=5").await.expect("set failed");
2646
2647 let result = kernel
2649 .execute(r#"
2650 while [[ ${N} -gt 3 ]]; do
2651 N=3
2652 echo "N was greater"
2653 done
2654 "#)
2655 .await
2656 .expect("while with > failed");
2657
2658 assert!(result.ok());
2659 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
2660 }
2661
2662 #[tokio::test]
2663 async fn test_break_in_while_loop() {
2664 let kernel = Kernel::transient().expect("failed to create kernel");
2665
2666 let result = kernel
2667 .execute(r#"
2668 I=0
2669 while true; do
2670 I=1
2671 echo "before break"
2672 break
2673 echo "after break"
2674 done
2675 "#)
2676 .await
2677 .expect("while with break failed");
2678
2679 assert!(result.ok());
2680 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
2681 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
2682
2683 let i = kernel.get_var("I").await;
2685 assert_eq!(i, Some(Value::Int(1)));
2686 }
2687
2688 #[tokio::test]
2689 async fn test_continue_in_while_loop() {
2690 let kernel = Kernel::transient().expect("failed to create kernel");
2691
2692 let result = kernel
2697 .execute(r#"
2698 STATE="start"
2699 AFTER_CONTINUE="no"
2700 while [[ ${STATE} != "done" ]]; do
2701 if [[ ${STATE} == "start" ]]; then
2702 STATE="middle"
2703 continue
2704 AFTER_CONTINUE="yes"
2705 fi
2706 if [[ ${STATE} == "middle" ]]; then
2707 STATE="done"
2708 fi
2709 done
2710 "#)
2711 .await
2712 .expect("while with continue failed");
2713
2714 assert!(result.ok());
2715
2716 let state = kernel.get_var("STATE").await;
2718 assert_eq!(state, Some(Value::String("done".into())));
2719
2720 let after = kernel.get_var("AFTER_CONTINUE").await;
2722 assert_eq!(after, Some(Value::String("no".into())));
2723 }
2724
2725 #[tokio::test]
2726 async fn test_break_with_level() {
2727 let kernel = Kernel::transient().expect("failed to create kernel");
2728
2729 let result = kernel
2734 .execute(r#"
2735 OUTER=0
2736 while true; do
2737 OUTER=1
2738 for X in "1 2"; do
2739 break 2
2740 done
2741 OUTER=2
2742 done
2743 "#)
2744 .await
2745 .expect("nested break failed");
2746
2747 assert!(result.ok());
2748
2749 let outer = kernel.get_var("OUTER").await;
2751 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
2752 }
2753
2754 #[tokio::test]
2755 async fn test_return_from_tool() {
2756 let kernel = Kernel::transient().expect("failed to create kernel");
2757
2758 kernel
2760 .execute(r#"early_return() {
2761 if [[ $1 == 1 ]]; then
2762 return 42
2763 fi
2764 echo "not returned"
2765 }"#)
2766 .await
2767 .expect("function definition failed");
2768
2769 let result = kernel
2772 .execute("early_return 1")
2773 .await
2774 .expect("function call failed");
2775
2776 assert_eq!(result.code, 42);
2778 assert!(result.out.is_empty());
2780 }
2781
2782 #[tokio::test]
2783 async fn test_return_without_value() {
2784 let kernel = Kernel::transient().expect("failed to create kernel");
2785
2786 kernel
2788 .execute(r#"early_exit() {
2789 if [[ $1 == "stop" ]]; then
2790 return
2791 fi
2792 echo "continued"
2793 }"#)
2794 .await
2795 .expect("function definition failed");
2796
2797 let result = kernel
2799 .execute(r#"early_exit "stop""#)
2800 .await
2801 .expect("function call failed");
2802
2803 assert!(result.ok());
2804 assert!(result.out.is_empty() || result.out.trim().is_empty());
2805 }
2806
2807 #[tokio::test]
2808 async fn test_exit_stops_execution() {
2809 let kernel = Kernel::transient().expect("failed to create kernel");
2810
2811 kernel
2813 .execute(r#"
2814 BEFORE="yes"
2815 exit 0
2816 AFTER="yes"
2817 "#)
2818 .await
2819 .expect("execution failed");
2820
2821 let before = kernel.get_var("BEFORE").await;
2823 assert_eq!(before, Some(Value::String("yes".into())));
2824
2825 let after = kernel.get_var("AFTER").await;
2826 assert!(after.is_none(), "AFTER should not be set after exit");
2827 }
2828
2829 #[tokio::test]
2830 async fn test_exit_with_code() {
2831 let kernel = Kernel::transient().expect("failed to create kernel");
2832
2833 let result = kernel
2835 .execute("exit 42")
2836 .await
2837 .expect("exit failed");
2838
2839 assert_eq!(result.out, "42");
2841 }
2842
2843 #[tokio::test]
2844 async fn test_set_e_stops_on_failure() {
2845 let kernel = Kernel::transient().expect("failed to create kernel");
2846
2847 kernel.execute("set -e").await.expect("set -e failed");
2849
2850 kernel
2852 .execute(r#"
2853 STEP1="done"
2854 false
2855 STEP2="done"
2856 "#)
2857 .await
2858 .expect("execution failed");
2859
2860 let step1 = kernel.get_var("STEP1").await;
2862 assert_eq!(step1, Some(Value::String("done".into())));
2863
2864 let step2 = kernel.get_var("STEP2").await;
2865 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
2866 }
2867
2868 #[tokio::test]
2869 async fn test_set_plus_e_disables_error_exit() {
2870 let kernel = Kernel::transient().expect("failed to create kernel");
2871
2872 kernel.execute("set -e").await.expect("set -e failed");
2874 kernel.execute("set +e").await.expect("set +e failed");
2875
2876 kernel
2878 .execute(r#"
2879 STEP1="done"
2880 false
2881 STEP2="done"
2882 "#)
2883 .await
2884 .expect("execution failed");
2885
2886 let step1 = kernel.get_var("STEP1").await;
2888 assert_eq!(step1, Some(Value::String("done".into())));
2889
2890 let step2 = kernel.get_var("STEP2").await;
2891 assert_eq!(step2, Some(Value::String("done".into())));
2892 }
2893
2894 #[tokio::test]
2895 async fn test_set_ignores_unknown_options() {
2896 let kernel = Kernel::transient().expect("failed to create kernel");
2897
2898 let result = kernel
2900 .execute("set -e -u -o pipefail")
2901 .await
2902 .expect("set with unknown options failed");
2903
2904 assert!(result.ok(), "set should succeed with unknown options");
2905
2906 kernel
2908 .execute(r#"
2909 BEFORE="yes"
2910 false
2911 AFTER="yes"
2912 "#)
2913 .await
2914 .ok();
2915
2916 let after = kernel.get_var("AFTER").await;
2917 assert!(after.is_none(), "-e should be enabled despite unknown options");
2918 }
2919
2920 #[tokio::test]
2921 async fn test_set_no_args_shows_settings() {
2922 let kernel = Kernel::transient().expect("failed to create kernel");
2923
2924 kernel.execute("set -e").await.expect("set -e failed");
2926
2927 let result = kernel.execute("set").await.expect("set failed");
2929
2930 assert!(result.ok());
2931 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
2932 }
2933
2934 #[tokio::test]
2935 async fn test_set_e_in_pipeline() {
2936 let kernel = Kernel::transient().expect("failed to create kernel");
2937
2938 kernel.execute("set -e").await.expect("set -e failed");
2939
2940 kernel
2942 .execute(r#"
2943 BEFORE="yes"
2944 false | cat
2945 AFTER="yes"
2946 "#)
2947 .await
2948 .ok();
2949
2950 let before = kernel.get_var("BEFORE").await;
2951 assert_eq!(before, Some(Value::String("yes".into())));
2952
2953 }
2958
2959 #[tokio::test]
2960 async fn test_set_e_with_and_chain() {
2961 let kernel = Kernel::transient().expect("failed to create kernel");
2962
2963 kernel.execute("set -e").await.expect("set -e failed");
2964
2965 kernel
2968 .execute(r#"
2969 RESULT="initial"
2970 false && RESULT="chained"
2971 RESULT="continued"
2972 "#)
2973 .await
2974 .ok();
2975
2976 let result = kernel.get_var("RESULT").await;
2979 assert!(result.is_some(), "RESULT should be set");
2982 }
2983
2984 #[tokio::test]
2989 async fn test_source_sets_variables() {
2990 let kernel = Kernel::transient().expect("failed to create kernel");
2991
2992 kernel
2994 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
2995 .await
2996 .expect("write failed");
2997
2998 let result = kernel
3000 .execute(r#"source "/test.kai""#)
3001 .await
3002 .expect("source failed");
3003
3004 assert!(result.ok(), "source should succeed");
3005
3006 let foo = kernel.get_var("FOO").await;
3008 assert_eq!(foo, Some(Value::String("bar".into())));
3009 }
3010
3011 #[tokio::test]
3012 async fn test_source_with_dot_alias() {
3013 let kernel = Kernel::transient().expect("failed to create kernel");
3014
3015 kernel
3017 .execute(r#"write "/vars.kai" 'X=42'"#)
3018 .await
3019 .expect("write failed");
3020
3021 let result = kernel
3023 .execute(r#". "/vars.kai""#)
3024 .await
3025 .expect(". failed");
3026
3027 assert!(result.ok(), ". should succeed");
3028
3029 let x = kernel.get_var("X").await;
3031 assert_eq!(x, Some(Value::Int(42)));
3032 }
3033
3034 #[tokio::test]
3035 async fn test_source_not_found() {
3036 let kernel = Kernel::transient().expect("failed to create kernel");
3037
3038 let result = kernel
3040 .execute(r#"source "/nonexistent.kai""#)
3041 .await
3042 .expect("source should not fail with error");
3043
3044 assert!(!result.ok(), "source of non-existent file should fail");
3045 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
3046 }
3047
3048 #[tokio::test]
3049 async fn test_source_missing_filename() {
3050 let kernel = Kernel::transient().expect("failed to create kernel");
3051
3052 let result = kernel
3054 .execute("source")
3055 .await
3056 .expect("source should not fail with error");
3057
3058 assert!(!result.ok(), "source without filename should fail");
3059 assert!(result.err.contains("missing filename"), "error should mention missing filename");
3060 }
3061
3062 #[tokio::test]
3063 async fn test_source_executes_multiple_statements() {
3064 let kernel = Kernel::transient().expect("failed to create kernel");
3065
3066 kernel
3068 .execute(r#"write "/multi.kai" 'A=1
3069B=2
3070C=3'"#)
3071 .await
3072 .expect("write failed");
3073
3074 kernel
3076 .execute(r#"source "/multi.kai""#)
3077 .await
3078 .expect("source failed");
3079
3080 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
3082 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
3083 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
3084 }
3085
3086 #[tokio::test]
3087 async fn test_source_can_define_functions() {
3088 let kernel = Kernel::transient().expect("failed to create kernel");
3089
3090 kernel
3092 .execute(r#"write "/functions.kai" 'greet() {
3093 echo "Hello, $1!"
3094}'"#)
3095 .await
3096 .expect("write failed");
3097
3098 kernel
3100 .execute(r#"source "/functions.kai""#)
3101 .await
3102 .expect("source failed");
3103
3104 let result = kernel
3106 .execute(r#"greet "World""#)
3107 .await
3108 .expect("greet failed");
3109
3110 assert!(result.ok());
3111 assert!(result.out.contains("Hello, World!"));
3112 }
3113
3114 #[tokio::test]
3115 async fn test_source_inherits_error_exit() {
3116 let kernel = Kernel::transient().expect("failed to create kernel");
3117
3118 kernel.execute("set -e").await.expect("set -e failed");
3120
3121 kernel
3123 .execute(r#"write "/fail.kai" 'BEFORE="yes"
3124false
3125AFTER="yes"'"#)
3126 .await
3127 .expect("write failed");
3128
3129 kernel
3131 .execute(r#"source "/fail.kai""#)
3132 .await
3133 .ok();
3134
3135 let before = kernel.get_var("BEFORE").await;
3137 assert_eq!(before, Some(Value::String("yes".into())));
3138
3139 }
3142
3143 #[tokio::test]
3148 async fn test_case_simple_match() {
3149 let kernel = Kernel::transient().expect("failed to create kernel");
3150
3151 let result = kernel
3152 .execute(r#"
3153 case "hello" in
3154 hello) echo "matched hello" ;;
3155 world) echo "matched world" ;;
3156 esac
3157 "#)
3158 .await
3159 .expect("case failed");
3160
3161 assert!(result.ok());
3162 assert_eq!(result.out.trim(), "matched hello");
3163 }
3164
3165 #[tokio::test]
3166 async fn test_case_wildcard_match() {
3167 let kernel = Kernel::transient().expect("failed to create kernel");
3168
3169 let result = kernel
3170 .execute(r#"
3171 case "main.rs" in
3172 "*.py") echo "Python" ;;
3173 "*.rs") echo "Rust" ;;
3174 "*") echo "Unknown" ;;
3175 esac
3176 "#)
3177 .await
3178 .expect("case failed");
3179
3180 assert!(result.ok());
3181 assert_eq!(result.out.trim(), "Rust");
3182 }
3183
3184 #[tokio::test]
3185 async fn test_case_default_match() {
3186 let kernel = Kernel::transient().expect("failed to create kernel");
3187
3188 let result = kernel
3189 .execute(r#"
3190 case "unknown.xyz" in
3191 "*.py") echo "Python" ;;
3192 "*.rs") echo "Rust" ;;
3193 "*") echo "Default" ;;
3194 esac
3195 "#)
3196 .await
3197 .expect("case failed");
3198
3199 assert!(result.ok());
3200 assert_eq!(result.out.trim(), "Default");
3201 }
3202
3203 #[tokio::test]
3204 async fn test_case_no_match() {
3205 let kernel = Kernel::transient().expect("failed to create kernel");
3206
3207 let result = kernel
3209 .execute(r#"
3210 case "nope" in
3211 "yes") echo "yes" ;;
3212 "no") echo "no" ;;
3213 esac
3214 "#)
3215 .await
3216 .expect("case failed");
3217
3218 assert!(result.ok());
3219 assert!(result.out.is_empty(), "no match should produce empty output");
3220 }
3221
3222 #[tokio::test]
3223 async fn test_case_with_variable() {
3224 let kernel = Kernel::transient().expect("failed to create kernel");
3225
3226 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
3227
3228 let result = kernel
3229 .execute(r#"
3230 case ${LANG} in
3231 python) echo "snake" ;;
3232 rust) echo "crab" ;;
3233 go) echo "gopher" ;;
3234 esac
3235 "#)
3236 .await
3237 .expect("case failed");
3238
3239 assert!(result.ok());
3240 assert_eq!(result.out.trim(), "crab");
3241 }
3242
3243 #[tokio::test]
3244 async fn test_case_multiple_patterns() {
3245 let kernel = Kernel::transient().expect("failed to create kernel");
3246
3247 let result = kernel
3248 .execute(r#"
3249 case "yes" in
3250 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
3251 "n"|"no"|"N"|"NO") echo "negative" ;;
3252 esac
3253 "#)
3254 .await
3255 .expect("case failed");
3256
3257 assert!(result.ok());
3258 assert_eq!(result.out.trim(), "affirmative");
3259 }
3260
3261 #[tokio::test]
3262 async fn test_case_glob_question_mark() {
3263 let kernel = Kernel::transient().expect("failed to create kernel");
3264
3265 let result = kernel
3266 .execute(r#"
3267 case "test1" in
3268 "test?") echo "matched test?" ;;
3269 "*") echo "default" ;;
3270 esac
3271 "#)
3272 .await
3273 .expect("case failed");
3274
3275 assert!(result.ok());
3276 assert_eq!(result.out.trim(), "matched test?");
3277 }
3278
3279 #[tokio::test]
3280 async fn test_case_char_class() {
3281 let kernel = Kernel::transient().expect("failed to create kernel");
3282
3283 let result = kernel
3284 .execute(r#"
3285 case "Yes" in
3286 "[Yy]*") echo "yes-like" ;;
3287 "[Nn]*") echo "no-like" ;;
3288 esac
3289 "#)
3290 .await
3291 .expect("case failed");
3292
3293 assert!(result.ok());
3294 assert_eq!(result.out.trim(), "yes-like");
3295 }
3296
3297 #[tokio::test]
3302 async fn test_cat_from_pipeline() {
3303 let kernel = Kernel::transient().expect("failed to create kernel");
3304
3305 let result = kernel
3306 .execute(r#"echo "piped text" | cat"#)
3307 .await
3308 .expect("cat pipeline failed");
3309
3310 assert!(result.ok(), "cat failed: {}", result.err);
3311 assert_eq!(result.out.trim(), "piped text");
3312 }
3313
3314 #[tokio::test]
3315 async fn test_cat_from_pipeline_multiline() {
3316 let kernel = Kernel::transient().expect("failed to create kernel");
3317
3318 let result = kernel
3319 .execute(r#"echo "line1\nline2" | cat -n"#)
3320 .await
3321 .expect("cat pipeline failed");
3322
3323 assert!(result.ok(), "cat failed: {}", result.err);
3324 assert!(result.out.contains("1\t"), "output: {}", result.out);
3325 }
3326
3327 #[tokio::test]
3332 async fn test_heredoc_basic() {
3333 let kernel = Kernel::transient().expect("failed to create kernel");
3334
3335 let result = kernel
3336 .execute("cat <<EOF\nhello\nEOF")
3337 .await
3338 .expect("heredoc failed");
3339
3340 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3341 assert_eq!(result.out.trim(), "hello");
3342 }
3343
3344 #[tokio::test]
3345 async fn test_arithmetic_in_string() {
3346 let kernel = Kernel::transient().expect("failed to create kernel");
3347
3348 let result = kernel
3349 .execute(r#"echo "result: $((1 + 2))""#)
3350 .await
3351 .expect("arithmetic in string failed");
3352
3353 assert!(result.ok(), "echo failed: {}", result.err);
3354 assert_eq!(result.out.trim(), "result: 3");
3355 }
3356
3357 #[tokio::test]
3358 async fn test_heredoc_multiline() {
3359 let kernel = Kernel::transient().expect("failed to create kernel");
3360
3361 let result = kernel
3362 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
3363 .await
3364 .expect("heredoc failed");
3365
3366 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3367 assert!(result.out.contains("line1"), "output: {}", result.out);
3368 assert!(result.out.contains("line2"), "output: {}", result.out);
3369 assert!(result.out.contains("line3"), "output: {}", result.out);
3370 }
3371
3372 #[tokio::test]
3377 async fn test_read_from_pipeline() {
3378 let kernel = Kernel::transient().expect("failed to create kernel");
3379
3380 let result = kernel
3382 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
3383 .await
3384 .expect("read pipeline failed");
3385
3386 assert!(result.ok(), "read failed: {}", result.err);
3387 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
3388 }
3389
3390 #[tokio::test]
3391 async fn test_read_multiple_vars_from_pipeline() {
3392 let kernel = Kernel::transient().expect("failed to create kernel");
3393
3394 let result = kernel
3395 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
3396 .await
3397 .expect("read pipeline failed");
3398
3399 assert!(result.ok(), "read failed: {}", result.err);
3400 assert!(result.out.contains("John is 42"), "output: {}", result.out);
3401 }
3402
3403 #[tokio::test]
3408 async fn test_posix_function_with_positional_params() {
3409 let kernel = Kernel::transient().expect("failed to create kernel");
3410
3411 kernel
3413 .execute(r#"greet() { echo "Hello, $1!" }"#)
3414 .await
3415 .expect("function definition failed");
3416
3417 let result = kernel
3419 .execute(r#"greet "Amy""#)
3420 .await
3421 .expect("function call failed");
3422
3423 assert!(result.ok(), "greet failed: {}", result.err);
3424 assert_eq!(result.out.trim(), "Hello, Amy!");
3425 }
3426
3427 #[tokio::test]
3428 async fn test_posix_function_multiple_args() {
3429 let kernel = Kernel::transient().expect("failed to create kernel");
3430
3431 kernel
3433 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
3434 .await
3435 .expect("function definition failed");
3436
3437 let result = kernel
3439 .execute(r#"add_greeting "Hello" "World""#)
3440 .await
3441 .expect("function call failed");
3442
3443 assert!(result.ok(), "function failed: {}", result.err);
3444 assert_eq!(result.out.trim(), "Hello World!");
3445 }
3446
3447 #[tokio::test]
3448 async fn test_bash_function_with_positional_params() {
3449 let kernel = Kernel::transient().expect("failed to create kernel");
3450
3451 kernel
3453 .execute(r#"function greet { echo "Hi $1" }"#)
3454 .await
3455 .expect("function definition failed");
3456
3457 let result = kernel
3459 .execute(r#"greet "Bob""#)
3460 .await
3461 .expect("function call failed");
3462
3463 assert!(result.ok(), "greet failed: {}", result.err);
3464 assert_eq!(result.out.trim(), "Hi Bob");
3465 }
3466
3467 #[tokio::test]
3468 async fn test_shell_function_with_all_args() {
3469 let kernel = Kernel::transient().expect("failed to create kernel");
3470
3471 kernel
3473 .execute(r#"echo_all() { echo "args: $@" }"#)
3474 .await
3475 .expect("function definition failed");
3476
3477 let result = kernel
3479 .execute(r#"echo_all "a" "b" "c""#)
3480 .await
3481 .expect("function call failed");
3482
3483 assert!(result.ok(), "function failed: {}", result.err);
3484 assert_eq!(result.out.trim(), "args: a b c");
3485 }
3486
3487 #[tokio::test]
3488 async fn test_shell_function_with_arg_count() {
3489 let kernel = Kernel::transient().expect("failed to create kernel");
3490
3491 kernel
3493 .execute(r#"count_args() { echo "count: $#" }"#)
3494 .await
3495 .expect("function definition failed");
3496
3497 let result = kernel
3499 .execute(r#"count_args "x" "y" "z""#)
3500 .await
3501 .expect("function call failed");
3502
3503 assert!(result.ok(), "function failed: {}", result.err);
3504 assert_eq!(result.out.trim(), "count: 3");
3505 }
3506
3507 #[tokio::test]
3508 async fn test_shell_function_shared_scope() {
3509 let kernel = Kernel::transient().expect("failed to create kernel");
3510
3511 kernel
3513 .execute(r#"PARENT_VAR="visible""#)
3514 .await
3515 .expect("set failed");
3516
3517 kernel
3519 .execute(r#"modify_parent() {
3520 echo "saw: ${PARENT_VAR}"
3521 PARENT_VAR="changed by function"
3522 }"#)
3523 .await
3524 .expect("function definition failed");
3525
3526 let result = kernel.execute("modify_parent").await.expect("function failed");
3528
3529 assert!(
3530 result.out.contains("visible"),
3531 "Shell function should access parent scope, got: {}",
3532 result.out
3533 );
3534
3535 let var = kernel.get_var("PARENT_VAR").await;
3537 assert_eq!(
3538 var,
3539 Some(Value::String("changed by function".into())),
3540 "Shell function should modify parent scope"
3541 );
3542 }
3543
3544 #[tokio::test]
3549 async fn test_script_execution_from_path() {
3550 let kernel = Kernel::transient().expect("failed to create kernel");
3551
3552 kernel.execute(r#"mkdir "/bin""#).await.ok();
3554 kernel
3555 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
3556 .await
3557 .expect("write script failed");
3558
3559 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3561
3562 let result = kernel
3564 .execute("hello")
3565 .await
3566 .expect("script execution failed");
3567
3568 assert!(result.ok(), "script failed: {}", result.err);
3569 assert_eq!(result.out.trim(), "Hello from script!");
3570 }
3571
3572 #[tokio::test]
3573 async fn test_script_with_args() {
3574 let kernel = Kernel::transient().expect("failed to create kernel");
3575
3576 kernel.execute(r#"mkdir "/bin""#).await.ok();
3578 kernel
3579 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
3580 .await
3581 .expect("write script failed");
3582
3583 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3585
3586 let result = kernel
3588 .execute(r#"greet "World""#)
3589 .await
3590 .expect("script execution failed");
3591
3592 assert!(result.ok(), "script failed: {}", result.err);
3593 assert_eq!(result.out.trim(), "Hello, World!");
3594 }
3595
3596 #[tokio::test]
3597 async fn test_script_not_found() {
3598 let kernel = Kernel::transient().expect("failed to create kernel");
3599
3600 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
3602
3603 let result = kernel
3605 .execute("noscript")
3606 .await
3607 .expect("execution failed");
3608
3609 assert!(!result.ok(), "should fail with command not found");
3610 assert_eq!(result.code, 127);
3611 assert!(result.err.contains("command not found"));
3612 }
3613
3614 #[tokio::test]
3615 async fn test_script_path_search_order() {
3616 let kernel = Kernel::transient().expect("failed to create kernel");
3617
3618 kernel.execute(r#"mkdir "/first""#).await.ok();
3621 kernel.execute(r#"mkdir "/second""#).await.ok();
3622 kernel
3623 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
3624 .await
3625 .expect("write failed");
3626 kernel
3627 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
3628 .await
3629 .expect("write failed");
3630
3631 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
3633
3634 let result = kernel
3636 .execute("myscript")
3637 .await
3638 .expect("script execution failed");
3639
3640 assert!(result.ok(), "script failed: {}", result.err);
3641 assert_eq!(result.out.trim(), "from first");
3642 }
3643
3644 #[tokio::test]
3649 async fn test_last_exit_code_success() {
3650 let kernel = Kernel::transient().expect("failed to create kernel");
3651
3652 let result = kernel.execute("true; echo $?").await.expect("execution failed");
3654 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
3655 }
3656
3657 #[tokio::test]
3658 async fn test_last_exit_code_failure() {
3659 let kernel = Kernel::transient().expect("failed to create kernel");
3660
3661 let result = kernel.execute("false; echo $?").await.expect("execution failed");
3663 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
3664 }
3665
3666 #[tokio::test]
3667 async fn test_current_pid() {
3668 let kernel = Kernel::transient().expect("failed to create kernel");
3669
3670 let result = kernel.execute("echo $$").await.expect("execution failed");
3671 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
3673 assert!(pid > 0, "PID should be positive");
3674 }
3675
3676 #[tokio::test]
3677 async fn test_unset_variable_expands_to_empty() {
3678 let kernel = Kernel::transient().expect("failed to create kernel");
3679
3680 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
3682 assert_eq!(result.out.trim(), "prefix::suffix");
3683 }
3684
3685 #[tokio::test]
3686 async fn test_eq_ne_operators() {
3687 let kernel = Kernel::transient().expect("failed to create kernel");
3688
3689 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
3691 assert_eq!(result.out.trim(), "eq works");
3692
3693 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
3695 assert_eq!(result.out.trim(), "ne works");
3696
3697 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
3699 assert_eq!(result.out.trim(), "correct");
3700 }
3701
3702 #[tokio::test]
3703 async fn test_escaped_dollar_in_string() {
3704 let kernel = Kernel::transient().expect("failed to create kernel");
3705
3706 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
3708 assert_eq!(result.out.trim(), "$100");
3709 }
3710
3711 #[tokio::test]
3712 async fn test_special_vars_in_interpolation() {
3713 let kernel = Kernel::transient().expect("failed to create kernel");
3714
3715 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
3717 assert_eq!(result.out.trim(), "exit: 0");
3718
3719 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
3721 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
3722 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
3723 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
3724 }
3725
3726 #[tokio::test]
3731 async fn test_command_subst_assignment() {
3732 let kernel = Kernel::transient().expect("failed to create kernel");
3733
3734 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
3736 assert_eq!(result.out.trim(), "hello");
3737 }
3738
3739 #[tokio::test]
3740 async fn test_command_subst_with_args() {
3741 let kernel = Kernel::transient().expect("failed to create kernel");
3742
3743 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
3745 assert_eq!(result.out.trim(), "a b c");
3746 }
3747
3748 #[tokio::test]
3749 async fn test_command_subst_nested_vars() {
3750 let kernel = Kernel::transient().expect("failed to create kernel");
3751
3752 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
3754 assert_eq!(result.out.trim(), "hello world");
3755 }
3756
3757 #[tokio::test]
3758 async fn test_background_job_basic() {
3759 use std::time::Duration;
3760
3761 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
3762
3763 let result = kernel.execute("echo hello &").await.expect("execution failed");
3765 assert!(result.ok(), "background command should succeed: {}", result.err);
3766 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
3767
3768 tokio::time::sleep(Duration::from_millis(100)).await;
3770
3771 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
3773 assert!(status.ok(), "status should succeed: {}", status.err);
3774 assert!(
3775 status.out.contains("done:") || status.out.contains("running"),
3776 "should have valid status: {}",
3777 status.out
3778 );
3779
3780 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
3782 assert!(stdout.ok());
3783 assert!(stdout.out.contains("hello"));
3784 }
3785
3786}