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 backend = self.exec_ctx.read().await.backend.clone();
1128 let tool_schema = backend.get_tool(name).await.ok().flatten().map(|t| {
1129 let mut s = t.schema;
1130 s.map_positionals = true;
1131 s
1132 });
1133 let tool_args = self.build_args_async(args, tool_schema.as_ref()).await?;
1134 let mut ctx = self.exec_ctx.write().await;
1135 {
1136 let scope = self.scope.read().await;
1137 ctx.scope = scope.clone();
1138 }
1139 let backend = ctx.backend.clone();
1140 match backend.call_tool(name, tool_args, &mut ctx).await {
1141 Ok(tool_result) => {
1142 let mut scope = self.scope.write().await;
1143 *scope = ctx.scope.clone();
1144 let mut exec = ExecResult::from_output(
1145 tool_result.code as i64, tool_result.stdout, tool_result.stderr,
1146 );
1147 exec.output = tool_result.output;
1148 return Ok(exec);
1149 }
1150 Err(BackendError::ToolNotFound(_)) => {
1151 }
1153 Err(e) => {
1154 return Ok(ExecResult::failure(1, e.to_string()));
1155 }
1156 }
1157
1158 return Ok(ExecResult::failure(127, format!("command not found: {}", name)));
1159 }
1160 };
1161
1162 let schema = tool.schema();
1164 let mut tool_args = self.build_args_async(args, Some(&schema)).await?;
1165 let output_format = extract_output_format(&mut tool_args, Some(&schema));
1166
1167 let mut ctx = self.exec_ctx.write().await;
1169 {
1170 let scope = self.scope.read().await;
1171 ctx.scope = scope.clone();
1172 }
1173
1174 let result = tool.execute(tool_args, &mut ctx).await;
1175
1176 {
1178 let mut scope = self.scope.write().await;
1179 *scope = ctx.scope.clone();
1180 }
1181
1182 let result = match output_format {
1183 Some(format) => apply_output_format(result, format),
1184 None => result,
1185 };
1186
1187 Ok(result)
1188 }
1189
1190 async fn build_args_async(&self, args: &[Arg], schema: Option<&crate::tools::ToolSchema>) -> Result<ToolArgs> {
1194 let mut tool_args = ToolArgs::new();
1195 let param_lookup = schema.map(schema_param_lookup).unwrap_or_default();
1196
1197 let mut consumed: std::collections::HashSet<usize> = std::collections::HashSet::new();
1199 let mut past_double_dash = false;
1200
1201 let positional_indices: Vec<usize> = args.iter().enumerate()
1203 .filter_map(|(i, a)| matches!(a, Arg::Positional(_)).then_some(i))
1204 .collect();
1205
1206 let mut i = 0;
1207 while i < args.len() {
1208 match &args[i] {
1209 Arg::DoubleDash => {
1210 past_double_dash = true;
1211 }
1212 Arg::Positional(expr) => {
1213 if !consumed.contains(&i) {
1214 let value = self.eval_expr_async(expr).await?;
1215 let value = apply_tilde_expansion(value);
1216 tool_args.positional.push(value);
1217 }
1218 }
1219 Arg::Named { key, value } => {
1220 let val = self.eval_expr_async(value).await?;
1221 let val = apply_tilde_expansion(val);
1222 tool_args.named.insert(key.clone(), val);
1223 }
1224 Arg::ShortFlag(name) => {
1225 if past_double_dash {
1226 tool_args.positional.push(Value::String(format!("-{name}")));
1227 } else if name.len() == 1 {
1228 let flag_name = name.as_str();
1229 let lookup = param_lookup.get(flag_name);
1230 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1231
1232 if is_bool {
1233 tool_args.flags.insert(flag_name.to_string());
1234 } else {
1235 let canonical = lookup.map(|(name, _)| *name).unwrap_or(flag_name);
1237 let next_pos = positional_indices.iter()
1238 .find(|idx| **idx > i && !consumed.contains(idx));
1239
1240 if let Some(&pos_idx) = next_pos {
1241 if let Arg::Positional(expr) = &args[pos_idx] {
1242 let value = self.eval_expr_async(expr).await?;
1243 let value = apply_tilde_expansion(value);
1244 tool_args.named.insert(canonical.to_string(), value);
1245 consumed.insert(pos_idx);
1246 }
1247 } else {
1248 tool_args.flags.insert(flag_name.to_string());
1249 }
1250 }
1251 } else {
1252 for c in name.chars() {
1254 tool_args.flags.insert(c.to_string());
1255 }
1256 }
1257 }
1258 Arg::LongFlag(name) => {
1259 if past_double_dash {
1260 tool_args.positional.push(Value::String(format!("--{name}")));
1261 } else {
1262 let lookup = param_lookup.get(name.as_str());
1263 let is_bool = lookup.map(|(_, typ)| is_bool_type(typ)).unwrap_or(true);
1264
1265 if is_bool {
1266 tool_args.flags.insert(name.clone());
1267 } else {
1268 let canonical = lookup.map(|(name, _)| *name).unwrap_or(name.as_str());
1269 let next_pos = positional_indices.iter()
1270 .find(|idx| **idx > i && !consumed.contains(idx));
1271
1272 if let Some(&pos_idx) = next_pos {
1273 if let Arg::Positional(expr) = &args[pos_idx] {
1274 let value = self.eval_expr_async(expr).await?;
1275 let value = apply_tilde_expansion(value);
1276 tool_args.named.insert(canonical.to_string(), value);
1277 consumed.insert(pos_idx);
1278 }
1279 } else {
1280 tool_args.flags.insert(name.clone());
1281 }
1282 }
1283 }
1284 }
1285 }
1286 i += 1;
1287 }
1288
1289 if let Some(schema) = schema.filter(|s| s.map_positionals) {
1294 let pre_dash_count = if past_double_dash {
1295 let dash_pos = args.iter().position(|a| matches!(a, Arg::DoubleDash)).unwrap_or(args.len());
1296 positional_indices.iter()
1297 .filter(|idx| **idx < dash_pos && !consumed.contains(idx))
1298 .count()
1299 } else {
1300 tool_args.positional.len()
1301 };
1302
1303 let mut remaining = Vec::new();
1304 let mut positional_iter = tool_args.positional.drain(..).enumerate();
1305
1306 for param in &schema.params {
1307 if tool_args.named.contains_key(¶m.name) || tool_args.flags.contains(¶m.name) {
1308 continue;
1309 }
1310 if is_bool_type(¶m.param_type) {
1311 continue;
1312 }
1313 loop {
1314 match positional_iter.next() {
1315 Some((idx, val)) if idx < pre_dash_count => {
1316 tool_args.named.insert(param.name.clone(), val);
1317 break;
1318 }
1319 Some((_, val)) => {
1320 remaining.push(val);
1321 }
1322 None => break,
1323 }
1324 }
1325 }
1326
1327 remaining.extend(positional_iter.map(|(_, v)| v));
1328 tool_args.positional = remaining;
1329 }
1330
1331 Ok(tool_args)
1332 }
1333
1334 async fn build_args_flat(&self, args: &[Arg]) -> Result<Vec<String>> {
1344 let mut argv = Vec::new();
1345 for arg in args {
1346 match arg {
1347 Arg::Positional(expr) => {
1348 let value = self.eval_expr_async(expr).await?;
1349 let value = apply_tilde_expansion(value);
1350 argv.push(value_to_string(&value));
1351 }
1352 Arg::Named { key, value } => {
1353 let val = self.eval_expr_async(value).await?;
1354 let val = apply_tilde_expansion(val);
1355 argv.push(format!("{}={}", key, value_to_string(&val)));
1356 }
1357 Arg::ShortFlag(name) => {
1358 argv.push(format!("-{}", name));
1360 }
1361 Arg::LongFlag(name) => {
1362 argv.push(format!("--{}", name));
1364 }
1365 Arg::DoubleDash => {
1366 argv.push("--".to_string());
1368 }
1369 }
1370 }
1371 Ok(argv)
1372 }
1373
1374 fn eval_expr_async<'a>(&'a self, expr: &'a Expr) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value>> + Send + 'a>> {
1379 Box::pin(async move {
1380 match expr {
1381 Expr::Literal(value) => Ok(value.clone()),
1382 Expr::VarRef(path) => {
1383 let scope = self.scope.read().await;
1384 scope.resolve_path(path)
1385 .ok_or_else(|| anyhow::anyhow!("undefined variable"))
1386 }
1387 Expr::Interpolated(parts) => {
1388 let mut result = String::new();
1389 for part in parts {
1390 result.push_str(&self.eval_string_part_async(part).await?);
1391 }
1392 Ok(Value::String(result))
1393 }
1394 Expr::BinaryOp { left, op, right } => {
1395 match op {
1396 BinaryOp::And => {
1397 let left_val = self.eval_expr_async(left).await?;
1398 if !is_truthy(&left_val) {
1399 return Ok(left_val);
1400 }
1401 self.eval_expr_async(right).await
1402 }
1403 BinaryOp::Or => {
1404 let left_val = self.eval_expr_async(left).await?;
1405 if is_truthy(&left_val) {
1406 return Ok(left_val);
1407 }
1408 self.eval_expr_async(right).await
1409 }
1410 _ => {
1411 let mut scope = self.scope.write().await;
1413 eval_expr(expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1414 }
1415 }
1416 }
1417 Expr::CommandSubst(pipeline) => {
1418 let saved_scope = { self.scope.read().await.clone() };
1421 let saved_cwd = {
1422 let ec = self.exec_ctx.read().await;
1423 (ec.cwd.clone(), ec.prev_cwd.clone())
1424 };
1425
1426 let run_result = self.execute_pipeline(pipeline).await;
1428
1429 {
1431 let mut scope = self.scope.write().await;
1432 *scope = saved_scope;
1433 if let Ok(ref r) = run_result {
1434 scope.set_last_result(r.clone());
1435 }
1436 }
1437 {
1438 let mut ec = self.exec_ctx.write().await;
1439 ec.cwd = saved_cwd.0;
1440 ec.prev_cwd = saved_cwd.1;
1441 }
1442
1443 let result = run_result?;
1445
1446 if let Some(data) = &result.data {
1448 Ok(data.clone())
1449 } else if let Some(ref output) = result.output {
1450 if output.is_flat() && !output.is_simple_text() && !output.root.is_empty() {
1452 let items: Vec<serde_json::Value> = output.root.iter()
1453 .map(|n| serde_json::Value::String(n.display_name().to_string()))
1454 .collect();
1455 Ok(Value::Json(serde_json::Value::Array(items)))
1456 } else {
1457 Ok(Value::String(result.out.trim_end().to_string()))
1458 }
1459 } else {
1460 Ok(Value::String(result.out.trim_end().to_string()))
1462 }
1463 }
1464 Expr::Test(test_expr) => {
1465 let expr = Expr::Test(test_expr.clone());
1467 let mut scope = self.scope.write().await;
1468 eval_expr(&expr, &mut scope).map_err(|e| anyhow::anyhow!("{}", e))
1469 }
1470 Expr::Positional(n) => {
1471 let scope = self.scope.read().await;
1472 match scope.get_positional(*n) {
1473 Some(s) => Ok(Value::String(s.to_string())),
1474 None => Ok(Value::String(String::new())),
1475 }
1476 }
1477 Expr::AllArgs => {
1478 let scope = self.scope.read().await;
1479 Ok(Value::String(scope.all_args().join(" ")))
1480 }
1481 Expr::ArgCount => {
1482 let scope = self.scope.read().await;
1483 Ok(Value::Int(scope.arg_count() as i64))
1484 }
1485 Expr::VarLength(name) => {
1486 let scope = self.scope.read().await;
1487 match scope.get(name) {
1488 Some(value) => Ok(Value::Int(value_to_string(value).len() as i64)),
1489 None => Ok(Value::Int(0)),
1490 }
1491 }
1492 Expr::VarWithDefault { name, default } => {
1493 let scope = self.scope.read().await;
1494 let use_default = match scope.get(name) {
1495 Some(value) => value_to_string(value).is_empty(),
1496 None => true,
1497 };
1498 drop(scope); if use_default {
1500 self.eval_string_parts_async(default).await.map(Value::String)
1502 } else {
1503 let scope = self.scope.read().await;
1504 scope.get(name).cloned().ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))
1505 }
1506 }
1507 Expr::Arithmetic(expr_str) => {
1508 let scope = self.scope.read().await;
1509 crate::arithmetic::eval_arithmetic(expr_str, &scope)
1510 .map(Value::Int)
1511 .map_err(|e| anyhow::anyhow!("arithmetic error: {}", e))
1512 }
1513 Expr::Command(cmd) => {
1514 let result = self.execute_command(&cmd.name, &cmd.args).await?;
1516 Ok(Value::Bool(result.code == 0))
1517 }
1518 Expr::LastExitCode => {
1519 let scope = self.scope.read().await;
1520 Ok(Value::Int(scope.last_result().code))
1521 }
1522 Expr::CurrentPid => {
1523 let scope = self.scope.read().await;
1524 Ok(Value::Int(scope.pid() as i64))
1525 }
1526 }
1527 })
1528 }
1529
1530 fn eval_string_parts_async<'a>(&'a self, parts: &'a [StringPart]) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1532 Box::pin(async move {
1533 let mut result = String::new();
1534 for part in parts {
1535 result.push_str(&self.eval_string_part_async(part).await?);
1536 }
1537 Ok(result)
1538 })
1539 }
1540
1541 fn eval_string_part_async<'a>(&'a self, part: &'a StringPart) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<String>> + Send + 'a>> {
1543 Box::pin(async move {
1544 match part {
1545 StringPart::Literal(s) => Ok(s.clone()),
1546 StringPart::Var(path) => {
1547 let scope = self.scope.read().await;
1548 match scope.resolve_path(path) {
1549 Some(value) => Ok(value_to_string(&value)),
1550 None => Ok(String::new()), }
1552 }
1553 StringPart::VarWithDefault { name, default } => {
1554 let scope = self.scope.read().await;
1555 let use_default = match scope.get(name) {
1556 Some(value) => value_to_string(value).is_empty(),
1557 None => true,
1558 };
1559 drop(scope); if use_default {
1561 self.eval_string_parts_async(default).await
1563 } else {
1564 let scope = self.scope.read().await;
1565 Ok(value_to_string(scope.get(name).ok_or_else(|| anyhow::anyhow!("variable '{}' not found", name))?))
1566 }
1567 }
1568 StringPart::VarLength(name) => {
1569 let scope = self.scope.read().await;
1570 match scope.get(name) {
1571 Some(value) => Ok(value_to_string(value).len().to_string()),
1572 None => Ok("0".to_string()),
1573 }
1574 }
1575 StringPart::Positional(n) => {
1576 let scope = self.scope.read().await;
1577 match scope.get_positional(*n) {
1578 Some(s) => Ok(s.to_string()),
1579 None => Ok(String::new()),
1580 }
1581 }
1582 StringPart::AllArgs => {
1583 let scope = self.scope.read().await;
1584 Ok(scope.all_args().join(" "))
1585 }
1586 StringPart::ArgCount => {
1587 let scope = self.scope.read().await;
1588 Ok(scope.arg_count().to_string())
1589 }
1590 StringPart::Arithmetic(expr) => {
1591 let scope = self.scope.read().await;
1592 match crate::arithmetic::eval_arithmetic(expr, &scope) {
1593 Ok(value) => Ok(value.to_string()),
1594 Err(_) => Ok(String::new()),
1595 }
1596 }
1597 StringPart::CommandSubst(pipeline) => {
1598 let saved_scope = { self.scope.read().await.clone() };
1601 let saved_cwd = {
1602 let ec = self.exec_ctx.read().await;
1603 (ec.cwd.clone(), ec.prev_cwd.clone())
1604 };
1605
1606 let run_result = self.execute_pipeline(pipeline).await;
1608
1609 {
1611 let mut scope = self.scope.write().await;
1612 *scope = saved_scope;
1613 if let Ok(ref r) = run_result {
1614 scope.set_last_result(r.clone());
1615 }
1616 }
1617 {
1618 let mut ec = self.exec_ctx.write().await;
1619 ec.cwd = saved_cwd.0;
1620 ec.prev_cwd = saved_cwd.1;
1621 }
1622
1623 let result = run_result?;
1625
1626 Ok(result.out.trim_end_matches('\n').to_string())
1627 }
1628 StringPart::LastExitCode => {
1629 let scope = self.scope.read().await;
1630 Ok(scope.last_result().code.to_string())
1631 }
1632 StringPart::CurrentPid => {
1633 let scope = self.scope.read().await;
1634 Ok(scope.pid().to_string())
1635 }
1636 }
1637 })
1638 }
1639
1640 async fn update_last_result(&self, result: &ExecResult) {
1642 let mut scope = self.scope.write().await;
1643 scope.set_last_result(result.clone());
1644 }
1645
1646 async fn execute_user_tool(&self, def: ToolDef, args: &[Arg]) -> Result<ExecResult> {
1652 let tool_args = self.build_args_async(args, None).await?;
1654
1655 {
1657 let mut scope = self.scope.write().await;
1658 scope.push_frame();
1659 }
1660
1661 let saved_positional = {
1663 let mut scope = self.scope.write().await;
1664 let saved = scope.save_positional();
1665
1666 let positional_args: Vec<String> = tool_args.positional
1668 .iter()
1669 .map(value_to_string)
1670 .collect();
1671 scope.set_positional(&def.name, positional_args);
1672
1673 saved
1674 };
1675
1676 let mut accumulated_out = String::new();
1679 let mut accumulated_err = String::new();
1680 let mut last_code = 0i64;
1681 let mut last_data: Option<Value> = None;
1682
1683 let mut exec_error: Option<anyhow::Error> = None;
1685 let mut exit_code: Option<i64> = None;
1686
1687 for stmt in &def.body {
1688 match self.execute_stmt_flow(stmt).await {
1689 Ok(flow) => {
1690 match flow {
1691 ControlFlow::Normal(r) => {
1692 accumulated_out.push_str(&r.out);
1693 accumulated_err.push_str(&r.err);
1694 last_code = r.code;
1695 last_data = r.data;
1696 }
1697 ControlFlow::Return { value } => {
1698 accumulated_out.push_str(&value.out);
1699 accumulated_err.push_str(&value.err);
1700 last_code = value.code;
1701 last_data = value.data;
1702 break;
1703 }
1704 ControlFlow::Exit { code } => {
1705 exit_code = Some(code);
1706 break;
1707 }
1708 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1709 accumulated_out.push_str(&r.out);
1710 accumulated_err.push_str(&r.err);
1711 last_code = r.code;
1712 last_data = r.data;
1713 }
1714 }
1715 }
1716 Err(e) => {
1717 exec_error = Some(e);
1718 break;
1719 }
1720 }
1721 }
1722
1723 {
1725 let mut scope = self.scope.write().await;
1726 scope.pop_frame();
1727 scope.set_positional(saved_positional.0, saved_positional.1);
1728 }
1729
1730 if let Some(e) = exec_error {
1732 return Err(e);
1733 }
1734 if let Some(code) = exit_code {
1735 return Ok(ExecResult {
1736 code,
1737 out: accumulated_out,
1738 err: accumulated_err,
1739 data: last_data,
1740 output: None,
1741 });
1742 }
1743
1744 Ok(ExecResult {
1745 code: last_code,
1746 out: accumulated_out,
1747 err: accumulated_err,
1748 data: last_data,
1749 output: None,
1750 })
1751 }
1752
1753 async fn execute_source(&self, args: &[Arg]) -> Result<ExecResult> {
1758 let tool_args = self.build_args_async(args, None).await?;
1760 let path = match tool_args.positional.first() {
1761 Some(Value::String(s)) => s.clone(),
1762 Some(v) => value_to_string(v),
1763 None => {
1764 return Ok(ExecResult::failure(1, "source: missing filename"));
1765 }
1766 };
1767
1768 let full_path = {
1770 let ctx = self.exec_ctx.read().await;
1771 if path.starts_with('/') {
1772 std::path::PathBuf::from(&path)
1773 } else {
1774 ctx.cwd.join(&path)
1775 }
1776 };
1777
1778 let content = {
1780 let ctx = self.exec_ctx.read().await;
1781 match ctx.backend.read(&full_path, None).await {
1782 Ok(bytes) => {
1783 String::from_utf8(bytes).map_err(|e| {
1784 anyhow::anyhow!("source: {}: invalid UTF-8: {}", path, e)
1785 })?
1786 }
1787 Err(e) => {
1788 return Ok(ExecResult::failure(
1789 1,
1790 format!("source: {}: {}", path, e),
1791 ));
1792 }
1793 }
1794 };
1795
1796 let program = match crate::parser::parse(&content) {
1798 Ok(p) => p,
1799 Err(errors) => {
1800 let msg = errors
1801 .iter()
1802 .map(|e| format!("{}:{}: {}", path, e.span.start, e.message))
1803 .collect::<Vec<_>>()
1804 .join("\n");
1805 return Ok(ExecResult::failure(1, format!("source: {}", msg)));
1806 }
1807 };
1808
1809 let mut result = ExecResult::success("");
1811 for stmt in program.statements {
1812 if matches!(stmt, crate::ast::Stmt::Empty) {
1813 continue;
1814 }
1815
1816 match self.execute_stmt_flow(&stmt).await {
1817 Ok(flow) => {
1818 match flow {
1819 ControlFlow::Normal(r) => {
1820 result = r.clone();
1821 self.update_last_result(&r).await;
1822 }
1823 ControlFlow::Break { .. } | ControlFlow::Continue { .. } => {
1824 return Err(anyhow::anyhow!(
1826 "source: {}: unexpected break/continue outside loop",
1827 path
1828 ));
1829 }
1830 ControlFlow::Return { value } => {
1831 return Ok(value);
1833 }
1834 ControlFlow::Exit { code } => {
1835 result.code = code;
1837 return Ok(result);
1838 }
1839 }
1840 }
1841 Err(e) => {
1842 return Err(e.context(format!("source: {}", path)));
1843 }
1844 }
1845 }
1846
1847 Ok(result)
1848 }
1849
1850 async fn try_execute_script(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
1855 let path_value = {
1857 let scope = self.scope.read().await;
1858 scope
1859 .get("PATH")
1860 .map(value_to_string)
1861 .unwrap_or_else(|| "/bin".to_string())
1862 };
1863
1864 for dir in path_value.split(':') {
1866 if dir.is_empty() {
1867 continue;
1868 }
1869
1870 let script_path = PathBuf::from(dir).join(format!("{}.kai", name));
1872
1873 let exists = {
1875 let ctx = self.exec_ctx.read().await;
1876 ctx.backend.exists(&script_path).await
1877 };
1878
1879 if !exists {
1880 continue;
1881 }
1882
1883 let content = {
1885 let ctx = self.exec_ctx.read().await;
1886 match ctx.backend.read(&script_path, None).await {
1887 Ok(bytes) => match String::from_utf8(bytes) {
1888 Ok(s) => s,
1889 Err(e) => {
1890 return Ok(Some(ExecResult::failure(
1891 1,
1892 format!("{}: invalid UTF-8: {}", script_path.display(), e),
1893 )));
1894 }
1895 },
1896 Err(e) => {
1897 return Ok(Some(ExecResult::failure(
1898 1,
1899 format!("{}: {}", script_path.display(), e),
1900 )));
1901 }
1902 }
1903 };
1904
1905 let program = match crate::parser::parse(&content) {
1907 Ok(p) => p,
1908 Err(errors) => {
1909 let msg = errors
1910 .iter()
1911 .map(|e| format!("{}:{}: {}", script_path.display(), e.span.start, e.message))
1912 .collect::<Vec<_>>()
1913 .join("\n");
1914 return Ok(Some(ExecResult::failure(1, msg)));
1915 }
1916 };
1917
1918 let tool_args = self.build_args_async(args, None).await?;
1920
1921 let mut isolated_scope = Scope::new();
1923
1924 let positional_args: Vec<String> = tool_args.positional
1926 .iter()
1927 .map(value_to_string)
1928 .collect();
1929 isolated_scope.set_positional(name, positional_args);
1930
1931 let original_scope = {
1933 let mut scope = self.scope.write().await;
1934 std::mem::replace(&mut *scope, isolated_scope)
1935 };
1936
1937 let mut result = ExecResult::success("");
1939 let mut exec_error: Option<anyhow::Error> = None;
1940 let mut exit_code: Option<i64> = None;
1941
1942 for stmt in program.statements {
1943 if matches!(stmt, crate::ast::Stmt::Empty) {
1944 continue;
1945 }
1946
1947 match self.execute_stmt_flow(&stmt).await {
1948 Ok(flow) => {
1949 match flow {
1950 ControlFlow::Normal(r) => result = r,
1951 ControlFlow::Return { value } => {
1952 result = value;
1953 break;
1954 }
1955 ControlFlow::Exit { code } => {
1956 exit_code = Some(code);
1957 break;
1958 }
1959 ControlFlow::Break { result: r, .. } | ControlFlow::Continue { result: r, .. } => {
1960 result = r;
1961 }
1962 }
1963 }
1964 Err(e) => {
1965 exec_error = Some(e);
1966 break;
1967 }
1968 }
1969 }
1970
1971 {
1973 let mut scope = self.scope.write().await;
1974 *scope = original_scope;
1975 }
1976
1977 if let Some(e) = exec_error {
1979 return Err(e.context(format!("script: {}", script_path.display())));
1980 }
1981 if let Some(code) = exit_code {
1982 result.code = code;
1983 return Ok(Some(result));
1984 }
1985
1986 return Ok(Some(result));
1987 }
1988
1989 Ok(None)
1991 }
1992
1993 #[tracing::instrument(level = "debug", skip(self, args), fields(command = %name))]
2007 async fn try_execute_external(&self, name: &str, args: &[Arg]) -> Result<Option<ExecResult>> {
2008 if name.contains('/') {
2010 return Ok(None);
2011 }
2012
2013 let path_var = {
2015 let scope = self.scope.read().await;
2016 scope
2017 .get("PATH")
2018 .map(value_to_string)
2019 .unwrap_or_else(|| std::env::var("PATH").unwrap_or_default())
2020 };
2021
2022 let executable = match resolve_in_path(name, &path_var) {
2024 Some(path) => path,
2025 None => return Ok(None), };
2027
2028 tracing::debug!(executable = %executable, "resolved external command");
2029
2030 let real_cwd = {
2032 let ctx = self.exec_ctx.read().await;
2033 match ctx.backend.resolve_real_path(&ctx.cwd) {
2034 Some(p) => p,
2035 None => {
2036 return Ok(Some(ExecResult::failure(
2037 1,
2038 format!(
2039 "{}: cannot run external command from virtual directory '{}'",
2040 name,
2041 ctx.cwd.display()
2042 ),
2043 )));
2044 }
2045 }
2046 };
2047
2048 let argv = self.build_args_flat(args).await?;
2050
2051 let stdin_data = {
2053 let mut ctx = self.exec_ctx.write().await;
2054 ctx.take_stdin()
2055 };
2056
2057 use tokio::process::Command;
2059
2060 let mut cmd = Command::new(&executable);
2061 cmd.args(&argv);
2062 cmd.current_dir(&real_cwd);
2063
2064 cmd.stdin(if stdin_data.is_some() {
2066 std::process::Stdio::piped()
2067 } else {
2068 std::process::Stdio::null()
2069 });
2070
2071 let inherit_output = self.interactive && stdin_data.is_none();
2074
2075 if inherit_output {
2076 cmd.stdout(std::process::Stdio::inherit());
2077 cmd.stderr(std::process::Stdio::inherit());
2078 } else {
2079 cmd.stdout(std::process::Stdio::piped());
2080 cmd.stderr(std::process::Stdio::piped());
2081 }
2082
2083 let mut child = match cmd.spawn() {
2085 Ok(child) => child,
2086 Err(e) => {
2087 return Ok(Some(ExecResult::failure(
2088 127,
2089 format!("{}: {}", name, e),
2090 )));
2091 }
2092 };
2093
2094 if let Some(data) = stdin_data
2096 && let Some(mut stdin) = child.stdin.take()
2097 {
2098 use tokio::io::AsyncWriteExt;
2099 if let Err(e) = stdin.write_all(data.as_bytes()).await {
2100 return Ok(Some(ExecResult::failure(
2101 1,
2102 format!("{}: failed to write stdin: {}", name, e),
2103 )));
2104 }
2105 }
2107
2108 if inherit_output {
2109 let status = match child.wait().await {
2111 Ok(s) => s,
2112 Err(e) => {
2113 return Ok(Some(ExecResult::failure(
2114 1,
2115 format!("{}: failed to wait: {}", name, e),
2116 )));
2117 }
2118 };
2119
2120 let code = status.code().unwrap_or_else(|| {
2121 #[cfg(unix)]
2122 {
2123 use std::os::unix::process::ExitStatusExt;
2124 128 + status.signal().unwrap_or(0)
2125 }
2126 #[cfg(not(unix))]
2127 {
2128 -1
2129 }
2130 }) as i64;
2131
2132 Ok(Some(ExecResult::from_output(code, String::new(), String::new())))
2134 } else {
2135 let stdout_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2137 let stderr_stream = Arc::new(BoundedStream::new(DEFAULT_STREAM_MAX_SIZE));
2138
2139 let stdout_pipe = child.stdout.take();
2140 let stderr_pipe = child.stderr.take();
2141
2142 let stdout_clone = stdout_stream.clone();
2143 let stderr_clone = stderr_stream.clone();
2144
2145 let stdout_task = stdout_pipe.map(|pipe| {
2146 tokio::spawn(async move {
2147 drain_to_stream(pipe, stdout_clone).await;
2148 })
2149 });
2150
2151 let stderr_task = stderr_pipe.map(|pipe| {
2152 tokio::spawn(async move {
2153 drain_to_stream(pipe, stderr_clone).await;
2154 })
2155 });
2156
2157 let status = match child.wait().await {
2158 Ok(s) => s,
2159 Err(e) => {
2160 return Ok(Some(ExecResult::failure(
2161 1,
2162 format!("{}: failed to wait: {}", name, e),
2163 )));
2164 }
2165 };
2166
2167 if let Some(task) = stdout_task {
2168 let _ = task.await;
2170 }
2171 if let Some(task) = stderr_task {
2172 let _ = task.await;
2173 }
2174
2175 let code = status.code().unwrap_or_else(|| {
2176 #[cfg(unix)]
2177 {
2178 use std::os::unix::process::ExitStatusExt;
2179 128 + status.signal().unwrap_or(0)
2180 }
2181 #[cfg(not(unix))]
2182 {
2183 -1
2184 }
2185 }) as i64;
2186
2187 let stdout = stdout_stream.read_string().await;
2188 let stderr = stderr_stream.read_string().await;
2189
2190 Ok(Some(ExecResult::from_output(code, stdout, stderr)))
2191 }
2192 }
2193
2194 pub async fn get_var(&self, name: &str) -> Option<Value> {
2198 let scope = self.scope.read().await;
2199 scope.get(name).cloned()
2200 }
2201
2202 #[cfg(test)]
2204 pub async fn error_exit_enabled(&self) -> bool {
2205 let scope = self.scope.read().await;
2206 scope.error_exit_enabled()
2207 }
2208
2209 pub async fn set_var(&self, name: &str, value: Value) {
2211 let mut scope = self.scope.write().await;
2212 scope.set(name.to_string(), value);
2213 }
2214
2215 pub async fn list_vars(&self) -> Vec<(String, Value)> {
2217 let scope = self.scope.read().await;
2218 scope.all()
2219 }
2220
2221 pub async fn cwd(&self) -> PathBuf {
2225 self.exec_ctx.read().await.cwd.clone()
2226 }
2227
2228 pub async fn set_cwd(&self, path: PathBuf) {
2230 let mut ctx = self.exec_ctx.write().await;
2231 ctx.set_cwd(path);
2232 }
2233
2234 pub async fn last_result(&self) -> ExecResult {
2238 let scope = self.scope.read().await;
2239 scope.last_result().clone()
2240 }
2241
2242 pub fn tool_schemas(&self) -> Vec<crate::tools::ToolSchema> {
2246 self.tools.schemas()
2247 }
2248
2249 pub fn jobs(&self) -> Arc<JobManager> {
2253 self.jobs.clone()
2254 }
2255
2256 pub fn vfs(&self) -> Arc<VfsRouter> {
2260 self.vfs.clone()
2261 }
2262
2263 pub async fn reset(&self) -> Result<()> {
2270 {
2271 let mut scope = self.scope.write().await;
2272 *scope = Scope::new();
2273 }
2274 {
2275 let mut ctx = self.exec_ctx.write().await;
2276 ctx.cwd = PathBuf::from("/");
2277 }
2278 Ok(())
2279 }
2280
2281 pub async fn shutdown(self) -> Result<()> {
2283 self.jobs.wait_all().await;
2285 Ok(())
2286 }
2287
2288 async fn dispatch_command(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2299 {
2301 let mut scope = self.scope.write().await;
2302 *scope = ctx.scope.clone();
2303 }
2304 {
2305 let mut ec = self.exec_ctx.write().await;
2306 ec.cwd = ctx.cwd.clone();
2307 ec.prev_cwd = ctx.prev_cwd.clone();
2308 ec.stdin = ctx.stdin.take();
2309 ec.stdin_data = ctx.stdin_data.take();
2310 }
2311
2312 let result = self.execute_command(&cmd.name, &cmd.args).await?;
2314
2315 {
2317 let scope = self.scope.read().await;
2318 ctx.scope = scope.clone();
2319 }
2320 {
2321 let ec = self.exec_ctx.read().await;
2322 ctx.cwd = ec.cwd.clone();
2323 ctx.prev_cwd = ec.prev_cwd.clone();
2324 }
2325
2326 Ok(result)
2327 }
2328}
2329
2330#[async_trait]
2331impl CommandDispatcher for Kernel {
2332 async fn dispatch(&self, cmd: &Command, ctx: &mut ExecContext) -> Result<ExecResult> {
2338 self.dispatch_command(cmd, ctx).await
2339 }
2340}
2341
2342fn accumulate_result(accumulated: &mut ExecResult, new: &ExecResult) {
2348 if !accumulated.out.is_empty() && !new.out.is_empty() && !accumulated.out.ends_with('\n') {
2349 accumulated.out.push('\n');
2350 }
2351 accumulated.out.push_str(&new.out);
2352 if !accumulated.err.is_empty() && !new.err.is_empty() && !accumulated.err.ends_with('\n') {
2353 accumulated.err.push('\n');
2354 }
2355 accumulated.err.push_str(&new.err);
2356 accumulated.code = new.code;
2357 accumulated.data = new.data.clone();
2358}
2359
2360fn is_truthy(value: &Value) -> bool {
2362 match value {
2363 Value::Null => false,
2364 Value::Bool(b) => *b,
2365 Value::Int(i) => *i != 0,
2366 Value::Float(f) => *f != 0.0,
2367 Value::String(s) => !s.is_empty(),
2368 Value::Json(json) => match json {
2369 serde_json::Value::Null => false,
2370 serde_json::Value::Array(arr) => !arr.is_empty(),
2371 serde_json::Value::Object(obj) => !obj.is_empty(),
2372 serde_json::Value::Bool(b) => *b,
2373 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
2374 serde_json::Value::String(s) => !s.is_empty(),
2375 },
2376 Value::Blob(_) => true, }
2378}
2379
2380fn apply_tilde_expansion(value: Value) -> Value {
2384 match value {
2385 Value::String(s) if s.starts_with('~') => Value::String(expand_tilde(&s)),
2386 _ => value,
2387 }
2388}
2389
2390#[cfg(test)]
2391mod tests {
2392 use super::*;
2393
2394 #[tokio::test]
2395 async fn test_kernel_transient() {
2396 let kernel = Kernel::transient().expect("failed to create kernel");
2397 assert_eq!(kernel.name(), "transient");
2398 }
2399
2400 #[tokio::test]
2401 async fn test_kernel_execute_echo() {
2402 let kernel = Kernel::transient().expect("failed to create kernel");
2403 let result = kernel.execute("echo hello").await.expect("execution failed");
2404 assert!(result.ok());
2405 assert_eq!(result.out.trim(), "hello");
2406 }
2407
2408 #[tokio::test]
2409 async fn test_multiple_statements_accumulate_output() {
2410 let kernel = Kernel::transient().expect("failed to create kernel");
2411 let result = kernel
2412 .execute("echo one\necho two\necho three")
2413 .await
2414 .expect("execution failed");
2415 assert!(result.ok());
2416 assert!(result.out.contains("one"), "missing 'one': {}", result.out);
2418 assert!(result.out.contains("two"), "missing 'two': {}", result.out);
2419 assert!(result.out.contains("three"), "missing 'three': {}", result.out);
2420 }
2421
2422 #[tokio::test]
2423 async fn test_and_chain_accumulates_output() {
2424 let kernel = Kernel::transient().expect("failed to create kernel");
2425 let result = kernel
2426 .execute("echo first && echo second")
2427 .await
2428 .expect("execution failed");
2429 assert!(result.ok());
2430 assert!(result.out.contains("first"), "missing 'first': {}", result.out);
2431 assert!(result.out.contains("second"), "missing 'second': {}", result.out);
2432 }
2433
2434 #[tokio::test]
2435 async fn test_for_loop_accumulates_output() {
2436 let kernel = Kernel::transient().expect("failed to create kernel");
2437 let result = kernel
2438 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
2439 .await
2440 .expect("execution failed");
2441 assert!(result.ok());
2442 assert!(result.out.contains("item: a"), "missing 'item: a': {}", result.out);
2443 assert!(result.out.contains("item: b"), "missing 'item: b': {}", result.out);
2444 assert!(result.out.contains("item: c"), "missing 'item: c': {}", result.out);
2445 }
2446
2447 #[tokio::test]
2448 async fn test_while_loop_accumulates_output() {
2449 let kernel = Kernel::transient().expect("failed to create kernel");
2450 let result = kernel
2451 .execute(r#"
2452 N=3
2453 while [[ ${N} -gt 0 ]]; do
2454 echo "N=${N}"
2455 N=$((N - 1))
2456 done
2457 "#)
2458 .await
2459 .expect("execution failed");
2460 assert!(result.ok());
2461 assert!(result.out.contains("N=3"), "missing 'N=3': {}", result.out);
2462 assert!(result.out.contains("N=2"), "missing 'N=2': {}", result.out);
2463 assert!(result.out.contains("N=1"), "missing 'N=1': {}", result.out);
2464 }
2465
2466 #[tokio::test]
2467 async fn test_kernel_set_var() {
2468 let kernel = Kernel::transient().expect("failed to create kernel");
2469
2470 kernel.execute("X=42").await.expect("set failed");
2471
2472 let value = kernel.get_var("X").await;
2473 assert_eq!(value, Some(Value::Int(42)));
2474 }
2475
2476 #[tokio::test]
2477 async fn test_kernel_var_expansion() {
2478 let kernel = Kernel::transient().expect("failed to create kernel");
2479
2480 kernel.execute("NAME=\"world\"").await.expect("set failed");
2481 let result = kernel.execute("echo \"hello ${NAME}\"").await.expect("echo failed");
2482
2483 assert!(result.ok());
2484 assert_eq!(result.out.trim(), "hello world");
2485 }
2486
2487 #[tokio::test]
2488 async fn test_kernel_last_result() {
2489 let kernel = Kernel::transient().expect("failed to create kernel");
2490
2491 kernel.execute("echo test").await.expect("echo failed");
2492
2493 let last = kernel.last_result().await;
2494 assert!(last.ok());
2495 assert_eq!(last.out.trim(), "test");
2496 }
2497
2498 #[tokio::test]
2499 async fn test_kernel_tool_not_found() {
2500 let kernel = Kernel::transient().expect("failed to create kernel");
2501
2502 let result = kernel.execute("nonexistent_tool").await.expect("execution failed");
2503 assert!(!result.ok());
2504 assert_eq!(result.code, 127);
2505 assert!(result.err.contains("command not found"));
2506 }
2507
2508 #[tokio::test]
2509 async fn test_external_command_true() {
2510 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2512
2513 let result = kernel.execute("true").await.expect("execution failed");
2515 assert!(result.ok(), "true should succeed: {:?}", result);
2517 }
2518
2519 #[tokio::test]
2520 async fn test_external_command_basic() {
2521 let kernel = Kernel::new(KernelConfig::repl()).expect("failed to create kernel");
2523
2524 let path_var = std::env::var("PATH").unwrap_or_default();
2529 eprintln!("System PATH: {}", path_var);
2530
2531 kernel.execute(&format!(r#"PATH="{}""#, path_var)).await.expect("set PATH failed");
2533
2534 let result = kernel.execute("uname").await.expect("execution failed");
2537 eprintln!("uname result: {:?}", result);
2538 assert!(result.ok() || result.code == 127, "uname: {:?}", result);
2540 }
2541
2542 #[tokio::test]
2543 async fn test_kernel_reset() {
2544 let kernel = Kernel::transient().expect("failed to create kernel");
2545
2546 kernel.execute("X=1").await.expect("set failed");
2547 assert!(kernel.get_var("X").await.is_some());
2548
2549 kernel.reset().await.expect("reset failed");
2550 assert!(kernel.get_var("X").await.is_none());
2551 }
2552
2553 #[tokio::test]
2554 async fn test_kernel_cwd() {
2555 let kernel = Kernel::transient().expect("failed to create kernel");
2556
2557 let cwd = kernel.cwd().await;
2559 let home = std::env::var("HOME")
2560 .map(PathBuf::from)
2561 .unwrap_or_else(|_| PathBuf::from("/"));
2562 assert_eq!(cwd, home);
2563
2564 kernel.set_cwd(PathBuf::from("/tmp")).await;
2565 assert_eq!(kernel.cwd().await, PathBuf::from("/tmp"));
2566 }
2567
2568 #[tokio::test]
2569 async fn test_kernel_list_vars() {
2570 let kernel = Kernel::transient().expect("failed to create kernel");
2571
2572 kernel.execute("A=1").await.ok();
2573 kernel.execute("B=2").await.ok();
2574
2575 let vars = kernel.list_vars().await;
2576 assert!(vars.iter().any(|(n, v)| n == "A" && *v == Value::Int(1)));
2577 assert!(vars.iter().any(|(n, v)| n == "B" && *v == Value::Int(2)));
2578 }
2579
2580 #[tokio::test]
2581 async fn test_is_truthy() {
2582 assert!(!is_truthy(&Value::Null));
2583 assert!(!is_truthy(&Value::Bool(false)));
2584 assert!(is_truthy(&Value::Bool(true)));
2585 assert!(!is_truthy(&Value::Int(0)));
2586 assert!(is_truthy(&Value::Int(1)));
2587 assert!(!is_truthy(&Value::String("".into())));
2588 assert!(is_truthy(&Value::String("x".into())));
2589 }
2590
2591 #[tokio::test]
2592 async fn test_jq_in_pipeline() {
2593 let kernel = Kernel::transient().expect("failed to create kernel");
2594 let result = kernel
2596 .execute(r#"echo "{\"name\": \"Alice\"}" | jq ".name" -r"#)
2597 .await
2598 .expect("execution failed");
2599 assert!(result.ok(), "jq pipeline failed: {}", result.err);
2600 assert_eq!(result.out.trim(), "Alice");
2601 }
2602
2603 #[tokio::test]
2604 async fn test_user_defined_tool() {
2605 let kernel = Kernel::transient().expect("failed to create kernel");
2606
2607 kernel
2609 .execute(r#"greet() { echo "Hello, $1!" }"#)
2610 .await
2611 .expect("function definition failed");
2612
2613 let result = kernel
2615 .execute(r#"greet "World""#)
2616 .await
2617 .expect("function call failed");
2618
2619 assert!(result.ok(), "greet failed: {}", result.err);
2620 assert_eq!(result.out.trim(), "Hello, World!");
2621 }
2622
2623 #[tokio::test]
2624 async fn test_user_tool_positional_args() {
2625 let kernel = Kernel::transient().expect("failed to create kernel");
2626
2627 kernel
2629 .execute(r#"greet() { echo "Hi $1" }"#)
2630 .await
2631 .expect("function definition failed");
2632
2633 let result = kernel
2635 .execute(r#"greet "Amy""#)
2636 .await
2637 .expect("function call failed");
2638
2639 assert!(result.ok(), "greet failed: {}", result.err);
2640 assert_eq!(result.out.trim(), "Hi Amy");
2641 }
2642
2643 #[tokio::test]
2644 async fn test_function_shared_scope() {
2645 let kernel = Kernel::transient().expect("failed to create kernel");
2646
2647 kernel
2649 .execute(r#"SECRET="hidden""#)
2650 .await
2651 .expect("set failed");
2652
2653 kernel
2655 .execute(r#"access_parent() {
2656 echo "${SECRET}"
2657 SECRET="modified"
2658 }"#)
2659 .await
2660 .expect("function definition failed");
2661
2662 let result = kernel.execute("access_parent").await.expect("function call failed");
2664
2665 assert!(
2667 result.out.contains("hidden"),
2668 "Function should access parent scope, got: {}",
2669 result.out
2670 );
2671
2672 let secret = kernel.get_var("SECRET").await;
2674 assert_eq!(
2675 secret,
2676 Some(Value::String("modified".into())),
2677 "Function should modify parent scope"
2678 );
2679 }
2680
2681 #[tokio::test]
2682 async fn test_exec_builtin() {
2683 let kernel = Kernel::transient().expect("failed to create kernel");
2684 let result = kernel
2686 .execute(r#"exec command="/bin/echo" argv="hello world""#)
2687 .await
2688 .expect("exec failed");
2689
2690 assert!(result.ok(), "exec failed: {}", result.err);
2691 assert_eq!(result.out.trim(), "hello world");
2692 }
2693
2694 #[tokio::test]
2695 async fn test_while_false_never_runs() {
2696 let kernel = Kernel::transient().expect("failed to create kernel");
2697
2698 let result = kernel
2700 .execute(r#"
2701 while false; do
2702 echo "should not run"
2703 done
2704 "#)
2705 .await
2706 .expect("while false failed");
2707
2708 assert!(result.ok());
2709 assert!(result.out.is_empty(), "while false should not execute body: {}", result.out);
2710 }
2711
2712 #[tokio::test]
2713 async fn test_while_string_comparison() {
2714 let kernel = Kernel::transient().expect("failed to create kernel");
2715
2716 kernel.execute(r#"FLAG="go""#).await.expect("set failed");
2718
2719 let result = kernel
2722 .execute(r#"
2723 while [[ ${FLAG} == "go" ]]; do
2724 FLAG="stop"
2725 echo "running"
2726 done
2727 "#)
2728 .await
2729 .expect("while with string cmp failed");
2730
2731 assert!(result.ok());
2732 assert!(result.out.contains("running"), "should have run once: {}", result.out);
2733
2734 let flag = kernel.get_var("FLAG").await;
2736 assert_eq!(flag, Some(Value::String("stop".into())));
2737 }
2738
2739 #[tokio::test]
2740 async fn test_while_numeric_comparison() {
2741 let kernel = Kernel::transient().expect("failed to create kernel");
2742
2743 kernel.execute("N=5").await.expect("set failed");
2745
2746 let result = kernel
2748 .execute(r#"
2749 while [[ ${N} -gt 3 ]]; do
2750 N=3
2751 echo "N was greater"
2752 done
2753 "#)
2754 .await
2755 .expect("while with > failed");
2756
2757 assert!(result.ok());
2758 assert!(result.out.contains("N was greater"), "should have run once: {}", result.out);
2759 }
2760
2761 #[tokio::test]
2762 async fn test_break_in_while_loop() {
2763 let kernel = Kernel::transient().expect("failed to create kernel");
2764
2765 let result = kernel
2766 .execute(r#"
2767 I=0
2768 while true; do
2769 I=1
2770 echo "before break"
2771 break
2772 echo "after break"
2773 done
2774 "#)
2775 .await
2776 .expect("while with break failed");
2777
2778 assert!(result.ok());
2779 assert!(result.out.contains("before break"), "should see before break: {}", result.out);
2780 assert!(!result.out.contains("after break"), "should not see after break: {}", result.out);
2781
2782 let i = kernel.get_var("I").await;
2784 assert_eq!(i, Some(Value::Int(1)));
2785 }
2786
2787 #[tokio::test]
2788 async fn test_continue_in_while_loop() {
2789 let kernel = Kernel::transient().expect("failed to create kernel");
2790
2791 let result = kernel
2796 .execute(r#"
2797 STATE="start"
2798 AFTER_CONTINUE="no"
2799 while [[ ${STATE} != "done" ]]; do
2800 if [[ ${STATE} == "start" ]]; then
2801 STATE="middle"
2802 continue
2803 AFTER_CONTINUE="yes"
2804 fi
2805 if [[ ${STATE} == "middle" ]]; then
2806 STATE="done"
2807 fi
2808 done
2809 "#)
2810 .await
2811 .expect("while with continue failed");
2812
2813 assert!(result.ok());
2814
2815 let state = kernel.get_var("STATE").await;
2817 assert_eq!(state, Some(Value::String("done".into())));
2818
2819 let after = kernel.get_var("AFTER_CONTINUE").await;
2821 assert_eq!(after, Some(Value::String("no".into())));
2822 }
2823
2824 #[tokio::test]
2825 async fn test_break_with_level() {
2826 let kernel = Kernel::transient().expect("failed to create kernel");
2827
2828 let result = kernel
2833 .execute(r#"
2834 OUTER=0
2835 while true; do
2836 OUTER=1
2837 for X in "1 2"; do
2838 break 2
2839 done
2840 OUTER=2
2841 done
2842 "#)
2843 .await
2844 .expect("nested break failed");
2845
2846 assert!(result.ok());
2847
2848 let outer = kernel.get_var("OUTER").await;
2850 assert_eq!(outer, Some(Value::Int(1)), "break 2 should have skipped OUTER=2");
2851 }
2852
2853 #[tokio::test]
2854 async fn test_return_from_tool() {
2855 let kernel = Kernel::transient().expect("failed to create kernel");
2856
2857 kernel
2859 .execute(r#"early_return() {
2860 if [[ $1 == 1 ]]; then
2861 return 42
2862 fi
2863 echo "not returned"
2864 }"#)
2865 .await
2866 .expect("function definition failed");
2867
2868 let result = kernel
2871 .execute("early_return 1")
2872 .await
2873 .expect("function call failed");
2874
2875 assert_eq!(result.code, 42);
2877 assert!(result.out.is_empty());
2879 }
2880
2881 #[tokio::test]
2882 async fn test_return_without_value() {
2883 let kernel = Kernel::transient().expect("failed to create kernel");
2884
2885 kernel
2887 .execute(r#"early_exit() {
2888 if [[ $1 == "stop" ]]; then
2889 return
2890 fi
2891 echo "continued"
2892 }"#)
2893 .await
2894 .expect("function definition failed");
2895
2896 let result = kernel
2898 .execute(r#"early_exit "stop""#)
2899 .await
2900 .expect("function call failed");
2901
2902 assert!(result.ok());
2903 assert!(result.out.is_empty() || result.out.trim().is_empty());
2904 }
2905
2906 #[tokio::test]
2907 async fn test_exit_stops_execution() {
2908 let kernel = Kernel::transient().expect("failed to create kernel");
2909
2910 kernel
2912 .execute(r#"
2913 BEFORE="yes"
2914 exit 0
2915 AFTER="yes"
2916 "#)
2917 .await
2918 .expect("execution failed");
2919
2920 let before = kernel.get_var("BEFORE").await;
2922 assert_eq!(before, Some(Value::String("yes".into())));
2923
2924 let after = kernel.get_var("AFTER").await;
2925 assert!(after.is_none(), "AFTER should not be set after exit");
2926 }
2927
2928 #[tokio::test]
2929 async fn test_exit_with_code() {
2930 let kernel = Kernel::transient().expect("failed to create kernel");
2931
2932 let result = kernel
2934 .execute("exit 42")
2935 .await
2936 .expect("exit failed");
2937
2938 assert_eq!(result.code, 42);
2939 assert!(result.out.is_empty(), "exit should not produce stdout");
2940 }
2941
2942 #[tokio::test]
2943 async fn test_set_e_stops_on_failure() {
2944 let kernel = Kernel::transient().expect("failed to create kernel");
2945
2946 kernel.execute("set -e").await.expect("set -e failed");
2948
2949 kernel
2951 .execute(r#"
2952 STEP1="done"
2953 false
2954 STEP2="done"
2955 "#)
2956 .await
2957 .expect("execution failed");
2958
2959 let step1 = kernel.get_var("STEP1").await;
2961 assert_eq!(step1, Some(Value::String("done".into())));
2962
2963 let step2 = kernel.get_var("STEP2").await;
2964 assert!(step2.is_none(), "STEP2 should not be set after false with set -e");
2965 }
2966
2967 #[tokio::test]
2968 async fn test_set_plus_e_disables_error_exit() {
2969 let kernel = Kernel::transient().expect("failed to create kernel");
2970
2971 kernel.execute("set -e").await.expect("set -e failed");
2973 kernel.execute("set +e").await.expect("set +e failed");
2974
2975 kernel
2977 .execute(r#"
2978 STEP1="done"
2979 false
2980 STEP2="done"
2981 "#)
2982 .await
2983 .expect("execution failed");
2984
2985 let step1 = kernel.get_var("STEP1").await;
2987 assert_eq!(step1, Some(Value::String("done".into())));
2988
2989 let step2 = kernel.get_var("STEP2").await;
2990 assert_eq!(step2, Some(Value::String("done".into())));
2991 }
2992
2993 #[tokio::test]
2994 async fn test_set_ignores_unknown_options() {
2995 let kernel = Kernel::transient().expect("failed to create kernel");
2996
2997 let result = kernel
2999 .execute("set -e -u -o pipefail")
3000 .await
3001 .expect("set with unknown options failed");
3002
3003 assert!(result.ok(), "set should succeed with unknown options");
3004
3005 kernel
3007 .execute(r#"
3008 BEFORE="yes"
3009 false
3010 AFTER="yes"
3011 "#)
3012 .await
3013 .ok();
3014
3015 let after = kernel.get_var("AFTER").await;
3016 assert!(after.is_none(), "-e should be enabled despite unknown options");
3017 }
3018
3019 #[tokio::test]
3020 async fn test_set_no_args_shows_settings() {
3021 let kernel = Kernel::transient().expect("failed to create kernel");
3022
3023 kernel.execute("set -e").await.expect("set -e failed");
3025
3026 let result = kernel.execute("set").await.expect("set failed");
3028
3029 assert!(result.ok());
3030 assert!(result.out.contains("set -e"), "should show -e is enabled: {}", result.out);
3031 }
3032
3033 #[tokio::test]
3034 async fn test_set_e_in_pipeline() {
3035 let kernel = Kernel::transient().expect("failed to create kernel");
3036
3037 kernel.execute("set -e").await.expect("set -e failed");
3038
3039 kernel
3041 .execute(r#"
3042 BEFORE="yes"
3043 false | cat
3044 AFTER="yes"
3045 "#)
3046 .await
3047 .ok();
3048
3049 let before = kernel.get_var("BEFORE").await;
3050 assert_eq!(before, Some(Value::String("yes".into())));
3051
3052 }
3057
3058 #[tokio::test]
3059 async fn test_set_e_with_and_chain() {
3060 let kernel = Kernel::transient().expect("failed to create kernel");
3061
3062 kernel.execute("set -e").await.expect("set -e failed");
3063
3064 kernel
3067 .execute(r#"
3068 RESULT="initial"
3069 false && RESULT="chained"
3070 RESULT="continued"
3071 "#)
3072 .await
3073 .ok();
3074
3075 let result = kernel.get_var("RESULT").await;
3078 assert!(result.is_some(), "RESULT should be set");
3081 }
3082
3083 #[tokio::test]
3084 async fn test_set_e_exits_in_for_loop() {
3085 let kernel = Kernel::transient().expect("failed to create kernel");
3086
3087 kernel.execute("set -e").await.expect("set -e failed");
3088
3089 kernel
3090 .execute(r#"
3091 REACHED="no"
3092 for x in 1 2 3; do
3093 false
3094 REACHED="yes"
3095 done
3096 "#)
3097 .await
3098 .ok();
3099
3100 let reached = kernel.get_var("REACHED").await;
3102 assert_eq!(reached, Some(Value::String("no".into())),
3103 "set -e should exit on failure in for loop body");
3104 }
3105
3106 #[tokio::test]
3107 async fn test_for_loop_continues_without_set_e() {
3108 let kernel = Kernel::transient().expect("failed to create kernel");
3109
3110 kernel
3112 .execute(r#"
3113 COUNT=0
3114 for x in 1 2 3; do
3115 false
3116 COUNT=$((COUNT + 1))
3117 done
3118 "#)
3119 .await
3120 .ok();
3121
3122 let count = kernel.get_var("COUNT").await;
3123 let count_val = match &count {
3125 Some(Value::Int(n)) => *n,
3126 Some(Value::String(s)) => s.parse().unwrap_or(-1),
3127 _ => -1,
3128 };
3129 assert_eq!(count_val, 3,
3130 "without set -e, loop should complete all iterations (got {:?})", count);
3131 }
3132
3133 #[tokio::test]
3138 async fn test_source_sets_variables() {
3139 let kernel = Kernel::transient().expect("failed to create kernel");
3140
3141 kernel
3143 .execute(r#"write "/test.kai" 'FOO="bar"'"#)
3144 .await
3145 .expect("write failed");
3146
3147 let result = kernel
3149 .execute(r#"source "/test.kai""#)
3150 .await
3151 .expect("source failed");
3152
3153 assert!(result.ok(), "source should succeed");
3154
3155 let foo = kernel.get_var("FOO").await;
3157 assert_eq!(foo, Some(Value::String("bar".into())));
3158 }
3159
3160 #[tokio::test]
3161 async fn test_source_with_dot_alias() {
3162 let kernel = Kernel::transient().expect("failed to create kernel");
3163
3164 kernel
3166 .execute(r#"write "/vars.kai" 'X=42'"#)
3167 .await
3168 .expect("write failed");
3169
3170 let result = kernel
3172 .execute(r#". "/vars.kai""#)
3173 .await
3174 .expect(". failed");
3175
3176 assert!(result.ok(), ". should succeed");
3177
3178 let x = kernel.get_var("X").await;
3180 assert_eq!(x, Some(Value::Int(42)));
3181 }
3182
3183 #[tokio::test]
3184 async fn test_source_not_found() {
3185 let kernel = Kernel::transient().expect("failed to create kernel");
3186
3187 let result = kernel
3189 .execute(r#"source "/nonexistent.kai""#)
3190 .await
3191 .expect("source should not fail with error");
3192
3193 assert!(!result.ok(), "source of non-existent file should fail");
3194 assert!(result.err.contains("nonexistent.kai"), "error should mention filename");
3195 }
3196
3197 #[tokio::test]
3198 async fn test_source_missing_filename() {
3199 let kernel = Kernel::transient().expect("failed to create kernel");
3200
3201 let result = kernel
3203 .execute("source")
3204 .await
3205 .expect("source should not fail with error");
3206
3207 assert!(!result.ok(), "source without filename should fail");
3208 assert!(result.err.contains("missing filename"), "error should mention missing filename");
3209 }
3210
3211 #[tokio::test]
3212 async fn test_source_executes_multiple_statements() {
3213 let kernel = Kernel::transient().expect("failed to create kernel");
3214
3215 kernel
3217 .execute(r#"write "/multi.kai" 'A=1
3218B=2
3219C=3'"#)
3220 .await
3221 .expect("write failed");
3222
3223 kernel
3225 .execute(r#"source "/multi.kai""#)
3226 .await
3227 .expect("source failed");
3228
3229 assert_eq!(kernel.get_var("A").await, Some(Value::Int(1)));
3231 assert_eq!(kernel.get_var("B").await, Some(Value::Int(2)));
3232 assert_eq!(kernel.get_var("C").await, Some(Value::Int(3)));
3233 }
3234
3235 #[tokio::test]
3236 async fn test_source_can_define_functions() {
3237 let kernel = Kernel::transient().expect("failed to create kernel");
3238
3239 kernel
3241 .execute(r#"write "/functions.kai" 'greet() {
3242 echo "Hello, $1!"
3243}'"#)
3244 .await
3245 .expect("write failed");
3246
3247 kernel
3249 .execute(r#"source "/functions.kai""#)
3250 .await
3251 .expect("source failed");
3252
3253 let result = kernel
3255 .execute(r#"greet "World""#)
3256 .await
3257 .expect("greet failed");
3258
3259 assert!(result.ok());
3260 assert!(result.out.contains("Hello, World!"));
3261 }
3262
3263 #[tokio::test]
3264 async fn test_source_inherits_error_exit() {
3265 let kernel = Kernel::transient().expect("failed to create kernel");
3266
3267 kernel.execute("set -e").await.expect("set -e failed");
3269
3270 kernel
3272 .execute(r#"write "/fail.kai" 'BEFORE="yes"
3273false
3274AFTER="yes"'"#)
3275 .await
3276 .expect("write failed");
3277
3278 kernel
3280 .execute(r#"source "/fail.kai""#)
3281 .await
3282 .ok();
3283
3284 let before = kernel.get_var("BEFORE").await;
3286 assert_eq!(before, Some(Value::String("yes".into())));
3287
3288 }
3291
3292 #[tokio::test]
3297 async fn test_case_simple_match() {
3298 let kernel = Kernel::transient().expect("failed to create kernel");
3299
3300 let result = kernel
3301 .execute(r#"
3302 case "hello" in
3303 hello) echo "matched hello" ;;
3304 world) echo "matched world" ;;
3305 esac
3306 "#)
3307 .await
3308 .expect("case failed");
3309
3310 assert!(result.ok());
3311 assert_eq!(result.out.trim(), "matched hello");
3312 }
3313
3314 #[tokio::test]
3315 async fn test_case_wildcard_match() {
3316 let kernel = Kernel::transient().expect("failed to create kernel");
3317
3318 let result = kernel
3319 .execute(r#"
3320 case "main.rs" in
3321 "*.py") echo "Python" ;;
3322 "*.rs") echo "Rust" ;;
3323 "*") echo "Unknown" ;;
3324 esac
3325 "#)
3326 .await
3327 .expect("case failed");
3328
3329 assert!(result.ok());
3330 assert_eq!(result.out.trim(), "Rust");
3331 }
3332
3333 #[tokio::test]
3334 async fn test_case_default_match() {
3335 let kernel = Kernel::transient().expect("failed to create kernel");
3336
3337 let result = kernel
3338 .execute(r#"
3339 case "unknown.xyz" in
3340 "*.py") echo "Python" ;;
3341 "*.rs") echo "Rust" ;;
3342 "*") echo "Default" ;;
3343 esac
3344 "#)
3345 .await
3346 .expect("case failed");
3347
3348 assert!(result.ok());
3349 assert_eq!(result.out.trim(), "Default");
3350 }
3351
3352 #[tokio::test]
3353 async fn test_case_no_match() {
3354 let kernel = Kernel::transient().expect("failed to create kernel");
3355
3356 let result = kernel
3358 .execute(r#"
3359 case "nope" in
3360 "yes") echo "yes" ;;
3361 "no") echo "no" ;;
3362 esac
3363 "#)
3364 .await
3365 .expect("case failed");
3366
3367 assert!(result.ok());
3368 assert!(result.out.is_empty(), "no match should produce empty output");
3369 }
3370
3371 #[tokio::test]
3372 async fn test_case_with_variable() {
3373 let kernel = Kernel::transient().expect("failed to create kernel");
3374
3375 kernel.execute(r#"LANG="rust""#).await.expect("set failed");
3376
3377 let result = kernel
3378 .execute(r#"
3379 case ${LANG} in
3380 python) echo "snake" ;;
3381 rust) echo "crab" ;;
3382 go) echo "gopher" ;;
3383 esac
3384 "#)
3385 .await
3386 .expect("case failed");
3387
3388 assert!(result.ok());
3389 assert_eq!(result.out.trim(), "crab");
3390 }
3391
3392 #[tokio::test]
3393 async fn test_case_multiple_patterns() {
3394 let kernel = Kernel::transient().expect("failed to create kernel");
3395
3396 let result = kernel
3397 .execute(r#"
3398 case "yes" in
3399 "y"|"yes"|"Y"|"YES") echo "affirmative" ;;
3400 "n"|"no"|"N"|"NO") echo "negative" ;;
3401 esac
3402 "#)
3403 .await
3404 .expect("case failed");
3405
3406 assert!(result.ok());
3407 assert_eq!(result.out.trim(), "affirmative");
3408 }
3409
3410 #[tokio::test]
3411 async fn test_case_glob_question_mark() {
3412 let kernel = Kernel::transient().expect("failed to create kernel");
3413
3414 let result = kernel
3415 .execute(r#"
3416 case "test1" in
3417 "test?") echo "matched test?" ;;
3418 "*") echo "default" ;;
3419 esac
3420 "#)
3421 .await
3422 .expect("case failed");
3423
3424 assert!(result.ok());
3425 assert_eq!(result.out.trim(), "matched test?");
3426 }
3427
3428 #[tokio::test]
3429 async fn test_case_char_class() {
3430 let kernel = Kernel::transient().expect("failed to create kernel");
3431
3432 let result = kernel
3433 .execute(r#"
3434 case "Yes" in
3435 "[Yy]*") echo "yes-like" ;;
3436 "[Nn]*") echo "no-like" ;;
3437 esac
3438 "#)
3439 .await
3440 .expect("case failed");
3441
3442 assert!(result.ok());
3443 assert_eq!(result.out.trim(), "yes-like");
3444 }
3445
3446 #[tokio::test]
3451 async fn test_cat_from_pipeline() {
3452 let kernel = Kernel::transient().expect("failed to create kernel");
3453
3454 let result = kernel
3455 .execute(r#"echo "piped text" | cat"#)
3456 .await
3457 .expect("cat pipeline failed");
3458
3459 assert!(result.ok(), "cat failed: {}", result.err);
3460 assert_eq!(result.out.trim(), "piped text");
3461 }
3462
3463 #[tokio::test]
3464 async fn test_cat_from_pipeline_multiline() {
3465 let kernel = Kernel::transient().expect("failed to create kernel");
3466
3467 let result = kernel
3468 .execute(r#"echo "line1\nline2" | cat -n"#)
3469 .await
3470 .expect("cat pipeline failed");
3471
3472 assert!(result.ok(), "cat failed: {}", result.err);
3473 assert!(result.out.contains("1\t"), "output: {}", result.out);
3474 }
3475
3476 #[tokio::test]
3481 async fn test_heredoc_basic() {
3482 let kernel = Kernel::transient().expect("failed to create kernel");
3483
3484 let result = kernel
3485 .execute("cat <<EOF\nhello\nEOF")
3486 .await
3487 .expect("heredoc failed");
3488
3489 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3490 assert_eq!(result.out.trim(), "hello");
3491 }
3492
3493 #[tokio::test]
3494 async fn test_arithmetic_in_string() {
3495 let kernel = Kernel::transient().expect("failed to create kernel");
3496
3497 let result = kernel
3498 .execute(r#"echo "result: $((1 + 2))""#)
3499 .await
3500 .expect("arithmetic in string failed");
3501
3502 assert!(result.ok(), "echo failed: {}", result.err);
3503 assert_eq!(result.out.trim(), "result: 3");
3504 }
3505
3506 #[tokio::test]
3507 async fn test_heredoc_multiline() {
3508 let kernel = Kernel::transient().expect("failed to create kernel");
3509
3510 let result = kernel
3511 .execute("cat <<EOF\nline1\nline2\nline3\nEOF")
3512 .await
3513 .expect("heredoc failed");
3514
3515 assert!(result.ok(), "cat with heredoc failed: {}", result.err);
3516 assert!(result.out.contains("line1"), "output: {}", result.out);
3517 assert!(result.out.contains("line2"), "output: {}", result.out);
3518 assert!(result.out.contains("line3"), "output: {}", result.out);
3519 }
3520
3521 #[tokio::test]
3522 async fn test_heredoc_variable_expansion() {
3523 let kernel = Kernel::transient().expect("failed to create kernel");
3525
3526 kernel.execute("GREETING=hello").await.expect("set var");
3527
3528 let result = kernel
3529 .execute("cat <<EOF\n$GREETING world\nEOF")
3530 .await
3531 .expect("heredoc expansion failed");
3532
3533 assert!(result.ok(), "heredoc expansion failed: {}", result.err);
3534 assert_eq!(result.out.trim(), "hello world");
3535 }
3536
3537 #[tokio::test]
3538 async fn test_heredoc_quoted_no_expansion() {
3539 let kernel = Kernel::transient().expect("failed to create kernel");
3541
3542 kernel.execute("GREETING=hello").await.expect("set var");
3543
3544 let result = kernel
3545 .execute("cat <<'EOF'\n$GREETING world\nEOF")
3546 .await
3547 .expect("quoted heredoc failed");
3548
3549 assert!(result.ok(), "quoted heredoc failed: {}", result.err);
3550 assert_eq!(result.out.trim(), "$GREETING world");
3551 }
3552
3553 #[tokio::test]
3554 async fn test_heredoc_default_value_expansion() {
3555 let kernel = Kernel::transient().expect("failed to create kernel");
3557
3558 let result = kernel
3559 .execute("cat <<EOF\n${UNSET:-fallback}\nEOF")
3560 .await
3561 .expect("heredoc default expansion failed");
3562
3563 assert!(result.ok(), "heredoc default expansion failed: {}", result.err);
3564 assert_eq!(result.out.trim(), "fallback");
3565 }
3566
3567 #[tokio::test]
3572 async fn test_read_from_pipeline() {
3573 let kernel = Kernel::transient().expect("failed to create kernel");
3574
3575 let result = kernel
3577 .execute(r#"echo "Alice" | read NAME; echo "Hello, ${NAME}""#)
3578 .await
3579 .expect("read pipeline failed");
3580
3581 assert!(result.ok(), "read failed: {}", result.err);
3582 assert!(result.out.contains("Hello, Alice"), "output: {}", result.out);
3583 }
3584
3585 #[tokio::test]
3586 async fn test_read_multiple_vars_from_pipeline() {
3587 let kernel = Kernel::transient().expect("failed to create kernel");
3588
3589 let result = kernel
3590 .execute(r#"echo "John Doe 42" | read FIRST LAST AGE; echo "${FIRST} is ${AGE}""#)
3591 .await
3592 .expect("read pipeline failed");
3593
3594 assert!(result.ok(), "read failed: {}", result.err);
3595 assert!(result.out.contains("John is 42"), "output: {}", result.out);
3596 }
3597
3598 #[tokio::test]
3603 async fn test_posix_function_with_positional_params() {
3604 let kernel = Kernel::transient().expect("failed to create kernel");
3605
3606 kernel
3608 .execute(r#"greet() { echo "Hello, $1!" }"#)
3609 .await
3610 .expect("function definition failed");
3611
3612 let result = kernel
3614 .execute(r#"greet "Amy""#)
3615 .await
3616 .expect("function call failed");
3617
3618 assert!(result.ok(), "greet failed: {}", result.err);
3619 assert_eq!(result.out.trim(), "Hello, Amy!");
3620 }
3621
3622 #[tokio::test]
3623 async fn test_posix_function_multiple_args() {
3624 let kernel = Kernel::transient().expect("failed to create kernel");
3625
3626 kernel
3628 .execute(r#"add_greeting() { echo "$1 $2!" }"#)
3629 .await
3630 .expect("function definition failed");
3631
3632 let result = kernel
3634 .execute(r#"add_greeting "Hello" "World""#)
3635 .await
3636 .expect("function call failed");
3637
3638 assert!(result.ok(), "function failed: {}", result.err);
3639 assert_eq!(result.out.trim(), "Hello World!");
3640 }
3641
3642 #[tokio::test]
3643 async fn test_bash_function_with_positional_params() {
3644 let kernel = Kernel::transient().expect("failed to create kernel");
3645
3646 kernel
3648 .execute(r#"function greet { echo "Hi $1" }"#)
3649 .await
3650 .expect("function definition failed");
3651
3652 let result = kernel
3654 .execute(r#"greet "Bob""#)
3655 .await
3656 .expect("function call failed");
3657
3658 assert!(result.ok(), "greet failed: {}", result.err);
3659 assert_eq!(result.out.trim(), "Hi Bob");
3660 }
3661
3662 #[tokio::test]
3663 async fn test_shell_function_with_all_args() {
3664 let kernel = Kernel::transient().expect("failed to create kernel");
3665
3666 kernel
3668 .execute(r#"echo_all() { echo "args: $@" }"#)
3669 .await
3670 .expect("function definition failed");
3671
3672 let result = kernel
3674 .execute(r#"echo_all "a" "b" "c""#)
3675 .await
3676 .expect("function call failed");
3677
3678 assert!(result.ok(), "function failed: {}", result.err);
3679 assert_eq!(result.out.trim(), "args: a b c");
3680 }
3681
3682 #[tokio::test]
3683 async fn test_shell_function_with_arg_count() {
3684 let kernel = Kernel::transient().expect("failed to create kernel");
3685
3686 kernel
3688 .execute(r#"count_args() { echo "count: $#" }"#)
3689 .await
3690 .expect("function definition failed");
3691
3692 let result = kernel
3694 .execute(r#"count_args "x" "y" "z""#)
3695 .await
3696 .expect("function call failed");
3697
3698 assert!(result.ok(), "function failed: {}", result.err);
3699 assert_eq!(result.out.trim(), "count: 3");
3700 }
3701
3702 #[tokio::test]
3703 async fn test_shell_function_shared_scope() {
3704 let kernel = Kernel::transient().expect("failed to create kernel");
3705
3706 kernel
3708 .execute(r#"PARENT_VAR="visible""#)
3709 .await
3710 .expect("set failed");
3711
3712 kernel
3714 .execute(r#"modify_parent() {
3715 echo "saw: ${PARENT_VAR}"
3716 PARENT_VAR="changed by function"
3717 }"#)
3718 .await
3719 .expect("function definition failed");
3720
3721 let result = kernel.execute("modify_parent").await.expect("function failed");
3723
3724 assert!(
3725 result.out.contains("visible"),
3726 "Shell function should access parent scope, got: {}",
3727 result.out
3728 );
3729
3730 let var = kernel.get_var("PARENT_VAR").await;
3732 assert_eq!(
3733 var,
3734 Some(Value::String("changed by function".into())),
3735 "Shell function should modify parent scope"
3736 );
3737 }
3738
3739 #[tokio::test]
3744 async fn test_script_execution_from_path() {
3745 let kernel = Kernel::transient().expect("failed to create kernel");
3746
3747 kernel.execute(r#"mkdir "/bin""#).await.ok();
3749 kernel
3750 .execute(r#"write "/bin/hello.kai" 'echo "Hello from script!"'"#)
3751 .await
3752 .expect("write script failed");
3753
3754 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3756
3757 let result = kernel
3759 .execute("hello")
3760 .await
3761 .expect("script execution failed");
3762
3763 assert!(result.ok(), "script failed: {}", result.err);
3764 assert_eq!(result.out.trim(), "Hello from script!");
3765 }
3766
3767 #[tokio::test]
3768 async fn test_script_with_args() {
3769 let kernel = Kernel::transient().expect("failed to create kernel");
3770
3771 kernel.execute(r#"mkdir "/bin""#).await.ok();
3773 kernel
3774 .execute(r#"write "/bin/greet.kai" 'echo "Hello, $1!"'"#)
3775 .await
3776 .expect("write script failed");
3777
3778 kernel.execute(r#"PATH="/bin""#).await.expect("set PATH failed");
3780
3781 let result = kernel
3783 .execute(r#"greet "World""#)
3784 .await
3785 .expect("script execution failed");
3786
3787 assert!(result.ok(), "script failed: {}", result.err);
3788 assert_eq!(result.out.trim(), "Hello, World!");
3789 }
3790
3791 #[tokio::test]
3792 async fn test_script_not_found() {
3793 let kernel = Kernel::transient().expect("failed to create kernel");
3794
3795 kernel.execute(r#"PATH="/nonexistent""#).await.expect("set PATH failed");
3797
3798 let result = kernel
3800 .execute("noscript")
3801 .await
3802 .expect("execution failed");
3803
3804 assert!(!result.ok(), "should fail with command not found");
3805 assert_eq!(result.code, 127);
3806 assert!(result.err.contains("command not found"));
3807 }
3808
3809 #[tokio::test]
3810 async fn test_script_path_search_order() {
3811 let kernel = Kernel::transient().expect("failed to create kernel");
3812
3813 kernel.execute(r#"mkdir "/first""#).await.ok();
3816 kernel.execute(r#"mkdir "/second""#).await.ok();
3817 kernel
3818 .execute(r#"write "/first/myscript.kai" 'echo "from first"'"#)
3819 .await
3820 .expect("write failed");
3821 kernel
3822 .execute(r#"write "/second/myscript.kai" 'echo "from second"'"#)
3823 .await
3824 .expect("write failed");
3825
3826 kernel.execute(r#"PATH="/first:/second""#).await.expect("set PATH failed");
3828
3829 let result = kernel
3831 .execute("myscript")
3832 .await
3833 .expect("script execution failed");
3834
3835 assert!(result.ok(), "script failed: {}", result.err);
3836 assert_eq!(result.out.trim(), "from first");
3837 }
3838
3839 #[tokio::test]
3844 async fn test_last_exit_code_success() {
3845 let kernel = Kernel::transient().expect("failed to create kernel");
3846
3847 let result = kernel.execute("true; echo $?").await.expect("execution failed");
3849 assert!(result.out.contains("0"), "expected 0, got: {}", result.out);
3850 }
3851
3852 #[tokio::test]
3853 async fn test_last_exit_code_failure() {
3854 let kernel = Kernel::transient().expect("failed to create kernel");
3855
3856 let result = kernel.execute("false; echo $?").await.expect("execution failed");
3858 assert!(result.out.contains("1"), "expected 1, got: {}", result.out);
3859 }
3860
3861 #[tokio::test]
3862 async fn test_current_pid() {
3863 let kernel = Kernel::transient().expect("failed to create kernel");
3864
3865 let result = kernel.execute("echo $$").await.expect("execution failed");
3866 let pid: u32 = result.out.trim().parse().expect("PID should be a number");
3868 assert!(pid > 0, "PID should be positive");
3869 }
3870
3871 #[tokio::test]
3872 async fn test_unset_variable_expands_to_empty() {
3873 let kernel = Kernel::transient().expect("failed to create kernel");
3874
3875 let result = kernel.execute(r#"echo "prefix:${UNSET_VAR}:suffix""#).await.expect("execution failed");
3877 assert_eq!(result.out.trim(), "prefix::suffix");
3878 }
3879
3880 #[tokio::test]
3881 async fn test_eq_ne_operators() {
3882 let kernel = Kernel::transient().expect("failed to create kernel");
3883
3884 let result = kernel.execute(r#"if [[ 5 -eq 5 ]]; then echo "eq works"; fi"#).await.expect("execution failed");
3886 assert_eq!(result.out.trim(), "eq works");
3887
3888 let result = kernel.execute(r#"if [[ 5 -ne 3 ]]; then echo "ne works"; fi"#).await.expect("execution failed");
3890 assert_eq!(result.out.trim(), "ne works");
3891
3892 let result = kernel.execute(r#"if [[ 5 -eq 3 ]]; then echo "wrong"; else echo "correct"; fi"#).await.expect("execution failed");
3894 assert_eq!(result.out.trim(), "correct");
3895 }
3896
3897 #[tokio::test]
3898 async fn test_escaped_dollar_in_string() {
3899 let kernel = Kernel::transient().expect("failed to create kernel");
3900
3901 let result = kernel.execute(r#"echo "\$100""#).await.expect("execution failed");
3903 assert_eq!(result.out.trim(), "$100");
3904 }
3905
3906 #[tokio::test]
3907 async fn test_special_vars_in_interpolation() {
3908 let kernel = Kernel::transient().expect("failed to create kernel");
3909
3910 let result = kernel.execute(r#"true; echo "exit: $?""#).await.expect("execution failed");
3912 assert_eq!(result.out.trim(), "exit: 0");
3913
3914 let result = kernel.execute(r#"echo "pid: $$""#).await.expect("execution failed");
3916 assert!(result.out.starts_with("pid: "), "unexpected output: {}", result.out);
3917 let pid_part = result.out.trim().strip_prefix("pid: ").unwrap();
3918 let _pid: u32 = pid_part.parse().expect("PID in string should be a number");
3919 }
3920
3921 #[tokio::test]
3926 async fn test_command_subst_assignment() {
3927 let kernel = Kernel::transient().expect("failed to create kernel");
3928
3929 let result = kernel.execute(r#"X=$(echo hello); echo "$X""#).await.expect("execution failed");
3931 assert_eq!(result.out.trim(), "hello");
3932 }
3933
3934 #[tokio::test]
3935 async fn test_command_subst_with_args() {
3936 let kernel = Kernel::transient().expect("failed to create kernel");
3937
3938 let result = kernel.execute(r#"X=$(echo "a b c"); echo "$X""#).await.expect("execution failed");
3940 assert_eq!(result.out.trim(), "a b c");
3941 }
3942
3943 #[tokio::test]
3944 async fn test_command_subst_nested_vars() {
3945 let kernel = Kernel::transient().expect("failed to create kernel");
3946
3947 let result = kernel.execute(r#"Y=world; X=$(echo "hello $Y"); echo "$X""#).await.expect("execution failed");
3949 assert_eq!(result.out.trim(), "hello world");
3950 }
3951
3952 #[tokio::test]
3953 async fn test_background_job_basic() {
3954 use std::time::Duration;
3955
3956 let kernel = Kernel::new(KernelConfig::isolated()).expect("failed to create kernel");
3957
3958 let result = kernel.execute("echo hello &").await.expect("execution failed");
3960 assert!(result.ok(), "background command should succeed: {}", result.err);
3961 assert!(result.out.contains("[1]"), "should return job ID: {}", result.out);
3962
3963 tokio::time::sleep(Duration::from_millis(100)).await;
3965
3966 let status = kernel.execute("cat /v/jobs/1/status").await.expect("status check failed");
3968 assert!(status.ok(), "status should succeed: {}", status.err);
3969 assert!(
3970 status.out.contains("done:") || status.out.contains("running"),
3971 "should have valid status: {}",
3972 status.out
3973 );
3974
3975 let stdout = kernel.execute("cat /v/jobs/1/stdout").await.expect("stdout check failed");
3977 assert!(stdout.ok());
3978 assert!(stdout.out.contains("hello"));
3979 }
3980
3981 #[tokio::test]
3982 async fn test_heredoc_piped_to_command() {
3983 let kernel = Kernel::transient().expect("kernel");
3985 let result = kernel.execute("cat <<EOF | cat\nhello world\nEOF").await.expect("exec");
3986 assert!(result.ok(), "heredoc | cat failed: {}", result.err);
3987 assert_eq!(result.out.trim(), "hello world");
3988 }
3989
3990 #[tokio::test]
3991 async fn test_for_loop_glob_iterates() {
3992 let kernel = Kernel::transient().expect("kernel");
3994 let dir = format!("/tmp/kaish_test_glob_{}", std::process::id());
3995 kernel.execute(&format!("mkdir -p {dir}")).await.unwrap();
3996 kernel.execute(&format!("echo a > {dir}/a.txt")).await.unwrap();
3997 kernel.execute(&format!("echo b > {dir}/b.txt")).await.unwrap();
3998 let result = kernel.execute(&format!(r#"
3999 N=0
4000 for F in $(glob "{dir}/*.txt"); do
4001 N=$((N + 1))
4002 done
4003 echo $N
4004 "#)).await.unwrap();
4005 assert!(result.ok(), "for glob failed: {}", result.err);
4006 assert_eq!(result.out.trim(), "2", "Should iterate 2 files, got: {}", result.out);
4007 kernel.execute(&format!("rm {dir}/a.txt")).await.unwrap();
4008 kernel.execute(&format!("rm {dir}/b.txt")).await.unwrap();
4009 }
4010
4011 #[tokio::test]
4012 async fn test_command_subst_echo_not_iterable() {
4013 let kernel = Kernel::transient().expect("kernel");
4015 let result = kernel.execute(r#"
4016 N=0
4017 for X in $(echo "a b c"); do N=$((N + 1)); done
4018 echo $N
4019 "#).await.unwrap();
4020 assert!(result.ok());
4021 assert_eq!(result.out.trim(), "1", "echo should be one item: {}", result.out);
4022 }
4023
4024 #[test]
4027 fn test_accumulate_no_double_newlines() {
4028 let mut acc = ExecResult::success("line1\n");
4030 let new = ExecResult::success("line2\n");
4031 accumulate_result(&mut acc, &new);
4032 assert_eq!(acc.out, "line1\nline2\n");
4033 assert!(!acc.out.contains("\n\n"), "should not have double newlines: {:?}", acc.out);
4034 }
4035
4036 #[test]
4037 fn test_accumulate_adds_separator_when_needed() {
4038 let mut acc = ExecResult::success("line1");
4040 let new = ExecResult::success("line2");
4041 accumulate_result(&mut acc, &new);
4042 assert_eq!(acc.out, "line1\nline2");
4043 }
4044
4045 #[test]
4046 fn test_accumulate_empty_into_nonempty() {
4047 let mut acc = ExecResult::success("");
4048 let new = ExecResult::success("hello\n");
4049 accumulate_result(&mut acc, &new);
4050 assert_eq!(acc.out, "hello\n");
4051 }
4052
4053 #[test]
4054 fn test_accumulate_nonempty_into_empty() {
4055 let mut acc = ExecResult::success("hello\n");
4056 let new = ExecResult::success("");
4057 accumulate_result(&mut acc, &new);
4058 assert_eq!(acc.out, "hello\n");
4059 }
4060
4061 #[test]
4062 fn test_accumulate_stderr_no_double_newlines() {
4063 let mut acc = ExecResult::failure(1, "err1\n");
4064 let new = ExecResult::failure(1, "err2\n");
4065 accumulate_result(&mut acc, &new);
4066 assert!(!acc.err.contains("\n\n"), "stderr should not have double newlines: {:?}", acc.err);
4067 }
4068
4069 #[tokio::test]
4070 async fn test_multiple_echo_no_blank_lines() {
4071 let kernel = Kernel::transient().expect("kernel");
4072 let result = kernel
4073 .execute("echo one\necho two\necho three")
4074 .await
4075 .expect("execution failed");
4076 assert!(result.ok());
4077 assert_eq!(result.out, "one\ntwo\nthree\n");
4078 }
4079
4080 #[tokio::test]
4081 async fn test_for_loop_no_blank_lines() {
4082 let kernel = Kernel::transient().expect("kernel");
4083 let result = kernel
4084 .execute(r#"for X in a b c; do echo "item: ${X}"; done"#)
4085 .await
4086 .expect("execution failed");
4087 assert!(result.ok());
4088 assert_eq!(result.out, "item: a\nitem: b\nitem: c\n");
4089 }
4090
4091 #[tokio::test]
4092 async fn test_for_command_subst_no_blank_lines() {
4093 let kernel = Kernel::transient().expect("kernel");
4094 let result = kernel
4095 .execute(r#"for N in $(seq 1 3); do echo "n=${N}"; done"#)
4096 .await
4097 .expect("execution failed");
4098 assert!(result.ok());
4099 assert_eq!(result.out, "n=1\nn=2\nn=3\n");
4100 }
4101
4102}