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 #[tracing::instrument(level = "info", skip(self, on_output), fields(input_len = input.len()))]
455 pub async fn execute_streaming(
456 &self,
457 input: &str,
458 on_output: &mut dyn FnMut(&ExecResult),
459 ) -> Result<ExecResult> {
460 let program = parse(input).map_err(|errors| {
461 let msg = errors
462 .iter()
463 .map(|e| e.to_string())
464 .collect::<Vec<_>>()
465 .join("; ");
466 anyhow::anyhow!("parse error: {}", msg)
467 })?;
468
469 if !self.skip_validation {
471 let user_tools = self.user_tools.read().await;
472 let validator = Validator::new(&self.tools, &user_tools);
473 let issues = validator.validate(&program);
474
475 let errors: Vec<_> = issues
477 .iter()
478 .filter(|i| i.severity == Severity::Error)
479 .collect();
480
481 if !errors.is_empty() {
482 let error_msg = errors
483 .iter()
484 .map(|e| e.format(input))
485 .collect::<Vec<_>>()
486 .join("\n");
487 return Err(anyhow::anyhow!("validation failed:\n{}", error_msg));
488 }
489
490 for warning in issues.iter().filter(|i| i.severity == Severity::Warning) {
492 tracing::trace!("validation: {}", warning.format(input));
493 }
494 }
495
496 let mut result = ExecResult::success("");
497
498 for stmt in program.statements {
499 if matches!(stmt, Stmt::Empty) {
500 continue;
501 }
502 let flow = self.execute_stmt_flow(&stmt).await?;
503 match flow {
504 ControlFlow::Normal(r) => {
505 on_output(&r);
506 let last_output = r.output.clone();
510 accumulate_result(&mut result, &r);
511 result.output = last_output;
512 }
513 ControlFlow::Exit { code } => {
514 result.code = code;
515 return Ok(result);
516 }
517 ControlFlow::Return { value } => {
518 on_output(&value);
519 result = value;
520 }
521 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
522 on_output(&r);
523 result = r;
524 }
525 }
526 }
527
528 Ok(result)
529 }
530
531 fn execute_stmt_flow<'a>(
533 &'a self,
534 stmt: &'a Stmt,
535 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<ControlFlow>> + Send + 'a>> {
536 use tracing::Instrument;
537 let span = tracing::debug_span!("execute_stmt_flow", stmt_type = %stmt.kind_name());
538 Box::pin(async move {
539 match stmt {
540 Stmt::Assignment(assign) => {
541 let value = self.eval_expr_async(&assign.value).await
543 .context("failed to evaluate assignment")?;
544 let mut scope = self.scope.write().await;
545 if assign.local {
546 scope.set(&assign.name, value.clone());
548 } else {
549 scope.set_global(&assign.name, value.clone());
551 }
552 drop(scope);
553
554 Ok(ControlFlow::ok(ExecResult::success("")))
556 }
557 Stmt::Command(cmd) => {
558 let pipeline = crate::ast::Pipeline {
561 commands: vec![cmd.clone()],
562 background: false,
563 };
564 let result = self.execute_pipeline(&pipeline).await?;
565 self.update_last_result(&result).await;
566
567 if !result.ok() {
569 let scope = self.scope.read().await;
570 if scope.error_exit_enabled() {
571 return Ok(ControlFlow::exit_code(result.code));
572 }
573 }
574
575 Ok(ControlFlow::ok(result))
576 }
577 Stmt::Pipeline(pipeline) => {
578 let result = self.execute_pipeline(pipeline).await?;
579 self.update_last_result(&result).await;
580
581 if !result.ok() {
583 let scope = self.scope.read().await;
584 if scope.error_exit_enabled() {
585 return Ok(ControlFlow::exit_code(result.code));
586 }
587 }
588
589 Ok(ControlFlow::ok(result))
590 }
591 Stmt::If(if_stmt) => {
592 let cond_value = self.eval_expr_async(&if_stmt.condition).await?;
594
595 let branch = if is_truthy(&cond_value) {
596 &if_stmt.then_branch
597 } else {
598 if_stmt.else_branch.as_deref().unwrap_or(&[])
599 };
600
601 let mut result = ExecResult::success("");
602 for stmt in branch {
603 let flow = self.execute_stmt_flow(stmt).await?;
604 match flow {
605 ControlFlow::Normal(r) => accumulate_result(&mut result, &r),
606 _ => return Ok(flow),
607 }
608 }
609 Ok(ControlFlow::ok(result))
610 }
611 Stmt::For(for_loop) => {
612 let mut items: Vec<Value> = Vec::new();
615 for item_expr in &for_loop.items {
616 let item = self.eval_expr_async(item_expr).await?;
617 match &item {
619 Value::Json(serde_json::Value::Array(arr)) => {
621 for elem in arr {
622 items.push(json_to_value(elem.clone()));
623 }
624 }
625 Value::String(_) => {
628 items.push(item);
629 }
630 _ => items.push(item),
632 }
633 }
634
635 let mut result = ExecResult::success("");
636 {
637 let mut scope = self.scope.write().await;
638 scope.push_frame();
639 }
640
641 'outer: for item in items {
642 {
643 let mut scope = self.scope.write().await;
644 scope.set(&for_loop.variable, item);
645 }
646 for stmt in &for_loop.body {
647 let mut flow = self.execute_stmt_flow(stmt).await?;
648 match &mut flow {
649 ControlFlow::Normal(r) => {
650 accumulate_result(&mut result, r);
651 if !r.ok() {
652 let scope = self.scope.read().await;
653 if scope.error_exit_enabled() {
654 drop(scope);
655 let mut scope = self.scope.write().await;
656 scope.pop_frame();
657 return Ok(ControlFlow::exit_code(r.code));
658 }
659 }
660 }
661 ControlFlow::Break { .. } => {
662 if flow.decrement_level() {
663 break 'outer;
665 }
666 let mut scope = self.scope.write().await;
668 scope.pop_frame();
669 return Ok(flow);
670 }
671 ControlFlow::Continue { .. } => {
672 if flow.decrement_level() {
673 continue 'outer;
675 }
676 let mut scope = self.scope.write().await;
678 scope.pop_frame();
679 return Ok(flow);
680 }
681 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
682 let mut scope = self.scope.write().await;
683 scope.pop_frame();
684 return Ok(flow);
685 }
686 }
687 }
688 }
689
690 {
691 let mut scope = self.scope.write().await;
692 scope.pop_frame();
693 }
694 Ok(ControlFlow::ok(result))
695 }
696 Stmt::While(while_loop) => {
697 let mut result = ExecResult::success("");
698
699 'outer: loop {
700 let cond_value = self.eval_expr_async(&while_loop.condition).await?;
702
703 if !is_truthy(&cond_value) {
704 break;
705 }
706
707 for stmt in &while_loop.body {
709 let mut flow = self.execute_stmt_flow(stmt).await?;
710 match &mut flow {
711 ControlFlow::Normal(r) => {
712 accumulate_result(&mut result, r);
713 if !r.ok() {
714 let scope = self.scope.read().await;
715 if scope.error_exit_enabled() {
716 return Ok(ControlFlow::exit_code(r.code));
717 }
718 }
719 }
720 ControlFlow::Break { .. } => {
721 if flow.decrement_level() {
722 break 'outer;
724 }
725 return Ok(flow);
727 }
728 ControlFlow::Continue { .. } => {
729 if flow.decrement_level() {
730 continue 'outer;
732 }
733 return Ok(flow);
735 }
736 ControlFlow::Return { .. } | ControlFlow::Exit { .. } => {
737 return Ok(flow);
738 }
739 }
740 }
741 }
742
743 Ok(ControlFlow::ok(result))
744 }
745 Stmt::Case(case_stmt) => {
746 let match_value = {
748 let mut scope = self.scope.write().await;
749 let value = eval_expr(&case_stmt.expr, &mut scope)?;
750 value_to_string(&value)
751 };
752
753 for branch in &case_stmt.branches {
755 let matched = branch.patterns.iter().any(|pattern| {
756 glob_match(pattern, &match_value)
757 });
758
759 if matched {
760 let mut result = ExecResult::success("");
762 for stmt in &branch.body {
763 let flow = self.execute_stmt_flow(stmt).await?;
764 match flow {
765 ControlFlow::Normal(r) => accumulate_result(&mut result, &r),
766 _ => return Ok(flow),
767 }
768 }
769 return Ok(ControlFlow::ok(result));
770 }
771 }
772
773 Ok(ControlFlow::ok(ExecResult::success("")))
775 }
776 Stmt::Break(levels) => {
777 Ok(ControlFlow::break_n(levels.unwrap_or(1)))
778 }
779 Stmt::Continue(levels) => {
780 Ok(ControlFlow::continue_n(levels.unwrap_or(1)))
781 }
782 Stmt::Return(expr) => {
783 let result = if let Some(e) = expr {
786 let mut scope = self.scope.write().await;
787 let val = eval_expr(e, &mut scope)?;
788 let code = match val {
790 Value::Int(n) => n,
791 Value::Bool(b) => if b { 0 } else { 1 },
792 _ => 0,
793 };
794 ExecResult {
795 code,
796 out: String::new(),
797 err: String::new(),
798 data: None,
799 output: None,
800 }
801 } else {
802 ExecResult::success("")
803 };
804 Ok(ControlFlow::return_value(result))
805 }
806 Stmt::Exit(expr) => {
807 let code = if let Some(e) = expr {
808 let mut scope = self.scope.write().await;
809 let val = eval_expr(e, &mut scope)?;
810 match val {
811 Value::Int(n) => n,
812 _ => 0,
813 }
814 } else {
815 0
816 };
817 Ok(ControlFlow::exit_code(code))
818 }
819 Stmt::ToolDef(tool_def) => {
820 let mut user_tools = self.user_tools.write().await;
821 user_tools.insert(tool_def.name.clone(), tool_def.clone());
822 Ok(ControlFlow::ok(ExecResult::success("")))
823 }
824 Stmt::AndChain { left, right } => {
825 let left_flow = self.execute_stmt_flow(left).await?;
827 match left_flow {
828 ControlFlow::Normal(left_result) => {
829 self.update_last_result(&left_result).await;
830 if left_result.ok() {
831 let right_flow = self.execute_stmt_flow(right).await?;
832 match right_flow {
833 ControlFlow::Normal(right_result) => {
834 self.update_last_result(&right_result).await;
835 let mut combined = left_result;
837 accumulate_result(&mut combined, &right_result);
838 Ok(ControlFlow::ok(combined))
839 }
840 other => Ok(other), }
842 } else {
843 Ok(ControlFlow::ok(left_result))
844 }
845 }
846 _ => Ok(left_flow), }
848 }
849 Stmt::OrChain { left, right } => {
850 let left_flow = self.execute_stmt_flow(left).await?;
852 match left_flow {
853 ControlFlow::Normal(left_result) => {
854 self.update_last_result(&left_result).await;
855 if !left_result.ok() {
856 let right_flow = self.execute_stmt_flow(right).await?;
857 match right_flow {
858 ControlFlow::Normal(right_result) => {
859 self.update_last_result(&right_result).await;
860 let mut combined = left_result;
862 accumulate_result(&mut combined, &right_result);
863 Ok(ControlFlow::ok(combined))
864 }
865 other => Ok(other), }
867 } else {
868 Ok(ControlFlow::ok(left_result))
869 }
870 }
871 _ => Ok(left_flow), }
873 }
874 Stmt::Test(test_expr) => {
875 let expr = crate::ast::Expr::Test(Box::new(test_expr.clone()));
877 let mut scope = self.scope.write().await;
878 let value = eval_expr(&expr, &mut scope)?;
879 drop(scope);
880 let is_true = match value {
881 crate::ast::Value::Bool(b) => b,
882 _ => false,
883 };
884 if is_true {
885 Ok(ControlFlow::ok(ExecResult::success("")))
886 } else {
887 Ok(ControlFlow::ok(ExecResult::failure(1, "")))
888 }
889 }
890 Stmt::Empty => Ok(ControlFlow::ok(ExecResult::success(""))),
891 }
892 }.instrument(span))
893 }
894
895 #[tracing::instrument(level = "debug", skip(self, pipeline), fields(background = pipeline.background, command_count = pipeline.commands.len()))]
897 async fn execute_pipeline(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
898 if pipeline.commands.is_empty() {
899 return Ok(ExecResult::success(""));
900 }
901
902 if pipeline.background {
904 return self.execute_background(pipeline).await;
905 }
906
907 let mut ctx = {
915 let ec = self.exec_ctx.read().await;
916 let scope = self.scope.read().await;
917 ExecContext {
918 backend: ec.backend.clone(),
919 scope: scope.clone(),
920 cwd: ec.cwd.clone(),
921 prev_cwd: ec.prev_cwd.clone(),
922 stdin: None,
923 stdin_data: None,
924 tool_schemas: ec.tool_schemas.clone(),
925 tools: ec.tools.clone(),
926 job_manager: ec.job_manager.clone(),
927 pipeline_position: PipelinePosition::Only,
928 }
929 }; let result = self.runner.run(&pipeline.commands, &mut ctx, self).await;
932
933 {
935 let mut ec = self.exec_ctx.write().await;
936 ec.cwd = ctx.cwd.clone();
937 ec.prev_cwd = ctx.prev_cwd.clone();
938 }
939 {
940 let mut scope = self.scope.write().await;
941 *scope = ctx.scope.clone();
942 }
943
944 Ok(result)
945 }
946
947 #[tracing::instrument(level = "debug", skip(self, pipeline), fields(command_count = pipeline.commands.len()))]
955 async fn execute_background(&self, pipeline: &crate::ast::Pipeline) -> Result<ExecResult> {
956 use tokio::sync::oneshot;
957
958 let command_str = self.format_pipeline(pipeline);
960
961 let stdout = Arc::new(BoundedStream::default_size());
963 let stderr = Arc::new(BoundedStream::default_size());
964
965 let (tx, rx) = oneshot::channel();
967
968 let job_id = self.jobs.register_with_streams(
970 command_str.clone(),
971 rx,
972 stdout.clone(),
973 stderr.clone(),
974 ).await;
975
976 let runner = self.runner.clone();
978 let commands = pipeline.commands.clone();
979 let backend = {
980 let ctx = self.exec_ctx.read().await;
981 ctx.backend.clone()
982 };
983 let scope = {
984 let scope = self.scope.read().await;
985 scope.clone()
986 };
987 let cwd = {
988 let ctx = self.exec_ctx.read().await;
989 ctx.cwd.clone()
990 };
991 let tools = self.tools.clone();
992 let tool_schemas = self.tools.schemas();
993
994 tokio::spawn(async move {
996 let mut bg_ctx = ExecContext::with_backend(backend);
999 bg_ctx.scope = scope;
1000 bg_ctx.cwd = cwd;
1001 bg_ctx.set_tools(tools.clone());
1002 bg_ctx.set_tool_schemas(tool_schemas);
1003
1004 let dispatcher = crate::dispatch::BackendDispatcher::new(tools);
1007
1008 let result = runner.run(&commands, &mut bg_ctx, &dispatcher).await;
1010
1011 if !result.out.is_empty() {
1013 stdout.write(result.out.as_bytes()).await;
1014 }
1015 if !result.err.is_empty() {
1016 stderr.write(result.err.as_bytes()).await;
1017 }
1018
1019 stdout.close().await;
1021 stderr.close().await;
1022
1023 let _ = tx.send(result);
1025 });
1026
1027 Ok(ExecResult::success(format!("[{}]", job_id)))
1028 }
1029
1030 fn format_pipeline(&self, pipeline: &crate::ast::Pipeline) -> String {
1032 pipeline.commands
1033 .iter()
1034 .map(|cmd| {
1035 let mut parts = vec![cmd.name.clone()];
1036 for arg in &cmd.args {
1037 match arg {
1038 Arg::Positional(expr) => {
1039 parts.push(self.format_expr(expr));
1040 }
1041 Arg::Named { key, value } => {
1042 parts.push(format!("{}={}", key, self.format_expr(value)));
1043 }
1044 Arg::ShortFlag(name) => {
1045 parts.push(format!("-{}", name));
1046 }
1047 Arg::LongFlag(name) => {
1048 parts.push(format!("--{}", name));
1049 }
1050 Arg::DoubleDash => {
1051 parts.push("--".to_string());
1052 }
1053 }
1054 }
1055 parts.join(" ")
1056 })
1057 .collect::<Vec<_>>()
1058 .join(" | ")
1059 }
1060
1061 fn format_expr(&self, expr: &Expr) -> String {
1063 match expr {
1064 Expr::Literal(Value::String(s)) => {
1065 if s.contains(' ') || s.contains('"') {
1066 format!("'{}'", s.replace('\'', "\\'"))
1067 } else {
1068 s.clone()
1069 }
1070 }
1071 Expr::Literal(Value::Int(i)) => i.to_string(),
1072 Expr::Literal(Value::Float(f)) => f.to_string(),
1073 Expr::Literal(Value::Bool(b)) => b.to_string(),
1074 Expr::Literal(Value::Null) => "null".to_string(),
1075 Expr::VarRef(path) => {
1076 let name = path.segments.iter()
1077 .map(|seg| match seg {
1078 crate::ast::VarSegment::Field(f) => f.clone(),
1079 })
1080 .collect::<Vec<_>>()
1081 .join(".");
1082 format!("${{{}}}", name)
1083 }
1084 Expr::Interpolated(_) => "\"...\"".to_string(),
1085 _ => "...".to_string(),
1086 }
1087 }
1088
1089 #[tracing::instrument(level = "info", skip(self, args), fields(command = %name), err)]
1091 async fn execute_command(&self, name: &str, args: &[Arg]) -> Result<ExecResult> {
1092 match name {
1094 "true" => return Ok(ExecResult::success("")),
1095 "false" => return Ok(ExecResult::failure(1, "")),
1096 "source" | "." => return self.execute_source(args).await,
1097 _ => {}
1098 }
1099
1100 {
1102 let user_tools = self.user_tools.read().await;
1103 if let Some(tool_def) = user_tools.get(name) {
1104 let tool_def = tool_def.clone();
1105 drop(user_tools);
1106 return self.execute_user_tool(tool_def, args).await;
1107 }
1108 }
1109
1110 let tool = match self.tools.get(name) {
1112 Some(t) => t,
1113 None => {
1114 if let Some(result) = self.try_execute_script(name, args).await? {
1116 return Ok(result);
1117 }
1118 if let Some(result) = self.try_execute_external(name, args).await? {
1120 return Ok(result);
1121 }
1122
1123 let tool_args = self.build_args_async(args, None).await?;
1125 let mut ctx = self.exec_ctx.write().await;
1126 {
1127 let scope = self.scope.read().await;
1128 ctx.scope = scope.clone();
1129 }
1130 let backend = ctx.backend.clone();
1131 match backend.call_tool(name, tool_args, &mut ctx).await {
1132 Ok(tool_result) => {
1133 let mut scope = self.scope.write().await;
1134 *scope = ctx.scope.clone();
1135 let mut exec = ExecResult::from_output(
1136 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1137 );
1138 exec.output = tool_result.output;
1139 return Ok(exec);
1140 }
1141 Err(BackendError::ToolNotFound(_)) => {
1142 }
1144 Err(e) => {
1145 return Ok(ExecResult::failure(1, e.to_string()));
1146 }
1147 }
1148
1149 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1150 }
1151 };
1152
1153 let schema = tool.schema();
1155 let mut tool_args = self.build_args_async(args, Some(&schema)).await?;
1156 let output_format = extract_output_format(&mut tool_args, Some(&schema));
1157
1158 let mut ctx = self.exec_ctx.write().await;
1160 {
1161 let scope = self.scope.read().await;
1162 ctx.scope = scope.clone();
1163 }
1164
1165 let result = tool.execute(tool_args, &mut ctx).await;
1166
1167 {
1169 let mut scope = self.scope.write().await;
1170 *scope = ctx.scope.clone();
1171 }
1172
1173 let result = match output_format {
1174 Some(format) => apply_output_format(result, format),
1175 None => result,
1176 };
1177
1178 Ok(result)
1179 }
1180
1181 async fn build_args_async(&self, args: &[Arg], schema: Option<&crate::tools::ToolSchema>) -> Result<ToolArgs> {
1185 let mut tool_args = ToolArgs::new();
1186 let param_lookup = schema.map(schema_param_lookup).unwrap_or_default();
1187
1188 let mut consumed: std::collections::HashSet<usize> = std::collections::HashSet::new();
1190 let mut past_double_dash = false;
1191
1192 let positional_indices: Vec<usize> = args.iter().enumerate()
1194 .filter_map(|(i, a)| matches!(a, Arg::Positional(_)).then_some(i))
1195 .collect();
1196
1197 let mut i = 0;
1198 while i < args.len() {
1199 match &args[i] {
1200 Arg::DoubleDash => {
1201 past_double_dash = true;
1202 }
1203 Arg::Positional(expr) => {
1204 if !consumed.contains(&i) {
1205 let value = self.eval_expr_async(expr).await?;
1206 let value = apply_tilde_expansion(value);
1207 tool_args.positional.push(value);
1208 }
1209 }
1210 Arg::Named { key, value } => {
1211 let val = self.eval_expr_async(value).await?;
1212 let val = apply_tilde_expansion(val);
1213 tool_args.named.insert(key.clone(), val);
1214 }
1215 Arg::ShortFlag(name) => {
1216 if past_double_dash {
1217 tool_args.positional.push(Value::String(format!("-{name}")));
1218 } else if name.len() == 1 {
1219 let flag_name = name.as_str();
1220 let lookup = param_lookup.get(flag_name);
1221 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1222
1223 if is_bool {
1224 tool_args.flags.insert(flag_name.to_string());
1225 } else {
1226 let canonical = lookup.map(|(name, _)| *name).unwrap_or(flag_name);
1228 let next_pos = positional_indices.iter()
1229 .find(|idx| **idx > i && !consumed.contains(idx));
1230
1231 if let Some(&pos_idx) = next_pos {
1232 if let Arg::Positional(expr) = &args[pos_idx] {
1233 let value = self.eval_expr_async(expr).await?;
1234 let value = apply_tilde_expansion(value);
1235 tool_args.named.insert(canonical.to_string(), value);
1236 consumed.insert(pos_idx);
1237 }
1238 } else {
1239 tool_args.flags.insert(flag_name.to_string());
1240 }
1241 }
1242 } else {
1243 for c in name.chars() {
1245 tool_args.flags.insert(c.to_string());
1246 }
1247 }
1248 }
1249 Arg::LongFlag(name) => {
1250 if past_double_dash {
1251 tool_args.positional.push(Value::String(format!("--{name}")));
1252 } else {
1253 let lookup = param_lookup.get(name.as_str());
1254 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1255
1256 if is_bool {
1257 tool_args.flags.insert(name.clone());
1258 } else {
1259 let canonical = lookup.map(|(name, _)| *name).unwrap_or(name.as_str());
1260 let next_pos = positional_indices.iter()
1261 .find(|idx| **idx > i && !consumed.contains(idx));
1262
1263 if let Some(&pos_idx) = next_pos {
1264 if let Arg::Positional(expr) = &args[pos_idx] {
1265 let value = self.eval_expr_async(expr).await?;
1266 let value = apply_tilde_expansion(value);
1267 tool_args.named.insert(canonical.to_string(), value);
1268 consumed.insert(pos_idx);
1269 }
1270 } else {
1271 tool_args.flags.insert(name.clone());
1272 }
1273 }
1274 }
1275 }
1276 }
1277 i += 1;
1278 }
1279
1280 Ok(tool_args)
1281 }
1282
1283 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1293 let mut argv = Vec::new();
1294 for arg in args {
1295 match arg {
1296 Arg::Positional(expr) => {
1297 let value = self.eval_expr_async(expr).await?;
1298 let value = apply_tilde_expansion(value);
1299 argv.push(value_to_string(&value));
1300 }
1301 Arg::Named { key, value } => {
1302 let val = self.eval_expr_async(value).await?;
1303 let val = apply_tilde_expansion(val);
1304 argv.push(format!("{}={}", key, value_to_string(&val)));
1305 }
1306 Arg::ShortFlag(name) => {
1307 argv.push(format!("-{}", name));
1309 }
1310 Arg::LongFlag(name) => {
1311 argv.push(format!("--{}", name));
1313 }
1314 Arg::DoubleDash => {
1315 argv.push("--".to_string());
1317 }
1318 }
1319 }
1320 Ok(argv)
1321 }
1322
1323 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send + 'a>> {
1328 Box::pin(async move {
1329 match expr {
1330 Expr::Literal(value) => Ok(value.clone()),
1331 Expr::VarRef(path) => {
1332 let scope = self.scope.read().await;
1333 scope.resolve_path(path)
1334 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1335 }
1336 Expr::Interpolated(parts) => {
1337 let mut result = String::new();
1338 for part in parts {
1339 result.push_str(&self.eval_string_part_async(part).await?);
1340 }
1341 Ok(Value::String(result))
1342 }
1343 Expr::BinaryOp { left, op, right } => {
1344 match op {
1345 BinaryOp::And => {
1346 let left_val = self.eval_expr_async(left).await?;
1347 if !is_truthy(&left_val) {
1348 return Ok(left_val);
1349 }
1350 self.eval_expr_async(right).await
1351 }
1352 BinaryOp::Or => {
1353 let left_val = self.eval_expr_async(left).await?;
1354 if is_truthy(&left_val) {
1355 return Ok(left_val);
1356 }
1357 self.eval_expr_async(right).await
1358 }
1359 _ => {
1360 let mut scope = self.scope.write().await;
1362 eval_expr(expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1363 }
1364 }
1365 }
1366 Expr::CommandSubst(pipeline) => {
1367 let saved_scope = { self.scope.read().await.clone() };
1370 let saved_cwd = {
1371 let ec = self.exec_ctx.read().await;
1372 (ec.cwd.clone(), ec.prev_cwd.clone())
1373 };
1374
1375 let run_result = self.execute_pipeline(pipeline).await;
1377
1378 {
1380 let mut scope = self.scope.write().await;
1381 *scope = saved_scope;
1382 if let Ok(ref r) = run_result {
1383 scope.set_last_result(r.clone());
1384 }
1385 }
1386 {
1387 let mut ec = self.exec_ctx.write().await;
1388 ec.cwd = saved_cwd.0;
1389 ec.prev_cwd = saved_cwd.1;
1390 }
1391
1392 let result = run_result?;
1394
1395 if let Some(data) = &result.data {
1397 Ok(data.clone())
1398 } else if let Some(ref output) = result.output {
1399 if output.is_flat() && !output.is_simple_text() && !output.root.is_empty() {
1401 let items: Vec<serde_json::Value> = output.root.iter()
1402 .map(|n| serde_json::Value::String(n.display_name().to_string()))
1403 .collect();
1404 Ok(Value::Json(serde_json::Value::Array(items)))
1405 } else {
1406 Ok(Value::String(result.out.trim_end().to_string()))
1407 }
1408 } else {
1409 Ok(Value::String(result.out.trim_end().to_string()))
1411 }
1412 }
1413 Expr::Test(test_expr) => {
1414 let expr = Expr::Test(test_expr.clone());
1416 let mut scope = self.scope.write().await;
1417 eval_expr(&expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1418 }
1419 Expr::Positional(n) => {
1420 let scope = self.scope.read().await;
1421 match scope.get_positional(*n) {
1422 Some(s) => Ok(Value::String(s.to_string())),
1423 None => Ok(Value::String(String::new())),
1424 }
1425 }
1426 Expr::AllArgs => {
1427 let scope = self.scope.read().await;
1428 Ok(Value::String(scope.all_args().join(" ")))
1429 }
1430 Expr::ArgCount => {
1431 let scope = self.scope.read().await;
1432 Ok(Value::Int(scope.arg_count() as i64))
1433 }
1434 Expr::VarLength(name) => {
1435 let scope = self.scope.read().await;
1436 match scope.get(name) {
1437 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1438 None => Ok(Value::Int(0)),
1439 }
1440 }
1441 Expr::VarWithDefault { name, default } => {
1442 let scope = self.scope.read().await;
1443 let use_default = match scope.get(name) {
1444 Some(value) => value_to_string(value).is_empty(),
1445 None => true,
1446 };
1447 drop(scope); if use_default {
1449 self.eval_string_parts_async(default).await.map(Value::String)
1451 } else {
1452 let scope = self.scope.read().await;
1453 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1454 }
1455 }
1456 Expr::Arithmetic(expr_str) => {
1457 let scope = self.scope.read().await;
1458 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1459 .map(Value::Int)
1460 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1461 }
1462 Expr::Command(cmd) => {
1463 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1465 Ok(Value::Bool(result.code == 0))
1466 }
1467 Expr::LastExitCode => {
1468 let scope = self.scope.read().await;
1469 Ok(Value::Int(scope.last_result().code))
1470 }
1471 Expr::CurrentPid => {
1472 let scope = self.scope.read().await;
1473 Ok(Value::Int(scope.pid() as i64))
1474 }
1475 }
1476 })
1477 }
1478
1479 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1481 Box::pin(async move {
1482 let mut result = String::new();
1483 for part in parts {
1484 result.push_str(&self.eval_string_part_async(part).await?);
1485 }
1486 Ok(result)
1487 })
1488 }
1489
1490 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1492 Box::pin(async move {
1493 match part {
1494 StringPart::Literal(s) => Ok(s.clone()),
1495 StringPart::Var(path) => {
1496 let scope = self.scope.read().await;
1497 match scope.resolve_path(path) {
1498 Some(value) => Ok(value_to_string(&value)),
1499 None => Ok(String::new()), }
1501 }
1502 StringPart::VarWithDefault { name, default } => {
1503 let scope = self.scope.read().await;
1504 let use_default = match scope.get(name) {
1505 Some(value) => value_to_string(value).is_empty(),
1506 None => true,
1507 };
1508 drop(scope); if use_default {
1510 self.eval_string_parts_async(default).await
1512 } else {
1513 let scope = self.scope.read().await;
1514 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
1515 }
1516 }
1517 StringPart::VarLength(name) => {
1518 let scope = self.scope.read().await;
1519 match scope.get(name) {
1520 Some(value) => Ok(value_to_string(value).len().to_string()),
1521 None => Ok("0".to_string()),
1522 }
1523 }
1524 StringPart::Positional(n) => {
1525 let scope = self.scope.read().await;
1526 match scope.get_positional(*n) {
1527 Some(s) => Ok(s.to_string()),
1528 None => Ok(String::new()),
1529 }
1530 }
1531 StringPart::AllArgs => {
1532 let scope = self.scope.read().await;
1533 Ok(scope.all_args().join(" "))
1534 }
1535 StringPart::ArgCount => {
1536 let scope = self.scope.read().await;
1537 Ok(scope.arg_count().to_string())
1538 }
1539 StringPart::Arithmetic(expr) => {
1540 let scope = self.scope.read().await;
1541 match crate::arithmetic::eval_arithmetic(expr, &scope) {
1542 Ok(value) => Ok(value.to_string()),
1543 Err(_) => Ok(String::new()),
1544 }
1545 }
1546 StringPart::CommandSubst(pipeline) => {
1547 let saved_scope = { self.scope.read().await.clone() };
1550 let saved_cwd = {
1551 let ec = self.exec_ctx.read().await;
1552 (ec.cwd.clone(), ec.prev_cwd.clone())
1553 };
1554
1555 let run_result = self.execute_pipeline(pipeline).await;
1557
1558 {
1560 let mut scope = self.scope.write().await;
1561 *scope = saved_scope;
1562 if let Ok(ref r) = run_result {
1563 scope.set_last_result(r.clone());
1564 }
1565 }
1566 {
1567 let mut ec = self.exec_ctx.write().await;
1568 ec.cwd = saved_cwd.0;
1569 ec.prev_cwd = saved_cwd.1;
1570 }
1571
1572 let result = run_result?;
1574
1575 Ok(result.out.trim_end_matches('\n').to_string())
1576 }
1577 StringPart::LastExitCode => {
1578 let scope = self.scope.read().await;
1579 Ok(scope.last_result().code.to_string())
1580 }
1581 StringPart::CurrentPid => {
1582 let scope = self.scope.read().await;
1583 Ok(scope.pid().to_string())
1584 }
1585 }
1586 })
1587 }
1588
1589 async fn update_last_result(&self, result: &ExecResult) {
1591 let mut scope = self.scope.write().await;
1592 scope.set_last_result(result.clone());
1593 }
1594
1595 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
1601 let tool_args = self.build_args_async(args, None).await?;
1603
1604 {
1606 let mut scope = self.scope.write().await;
1607 scope.push_frame();
1608 }
1609
1610 let saved_positional = {
1612 let mut scope = self.scope.write().await;
1613 let saved = scope.save_positional();
1614
1615 let positional_args: Vec<String> = tool_args.positional
1617 .iter()
1618 .map(value_to_string)
1619 .collect();
1620 scope.set_positional(&def.name, positional_args);
1621
1622 saved
1623 };
1624
1625 let mut accumulated_out = String::new();
1628 let mut accumulated_err = String::new();
1629 let mut last_code = 0i64;
1630 let mut last_data: Option<Value> = None;
1631
1632 let mut exec_error: Option<anyhow::Error> = None;
1634 let mut exit_code: Option<i64> = None;
1635
1636 for stmt in &def.body {
1637 match self.execute_stmt_flow(stmt).await {
1638 Ok(flow) => {
1639 match flow {
1640 ControlFlow::Normal(r) => {
1641 accumulated_out.push_str(&r.out);
1642 accumulated_err.push_str(&r.err);
1643 last_code = r.code;
1644 last_data = r.data;
1645 }
1646 ControlFlow::Return { value } => {
1647 accumulated_out.push_str(&value.out);
1648 accumulated_err.push_str(&value.err);
1649 last_code = value.code;
1650 last_data = value.data;
1651 break;
1652 }
1653 ControlFlow::Exit { code } => {
1654 exit_code = Some(code);
1655 break;
1656 }
1657 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1658 accumulated_out.push_str(&r.out);
1659 accumulated_err.push_str(&r.err);
1660 last_code = r.code;
1661 last_data = r.data;
1662 }
1663 }
1664 }
1665 Err(e) => {
1666 exec_error = Some(e);
1667 break;
1668 }
1669 }
1670 }
1671
1672 {
1674 let mut scope = self.scope.write().await;
1675 scope.pop_frame();
1676 scope.set_positional(saved_positional.0, saved_positional.1);
1677 }
1678
1679 if let Some(e) = exec_error {
1681 return Err(e);
1682 }
1683 if let Some(code) = exit_code {
1684 return Ok(ExecResult {
1685 code,
1686 out: accumulated_out,
1687 err: accumulated_err,
1688 data: last_data,
1689 output: None,
1690 });
1691 }
1692
1693 Ok(ExecResult {
1694 code: last_code,
1695 out: accumulated_out,
1696 err: accumulated_err,
1697 data: last_data,
1698 output: None,
1699 })
1700 }
1701
1702 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
1707 let tool_args = self.build_args_async(args, None).await?;
1709 let path = match tool_args.positional.first() {
1710 Some(Value::String(s)) => s.clone(),
1711 Some(v) => value_to_string(v),
1712 None => {
1713 return Ok(ExecResult::failure(1, "source: missing filename"));
1714 }
1715 };
1716
1717 let full_path = {
1719 let ctx = self.exec_ctx.read().await;
1720 if path.starts_with('/') {
1721 std::path::PathBuf::from(&path)
1722 } else {
1723 ctx.cwd.join(&path)
1724 }
1725 };
1726
1727 let content = {
1729 let ctx = self.exec_ctx.read().await;
1730 match ctx.backend.read(&full_path, None).await {
1731 Ok(bytes) => {
1732 String::from_utf8(bytes).map_err(|e| {
1733 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
1734 })?
1735 }
1736 Err(e) => {
1737 return Ok(ExecResult::failure(
1738 1,
1739 format!("source: {}: {}", path, e),
1740 ));
1741 }
1742 }
1743 };
1744
1745 let program = match crate::parser::parse(&content) {
1747 Ok(p) => p,
1748 Err(errors) => {
1749 let msg = errors
1750 .iter()
1751 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
1752 .collect::<Vec<_>>()
1753 .join("\n");
1754 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
1755 }
1756 };
1757
1758 let mut result = ExecResult::success("");
1760 for stmt in program.statements {
1761 if matches!(stmt, crate::ast::Stmt::Empty) {
1762 continue;
1763 }
1764
1765 match self.execute_stmt_flow(&stmt).await {
1766 Ok(flow) => {
1767 match flow {
1768 ControlFlow::Normal(r) => {
1769 result = r.clone();
1770 self.update_last_result(&r).await;
1771 }
1772 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
1773 return Err(anyhow::anyhow!(
1775 "source: {}: unexpected break/continue outside loop",
1776 path
1777 ));
1778 }
1779 ControlFlow::Return { value } => {
1780 return Ok(value);
1782 }
1783 ControlFlow::Exit { code } => {
1784 result.code = code;
1786 return Ok(result);
1787 }
1788 }
1789 }
1790 Err(e) => {
1791 return Err(e.context(format!("source: {}", path)));
1792 }
1793 }
1794 }
1795
1796 Ok(result)
1797 }
1798
1799 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1804 let path_value = {
1806 let scope = self.scope.read().await;
1807 scope
1808 .get("PATH")
1809 .map(value_to_string)
1810 .unwrap_or_else(|| "/bin".to_string())
1811 };
1812
1813 for dir in path_value.split(':') {
1815 if dir.is_empty() {
1816 continue;
1817 }
1818
1819 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
1821
1822 let exists = {
1824 let ctx = self.exec_ctx.read().await;
1825 ctx.backend.exists(&script_path).await
1826 };
1827
1828 if !exists {
1829 continue;
1830 }
1831
1832 let content = {
1834 let ctx = self.exec_ctx.read().await;
1835 match ctx.backend.read(&script_path, None).await {
1836 Ok(bytes) => match String::from_utf8(bytes) {
1837 Ok(s) => s,
1838 Err(e) => {
1839 return Ok(Some(ExecResult::failure(
1840 1,
1841 format!("{}: invalid UTF-8: {}", script_path.display(), e),
1842 )));
1843 }
1844 },
1845 Err(e) => {
1846 return Ok(Some(ExecResult::failure(
1847 1,
1848 format!("{}: {}", script_path.display(), e),
1849 )));
1850 }
1851 }
1852 };
1853
1854 let program = match crate::parser::parse(&content) {
1856 Ok(p) => p,
1857 Err(errors) => {
1858 let msg = errors
1859 .iter()
1860 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
1861 .collect::<Vec<_>>()
1862 .join("\n");
1863 return Ok(Some(ExecResult::failure(1, msg)));
1864 }
1865 };
1866
1867 let tool_args = self.build_args_async(args, None).await?;
1869
1870 let mut isolated_scope = Scope::new();
1872
1873 let positional_args: Vec<String> = tool_args.positional
1875 .iter()
1876 .map(value_to_string)
1877 .collect();
1878 isolated_scope.set_positional(name, positional_args);
1879
1880 let original_scope = {
1882 let mut scope = self.scope.write().await;
1883 std::mem::replace(&mut *scope, isolated_scope)
1884 };
1885
1886 let mut result = ExecResult::success("");
1888 let mut exec_error: Option<anyhow::Error> = None;
1889 let mut exit_code: Option<i64> = None;
1890
1891 for stmt in program.statements {
1892 if matches!(stmt, crate::ast::Stmt::Empty) {
1893 continue;
1894 }
1895
1896 match self.execute_stmt_flow(&stmt).await {
1897 Ok(flow) => {
1898 match flow {
1899 ControlFlow::Normal(r) => result = r,
1900 ControlFlow::Return { value } => {
1901 result = value;
1902 break;
1903 }
1904 ControlFlow::Exit { code } => {
1905 exit_code = Some(code);
1906 break;
1907 }
1908 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1909 result = r;
1910 }
1911 }
1912 }
1913 Err(e) => {
1914 exec_error = Some(e);
1915 break;
1916 }
1917 }
1918 }
1919
1920 {
1922 let mut scope = self.scope.write().await;
1923 *scope = original_scope;
1924 }
1925
1926 if let Some(e) = exec_error {
1928 return Err(e.context(format!("script: {}", script_path.display())));
1929 }
1930 if let Some(code) = exit_code {
1931 result.code = code;
1932 return Ok(Some(result));
1933 }
1934
1935 return Ok(Some(result));
1936 }
1937
1938 Ok(None)
1940 }
1941
1942 #[tracing::instrument(level = "debug", skip(self, args), fields(command = %name))]
1956 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1957 if name.contains('/') {
1959 return Ok(None);
1960 }
1961
1962 let path_var = {
1964 let scope = self.scope.read().await;
1965 scope
1966 .get("PATH")
1967 .map(value_to_string)
1968 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
1969 };
1970
1971 let executable = match resolve_in_path(name, &path_var) {
1973 Some(path) => path,
1974 None => return Ok(None), };
1976
1977 tracing::debug!(executable = %executable, "resolved external command");
1978
1979 let real_cwd = {
1981 let ctx = self.exec_ctx.read().await;
1982 match ctx.backend.resolve_real_path(&ctx.cwd) {
1983 Some(p) => p,
1984 None => {
1985 return Ok(Some(ExecResult::failure(
1986 1,
1987 format!(
1988 "{}: cannot run external command from virtual directory '{}'",
1989 name,
1990 ctx.cwd.display()
1991 ),
1992 )));
1993 }
1994 }
1995 };
1996
1997 let argv = self.build_args_flat(args).await?;
1999
2000 let stdin_data = {
2002 let mut ctx = self.exec_ctx.write().await;
2003 ctx.take_stdin()
2004 };
2005
2006 use tokio::process::Command;
2008
2009 let mut cmd = Command::new(&executable);
2010 cmd.args(&argv);
2011 cmd.current_dir(&real_cwd);
2012
2013 cmd.stdin(if stdin_data.is_some() {
2015 std::process::Stdio::piped()
2016 } else {
2017 std::process::Stdio::null()
2018 });
2019
2020 let inherit_output = self.interactive && stdin_data.is_none();
2023
2024 if inherit_output {
2025 cmd.stdout(std::process::Stdio::inherit());
2026 cmd.stderr(std::process::Stdio::inherit());
2027 } else {
2028 cmd.stdout(std::process::Stdio::piped());
2029 cmd.stderr(std::process::Stdio::piped());
2030 }
2031
2032 let mut child = match cmd.spawn() {
2034 Ok(child) => child,
2035 Err(e) => {
2036 return Ok(Some(ExecResult::failure(
2037 127,
2038 format!("{}: {}", name, e),
2039 )));
2040 }
2041 };
2042
2043 if let Some(data) = stdin_data
2045 && let Some(mut stdin) = child.stdin.take()
2046 {
2047 use tokio::io::AsyncWriteExt;
2048 if let Err(e) = stdin.write_all(data.as_bytes()).await {
2049 return Ok(Some(ExecResult::failure(
2050 1,
2051 format!("{}: failed to write stdin: {}", name, e),
2052 )));
2053 }
2054 }
2056
2057 if inherit_output {
2058 let status = match child.wait().await {
2060 Ok(s) => s,
2061 Err(e) => {
2062 return Ok(Some(ExecResult::failure(
2063 1,
2064 format!("{}: failed to wait: {}", name, e),
2065 )));
2066 }
2067 };
2068
2069 let code = status.code().unwrap_or_else(|| {
2070 #[cfg(unix)]
2071 {
2072 use std::os::unix::process::ExitStatusExt;
2073 128 + status.signal().unwrap_or(0)
2074 }
2075 #[cfg(not(unix))]
2076 {
2077 -1
2078 }
2079 }) as i64;
2080
2081 Ok(Some(ExecResult::from_output(code, String::new(), String::new())))
2083 } else {
2084 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2086 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2087
2088 let stdout_pipe = child.stdout.take();
2089 let stderr_pipe = child.stderr.take();
2090
2091 let stdout_clone = stdout_stream.clone();
2092 let stderr_clone = stderr_stream.clone();
2093
2094 let stdout_task = stdout_pipe.map(|pipe| {
2095 tokio::spawn(async move {
2096 drain_to_stream(pipe, stdout_clone).await;
2097 })
2098 });
2099
2100 let stderr_task = stderr_pipe.map(|pipe| {
2101 tokio::spawn(async move {
2102 drain_to_stream(pipe, stderr_clone).await;
2103 })
2104 });
2105
2106 let status = match child.wait().await {
2107 Ok(s) => s,
2108 Err(e) => {
2109 return Ok(Some(ExecResult::failure(
2110 1,
2111 format!("{}: failed to wait: {}", name, e),
2112 )));
2113 }
2114 };
2115
2116 if let Some(task) = stdout_task {
2117 let _ = task.await;
2119 }
2120 if let Some(task) = stderr_task {
2121 let _ = task.await;
2122 }
2123
2124 let code = status.code().unwrap_or_else(|| {
2125 #[cfg(unix)]
2126 {
2127 use std::os::unix::process::ExitStatusExt;
2128 128 + status.signal().unwrap_or(0)
2129 }
2130 #[cfg(not(unix))]
2131 {
2132 -1
2133 }
2134 }) as i64;
2135
2136 let stdout = stdout_stream.read_string().await;
2137 let stderr = stderr_stream.read_string().await;
2138
2139 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
2140 }
2141 }
2142
2143 pub async fn get_var(&self, name: &str) -> Option<Value> {
2147 let scope = self.scope.read().await;
2148 scope.get(name).cloned()
2149 }
2150
2151 #[cfg(test)]
2153 pub async fn error_exit_enabled(&self) -> bool {
2154 let scope = self.scope.read().await;
2155 scope.error_exit_enabled()
2156 }
2157
2158 pub async fn set_var(&self, name: &str, value: Value) {
2160 let mut scope = self.scope.write().await;
2161 scope.set(name.to_string(), value);
2162 }
2163
2164 pub async fn list_vars(&self) -> Vec<(String, Value)> {
2166 let scope = self.scope.read().await;
2167 scope.all()
2168 }
2169
2170 pub async fn cwd(&self) -> PathBuf {
2174 self.exec_ctx.read().await.cwd.clone()
2175 }
2176
2177 pub async fn set_cwd(&self, path: PathBuf) {
2179 let mut ctx = self.exec_ctx.write().await;
2180 ctx.set_cwd(path);
2181 }
2182
2183 pub async fn last_result(&self) -> ExecResult {
2187 let scope = self.scope.read().await;
2188 scope.last_result().clone()
2189 }
2190
2191 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
2195 self.tools.schemas()
2196 }
2197
2198 pub fn jobs(&self) -> Arc<JobManager> {
2202 self.jobs.clone()
2203 }
2204
2205 pub fn vfs(&self) -> Arc<VfsRouter> {
2209 self.vfs.clone()
2210 }
2211
2212 pub async fn reset(&self) -> Result<()> {
2219 {
2220 let mut scope = self.scope.write().await;
2221 *scope = Scope::new();
2222 }
2223 {
2224 let mut ctx = self.exec_ctx.write().await;
2225 ctx.cwd = PathBuf::from("/");
2226 }
2227 Ok(())
2228 }
2229
2230 pub async fn shutdown(self) -> Result<()> {
2232 self.jobs.wait_all().await;
2234 Ok(())
2235 }
2236
2237 async fn dispatch_command(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2248 {
2250 let mut scope = self.scope.write().await;
2251 *scope = ctx.scope.clone();
2252 }
2253 {
2254 let mut ec = self.exec_ctx.write().await;
2255 ec.cwd = ctx.cwd.clone();
2256 ec.prev_cwd = ctx.prev_cwd.clone();
2257 ec.stdin = ctx.stdin.take();
2258 ec.stdin_data = ctx.stdin_data.take();
2259 }
2260
2261 let result = self.execute_command(&cmd.name, &cmd.args).await?;
2263
2264 {
2266 let scope = self.scope.read().await;
2267 ctx.scope = scope.clone();
2268 }
2269 {
2270 let ec = self.exec_ctx.read().await;
2271 ctx.cwd = ec.cwd.clone();
2272 ctx.prev_cwd = ec.prev_cwd.clone();
2273 }
2274
2275 Ok(result)
2276 }
2277}
2278
2279#[async_trait]
2280impl CommandDispatcher for Kernel {
2281 async fn dispatch(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2287 self.dispatch_command(cmd, ctx).await
2288 }
2289}
2290
2291fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2297 if !accumulated.out.is_empty() && !new.out.is_empty() {
2298 accumulated.out.push('\n');
2299 }
2300 accumulated.out.push_str(&new.out);
2301 if !accumulated.err.is_empty() && !new.err.is_empty() {
2302 accumulated.err.push('\n');
2303 }
2304 accumulated.err.push_str(&new.err);
2305 accumulated.code = new.code;
2306 accumulated.data = new.data.clone();
2307}
2308
2309fn is_truthy(value: &Value) -> bool {
2311 match value {
2312 Value::Null => false,
2313 Value::Bool(b) => *b,
2314 Value::Int(i) => *i != 0,
2315 Value::Float(f) => *f != 0.0,
2316 Value::String(s) => !s.is_empty(),
2317 Value::Json(json) => match json {
2318 serde_json::Value::Null => false,
2319 serde_json::Value::Array(arr) => !arr.is_empty(),
2320 serde_json::Value::Object(obj) => !obj.is_empty(),
2321 serde_json::Value::Bool(b) => *b,
2322 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
2323 serde_json::Value::String(s) => !s.is_empty(),
2324 },
2325 Value::Blob(_) => true, }
2327}
2328
2329fn apply_tilde_expansion(value: Value) -> Value {
2333 match value {
2334 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
2335 _ => value,
2336 }
2337}
2338
2339#[cfg(test)]
2340mod tests {
2341 use super::*;
2342
2343 #[tokio::test]
2344 async fn test_kernel_transient() {
2345 let kernel = Kernel::transient().expect("failed to create kernel");
2346 assert_eq!(kernel.name(), "transient");
2347 }
2348
2349 #[tokio::test]
2350 async fn test_kernel_execute_echo() {
2351 let kernel = Kernel::transient().expect("failed to create kernel");
2352 let result = kernel.execute("echo hello").await.expect("execution failed");
2353 assert!(result.ok());
2354 assert_eq!(result.out.trim(), "hello");
2355 }
2356
2357 #[tokio::test]
2358 async fn test_multiple_statements_accumulate_output() {
2359 let kernel = Kernel::transient().expect("failed to create kernel");
2360 let result = kernel
2361 .execute("echo one\necho two\necho three")
2362 .await
2363 .expect("execution failed");
2364 assert!(result.ok());
2365 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
2367 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
2368 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
2369 }
2370
2371 #[tokio::test]
2372 async fn test_and_chain_accumulates_output() {
2373 let kernel = Kernel::transient().expect("failed to create kernel");
2374 let result = kernel
2375 .execute("echo first && echo second")
2376 .await
2377 .expect("execution failed");
2378 assert!(result.ok());
2379 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
2380 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
2381 }
2382
2383 #[tokio::test]
2384 async fn test_for_loop_accumulates_output() {
2385 let kernel = Kernel::transient().expect("failed to create kernel");
2386 let result = kernel
2387 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
2388 .await
2389 .expect("execution failed");
2390 assert!(result.ok());
2391 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
2392 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
2393 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
2394 }
2395
2396 #[tokio::test]
2397 async fn test_while_loop_accumulates_output() {
2398 let kernel = Kernel::transient().expect("failed to create kernel");
2399 let result = kernel
2400 .execute(r#"
2401 N=3
2402 while [[ ${N} -gt 0 ]]; do
2403 echo "N=${N}"
2404 N=$((N - 1))
2405 done
2406 "#)
2407 .await
2408 .expect("execution failed");
2409 assert!(result.ok());
2410 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
2411 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
2412 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
2413 }
2414
2415 #[tokio::test]
2416 async fn test_kernel_set_var() {
2417 let kernel = Kernel::transient().expect("failed to create kernel");
2418
2419 kernel.execute("X=42").await.expect("set failed");
2420
2421 let value = kernel.get_var("X").await;
2422 assert_eq!(value, Some(Value::Int(42)));
2423 }
2424
2425 #[tokio::test]
2426 async fn test_kernel_var_expansion() {
2427 let kernel = Kernel::transient().expect("failed to create kernel");
2428
2429 kernel.execute("NAME=\"world\"").await.expect("set failed");
2430 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
2431
2432 assert!(result.ok());
2433 assert_eq!(result.out.trim(), "hello world");
2434 }
2435
2436 #[tokio::test]
2437 async fn test_kernel_last_result() {
2438 let kernel = Kernel::transient().expect("failed to create kernel");
2439
2440 kernel.execute("echo test").await.expect("echo failed");
2441
2442 let last = kernel.last_result().await;
2443 assert!(last.ok());
2444 assert_eq!(last.out.trim(), "test");
2445 }
2446
2447 #[tokio::test]
2448 async fn test_kernel_tool_not_found() {
2449 let kernel = Kernel::transient().expect("failed to create kernel");
2450
2451 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
2452 assert!(!result.ok());
2453 assert_eq!(result.code, 127);
2454 assert!(result.err.contains("command not found"));
2455 }
2456
2457 #[tokio::test]
2458 async fn test_external_command_true() {
2459 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2461
2462 let result = kernel.execute("true").await.expect("execution failed");
2464 assert!(result.ok(), "true should succeed: {:?}", result);
2466 }
2467
2468 #[tokio::test]
2469 async fn test_external_command_basic() {
2470 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2472
2473 let path_var = std::env::var("PATH").unwrap_or_default();
2478 eprintln!("System PATH: {}", path_var);
2479
2480 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
2482
2483 let result = kernel.execute("uname").await.expect("execution failed");
2486 eprintln!("uname result: {:?}", result);
2487 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
2489 }
2490
2491 #[tokio::test]
2492 async fn test_kernel_reset() {
2493 let kernel = Kernel::transient().expect("failed to create kernel");
2494
2495 kernel.execute("X=1").await.expect("set failed");
2496 assert!(kernel.get_var("X").await.is_some());
2497
2498 kernel.reset().await.expect("reset failed");
2499 assert!(kernel.get_var("X").await.is_none());
2500 }
2501
2502 #[tokio::test]
2503 async fn test_kernel_cwd() {
2504 let kernel = Kernel::transient().expect("failed to create kernel");
2505
2506 let cwd = kernel.cwd().await;
2508 let home = std::env::var("HOME")
2509 .map(PathBuf::from)
2510 .unwrap_or_else(|_| PathBuf::from("/"));
2511 assert_eq!(cwd, home);
2512
2513 kernel.set_cwd(PathBuf::from("/tmp")).await;
2514 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
2515 }
2516
2517 #[tokio::test]
2518 async fn test_kernel_list_vars() {
2519 let kernel = Kernel::transient().expect("failed to create kernel");
2520
2521 kernel.execute("A=1").await.ok();
2522 kernel.execute("B=2").await.ok();
2523
2524 let vars = kernel.list_vars().await;
2525 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
2526 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
2527 }
2528
2529 #[tokio::test]
2530 async fn test_is_truthy() {
2531 assert!(!is_truthy(&Value::Null));
2532 assert!(!is_truthy(&Value::Bool(false)));
2533 assert!(is_truthy(&Value::Bool(true)));
2534 assert!(!is_truthy(&Value::Int(0)));
2535 assert!(is_truthy(&Value::Int(1)));
2536 assert!(!is_truthy(&Value::String("".into())));
2537 assert!(is_truthy(&Value::String("x".into())));
2538 }
2539
2540 #[tokio::test]
2541 async fn test_jq_in_pipeline() {
2542 let kernel = Kernel::transient().expect("failed to create kernel");
2543 let result = kernel
2545 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
2546 .await
2547 .expect("execution failed");
2548 assert!(result.ok(), "jq pipeline failed: {}", result.err);
2549 assert_eq!(result.out.trim(), "Alice");
2550 }
2551
2552 #[tokio::test]
2553 async fn test_user_defined_tool() {
2554 let kernel = Kernel::transient().expect("failed to create kernel");
2555
2556 kernel
2558 .execute(r#"greet() { echo "Hello, $1!" }"#)
2559 .await
2560 .expect("function definition failed");
2561
2562 let result = kernel
2564 .execute(r#"greet "World""#)
2565 .await
2566 .expect("function call failed");
2567
2568 assert!(result.ok(), "greet failed: {}", result.err);
2569 assert_eq!(result.out.trim(), "Hello, World!");
2570 }
2571
2572 #[tokio::test]
2573 async fn test_user_tool_positional_args() {
2574 let kernel = Kernel::transient().expect("failed to create kernel");
2575
2576 kernel
2578 .execute(r#"greet() { echo "Hi $1" }"#)
2579 .await
2580 .expect("function definition failed");
2581
2582 let result = kernel
2584 .execute(r#"greet "Amy""#)
2585 .await
2586 .expect("function call failed");
2587
2588 assert!(result.ok(), "greet failed: {}", result.err);
2589 assert_eq!(result.out.trim(), "Hi Amy");
2590 }
2591
2592 #[tokio::test]
2593 async fn test_function_shared_scope() {
2594 let kernel = Kernel::transient().expect("failed to create kernel");
2595
2596 kernel
2598 .execute(r#"SECRET="hidden""#)
2599 .await
2600 .expect("set failed");
2601
2602 kernel
2604 .execute(r#"access_parent() {
2605 echo "${SECRET}"
2606 SECRET="modified"
2607 }"#)
2608 .await
2609 .expect("function definition failed");
2610
2611 let result = kernel.execute("access_parent").await.expect("function call failed");
2613
2614 assert!(
2616 result.out.contains("hidden"),
2617 "Function should access parent scope, got: {}",
2618 result.out
2619 );
2620
2621 let secret = kernel.get_var("SECRET").await;
2623 assert_eq!(
2624 secret,
2625 Some(Value::String("modified".into())),
2626 "Function should modify parent scope"
2627 );
2628 }
2629
2630 #[tokio::test]
2631 async fn test_exec_builtin() {
2632 let kernel = Kernel::transient().expect("failed to create kernel");
2633 let result = kernel
2635 .execute(r#"exec command="/bin/echo" argv="hello world""#)
2636 .await
2637 .expect("exec failed");
2638
2639 assert!(result.ok(), "exec failed: {}", result.err);
2640 assert_eq!(result.out.trim(), "hello world");
2641 }
2642
2643 #[tokio::test]
2644 async fn test_while_false_never_runs() {
2645 let kernel = Kernel::transient().expect("failed to create kernel");
2646
2647 let result = kernel
2649 .execute(r#"
2650 while false; do
2651 echo "should not run"
2652 done
2653 "#)
2654 .await
2655 .expect("while false failed");
2656
2657 assert!(result.ok());
2658 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
2659 }
2660
2661 #[tokio::test]
2662 async fn test_while_string_comparison() {
2663 let kernel = Kernel::transient().expect("failed to create kernel");
2664
2665 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
2667
2668 let result = kernel
2671 .execute(r#"
2672 while [[ ${FLAG} == "go" ]]; do
2673 FLAG="stop"
2674 echo "running"
2675 done
2676 "#)
2677 .await
2678 .expect("while with string cmp failed");
2679
2680 assert!(result.ok());
2681 assert!(result.out.contains("running"), "should have run once: {}", result.out);
2682
2683 let flag = kernel.get_var("FLAG").await;
2685 assert_eq!(flag, Some(Value::String("stop".into())));
2686 }
2687
2688 #[tokio::test]
2689 async fn test_while_numeric_comparison() {
2690 let kernel = Kernel::transient().expect("failed to create kernel");
2691
2692 kernel.execute("N=5").await.expect("set failed");
2694
2695 let result = kernel
2697 .execute(r#"
2698 while [[ ${N} -gt 3 ]]; do
2699 N=3
2700 echo "N was greater"
2701 done
2702 "#)
2703 .await
2704 .expect("while with > failed");
2705
2706 assert!(result.ok());
2707 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
2708 }
2709
2710 #[tokio::test]
2711 async fn test_break_in_while_loop() {
2712 let kernel = Kernel::transient().expect("failed to create kernel");
2713
2714 let result = kernel
2715 .execute(r#"
2716 I=0
2717 while true; do
2718 I=1
2719 echo "before break"
2720 break
2721 echo "after break"
2722 done
2723 "#)
2724 .await
2725 .expect("while with break failed");
2726
2727 assert!(result.ok());
2728 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
2729 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
2730
2731 let i = kernel.get_var("I").await;
2733 assert_eq!(i, Some(Value::Int(1)));
2734 }
2735
2736 #[tokio::test]
2737 async fn test_continue_in_while_loop() {
2738 let kernel = Kernel::transient().expect("failed to create kernel");
2739
2740 let result = kernel
2745 .execute(r#"
2746 STATE="start"
2747 AFTER_CONTINUE="no"
2748 while [[ ${STATE} != "done" ]]; do
2749 if [[ ${STATE} == "start" ]]; then
2750 STATE="middle"
2751 continue
2752 AFTER_CONTINUE="yes"
2753 fi
2754 if [[ ${STATE} == "middle" ]]; then
2755 STATE="done"
2756 fi
2757 done
2758 "#)
2759 .await
2760 .expect("while with continue failed");
2761
2762 assert!(result.ok());
2763
2764 let state = kernel.get_var("STATE").await;
2766 assert_eq!(state, Some(Value::String("done".into())));
2767
2768 let after = kernel.get_var("AFTER_CONTINUE").await;
2770 assert_eq!(after, Some(Value::String("no".into())));
2771 }
2772
2773 #[tokio::test]
2774 async fn test_break_with_level() {
2775 let kernel = Kernel::transient().expect("failed to create kernel");
2776
2777 let result = kernel
2782 .execute(r#"
2783 OUTER=0
2784 while true; do
2785 OUTER=1
2786 for X in "1 2"; do
2787 break 2
2788 done
2789 OUTER=2
2790 done
2791 "#)
2792 .await
2793 .expect("nested break failed");
2794
2795 assert!(result.ok());
2796
2797 let outer = kernel.get_var("OUTER").await;
2799 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
2800 }
2801
2802 #[tokio::test]
2803 async fn test_return_from_tool() {
2804 let kernel = Kernel::transient().expect("failed to create kernel");
2805
2806 kernel
2808 .execute(r#"early_return() {
2809 if [[ $1 == 1 ]]; then
2810 return 42
2811 fi
2812 echo "not returned"
2813 }"#)
2814 .await
2815 .expect("function definition failed");
2816
2817 let result = kernel
2820 .execute("early_return 1")
2821 .await
2822 .expect("function call failed");
2823
2824 assert_eq!(result.code, 42);
2826 assert!(result.out.is_empty());
2828 }
2829
2830 #[tokio::test]
2831 async fn test_return_without_value() {
2832 let kernel = Kernel::transient().expect("failed to create kernel");
2833
2834 kernel
2836 .execute(r#"early_exit() {
2837 if [[ $1 == "stop" ]]; then
2838 return
2839 fi
2840 echo "continued"
2841 }"#)
2842 .await
2843 .expect("function definition failed");
2844
2845 let result = kernel
2847 .execute(r#"early_exit "stop""#)
2848 .await
2849 .expect("function call failed");
2850
2851 assert!(result.ok());
2852 assert!(result.out.is_empty() || result.out.trim().is_empty());
2853 }
2854
2855 #[tokio::test]
2856 async fn test_exit_stops_execution() {
2857 let kernel = Kernel::transient().expect("failed to create kernel");
2858
2859 kernel
2861 .execute(r#"
2862 BEFORE="yes"
2863 exit 0
2864 AFTER="yes"
2865 "#)
2866 .await
2867 .expect("execution failed");
2868
2869 let before = kernel.get_var("BEFORE").await;
2871 assert_eq!(before, Some(Value::String("yes".into())));
2872
2873 let after = kernel.get_var("AFTER").await;
2874 assert!(after.is_none(), "AFTER should not be set after exit");
2875 }
2876
2877 #[tokio::test]
2878 async fn test_exit_with_code() {
2879 let kernel = Kernel::transient().expect("failed to create kernel");
2880
2881 let result = kernel
2883 .execute("exit 42")
2884 .await
2885 .expect("exit failed");
2886
2887 assert_eq!(result.code, 42);
2888 assert!(result.out.is_empty(), "exit should not produce stdout");
2889 }
2890
2891 #[tokio::test]
2892 async fn test_set_e_stops_on_failure() {
2893 let kernel = Kernel::transient().expect("failed to create kernel");
2894
2895 kernel.execute("set -e").await.expect("set -e failed");
2897
2898 kernel
2900 .execute(r#"
2901 STEP1="done"
2902 false
2903 STEP2="done"
2904 "#)
2905 .await
2906 .expect("execution failed");
2907
2908 let step1 = kernel.get_var("STEP1").await;
2910 assert_eq!(step1, Some(Value::String("done".into())));
2911
2912 let step2 = kernel.get_var("STEP2").await;
2913 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
2914 }
2915
2916 #[tokio::test]
2917 async fn test_set_plus_e_disables_error_exit() {
2918 let kernel = Kernel::transient().expect("failed to create kernel");
2919
2920 kernel.execute("set -e").await.expect("set -e failed");
2922 kernel.execute("set +e").await.expect("set +e failed");
2923
2924 kernel
2926 .execute(r#"
2927 STEP1="done"
2928 false
2929 STEP2="done"
2930 "#)
2931 .await
2932 .expect("execution failed");
2933
2934 let step1 = kernel.get_var("STEP1").await;
2936 assert_eq!(step1, Some(Value::String("done".into())));
2937
2938 let step2 = kernel.get_var("STEP2").await;
2939 assert_eq!(step2, Some(Value::String("done".into())));
2940 }
2941
2942 #[tokio::test]
2943 async fn test_set_ignores_unknown_options() {
2944 let kernel = Kernel::transient().expect("failed to create kernel");
2945
2946 let result = kernel
2948 .execute("set -e -u -o pipefail")
2949 .await
2950 .expect("set with unknown options failed");
2951
2952 assert!(result.ok(), "set should succeed with unknown options");
2953
2954 kernel
2956 .execute(r#"
2957 BEFORE="yes"
2958 false
2959 AFTER="yes"
2960 "#)
2961 .await
2962 .ok();
2963
2964 let after = kernel.get_var("AFTER").await;
2965 assert!(after.is_none(), "-e should be enabled despite unknown options");
2966 }
2967
2968 #[tokio::test]
2969 async fn test_set_no_args_shows_settings() {
2970 let kernel = Kernel::transient().expect("failed to create kernel");
2971
2972 kernel.execute("set -e").await.expect("set -e failed");
2974
2975 let result = kernel.execute("set").await.expect("set failed");
2977
2978 assert!(result.ok());
2979 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
2980 }
2981
2982 #[tokio::test]
2983 async fn test_set_e_in_pipeline() {
2984 let kernel = Kernel::transient().expect("failed to create kernel");
2985
2986 kernel.execute("set -e").await.expect("set -e failed");
2987
2988 kernel
2990 .execute(r#"
2991 BEFORE="yes"
2992 false | cat
2993 AFTER="yes"
2994 "#)
2995 .await
2996 .ok();
2997
2998 let before = kernel.get_var("BEFORE").await;
2999 assert_eq!(before, Some(Value::String("yes".into())));
3000
3001 }
3006
3007 #[tokio::test]
3008 async fn test_set_e_with_and_chain() {
3009 let kernel = Kernel::transient().expect("failed to create kernel");
3010
3011 kernel.execute("set -e").await.expect("set -e failed");
3012
3013 kernel
3016 .execute(r#"
3017 RESULT="initial"
3018 false && RESULT="chained"
3019 RESULT="continued"
3020 "#)
3021 .await
3022 .ok();
3023
3024 let result = kernel.get_var("RESULT").await;
3027 assert!(result.is_some(), "RESULT should be set");
3030 }
3031
3032 #[tokio::test]
3033 async fn test_set_e_exits_in_for_loop() {
3034 let kernel = Kernel::transient().expect("failed to create kernel");
3035
3036 kernel.execute("set -e").await.expect("set -e failed");
3037
3038 kernel
3039 .execute(r#"
3040 REACHED="no"
3041 for x in 1 2 3; do
3042 false
3043 REACHED="yes"
3044 done
3045 "#)
3046 .await
3047 .ok();
3048
3049 let reached = kernel.get_var("REACHED").await;
3051 assert_eq!(reached, Some(Value::String("no".into())),
3052 "set -e should exit on failure in for loop body");
3053 }
3054
3055 #[tokio::test]
3056 async fn test_for_loop_continues_without_set_e() {
3057 let kernel = Kernel::transient().expect("failed to create kernel");
3058
3059 kernel
3061 .execute(r#"
3062 COUNT=0
3063 for x in 1 2 3; do
3064 false
3065 COUNT=$((COUNT + 1))
3066 done
3067 "#)
3068 .await
3069 .ok();
3070
3071 let count = kernel.get_var("COUNT").await;
3072 let count_val = match &count {
3074 Some(Value::Int(n)) => *n,
3075 Some(Value::String(s)) => s.parse().unwrap_or(-1),
3076 _ => -1,
3077 };
3078 assert_eq!(count_val, 3,
3079 "without set -e, loop should complete all iterations (got {:?})", count);
3080 }
3081
3082 #[tokio::test]
3087 async fn test_source_sets_variables() {
3088 let kernel = Kernel::transient().expect("failed to create kernel");
3089
3090 kernel
3092 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
3093 .await
3094 .expect("write failed");
3095
3096 let result = kernel
3098 .execute(r#"source "/test.kai""#)
3099 .await
3100 .expect("source failed");
3101
3102 assert!(result.ok(), "source should succeed");
3103
3104 let foo = kernel.get_var("FOO").await;
3106 assert_eq!(foo, Some(Value::String("bar".into())));
3107 }
3108
3109 #[tokio::test]
3110 async fn test_source_with_dot_alias() {
3111 let kernel = Kernel::transient().expect("failed to create kernel");
3112
3113 kernel
3115 .execute(r#"write "/vars.kai" 'X=42'"#)
3116 .await
3117 .expect("write failed");
3118
3119 let result = kernel
3121 .execute(r#". "/vars.kai""#)
3122 .await
3123 .expect(". failed");
3124
3125 assert!(result.ok(), ". should succeed");
3126
3127 let x = kernel.get_var("X").await;
3129 assert_eq!(x, Some(Value::Int(42)));
3130 }
3131
3132 #[tokio::test]
3133 async fn test_source_not_found() {
3134 let kernel = Kernel::transient().expect("failed to create kernel");
3135
3136 let result = kernel
3138 .execute(r#"source "/nonexistent.kai""#)
3139 .await
3140 .expect("source should not fail with error");
3141
3142 assert!(!result.ok(), "source of non-existent file should fail");
3143 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
3144 }
3145
3146 #[tokio::test]
3147 async fn test_source_missing_filename() {
3148 let kernel = Kernel::transient().expect("failed to create kernel");
3149
3150 let result = kernel
3152 .execute("source")
3153 .await
3154 .expect("source should not fail with error");
3155
3156 assert!(!result.ok(), "source without filename should fail");
3157 assert!(result.err.contains("missing filename"), "error should mention missing filename");
3158 }
3159
3160 #[tokio::test]
3161 async fn test_source_executes_multiple_statements() {
3162 let kernel = Kernel::transient().expect("failed to create kernel");
3163
3164 kernel
3166 .execute(r#"write "/multi.kai" 'A=1
3167B=2
3168C=3'"#)
3169 .await
3170 .expect("write failed");
3171
3172 kernel
3174 .execute(r#"source "/multi.kai""#)
3175 .await
3176 .expect("source failed");
3177
3178 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
3180 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
3181 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
3182 }
3183
3184 #[tokio::test]
3185 async fn test_source_can_define_functions() {
3186 let kernel = Kernel::transient().expect("failed to create kernel");
3187
3188 kernel
3190 .execute(r#"write "/functions.kai" 'greet() {
3191 echo "Hello, $1!"
3192}'"#)
3193 .await
3194 .expect("write failed");
3195
3196 kernel
3198 .execute(r#"source "/functions.kai""#)
3199 .await
3200 .expect("source failed");
3201
3202 let result = kernel
3204 .execute(r#"greet "World""#)
3205 .await
3206 .expect("greet failed");
3207
3208 assert!(result.ok());
3209 assert!(result.out.contains("Hello, World!"));
3210 }
3211
3212 #[tokio::test]
3213 async fn test_source_inherits_error_exit() {
3214 let kernel = Kernel::transient().expect("failed to create kernel");
3215
3216 kernel.execute("set -e").await.expect("set -e failed");
3218
3219 kernel
3221 .execute(r#"write "/fail.kai" 'BEFORE="yes"
3222false
3223AFTER="yes"'"#)
3224 .await
3225 .expect("write failed");
3226
3227 kernel
3229 .execute(r#"source "/fail.kai""#)
3230 .await
3231 .ok();
3232
3233 let before = kernel.get_var("BEFORE").await;
3235 assert_eq!(before, Some(Value::String("yes".into())));
3236
3237 }
3240
3241 #[tokio::test]
3246 async fn test_case_simple_match() {
3247 let kernel = Kernel::transient().expect("failed to create kernel");
3248
3249 let result = kernel
3250 .execute(r#"
3251 case "hello" in
3252 hello) echo "matched hello" ;;
3253 world) echo "matched world" ;;
3254 esac
3255 "#)
3256 .await
3257 .expect("case failed");
3258
3259 assert!(result.ok());
3260 assert_eq!(result.out.trim(), "matched hello");
3261 }
3262
3263 #[tokio::test]
3264 async fn test_case_wildcard_match() {
3265 let kernel = Kernel::transient().expect("failed to create kernel");
3266
3267 let result = kernel
3268 .execute(r#"
3269 case "main.rs" in
3270 "*.py") echo "Python" ;;
3271 "*.rs") echo "Rust" ;;
3272 "*") echo "Unknown" ;;
3273 esac
3274 "#)
3275 .await
3276 .expect("case failed");
3277
3278 assert!(result.ok());
3279 assert_eq!(result.out.trim(), "Rust");
3280 }
3281
3282 #[tokio::test]
3283 async fn test_case_default_match() {
3284 let kernel = Kernel::transient().expect("failed to create kernel");
3285
3286 let result = kernel
3287 .execute(r#"
3288 case "unknown.xyz" in
3289 "*.py") echo "Python" ;;
3290 "*.rs") echo "Rust" ;;
3291 "*") echo "Default" ;;
3292 esac
3293 "#)
3294 .await
3295 .expect("case failed");
3296
3297 assert!(result.ok());
3298 assert_eq!(result.out.trim(), "Default");
3299 }
3300
3301 #[tokio::test]
3302 async fn test_case_no_match() {
3303 let kernel = Kernel::transient().expect("failed to create kernel");
3304
3305 let result = kernel
3307 .execute(r#"
3308 case "nope" in
3309 "yes") echo "yes" ;;
3310 "no") echo "no" ;;
3311 esac
3312 "#)
3313 .await
3314 .expect("case failed");
3315
3316 assert!(result.ok());
3317 assert!(result.out.is_empty(), "no match should produce empty output");
3318 }
3319
3320 #[tokio::test]
3321 async fn test_case_with_variable() {
3322 let kernel = Kernel::transient().expect("failed to create kernel");
3323
3324 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
3325
3326 let result = kernel
3327 .execute(r#"
3328 case ${LANG} in
3329 python) echo "snake" ;;
3330 rust) echo "crab" ;;
3331 go) echo "gopher" ;;
3332 esac
3333 "#)
3334 .await
3335 .expect("case failed");
3336
3337 assert!(result.ok());
3338 assert_eq!(result.out.trim(), "crab");
3339 }
3340
3341 #[tokio::test]
3342 async fn test_case_multiple_patterns() {
3343 let kernel = Kernel::transient().expect("failed to create kernel");
3344
3345 let result = kernel
3346 .execute(r#"
3347 case "yes" in
3348 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
3349 "n"|"no"|"N"|"NO") echo "negative" ;;
3350 esac
3351 "#)
3352 .await
3353 .expect("case failed");
3354
3355 assert!(result.ok());
3356 assert_eq!(result.out.trim(), "affirmative");
3357 }
3358
3359 #[tokio::test]
3360 async fn test_case_glob_question_mark() {
3361 let kernel = Kernel::transient().expect("failed to create kernel");
3362
3363 let result = kernel
3364 .execute(r#"
3365 case "test1" in
3366 "test?") echo "matched test?" ;;
3367 "*") echo "default" ;;
3368 esac
3369 "#)
3370 .await
3371 .expect("case failed");
3372
3373 assert!(result.ok());
3374 assert_eq!(result.out.trim(), "matched test?");
3375 }
3376
3377 #[tokio::test]
3378 async fn test_case_char_class() {
3379 let kernel = Kernel::transient().expect("failed to create kernel");
3380
3381 let result = kernel
3382 .execute(r#"
3383 case "Yes" in
3384 "[Yy]*") echo "yes-like" ;;
3385 "[Nn]*") echo "no-like" ;;
3386 esac
3387 "#)
3388 .await
3389 .expect("case failed");
3390
3391 assert!(result.ok());
3392 assert_eq!(result.out.trim(), "yes-like");
3393 }
3394
3395 #[tokio::test]
3400 async fn test_cat_from_pipeline() {
3401 let kernel = Kernel::transient().expect("failed to create kernel");
3402
3403 let result = kernel
3404 .execute(r#"echo "piped text" | cat"#)
3405 .await
3406 .expect("cat pipeline failed");
3407
3408 assert!(result.ok(), "cat failed: {}", result.err);
3409 assert_eq!(result.out.trim(), "piped text");
3410 }
3411
3412 #[tokio::test]
3413 async fn test_cat_from_pipeline_multiline() {
3414 let kernel = Kernel::transient().expect("failed to create kernel");
3415
3416 let result = kernel
3417 .execute(r#"echo "line1\nline2" | cat -n"#)
3418 .await
3419 .expect("cat pipeline failed");
3420
3421 assert!(result.ok(), "cat failed: {}", result.err);
3422 assert!(result.out.contains("1\t"), "output: {}", result.out);
3423 }
3424
3425 #[tokio::test]
3430 async fn test_heredoc_basic() {
3431 let kernel = Kernel::transient().expect("failed to create kernel");
3432
3433 let result = kernel
3434 .execute("cat <<EOF\nhello\nEOF")
3435 .await
3436 .expect("heredoc failed");
3437
3438 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3439 assert_eq!(result.out.trim(), "hello");
3440 }
3441
3442 #[tokio::test]
3443 async fn test_arithmetic_in_string() {
3444 let kernel = Kernel::transient().expect("failed to create kernel");
3445
3446 let result = kernel
3447 .execute(r#"echo "result: $((1 + 2))""#)
3448 .await
3449 .expect("arithmetic in string failed");
3450
3451 assert!(result.ok(), "echo failed: {}", result.err);
3452 assert_eq!(result.out.trim(), "result: 3");
3453 }
3454
3455 #[tokio::test]
3456 async fn test_heredoc_multiline() {
3457 let kernel = Kernel::transient().expect("failed to create kernel");
3458
3459 let result = kernel
3460 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
3461 .await
3462 .expect("heredoc failed");
3463
3464 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3465 assert!(result.out.contains("line1"), "output: {}", result.out);
3466 assert!(result.out.contains("line2"), "output: {}", result.out);
3467 assert!(result.out.contains("line3"), "output: {}", result.out);
3468 }
3469
3470 #[tokio::test]
3471 async fn test_heredoc_variable_expansion() {
3472 let kernel = Kernel::transient().expect("failed to create kernel");
3474
3475 kernel.execute("GREETING=hello").await.expect("set var");
3476
3477 let result = kernel
3478 .execute("cat <<EOF\n$GREETING world\nEOF")
3479 .await
3480 .expect("heredoc expansion failed");
3481
3482 assert!(result.ok(), "heredoc expansion failed: {}", result.err);
3483 assert_eq!(result.out.trim(), "hello world");
3484 }
3485
3486 #[tokio::test]
3487 async fn test_heredoc_quoted_no_expansion() {
3488 let kernel = Kernel::transient().expect("failed to create kernel");
3490
3491 kernel.execute("GREETING=hello").await.expect("set var");
3492
3493 let result = kernel
3494 .execute("cat <<'EOF'\n$GREETING world\nEOF")
3495 .await
3496 .expect("quoted heredoc failed");
3497
3498 assert!(result.ok(), "quoted heredoc failed: {}", result.err);
3499 assert_eq!(result.out.trim(), "$GREETING world");
3500 }
3501
3502 #[tokio::test]
3503 async fn test_heredoc_default_value_expansion() {
3504 let kernel = Kernel::transient().expect("failed to create kernel");
3506
3507 let result = kernel
3508 .execute("cat <<EOF\n${UNSET:-fallback}\nEOF")
3509 .await
3510 .expect("heredoc default expansion failed");
3511
3512 assert!(result.ok(), "heredoc default expansion failed: {}", result.err);
3513 assert_eq!(result.out.trim(), "fallback");
3514 }
3515
3516 #[tokio::test]
3521 async fn test_read_from_pipeline() {
3522 let kernel = Kernel::transient().expect("failed to create kernel");
3523
3524 let result = kernel
3526 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
3527 .await
3528 .expect("read pipeline failed");
3529
3530 assert!(result.ok(), "read failed: {}", result.err);
3531 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
3532 }
3533
3534 #[tokio::test]
3535 async fn test_read_multiple_vars_from_pipeline() {
3536 let kernel = Kernel::transient().expect("failed to create kernel");
3537
3538 let result = kernel
3539 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
3540 .await
3541 .expect("read pipeline failed");
3542
3543 assert!(result.ok(), "read failed: {}", result.err);
3544 assert!(result.out.contains("John is 42"), "output: {}", result.out);
3545 }
3546
3547 #[tokio::test]
3552 async fn test_posix_function_with_positional_params() {
3553 let kernel = Kernel::transient().expect("failed to create kernel");
3554
3555 kernel
3557 .execute(r#"greet() { echo "Hello, $1!" }"#)
3558 .await
3559 .expect("function definition failed");
3560
3561 let result = kernel
3563 .execute(r#"greet "Amy""#)
3564 .await
3565 .expect("function call failed");
3566
3567 assert!(result.ok(), "greet failed: {}", result.err);
3568 assert_eq!(result.out.trim(), "Hello, Amy!");
3569 }
3570
3571 #[tokio::test]
3572 async fn test_posix_function_multiple_args() {
3573 let kernel = Kernel::transient().expect("failed to create kernel");
3574
3575 kernel
3577 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
3578 .await
3579 .expect("function definition failed");
3580
3581 let result = kernel
3583 .execute(r#"add_greeting "Hello" "World""#)
3584 .await
3585 .expect("function call failed");
3586
3587 assert!(result.ok(), "function failed: {}", result.err);
3588 assert_eq!(result.out.trim(), "Hello World!");
3589 }
3590
3591 #[tokio::test]
3592 async fn test_bash_function_with_positional_params() {
3593 let kernel = Kernel::transient().expect("failed to create kernel");
3594
3595 kernel
3597 .execute(r#"function greet { echo "Hi $1" }"#)
3598 .await
3599 .expect("function definition failed");
3600
3601 let result = kernel
3603 .execute(r#"greet "Bob""#)
3604 .await
3605 .expect("function call failed");
3606
3607 assert!(result.ok(), "greet failed: {}", result.err);
3608 assert_eq!(result.out.trim(), "Hi Bob");
3609 }
3610
3611 #[tokio::test]
3612 async fn test_shell_function_with_all_args() {
3613 let kernel = Kernel::transient().expect("failed to create kernel");
3614
3615 kernel
3617 .execute(r#"echo_all() { echo "args: $@" }"#)
3618 .await
3619 .expect("function definition failed");
3620
3621 let result = kernel
3623 .execute(r#"echo_all "a" "b" "c""#)
3624 .await
3625 .expect("function call failed");
3626
3627 assert!(result.ok(), "function failed: {}", result.err);
3628 assert_eq!(result.out.trim(), "args: a b c");
3629 }
3630
3631 #[tokio::test]
3632 async fn test_shell_function_with_arg_count() {
3633 let kernel = Kernel::transient().expect("failed to create kernel");
3634
3635 kernel
3637 .execute(r#"count_args() { echo "count: $#" }"#)
3638 .await
3639 .expect("function definition failed");
3640
3641 let result = kernel
3643 .execute(r#"count_args "x" "y" "z""#)
3644 .await
3645 .expect("function call failed");
3646
3647 assert!(result.ok(), "function failed: {}", result.err);
3648 assert_eq!(result.out.trim(), "count: 3");
3649 }
3650
3651 #[tokio::test]
3652 async fn test_shell_function_shared_scope() {
3653 let kernel = Kernel::transient().expect("failed to create kernel");
3654
3655 kernel
3657 .execute(r#"PARENT_VAR="visible""#)
3658 .await
3659 .expect("set failed");
3660
3661 kernel
3663 .execute(r#"modify_parent() {
3664 echo "saw: ${PARENT_VAR}"
3665 PARENT_VAR="changed by function"
3666 }"#)
3667 .await
3668 .expect("function definition failed");
3669
3670 let result = kernel.execute("modify_parent").await.expect("function failed");
3672
3673 assert!(
3674 result.out.contains("visible"),
3675 "Shell function should access parent scope, got: {}",
3676 result.out
3677 );
3678
3679 let var = kernel.get_var("PARENT_VAR").await;
3681 assert_eq!(
3682 var,
3683 Some(Value::String("changed by function".into())),
3684 "Shell function should modify parent scope"
3685 );
3686 }
3687
3688 #[tokio::test]
3693 async fn test_script_execution_from_path() {
3694 let kernel = Kernel::transient().expect("failed to create kernel");
3695
3696 kernel.execute(r#"mkdir "/bin""#).await.ok();
3698 kernel
3699 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
3700 .await
3701 .expect("write script failed");
3702
3703 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3705
3706 let result = kernel
3708 .execute("hello")
3709 .await
3710 .expect("script execution failed");
3711
3712 assert!(result.ok(), "script failed: {}", result.err);
3713 assert_eq!(result.out.trim(), "Hello from script!");
3714 }
3715
3716 #[tokio::test]
3717 async fn test_script_with_args() {
3718 let kernel = Kernel::transient().expect("failed to create kernel");
3719
3720 kernel.execute(r#"mkdir "/bin""#).await.ok();
3722 kernel
3723 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
3724 .await
3725 .expect("write script failed");
3726
3727 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3729
3730 let result = kernel
3732 .execute(r#"greet "World""#)
3733 .await
3734 .expect("script execution failed");
3735
3736 assert!(result.ok(), "script failed: {}", result.err);
3737 assert_eq!(result.out.trim(), "Hello, World!");
3738 }
3739
3740 #[tokio::test]
3741 async fn test_script_not_found() {
3742 let kernel = Kernel::transient().expect("failed to create kernel");
3743
3744 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
3746
3747 let result = kernel
3749 .execute("noscript")
3750 .await
3751 .expect("execution failed");
3752
3753 assert!(!result.ok(), "should fail with command not found");
3754 assert_eq!(result.code, 127);
3755 assert!(result.err.contains("command not found"));
3756 }
3757
3758 #[tokio::test]
3759 async fn test_script_path_search_order() {
3760 let kernel = Kernel::transient().expect("failed to create kernel");
3761
3762 kernel.execute(r#"mkdir "/first""#).await.ok();
3765 kernel.execute(r#"mkdir "/second""#).await.ok();
3766 kernel
3767 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
3768 .await
3769 .expect("write failed");
3770 kernel
3771 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
3772 .await
3773 .expect("write failed");
3774
3775 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
3777
3778 let result = kernel
3780 .execute("myscript")
3781 .await
3782 .expect("script execution failed");
3783
3784 assert!(result.ok(), "script failed: {}", result.err);
3785 assert_eq!(result.out.trim(), "from first");
3786 }
3787
3788 #[tokio::test]
3793 async fn test_last_exit_code_success() {
3794 let kernel = Kernel::transient().expect("failed to create kernel");
3795
3796 let result = kernel.execute("true; echo $?").await.expect("execution failed");
3798 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
3799 }
3800
3801 #[tokio::test]
3802 async fn test_last_exit_code_failure() {
3803 let kernel = Kernel::transient().expect("failed to create kernel");
3804
3805 let result = kernel.execute("false; echo $?").await.expect("execution failed");
3807 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
3808 }
3809
3810 #[tokio::test]
3811 async fn test_current_pid() {
3812 let kernel = Kernel::transient().expect("failed to create kernel");
3813
3814 let result = kernel.execute("echo $$").await.expect("execution failed");
3815 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
3817 assert!(pid > 0, "PID should be positive");
3818 }
3819
3820 #[tokio::test]
3821 async fn test_unset_variable_expands_to_empty() {
3822 let kernel = Kernel::transient().expect("failed to create kernel");
3823
3824 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
3826 assert_eq!(result.out.trim(), "prefix::suffix");
3827 }
3828
3829 #[tokio::test]
3830 async fn test_eq_ne_operators() {
3831 let kernel = Kernel::transient().expect("failed to create kernel");
3832
3833 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
3835 assert_eq!(result.out.trim(), "eq works");
3836
3837 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
3839 assert_eq!(result.out.trim(), "ne works");
3840
3841 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
3843 assert_eq!(result.out.trim(), "correct");
3844 }
3845
3846 #[tokio::test]
3847 async fn test_escaped_dollar_in_string() {
3848 let kernel = Kernel::transient().expect("failed to create kernel");
3849
3850 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
3852 assert_eq!(result.out.trim(), "$100");
3853 }
3854
3855 #[tokio::test]
3856 async fn test_special_vars_in_interpolation() {
3857 let kernel = Kernel::transient().expect("failed to create kernel");
3858
3859 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
3861 assert_eq!(result.out.trim(), "exit: 0");
3862
3863 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
3865 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
3866 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
3867 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
3868 }
3869
3870 #[tokio::test]
3875 async fn test_command_subst_assignment() {
3876 let kernel = Kernel::transient().expect("failed to create kernel");
3877
3878 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
3880 assert_eq!(result.out.trim(), "hello");
3881 }
3882
3883 #[tokio::test]
3884 async fn test_command_subst_with_args() {
3885 let kernel = Kernel::transient().expect("failed to create kernel");
3886
3887 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
3889 assert_eq!(result.out.trim(), "a b c");
3890 }
3891
3892 #[tokio::test]
3893 async fn test_command_subst_nested_vars() {
3894 let kernel = Kernel::transient().expect("failed to create kernel");
3895
3896 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
3898 assert_eq!(result.out.trim(), "hello world");
3899 }
3900
3901 #[tokio::test]
3902 async fn test_background_job_basic() {
3903 use std::time::Duration;
3904
3905 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
3906
3907 let result = kernel.execute("echo hello &").await.expect("execution failed");
3909 assert!(result.ok(), "background command should succeed: {}", result.err);
3910 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
3911
3912 tokio::time::sleep(Duration::from_millis(100)).await;
3914
3915 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
3917 assert!(status.ok(), "status should succeed: {}", status.err);
3918 assert!(
3919 status.out.contains("done:") || status.out.contains("running"),
3920 "should have valid status: {}",
3921 status.out
3922 );
3923
3924 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
3926 assert!(stdout.ok());
3927 assert!(stdout.out.contains("hello"));
3928 }
3929
3930 #[tokio::test]
3931 async fn test_heredoc_piped_to_command() {
3932 let kernel = Kernel::transient().expect("kernel");
3934 let result = kernel.execute("cat <<EOF | cat\nhello world\nEOF").await.expect("exec");
3935 assert!(result.ok(), "heredoc | cat failed: {}", result.err);
3936 assert_eq!(result.out.trim(), "hello world");
3937 }
3938
3939 #[tokio::test]
3940 async fn test_for_loop_glob_iterates() {
3941 let kernel = Kernel::transient().expect("kernel");
3943 kernel.execute("echo a > /tmp/kaish_glob_test_a.txt").await.unwrap();
3944 kernel.execute("echo b > /tmp/kaish_glob_test_b.txt").await.unwrap();
3945 let result = kernel.execute(r#"
3946 N=0
3947 for F in $(glob "/tmp/kaish_glob_test_*.txt"); do
3948 N=$((N + 1))
3949 done
3950 echo $N
3951 "#).await.unwrap();
3952 assert!(result.ok(), "for glob failed: {}", result.err);
3953 assert_eq!(result.out.trim(), "2", "Should iterate 2 files, got: {}", result.out);
3954 kernel.execute("rm /tmp/kaish_glob_test_a.txt").await.unwrap();
3955 kernel.execute("rm /tmp/kaish_glob_test_b.txt").await.unwrap();
3956 }
3957
3958 #[tokio::test]
3959 async fn test_command_subst_echo_not_iterable() {
3960 let kernel = Kernel::transient().expect("kernel");
3962 let result = kernel.execute(r#"
3963 N=0
3964 for X in $(echo "a b c"); do N=$((N + 1)); done
3965 echo $N
3966 "#).await.unwrap();
3967 assert!(result.ok());
3968 assert_eq!(result.out.trim(), "1", "echo should be one item: {}", result.out);
3969 }
3970
3971}