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,
63
64 Sandboxed {
77 root: Option<PathBuf>,
80 },
81
82 NoLocal,
93}
94
95impl Default for VfsMountMode {
96 fn default() -> Self {
97 VfsMountMode::Sandboxed { root: None }
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct KernelConfig {
104 pub name: String,
106
107 pub vfs_mode: VfsMountMode,
109
110 pub cwd: PathBuf,
112
113 pub skip_validation: bool,
119
120 pub interactive: bool,
125}
126
127fn default_sandbox_root() -> PathBuf {
129 std::env::var("HOME")
130 .map(PathBuf::from)
131 .unwrap_or_else(|_| PathBuf::from("/"))
132}
133
134impl Default for KernelConfig {
135 fn default() -> Self {
136 let home = default_sandbox_root();
137 Self {
138 name: "default".to_string(),
139 vfs_mode: VfsMountMode::Sandboxed { root: None },
140 cwd: home,
141 skip_validation: false,
142 interactive: false,
143 }
144 }
145}
146
147impl KernelConfig {
148 pub fn transient() -> Self {
150 let home = default_sandbox_root();
151 Self {
152 name: "transient".to_string(),
153 vfs_mode: VfsMountMode::Sandboxed { root: None },
154 cwd: home,
155 skip_validation: false,
156 interactive: false,
157 }
158 }
159
160 pub fn named(name: &str) -> Self {
162 let home = default_sandbox_root();
163 Self {
164 name: name.to_string(),
165 vfs_mode: VfsMountMode::Sandboxed { root: None },
166 cwd: home,
167 skip_validation: false,
168 interactive: false,
169 }
170 }
171
172 pub fn repl() -> Self {
177 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
178 Self {
179 name: "repl".to_string(),
180 vfs_mode: VfsMountMode::Passthrough,
181 cwd,
182 skip_validation: false,
183 interactive: false,
184 }
185 }
186
187 pub fn mcp() -> Self {
192 let home = default_sandbox_root();
193 Self {
194 name: "mcp".to_string(),
195 vfs_mode: VfsMountMode::Sandboxed { root: None },
196 cwd: home,
197 skip_validation: false,
198 interactive: false,
199 }
200 }
201
202 pub fn mcp_with_root(root: PathBuf) -> Self {
206 Self {
207 name: "mcp".to_string(),
208 vfs_mode: VfsMountMode::Sandboxed { root: Some(root.clone()) },
209 cwd: root,
210 skip_validation: false,
211 interactive: false,
212 }
213 }
214
215 pub fn isolated() -> Self {
219 Self {
220 name: "isolated".to_string(),
221 vfs_mode: VfsMountMode::NoLocal,
222 cwd: PathBuf::from("/"),
223 skip_validation: false,
224 interactive: false,
225 }
226 }
227
228 pub fn with_vfs_mode(mut self, mode: VfsMountMode) -> Self {
230 self.vfs_mode = mode;
231 self
232 }
233
234 pub fn with_cwd(mut self, cwd: PathBuf) -> Self {
236 self.cwd = cwd;
237 self
238 }
239
240 pub fn with_skip_validation(mut self, skip: bool) -> Self {
242 self.skip_validation = skip;
243 self
244 }
245
246 pub fn with_interactive(mut self, interactive: bool) -> Self {
248 self.interactive = interactive;
249 self
250 }
251}
252
253pub struct Kernel {
258 name: String,
260 scope: RwLock<Scope>,
262 tools: Arc<ToolRegistry>,
264 user_tools: RwLock<HashMap<String, ToolDef>>,
266 vfs: Arc<VfsRouter>,
268 jobs: Arc<JobManager>,
270 runner: PipelineRunner,
272 exec_ctx: RwLock<ExecContext>,
274 skip_validation: bool,
276 interactive: bool,
278}
279
280impl Kernel {
281 pub fn new(config: KernelConfig) -> Result<Self> {
283 let mut vfs = Self::setup_vfs(&config);
284 let jobs = Arc::new(JobManager::new());
285
286 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
288
289 let vfs = Arc::new(vfs);
290
291 let mut tools = ToolRegistry::new();
293 register_builtins(&mut tools);
294 let tools = Arc::new(tools);
295
296 let runner = PipelineRunner::new(tools.clone());
298
299 let scope = Scope::new();
300 let cwd = config.cwd;
301
302 let mut exec_ctx = ExecContext::with_vfs_and_tools(vfs.clone(), tools.clone());
304 exec_ctx.set_cwd(cwd);
305 exec_ctx.set_job_manager(jobs.clone());
306 exec_ctx.set_tool_schemas(tools.schemas());
307
308 Ok(Self {
309 name: config.name,
310 scope: RwLock::new(scope),
311 tools,
312 user_tools: RwLock::new(HashMap::new()),
313 vfs,
314 jobs,
315 runner,
316 exec_ctx: RwLock::new(exec_ctx),
317 skip_validation: config.skip_validation,
318 interactive: config.interactive,
319 })
320 }
321
322 fn setup_vfs(config: &KernelConfig) -> VfsRouter {
324 let mut vfs = VfsRouter::new();
325
326 match &config.vfs_mode {
327 VfsMountMode::Passthrough => {
328 vfs.mount("/", LocalFs::new(PathBuf::from("/")));
330 vfs.mount("/v", MemoryFs::new());
332 vfs.mount("/scratch", MemoryFs::new());
333 }
334 VfsMountMode::Sandboxed { root } => {
335 vfs.mount("/", MemoryFs::new());
337 vfs.mount("/v", MemoryFs::new());
338 vfs.mount("/scratch", MemoryFs::new());
339
340 vfs.mount("/tmp", LocalFs::new(PathBuf::from("/tmp")));
342
343 let local_root = root.clone().unwrap_or_else(|| {
345 std::env::var("HOME")
346 .map(PathBuf::from)
347 .unwrap_or_else(|_| PathBuf::from("/"))
348 });
349
350 let mount_point = local_root.to_string_lossy().to_string();
354 vfs.mount(&mount_point, LocalFs::new(local_root));
355 }
356 VfsMountMode::NoLocal => {
357 vfs.mount("/", MemoryFs::new());
359 vfs.mount("/tmp", MemoryFs::new());
360 vfs.mount("/v", MemoryFs::new());
361 vfs.mount("/scratch", MemoryFs::new());
362 }
363 }
364
365 vfs
366 }
367
368 pub fn transient() -> Result<Self> {
370 Self::new(KernelConfig::transient())
371 }
372
373 pub fn with_backend(backend: Arc<dyn KernelBackend>, config: KernelConfig) -> Result<Self> {
382 let mut vfs = Self::setup_vfs(&config);
384 let jobs = Arc::new(JobManager::new());
385
386 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
388
389 let vfs = Arc::new(vfs);
390
391 let mut tools = ToolRegistry::new();
393 register_builtins(&mut tools);
394 let tools = Arc::new(tools);
395
396 let runner = PipelineRunner::new(tools.clone());
398
399 let scope = Scope::new();
400 let cwd = config.cwd;
401
402 let mut exec_ctx = ExecContext::with_backend(backend);
404 exec_ctx.set_cwd(cwd);
405 exec_ctx.set_job_manager(jobs.clone());
406 exec_ctx.set_tool_schemas(tools.schemas());
407 exec_ctx.set_tools(tools.clone());
408
409 Ok(Self {
410 name: config.name,
411 scope: RwLock::new(scope),
412 tools,
413 user_tools: RwLock::new(HashMap::new()),
414 vfs,
415 jobs,
416 runner,
417 exec_ctx: RwLock::new(exec_ctx),
418 skip_validation: config.skip_validation,
419 interactive: config.interactive,
420 })
421 }
422
423 pub fn with_backend_and_virtual_paths(
443 backend: Arc<dyn KernelBackend>,
444 config: KernelConfig,
445 ) -> Result<Self> {
446 use crate::backend::VirtualOverlayBackend;
447
448 let mut vfs = VfsRouter::new();
450 let jobs = Arc::new(JobManager::new());
451
452 vfs.mount("/v/jobs", JobFs::new(jobs.clone()));
454 vfs.mount("/v/blobs", MemoryFs::new());
456 vfs.mount("/v/scratch", MemoryFs::new());
458
459 let vfs = Arc::new(vfs);
460
461 let overlay: Arc<dyn KernelBackend> = Arc::new(VirtualOverlayBackend::new(backend, vfs.clone()));
463
464 let mut tools = ToolRegistry::new();
466 register_builtins(&mut tools);
467 let tools = Arc::new(tools);
468
469 let runner = PipelineRunner::new(tools.clone());
471
472 let scope = Scope::new();
473 let cwd = config.cwd;
474
475 let mut exec_ctx = ExecContext::with_backend(overlay);
477 exec_ctx.set_cwd(cwd);
478 exec_ctx.set_job_manager(jobs.clone());
479 exec_ctx.set_tool_schemas(tools.schemas());
480 exec_ctx.set_tools(tools.clone());
481
482 Ok(Self {
483 name: config.name,
484 scope: RwLock::new(scope),
485 tools,
486 user_tools: RwLock::new(HashMap::new()),
487 vfs,
488 jobs,
489 runner,
490 exec_ctx: RwLock::new(exec_ctx),
491 skip_validation: config.skip_validation,
492 interactive: config.interactive,
493 })
494 }
495
496 pub fn name(&self) -> &str {
498 &self.name
499 }
500
501 pub async fn execute(&self, input: &str) -> Result<ExecResult> {
505 self.execute_streaming(input, &mut |_| {}).await
506 }
507
508 pub async fn execute_streaming(
517 &self,
518 input: &str,
519 on_output: &mut dyn FnMut(&ExecResult),
520 ) -> Result<ExecResult> {
521 let program = parse(input).map_err(|errors| {
522 let msg = errors
523 .iter()
524 .map(|e| e.to_string())
525 .collect::<Vec<_>>()
526 .join("; ");
527 anyhow::anyhow!("parse error: {}", msg)
528 })?;
529
530 if !self.skip_validation {
532 let user_tools = self.user_tools.read().await;
533 let validator = Validator::new(&self.tools, &user_tools);
534 let issues = validator.validate(&program);
535
536 let errors: Vec<_> = issues
538 .iter()
539 .filter(|i| i.severity == Severity::Error)
540 .collect();
541
542 if !errors.is_empty() {
543 let error_msg = errors
544 .iter()
545 .map(|e| e.format(input))
546 .collect::<Vec<_>>()
547 .join("\n");
548 return Err(anyhow::anyhow!("validation failed:\n{}", error_msg));
549 }
550
551 for warning in issues.iter().filter(|i| i.severity == Severity::Warning) {
553 tracing::trace!("validation: {}", warning.format(input));
554 }
555 }
556
557 let mut result = ExecResult::success("");
558
559 for stmt in program.statements {
560 if matches!(stmt, Stmt::Empty) {
561 continue;
562 }
563 let flow = self.execute_stmt_flow(&stmt).await?;
564 match flow {
565 ControlFlow::Normal(r) => {
566 on_output(&r);
567 let last_output = r.output.clone();
571 accumulate_result(&mut result, &r);
572 result.output = last_output;
573 }
574 ControlFlow::Exit { code } => {
575 let exit_result = ExecResult::success(code.to_string());
576 return Ok(exit_result);
577 }
578 ControlFlow::Return { value } => {
579 on_output(&value);
580 result = value;
581 }
582 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
583 on_output(&r);
584 result = r;
585 }
586 }
587 }
588
589 Ok(result)
590 }
591
592 fn execute_stmt_flow<'a>(
594 &'a self,
595 stmt: &'a Stmt,
596 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ControlFlow>> + Send + 'a>> {
597 Box::pin(async move {
598 match stmt {
599 Stmt::Assignment(assign) => {
600 let value = self.eval_expr_async(&assign.value).await
602 .context("failed to evaluate assignment")?;
603 let mut scope = self.scope.write().await;
604 if assign.local {
605 scope.set(&assign.name, value.clone());
607 } else {
608 scope.set_global(&assign.name, value.clone());
610 }
611 drop(scope);
612
613 Ok(ControlFlow::ok(ExecResult::success("")))
615 }
616 Stmt::Command(cmd) => {
617 let pipeline = crate::ast::Pipeline {
620 commands: vec![cmd.clone()],
621 background: false,
622 };
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::Pipeline(pipeline) => {
637 let result = self.execute_pipeline(pipeline).await?;
638 self.update_last_result(&result).await;
639
640 if !result.ok() {
642 let scope = self.scope.read().await;
643 if scope.error_exit_enabled() {
644 return Ok(ControlFlow::exit_code(result.code));
645 }
646 }
647
648 Ok(ControlFlow::ok(result))
649 }
650 Stmt::If(if_stmt) => {
651 let cond_value = self.eval_expr_async(&if_stmt.condition).await?;
653
654 let branch = if is_truthy(&cond_value) {
655 &if_stmt.then_branch
656 } else {
657 if_stmt.else_branch.as_deref().unwrap_or(&[])
658 };
659
660 let mut flow = ControlFlow::ok(ExecResult::success(""));
661 for stmt in branch {
662 flow = self.execute_stmt_flow(stmt).await?;
663 if !flow.is_normal() {
664 return Ok(flow);
665 }
666 }
667 Ok(flow)
668 }
669 Stmt::For(for_loop) => {
670 let mut items: Vec<Value> = Vec::new();
673 for item_expr in &for_loop.items {
674 let item = self.eval_expr_async(item_expr).await?;
675 match &item {
677 Value::Json(serde_json::Value::Array(arr)) => {
679 for elem in arr {
680 items.push(json_to_value(elem.clone()));
681 }
682 }
683 Value::String(_) => {
686 items.push(item);
687 }
688 _ => items.push(item),
690 }
691 }
692
693 let mut result = ExecResult::success("");
694 {
695 let mut scope = self.scope.write().await;
696 scope.push_frame();
697 }
698
699 'outer: for item in items {
700 {
701 let mut scope = self.scope.write().await;
702 scope.set(&for_loop.variable, item);
703 }
704 for stmt in &for_loop.body {
705 let mut flow = self.execute_stmt_flow(stmt).await?;
706 match &mut flow {
707 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
708 ControlFlow::Break { .. } => {
709 if flow.decrement_level() {
710 break 'outer;
712 }
713 let mut scope = self.scope.write().await;
715 scope.pop_frame();
716 return Ok(flow);
717 }
718 ControlFlow::Continue { .. } => {
719 if flow.decrement_level() {
720 continue 'outer;
722 }
723 let mut scope = self.scope.write().await;
725 scope.pop_frame();
726 return Ok(flow);
727 }
728 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
729 let mut scope = self.scope.write().await;
730 scope.pop_frame();
731 return Ok(flow);
732 }
733 }
734 }
735 }
736
737 {
738 let mut scope = self.scope.write().await;
739 scope.pop_frame();
740 }
741 Ok(ControlFlow::ok(result))
742 }
743 Stmt::While(while_loop) => {
744 let mut result = ExecResult::success("");
745
746 'outer: loop {
747 let cond_value = self.eval_expr_async(&while_loop.condition).await?;
749
750 if !is_truthy(&cond_value) {
751 break;
752 }
753
754 for stmt in &while_loop.body {
756 let mut flow = self.execute_stmt_flow(stmt).await?;
757 match &mut flow {
758 ControlFlow::Normal(r) => accumulate_result(&mut result, r),
759 ControlFlow::Break { .. } => {
760 if flow.decrement_level() {
761 break 'outer;
763 }
764 return Ok(flow);
766 }
767 ControlFlow::Continue { .. } => {
768 if flow.decrement_level() {
769 continue 'outer;
771 }
772 return Ok(flow);
774 }
775 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
776 return Ok(flow);
777 }
778 }
779 }
780 }
781
782 Ok(ControlFlow::ok(result))
783 }
784 Stmt::Case(case_stmt) => {
785 let match_value = {
787 let mut scope = self.scope.write().await;
788 let value = eval_expr(&case_stmt.expr, &mut scope)?;
789 value_to_string(&value)
790 };
791
792 for branch in &case_stmt.branches {
794 let matched = branch.patterns.iter().any(|pattern| {
795 glob_match(pattern, &match_value)
796 });
797
798 if matched {
799 let mut result = ControlFlow::ok(ExecResult::success(""));
801 for stmt in &branch.body {
802 result = self.execute_stmt_flow(stmt).await?;
803 if !result.is_normal() {
804 return Ok(result);
805 }
806 }
807 return Ok(result);
808 }
809 }
810
811 Ok(ControlFlow::ok(ExecResult::success("")))
813 }
814 Stmt::Break(levels) => {
815 Ok(ControlFlow::break_n(levels.unwrap_or(1)))
816 }
817 Stmt::Continue(levels) => {
818 Ok(ControlFlow::continue_n(levels.unwrap_or(1)))
819 }
820 Stmt::Return(expr) => {
821 let result = if let Some(e) = expr {
824 let mut scope = self.scope.write().await;
825 let val = eval_expr(e, &mut scope)?;
826 let code = match val {
828 Value::Int(n) => n,
829 Value::Bool(b) => if b { 0 } else { 1 },
830 _ => 0,
831 };
832 ExecResult {
833 code,
834 out: String::new(),
835 err: String::new(),
836 data: None,
837 output: None,
838 }
839 } else {
840 ExecResult::success("")
841 };
842 Ok(ControlFlow::return_value(result))
843 }
844 Stmt::Exit(expr) => {
845 let code = if let Some(e) = expr {
846 let mut scope = self.scope.write().await;
847 let val = eval_expr(e, &mut scope)?;
848 match val {
849 Value::Int(n) => n,
850 _ => 0,
851 }
852 } else {
853 0
854 };
855 Ok(ControlFlow::exit_code(code))
856 }
857 Stmt::ToolDef(tool_def) => {
858 let mut user_tools = self.user_tools.write().await;
859 user_tools.insert(tool_def.name.clone(), tool_def.clone());
860 Ok(ControlFlow::ok(ExecResult::success("")))
861 }
862 Stmt::AndChain { left, right } => {
863 let left_flow = self.execute_stmt_flow(left).await?;
865 match left_flow {
866 ControlFlow::Normal(left_result) => {
867 self.update_last_result(&left_result).await;
868 if left_result.ok() {
869 let right_flow = self.execute_stmt_flow(right).await?;
870 match right_flow {
871 ControlFlow::Normal(right_result) => {
872 self.update_last_result(&right_result).await;
873 let mut combined = left_result;
875 accumulate_result(&mut combined, &right_result);
876 Ok(ControlFlow::ok(combined))
877 }
878 other => Ok(other), }
880 } else {
881 Ok(ControlFlow::ok(left_result))
882 }
883 }
884 _ => Ok(left_flow), }
886 }
887 Stmt::OrChain { left, right } => {
888 let left_flow = self.execute_stmt_flow(left).await?;
890 match left_flow {
891 ControlFlow::Normal(left_result) => {
892 self.update_last_result(&left_result).await;
893 if !left_result.ok() {
894 let right_flow = self.execute_stmt_flow(right).await?;
895 match right_flow {
896 ControlFlow::Normal(right_result) => {
897 self.update_last_result(&right_result).await;
898 let mut combined = left_result;
900 accumulate_result(&mut combined, &right_result);
901 Ok(ControlFlow::ok(combined))
902 }
903 other => Ok(other), }
905 } else {
906 Ok(ControlFlow::ok(left_result))
907 }
908 }
909 _ => Ok(left_flow), }
911 }
912 Stmt::Test(test_expr) => {
913 let expr = crate::ast::Expr::Test(Box::new(test_expr.clone()));
915 let mut scope = self.scope.write().await;
916 let value = eval_expr(&expr, &mut scope)?;
917 drop(scope);
918 let is_true = match value {
919 crate::ast::Value::Bool(b) => b,
920 _ => false,
921 };
922 if is_true {
923 Ok(ControlFlow::ok(ExecResult::success("")))
924 } else {
925 Ok(ControlFlow::ok(ExecResult::failure(1, "")))
926 }
927 }
928 Stmt::Empty => Ok(ControlFlow::ok(ExecResult::success(""))),
929 }
930 })
931 }
932
933 async fn execute_pipeline(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
935 if pipeline.commands.is_empty() {
936 return Ok(ExecResult::success(""));
937 }
938
939 if pipeline.background {
941 return self.execute_background(pipeline).await;
942 }
943
944 let mut ctx = {
952 let ec = self.exec_ctx.read().await;
953 let scope = self.scope.read().await;
954 ExecContext {
955 backend: ec.backend.clone(),
956 scope: scope.clone(),
957 cwd: ec.cwd.clone(),
958 prev_cwd: ec.prev_cwd.clone(),
959 stdin: None,
960 stdin_data: None,
961 tool_schemas: ec.tool_schemas.clone(),
962 tools: ec.tools.clone(),
963 job_manager: ec.job_manager.clone(),
964 pipeline_position: PipelinePosition::Only,
965 }
966 }; let result = self.runner.run(&pipeline.commands, &mut ctx, self).await;
969
970 {
972 let mut ec = self.exec_ctx.write().await;
973 ec.cwd = ctx.cwd.clone();
974 ec.prev_cwd = ctx.prev_cwd.clone();
975 }
976 {
977 let mut scope = self.scope.write().await;
978 *scope = ctx.scope.clone();
979 }
980
981 Ok(result)
982 }
983
984 async fn execute_background(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
992 use tokio::sync::oneshot;
993
994 let command_str = self.format_pipeline(pipeline);
996
997 let stdout = Arc::new(BoundedStream::default_size());
999 let stderr = Arc::new(BoundedStream::default_size());
1000
1001 let (tx, rx) = oneshot::channel();
1003
1004 let job_id = self.jobs.register_with_streams(
1006 command_str.clone(),
1007 rx,
1008 stdout.clone(),
1009 stderr.clone(),
1010 ).await;
1011
1012 let runner = self.runner.clone();
1014 let commands = pipeline.commands.clone();
1015 let backend = {
1016 let ctx = self.exec_ctx.read().await;
1017 ctx.backend.clone()
1018 };
1019 let scope = {
1020 let scope = self.scope.read().await;
1021 scope.clone()
1022 };
1023 let cwd = {
1024 let ctx = self.exec_ctx.read().await;
1025 ctx.cwd.clone()
1026 };
1027 let tools = self.tools.clone();
1028 let tool_schemas = self.tools.schemas();
1029
1030 tokio::spawn(async move {
1032 let mut bg_ctx = ExecContext::with_backend(backend);
1035 bg_ctx.scope = scope;
1036 bg_ctx.cwd = cwd;
1037 bg_ctx.set_tools(tools.clone());
1038 bg_ctx.set_tool_schemas(tool_schemas);
1039
1040 let dispatcher = crate::dispatch::BackendDispatcher::new(tools);
1043
1044 let result = runner.run(&commands, &mut bg_ctx, &dispatcher).await;
1046
1047 if !result.out.is_empty() {
1049 stdout.write(result.out.as_bytes()).await;
1050 }
1051 if !result.err.is_empty() {
1052 stderr.write(result.err.as_bytes()).await;
1053 }
1054
1055 stdout.close().await;
1057 stderr.close().await;
1058
1059 let _ = tx.send(result);
1061 });
1062
1063 Ok(ExecResult::success(format!("[{}]", job_id)))
1064 }
1065
1066 fn format_pipeline(&self, pipeline: &crate::ast::Pipeline) -> String {
1068 pipeline.commands
1069 .iter()
1070 .map(|cmd| {
1071 let mut parts = vec![cmd.name.clone()];
1072 for arg in &cmd.args {
1073 match arg {
1074 Arg::Positional(expr) => {
1075 parts.push(self.format_expr(expr));
1076 }
1077 Arg::Named { key, value } => {
1078 parts.push(format!("{}={}", key, self.format_expr(value)));
1079 }
1080 Arg::ShortFlag(name) => {
1081 parts.push(format!("-{}", name));
1082 }
1083 Arg::LongFlag(name) => {
1084 parts.push(format!("--{}", name));
1085 }
1086 Arg::DoubleDash => {
1087 parts.push("--".to_string());
1088 }
1089 }
1090 }
1091 parts.join(" ")
1092 })
1093 .collect::<Vec<_>>()
1094 .join(" | ")
1095 }
1096
1097 fn format_expr(&self, expr: &Expr) -> String {
1099 match expr {
1100 Expr::Literal(Value::String(s)) => {
1101 if s.contains(' ') || s.contains('"') {
1102 format!("'{}'", s.replace('\'', "\\'"))
1103 } else {
1104 s.clone()
1105 }
1106 }
1107 Expr::Literal(Value::Int(i)) => i.to_string(),
1108 Expr::Literal(Value::Float(f)) => f.to_string(),
1109 Expr::Literal(Value::Bool(b)) => b.to_string(),
1110 Expr::Literal(Value::Null) => "null".to_string(),
1111 Expr::VarRef(path) => {
1112 let name = path.segments.iter()
1113 .map(|seg| match seg {
1114 crate::ast::VarSegment::Field(f) => f.clone(),
1115 })
1116 .collect::<Vec<_>>()
1117 .join(".");
1118 format!("${{{}}}", name)
1119 }
1120 Expr::Interpolated(_) => "\"...\"".to_string(),
1121 _ => "...".to_string(),
1122 }
1123 }
1124
1125 async fn execute_command(&self, name: &str, args: &[Arg]) -> Result<ExecResult> {
1127 match name {
1129 "true" => return Ok(ExecResult::success("")),
1130 "false" => return Ok(ExecResult::failure(1, "")),
1131 "source" | "." => return self.execute_source(args).await,
1132 _ => {}
1133 }
1134
1135 {
1137 let user_tools = self.user_tools.read().await;
1138 if let Some(tool_def) = user_tools.get(name) {
1139 let tool_def = tool_def.clone();
1140 drop(user_tools);
1141 return self.execute_user_tool(tool_def, args).await;
1142 }
1143 }
1144
1145 let tool = match self.tools.get(name) {
1147 Some(t) => t,
1148 None => {
1149 if let Some(result) = self.try_execute_script(name, args).await? {
1151 return Ok(result);
1152 }
1153 if let Some(result) = self.try_execute_external(name, args).await? {
1155 return Ok(result);
1156 }
1157
1158 let tool_args = self.build_args_async(args, None).await?;
1160 let mut ctx = self.exec_ctx.write().await;
1161 {
1162 let scope = self.scope.read().await;
1163 ctx.scope = scope.clone();
1164 }
1165 let backend = ctx.backend.clone();
1166 match backend.call_tool(name, tool_args, &mut ctx).await {
1167 Ok(tool_result) => {
1168 let mut scope = self.scope.write().await;
1169 *scope = ctx.scope.clone();
1170 let mut exec = ExecResult::from_output(
1171 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1172 );
1173 exec.output = tool_result.output;
1174 return Ok(exec);
1175 }
1176 Err(BackendError::ToolNotFound(_)) => {
1177 }
1179 Err(e) => {
1180 return Ok(ExecResult::failure(1, e.to_string()));
1181 }
1182 }
1183
1184 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1185 }
1186 };
1187
1188 let schema = tool.schema();
1190 let mut tool_args = self.build_args_async(args, Some(&schema)).await?;
1191 let output_format = extract_output_format(&mut tool_args, Some(&schema));
1192
1193 let mut ctx = self.exec_ctx.write().await;
1195 {
1196 let scope = self.scope.read().await;
1197 ctx.scope = scope.clone();
1198 }
1199
1200 let result = tool.execute(tool_args, &mut ctx).await;
1201
1202 {
1204 let mut scope = self.scope.write().await;
1205 *scope = ctx.scope.clone();
1206 }
1207
1208 let result = match output_format {
1209 Some(format) => apply_output_format(result, format),
1210 None => result,
1211 };
1212
1213 Ok(result)
1214 }
1215
1216 async fn build_args_async(&self, args: &[Arg], schema: Option<&crate::tools::ToolSchema>) -> Result<ToolArgs> {
1220 let mut tool_args = ToolArgs::new();
1221 let param_lookup = schema.map(schema_param_lookup).unwrap_or_default();
1222
1223 let mut consumed: std::collections::HashSet<usize> = std::collections::HashSet::new();
1225 let mut past_double_dash = false;
1226
1227 let positional_indices: Vec<usize> = args.iter().enumerate()
1229 .filter_map(|(i, a)| matches!(a, Arg::Positional(_)).then_some(i))
1230 .collect();
1231
1232 let mut i = 0;
1233 while i < args.len() {
1234 match &args[i] {
1235 Arg::DoubleDash => {
1236 past_double_dash = true;
1237 }
1238 Arg::Positional(expr) => {
1239 if !consumed.contains(&i) {
1240 let value = self.eval_expr_async(expr).await?;
1241 let value = apply_tilde_expansion(value);
1242 tool_args.positional.push(value);
1243 }
1244 }
1245 Arg::Named { key, value } => {
1246 let val = self.eval_expr_async(value).await?;
1247 let val = apply_tilde_expansion(val);
1248 tool_args.named.insert(key.clone(), val);
1249 }
1250 Arg::ShortFlag(name) => {
1251 if past_double_dash {
1252 tool_args.positional.push(Value::String(format!("-{name}")));
1253 } else if name.len() == 1 {
1254 let flag_name = name.as_str();
1255 let lookup = param_lookup.get(flag_name);
1256 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1257
1258 if is_bool {
1259 tool_args.flags.insert(flag_name.to_string());
1260 } else {
1261 let canonical = lookup.map(|(name, _)| *name).unwrap_or(flag_name);
1263 let next_pos = positional_indices.iter()
1264 .find(|idx| **idx > i && !consumed.contains(idx));
1265
1266 if let Some(&pos_idx) = next_pos {
1267 if let Arg::Positional(expr) = &args[pos_idx] {
1268 let value = self.eval_expr_async(expr).await?;
1269 let value = apply_tilde_expansion(value);
1270 tool_args.named.insert(canonical.to_string(), value);
1271 consumed.insert(pos_idx);
1272 }
1273 } else {
1274 tool_args.flags.insert(flag_name.to_string());
1275 }
1276 }
1277 } else {
1278 for c in name.chars() {
1280 tool_args.flags.insert(c.to_string());
1281 }
1282 }
1283 }
1284 Arg::LongFlag(name) => {
1285 if past_double_dash {
1286 tool_args.positional.push(Value::String(format!("--{name}")));
1287 } else {
1288 let lookup = param_lookup.get(name.as_str());
1289 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1290
1291 if is_bool {
1292 tool_args.flags.insert(name.clone());
1293 } else {
1294 let canonical = lookup.map(|(name, _)| *name).unwrap_or(name.as_str());
1295 let next_pos = positional_indices.iter()
1296 .find(|idx| **idx > i && !consumed.contains(idx));
1297
1298 if let Some(&pos_idx) = next_pos {
1299 if let Arg::Positional(expr) = &args[pos_idx] {
1300 let value = self.eval_expr_async(expr).await?;
1301 let value = apply_tilde_expansion(value);
1302 tool_args.named.insert(canonical.to_string(), value);
1303 consumed.insert(pos_idx);
1304 }
1305 } else {
1306 tool_args.flags.insert(name.clone());
1307 }
1308 }
1309 }
1310 }
1311 }
1312 i += 1;
1313 }
1314
1315 Ok(tool_args)
1316 }
1317
1318 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1328 let mut argv = Vec::new();
1329 for arg in args {
1330 match arg {
1331 Arg::Positional(expr) => {
1332 let value = self.eval_expr_async(expr).await?;
1333 let value = apply_tilde_expansion(value);
1334 argv.push(value_to_string(&value));
1335 }
1336 Arg::Named { key, value } => {
1337 let val = self.eval_expr_async(value).await?;
1338 let val = apply_tilde_expansion(val);
1339 argv.push(format!("{}={}", key, value_to_string(&val)));
1340 }
1341 Arg::ShortFlag(name) => {
1342 argv.push(format!("-{}", name));
1344 }
1345 Arg::LongFlag(name) => {
1346 argv.push(format!("--{}", name));
1348 }
1349 Arg::DoubleDash => {
1350 argv.push("--".to_string());
1352 }
1353 }
1354 }
1355 Ok(argv)
1356 }
1357
1358 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send + 'a>> {
1363 Box::pin(async move {
1364 match expr {
1365 Expr::Literal(value) => Ok(value.clone()),
1366 Expr::VarRef(path) => {
1367 let scope = self.scope.read().await;
1368 scope.resolve_path(path)
1369 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1370 }
1371 Expr::Interpolated(parts) => {
1372 let mut result = String::new();
1373 for part in parts {
1374 result.push_str(&self.eval_string_part_async(part).await?);
1375 }
1376 Ok(Value::String(result))
1377 }
1378 Expr::BinaryOp { left, op, right } => {
1379 match op {
1380 BinaryOp::And => {
1381 let left_val = self.eval_expr_async(left).await?;
1382 if !is_truthy(&left_val) {
1383 return Ok(left_val);
1384 }
1385 self.eval_expr_async(right).await
1386 }
1387 BinaryOp::Or => {
1388 let left_val = self.eval_expr_async(left).await?;
1389 if is_truthy(&left_val) {
1390 return Ok(left_val);
1391 }
1392 self.eval_expr_async(right).await
1393 }
1394 _ => {
1395 let mut scope = self.scope.write().await;
1397 eval_expr(expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1398 }
1399 }
1400 }
1401 Expr::CommandSubst(pipeline) => {
1402 let saved_scope = { self.scope.read().await.clone() };
1405 let saved_cwd = {
1406 let ec = self.exec_ctx.read().await;
1407 (ec.cwd.clone(), ec.prev_cwd.clone())
1408 };
1409
1410 let result = self.execute_pipeline(pipeline).await?;
1411 self.update_last_result(&result).await;
1412
1413 {
1415 let mut scope = self.scope.write().await;
1416 let last_result = scope.last_result().clone();
1417 *scope = saved_scope;
1418 scope.set_last_result(last_result);
1419 }
1420 {
1421 let mut ec = self.exec_ctx.write().await;
1422 ec.cwd = saved_cwd.0;
1423 ec.prev_cwd = saved_cwd.1;
1424 }
1425
1426 if let Some(data) = &result.data {
1428 Ok(data.clone())
1429 } else {
1430 Ok(Value::String(result.out.trim_end().to_string()))
1432 }
1433 }
1434 Expr::Test(test_expr) => {
1435 let expr = Expr::Test(test_expr.clone());
1437 let mut scope = self.scope.write().await;
1438 eval_expr(&expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1439 }
1440 Expr::Positional(n) => {
1441 let scope = self.scope.read().await;
1442 match scope.get_positional(*n) {
1443 Some(s) => Ok(Value::String(s.to_string())),
1444 None => Ok(Value::String(String::new())),
1445 }
1446 }
1447 Expr::AllArgs => {
1448 let scope = self.scope.read().await;
1449 Ok(Value::String(scope.all_args().join(" ")))
1450 }
1451 Expr::ArgCount => {
1452 let scope = self.scope.read().await;
1453 Ok(Value::Int(scope.arg_count() as i64))
1454 }
1455 Expr::VarLength(name) => {
1456 let scope = self.scope.read().await;
1457 match scope.get(name) {
1458 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1459 None => Ok(Value::Int(0)),
1460 }
1461 }
1462 Expr::VarWithDefault { name, default } => {
1463 let scope = self.scope.read().await;
1464 let use_default = match scope.get(name) {
1465 Some(value) => value_to_string(value).is_empty(),
1466 None => true,
1467 };
1468 drop(scope); if use_default {
1470 self.eval_string_parts_async(default).await.map(Value::String)
1472 } else {
1473 let scope = self.scope.read().await;
1474 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1475 }
1476 }
1477 Expr::Arithmetic(expr_str) => {
1478 let scope = self.scope.read().await;
1479 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1480 .map(Value::Int)
1481 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1482 }
1483 Expr::Command(cmd) => {
1484 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1486 Ok(Value::Bool(result.code == 0))
1487 }
1488 Expr::LastExitCode => {
1489 let scope = self.scope.read().await;
1490 Ok(Value::Int(scope.last_result().code))
1491 }
1492 Expr::CurrentPid => {
1493 let scope = self.scope.read().await;
1494 Ok(Value::Int(scope.pid() as i64))
1495 }
1496 }
1497 })
1498 }
1499
1500 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1502 Box::pin(async move {
1503 let mut result = String::new();
1504 for part in parts {
1505 result.push_str(&self.eval_string_part_async(part).await?);
1506 }
1507 Ok(result)
1508 })
1509 }
1510
1511 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1513 Box::pin(async move {
1514 match part {
1515 StringPart::Literal(s) => Ok(s.clone()),
1516 StringPart::Var(path) => {
1517 let scope = self.scope.read().await;
1518 match scope.resolve_path(path) {
1519 Some(value) => Ok(value_to_string(&value)),
1520 None => Ok(String::new()), }
1522 }
1523 StringPart::VarWithDefault { name, default } => {
1524 let scope = self.scope.read().await;
1525 let use_default = match scope.get(name) {
1526 Some(value) => value_to_string(value).is_empty(),
1527 None => true,
1528 };
1529 drop(scope); if use_default {
1531 self.eval_string_parts_async(default).await
1533 } else {
1534 let scope = self.scope.read().await;
1535 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
1536 }
1537 }
1538 StringPart::VarLength(name) => {
1539 let scope = self.scope.read().await;
1540 match scope.get(name) {
1541 Some(value) => Ok(value_to_string(value).len().to_string()),
1542 None => Ok("0".to_string()),
1543 }
1544 }
1545 StringPart::Positional(n) => {
1546 let scope = self.scope.read().await;
1547 match scope.get_positional(*n) {
1548 Some(s) => Ok(s.to_string()),
1549 None => Ok(String::new()),
1550 }
1551 }
1552 StringPart::AllArgs => {
1553 let scope = self.scope.read().await;
1554 Ok(scope.all_args().join(" "))
1555 }
1556 StringPart::ArgCount => {
1557 let scope = self.scope.read().await;
1558 Ok(scope.arg_count().to_string())
1559 }
1560 StringPart::Arithmetic(expr) => {
1561 let scope = self.scope.read().await;
1562 match crate::arithmetic::eval_arithmetic(expr, &scope) {
1563 Ok(value) => Ok(value.to_string()),
1564 Err(_) => Ok(String::new()),
1565 }
1566 }
1567 StringPart::CommandSubst(pipeline) => {
1568 let saved_scope = { self.scope.read().await.clone() };
1571 let saved_cwd = {
1572 let ec = self.exec_ctx.read().await;
1573 (ec.cwd.clone(), ec.prev_cwd.clone())
1574 };
1575
1576 let result = self.execute_pipeline(pipeline).await?;
1577
1578 {
1580 let mut scope = self.scope.write().await;
1581 let last_result = scope.last_result().clone();
1582 *scope = saved_scope;
1583 scope.set_last_result(last_result);
1584 }
1585 {
1586 let mut ec = self.exec_ctx.write().await;
1587 ec.cwd = saved_cwd.0;
1588 ec.prev_cwd = saved_cwd.1;
1589 }
1590
1591 Ok(result.out.trim_end_matches('\n').to_string())
1592 }
1593 StringPart::LastExitCode => {
1594 let scope = self.scope.read().await;
1595 Ok(scope.last_result().code.to_string())
1596 }
1597 StringPart::CurrentPid => {
1598 let scope = self.scope.read().await;
1599 Ok(scope.pid().to_string())
1600 }
1601 }
1602 })
1603 }
1604
1605 async fn update_last_result(&self, result: &ExecResult) {
1607 let mut scope = self.scope.write().await;
1608 scope.set_last_result(result.clone());
1609 }
1610
1611 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
1617 let tool_args = self.build_args_async(args, None).await?;
1619
1620 {
1622 let mut scope = self.scope.write().await;
1623 scope.push_frame();
1624 }
1625
1626 let saved_positional = {
1628 let mut scope = self.scope.write().await;
1629 let saved = scope.save_positional();
1630
1631 let positional_args: Vec<String> = tool_args.positional
1633 .iter()
1634 .map(value_to_string)
1635 .collect();
1636 scope.set_positional(&def.name, positional_args);
1637
1638 saved
1639 };
1640
1641 let mut accumulated_out = String::new();
1644 let mut accumulated_err = String::new();
1645 let mut last_code = 0i64;
1646 let mut last_data: Option<Value> = None;
1647
1648 for stmt in &def.body {
1649 match self.execute_stmt_flow(stmt).await {
1650 Ok(flow) => {
1651 match flow {
1652 ControlFlow::Normal(r) => {
1653 accumulated_out.push_str(&r.out);
1654 accumulated_err.push_str(&r.err);
1655 last_code = r.code;
1656 last_data = r.data;
1657 }
1658 ControlFlow::Return { value } => {
1659 accumulated_out.push_str(&value.out);
1661 accumulated_err.push_str(&value.err);
1662 last_code = value.code;
1663 last_data = value.data;
1664 break;
1665 }
1666 ControlFlow::Exit { code } => {
1667 let mut scope = self.scope.write().await;
1669 scope.pop_frame();
1670 scope.set_positional(saved_positional.0.clone(), saved_positional.1.clone());
1671 return Ok(ExecResult::failure(code, "exit"));
1672 }
1673 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1674 accumulated_out.push_str(&r.out);
1676 accumulated_err.push_str(&r.err);
1677 last_code = r.code;
1678 last_data = r.data;
1679 }
1680 }
1681 }
1682 Err(e) => {
1683 let mut scope = self.scope.write().await;
1685 scope.pop_frame();
1686 scope.set_positional(saved_positional.0.clone(), saved_positional.1.clone());
1687 return Err(e);
1688 }
1689 }
1690 }
1691
1692 let result = ExecResult {
1693 code: last_code,
1694 out: accumulated_out,
1695 err: accumulated_err,
1696 data: last_data,
1697 output: None,
1698 };
1699
1700 {
1702 let mut scope = self.scope.write().await;
1703 scope.pop_frame();
1704 scope.set_positional(saved_positional.0, saved_positional.1);
1705 }
1706
1707 Ok(result)
1709 }
1710
1711 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
1716 let tool_args = self.build_args_async(args, None).await?;
1718 let path = match tool_args.positional.first() {
1719 Some(Value::String(s)) => s.clone(),
1720 Some(v) => value_to_string(v),
1721 None => {
1722 return Ok(ExecResult::failure(1, "source: missing filename"));
1723 }
1724 };
1725
1726 let full_path = {
1728 let ctx = self.exec_ctx.read().await;
1729 if path.starts_with('/') {
1730 std::path::PathBuf::from(&path)
1731 } else {
1732 ctx.cwd.join(&path)
1733 }
1734 };
1735
1736 let content = {
1738 let ctx = self.exec_ctx.read().await;
1739 match ctx.backend.read(&full_path, None).await {
1740 Ok(bytes) => {
1741 String::from_utf8(bytes).map_err(|e| {
1742 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
1743 })?
1744 }
1745 Err(e) => {
1746 return Ok(ExecResult::failure(
1747 1,
1748 format!("source: {}: {}", path, e),
1749 ));
1750 }
1751 }
1752 };
1753
1754 let program = match crate::parser::parse(&content) {
1756 Ok(p) => p,
1757 Err(errors) => {
1758 let msg = errors
1759 .iter()
1760 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
1761 .collect::<Vec<_>>()
1762 .join("\n");
1763 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
1764 }
1765 };
1766
1767 let mut result = ExecResult::success("");
1769 for stmt in program.statements {
1770 if matches!(stmt, crate::ast::Stmt::Empty) {
1771 continue;
1772 }
1773
1774 match self.execute_stmt_flow(&stmt).await {
1775 Ok(flow) => {
1776 match flow {
1777 ControlFlow::Normal(r) => {
1778 result = r.clone();
1779 self.update_last_result(&r).await;
1780 }
1781 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
1782 return Err(anyhow::anyhow!(
1784 "source: {}: unexpected break/continue outside loop",
1785 path
1786 ));
1787 }
1788 ControlFlow::Return { value } => {
1789 return Ok(value);
1791 }
1792 ControlFlow::Exit { code } => {
1793 return Ok(ExecResult::failure(code, "exit"));
1795 }
1796 }
1797 }
1798 Err(e) => {
1799 return Err(e.context(format!("source: {}", path)));
1800 }
1801 }
1802 }
1803
1804 Ok(result)
1805 }
1806
1807 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1812 let path_value = {
1814 let scope = self.scope.read().await;
1815 scope
1816 .get("PATH")
1817 .map(value_to_string)
1818 .unwrap_or_else(|| "/bin".to_string())
1819 };
1820
1821 for dir in path_value.split(':') {
1823 if dir.is_empty() {
1824 continue;
1825 }
1826
1827 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
1829
1830 let exists = {
1832 let ctx = self.exec_ctx.read().await;
1833 ctx.backend.exists(&script_path).await
1834 };
1835
1836 if !exists {
1837 continue;
1838 }
1839
1840 let content = {
1842 let ctx = self.exec_ctx.read().await;
1843 match ctx.backend.read(&script_path, None).await {
1844 Ok(bytes) => match String::from_utf8(bytes) {
1845 Ok(s) => s,
1846 Err(e) => {
1847 return Ok(Some(ExecResult::failure(
1848 1,
1849 format!("{}: invalid UTF-8: {}", script_path.display(), e),
1850 )));
1851 }
1852 },
1853 Err(e) => {
1854 return Ok(Some(ExecResult::failure(
1855 1,
1856 format!("{}: {}", script_path.display(), e),
1857 )));
1858 }
1859 }
1860 };
1861
1862 let program = match crate::parser::parse(&content) {
1864 Ok(p) => p,
1865 Err(errors) => {
1866 let msg = errors
1867 .iter()
1868 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
1869 .collect::<Vec<_>>()
1870 .join("\n");
1871 return Ok(Some(ExecResult::failure(1, msg)));
1872 }
1873 };
1874
1875 let tool_args = self.build_args_async(args, None).await?;
1877
1878 let mut isolated_scope = Scope::new();
1880
1881 let positional_args: Vec<String> = tool_args.positional
1883 .iter()
1884 .map(value_to_string)
1885 .collect();
1886 isolated_scope.set_positional(name, positional_args);
1887
1888 let original_scope = {
1890 let mut scope = self.scope.write().await;
1891 std::mem::replace(&mut *scope, isolated_scope)
1892 };
1893
1894 let mut result = ExecResult::success("");
1896 for stmt in program.statements {
1897 if matches!(stmt, crate::ast::Stmt::Empty) {
1898 continue;
1899 }
1900
1901 match self.execute_stmt_flow(&stmt).await {
1902 Ok(flow) => {
1903 match flow {
1904 ControlFlow::Normal(r) => result = r,
1905 ControlFlow::Return { value } => {
1906 result = value;
1907 break;
1908 }
1909 ControlFlow::Exit { code } => {
1910 let mut scope = self.scope.write().await;
1912 *scope = original_scope;
1913 return Ok(Some(ExecResult::failure(code, "exit")));
1914 }
1915 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1916 result = r;
1917 }
1918 }
1919 }
1920 Err(e) => {
1921 let mut scope = self.scope.write().await;
1923 *scope = original_scope;
1924 return Err(e.context(format!("script: {}", script_path.display())));
1925 }
1926 }
1927 }
1928
1929 {
1931 let mut scope = self.scope.write().await;
1932 *scope = original_scope;
1933 }
1934
1935 return Ok(Some(result));
1936 }
1937
1938 Ok(None)
1940 }
1941
1942 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1956 if name.contains('/') {
1958 return Ok(None);
1959 }
1960
1961 let path_var = {
1963 let scope = self.scope.read().await;
1964 scope
1965 .get("PATH")
1966 .map(value_to_string)
1967 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
1968 };
1969
1970 let executable = match resolve_in_path(name, &path_var) {
1972 Some(path) => path,
1973 None => return Ok(None), };
1975
1976 let real_cwd = {
1978 let ctx = self.exec_ctx.read().await;
1979 match ctx.backend.resolve_real_path(&ctx.cwd) {
1980 Some(p) => p,
1981 None => {
1982 return Ok(Some(ExecResult::failure(
1983 1,
1984 format!(
1985 "{}: cannot run external command from virtual directory '{}'",
1986 name,
1987 ctx.cwd.display()
1988 ),
1989 )));
1990 }
1991 }
1992 };
1993
1994 let argv = self.build_args_flat(args).await?;
1996
1997 let stdin_data = {
1999 let mut ctx = self.exec_ctx.write().await;
2000 ctx.take_stdin()
2001 };
2002
2003 use tokio::process::Command;
2005
2006 let mut cmd = Command::new(&executable);
2007 cmd.args(&argv);
2008 cmd.current_dir(&real_cwd);
2009
2010 cmd.stdin(if stdin_data.is_some() {
2012 std::process::Stdio::piped()
2013 } else {
2014 std::process::Stdio::null()
2015 });
2016
2017 let inherit_output = self.interactive && stdin_data.is_none();
2020
2021 if inherit_output {
2022 cmd.stdout(std::process::Stdio::inherit());
2023 cmd.stderr(std::process::Stdio::inherit());
2024 } else {
2025 cmd.stdout(std::process::Stdio::piped());
2026 cmd.stderr(std::process::Stdio::piped());
2027 }
2028
2029 let mut child = match cmd.spawn() {
2031 Ok(child) => child,
2032 Err(e) => {
2033 return Ok(Some(ExecResult::failure(
2034 127,
2035 format!("{}: {}", name, e),
2036 )));
2037 }
2038 };
2039
2040 if let Some(data) = stdin_data
2042 && let Some(mut stdin) = child.stdin.take()
2043 {
2044 use tokio::io::AsyncWriteExt;
2045 if let Err(e) = stdin.write_all(data.as_bytes()).await {
2046 return Ok(Some(ExecResult::failure(
2047 1,
2048 format!("{}: failed to write stdin: {}", name, e),
2049 )));
2050 }
2051 }
2053
2054 if inherit_output {
2055 let status = match child.wait().await {
2057 Ok(s) => s,
2058 Err(e) => {
2059 return Ok(Some(ExecResult::failure(
2060 1,
2061 format!("{}: failed to wait: {}", name, e),
2062 )));
2063 }
2064 };
2065
2066 let code = status.code().unwrap_or_else(|| {
2067 #[cfg(unix)]
2068 {
2069 use std::os::unix::process::ExitStatusExt;
2070 128 + status.signal().unwrap_or(0)
2071 }
2072 #[cfg(not(unix))]
2073 {
2074 -1
2075 }
2076 }) as i64;
2077
2078 Ok(Some(ExecResult::from_output(code, String::new(), String::new())))
2080 } else {
2081 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2083 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2084
2085 let stdout_pipe = child.stdout.take();
2086 let stderr_pipe = child.stderr.take();
2087
2088 let stdout_clone = stdout_stream.clone();
2089 let stderr_clone = stderr_stream.clone();
2090
2091 let stdout_task = stdout_pipe.map(|pipe| {
2092 tokio::spawn(async move {
2093 drain_to_stream(pipe, stdout_clone).await;
2094 })
2095 });
2096
2097 let stderr_task = stderr_pipe.map(|pipe| {
2098 tokio::spawn(async move {
2099 drain_to_stream(pipe, stderr_clone).await;
2100 })
2101 });
2102
2103 let status = match child.wait().await {
2104 Ok(s) => s,
2105 Err(e) => {
2106 return Ok(Some(ExecResult::failure(
2107 1,
2108 format!("{}: failed to wait: {}", name, e),
2109 )));
2110 }
2111 };
2112
2113 if let Some(task) = stdout_task {
2114 let _ = task.await;
2116 }
2117 if let Some(task) = stderr_task {
2118 let _ = task.await;
2119 }
2120
2121 let code = status.code().unwrap_or_else(|| {
2122 #[cfg(unix)]
2123 {
2124 use std::os::unix::process::ExitStatusExt;
2125 128 + status.signal().unwrap_or(0)
2126 }
2127 #[cfg(not(unix))]
2128 {
2129 -1
2130 }
2131 }) as i64;
2132
2133 let stdout = stdout_stream.read_string().await;
2134 let stderr = stderr_stream.read_string().await;
2135
2136 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
2137 }
2138 }
2139
2140 pub async fn get_var(&self, name: &str) -> Option<Value> {
2144 let scope = self.scope.read().await;
2145 scope.get(name).cloned()
2146 }
2147
2148 #[cfg(test)]
2150 pub async fn error_exit_enabled(&self) -> bool {
2151 let scope = self.scope.read().await;
2152 scope.error_exit_enabled()
2153 }
2154
2155 pub async fn set_var(&self, name: &str, value: Value) {
2157 let mut scope = self.scope.write().await;
2158 scope.set(name.to_string(), value);
2159 }
2160
2161 pub async fn list_vars(&self) -> Vec<(String, Value)> {
2163 let scope = self.scope.read().await;
2164 scope.all()
2165 }
2166
2167 pub async fn cwd(&self) -> PathBuf {
2171 self.exec_ctx.read().await.cwd.clone()
2172 }
2173
2174 pub async fn set_cwd(&self, path: PathBuf) {
2176 let mut ctx = self.exec_ctx.write().await;
2177 ctx.set_cwd(path);
2178 }
2179
2180 pub async fn last_result(&self) -> ExecResult {
2184 let scope = self.scope.read().await;
2185 scope.last_result().clone()
2186 }
2187
2188 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
2192 self.tools.schemas()
2193 }
2194
2195 pub fn jobs(&self) -> Arc<JobManager> {
2199 self.jobs.clone()
2200 }
2201
2202 pub fn vfs(&self) -> Arc<VfsRouter> {
2206 self.vfs.clone()
2207 }
2208
2209 pub async fn reset(&self) -> Result<()> {
2216 {
2217 let mut scope = self.scope.write().await;
2218 *scope = Scope::new();
2219 }
2220 {
2221 let mut ctx = self.exec_ctx.write().await;
2222 ctx.cwd = PathBuf::from("/");
2223 }
2224 Ok(())
2225 }
2226
2227 pub async fn shutdown(self) -> Result<()> {
2229 self.jobs.wait_all().await;
2231 Ok(())
2232 }
2233
2234 async fn dispatch_command(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2245 {
2247 let mut scope = self.scope.write().await;
2248 *scope = ctx.scope.clone();
2249 }
2250 {
2251 let mut ec = self.exec_ctx.write().await;
2252 ec.cwd = ctx.cwd.clone();
2253 ec.prev_cwd = ctx.prev_cwd.clone();
2254 ec.stdin = ctx.stdin.take();
2255 ec.stdin_data = ctx.stdin_data.take();
2256 }
2257
2258 let result = self.execute_command(&cmd.name, &cmd.args).await?;
2260
2261 {
2263 let scope = self.scope.read().await;
2264 ctx.scope = scope.clone();
2265 }
2266 {
2267 let ec = self.exec_ctx.read().await;
2268 ctx.cwd = ec.cwd.clone();
2269 ctx.prev_cwd = ec.prev_cwd.clone();
2270 }
2271
2272 Ok(result)
2273 }
2274}
2275
2276#[async_trait]
2277impl CommandDispatcher for Kernel {
2278 async fn dispatch(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2284 self.dispatch_command(cmd, ctx).await
2285 }
2286}
2287
2288fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2294 if !accumulated.out.is_empty() && !new.out.is_empty() {
2295 accumulated.out.push('\n');
2296 }
2297 accumulated.out.push_str(&new.out);
2298 if !accumulated.err.is_empty() && !new.err.is_empty() {
2299 accumulated.err.push('\n');
2300 }
2301 accumulated.err.push_str(&new.err);
2302 accumulated.code = new.code;
2303 accumulated.data = new.data.clone();
2304}
2305
2306fn is_truthy(value: &Value) -> bool {
2308 match value {
2309 Value::Null => false,
2310 Value::Bool(b) => *b,
2311 Value::Int(i) => *i != 0,
2312 Value::Float(f) => *f != 0.0,
2313 Value::String(s) => !s.is_empty(),
2314 Value::Json(json) => match json {
2315 serde_json::Value::Null => false,
2316 serde_json::Value::Array(arr) => !arr.is_empty(),
2317 serde_json::Value::Object(obj) => !obj.is_empty(),
2318 serde_json::Value::Bool(b) => *b,
2319 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
2320 serde_json::Value::String(s) => !s.is_empty(),
2321 },
2322 Value::Blob(_) => true, }
2324}
2325
2326fn apply_tilde_expansion(value: Value) -> Value {
2330 match value {
2331 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
2332 _ => value,
2333 }
2334}
2335
2336#[cfg(test)]
2337mod tests {
2338 use super::*;
2339
2340 #[tokio::test]
2341 async fn test_kernel_transient() {
2342 let kernel = Kernel::transient().expect("failed to create kernel");
2343 assert_eq!(kernel.name(), "transient");
2344 }
2345
2346 #[tokio::test]
2347 async fn test_kernel_execute_echo() {
2348 let kernel = Kernel::transient().expect("failed to create kernel");
2349 let result = kernel.execute("echo hello").await.expect("execution failed");
2350 assert!(result.ok());
2351 assert_eq!(result.out.trim(), "hello");
2352 }
2353
2354 #[tokio::test]
2355 async fn test_multiple_statements_accumulate_output() {
2356 let kernel = Kernel::transient().expect("failed to create kernel");
2357 let result = kernel
2358 .execute("echo one\necho two\necho three")
2359 .await
2360 .expect("execution failed");
2361 assert!(result.ok());
2362 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
2364 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
2365 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
2366 }
2367
2368 #[tokio::test]
2369 async fn test_and_chain_accumulates_output() {
2370 let kernel = Kernel::transient().expect("failed to create kernel");
2371 let result = kernel
2372 .execute("echo first && echo second")
2373 .await
2374 .expect("execution failed");
2375 assert!(result.ok());
2376 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
2377 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
2378 }
2379
2380 #[tokio::test]
2381 async fn test_for_loop_accumulates_output() {
2382 let kernel = Kernel::transient().expect("failed to create kernel");
2383 let result = kernel
2384 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
2385 .await
2386 .expect("execution failed");
2387 assert!(result.ok());
2388 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
2389 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
2390 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
2391 }
2392
2393 #[tokio::test]
2394 async fn test_while_loop_accumulates_output() {
2395 let kernel = Kernel::transient().expect("failed to create kernel");
2396 let result = kernel
2397 .execute(r#"
2398 N=3
2399 while [[ ${N} -gt 0 ]]; do
2400 echo "N=${N}"
2401 N=$((N - 1))
2402 done
2403 "#)
2404 .await
2405 .expect("execution failed");
2406 assert!(result.ok());
2407 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
2408 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
2409 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
2410 }
2411
2412 #[tokio::test]
2413 async fn test_kernel_set_var() {
2414 let kernel = Kernel::transient().expect("failed to create kernel");
2415
2416 kernel.execute("X=42").await.expect("set failed");
2417
2418 let value = kernel.get_var("X").await;
2419 assert_eq!(value, Some(Value::Int(42)));
2420 }
2421
2422 #[tokio::test]
2423 async fn test_kernel_var_expansion() {
2424 let kernel = Kernel::transient().expect("failed to create kernel");
2425
2426 kernel.execute("NAME=\"world\"").await.expect("set failed");
2427 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
2428
2429 assert!(result.ok());
2430 assert_eq!(result.out.trim(), "hello world");
2431 }
2432
2433 #[tokio::test]
2434 async fn test_kernel_last_result() {
2435 let kernel = Kernel::transient().expect("failed to create kernel");
2436
2437 kernel.execute("echo test").await.expect("echo failed");
2438
2439 let last = kernel.last_result().await;
2440 assert!(last.ok());
2441 assert_eq!(last.out.trim(), "test");
2442 }
2443
2444 #[tokio::test]
2445 async fn test_kernel_tool_not_found() {
2446 let kernel = Kernel::transient().expect("failed to create kernel");
2447
2448 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
2449 assert!(!result.ok());
2450 assert_eq!(result.code, 127);
2451 assert!(result.err.contains("command not found"));
2452 }
2453
2454 #[tokio::test]
2455 async fn test_external_command_true() {
2456 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2458
2459 let result = kernel.execute("true").await.expect("execution failed");
2461 assert!(result.ok(), "true should succeed: {:?}", result);
2463 }
2464
2465 #[tokio::test]
2466 async fn test_external_command_basic() {
2467 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2469
2470 let path_var = std::env::var("PATH").unwrap_or_default();
2475 eprintln!("System PATH: {}", path_var);
2476
2477 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
2479
2480 let result = kernel.execute("uname").await.expect("execution failed");
2483 eprintln!("uname result: {:?}", result);
2484 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
2486 }
2487
2488 #[tokio::test]
2489 async fn test_kernel_reset() {
2490 let kernel = Kernel::transient().expect("failed to create kernel");
2491
2492 kernel.execute("X=1").await.expect("set failed");
2493 assert!(kernel.get_var("X").await.is_some());
2494
2495 kernel.reset().await.expect("reset failed");
2496 assert!(kernel.get_var("X").await.is_none());
2497 }
2498
2499 #[tokio::test]
2500 async fn test_kernel_cwd() {
2501 let kernel = Kernel::transient().expect("failed to create kernel");
2502
2503 let cwd = kernel.cwd().await;
2505 let home = std::env::var("HOME")
2506 .map(PathBuf::from)
2507 .unwrap_or_else(|_| PathBuf::from("/"));
2508 assert_eq!(cwd, home);
2509
2510 kernel.set_cwd(PathBuf::from("/tmp")).await;
2511 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
2512 }
2513
2514 #[tokio::test]
2515 async fn test_kernel_list_vars() {
2516 let kernel = Kernel::transient().expect("failed to create kernel");
2517
2518 kernel.execute("A=1").await.ok();
2519 kernel.execute("B=2").await.ok();
2520
2521 let vars = kernel.list_vars().await;
2522 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
2523 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
2524 }
2525
2526 #[tokio::test]
2527 async fn test_is_truthy() {
2528 assert!(!is_truthy(&Value::Null));
2529 assert!(!is_truthy(&Value::Bool(false)));
2530 assert!(is_truthy(&Value::Bool(true)));
2531 assert!(!is_truthy(&Value::Int(0)));
2532 assert!(is_truthy(&Value::Int(1)));
2533 assert!(!is_truthy(&Value::String("".into())));
2534 assert!(is_truthy(&Value::String("x".into())));
2535 }
2536
2537 #[tokio::test]
2538 async fn test_jq_in_pipeline() {
2539 let kernel = Kernel::transient().expect("failed to create kernel");
2540 let result = kernel
2542 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
2543 .await
2544 .expect("execution failed");
2545 assert!(result.ok(), "jq pipeline failed: {}", result.err);
2546 assert_eq!(result.out.trim(), "Alice");
2547 }
2548
2549 #[tokio::test]
2550 async fn test_user_defined_tool() {
2551 let kernel = Kernel::transient().expect("failed to create kernel");
2552
2553 kernel
2555 .execute(r#"greet() { echo "Hello, $1!" }"#)
2556 .await
2557 .expect("function definition failed");
2558
2559 let result = kernel
2561 .execute(r#"greet "World""#)
2562 .await
2563 .expect("function call failed");
2564
2565 assert!(result.ok(), "greet failed: {}", result.err);
2566 assert_eq!(result.out.trim(), "Hello, World!");
2567 }
2568
2569 #[tokio::test]
2570 async fn test_user_tool_positional_args() {
2571 let kernel = Kernel::transient().expect("failed to create kernel");
2572
2573 kernel
2575 .execute(r#"greet() { echo "Hi $1" }"#)
2576 .await
2577 .expect("function definition failed");
2578
2579 let result = kernel
2581 .execute(r#"greet "Amy""#)
2582 .await
2583 .expect("function call failed");
2584
2585 assert!(result.ok(), "greet failed: {}", result.err);
2586 assert_eq!(result.out.trim(), "Hi Amy");
2587 }
2588
2589 #[tokio::test]
2590 async fn test_function_shared_scope() {
2591 let kernel = Kernel::transient().expect("failed to create kernel");
2592
2593 kernel
2595 .execute(r#"SECRET="hidden""#)
2596 .await
2597 .expect("set failed");
2598
2599 kernel
2601 .execute(r#"access_parent() {
2602 echo "${SECRET}"
2603 SECRET="modified"
2604 }"#)
2605 .await
2606 .expect("function definition failed");
2607
2608 let result = kernel.execute("access_parent").await.expect("function call failed");
2610
2611 assert!(
2613 result.out.contains("hidden"),
2614 "Function should access parent scope, got: {}",
2615 result.out
2616 );
2617
2618 let secret = kernel.get_var("SECRET").await;
2620 assert_eq!(
2621 secret,
2622 Some(Value::String("modified".into())),
2623 "Function should modify parent scope"
2624 );
2625 }
2626
2627 #[tokio::test]
2628 async fn test_exec_builtin() {
2629 let kernel = Kernel::transient().expect("failed to create kernel");
2630 let result = kernel
2632 .execute(r#"exec command="/bin/echo" argv="hello world""#)
2633 .await
2634 .expect("exec failed");
2635
2636 assert!(result.ok(), "exec failed: {}", result.err);
2637 assert_eq!(result.out.trim(), "hello world");
2638 }
2639
2640 #[tokio::test]
2641 async fn test_while_false_never_runs() {
2642 let kernel = Kernel::transient().expect("failed to create kernel");
2643
2644 let result = kernel
2646 .execute(r#"
2647 while false; do
2648 echo "should not run"
2649 done
2650 "#)
2651 .await
2652 .expect("while false failed");
2653
2654 assert!(result.ok());
2655 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
2656 }
2657
2658 #[tokio::test]
2659 async fn test_while_string_comparison() {
2660 let kernel = Kernel::transient().expect("failed to create kernel");
2661
2662 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
2664
2665 let result = kernel
2668 .execute(r#"
2669 while [[ ${FLAG} == "go" ]]; do
2670 FLAG="stop"
2671 echo "running"
2672 done
2673 "#)
2674 .await
2675 .expect("while with string cmp failed");
2676
2677 assert!(result.ok());
2678 assert!(result.out.contains("running"), "should have run once: {}", result.out);
2679
2680 let flag = kernel.get_var("FLAG").await;
2682 assert_eq!(flag, Some(Value::String("stop".into())));
2683 }
2684
2685 #[tokio::test]
2686 async fn test_while_numeric_comparison() {
2687 let kernel = Kernel::transient().expect("failed to create kernel");
2688
2689 kernel.execute("N=5").await.expect("set failed");
2691
2692 let result = kernel
2694 .execute(r#"
2695 while [[ ${N} -gt 3 ]]; do
2696 N=3
2697 echo "N was greater"
2698 done
2699 "#)
2700 .await
2701 .expect("while with > failed");
2702
2703 assert!(result.ok());
2704 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
2705 }
2706
2707 #[tokio::test]
2708 async fn test_break_in_while_loop() {
2709 let kernel = Kernel::transient().expect("failed to create kernel");
2710
2711 let result = kernel
2712 .execute(r#"
2713 I=0
2714 while true; do
2715 I=1
2716 echo "before break"
2717 break
2718 echo "after break"
2719 done
2720 "#)
2721 .await
2722 .expect("while with break failed");
2723
2724 assert!(result.ok());
2725 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
2726 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
2727
2728 let i = kernel.get_var("I").await;
2730 assert_eq!(i, Some(Value::Int(1)));
2731 }
2732
2733 #[tokio::test]
2734 async fn test_continue_in_while_loop() {
2735 let kernel = Kernel::transient().expect("failed to create kernel");
2736
2737 let result = kernel
2742 .execute(r#"
2743 STATE="start"
2744 AFTER_CONTINUE="no"
2745 while [[ ${STATE} != "done" ]]; do
2746 if [[ ${STATE} == "start" ]]; then
2747 STATE="middle"
2748 continue
2749 AFTER_CONTINUE="yes"
2750 fi
2751 if [[ ${STATE} == "middle" ]]; then
2752 STATE="done"
2753 fi
2754 done
2755 "#)
2756 .await
2757 .expect("while with continue failed");
2758
2759 assert!(result.ok());
2760
2761 let state = kernel.get_var("STATE").await;
2763 assert_eq!(state, Some(Value::String("done".into())));
2764
2765 let after = kernel.get_var("AFTER_CONTINUE").await;
2767 assert_eq!(after, Some(Value::String("no".into())));
2768 }
2769
2770 #[tokio::test]
2771 async fn test_break_with_level() {
2772 let kernel = Kernel::transient().expect("failed to create kernel");
2773
2774 let result = kernel
2779 .execute(r#"
2780 OUTER=0
2781 while true; do
2782 OUTER=1
2783 for X in "1 2"; do
2784 break 2
2785 done
2786 OUTER=2
2787 done
2788 "#)
2789 .await
2790 .expect("nested break failed");
2791
2792 assert!(result.ok());
2793
2794 let outer = kernel.get_var("OUTER").await;
2796 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
2797 }
2798
2799 #[tokio::test]
2800 async fn test_return_from_tool() {
2801 let kernel = Kernel::transient().expect("failed to create kernel");
2802
2803 kernel
2805 .execute(r#"early_return() {
2806 if [[ $1 == 1 ]]; then
2807 return 42
2808 fi
2809 echo "not returned"
2810 }"#)
2811 .await
2812 .expect("function definition failed");
2813
2814 let result = kernel
2817 .execute("early_return 1")
2818 .await
2819 .expect("function call failed");
2820
2821 assert_eq!(result.code, 42);
2823 assert!(result.out.is_empty());
2825 }
2826
2827 #[tokio::test]
2828 async fn test_return_without_value() {
2829 let kernel = Kernel::transient().expect("failed to create kernel");
2830
2831 kernel
2833 .execute(r#"early_exit() {
2834 if [[ $1 == "stop" ]]; then
2835 return
2836 fi
2837 echo "continued"
2838 }"#)
2839 .await
2840 .expect("function definition failed");
2841
2842 let result = kernel
2844 .execute(r#"early_exit "stop""#)
2845 .await
2846 .expect("function call failed");
2847
2848 assert!(result.ok());
2849 assert!(result.out.is_empty() || result.out.trim().is_empty());
2850 }
2851
2852 #[tokio::test]
2853 async fn test_exit_stops_execution() {
2854 let kernel = Kernel::transient().expect("failed to create kernel");
2855
2856 kernel
2858 .execute(r#"
2859 BEFORE="yes"
2860 exit 0
2861 AFTER="yes"
2862 "#)
2863 .await
2864 .expect("execution failed");
2865
2866 let before = kernel.get_var("BEFORE").await;
2868 assert_eq!(before, Some(Value::String("yes".into())));
2869
2870 let after = kernel.get_var("AFTER").await;
2871 assert!(after.is_none(), "AFTER should not be set after exit");
2872 }
2873
2874 #[tokio::test]
2875 async fn test_exit_with_code() {
2876 let kernel = Kernel::transient().expect("failed to create kernel");
2877
2878 let result = kernel
2880 .execute("exit 42")
2881 .await
2882 .expect("exit failed");
2883
2884 assert_eq!(result.out, "42");
2886 }
2887
2888 #[tokio::test]
2889 async fn test_set_e_stops_on_failure() {
2890 let kernel = Kernel::transient().expect("failed to create kernel");
2891
2892 kernel.execute("set -e").await.expect("set -e failed");
2894
2895 kernel
2897 .execute(r#"
2898 STEP1="done"
2899 false
2900 STEP2="done"
2901 "#)
2902 .await
2903 .expect("execution failed");
2904
2905 let step1 = kernel.get_var("STEP1").await;
2907 assert_eq!(step1, Some(Value::String("done".into())));
2908
2909 let step2 = kernel.get_var("STEP2").await;
2910 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
2911 }
2912
2913 #[tokio::test]
2914 async fn test_set_plus_e_disables_error_exit() {
2915 let kernel = Kernel::transient().expect("failed to create kernel");
2916
2917 kernel.execute("set -e").await.expect("set -e failed");
2919 kernel.execute("set +e").await.expect("set +e failed");
2920
2921 kernel
2923 .execute(r#"
2924 STEP1="done"
2925 false
2926 STEP2="done"
2927 "#)
2928 .await
2929 .expect("execution failed");
2930
2931 let step1 = kernel.get_var("STEP1").await;
2933 assert_eq!(step1, Some(Value::String("done".into())));
2934
2935 let step2 = kernel.get_var("STEP2").await;
2936 assert_eq!(step2, Some(Value::String("done".into())));
2937 }
2938
2939 #[tokio::test]
2940 async fn test_set_ignores_unknown_options() {
2941 let kernel = Kernel::transient().expect("failed to create kernel");
2942
2943 let result = kernel
2945 .execute("set -e -u -o pipefail")
2946 .await
2947 .expect("set with unknown options failed");
2948
2949 assert!(result.ok(), "set should succeed with unknown options");
2950
2951 kernel
2953 .execute(r#"
2954 BEFORE="yes"
2955 false
2956 AFTER="yes"
2957 "#)
2958 .await
2959 .ok();
2960
2961 let after = kernel.get_var("AFTER").await;
2962 assert!(after.is_none(), "-e should be enabled despite unknown options");
2963 }
2964
2965 #[tokio::test]
2966 async fn test_set_no_args_shows_settings() {
2967 let kernel = Kernel::transient().expect("failed to create kernel");
2968
2969 kernel.execute("set -e").await.expect("set -e failed");
2971
2972 let result = kernel.execute("set").await.expect("set failed");
2974
2975 assert!(result.ok());
2976 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
2977 }
2978
2979 #[tokio::test]
2980 async fn test_set_e_in_pipeline() {
2981 let kernel = Kernel::transient().expect("failed to create kernel");
2982
2983 kernel.execute("set -e").await.expect("set -e failed");
2984
2985 kernel
2987 .execute(r#"
2988 BEFORE="yes"
2989 false | cat
2990 AFTER="yes"
2991 "#)
2992 .await
2993 .ok();
2994
2995 let before = kernel.get_var("BEFORE").await;
2996 assert_eq!(before, Some(Value::String("yes".into())));
2997
2998 }
3003
3004 #[tokio::test]
3005 async fn test_set_e_with_and_chain() {
3006 let kernel = Kernel::transient().expect("failed to create kernel");
3007
3008 kernel.execute("set -e").await.expect("set -e failed");
3009
3010 kernel
3013 .execute(r#"
3014 RESULT="initial"
3015 false && RESULT="chained"
3016 RESULT="continued"
3017 "#)
3018 .await
3019 .ok();
3020
3021 let result = kernel.get_var("RESULT").await;
3024 assert!(result.is_some(), "RESULT should be set");
3027 }
3028
3029 #[tokio::test]
3034 async fn test_source_sets_variables() {
3035 let kernel = Kernel::transient().expect("failed to create kernel");
3036
3037 kernel
3039 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
3040 .await
3041 .expect("write failed");
3042
3043 let result = kernel
3045 .execute(r#"source "/test.kai""#)
3046 .await
3047 .expect("source failed");
3048
3049 assert!(result.ok(), "source should succeed");
3050
3051 let foo = kernel.get_var("FOO").await;
3053 assert_eq!(foo, Some(Value::String("bar".into())));
3054 }
3055
3056 #[tokio::test]
3057 async fn test_source_with_dot_alias() {
3058 let kernel = Kernel::transient().expect("failed to create kernel");
3059
3060 kernel
3062 .execute(r#"write "/vars.kai" 'X=42'"#)
3063 .await
3064 .expect("write failed");
3065
3066 let result = kernel
3068 .execute(r#". "/vars.kai""#)
3069 .await
3070 .expect(". failed");
3071
3072 assert!(result.ok(), ". should succeed");
3073
3074 let x = kernel.get_var("X").await;
3076 assert_eq!(x, Some(Value::Int(42)));
3077 }
3078
3079 #[tokio::test]
3080 async fn test_source_not_found() {
3081 let kernel = Kernel::transient().expect("failed to create kernel");
3082
3083 let result = kernel
3085 .execute(r#"source "/nonexistent.kai""#)
3086 .await
3087 .expect("source should not fail with error");
3088
3089 assert!(!result.ok(), "source of non-existent file should fail");
3090 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
3091 }
3092
3093 #[tokio::test]
3094 async fn test_source_missing_filename() {
3095 let kernel = Kernel::transient().expect("failed to create kernel");
3096
3097 let result = kernel
3099 .execute("source")
3100 .await
3101 .expect("source should not fail with error");
3102
3103 assert!(!result.ok(), "source without filename should fail");
3104 assert!(result.err.contains("missing filename"), "error should mention missing filename");
3105 }
3106
3107 #[tokio::test]
3108 async fn test_source_executes_multiple_statements() {
3109 let kernel = Kernel::transient().expect("failed to create kernel");
3110
3111 kernel
3113 .execute(r#"write "/multi.kai" 'A=1
3114B=2
3115C=3'"#)
3116 .await
3117 .expect("write failed");
3118
3119 kernel
3121 .execute(r#"source "/multi.kai""#)
3122 .await
3123 .expect("source failed");
3124
3125 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
3127 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
3128 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
3129 }
3130
3131 #[tokio::test]
3132 async fn test_source_can_define_functions() {
3133 let kernel = Kernel::transient().expect("failed to create kernel");
3134
3135 kernel
3137 .execute(r#"write "/functions.kai" 'greet() {
3138 echo "Hello, $1!"
3139}'"#)
3140 .await
3141 .expect("write failed");
3142
3143 kernel
3145 .execute(r#"source "/functions.kai""#)
3146 .await
3147 .expect("source failed");
3148
3149 let result = kernel
3151 .execute(r#"greet "World""#)
3152 .await
3153 .expect("greet failed");
3154
3155 assert!(result.ok());
3156 assert!(result.out.contains("Hello, World!"));
3157 }
3158
3159 #[tokio::test]
3160 async fn test_source_inherits_error_exit() {
3161 let kernel = Kernel::transient().expect("failed to create kernel");
3162
3163 kernel.execute("set -e").await.expect("set -e failed");
3165
3166 kernel
3168 .execute(r#"write "/fail.kai" 'BEFORE="yes"
3169false
3170AFTER="yes"'"#)
3171 .await
3172 .expect("write failed");
3173
3174 kernel
3176 .execute(r#"source "/fail.kai""#)
3177 .await
3178 .ok();
3179
3180 let before = kernel.get_var("BEFORE").await;
3182 assert_eq!(before, Some(Value::String("yes".into())));
3183
3184 }
3187
3188 #[tokio::test]
3193 async fn test_case_simple_match() {
3194 let kernel = Kernel::transient().expect("failed to create kernel");
3195
3196 let result = kernel
3197 .execute(r#"
3198 case "hello" in
3199 hello) echo "matched hello" ;;
3200 world) echo "matched world" ;;
3201 esac
3202 "#)
3203 .await
3204 .expect("case failed");
3205
3206 assert!(result.ok());
3207 assert_eq!(result.out.trim(), "matched hello");
3208 }
3209
3210 #[tokio::test]
3211 async fn test_case_wildcard_match() {
3212 let kernel = Kernel::transient().expect("failed to create kernel");
3213
3214 let result = kernel
3215 .execute(r#"
3216 case "main.rs" in
3217 "*.py") echo "Python" ;;
3218 "*.rs") echo "Rust" ;;
3219 "*") echo "Unknown" ;;
3220 esac
3221 "#)
3222 .await
3223 .expect("case failed");
3224
3225 assert!(result.ok());
3226 assert_eq!(result.out.trim(), "Rust");
3227 }
3228
3229 #[tokio::test]
3230 async fn test_case_default_match() {
3231 let kernel = Kernel::transient().expect("failed to create kernel");
3232
3233 let result = kernel
3234 .execute(r#"
3235 case "unknown.xyz" in
3236 "*.py") echo "Python" ;;
3237 "*.rs") echo "Rust" ;;
3238 "*") echo "Default" ;;
3239 esac
3240 "#)
3241 .await
3242 .expect("case failed");
3243
3244 assert!(result.ok());
3245 assert_eq!(result.out.trim(), "Default");
3246 }
3247
3248 #[tokio::test]
3249 async fn test_case_no_match() {
3250 let kernel = Kernel::transient().expect("failed to create kernel");
3251
3252 let result = kernel
3254 .execute(r#"
3255 case "nope" in
3256 "yes") echo "yes" ;;
3257 "no") echo "no" ;;
3258 esac
3259 "#)
3260 .await
3261 .expect("case failed");
3262
3263 assert!(result.ok());
3264 assert!(result.out.is_empty(), "no match should produce empty output");
3265 }
3266
3267 #[tokio::test]
3268 async fn test_case_with_variable() {
3269 let kernel = Kernel::transient().expect("failed to create kernel");
3270
3271 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
3272
3273 let result = kernel
3274 .execute(r#"
3275 case ${LANG} in
3276 python) echo "snake" ;;
3277 rust) echo "crab" ;;
3278 go) echo "gopher" ;;
3279 esac
3280 "#)
3281 .await
3282 .expect("case failed");
3283
3284 assert!(result.ok());
3285 assert_eq!(result.out.trim(), "crab");
3286 }
3287
3288 #[tokio::test]
3289 async fn test_case_multiple_patterns() {
3290 let kernel = Kernel::transient().expect("failed to create kernel");
3291
3292 let result = kernel
3293 .execute(r#"
3294 case "yes" in
3295 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
3296 "n"|"no"|"N"|"NO") echo "negative" ;;
3297 esac
3298 "#)
3299 .await
3300 .expect("case failed");
3301
3302 assert!(result.ok());
3303 assert_eq!(result.out.trim(), "affirmative");
3304 }
3305
3306 #[tokio::test]
3307 async fn test_case_glob_question_mark() {
3308 let kernel = Kernel::transient().expect("failed to create kernel");
3309
3310 let result = kernel
3311 .execute(r#"
3312 case "test1" in
3313 "test?") echo "matched test?" ;;
3314 "*") echo "default" ;;
3315 esac
3316 "#)
3317 .await
3318 .expect("case failed");
3319
3320 assert!(result.ok());
3321 assert_eq!(result.out.trim(), "matched test?");
3322 }
3323
3324 #[tokio::test]
3325 async fn test_case_char_class() {
3326 let kernel = Kernel::transient().expect("failed to create kernel");
3327
3328 let result = kernel
3329 .execute(r#"
3330 case "Yes" in
3331 "[Yy]*") echo "yes-like" ;;
3332 "[Nn]*") echo "no-like" ;;
3333 esac
3334 "#)
3335 .await
3336 .expect("case failed");
3337
3338 assert!(result.ok());
3339 assert_eq!(result.out.trim(), "yes-like");
3340 }
3341
3342 #[tokio::test]
3347 async fn test_cat_from_pipeline() {
3348 let kernel = Kernel::transient().expect("failed to create kernel");
3349
3350 let result = kernel
3351 .execute(r#"echo "piped text" | cat"#)
3352 .await
3353 .expect("cat pipeline failed");
3354
3355 assert!(result.ok(), "cat failed: {}", result.err);
3356 assert_eq!(result.out.trim(), "piped text");
3357 }
3358
3359 #[tokio::test]
3360 async fn test_cat_from_pipeline_multiline() {
3361 let kernel = Kernel::transient().expect("failed to create kernel");
3362
3363 let result = kernel
3364 .execute(r#"echo "line1\nline2" | cat -n"#)
3365 .await
3366 .expect("cat pipeline failed");
3367
3368 assert!(result.ok(), "cat failed: {}", result.err);
3369 assert!(result.out.contains("1\t"), "output: {}", result.out);
3370 }
3371
3372 #[tokio::test]
3377 async fn test_heredoc_basic() {
3378 let kernel = Kernel::transient().expect("failed to create kernel");
3379
3380 let result = kernel
3381 .execute("cat <<EOF\nhello\nEOF")
3382 .await
3383 .expect("heredoc failed");
3384
3385 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3386 assert_eq!(result.out.trim(), "hello");
3387 }
3388
3389 #[tokio::test]
3390 async fn test_arithmetic_in_string() {
3391 let kernel = Kernel::transient().expect("failed to create kernel");
3392
3393 let result = kernel
3394 .execute(r#"echo "result: $((1 + 2))""#)
3395 .await
3396 .expect("arithmetic in string failed");
3397
3398 assert!(result.ok(), "echo failed: {}", result.err);
3399 assert_eq!(result.out.trim(), "result: 3");
3400 }
3401
3402 #[tokio::test]
3403 async fn test_heredoc_multiline() {
3404 let kernel = Kernel::transient().expect("failed to create kernel");
3405
3406 let result = kernel
3407 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
3408 .await
3409 .expect("heredoc failed");
3410
3411 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3412 assert!(result.out.contains("line1"), "output: {}", result.out);
3413 assert!(result.out.contains("line2"), "output: {}", result.out);
3414 assert!(result.out.contains("line3"), "output: {}", result.out);
3415 }
3416
3417 #[tokio::test]
3422 async fn test_read_from_pipeline() {
3423 let kernel = Kernel::transient().expect("failed to create kernel");
3424
3425 let result = kernel
3427 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
3428 .await
3429 .expect("read pipeline failed");
3430
3431 assert!(result.ok(), "read failed: {}", result.err);
3432 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
3433 }
3434
3435 #[tokio::test]
3436 async fn test_read_multiple_vars_from_pipeline() {
3437 let kernel = Kernel::transient().expect("failed to create kernel");
3438
3439 let result = kernel
3440 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
3441 .await
3442 .expect("read pipeline failed");
3443
3444 assert!(result.ok(), "read failed: {}", result.err);
3445 assert!(result.out.contains("John is 42"), "output: {}", result.out);
3446 }
3447
3448 #[tokio::test]
3453 async fn test_posix_function_with_positional_params() {
3454 let kernel = Kernel::transient().expect("failed to create kernel");
3455
3456 kernel
3458 .execute(r#"greet() { echo "Hello, $1!" }"#)
3459 .await
3460 .expect("function definition failed");
3461
3462 let result = kernel
3464 .execute(r#"greet "Amy""#)
3465 .await
3466 .expect("function call failed");
3467
3468 assert!(result.ok(), "greet failed: {}", result.err);
3469 assert_eq!(result.out.trim(), "Hello, Amy!");
3470 }
3471
3472 #[tokio::test]
3473 async fn test_posix_function_multiple_args() {
3474 let kernel = Kernel::transient().expect("failed to create kernel");
3475
3476 kernel
3478 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
3479 .await
3480 .expect("function definition failed");
3481
3482 let result = kernel
3484 .execute(r#"add_greeting "Hello" "World""#)
3485 .await
3486 .expect("function call failed");
3487
3488 assert!(result.ok(), "function failed: {}", result.err);
3489 assert_eq!(result.out.trim(), "Hello World!");
3490 }
3491
3492 #[tokio::test]
3493 async fn test_bash_function_with_positional_params() {
3494 let kernel = Kernel::transient().expect("failed to create kernel");
3495
3496 kernel
3498 .execute(r#"function greet { echo "Hi $1" }"#)
3499 .await
3500 .expect("function definition failed");
3501
3502 let result = kernel
3504 .execute(r#"greet "Bob""#)
3505 .await
3506 .expect("function call failed");
3507
3508 assert!(result.ok(), "greet failed: {}", result.err);
3509 assert_eq!(result.out.trim(), "Hi Bob");
3510 }
3511
3512 #[tokio::test]
3513 async fn test_shell_function_with_all_args() {
3514 let kernel = Kernel::transient().expect("failed to create kernel");
3515
3516 kernel
3518 .execute(r#"echo_all() { echo "args: $@" }"#)
3519 .await
3520 .expect("function definition failed");
3521
3522 let result = kernel
3524 .execute(r#"echo_all "a" "b" "c""#)
3525 .await
3526 .expect("function call failed");
3527
3528 assert!(result.ok(), "function failed: {}", result.err);
3529 assert_eq!(result.out.trim(), "args: a b c");
3530 }
3531
3532 #[tokio::test]
3533 async fn test_shell_function_with_arg_count() {
3534 let kernel = Kernel::transient().expect("failed to create kernel");
3535
3536 kernel
3538 .execute(r#"count_args() { echo "count: $#" }"#)
3539 .await
3540 .expect("function definition failed");
3541
3542 let result = kernel
3544 .execute(r#"count_args "x" "y" "z""#)
3545 .await
3546 .expect("function call failed");
3547
3548 assert!(result.ok(), "function failed: {}", result.err);
3549 assert_eq!(result.out.trim(), "count: 3");
3550 }
3551
3552 #[tokio::test]
3553 async fn test_shell_function_shared_scope() {
3554 let kernel = Kernel::transient().expect("failed to create kernel");
3555
3556 kernel
3558 .execute(r#"PARENT_VAR="visible""#)
3559 .await
3560 .expect("set failed");
3561
3562 kernel
3564 .execute(r#"modify_parent() {
3565 echo "saw: ${PARENT_VAR}"
3566 PARENT_VAR="changed by function"
3567 }"#)
3568 .await
3569 .expect("function definition failed");
3570
3571 let result = kernel.execute("modify_parent").await.expect("function failed");
3573
3574 assert!(
3575 result.out.contains("visible"),
3576 "Shell function should access parent scope, got: {}",
3577 result.out
3578 );
3579
3580 let var = kernel.get_var("PARENT_VAR").await;
3582 assert_eq!(
3583 var,
3584 Some(Value::String("changed by function".into())),
3585 "Shell function should modify parent scope"
3586 );
3587 }
3588
3589 #[tokio::test]
3594 async fn test_script_execution_from_path() {
3595 let kernel = Kernel::transient().expect("failed to create kernel");
3596
3597 kernel.execute(r#"mkdir "/bin""#).await.ok();
3599 kernel
3600 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
3601 .await
3602 .expect("write script failed");
3603
3604 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3606
3607 let result = kernel
3609 .execute("hello")
3610 .await
3611 .expect("script execution failed");
3612
3613 assert!(result.ok(), "script failed: {}", result.err);
3614 assert_eq!(result.out.trim(), "Hello from script!");
3615 }
3616
3617 #[tokio::test]
3618 async fn test_script_with_args() {
3619 let kernel = Kernel::transient().expect("failed to create kernel");
3620
3621 kernel.execute(r#"mkdir "/bin""#).await.ok();
3623 kernel
3624 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
3625 .await
3626 .expect("write script failed");
3627
3628 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3630
3631 let result = kernel
3633 .execute(r#"greet "World""#)
3634 .await
3635 .expect("script execution failed");
3636
3637 assert!(result.ok(), "script failed: {}", result.err);
3638 assert_eq!(result.out.trim(), "Hello, World!");
3639 }
3640
3641 #[tokio::test]
3642 async fn test_script_not_found() {
3643 let kernel = Kernel::transient().expect("failed to create kernel");
3644
3645 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
3647
3648 let result = kernel
3650 .execute("noscript")
3651 .await
3652 .expect("execution failed");
3653
3654 assert!(!result.ok(), "should fail with command not found");
3655 assert_eq!(result.code, 127);
3656 assert!(result.err.contains("command not found"));
3657 }
3658
3659 #[tokio::test]
3660 async fn test_script_path_search_order() {
3661 let kernel = Kernel::transient().expect("failed to create kernel");
3662
3663 kernel.execute(r#"mkdir "/first""#).await.ok();
3666 kernel.execute(r#"mkdir "/second""#).await.ok();
3667 kernel
3668 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
3669 .await
3670 .expect("write failed");
3671 kernel
3672 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
3673 .await
3674 .expect("write failed");
3675
3676 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
3678
3679 let result = kernel
3681 .execute("myscript")
3682 .await
3683 .expect("script execution failed");
3684
3685 assert!(result.ok(), "script failed: {}", result.err);
3686 assert_eq!(result.out.trim(), "from first");
3687 }
3688
3689 #[tokio::test]
3694 async fn test_last_exit_code_success() {
3695 let kernel = Kernel::transient().expect("failed to create kernel");
3696
3697 let result = kernel.execute("true; echo $?").await.expect("execution failed");
3699 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
3700 }
3701
3702 #[tokio::test]
3703 async fn test_last_exit_code_failure() {
3704 let kernel = Kernel::transient().expect("failed to create kernel");
3705
3706 let result = kernel.execute("false; echo $?").await.expect("execution failed");
3708 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
3709 }
3710
3711 #[tokio::test]
3712 async fn test_current_pid() {
3713 let kernel = Kernel::transient().expect("failed to create kernel");
3714
3715 let result = kernel.execute("echo $$").await.expect("execution failed");
3716 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
3718 assert!(pid > 0, "PID should be positive");
3719 }
3720
3721 #[tokio::test]
3722 async fn test_unset_variable_expands_to_empty() {
3723 let kernel = Kernel::transient().expect("failed to create kernel");
3724
3725 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
3727 assert_eq!(result.out.trim(), "prefix::suffix");
3728 }
3729
3730 #[tokio::test]
3731 async fn test_eq_ne_operators() {
3732 let kernel = Kernel::transient().expect("failed to create kernel");
3733
3734 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
3736 assert_eq!(result.out.trim(), "eq works");
3737
3738 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
3740 assert_eq!(result.out.trim(), "ne works");
3741
3742 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
3744 assert_eq!(result.out.trim(), "correct");
3745 }
3746
3747 #[tokio::test]
3748 async fn test_escaped_dollar_in_string() {
3749 let kernel = Kernel::transient().expect("failed to create kernel");
3750
3751 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
3753 assert_eq!(result.out.trim(), "$100");
3754 }
3755
3756 #[tokio::test]
3757 async fn test_special_vars_in_interpolation() {
3758 let kernel = Kernel::transient().expect("failed to create kernel");
3759
3760 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
3762 assert_eq!(result.out.trim(), "exit: 0");
3763
3764 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
3766 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
3767 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
3768 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
3769 }
3770
3771 #[tokio::test]
3776 async fn test_command_subst_assignment() {
3777 let kernel = Kernel::transient().expect("failed to create kernel");
3778
3779 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
3781 assert_eq!(result.out.trim(), "hello");
3782 }
3783
3784 #[tokio::test]
3785 async fn test_command_subst_with_args() {
3786 let kernel = Kernel::transient().expect("failed to create kernel");
3787
3788 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
3790 assert_eq!(result.out.trim(), "a b c");
3791 }
3792
3793 #[tokio::test]
3794 async fn test_command_subst_nested_vars() {
3795 let kernel = Kernel::transient().expect("failed to create kernel");
3796
3797 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
3799 assert_eq!(result.out.trim(), "hello world");
3800 }
3801
3802 #[tokio::test]
3803 async fn test_background_job_basic() {
3804 use std::time::Duration;
3805
3806 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
3807
3808 let result = kernel.execute("echo hello &").await.expect("execution failed");
3810 assert!(result.ok(), "background command should succeed: {}", result.err);
3811 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
3812
3813 tokio::time::sleep(Duration::from_millis(100)).await;
3815
3816 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
3818 assert!(status.ok(), "status should succeed: {}", status.err);
3819 assert!(
3820 status.out.contains("done:") || status.out.contains("running"),
3821 "should have valid status: {}",
3822 status.out
3823 );
3824
3825 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
3827 assert!(stdout.ok());
3828 assert!(stdout.out.contains("hello"));
3829 }
3830
3831}