@glbgpt/cli 0.1.3 → 0.2.0

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.
Files changed (112) hide show
  1. package/README.md +74 -39
  2. package/dist/src/coding/backgroundTasks.d.ts +34 -0
  3. package/dist/src/coding/backgroundTasks.js +154 -0
  4. package/dist/src/coding/backgroundTasks.js.map +1 -0
  5. package/dist/src/coding/backgroundTools.d.ts +9 -0
  6. package/dist/src/coding/backgroundTools.js +63 -0
  7. package/dist/src/coding/backgroundTools.js.map +1 -0
  8. package/dist/src/coding/bashTool.d.ts +27 -0
  9. package/dist/src/coding/bashTool.js +144 -0
  10. package/dist/src/coding/bashTool.js.map +1 -0
  11. package/dist/src/coding/dangerousCommands.d.ts +29 -0
  12. package/dist/src/coding/dangerousCommands.js +156 -0
  13. package/dist/src/coding/dangerousCommands.js.map +1 -0
  14. package/dist/src/coding/editDiff.d.ts +45 -0
  15. package/dist/src/coding/editDiff.js +156 -0
  16. package/dist/src/coding/editDiff.js.map +1 -0
  17. package/dist/src/coding/editTool.d.ts +14 -0
  18. package/dist/src/coding/editTool.js +154 -0
  19. package/dist/src/coding/editTool.js.map +1 -0
  20. package/dist/src/coding/index.d.ts +25 -0
  21. package/dist/src/coding/index.js +30 -0
  22. package/dist/src/coding/index.js.map +1 -0
  23. package/dist/src/coding/outputAccumulator.d.ts +34 -0
  24. package/dist/src/coding/outputAccumulator.js +111 -0
  25. package/dist/src/coding/outputAccumulator.js.map +1 -0
  26. package/dist/src/coding/permissionGate.d.ts +65 -0
  27. package/dist/src/coding/permissionGate.js +138 -0
  28. package/dist/src/coding/permissionGate.js.map +1 -0
  29. package/dist/src/coding/permissionStore.d.ts +36 -0
  30. package/dist/src/coding/permissionStore.js +56 -0
  31. package/dist/src/coding/permissionStore.js.map +1 -0
  32. package/dist/src/coding/permissions.d.ts +34 -0
  33. package/dist/src/coding/permissions.js +18 -0
  34. package/dist/src/coding/permissions.js.map +1 -0
  35. package/dist/src/coding/projectContext.d.ts +21 -0
  36. package/dist/src/coding/projectContext.js +118 -0
  37. package/dist/src/coding/projectContext.js.map +1 -0
  38. package/dist/src/coding/readState.d.ts +22 -0
  39. package/dist/src/coding/readState.js +30 -0
  40. package/dist/src/coding/readState.js.map +1 -0
  41. package/dist/src/coding/truncate.d.ts +39 -0
  42. package/dist/src/coding/truncate.js +83 -0
  43. package/dist/src/coding/truncate.js.map +1 -0
  44. package/dist/src/coding/writeTool.d.ts +14 -0
  45. package/dist/src/coding/writeTool.js +82 -0
  46. package/dist/src/coding/writeTool.js.map +1 -0
  47. package/dist/src/commands/execCommand.d.ts +8 -0
  48. package/dist/src/commands/execCommand.js +47 -7
  49. package/dist/src/commands/execCommand.js.map +1 -1
  50. package/dist/src/core/catalog.js +11 -0
  51. package/dist/src/core/catalog.js.map +1 -1
  52. package/dist/src/core/compaction.d.ts +52 -0
  53. package/dist/src/core/compaction.js +151 -0
  54. package/dist/src/core/compaction.js.map +1 -0
  55. package/dist/src/core/contextPrune.js +8 -1
  56. package/dist/src/core/contextPrune.js.map +1 -1
  57. package/dist/src/core/httpChatBackend.js +21 -2
  58. package/dist/src/core/httpChatBackend.js.map +1 -1
  59. package/dist/src/core/runChat.d.ts +12 -2
  60. package/dist/src/core/runChat.js +72 -32
  61. package/dist/src/core/runChat.js.map +1 -1
  62. package/dist/src/core/session.d.ts +10 -1
  63. package/dist/src/core/session.js +11 -2
  64. package/dist/src/core/session.js.map +1 -1
  65. package/dist/src/core/structuredOutput.d.ts +63 -0
  66. package/dist/src/core/structuredOutput.js +328 -0
  67. package/dist/src/core/structuredOutput.js.map +1 -0
  68. package/dist/src/core/turnLoop.d.ts +18 -2
  69. package/dist/src/core/turnLoop.js +79 -12
  70. package/dist/src/core/turnLoop.js.map +1 -1
  71. package/dist/src/exec/input.d.ts +5 -3
  72. package/dist/src/exec/input.js +7 -3
  73. package/dist/src/exec/input.js.map +1 -1
  74. package/dist/src/exec/output.d.ts +3 -2
  75. package/dist/src/exec/output.js +17 -4
  76. package/dist/src/exec/output.js.map +1 -1
  77. package/dist/src/fs/index.d.ts +4 -1
  78. package/dist/src/fs/index.js +7 -2
  79. package/dist/src/fs/index.js.map +1 -1
  80. package/dist/src/fs/limits.d.ts +1 -1
  81. package/dist/src/fs/limits.js +3 -1
  82. package/dist/src/fs/limits.js.map +1 -1
  83. package/dist/src/fs/readFileTool.d.ts +2 -1
  84. package/dist/src/fs/readFileTool.js +4 -1
  85. package/dist/src/fs/readFileTool.js.map +1 -1
  86. package/dist/src/fs/workspaceGuard.d.ts +8 -0
  87. package/dist/src/fs/workspaceGuard.js +48 -0
  88. package/dist/src/fs/workspaceGuard.js.map +1 -1
  89. package/dist/src/index.js +37 -3
  90. package/dist/src/index.js.map +1 -1
  91. package/dist/src/mcp/server.js +4 -4
  92. package/dist/src/mcp/server.js.map +1 -1
  93. package/dist/src/tui/App.d.ts +10 -1
  94. package/dist/src/tui/App.js +114 -15
  95. package/dist/src/tui/App.js.map +1 -1
  96. package/dist/src/tui/approvalBridge.d.ts +21 -0
  97. package/dist/src/tui/approvalBridge.js +45 -0
  98. package/dist/src/tui/approvalBridge.js.map +1 -0
  99. package/dist/src/tui/chatController.d.ts +21 -0
  100. package/dist/src/tui/chatController.js +155 -16
  101. package/dist/src/tui/chatController.js.map +1 -1
  102. package/dist/src/tui/commands.js +16 -2
  103. package/dist/src/tui/commands.js.map +1 -1
  104. package/dist/src/tui/models.d.ts +9 -0
  105. package/dist/src/tui/models.js +14 -2
  106. package/dist/src/tui/models.js.map +1 -1
  107. package/dist/src/tui/run.d.ts +7 -0
  108. package/dist/src/tui/run.js +69 -9
  109. package/dist/src/tui/run.js.map +1 -1
  110. package/dist/src/web/webFetchTool.js +41 -5
  111. package/dist/src/web/webFetchTool.js.map +1 -1
  112. package/package.json +4 -4
