iosm-cli 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.4] - 2026-04-06
11
+
12
+ ### Fixed
13
+
14
+ - **Windows shell command compatibility** — added runtime auto-adaptation for Windows command syntax, routing cmd-style commands (`%VAR%`, `dir`, `C:\...`) to `cmd.exe` and PowerShell-style commands (`$env:...`, `Get-*`) to `powershell.exe`
15
+ - **Telegram bridge command execution on Windows** — fixed failures when users sent native Windows shell commands through Telegram-driven runs while IOSM executed under bash
16
+ - **Execution path consistency** — applied shell adaptation uniformly across foreground bash execution, bash tool execution, and detached background processes
17
+
18
+ ### Tests
19
+
20
+ - Added regression suite for Windows command adaptation and explicit-shell passthrough behavior
21
+
22
+ ## [0.3.3] - 2026-04-05
23
+
24
+ ### Fixed
25
+
26
+ - **META orchestration default fan-out** — actionable META prompts without explicit `agents=N` now require at least two top-level `task` calls, preventing silent single-thread execution for orchestration-first requests
27
+ - **Locale-agnostic META fallback** — replaced language-specific complexity keyword detection with runtime orchestration compliance signals (`required task calls`, nested delegation status) for stable behavior across languages
28
+ - **MCP/external tool output rendering** — unknown external tools now use generic fallback rendering instead of empty tool boxes, restoring visible arguments/results in interactive UI
29
+
30
+ ### Tests
31
+
32
+ - Updated META prompt fallback tests to cover actionable vs question-like prompts
33
+ - Added regression test for unknown external tool result rendering in `ToolExecutionComponent`
34
+
10
35
  ## [0.3.2] - 2026-04-05
11
36
 
12
37
  ### Fixed
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  <div align="center">
2
2
 
3
- <h1>IOSM CLI 0.3.2</h1>
3
+ <h1>IOSM CLI 0.3.4</h1>
4
4
 
5
5
  <p><strong>Terminal-native AI runtime for controlled, measurable engineering work on real codebases.</strong></p>
6
6
 
7
7
  <p>
8
- <a href="https://www.npmjs.com/package/iosm-cli"><img alt="npm version" src="https://img.shields.io/npm/v/iosm-cli?style=flat-square&color=cb3837&logo=npm"></a>
9
- <a href="https://www.npmjs.com/package/iosm-cli"><img alt="npm downloads" src="https://img.shields.io/npm/dm/iosm-cli?style=flat-square&logo=npm"></a>
10
- <a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue?style=flat-square"></a>
11
- <img alt="Node.js" src="https://img.shields.io/badge/node-%3E%3D20.6.0-brightgreen?style=flat-square&logo=node.js&logoColor=white">
12
- <a href="https://github.com/rokoss21/iosm-cli"><img alt="GitHub Stars" src="https://img.shields.io/github/stars/rokoss21/iosm-cli?style=flat-square&logo=github"></a>
8
+ <a href="https://www.npmjs.com/package/iosm-cli"><img alt="npm version" src="https://badgen.net/npm/v/iosm-cli"></a>
9
+ <a href="https://www.npmjs.com/package/iosm-cli"><img alt="npm downloads" src="https://img.shields.io/npm/dw/iosm-cli.svg"></a>
10
+ <a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-blue.svg"></a>
11
+ <img alt="Node.js" src="https://img.shields.io/badge/node-%3E%3D20.6.0-brightgreen.svg">
12
+ <a href="https://github.com/rokoss21/iosm-cli"><img alt="GitHub Stars" src="https://badgen.net/github/stars/rokoss21/iosm-cli"></a>
13
13
  </p>
14
14
 
15
15
  <p>
@@ -32,13 +32,12 @@ It is not a chat interface. It is a runtime.
32
32
 
33
33
  ---
34
34
 
35
- ## ✦ What's New in 0.3.2
35
+ ## ✦ What's New in 0.3.4
36
36
 
37
- - Canonical playbook filenames are now enforced during init flows: lowercase variants (`agents.md`, `iosm.md`) are automatically renamed to `AGENTS.md` and `IOSM.md`
38
- - Standard `/init` now always reads/writes through canonical `AGENTS.md` path, including existing repositories with mixed filename casing
39
- - IOSM guide writers now canonicalize `AGENTS.md` and `IOSM.md` before updates for consistent context loading across sessions
40
- - Improved runtime profile switching continuity by keeping `IOSM.md` context loaded in resource layer while preserving profile-aware prompt/UI filtering
41
- - Added regression tests covering filename normalization and profile-switch context behavior
37
+ - Added Windows shell command auto-adapter in runtime execution paths: cmd-style commands (`%VAR%`, `dir`, `C:\...`) and PowerShell-style commands (`$env:...`, `Get-*`) are now auto-routed to the correct interpreter even when IOSM runs via bash
38
+ - Fixed Telegram/Windows remote execution friction where native Windows command syntax failed in bridge-driven shell calls
39
+ - Applied adapter behavior consistently for foreground bash execution, bash tool execution, and detached background processes
40
+ - Added regression tests for Windows command adaptation behavior
42
41
 
43
42
  ## ✦ Major Additions in 0.2.16
44
43
 
@@ -1 +1 @@
1
- {"version":3,"file":"background-processes.d.ts","sourceRoot":"","sources":["../../src/core/background-processes.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC;AAiB9F,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,uBAAuB,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,8BAA8B;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACvB;AA2KD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,uBAAuB,CA2DlG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,uBAAuB,EAAE,CAiB9F;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAOrG;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CA8BtG;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,GAAG,SAAS,CASxG;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IACT,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,GACC,8BAA8B,CAuDhC"}
1
+ {"version":3,"file":"background-processes.d.ts","sourceRoot":"","sources":["../../src/core/background-processes.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,YAAY,GAAG,SAAS,CAAC;AAiB9F,MAAM,WAAW,uBAAuB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,uBAAuB,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,aAAa,GAAG,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,8BAA8B;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACvB;AA2KD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,2BAA2B,GAAG,uBAAuB,CA4DlG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,uBAAuB,EAAE,CAiB9F;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAOrG;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CA8BtG;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,GAAG,SAAS,CASxG;AAED,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IACT,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,GACC,8BAA8B,CAuDhC"}
@@ -2,7 +2,7 @@ import { randomBytes } from "node:crypto";
2
2
  import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { spawn, spawnSync } from "child_process";
5
- import { getShellConfig, getShellEnv, killProcessTree } from "../utils/shell.js";
5
+ import { adaptCommandForShell, getShellConfig, getShellEnv, killProcessTree } from "../utils/shell.js";
6
6
  const BACKGROUND_DIR_SEGMENTS = [".iosm", "background", "processes"];
7
7
  const MAX_LOG_TAIL_BYTES = 256 * 1024;
8
8
  const BACKGROUND_STOP_FORCE_KILL_DELAY_MS = 1500;
@@ -187,9 +187,10 @@ export function startBackgroundProcess(input) {
187
187
  const metaPath = join(processesDir, `${id}.json`);
188
188
  const now = new Date().toISOString();
189
189
  const { shell, args } = getShellConfig();
190
+ const adaptedCommand = adaptCommandForShell(input.command);
190
191
  const wrappedCommand = [
191
192
  "(",
192
- input.command,
193
+ adaptedCommand,
193
194
  ")",
194
195
  "__iosm_bg_exit_code=$?",
195
196
  `__iosm_bg_finished_at=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || node -e 'console.log(new Date().toISOString())')`,
@@ -1 +1 @@
1
- {"version":3,"file":"background-processes.js","sourceRoot":"","sources":["../../src/core/background-processes.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n\tcloseSync,\n\texistsSync,\n\tfstatSync,\n\tmkdirSync,\n\topenSync,\n\treadFileSync,\n\treadSync,\n\treaddirSync,\n\trmSync,\n\tstatSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getShellConfig, getShellEnv, killProcessTree } from \"../utils/shell.js\";\n\nexport type BackgroundProcessStatus = \"running\" | \"done\" | \"error\" | \"terminated\" | \"unknown\";\n\ninterface BackgroundProcessMeta {\n\tid: string;\n\tcommand: string;\n\tcwd: string;\n\trootCwd: string;\n\tpid: number;\n\tcreatedAt: string;\n\tstartedAt: string;\n\tsource?: \"interactive\" | \"tool\";\n\trequestedStopAt?: string;\n\tlogPath: string;\n\tfinishedAtPath: string;\n\texitCodePath: string;\n}\n\nexport interface BackgroundProcessRecord {\n\tid: string;\n\tcommand: string;\n\tcwd: string;\n\trootCwd: string;\n\tpid: number;\n\tcreatedAt: string;\n\tstartedAt: string;\n\tsource?: \"interactive\" | \"tool\";\n\trequestedStopAt?: string;\n\tlogPath: string;\n\tfinishedAtPath: string;\n\texitCodePath: string;\n\tmetaPath: string;\n\tstatus: BackgroundProcessStatus;\n\tfinishedAt?: string;\n\texitCode?: number;\n}\n\nexport interface StartBackgroundProcessInput {\n\trootCwd: string;\n\tcommand: string;\n\tcwd?: string;\n\tsource?: \"interactive\" | \"tool\";\n}\n\nexport interface PruneBackgroundProcessesResult {\n\tremoved: number;\n\tremovedIds: string[];\n\tskippedRunning: number;\n\tskippedRecent: number;\n\tthresholdHours: number;\n}\n\nconst BACKGROUND_DIR_SEGMENTS = [\".iosm\", \"background\", \"processes\"] as const;\nconst MAX_LOG_TAIL_BYTES = 256 * 1024;\nconst BACKGROUND_STOP_FORCE_KILL_DELAY_MS = 1500;\nconst BACKGROUND_STOP_FALLBACK_EXIT_CODE = 143;\n\nfunction getProcessesDir(rootCwd: string): string {\n\treturn join(rootCwd, ...BACKGROUND_DIR_SEGMENTS);\n}\n\nfunction shellQuote(value: string): string {\n\treturn `'${value.replace(/'/g, `'\\\"'\\\"'`)}'`;\n}\n\nfunction isProcessAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException | undefined)?.code === \"EPERM\") return true;\n\t\treturn false;\n\t}\n}\n\nfunction parseIntegerFile(path: string): number | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst raw = readFileSync(path, \"utf8\").trim();\n\t\tif (!raw) return undefined;\n\t\tconst value = Number.parseInt(raw, 10);\n\t\treturn Number.isFinite(value) ? value : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parseTextFile(path: string): string | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst raw = readFileSync(path, \"utf8\").trim();\n\t\treturn raw || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction writeCompletionArtifacts(meta: BackgroundProcessMeta, finishedAt: string, exitCode: number): void {\n\twriteFileSync(meta.finishedAtPath, `${finishedAt}\\n`, \"utf8\");\n\twriteFileSync(meta.exitCodePath, `${exitCode}\\n`, \"utf8\");\n}\n\nfunction signalProcessTree(pid: number, signal: \"SIGTERM\" | \"SIGKILL\"): void {\n\tif (process.platform === \"win32\") {\n\t\t// /F is forced kill; omit it for soft terminate attempt.\n\t\tconst args = signal === \"SIGKILL\" ? [\"/F\", \"/T\", \"/PID\", String(pid)] : [\"/T\", \"/PID\", String(pid)];\n\t\ttry {\n\t\t\tspawnSync(\"taskkill\", args, { stdio: \"ignore\", timeout: 5000 });\n\t\t} catch {\n\t\t\t// Ignore termination errors (process may already be gone).\n\t\t}\n\t\treturn;\n\t}\n\n\ttry {\n\t\tprocess.kill(-pid, signal);\n\t} catch {\n\t\ttry {\n\t\t\tprocess.kill(pid, signal);\n\t\t} catch {\n\t\t\t// Process already dead.\n\t\t}\n\t}\n}\n\nfunction scheduleForceKill(pid: number): void {\n\tconst timer = setTimeout(() => {\n\t\tif (!isProcessAlive(pid)) return;\n\t\tkillProcessTree(pid);\n\t}, BACKGROUND_STOP_FORCE_KILL_DELAY_MS);\n\ttimer.unref();\n}\n\nfunction toRecord(meta: BackgroundProcessMeta, metaPath: string): BackgroundProcessRecord {\n\tconst finishedAt = parseTextFile(meta.finishedAtPath);\n\tconst exitCode = parseIntegerFile(meta.exitCodePath);\n\tconst alive = isProcessAlive(meta.pid);\n\tlet status: BackgroundProcessStatus = \"unknown\";\n\tif (!alive && meta.requestedStopAt) {\n\t\tstatus = \"terminated\";\n\t} else if (alive) {\n\t\tstatus = \"running\";\n\t} else if (finishedAt && typeof exitCode === \"number\") {\n\t\tstatus = exitCode === 0 ? \"done\" : \"error\";\n\t}\n\n\treturn {\n\t\tid: meta.id,\n\t\tcommand: meta.command,\n\t\tcwd: meta.cwd,\n\t\trootCwd: meta.rootCwd,\n\t\tpid: meta.pid,\n\t\tcreatedAt: meta.createdAt,\n\t\tstartedAt: meta.startedAt,\n\t\tsource: meta.source,\n\t\trequestedStopAt: meta.requestedStopAt,\n\t\tlogPath: meta.logPath,\n\t\tfinishedAtPath: meta.finishedAtPath,\n\t\texitCodePath: meta.exitCodePath,\n\t\tmetaPath,\n\t\tstatus,\n\t\tfinishedAt,\n\t\texitCode,\n\t};\n}\n\nfunction loadMeta(metaPath: string): BackgroundProcessMeta | undefined {\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(metaPath, \"utf8\")) as Partial<BackgroundProcessMeta>;\n\t\tif (\n\t\t\ttypeof parsed.id !== \"string\" ||\n\t\t\ttypeof parsed.command !== \"string\" ||\n\t\t\ttypeof parsed.cwd !== \"string\" ||\n\t\t\ttypeof parsed.rootCwd !== \"string\" ||\n\t\t\ttypeof parsed.pid !== \"number\" ||\n\t\t\ttypeof parsed.createdAt !== \"string\" ||\n\t\t\ttypeof parsed.startedAt !== \"string\" ||\n\t\t\ttypeof parsed.logPath !== \"string\" ||\n\t\t\ttypeof parsed.finishedAtPath !== \"string\" ||\n\t\t\ttypeof parsed.exitCodePath !== \"string\"\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tid: parsed.id,\n\t\t\tcommand: parsed.command,\n\t\t\tcwd: parsed.cwd,\n\t\t\trootCwd: parsed.rootCwd,\n\t\t\tpid: parsed.pid,\n\t\t\tcreatedAt: parsed.createdAt,\n\t\t\tstartedAt: parsed.startedAt,\n\t\t\tsource: parsed.source,\n\t\t\trequestedStopAt: parsed.requestedStopAt,\n\t\t\tlogPath: parsed.logPath,\n\t\t\tfinishedAtPath: parsed.finishedAtPath,\n\t\t\texitCodePath: parsed.exitCodePath,\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction saveMeta(metaPath: string, meta: BackgroundProcessMeta): void {\n\twriteFileSync(metaPath, `${JSON.stringify(meta, null, 2)}\\n`, \"utf8\");\n}\n\nfunction readFileTailUtf8(path: string, maxBytes: number): string {\n\tif (!existsSync(path)) return \"\";\n\tconst fd = openSync(path, \"r\");\n\ttry {\n\t\tconst size = fstatSync(fd).size;\n\t\tif (size <= 0) return \"\";\n\t\tconst readSize = Math.min(size, maxBytes);\n\t\tconst buffer = Buffer.alloc(readSize);\n\t\treadSync(fd, buffer, 0, readSize, size - readSize);\n\t\treturn buffer.toString(\"utf8\");\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n}\n\nexport function startBackgroundProcess(input: StartBackgroundProcessInput): BackgroundProcessRecord {\n\tconst rootCwd = resolve(input.rootCwd);\n\tconst processCwd = resolve(input.cwd ?? rootCwd);\n\tif (!existsSync(processCwd) || !statSync(processCwd).isDirectory()) {\n\t\tthrow new Error(`Background process cwd does not exist: ${processCwd}`);\n\t}\n\n\tconst processesDir = getProcessesDir(rootCwd);\n\tmkdirSync(processesDir, { recursive: true });\n\n\tconst id = `bg_${Date.now()}_${randomBytes(4).toString(\"hex\")}`;\n\tconst logPath = join(processesDir, `${id}.log`);\n\tconst finishedAtPath = join(processesDir, `${id}.finished`);\n\tconst exitCodePath = join(processesDir, `${id}.exitcode`);\n\tconst metaPath = join(processesDir, `${id}.json`);\n\tconst now = new Date().toISOString();\n\n\tconst { shell, args } = getShellConfig();\n\tconst wrappedCommand = [\n\t\t\"(\",\n\t\tinput.command,\n\t\t\")\",\n\t\t\"__iosm_bg_exit_code=$?\",\n\t\t`__iosm_bg_finished_at=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || node -e 'console.log(new Date().toISOString())')`,\n\t\t`printf \"%s\\\\n\" \"$__iosm_bg_finished_at\" > ${shellQuote(finishedAtPath)}`,\n\t\t`printf \"%s\\\\n\" \"$__iosm_bg_exit_code\" > ${shellQuote(exitCodePath)}`,\n\t\t'exit \"$__iosm_bg_exit_code\"',\n\t].join(\"\\n\");\n\n\tconst logFd = openSync(logPath, \"a\");\n\tconst child = spawn(shell, [...args, wrappedCommand], {\n\t\tcwd: processCwd,\n\t\tdetached: true,\n\t\tenv: getShellEnv(),\n\t\tstdio: [\"ignore\", logFd, logFd],\n\t});\n\tcloseSync(logFd);\n\n\tif (!child.pid) {\n\t\tthrow new Error(\"Failed to start background process: missing pid.\");\n\t}\n\n\tchild.unref();\n\n\tconst meta: BackgroundProcessMeta = {\n\t\tid,\n\t\tcommand: input.command,\n\t\tcwd: processCwd,\n\t\trootCwd,\n\t\tpid: child.pid,\n\t\tcreatedAt: now,\n\t\tstartedAt: now,\n\t\tsource: input.source,\n\t\tlogPath,\n\t\tfinishedAtPath,\n\t\texitCodePath,\n\t};\n\tsaveMeta(metaPath, meta);\n\treturn toRecord(meta, metaPath);\n}\n\nexport function listBackgroundProcesses(rootCwd: string, limit = 20): BackgroundProcessRecord[] {\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tif (!existsSync(dir)) return [];\n\tconst maxItems = Math.max(1, Math.min(200, Math.floor(limit)));\n\tconst files = readdirSync(dir)\n\t\t.filter((name) => name.endsWith(\".json\"))\n\t\t.map((name) => join(dir, name))\n\t\t.sort((a, b) => b.localeCompare(a))\n\t\t.slice(0, maxItems);\n\n\tconst records: BackgroundProcessRecord[] = [];\n\tfor (const metaPath of files) {\n\t\tconst meta = loadMeta(metaPath);\n\t\tif (!meta) continue;\n\t\trecords.push(toRecord(meta, metaPath));\n\t}\n\treturn records;\n}\n\nexport function getBackgroundProcess(rootCwd: string, id: string): BackgroundProcessRecord | undefined {\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tconst metaPath = join(dir, `${id}.json`);\n\tif (!existsSync(metaPath)) return undefined;\n\tconst meta = loadMeta(metaPath);\n\tif (!meta) return undefined;\n\treturn toRecord(meta, metaPath);\n}\n\nexport function stopBackgroundProcess(rootCwd: string, id: string): BackgroundProcessRecord | undefined {\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tconst metaPath = join(dir, `${id}.json`);\n\tconst meta = loadMeta(metaPath);\n\tif (!meta) return undefined;\n\n\tconst wasAlive = isProcessAlive(meta.pid);\n\tif (!wasAlive) {\n\t\treturn toRecord(meta, metaPath);\n\t}\n\n\tmeta.requestedStopAt = new Date().toISOString();\n\tsaveMeta(metaPath, meta);\n\n\t// Try graceful shutdown first and then enforce a hard kill in the background if needed.\n\tsignalProcessTree(meta.pid, \"SIGTERM\");\n\tscheduleForceKill(meta.pid);\n\n\tif (isProcessAlive(meta.pid)) {\n\t\treturn toRecord(meta, metaPath);\n\t}\n\n\t// Process already stopped synchronously; write fallback completion markers if shell did not flush them.\n\tconst hasFinishedAt = existsSync(meta.finishedAtPath);\n\tconst hasExitCode = existsSync(meta.exitCodePath);\n\tif (!hasFinishedAt || !hasExitCode) {\n\t\twriteCompletionArtifacts(meta, new Date().toISOString(), BACKGROUND_STOP_FALLBACK_EXIT_CODE);\n\t}\n\n\treturn toRecord(meta, metaPath);\n}\n\nexport function readBackgroundProcessLogTail(rootCwd: string, id: string, lines = 80): string | undefined {\n\tconst record = getBackgroundProcess(rootCwd, id);\n\tif (!record) return undefined;\n\tconst maxLines = Math.max(1, Math.min(1000, Math.floor(lines)));\n\tconst tail = readFileTailUtf8(record.logPath, MAX_LOG_TAIL_BYTES);\n\tif (!tail) return \"\";\n\tconst chunks = tail.split(/\\r?\\n/);\n\tconst sliced = chunks.slice(-maxLines);\n\treturn sliced.join(\"\\n\").trimEnd();\n}\n\nexport function pruneBackgroundProcesses(\n\trootCwd: string,\n\toptions?: {\n\t\tmaxAgeHours?: number;\n\t\tlimit?: number;\n\t},\n): PruneBackgroundProcessesResult {\n\tconst thresholdHoursRaw = options?.maxAgeHours;\n\tconst thresholdHours =\n\t\ttypeof thresholdHoursRaw === \"number\" && Number.isFinite(thresholdHoursRaw)\n\t\t\t? Math.max(1, Math.floor(thresholdHoursRaw))\n\t\t\t: 168;\n\tconst limitRaw = options?.limit;\n\tconst limit = typeof limitRaw === \"number\" && Number.isFinite(limitRaw) ? Math.max(1, Math.floor(limitRaw)) : 1000;\n\tconst thresholdMs = thresholdHours * 60 * 60 * 1000;\n\tconst now = Date.now();\n\tconst result: PruneBackgroundProcessesResult = {\n\t\tremoved: 0,\n\t\tremovedIds: [],\n\t\tskippedRunning: 0,\n\t\tskippedRecent: 0,\n\t\tthresholdHours,\n\t};\n\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tif (!existsSync(dir)) return result;\n\n\tconst metaPaths = readdirSync(dir)\n\t\t.filter((name) => name.endsWith(\".json\"))\n\t\t.map((name) => join(dir, name))\n\t\t.sort((a, b) => b.localeCompare(a))\n\t\t.slice(0, limit);\n\n\tfor (const metaPath of metaPaths) {\n\t\tconst meta = loadMeta(metaPath);\n\t\tif (!meta) continue;\n\t\tconst record = toRecord(meta, metaPath);\n\t\tif (record.status === \"running\") {\n\t\t\tresult.skippedRunning += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst createdAtMs = Date.parse(record.createdAt);\n\t\tif (Number.isFinite(createdAtMs) && now - createdAtMs < thresholdMs) {\n\t\t\tresult.skippedRecent += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst filesToDelete = [record.metaPath, record.logPath, record.finishedAtPath, record.exitCodePath];\n\t\tfor (const filePath of filesToDelete) {\n\t\t\ttry {\n\t\t\t\trmSync(filePath, { force: true });\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors for best-effort pruning.\n\t\t\t}\n\t\t}\n\t\tresult.removed += 1;\n\t\tresult.removedIds.push(record.id);\n\t}\n\n\treturn result;\n}\n"]}
1
+ {"version":3,"file":"background-processes.js","sourceRoot":"","sources":["../../src/core/background-processes.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport {\n\tcloseSync,\n\texistsSync,\n\tfstatSync,\n\tmkdirSync,\n\topenSync,\n\treadFileSync,\n\treadSync,\n\treaddirSync,\n\trmSync,\n\tstatSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { adaptCommandForShell, getShellConfig, getShellEnv, killProcessTree } from \"../utils/shell.js\";\n\nexport type BackgroundProcessStatus = \"running\" | \"done\" | \"error\" | \"terminated\" | \"unknown\";\n\ninterface BackgroundProcessMeta {\n\tid: string;\n\tcommand: string;\n\tcwd: string;\n\trootCwd: string;\n\tpid: number;\n\tcreatedAt: string;\n\tstartedAt: string;\n\tsource?: \"interactive\" | \"tool\";\n\trequestedStopAt?: string;\n\tlogPath: string;\n\tfinishedAtPath: string;\n\texitCodePath: string;\n}\n\nexport interface BackgroundProcessRecord {\n\tid: string;\n\tcommand: string;\n\tcwd: string;\n\trootCwd: string;\n\tpid: number;\n\tcreatedAt: string;\n\tstartedAt: string;\n\tsource?: \"interactive\" | \"tool\";\n\trequestedStopAt?: string;\n\tlogPath: string;\n\tfinishedAtPath: string;\n\texitCodePath: string;\n\tmetaPath: string;\n\tstatus: BackgroundProcessStatus;\n\tfinishedAt?: string;\n\texitCode?: number;\n}\n\nexport interface StartBackgroundProcessInput {\n\trootCwd: string;\n\tcommand: string;\n\tcwd?: string;\n\tsource?: \"interactive\" | \"tool\";\n}\n\nexport interface PruneBackgroundProcessesResult {\n\tremoved: number;\n\tremovedIds: string[];\n\tskippedRunning: number;\n\tskippedRecent: number;\n\tthresholdHours: number;\n}\n\nconst BACKGROUND_DIR_SEGMENTS = [\".iosm\", \"background\", \"processes\"] as const;\nconst MAX_LOG_TAIL_BYTES = 256 * 1024;\nconst BACKGROUND_STOP_FORCE_KILL_DELAY_MS = 1500;\nconst BACKGROUND_STOP_FALLBACK_EXIT_CODE = 143;\n\nfunction getProcessesDir(rootCwd: string): string {\n\treturn join(rootCwd, ...BACKGROUND_DIR_SEGMENTS);\n}\n\nfunction shellQuote(value: string): string {\n\treturn `'${value.replace(/'/g, `'\\\"'\\\"'`)}'`;\n}\n\nfunction isProcessAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException | undefined)?.code === \"EPERM\") return true;\n\t\treturn false;\n\t}\n}\n\nfunction parseIntegerFile(path: string): number | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst raw = readFileSync(path, \"utf8\").trim();\n\t\tif (!raw) return undefined;\n\t\tconst value = Number.parseInt(raw, 10);\n\t\treturn Number.isFinite(value) ? value : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction parseTextFile(path: string): string | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst raw = readFileSync(path, \"utf8\").trim();\n\t\treturn raw || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction writeCompletionArtifacts(meta: BackgroundProcessMeta, finishedAt: string, exitCode: number): void {\n\twriteFileSync(meta.finishedAtPath, `${finishedAt}\\n`, \"utf8\");\n\twriteFileSync(meta.exitCodePath, `${exitCode}\\n`, \"utf8\");\n}\n\nfunction signalProcessTree(pid: number, signal: \"SIGTERM\" | \"SIGKILL\"): void {\n\tif (process.platform === \"win32\") {\n\t\t// /F is forced kill; omit it for soft terminate attempt.\n\t\tconst args = signal === \"SIGKILL\" ? [\"/F\", \"/T\", \"/PID\", String(pid)] : [\"/T\", \"/PID\", String(pid)];\n\t\ttry {\n\t\t\tspawnSync(\"taskkill\", args, { stdio: \"ignore\", timeout: 5000 });\n\t\t} catch {\n\t\t\t// Ignore termination errors (process may already be gone).\n\t\t}\n\t\treturn;\n\t}\n\n\ttry {\n\t\tprocess.kill(-pid, signal);\n\t} catch {\n\t\ttry {\n\t\t\tprocess.kill(pid, signal);\n\t\t} catch {\n\t\t\t// Process already dead.\n\t\t}\n\t}\n}\n\nfunction scheduleForceKill(pid: number): void {\n\tconst timer = setTimeout(() => {\n\t\tif (!isProcessAlive(pid)) return;\n\t\tkillProcessTree(pid);\n\t}, BACKGROUND_STOP_FORCE_KILL_DELAY_MS);\n\ttimer.unref();\n}\n\nfunction toRecord(meta: BackgroundProcessMeta, metaPath: string): BackgroundProcessRecord {\n\tconst finishedAt = parseTextFile(meta.finishedAtPath);\n\tconst exitCode = parseIntegerFile(meta.exitCodePath);\n\tconst alive = isProcessAlive(meta.pid);\n\tlet status: BackgroundProcessStatus = \"unknown\";\n\tif (!alive && meta.requestedStopAt) {\n\t\tstatus = \"terminated\";\n\t} else if (alive) {\n\t\tstatus = \"running\";\n\t} else if (finishedAt && typeof exitCode === \"number\") {\n\t\tstatus = exitCode === 0 ? \"done\" : \"error\";\n\t}\n\n\treturn {\n\t\tid: meta.id,\n\t\tcommand: meta.command,\n\t\tcwd: meta.cwd,\n\t\trootCwd: meta.rootCwd,\n\t\tpid: meta.pid,\n\t\tcreatedAt: meta.createdAt,\n\t\tstartedAt: meta.startedAt,\n\t\tsource: meta.source,\n\t\trequestedStopAt: meta.requestedStopAt,\n\t\tlogPath: meta.logPath,\n\t\tfinishedAtPath: meta.finishedAtPath,\n\t\texitCodePath: meta.exitCodePath,\n\t\tmetaPath,\n\t\tstatus,\n\t\tfinishedAt,\n\t\texitCode,\n\t};\n}\n\nfunction loadMeta(metaPath: string): BackgroundProcessMeta | undefined {\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(metaPath, \"utf8\")) as Partial<BackgroundProcessMeta>;\n\t\tif (\n\t\t\ttypeof parsed.id !== \"string\" ||\n\t\t\ttypeof parsed.command !== \"string\" ||\n\t\t\ttypeof parsed.cwd !== \"string\" ||\n\t\t\ttypeof parsed.rootCwd !== \"string\" ||\n\t\t\ttypeof parsed.pid !== \"number\" ||\n\t\t\ttypeof parsed.createdAt !== \"string\" ||\n\t\t\ttypeof parsed.startedAt !== \"string\" ||\n\t\t\ttypeof parsed.logPath !== \"string\" ||\n\t\t\ttypeof parsed.finishedAtPath !== \"string\" ||\n\t\t\ttypeof parsed.exitCodePath !== \"string\"\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn {\n\t\t\tid: parsed.id,\n\t\t\tcommand: parsed.command,\n\t\t\tcwd: parsed.cwd,\n\t\t\trootCwd: parsed.rootCwd,\n\t\t\tpid: parsed.pid,\n\t\t\tcreatedAt: parsed.createdAt,\n\t\t\tstartedAt: parsed.startedAt,\n\t\t\tsource: parsed.source,\n\t\t\trequestedStopAt: parsed.requestedStopAt,\n\t\t\tlogPath: parsed.logPath,\n\t\t\tfinishedAtPath: parsed.finishedAtPath,\n\t\t\texitCodePath: parsed.exitCodePath,\n\t\t};\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction saveMeta(metaPath: string, meta: BackgroundProcessMeta): void {\n\twriteFileSync(metaPath, `${JSON.stringify(meta, null, 2)}\\n`, \"utf8\");\n}\n\nfunction readFileTailUtf8(path: string, maxBytes: number): string {\n\tif (!existsSync(path)) return \"\";\n\tconst fd = openSync(path, \"r\");\n\ttry {\n\t\tconst size = fstatSync(fd).size;\n\t\tif (size <= 0) return \"\";\n\t\tconst readSize = Math.min(size, maxBytes);\n\t\tconst buffer = Buffer.alloc(readSize);\n\t\treadSync(fd, buffer, 0, readSize, size - readSize);\n\t\treturn buffer.toString(\"utf8\");\n\t} finally {\n\t\tcloseSync(fd);\n\t}\n}\n\nexport function startBackgroundProcess(input: StartBackgroundProcessInput): BackgroundProcessRecord {\n\tconst rootCwd = resolve(input.rootCwd);\n\tconst processCwd = resolve(input.cwd ?? rootCwd);\n\tif (!existsSync(processCwd) || !statSync(processCwd).isDirectory()) {\n\t\tthrow new Error(`Background process cwd does not exist: ${processCwd}`);\n\t}\n\n\tconst processesDir = getProcessesDir(rootCwd);\n\tmkdirSync(processesDir, { recursive: true });\n\n\tconst id = `bg_${Date.now()}_${randomBytes(4).toString(\"hex\")}`;\n\tconst logPath = join(processesDir, `${id}.log`);\n\tconst finishedAtPath = join(processesDir, `${id}.finished`);\n\tconst exitCodePath = join(processesDir, `${id}.exitcode`);\n\tconst metaPath = join(processesDir, `${id}.json`);\n\tconst now = new Date().toISOString();\n\n\tconst { shell, args } = getShellConfig();\n\tconst adaptedCommand = adaptCommandForShell(input.command);\n\tconst wrappedCommand = [\n\t\t\"(\",\n\t\tadaptedCommand,\n\t\t\")\",\n\t\t\"__iosm_bg_exit_code=$?\",\n\t\t`__iosm_bg_finished_at=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || node -e 'console.log(new Date().toISOString())')`,\n\t\t`printf \"%s\\\\n\" \"$__iosm_bg_finished_at\" > ${shellQuote(finishedAtPath)}`,\n\t\t`printf \"%s\\\\n\" \"$__iosm_bg_exit_code\" > ${shellQuote(exitCodePath)}`,\n\t\t'exit \"$__iosm_bg_exit_code\"',\n\t].join(\"\\n\");\n\n\tconst logFd = openSync(logPath, \"a\");\n\tconst child = spawn(shell, [...args, wrappedCommand], {\n\t\tcwd: processCwd,\n\t\tdetached: true,\n\t\tenv: getShellEnv(),\n\t\tstdio: [\"ignore\", logFd, logFd],\n\t});\n\tcloseSync(logFd);\n\n\tif (!child.pid) {\n\t\tthrow new Error(\"Failed to start background process: missing pid.\");\n\t}\n\n\tchild.unref();\n\n\tconst meta: BackgroundProcessMeta = {\n\t\tid,\n\t\tcommand: input.command,\n\t\tcwd: processCwd,\n\t\trootCwd,\n\t\tpid: child.pid,\n\t\tcreatedAt: now,\n\t\tstartedAt: now,\n\t\tsource: input.source,\n\t\tlogPath,\n\t\tfinishedAtPath,\n\t\texitCodePath,\n\t};\n\tsaveMeta(metaPath, meta);\n\treturn toRecord(meta, metaPath);\n}\n\nexport function listBackgroundProcesses(rootCwd: string, limit = 20): BackgroundProcessRecord[] {\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tif (!existsSync(dir)) return [];\n\tconst maxItems = Math.max(1, Math.min(200, Math.floor(limit)));\n\tconst files = readdirSync(dir)\n\t\t.filter((name) => name.endsWith(\".json\"))\n\t\t.map((name) => join(dir, name))\n\t\t.sort((a, b) => b.localeCompare(a))\n\t\t.slice(0, maxItems);\n\n\tconst records: BackgroundProcessRecord[] = [];\n\tfor (const metaPath of files) {\n\t\tconst meta = loadMeta(metaPath);\n\t\tif (!meta) continue;\n\t\trecords.push(toRecord(meta, metaPath));\n\t}\n\treturn records;\n}\n\nexport function getBackgroundProcess(rootCwd: string, id: string): BackgroundProcessRecord | undefined {\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tconst metaPath = join(dir, `${id}.json`);\n\tif (!existsSync(metaPath)) return undefined;\n\tconst meta = loadMeta(metaPath);\n\tif (!meta) return undefined;\n\treturn toRecord(meta, metaPath);\n}\n\nexport function stopBackgroundProcess(rootCwd: string, id: string): BackgroundProcessRecord | undefined {\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tconst metaPath = join(dir, `${id}.json`);\n\tconst meta = loadMeta(metaPath);\n\tif (!meta) return undefined;\n\n\tconst wasAlive = isProcessAlive(meta.pid);\n\tif (!wasAlive) {\n\t\treturn toRecord(meta, metaPath);\n\t}\n\n\tmeta.requestedStopAt = new Date().toISOString();\n\tsaveMeta(metaPath, meta);\n\n\t// Try graceful shutdown first and then enforce a hard kill in the background if needed.\n\tsignalProcessTree(meta.pid, \"SIGTERM\");\n\tscheduleForceKill(meta.pid);\n\n\tif (isProcessAlive(meta.pid)) {\n\t\treturn toRecord(meta, metaPath);\n\t}\n\n\t// Process already stopped synchronously; write fallback completion markers if shell did not flush them.\n\tconst hasFinishedAt = existsSync(meta.finishedAtPath);\n\tconst hasExitCode = existsSync(meta.exitCodePath);\n\tif (!hasFinishedAt || !hasExitCode) {\n\t\twriteCompletionArtifacts(meta, new Date().toISOString(), BACKGROUND_STOP_FALLBACK_EXIT_CODE);\n\t}\n\n\treturn toRecord(meta, metaPath);\n}\n\nexport function readBackgroundProcessLogTail(rootCwd: string, id: string, lines = 80): string | undefined {\n\tconst record = getBackgroundProcess(rootCwd, id);\n\tif (!record) return undefined;\n\tconst maxLines = Math.max(1, Math.min(1000, Math.floor(lines)));\n\tconst tail = readFileTailUtf8(record.logPath, MAX_LOG_TAIL_BYTES);\n\tif (!tail) return \"\";\n\tconst chunks = tail.split(/\\r?\\n/);\n\tconst sliced = chunks.slice(-maxLines);\n\treturn sliced.join(\"\\n\").trimEnd();\n}\n\nexport function pruneBackgroundProcesses(\n\trootCwd: string,\n\toptions?: {\n\t\tmaxAgeHours?: number;\n\t\tlimit?: number;\n\t},\n): PruneBackgroundProcessesResult {\n\tconst thresholdHoursRaw = options?.maxAgeHours;\n\tconst thresholdHours =\n\t\ttypeof thresholdHoursRaw === \"number\" && Number.isFinite(thresholdHoursRaw)\n\t\t\t? Math.max(1, Math.floor(thresholdHoursRaw))\n\t\t\t: 168;\n\tconst limitRaw = options?.limit;\n\tconst limit = typeof limitRaw === \"number\" && Number.isFinite(limitRaw) ? Math.max(1, Math.floor(limitRaw)) : 1000;\n\tconst thresholdMs = thresholdHours * 60 * 60 * 1000;\n\tconst now = Date.now();\n\tconst result: PruneBackgroundProcessesResult = {\n\t\tremoved: 0,\n\t\tremovedIds: [],\n\t\tskippedRunning: 0,\n\t\tskippedRecent: 0,\n\t\tthresholdHours,\n\t};\n\n\tconst dir = getProcessesDir(resolve(rootCwd));\n\tif (!existsSync(dir)) return result;\n\n\tconst metaPaths = readdirSync(dir)\n\t\t.filter((name) => name.endsWith(\".json\"))\n\t\t.map((name) => join(dir, name))\n\t\t.sort((a, b) => b.localeCompare(a))\n\t\t.slice(0, limit);\n\n\tfor (const metaPath of metaPaths) {\n\t\tconst meta = loadMeta(metaPath);\n\t\tif (!meta) continue;\n\t\tconst record = toRecord(meta, metaPath);\n\t\tif (record.status === \"running\") {\n\t\t\tresult.skippedRunning += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst createdAtMs = Date.parse(record.createdAt);\n\t\tif (Number.isFinite(createdAtMs) && now - createdAtMs < thresholdMs) {\n\t\t\tresult.skippedRecent += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst filesToDelete = [record.metaPath, record.logPath, record.finishedAtPath, record.exitCodePath];\n\t\tfor (const filePath of filesToDelete) {\n\t\t\ttry {\n\t\t\t\trmSync(filePath, { force: true });\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors for best-effort pruning.\n\t\t\t}\n\t\t}\n\t\tresult.removed += 1;\n\t\tresult.removedIds.push(record.id);\n\t}\n\n\treturn result;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAyH/F;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAsFrB"}
1
+ {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0H/F;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAsFrB"}
@@ -11,7 +11,7 @@ import { tmpdir } from "node:os";
11
11
  import { join } from "node:path";
12
12
  import { spawn } from "child_process";
13
13
  import stripAnsi from "strip-ansi";
14
- import { getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from "../utils/shell.js";
14
+ import { adaptCommandForShell, getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from "../utils/shell.js";
15
15
  import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
16
16
  // ============================================================================
17
17
  // Implementation
@@ -33,7 +33,8 @@ import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
33
33
  export function executeBash(command, options) {
34
34
  return new Promise((resolve, reject) => {
35
35
  const { shell, args } = getShellConfig();
36
- const child = spawn(shell, [...args, command], {
36
+ const adaptedCommand = adaptCommandForShell(command);
37
+ const child = spawn(shell, [...args, adaptedCommand], {
37
38
  cwd: options?.cwd,
38
39
  detached: true,
39
40
  env: getShellEnv(),
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type ChildProcess, spawn } from \"child_process\";\nimport stripAnsi from \"strip-ansi\";\nimport { getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from \"../utils/shell.js\";\nimport type { BashOperations } from \"./tools/bash.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n\t/** Working directory for command execution. Defaults to process.cwd() */\n\tcwd?: string;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n\t/** Background process id (present only for detached background execution). */\n\tbackgroundTaskId?: string;\n\t/** Path to JSON status metadata for a background process. */\n\tbackgroundStatusPath?: string;\n\t/** Path to log file for a background process. */\n\tbackgroundLogPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command with optional streaming and cancellation support.\n *\n * Features:\n * - Streams sanitized output via onChunk callback\n * - Writes large output to temp file for later retrieval\n * - Supports cancellation via AbortSignal\n * - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)\n * - Truncates output if it exceeds the default max bytes\n *\n * @param command - The bash command to execute\n * @param options - Optional streaming callback and abort signal\n * @returns Promise resolving to execution result\n */\nexport function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst { shell, args } = getShellConfig();\n\t\tconst child: ChildProcess = spawn(shell, [...args, command], {\n\t\t\tcwd: options?.cwd,\n\t\t\tdetached: true,\n\t\t\tenv: getShellEnv(),\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\t// Track sanitized output for truncation\n\t\tconst outputChunks: string[] = [];\n\t\tlet outputBytes = 0;\n\t\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t// Temp file for large output\n\t\tlet tempFilePath: string | undefined;\n\t\tlet tempFileStream: WriteStream | undefined;\n\t\tlet totalBytes = 0;\n\n\t\t// Handle abort signal\n\t\tconst abortHandler = () => {\n\t\t\tif (child.pid) {\n\t\t\t\tkillProcessTree(child.pid);\n\t\t\t}\n\t\t};\n\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\t// Already aborted, don't even start\n\t\t\t\tchild.kill();\n\t\t\t\tresolve({\n\t\t\t\t\toutput: \"\",\n\t\t\t\t\texitCode: undefined,\n\t\t\t\t\tcancelled: true,\n\t\t\t\t\ttruncated: false,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toptions.signal.addEventListener(\"abort\", abortHandler, { once: true });\n\t\t}\n\n\t\tconst decoder = new TextDecoder();\n\n\t\tconst handleData = (data: Buffer) => {\n\t\t\ttotalBytes += data.length;\n\n\t\t\t// Sanitize once at the source: strip ANSI, replace binary garbage, normalize newlines\n\t\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t\t// Start writing to temp file if exceeds threshold\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\t\ttempFilePath = join(tmpdir(), `iosm-bash-${id}.log`);\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t// Write already-buffered chunks to temp file\n\t\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.write(text);\n\t\t\t}\n\n\t\t\t// Keep rolling buffer of sanitized text\n\t\t\toutputChunks.push(text);\n\t\t\toutputBytes += text.length;\n\t\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\t\tconst removed = outputChunks.shift()!;\n\t\t\t\toutputBytes -= removed.length;\n\t\t\t}\n\n\t\t\t// Stream to callback if provided\n\t\t\tif (options?.onChunk) {\n\t\t\t\toptions.onChunk(text);\n\t\t\t}\n\t\t};\n\n\t\tchild.stdout?.on(\"data\", handleData);\n\t\tchild.stderr?.on(\"data\", handleData);\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Combine buffered chunks for truncation (already sanitized)\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\n\t\t\t// code === null means killed (cancelled)\n\t\t\tconst cancelled = code === null;\n\n\t\t\tresolve({\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: cancelled ? undefined : code,\n\t\t\t\tcancelled,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t});\n\t\t});\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\treject(err);\n\t\t});\n\t});\n}\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\ttempFilePath = join(tmpdir(), `iosm-bash-${id}.log`);\n\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\ttempFileStream.write(chunk);\n\t\t\t}\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
1
+ {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type ChildProcess, spawn } from \"child_process\";\nimport stripAnsi from \"strip-ansi\";\nimport { adaptCommandForShell, getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from \"../utils/shell.js\";\nimport type { BashOperations } from \"./tools/bash.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n\t/** Working directory for command execution. Defaults to process.cwd() */\n\tcwd?: string;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n\t/** Background process id (present only for detached background execution). */\n\tbackgroundTaskId?: string;\n\t/** Path to JSON status metadata for a background process. */\n\tbackgroundStatusPath?: string;\n\t/** Path to log file for a background process. */\n\tbackgroundLogPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command with optional streaming and cancellation support.\n *\n * Features:\n * - Streams sanitized output via onChunk callback\n * - Writes large output to temp file for later retrieval\n * - Supports cancellation via AbortSignal\n * - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)\n * - Truncates output if it exceeds the default max bytes\n *\n * @param command - The bash command to execute\n * @param options - Optional streaming callback and abort signal\n * @returns Promise resolving to execution result\n */\nexport function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst { shell, args } = getShellConfig();\n\t\tconst adaptedCommand = adaptCommandForShell(command);\n\t\tconst child: ChildProcess = spawn(shell, [...args, adaptedCommand], {\n\t\t\tcwd: options?.cwd,\n\t\t\tdetached: true,\n\t\t\tenv: getShellEnv(),\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\t// Track sanitized output for truncation\n\t\tconst outputChunks: string[] = [];\n\t\tlet outputBytes = 0;\n\t\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t// Temp file for large output\n\t\tlet tempFilePath: string | undefined;\n\t\tlet tempFileStream: WriteStream | undefined;\n\t\tlet totalBytes = 0;\n\n\t\t// Handle abort signal\n\t\tconst abortHandler = () => {\n\t\t\tif (child.pid) {\n\t\t\t\tkillProcessTree(child.pid);\n\t\t\t}\n\t\t};\n\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\t// Already aborted, don't even start\n\t\t\t\tchild.kill();\n\t\t\t\tresolve({\n\t\t\t\t\toutput: \"\",\n\t\t\t\t\texitCode: undefined,\n\t\t\t\t\tcancelled: true,\n\t\t\t\t\ttruncated: false,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toptions.signal.addEventListener(\"abort\", abortHandler, { once: true });\n\t\t}\n\n\t\tconst decoder = new TextDecoder();\n\n\t\tconst handleData = (data: Buffer) => {\n\t\t\ttotalBytes += data.length;\n\n\t\t\t// Sanitize once at the source: strip ANSI, replace binary garbage, normalize newlines\n\t\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t\t// Start writing to temp file if exceeds threshold\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\t\ttempFilePath = join(tmpdir(), `iosm-bash-${id}.log`);\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t// Write already-buffered chunks to temp file\n\t\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.write(text);\n\t\t\t}\n\n\t\t\t// Keep rolling buffer of sanitized text\n\t\t\toutputChunks.push(text);\n\t\t\toutputBytes += text.length;\n\t\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\t\tconst removed = outputChunks.shift()!;\n\t\t\t\toutputBytes -= removed.length;\n\t\t\t}\n\n\t\t\t// Stream to callback if provided\n\t\t\tif (options?.onChunk) {\n\t\t\t\toptions.onChunk(text);\n\t\t\t}\n\t\t};\n\n\t\tchild.stdout?.on(\"data\", handleData);\n\t\tchild.stderr?.on(\"data\", handleData);\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Combine buffered chunks for truncation (already sanitized)\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\n\t\t\t// code === null means killed (cancelled)\n\t\t\tconst cancelled = code === null;\n\n\t\t\tresolve({\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: cancelled ? undefined : code,\n\t\t\t\tcancelled,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t});\n\t\t});\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\treject(err);\n\t\t});\n\t});\n}\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\ttempFilePath = join(tmpdir(), `iosm-bash-${id}.log`);\n\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\ttempFileStream.write(chunk);\n\t\t\t}\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAMtD,OAAO,EAAoD,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AACtH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAU5D,QAAA,MAAM,UAAU;;;;EAMd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;;OAMG;IACH,IAAI,EAAE,CACL,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;KACxB,KACG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAC1C;AAmGD,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACvB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,gBAAgB,CAAC;AA6B5E,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,oGAAoG;IACpG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CA0MnG;AAED,0EAA0E;AAC1E,eAAO,MAAM,QAAQ;;;;QAAgC,CAAC"}
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAMtD,OAAO,EAAoD,KAAK,gBAAgB,EAAgB,MAAM,eAAe,CAAC;AACtH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAU5D,QAAA,MAAM,UAAU;;;;EAMd,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,UAAU,CAAC,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;;OAMG;IACH,IAAI,EAAE,CACL,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;KACxB,KACG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAC1C;AAmGD,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;CACvB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,gBAAgB,CAAC;AAgC5E,MAAM,WAAW,eAAe;IAC/B,oEAAoE;IACpE,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,oGAAoG;IACpG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC,OAAO,UAAU,CAAC,CA0MnG;AAED,0EAA0E;AAC1E,eAAO,MAAM,QAAQ;;;;QAAgC,CAAC"}
@@ -5,7 +5,7 @@ import { join } from "node:path";
5
5
  import { Type } from "@sinclair/typebox";
6
6
  import { spawn } from "child_process";
7
7
  import stripAnsi from "strip-ansi";
8
- import { getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from "../../utils/shell.js";
8
+ import { adaptCommandForShell, getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from "../../utils/shell.js";
9
9
  import { startBackgroundProcess } from "../background-processes.js";
10
10
  import { isSandboxEnabledFromEnv, wrapCommandWithSandbox } from "../sandbox/executor.js";
11
11
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateTail } from "./truncate.js";
@@ -128,7 +128,10 @@ function resolveSpawnContext(command, cwd, spawnHook) {
128
128
  if (modified.cwd !== baseContext.cwd) {
129
129
  process.stderr.write(`[iosm] Extension modified bash cwd: ${baseContext.cwd} → ${modified.cwd}\n`);
130
130
  }
131
- return modified;
131
+ return {
132
+ ...modified,
133
+ command: adaptCommandForShell(modified.command),
134
+ };
132
135
  }
133
136
  export function createBashTool(cwd, options) {
134
137
  const ops = options?.operations ?? defaultBashOperations;
@@ -1 +1 @@
1
- {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream, existsSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport stripAnsi from \"strip-ansi\";\nimport { getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from \"../../utils/shell.js\";\nimport { startBackgroundProcess } from \"../background-processes.js\";\nimport { isSandboxEnabledFromEnv, wrapCommandWithSandbox } from \"../sandbox/executor.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\nimport type { ToolPermissionGuard } from \"./permissions.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `iosm-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n\trun_in_background: Type.Optional(\n\t\tType.Boolean({ description: \"Run command in detached background mode and return task id immediately\" }),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tbackgroundTaskId?: string;\n\tbackgroundStatusPath?: string;\n\tbackgroundLogPath?: string;\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (e.g., SSH).\n */\nexport interface BashOperations {\n\t/**\n\t * Execute a command and stream output.\n\t * @param command - The command to execute\n\t * @param cwd - Working directory\n\t * @param options - Execution options\n\t * @returns Promise resolving to exit code (null if killed)\n\t */\n\texec: (\n\t\tcommand: string,\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\ttimeout?: number;\n\t\t\tenv?: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\n/**\n * Default bash operations using local shell\n */\nconst defaultBashOperations: BashOperations = {\n\texec: (command, cwd, { onData, signal, timeout, env }) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst { shell, args } = getShellConfig();\n\n\t\t\tif (!existsSync(cwd)) {\n\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}\\nCannot execute bash commands.`));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet wrapped;\n\t\t\ttry {\n\t\t\t\twrapped = wrapCommandWithSandbox({\n\t\t\t\t\tcommand: shell,\n\t\t\t\t\targs: [...args, command],\n\t\t\t\t\tcwd,\n\t\t\t\t\tenabled: isSandboxEnabledFromEnv(),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst child = spawn(wrapped.command, wrapped.args, {\n\t\t\t\tcwd,\n\t\t\t\tdetached: true,\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout if provided\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tif (child.pid) {\n\t\t\t\t\t\tkillProcessTree(child.pid);\n\t\t\t\t\t}\n\t\t\t\t}, timeout * 1000);\n\t\t\t}\n\n\t\t\t// Stream stdout and stderr\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", onData);\n\t\t\t}\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", onData);\n\t\t\t}\n\n\t\t\t// Handle shell spawn errors\n\t\t\tchild.on(\"error\", (err) => {\n\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\treject(err);\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\tkillProcessTree(child.pid);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\treject(new Error(`timeout:${timeout}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve({ exitCode: code });\n\t\t\t});\n\t\t});\n\t},\n};\n\nexport interface BashSpawnContext {\n\tcommand: string;\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n}\n\nexport type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;\n\nfunction sanitizeBashChunk(text: string): string {\n\treturn sanitizeBinaryOutput(stripAnsi(text)).replace(/\\r/g, \"\");\n}\n\nfunction resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {\n\tconst baseContext: BashSpawnContext = {\n\t\tcommand,\n\t\tcwd,\n\t\tenv: { ...getShellEnv() },\n\t};\n\n\tif (!spawnHook) return baseContext;\n\n\tconst modified = spawnHook(baseContext);\n\n\tif (modified.command !== baseContext.command) {\n\t\tprocess.stderr.write(\n\t\t\t`[iosm] Extension modified bash command:\\n before: ${baseContext.command}\\n after: ${modified.command}\\n`,\n\t\t);\n\t}\n\tif (modified.cwd !== baseContext.cwd) {\n\t\tprocess.stderr.write(`[iosm] Extension modified bash cwd: ${baseContext.cwd} → ${modified.cwd}\\n`);\n\t}\n\n\treturn modified;\n}\n\nexport interface BashToolOptions {\n\t/** Custom operations for command execution. Default: local shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (e.g., \"shopt -s expand_aliases\" for alias support) */\n\tcommandPrefix?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n\t/** Optional permission guard executed before command starts */\n\tpermissionGuard?: ToolPermissionGuard;\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\tconst ops = options?.operations ?? defaultBashOperations;\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst permissionGuard = options?.permissionGuard;\n\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout, run_in_background }: { command: string; timeout?: number; run_in_background?: boolean },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t) => {\n\t\t\tif (permissionGuard) {\n\t\t\t\tconst allowed = await permissionGuard({\n\t\t\t\t\ttoolName: \"bash\",\n\t\t\t\t\tcwd,\n\t\t\t\t\tinput: { command, timeout, run_in_background },\n\t\t\t\t\tsummary: command.trim().slice(0, 200),\n\t\t\t\t});\n\t\t\t\tif (!allowed) {\n\t\t\t\t\tthrow new Error(\"Permission denied for bash command.\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply command prefix if configured (e.g., \"shopt -s expand_aliases\" for alias support)\n\t\t\tconst resolvedCommand = commandPrefix ? `${commandPrefix}\\n${command}` : command;\n\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\t\t\tif (run_in_background) {\n\t\t\t\tif (ops !== defaultBashOperations) {\n\t\t\t\t\tthrow new Error(\"Background bash is not supported with custom execution operations.\");\n\t\t\t\t}\n\t\t\t\tconst background = startBackgroundProcess({\n\t\t\t\t\trootCwd: cwd,\n\t\t\t\t\tcwd: spawnContext.cwd,\n\t\t\t\t\tcommand: spawnContext.command,\n\t\t\t\t\tsource: \"tool\",\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Started background process ${background.id} (pid ${background.pid}). Use /bg status ${background.id}, /bg logs ${background.id}, or /bg stop ${background.id}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tbackgroundTaskId: background.id,\n\t\t\t\t\t\tbackgroundStatusPath: background.metaPath,\n\t\t\t\t\t\tbackgroundLogPath: background.logPath,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t// We'll stream to a temp file if output gets large\n\t\t\t\tlet tempFilePath: string | undefined;\n\t\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\t\t\t\tlet totalBytes = 0;\n\n\t\t\t\tconst decoder = new TextDecoder();\n\t\t\t\t// Keep a rolling buffer of recent sanitized output\n\t\t\t\tconst chunks: string[] = [];\n\t\t\t\tlet rollingOutput = \"\";\n\t\t\t\tlet chunksBytes = 0;\n\t\t\t\t// Keep more than we need so we have enough context for tail truncation\n\t\t\t\tconst maxChunksBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\t\tconst pushText = (text: string) => {\n\t\t\t\t\tif (!text) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Start writing to temp file once we exceed the threshold\n\t\t\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t\t\t// Write all buffered chunks to the file\n\t\t\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Write to temp file if we have one\n\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\ttempFileStream.write(text);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Keep rolling buffer of recent data\n\t\t\t\t\tchunks.push(text);\n\t\t\t\t\trollingOutput += text;\n\t\t\t\t\tchunksBytes += Buffer.byteLength(text, \"utf-8\");\n\n\t\t\t\t\t// Trim old chunks if buffer is too large\n\t\t\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\n\t\t\t\t\t\tconst removed = chunks.shift()!;\n\t\t\t\t\t\tchunksBytes -= Buffer.byteLength(removed, \"utf-8\");\n\t\t\t\t\t\trollingOutput = rollingOutput.slice(removed.length);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Stream partial output to callback (truncated rolling buffer)\n\t\t\t\t\tif (onUpdate) {\n\t\t\t\t\t\tconst truncation = truncateTail(rollingOutput);\n\t\t\t\t\t\tonUpdate({\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: truncation.content || \"\" }],\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\ttruncation: truncation.truncated ? truncation : undefined,\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\t\ttotalBytes += data.length;\n\t\t\t\t\tpushText(sanitizeBashChunk(decoder.decode(data, { stream: true })));\n\t\t\t\t};\n\n\t\t\t\tconst flushDecoder = () => {\n\t\t\t\t\tpushText(sanitizeBashChunk(decoder.decode()));\n\t\t\t\t};\n\n\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\tonData: handleData,\n\t\t\t\t\tsignal,\n\t\t\t\t\ttimeout,\n\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t})\n\t\t\t\t\t.then(({ exitCode }) => {\n\t\t\t\t\t\tflushDecoder();\n\n\t\t\t\t\t\t// Close temp file stream\n\t\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\t\ttempFileStream.end();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Apply tail truncation\n\t\t\t\t\t\tconst truncation = truncateTail(rollingOutput);\n\t\t\t\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t\t\t\t// Build details with truncation info\n\t\t\t\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\tdetails = {\n\t\t\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Build actionable notice\n\t\t\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\t\t\t// Edge case: last line alone > 30KB\n\t\t\t\t\t\t\t\tconst lastLineSize = formatSize(\n\t\t\t\t\t\t\t\t\tBuffer.byteLength(rollingOutput.split(\"\\n\").pop() || \"\", \"utf-8\"),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\t\t\toutputText += `\\n\\nCommand exited with code ${exitCode}`;\n\t\t\t\t\t\t\treject(new Error(outputText));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: outputText }], details });\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: Error) => {\n\t\t\t\t\t\tflushDecoder();\n\n\t\t\t\t\t\t// Close temp file stream\n\t\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\t\ttempFileStream.end();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet output = rollingOutput;\n\n\t\t\t\t\t\tif (err.message === \"aborted\") {\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\t\t\treject(new Error(output));\n\t\t\t\t\t\t} else if (err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\t\t\toutput += `Command timed out after ${timeoutSecs} seconds`;\n\t\t\t\t\t\t\treject(new Error(output));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n\n/** Default bash tool using process.cwd() - for backwards compatibility */\nexport const bashTool = createBashTool(process.cwd());\n"]}
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream, existsSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { type Static, Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport stripAnsi from \"strip-ansi\";\nimport { adaptCommandForShell, getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from \"../../utils/shell.js\";\nimport { startBackgroundProcess } from \"../background-processes.js\";\nimport { isSandboxEnabledFromEnv, wrapCommandWithSandbox } from \"../sandbox/executor.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\nimport type { ToolPermissionGuard } from \"./permissions.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `iosm-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n\trun_in_background: Type.Optional(\n\t\tType.Boolean({ description: \"Run command in detached background mode and return task id immediately\" }),\n\t),\n});\n\nexport type BashToolInput = Static<typeof bashSchema>;\n\nexport interface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n\tbackgroundTaskId?: string;\n\tbackgroundStatusPath?: string;\n\tbackgroundLogPath?: string;\n}\n\n/**\n * Pluggable operations for the bash tool.\n * Override these to delegate command execution to remote systems (e.g., SSH).\n */\nexport interface BashOperations {\n\t/**\n\t * Execute a command and stream output.\n\t * @param command - The command to execute\n\t * @param cwd - Working directory\n\t * @param options - Execution options\n\t * @returns Promise resolving to exit code (null if killed)\n\t */\n\texec: (\n\t\tcommand: string,\n\t\tcwd: string,\n\t\toptions: {\n\t\t\tonData: (data: Buffer) => void;\n\t\t\tsignal?: AbortSignal;\n\t\t\ttimeout?: number;\n\t\t\tenv?: NodeJS.ProcessEnv;\n\t\t},\n\t) => Promise<{ exitCode: number | null }>;\n}\n\n/**\n * Default bash operations using local shell\n */\nconst defaultBashOperations: BashOperations = {\n\texec: (command, cwd, { onData, signal, timeout, env }) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst { shell, args } = getShellConfig();\n\n\t\t\tif (!existsSync(cwd)) {\n\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}\\nCannot execute bash commands.`));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet wrapped;\n\t\t\ttry {\n\t\t\t\twrapped = wrapCommandWithSandbox({\n\t\t\t\t\tcommand: shell,\n\t\t\t\t\targs: [...args, command],\n\t\t\t\t\tcwd,\n\t\t\t\t\tenabled: isSandboxEnabledFromEnv(),\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\treject(error instanceof Error ? error : new Error(String(error)));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst child = spawn(wrapped.command, wrapped.args, {\n\t\t\t\tcwd,\n\t\t\t\tdetached: true,\n\t\t\t\tenv: env ?? getShellEnv(),\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout if provided\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tif (child.pid) {\n\t\t\t\t\t\tkillProcessTree(child.pid);\n\t\t\t\t\t}\n\t\t\t\t}, timeout * 1000);\n\t\t\t}\n\n\t\t\t// Stream stdout and stderr\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", onData);\n\t\t\t}\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", onData);\n\t\t\t}\n\n\t\t\t// Handle shell spawn errors\n\t\t\tchild.on(\"error\", (err) => {\n\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\t\t\t\treject(err);\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\tkillProcessTree(child.pid);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\treject(new Error(\"aborted\"));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\treject(new Error(`timeout:${timeout}`));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tresolve({ exitCode: code });\n\t\t\t});\n\t\t});\n\t},\n};\n\nexport interface BashSpawnContext {\n\tcommand: string;\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n}\n\nexport type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;\n\nfunction sanitizeBashChunk(text: string): string {\n\treturn sanitizeBinaryOutput(stripAnsi(text)).replace(/\\r/g, \"\");\n}\n\nfunction resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {\n\tconst baseContext: BashSpawnContext = {\n\t\tcommand,\n\t\tcwd,\n\t\tenv: { ...getShellEnv() },\n\t};\n\n\tif (!spawnHook) return baseContext;\n\n\tconst modified = spawnHook(baseContext);\n\n\tif (modified.command !== baseContext.command) {\n\t\tprocess.stderr.write(\n\t\t\t`[iosm] Extension modified bash command:\\n before: ${baseContext.command}\\n after: ${modified.command}\\n`,\n\t\t);\n\t}\n\tif (modified.cwd !== baseContext.cwd) {\n\t\tprocess.stderr.write(`[iosm] Extension modified bash cwd: ${baseContext.cwd} → ${modified.cwd}\\n`);\n\t}\n\n\treturn {\n\t\t...modified,\n\t\tcommand: adaptCommandForShell(modified.command),\n\t};\n}\n\nexport interface BashToolOptions {\n\t/** Custom operations for command execution. Default: local shell */\n\toperations?: BashOperations;\n\t/** Command prefix prepended to every command (e.g., \"shopt -s expand_aliases\" for alias support) */\n\tcommandPrefix?: string;\n\t/** Hook to adjust command, cwd, or env before execution */\n\tspawnHook?: BashSpawnHook;\n\t/** Optional permission guard executed before command starts */\n\tpermissionGuard?: ToolPermissionGuard;\n}\n\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\n\tconst ops = options?.operations ?? defaultBashOperations;\n\tconst commandPrefix = options?.commandPrefix;\n\tconst spawnHook = options?.spawnHook;\n\tconst permissionGuard = options?.permissionGuard;\n\n\treturn {\n\t\tname: \"bash\",\n\t\tlabel: \"bash\",\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\n\t\tparameters: bashSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ command, timeout, run_in_background }: { command: string; timeout?: number; run_in_background?: boolean },\n\t\t\tsignal?: AbortSignal,\n\t\t\tonUpdate?,\n\t\t) => {\n\t\t\tif (permissionGuard) {\n\t\t\t\tconst allowed = await permissionGuard({\n\t\t\t\t\ttoolName: \"bash\",\n\t\t\t\t\tcwd,\n\t\t\t\t\tinput: { command, timeout, run_in_background },\n\t\t\t\t\tsummary: command.trim().slice(0, 200),\n\t\t\t\t});\n\t\t\t\tif (!allowed) {\n\t\t\t\t\tthrow new Error(\"Permission denied for bash command.\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply command prefix if configured (e.g., \"shopt -s expand_aliases\" for alias support)\n\t\t\tconst resolvedCommand = commandPrefix ? `${commandPrefix}\\n${command}` : command;\n\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\n\t\t\tif (run_in_background) {\n\t\t\t\tif (ops !== defaultBashOperations) {\n\t\t\t\t\tthrow new Error(\"Background bash is not supported with custom execution operations.\");\n\t\t\t\t}\n\t\t\t\tconst background = startBackgroundProcess({\n\t\t\t\t\trootCwd: cwd,\n\t\t\t\t\tcwd: spawnContext.cwd,\n\t\t\t\t\tcommand: spawnContext.command,\n\t\t\t\t\tsource: \"tool\",\n\t\t\t\t});\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\ttext: `Started background process ${background.id} (pid ${background.pid}). Use /bg status ${background.id}, /bg logs ${background.id}, or /bg stop ${background.id}.`,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tbackgroundTaskId: background.id,\n\t\t\t\t\t\tbackgroundStatusPath: background.metaPath,\n\t\t\t\t\t\tbackgroundLogPath: background.logPath,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t// We'll stream to a temp file if output gets large\n\t\t\t\tlet tempFilePath: string | undefined;\n\t\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\t\t\t\tlet totalBytes = 0;\n\n\t\t\t\tconst decoder = new TextDecoder();\n\t\t\t\t// Keep a rolling buffer of recent sanitized output\n\t\t\t\tconst chunks: string[] = [];\n\t\t\t\tlet rollingOutput = \"\";\n\t\t\t\tlet chunksBytes = 0;\n\t\t\t\t// Keep more than we need so we have enough context for tail truncation\n\t\t\t\tconst maxChunksBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\t\tconst pushText = (text: string) => {\n\t\t\t\t\tif (!text) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Start writing to temp file once we exceed the threshold\n\t\t\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t\t\t// Write all buffered chunks to the file\n\t\t\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Write to temp file if we have one\n\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\ttempFileStream.write(text);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Keep rolling buffer of recent data\n\t\t\t\t\tchunks.push(text);\n\t\t\t\t\trollingOutput += text;\n\t\t\t\t\tchunksBytes += Buffer.byteLength(text, \"utf-8\");\n\n\t\t\t\t\t// Trim old chunks if buffer is too large\n\t\t\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\n\t\t\t\t\t\tconst removed = chunks.shift()!;\n\t\t\t\t\t\tchunksBytes -= Buffer.byteLength(removed, \"utf-8\");\n\t\t\t\t\t\trollingOutput = rollingOutput.slice(removed.length);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Stream partial output to callback (truncated rolling buffer)\n\t\t\t\t\tif (onUpdate) {\n\t\t\t\t\t\tconst truncation = truncateTail(rollingOutput);\n\t\t\t\t\t\tonUpdate({\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: truncation.content || \"\" }],\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\ttruncation: truncation.truncated ? truncation : undefined,\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\t\ttotalBytes += data.length;\n\t\t\t\t\tpushText(sanitizeBashChunk(decoder.decode(data, { stream: true })));\n\t\t\t\t};\n\n\t\t\t\tconst flushDecoder = () => {\n\t\t\t\t\tpushText(sanitizeBashChunk(decoder.decode()));\n\t\t\t\t};\n\n\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\n\t\t\t\t\tonData: handleData,\n\t\t\t\t\tsignal,\n\t\t\t\t\ttimeout,\n\t\t\t\t\tenv: spawnContext.env,\n\t\t\t\t})\n\t\t\t\t\t.then(({ exitCode }) => {\n\t\t\t\t\t\tflushDecoder();\n\n\t\t\t\t\t\t// Close temp file stream\n\t\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\t\ttempFileStream.end();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Apply tail truncation\n\t\t\t\t\t\tconst truncation = truncateTail(rollingOutput);\n\t\t\t\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t\t\t\t// Build details with truncation info\n\t\t\t\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\tdetails = {\n\t\t\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t// Build actionable notice\n\t\t\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\t\t\t// Edge case: last line alone > 30KB\n\t\t\t\t\t\t\t\tconst lastLineSize = formatSize(\n\t\t\t\t\t\t\t\t\tBuffer.byteLength(rollingOutput.split(\"\\n\").pop() || \"\", \"utf-8\"),\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\n\t\t\t\t\t\t\toutputText += `\\n\\nCommand exited with code ${exitCode}`;\n\t\t\t\t\t\t\treject(new Error(outputText));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: outputText }], details });\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t\t.catch((err: Error) => {\n\t\t\t\t\t\tflushDecoder();\n\n\t\t\t\t\t\t// Close temp file stream\n\t\t\t\t\t\tif (tempFileStream) {\n\t\t\t\t\t\t\ttempFileStream.end();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet output = rollingOutput;\n\n\t\t\t\t\t\tif (err.message === \"aborted\") {\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\t\t\treject(new Error(output));\n\t\t\t\t\t\t} else if (err.message.startsWith(\"timeout:\")) {\n\t\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\t\t\toutput += `Command timed out after ${timeoutSecs} seconds`;\n\t\t\t\t\t\t\treject(new Error(output));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treject(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t});\n\t\t},\n\t};\n}\n\n/** Default bash tool using process.cwd() - for backwards compatibility */\nexport const bashTool = createBashTool(process.cwd());\n"]}
@@ -474,7 +474,7 @@ export class ToolExecutionComponent extends Container {
474
474
  updateDisplay() {
475
475
  const noBg = (text) => text;
476
476
  const useBuiltInRenderer = this.shouldUseBuiltInRenderer();
477
- const usesBoxRenderer = this.toolName === "bash" || !useBuiltInRenderer;
477
+ const usesBoxRenderer = this.toolName === "bash" || (!useBuiltInRenderer && Boolean(this.toolDefinition));
478
478
  const primaryComponent = usesBoxRenderer ? this.contentBox : this.contentText;
479
479
  let customRendererHasContent = false;
480
480
  this.hideComponent = false;