package/README.md CHANGED
@@ -25,14 +25,15 @@ Run `glbgpt <command> --help` for the full flags of any command.
25
25
 
26
26
  ### Chat
27
27
 
28
- | Command | What it does |
29
- |---|---|
30
- | `glbgpt` | Start the interactive chat TUI. |
31
- | `glbgpt "<prompt>"` | Start the TUI with a first message. |
32
- | `glbgpt resume last` · `glbgpt resume <id>` | Resume a previous conversation. |
28
+ | Command | What it does |
29
+ | ------------------------------------------- | ----------------------------------- |
30
+ | `glbgpt` | Start the interactive chat TUI. |
31
+ | `glbgpt "<prompt>"` | Start the TUI with a first message. |
32
+ | `glbgpt resume last` · `glbgpt resume <id>` | Resume a previous conversation. |
33
33
 
34
34
  Inside the TUI, type `/` for commands: `/model` `/models` `/image` `/video` `/tasks` `/status`
35
- `/account` `/transactions` `/cost` `/login` `/logout` `/new` `/resume` `/help` `/exit`.
35
+ `/permissions` `/compact` `/account` `/transactions` `/cost` `/login` `/logout` `/new` `/resume`
36
+ `/help` `/exit`.
36
37
  Attach images with `@image` (last generation), `Ctrl+V` or `@clipboard`, or `@./photo.png`.
37
38
  Multi-line: end a line with `\` then Enter (or Ctrl+J); plain Enter sends; Esc interrupts.
38
39
  On tool-capable models the assistant can also read files under the directory you started
@@ -40,22 +41,48 @@ On tool-capable models the assistant can also read files under the directory you
40
41
  web when current information matters (a small per-search credit charge applies, at most a
41
42
  few searches per message), and fetch public web pages by URL (free).
42
43
 
44
+ #### Coding tools
45
+
46
+ On tool-capable models the assistant can also **run shell commands, create files, and edit
47
+ files** in your workspace — every command and file change asks for your approval first
48
+ (`[y]` allow once, `[a]` allow similar actions for this session, `[p]` always allow for this
49
+ project, `[n]` deny). Diffs are shown before an edit runs. Project instructions in
50
+ `AGENTS.md` or `CLAUDE.md` (from the repository root down to the current directory) are
51
+ picked up automatically (`--no-project-context` to skip). Long-running commands (dev
52
+ servers, watchers) can run in the background — the assistant starts them detached, polls
53
+ their output, and they are stopped when the CLI exits.
54
+
55
+ Long conversations compact automatically: when the history approaches the model's context
56
+ limit the older part is summarized (one model call) and the conversation continues
57
+ seamlessly — `/compact` triggers it manually. The full history always stays on disk.
58
+
59
+ Permission modes — switch with `/permissions <mode>` or start with `--permission-mode <mode>`:
60
+
61
+ | Mode | Behavior |
62
+ | -------------- | ------------------------------------------------------------------------------ |
63
+ | `default` | Ask before every command and file write. |
64
+ | `accept-edits` | File writes run without asking; commands still ask. |
65
+ | `yolo` | Nothing asks (also `--yolo`). Dangerous — use only in disposable environments. |
66
+
67
+ Dangerous commands (`sudo`, forced/recursive `rm`, ...) always ask individually and can never
68
+ be remembered for the session; writes inside `.git/` always ask.
69
+
43
70
  ### Account
44
71
 
45
- | Command | What it does |
46
- |---|---|
72
+ | Command | What it does |
73
+ | -------------------------------------- | --------------------------------------------------------------------------------------- |
47
74
  | `glbgpt login [--no-browser] [--code]` | Sign in. `--no-browser` prints the URL; `--code` pastes a one-time code (headless/SSH). |
48
- | `glbgpt logout` | Sign out and remove stored credentials. |
49
- | `glbgpt whoami` | Show the signed-in account. |
50
- | `glbgpt account` | Membership and credit balance. |
51
- | `glbgpt account transactions` | Recent spend (authoritative billing). |
75
+ | `glbgpt logout` | Sign out and remove stored credentials. |
76
+ | `glbgpt whoami` | Show the signed-in account. |
77
+ | `glbgpt account` | Membership and credit balance. |
78
+ | `glbgpt account transactions` | Recent spend (authoritative billing). |
52
79
 
53
80
  ### Images and video
54
81
 
55
- | Command | What it does |
56
- |---|---|
82
+ | Command | What it does |
83
+ | ------------------------- | ----------------------------------------- |
57
84
  | `glbgpt image "<prompt>"` | Generate an image (asks before spending). |
58
- | `glbgpt video "<prompt>"` | Generate a video (asks before spending). |
85
+ | `glbgpt video "<prompt>"` | Generate a video (asks before spending). |
59
86
 
60
87
  Common flags: `-m/--model`, `--ar 16:9`, `--res 2K`, `--seed N`, `--negative-prompt "..."`,
61
88
  `-i ./ref.png` (reference image), `--duration N` and `--audio/--no-audio` (video),
@@ -64,14 +91,14 @@ task id without waiting).
64
91
 
65
92
  ### Models and tasks
66
93
 
67
- | Command | What it does |
68
- |---|---|
69
- | `glbgpt model list [chat\|image\|video]` | List available models. |
70
- | `glbgpt model show <model>` | Parameters and limits for one model. |
71
- | `glbgpt task list` | Recent generation tasks. |
72
- | `glbgpt task show last` · `glbgpt task show <id>` | A task's status. |
73
- | `glbgpt task wait last` | Block until a task finishes. |
74
- | `glbgpt task download last` | Download a finished result. |
94
+ | Command | What it does |
95
+ | ------------------------------------------------- | ------------------------------------ |
96
+ | `glbgpt model list [chat\|image\|video]` | List available models. |
97
+ | `glbgpt model show <model>` | Parameters and limits for one model. |
98
+ | `glbgpt task list` | Recent generation tasks. |
99
+ | `glbgpt task show last` · `glbgpt task show <id>` | A task's status. |
100
+ | `glbgpt task wait last` | Block until a task finishes. |
101
+ | `glbgpt task download last` | Download a finished result. |
75
102
 
76
103
  ### exec — non-interactive
77
104
 
@@ -94,19 +121,27 @@ On tool-capable models the assistant can also read files under the current direc
94
121
  search the web / fetch pages, and generate images/videos: exec waits for in-flight generations
95
122
  before exiting and reports each as a `media_result` event (`--jsonl`) or a stderr line.
96
123
 
124
+ Coding tools are **off by default in exec** (no one is present to approve): the model sees the
125
+ tools but every command/write is refused with a message naming the enable flag. Opt in with
126
+ `--allow-write` (create/edit files), `--allow-bash` (shell commands), or `--yolo` (both):
127
+
128
+ ```bash
129
+ glbgpt exec "fix the failing test in src/utils.test.ts and run the tests" --allow-write --allow-bash
130
+ ```
131
+
97
132
  ### mcp — use from other agents
98
133
 
99
134
  `glbgpt mcp` runs a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio,
100
135
  so agents like Claude Code, Codex, or Cursor can chat and generate media through your GlobalGPT
101
136
  account. Sign in once with `glbgpt login` first.
102
137
 
103
- | Tool | What it does |
104
- |---|---|
105
- | `glbgpt_chat` | One chat turn: question in, answer out (system prompt, vision, JSON schema, and — on tool-capable models — live web search/fetch supported). |
106
- | `glbgpt_generate_image` | Generate an image; usually returns the finished result (URL + local file) in one call. |
107
- | `glbgpt_generate_video` | Generate a video; returns a `task_id` to check. |
108
- | `glbgpt_check_task` | Check a generation task. Waits server-side — the agent can just call it again immediately until done. |
109
- | `glbgpt_list_models` | List available chat/image/video models and what parameters each supports. |
138
+ | Tool | What it does |
139
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
140
+ | `glbgpt_chat` | One chat turn: question in, answer out (system prompt, vision, JSON schema, and — on tool-capable models — live web search/fetch supported). |
141
+ | `glbgpt_generate_image` | Generate an image; usually returns the finished result (URL + local file) in one call. |
142
+ | `glbgpt_generate_video` | Generate a video; returns a `task_id` to check. |
143
+ | `glbgpt_check_task` | Check a generation task. Waits server-side — the agent can just call it again immediately until done. |
144
+ | `glbgpt_list_models` | List available chat/image/video models and what parameters each supports. |
110
145
 
111
146
  Register it:
112
147
 
@@ -182,18 +217,18 @@ re-points the link and leaves nothing to lose.
182
217
 
183
218
  **Which surface should you use?**
184
219
 
185
- | | Use it when |
186
- |---|---|
187
- | **Skill** | Your agent runs shell commands (Claude Code, Cursor, Codex). Zero config — install once and ask for an image. |
188
- | **MCP** | Your agent speaks MCP but cannot run shell commands, or you want the session credit budget enforced (`--max-credits`). |
189
- | **CLI / exec** | You are scripting it yourself, in CI, or piping output between tools. |
220
+ | | Use it when |
221
+ | -------------- | ---------------------------------------------------------------------------------------------------------------------- |
222
+ | **Skill** | Your agent runs shell commands (Claude Code, Cursor, Codex). Zero config — install once and ask for an image. |
223
+ | **MCP** | Your agent speaks MCP but cannot run shell commands, or you want the session credit budget enforced (`--max-credits`). |
224
+ | **CLI / exec** | You are scripting it yourself, in CI, or piping output between tools. |
190
225
 
191
226
  ### config
192
227
 
193
- | Command | What it does |
194
- |---|---|
195
- | `glbgpt config list` | Show persisted settings. |
196
- | `glbgpt config get <key>` · `set <key> <value>` · `unset <key>` | Manage settings. |
228
+ | Command | What it does |
229
+ | --------------------------------------------------------------- | ------------------------ |
230
+ | `glbgpt config list` | Show persisted settings. |
231
+ | `glbgpt config get <key>` · `set <key> <value>` · `unset <key>` | Manage settings. |
197
232
 
198
233
  Keys: `lang`, `max-credits` (unattended media budget), `context-pruning` (set `false` to
199
234
  keep old tool results in long chats), `update-check` (set `false` to disable the startup
@@ -0,0 +1,34 @@
1
+ export declare class BackgroundBashTasks {
2
+ private readonly spillDir;
3
+ private readonly tasks;
4
+ private seq;
5
+ constructor(spillDir?: string);
6
+ /** Ids of tasks still running (for surfaces that want to report before exit). */
7
+ running(): Array<{
8
+ id: string;
9
+ command: string;
10
+ }>;
11
+ start(command: string, cwd: string): {
12
+ taskId: string;
13
+ outputPath: string;
14
+ } | {
15
+ error: string;
16
+ };
17
+ /** New output since the last read (tail-capped), plus liveness/exit status. */
18
+ readNew(taskId: string): Promise<{
19
+ output: string;
20
+ truncatedBytes: number;
21
+ status: 'running' | 'exited' | 'killed';
22
+ exitCode: number | null;
23
+ } | {
24
+ error: string;
25
+ }>;
26
+ kill(taskId: string): {
27
+ ok: true;
28
+ status: string;
29
+ } | {
30
+ error: string;
31
+ };
32
+ /** Kill every still-running task (CLI exit path). Synchronous — safe in an exit handler. */
33
+ killAll(): void;
34
+ }
@@ -0,0 +1,154 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createWriteStream, promises as fs, existsSync } from 'node:fs';
3
+ import { randomBytes } from 'node:crypto';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { cutToBytesFromEnd } from './truncate.js';
7
+ /**
8
+ * Registry for background bash tasks (`bash` with run_in_background, P1 2026-08-21).
9
+ *
10
+ * Every task's stdout+stderr streams to a spill file from byte 0 (background tasks
11
+ * are long-lived by definition — memory holds nothing). `readNew` returns the bytes
12
+ * appended since the caller's last read (tail-capped per read), so the model polls
13
+ * incrementally like `tail -f`. Tasks die with the CLI (process-group kill on exit).
14
+ */
15
+ const MAX_CONCURRENT_TASKS = 5;
16
+ const MAX_READ_BYTES = 50 * 1024;
17
+ export class BackgroundBashTasks {
18
+ spillDir;
19
+ tasks = new Map();
20
+ seq = 0;
21
+ constructor(spillDir = os.tmpdir()) {
22
+ this.spillDir = spillDir;
23
+ // Belt and suspenders: kill everything if the process exits without cleanup.
24
+ process.once('exit', () => this.killAll());
25
+ }
26
+ /** Ids of tasks still running (for surfaces that want to report before exit). */
27
+ running() {
28
+ return [...this.tasks.values()].filter((t) => !t.exited).map((t) => ({ id: t.id, command: t.command }));
29
+ }
30
+ start(command, cwd) {
31
+ const live = [...this.tasks.values()].filter((t) => !t.exited).length;
32
+ if (live >= MAX_CONCURRENT_TASKS) {
33
+ return {
34
+ error: `Too many background tasks (${live} running, max ${MAX_CONCURRENT_TASKS}). Kill one with bash_kill first.`,
35
+ };
36
+ }
37
+ const shell = process.platform === 'win32' ? (process.env.ComSpec ?? 'cmd.exe') : pickShell();
38
+ const id = `bg-${++this.seq}`;
39
+ const outputPath = path.join(this.spillDir, `glbgpt-bgtask-${randomBytes(6).toString('hex')}.log`);
40
+ const out = createWriteStream(outputPath, { flags: 'w' });
41
+ const child = spawn(shell, ['-c', command], {
42
+ cwd,
43
+ detached: process.platform !== 'win32',
44
+ env: process.env,
45
+ stdio: ['ignore', 'pipe', 'pipe'],
46
+ windowsHide: true,
47
+ });
48
+ child.stdout?.pipe(out, { end: false });
49
+ child.stderr?.pipe(out, { end: false });
50
+ const task = {
51
+ id,
52
+ command,
53
+ pid: child.pid,
54
+ outputPath,
55
+ cursor: 0,
56
+ startedAt: Date.now(),
57
+ exitCode: null,
58
+ exited: false,
59
+ killed: false,
60
+ };
61
+ child.on('error', () => {
62
+ task.exited = true;
63
+ out.end();
64
+ });
65
+ child.on('close', (code) => {
66
+ task.exited = true;
67
+ task.exitCode = code;
68
+ out.end();
69
+ });
70
+ this.tasks.set(id, task);
71
+ return { taskId: id, outputPath };
72
+ }
73
+ /** New output since the last read (tail-capped), plus liveness/exit status. */
74
+ async readNew(taskId) {
75
+ const task = this.tasks.get(taskId);
76
+ if (!task)
77
+ return { error: `Unknown background task: ${taskId}` };
78
+ let data = '';
79
+ let truncatedBytes = 0;
80
+ try {
81
+ const stat = await fs.stat(task.outputPath);
82
+ if (stat.size > task.cursor) {
83
+ const newBytes = stat.size - task.cursor;
84
+ const fh = await fs.open(task.outputPath, 'r');
85
+ try {
86
+ const readFrom = newBytes > MAX_READ_BYTES ? stat.size - MAX_READ_BYTES : task.cursor;
87
+ truncatedBytes = readFrom - task.cursor;
88
+ const buf = Buffer.alloc(stat.size - readFrom);
89
+ await fh.read(buf, 0, buf.length, readFrom);
90
+ data =
91
+ truncatedBytes > 0
92
+ ? cutToBytesFromEnd(buf.toString('utf8'), MAX_READ_BYTES)
93
+ : buf.toString('utf8');
94
+ }
95
+ finally {
96
+ await fh.close();
97
+ }
98
+ task.cursor = stat.size;
99
+ }
100
+ }
101
+ catch {
102
+ // Spill unreadable (already cleaned?) — report status only.
103
+ }
104
+ return {
105
+ output: data,
106
+ truncatedBytes,
107
+ status: task.killed ? 'killed' : task.exited ? 'exited' : 'running',
108
+ exitCode: task.exitCode,
109
+ };
110
+ }
111
+ kill(taskId) {
112
+ const task = this.tasks.get(taskId);
113
+ if (!task)
114
+ return { error: `Unknown background task: ${taskId}` };
115
+ if (task.exited)
116
+ return { ok: true, status: `already exited with code ${task.exitCode}` };
117
+ task.killed = true;
118
+ killTree(task.pid);
119
+ return { ok: true, status: 'killed' };
120
+ }
121
+ /** Kill every still-running task (CLI exit path). Synchronous — safe in an exit handler. */
122
+ killAll() {
123
+ for (const task of this.tasks.values()) {
124
+ if (!task.exited) {
125
+ task.killed = true;
126
+ killTree(task.pid);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ function pickShell() {
132
+ return existsSync('/bin/bash') ? '/bin/bash' : 'sh';
133
+ }
134
+ function killTree(pid) {
135
+ if (!pid)
136
+ return;
137
+ try {
138
+ if (process.platform === 'win32') {
139
+ spawn('taskkill', ['/F', '/T', '/PID', String(pid)], { windowsHide: true });
140
+ }
141
+ else {
142
+ process.kill(-pid, 'SIGKILL');
143
+ }
144
+ }
145
+ catch {
146
+ try {
147
+ process.kill(pid, 'SIGKILL');
148
+ }
149
+ catch {
150
+ // Already gone.
151
+ }
152
+ }
153
+ }
154
+ //# sourceMappingURL=backgroundTasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgroundTasks.js","sourceRoot":"","sources":["../../../src/coding/backgroundTasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,QAAQ,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD;;;;;;;GAOG;AAEH,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAC/B,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,CAAC;AAcjC,MAAM,OAAO,mBAAmB;IAID;IAHZ,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;IACzC,GAAG,GAAG,CAAC,CAAC;IAEhB,YAA6B,WAAmB,EAAE,CAAC,MAAM,EAAE;QAA9B,aAAQ,GAAR,QAAQ,CAAsB;QACzD,6EAA6E;QAC7E,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,iFAAiF;IACjF,OAAO;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC1G,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAW;QAChC,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;QACtE,IAAI,IAAI,IAAI,oBAAoB,EAAE,CAAC;YACjC,OAAO;gBACL,KAAK,EAAE,8BAA8B,IAAI,iBAAiB,oBAAoB,mCAAmC;aAClH,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QAC9F,MAAM,EAAE,GAAG,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACnG,MAAM,GAAG,GAAG,iBAAiB,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE;YAC1C,GAAG;YACH,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;YACtC,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;YACjC,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QACxC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QACxC,MAAM,IAAI,GAAS;YACjB,EAAE;YACF,OAAO;YACP,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,UAAU;YACV,MAAM,EAAE,CAAC;YACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;YACrB,QAAQ,EAAE,IAAI;YACd,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,KAAK;SACd,CAAC;QACF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACzB,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,CAAC;IACpC,CAAC;IAED,+EAA+E;IAC/E,KAAK,CAAC,OAAO,CACX,MAAc;QAUd,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,KAAK,EAAE,4BAA4B,MAAM,EAAE,EAAE,CAAC;QAClE,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC5C,IAAI,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;gBACzC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;gBAC/C,IAAI,CAAC;oBACH,MAAM,QAAQ,GAAG,QAAQ,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;oBACtF,cAAc,GAAG,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC;oBACxC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC;oBAC/C,MAAM,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;oBAC5C,IAAI;wBACF,cAAc,GAAG,CAAC;4BAChB,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;4BACzD,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC7B,CAAC;wBAAS,CAAC;oBACT,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;gBACnB,CAAC;gBACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC;YAC1B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4DAA4D;QAC9D,CAAC;QACD,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,cAAc;YACd,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;YACnE,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,MAAc;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,KAAK,EAAE,4BAA4B,MAAM,EAAE,EAAE,CAAC;QAClE,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,4BAA4B,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;QAC1F,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC;IAED,4FAA4F;IAC5F,OAAO;QACL,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACjB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,SAAS,SAAS;IAChB,OAAO,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;AACtD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAuB;IACvC,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,KAAK,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB;QAClB,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { ClientTool } from '../core/types.js';
2
+ import type { BackgroundBashTasks } from './backgroundTasks.js';
3
+ /**
4
+ * Companion tools for background bash tasks: poll new output, stop a task.
5
+ * Neither is gated by the permission gate — the task itself was approved when
6
+ * it was started, and observing/stopping your own task needs no new authority.
7
+ */
8
+ export declare function createBashOutputTool(tasks: BackgroundBashTasks): ClientTool;
9
+ export declare function createBashKillTool(tasks: BackgroundBashTasks): ClientTool;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Companion tools for background bash tasks: poll new output, stop a task.
3
+ * Neither is gated by the permission gate — the task itself was approved when
4
+ * it was started, and observing/stopping your own task needs no new authority.
5
+ */
6
+ export function createBashOutputTool(tasks) {
7
+ return {
8
+ name: 'bash_output',
9
+ readOnly: true,
10
+ description: 'Read NEW output from a background bash task since the last bash_output call (like tail -f), ' +
11
+ 'plus its status (running/exited/killed and exit code). Poll this after starting a command ' +
12
+ 'with run_in_background.',
13
+ parameters: {
14
+ type: 'object',
15
+ properties: {
16
+ task_id: { type: 'string', description: 'Background task id from the bash tool result.' },
17
+ },
18
+ required: ['task_id'],
19
+ },
20
+ async execute(args) {
21
+ if (typeof args.task_id !== 'string' || !args.task_id.trim()) {
22
+ return { error: 'Missing required "task_id" argument.' };
23
+ }
24
+ const r = await tasks.readNew(args.task_id.trim());
25
+ if ('error' in r)
26
+ return { error: r.error };
27
+ const parts = [];
28
+ if (r.truncatedBytes > 0)
29
+ parts.push(`[${r.truncatedBytes} bytes of new output skipped — showing the tail]`);
30
+ parts.push(r.output.length > 0 ? r.output : '(no new output)');
31
+ parts.push(r.status === 'running'
32
+ ? 'Status: running'
33
+ : r.status === 'killed'
34
+ ? 'Status: killed'
35
+ : `Status: exited with code ${r.exitCode}`);
36
+ return { output: parts.join('\n') };
37
+ },
38
+ };
39
+ }
40
+ export function createBashKillTool(tasks) {
41
+ return {
42
+ name: 'bash_kill',
43
+ readOnly: false,
44
+ description: 'Stop a background bash task (kills its whole process group).',
45
+ parameters: {
46
+ type: 'object',
47
+ properties: {
48
+ task_id: { type: 'string', description: 'Background task id from the bash tool result.' },
49
+ },
50
+ required: ['task_id'],
51
+ },
52
+ async execute(args) {
53
+ if (typeof args.task_id !== 'string' || !args.task_id.trim()) {
54
+ return { error: 'Missing required "task_id" argument.' };
55
+ }
56
+ const r = tasks.kill(args.task_id.trim());
57
+ if ('error' in r)
58
+ return { error: r.error };
59
+ return { output: `Task ${args.task_id}: ${r.status}.` };
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=backgroundTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgroundTools.js","sourceRoot":"","sources":["../../../src/coding/backgroundTools.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AAEH,MAAM,UAAU,oBAAoB,CAAC,KAA0B;IAC7D,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,8FAA8F;YAC9F,4FAA4F;YAC5F,yBAAyB;QAC3B,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+CAA+C,EAAE;aAC1F;YACD,QAAQ,EAAE,CAAC,SAAS,CAAC;SACtB;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC7D,OAAO,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YACnD,IAAI,OAAO,IAAI,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;YAC3B,IAAI,CAAC,CAAC,cAAc,GAAG,CAAC;gBACtB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,cAAc,kDAAkD,CAAC,CAAC;YACrF,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;YAC/D,KAAK,CAAC,IAAI,CACR,CAAC,CAAC,MAAM,KAAK,SAAS;gBACpB,CAAC,CAAC,iBAAiB;gBACnB,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ;oBACrB,CAAC,CAAC,gBAAgB;oBAClB,CAAC,CAAC,4BAA4B,CAAC,CAAC,QAAQ,EAAE,CAC/C,CAAC;YACF,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAA0B;IAC3D,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,8DAA8D;QAC3E,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+CAA+C,EAAE;aAC1F;YACD,QAAQ,EAAE,CAAC,SAAS,CAAC;SACtB;QACD,KAAK,CAAC,OAAO,CAAC,IAAI;YAChB,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC7D,OAAO,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;YAC3D,CAAC;YACD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;YAC1C,IAAI,OAAO,IAAI,CAAC;gBAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YAC5C,OAAO,EAAE,MAAM,EAAE,QAAQ,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;QAC1D,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { ClientTool } from '../core/types.js';
2
+ import type { PermissionGate } from './permissions.js';
3
+ import type { BackgroundBashTasks } from './backgroundTasks.js';
4
+ export declare const BASH_DEFAULT_TIMEOUT_S = 120;
5
+ export declare const BASH_MAX_TIMEOUT_S = 600;
6
+ /**
7
+ * The `bash` tool: run a shell command in the workspace directory.
8
+ *
9
+ * Execution contract (2026-08-21 design, pi lineage):
10
+ * - New process per call; no shell state persists. Commands run in the session cwd —
11
+ * there is NO cwd parameter and `cd` does not carry over (prompt guidance says to use
12
+ * absolute paths or `cd` within a single command).
13
+ * - stdout+stderr merged in arrival order; tail-truncated at 2000 lines / 50KB with the
14
+ * full output spilled to a temp file (footnote names the path).
15
+ * - Non-zero exit is an error result but the collected output is still attached — the
16
+ * model needs the output to react.
17
+ * - Timeout (default 120s, cap 600s) kills the whole process GROUP (detached spawn), so
18
+ * children die with the shell.
19
+ */
20
+ export declare function createBashTool(opts: {
21
+ cwd: string;
22
+ gate: PermissionGate;
23
+ /** Background task registry; absent = run_in_background rejected. */
24
+ background?: BackgroundBashTasks;
25
+ /** Test hook: where spill files go. */
26
+ spillDir?: string;
27
+ }): ClientTool;
@@ -0,0 +1,144 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { OutputAccumulator } from './outputAccumulator.js';
4
+ export const BASH_DEFAULT_TIMEOUT_S = 120;
5
+ export const BASH_MAX_TIMEOUT_S = 600;
6
+ /**
7
+ * The `bash` tool: run a shell command in the workspace directory.
8
+ *
9
+ * Execution contract (2026-08-21 design, pi lineage):
10
+ * - New process per call; no shell state persists. Commands run in the session cwd —
11
+ * there is NO cwd parameter and `cd` does not carry over (prompt guidance says to use
12
+ * absolute paths or `cd` within a single command).
13
+ * - stdout+stderr merged in arrival order; tail-truncated at 2000 lines / 50KB with the
14
+ * full output spilled to a temp file (footnote names the path).
15
+ * - Non-zero exit is an error result but the collected output is still attached — the
16
+ * model needs the output to react.
17
+ * - Timeout (default 120s, cap 600s) kills the whole process GROUP (detached spawn), so
18
+ * children die with the shell.
19
+ */
20
+ export function createBashTool(opts) {
21
+ return {
22
+ name: 'bash',
23
+ description: 'Execute a bash command in the workspace directory. Returns stdout and stderr combined. ' +
24
+ 'Output is truncated to the last 2000 lines or 50KB (whichever is hit first); when truncated, ' +
25
+ 'the full output is saved to a temp file whose path is included in the result. ' +
26
+ `Optional timeout in seconds (default ${BASH_DEFAULT_TIMEOUT_S}, max ${BASH_MAX_TIMEOUT_S}). ` +
27
+ 'Shell state does not persist between calls; the working directory is always the workspace root. ' +
28
+ 'For long-running commands (dev servers, watchers) set run_in_background: true — the result gives ' +
29
+ 'a task id to poll with bash_output and stop with bash_kill.',
30
+ parameters: {
31
+ type: 'object',
32
+ properties: {
33
+ command: { type: 'string', description: 'Bash command to execute' },
34
+ timeout_seconds: {
35
+ type: 'number',
36
+ description: `Timeout in seconds (default ${BASH_DEFAULT_TIMEOUT_S}, max ${BASH_MAX_TIMEOUT_S})`,
37
+ },
38
+ run_in_background: {
39
+ type: 'boolean',
40
+ description: 'Run detached and return a task id immediately (for dev servers / long jobs). ' +
41
+ 'Poll with bash_output; stop with bash_kill. No timeout applies.',
42
+ },
43
+ },
44
+ required: ['command'],
45
+ },
46
+ readOnly: false,
47
+ async execute(args) {
48
+ const command = typeof args.command === 'string' ? args.command.trim() : '';
49
+ if (!command)
50
+ return { error: 'command is required' };
51
+ const rawTimeout = args.timeout_seconds;
52
+ let timeoutS = BASH_DEFAULT_TIMEOUT_S;
53
+ if (rawTimeout !== undefined && rawTimeout !== null) {
54
+ const n = Number(rawTimeout);
55
+ if (!Number.isFinite(n) || n <= 0) {
56
+ return { error: 'Invalid timeout_seconds: must be a positive number of seconds' };
57
+ }
58
+ timeoutS = Math.min(n, BASH_MAX_TIMEOUT_S);
59
+ }
60
+ const verdict = await opts.gate.check({ kind: 'bash', command });
61
+ if (!verdict.ok)
62
+ return { error: verdict.message };
63
+ if (args.run_in_background === true) {
64
+ if (!opts.background)
65
+ return { error: 'Background tasks are not available here.' };
66
+ const started = opts.background.start(command, opts.cwd);
67
+ if ('error' in started)
68
+ return { error: started.error };
69
+ return {
70
+ output: `Started background task ${started.taskId}. ` +
71
+ 'Poll new output with bash_output; stop it with bash_kill. It dies when the CLI exits.',
72
+ task_id: started.taskId,
73
+ };
74
+ }
75
+ return runBash(command, opts.cwd, timeoutS, opts.spillDir);
76
+ },
77
+ };
78
+ }
79
+ function pickShell() {
80
+ if (process.platform === 'win32')
81
+ return process.env.ComSpec ?? 'cmd.exe';
82
+ if (existsSync('/bin/bash'))
83
+ return '/bin/bash';
84
+ return 'sh';
85
+ }
86
+ async function runBash(command, cwd, timeoutS, spillDir) {
87
+ if (!existsSync(cwd)) {
88
+ return { error: `Working directory does not exist: ${cwd}` };
89
+ }
90
+ const shell = pickShell();
91
+ const detached = process.platform !== 'win32';
92
+ const child = spawn(shell, ['-c', command], {
93
+ cwd,
94
+ detached,
95
+ env: process.env,
96
+ stdio: ['ignore', 'pipe', 'pipe'],
97
+ windowsHide: true,
98
+ });
99
+ const acc = new OutputAccumulator(undefined, undefined, spillDir);
100
+ child.stdout?.on('data', (b) => acc.append(b));
101
+ child.stderr?.on('data', (b) => acc.append(b));
102
+ let timedOut = false;
103
+ const timer = setTimeout(() => {
104
+ timedOut = true;
105
+ killTree(child.pid);
106
+ }, timeoutS * 1000);
107
+ const exitCode = await new Promise((resolve) => {
108
+ child.on('error', () => resolve(null));
109
+ // 'close' (not 'exit') so stdio has fully flushed before we snapshot the output.
110
+ child.on('close', (code) => resolve(code));
111
+ });
112
+ clearTimeout(timer);
113
+ const { text } = await acc.finalize();
114
+ const output = text.length > 0 ? text : '(no output)';
115
+ if (timedOut) {
116
+ return { error: `${output}\n\nCommand timed out after ${timeoutS} seconds` };
117
+ }
118
+ if (exitCode !== 0 && exitCode !== null) {
119
+ return { error: `${output}\n\nCommand exited with code ${exitCode}` };
120
+ }
121
+ return { output };
122
+ }
123
+ /** Kill the whole process group (detached spawn) so children die with the shell. */
124
+ function killTree(pid) {
125
+ if (!pid)
126
+ return;
127
+ try {
128
+ if (process.platform === 'win32') {
129
+ spawn('taskkill', ['/F', '/T', '/PID', String(pid)], { windowsHide: true });
130
+ }
131
+ else {
132
+ process.kill(-pid, 'SIGKILL');
133
+ }
134
+ }
135
+ catch {
136
+ try {
137
+ process.kill(pid, 'SIGKILL');
138
+ }
139
+ catch {
140
+ // Already gone.
141
+ }
142
+ }
143
+ }
144
+ //# sourceMappingURL=bashTool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bashTool.js","sourceRoot":"","sources":["../../../src/coding/bashTool.ts"],"names":[],"mappings":"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"}