@sema-agent/core 1.451.0 → 2.0.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 (191) hide show
  1. package/dist/agents/agent-transcript-tool.d.ts +15 -0
  2. package/dist/agents/agent-transcript-tool.js +122 -0
  3. package/dist/agents/cascade.js +5 -8
  4. package/dist/agents/observer.js +3 -0
  5. package/dist/agents/repair-loop.d.ts +1 -1
  6. package/dist/agents/repair-loop.js +1 -1
  7. package/dist/agents/retain-ledger.d.ts +60 -0
  8. package/dist/agents/retain-ledger.js +225 -0
  9. package/dist/agents/roster-store.d.ts +1 -0
  10. package/dist/agents/roster-store.js +9 -0
  11. package/dist/agents/send-message-tool.d.ts +41 -0
  12. package/dist/agents/send-message-tool.js +469 -0
  13. package/dist/agents/subagent.d.ts +33 -108
  14. package/dist/agents/subagent.js +40 -813
  15. package/dist/agents/teacher.d.ts +0 -1
  16. package/dist/agents/teacher.js +0 -9
  17. package/dist/bin/sema-tb.js +3 -3
  18. package/dist/brain/anthropic.js +15 -40
  19. package/dist/brain/openai.js +15 -40
  20. package/dist/brain/reasoning.d.ts +1 -1
  21. package/dist/brain/stream-engine.js +2 -2
  22. package/dist/brain/stream-shared.d.ts +17 -0
  23. package/dist/brain/stream-shared.js +50 -0
  24. package/dist/brain/timeout.d.ts +1 -1
  25. package/dist/brain/timeout.js +1 -1
  26. package/dist/config/catalog.js +1 -3
  27. package/dist/config/defaults.d.ts +6 -0
  28. package/dist/config/defaults.js +6 -0
  29. package/dist/core/ask-question.d.ts +1 -1
  30. package/dist/core/ask-question.js +5 -5
  31. package/dist/core/background-agent-store.d.ts +2 -0
  32. package/dist/core/background-agent-store.js +5 -1
  33. package/dist/core/background-shell.d.ts +1 -1
  34. package/dist/core/checkpoint-store.d.ts +0 -1
  35. package/dist/core/exec-gate.d.ts +1 -1
  36. package/dist/core/exec-output-tail.d.ts +19 -1
  37. package/dist/core/exec-output-tail.js +44 -5
  38. package/dist/core/file-snapshot-store.d.ts +1 -1
  39. package/dist/core/fs-write-gate-policy.d.ts +1 -1
  40. package/dist/core/git-worktree-env.d.ts +1 -1
  41. package/dist/core/hooks.d.ts +1 -1
  42. package/dist/core/image-downsample.d.ts +44 -0
  43. package/dist/core/image-downsample.js +97 -0
  44. package/dist/core/lsp-session.d.ts +1 -0
  45. package/dist/core/lsp-session.js +24 -6
  46. package/dist/core/lsp.d.ts +11 -1
  47. package/dist/core/lsp.js +63 -6
  48. package/dist/core/mailbox-store.d.ts +3 -2
  49. package/dist/core/mailbox-store.js +19 -4
  50. package/dist/core/mcp.d.ts +3 -45
  51. package/dist/core/mcp.js +2 -97
  52. package/dist/core/media-byte-cap.d.ts +1 -1
  53. package/dist/core/memory-recall.d.ts +0 -12
  54. package/dist/core/memory-recall.js +0 -222
  55. package/dist/core/memory.d.ts +0 -6
  56. package/dist/core/memory.js +0 -88
  57. package/dist/core/message-utils.d.ts +1 -1
  58. package/dist/core/oracle-isolation.d.ts +1 -1
  59. package/dist/core/remote-env.d.ts +1 -1
  60. package/dist/core/roles.d.ts +1 -1
  61. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  62. package/dist/core/runner/assemble-result.d.ts +0 -2
  63. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  64. package/dist/core/runner/prepare-memory.d.ts +16 -0
  65. package/dist/core/runner/prepare-memory.js +191 -0
  66. package/dist/core/runner/prepare-task.d.ts +4 -28
  67. package/dist/core/runner/prepare-task.js +29 -208
  68. package/dist/core/runner/prompt-suggestions.d.ts +1 -1
  69. package/dist/core/runner/runtask.d.ts +2 -3
  70. package/dist/core/runner/runtask.js +337 -529
  71. package/dist/core/runner/session-rule-policy.d.ts +1 -1
  72. package/dist/core/runner/synthetic-tools.d.ts +5 -5
  73. package/dist/core/runner/synthetic-tools.js +4 -4
  74. package/dist/core/runner/tool-disclosure.d.ts +3 -3
  75. package/dist/core/runner/tool-disclosure.js +2 -2
  76. package/dist/core/runner/tool-output-projection.d.ts +6 -0
  77. package/dist/core/runner/tool-output-projection.js +81 -0
  78. package/dist/core/runtime.d.ts +1 -1
  79. package/dist/core/scheduler.d.ts +1 -1
  80. package/dist/core/sensitive-path-policy.d.ts +1 -1
  81. package/dist/core/session-reconcile.d.ts +1 -1
  82. package/dist/core/session-reconcile.js +5 -2
  83. package/dist/core/session-store.d.ts +2 -1
  84. package/dist/core/session-store.js +2 -1
  85. package/dist/core/session.d.ts +0 -1
  86. package/dist/core/task-notification.d.ts +1 -0
  87. package/dist/core/task-registry-agent.d.ts +115 -0
  88. package/dist/core/task-registry-agent.js +1163 -0
  89. package/dist/core/task-registry-monitor.d.ts +13 -0
  90. package/dist/core/task-registry-monitor.js +347 -0
  91. package/dist/core/task-registry-shared.d.ts +294 -0
  92. package/dist/core/task-registry-shared.js +271 -0
  93. package/dist/core/task-registry-workflow.d.ts +5 -0
  94. package/dist/core/task-registry-workflow.js +143 -0
  95. package/dist/core/task-registry.d.ts +54 -213
  96. package/dist/core/task-registry.js +190 -1766
  97. package/dist/core/task-tool-shape.d.ts +32 -0
  98. package/dist/core/task-tool-shape.js +83 -0
  99. package/dist/core/tool-policy.d.ts +1 -2
  100. package/dist/core/tool-result-budget.d.ts +1 -1
  101. package/dist/core/tool-result-store.d.ts +2 -2
  102. package/dist/core/tool-result-store.js +3 -3
  103. package/dist/core/tools.d.ts +7 -3
  104. package/dist/core/tools.js +3 -8
  105. package/dist/core/trace.d.ts +2 -2
  106. package/dist/core/types.d.ts +1 -29
  107. package/dist/core/workflow-journal-store.d.ts +2 -0
  108. package/dist/core/workflow-journal-store.js +14 -0
  109. package/dist/core/workflow-run-store-contract.d.ts +1 -1
  110. package/dist/core/workflow-run-store-contract.js +19 -19
  111. package/dist/engine/execution-env/node-execution-env.d.ts +14 -0
  112. package/dist/engine/execution-env/node-execution-env.js +184 -44
  113. package/dist/engine/lsp/frame-decoder.d.ts +1 -1
  114. package/dist/engine/lsp/frame-decoder.js +1 -1
  115. package/dist/engine/lsp/node-lsp-manager.d.ts +4 -2
  116. package/dist/engine/lsp/node-lsp-manager.js +22 -5
  117. package/dist/engine/lsp/stdio-lsp-transport.d.ts +1 -1
  118. package/dist/engine/lsp/stdio-lsp-transport.js +19 -8
  119. package/dist/index.d.ts +18 -16
  120. package/dist/index.js +15 -13
  121. package/dist/internal/harness-types.d.ts +7 -0
  122. package/dist/internal/harness-types.js +1 -0
  123. package/dist/internal/harness.d.ts +1 -7
  124. package/dist/internal/harness.js +1 -0
  125. package/dist/orchestration/goal.js +2 -2
  126. package/dist/orchestration/run-workflow-tool.d.ts +2 -1
  127. package/dist/orchestration/run-workflow-tool.js +29 -6
  128. package/dist/orchestration/workflow-types.d.ts +193 -0
  129. package/dist/orchestration/workflow-types.js +54 -0
  130. package/dist/orchestration/workflow.d.ts +3 -184
  131. package/dist/orchestration/workflow.js +87 -64
  132. package/dist/prompt-assembly/assemble.d.ts +3 -3
  133. package/dist/prompt-assembly/assemble.js +0 -16
  134. package/dist/prompt-assembly/explain.d.ts +1 -1
  135. package/dist/prompt-assembly/tool-catalog.d.ts +1 -1
  136. package/dist/prompts/default.d.ts +1 -6
  137. package/dist/prompts/default.js +3 -11
  138. package/dist/scenarios/env.d.ts +1 -1
  139. package/dist/scenarios/full-body.d.ts +0 -3
  140. package/dist/scenarios/full-body.js +0 -2
  141. package/dist/stores/cc/mailbox-store.js +6 -1
  142. package/dist/stores/file/background-agent-store.d.ts +2 -15
  143. package/dist/stores/file/background-agent-store.js +33 -102
  144. package/dist/stores/file/checkpoint-store.d.ts +1 -14
  145. package/dist/stores/file/checkpoint-store.js +47 -119
  146. package/dist/stores/file/file-snapshot-store.d.ts +1 -1
  147. package/dist/stores/file/mailbox-store.d.ts +1 -1
  148. package/dist/stores/file/mailbox-store.js +9 -7
  149. package/dist/stores/file/shared-ledger.d.ts +35 -0
  150. package/dist/stores/file/shared-ledger.js +94 -0
  151. package/dist/stores/file/workflow-run-store.d.ts +1 -14
  152. package/dist/stores/file/workflow-run-store.js +24 -93
  153. package/dist/tools/fs/bash-readonly-classifier.d.ts +8 -0
  154. package/dist/tools/fs/bash-readonly-classifier.js +166 -0
  155. package/dist/tools/fs/encoding.d.ts +5 -0
  156. package/dist/tools/fs/encoding.js +6 -0
  157. package/dist/tools/fs/fs-bash.d.ts +31 -0
  158. package/dist/tools/fs/fs-bash.js +672 -0
  159. package/dist/tools/fs/fs-pdf.d.ts +22 -0
  160. package/dist/tools/fs/fs-pdf.js +236 -0
  161. package/dist/tools/fs/fs-read.d.ts +8 -0
  162. package/dist/tools/fs/fs-read.js +286 -0
  163. package/dist/tools/fs/fs-search-tools.d.ts +5 -0
  164. package/dist/tools/fs/fs-search-tools.js +173 -0
  165. package/dist/tools/fs/fs-shared.d.ts +55 -0
  166. package/dist/tools/fs/fs-shared.js +144 -0
  167. package/dist/tools/fs/fs-write.d.ts +8 -0
  168. package/dist/tools/fs/fs-write.js +423 -0
  169. package/dist/tools/fs/index.d.ts +12 -65
  170. package/dist/tools/fs/index.js +26 -2026
  171. package/dist/tools/fs/notebook.d.ts +43 -0
  172. package/dist/tools/fs/notebook.js +141 -0
  173. package/dist/tools/fs/pdf.d.ts +1 -1
  174. package/dist/tools/fs/repo-map.d.ts +3 -3
  175. package/dist/tools/fs/repo-map.js +3 -3
  176. package/dist/tools/fs/safety.d.ts +1 -1
  177. package/dist/tools/fs/search.d.ts +1 -1
  178. package/dist/tools/fs/search.js +141 -12
  179. package/dist/tools/gitea-issue.js +4 -2
  180. package/dist/tools/monitor.d.ts +1 -1
  181. package/dist/tools/monitor.js +12 -8
  182. package/dist/tools/scheduler-tools.d.ts +1 -1
  183. package/dist/tools/scheduler-tools.js +16 -16
  184. package/dist/tools/task-list.js +34 -12
  185. package/dist/tools/web.d.ts +4 -2
  186. package/dist/tools/web.js +106 -20
  187. package/dist/tools/worktree.d.ts +1 -1
  188. package/dist/tools/worktree.js +14 -14
  189. package/package.json +1 -1
  190. package/dist/core/dynamic-recall.d.ts +0 -32
  191. package/dist/core/dynamic-recall.js +0 -76
@@ -0,0 +1,94 @@
1
+ import { AppendLog, atomicWriteFile, canonicalStoreKey, ensureDir, readJsonlRecords } from "./fs-atomic.js";
2
+ export class LedgerCore {
3
+ key;
4
+ paths;
5
+ model;
6
+ table;
7
+ rows = new Map();
8
+ refs = 1;
9
+ locks = new Map();
10
+ events = 0;
11
+ handle;
12
+ constructor(key, paths, model, table) {
13
+ this.key = key;
14
+ this.paths = paths;
15
+ this.model = model;
16
+ this.table = table;
17
+ }
18
+ bootstrap() {
19
+ for (const row of readJsonlRecords(this.paths.snapshotPath))
20
+ this.rows.set(this.model.keyOf(row), row);
21
+ const events = readJsonlRecords(this.paths.ledgerPath);
22
+ this.events = events.length;
23
+ for (const ev of events)
24
+ this.model.apply(this.rows, ev);
25
+ this.handle = new AppendLog(this.paths.ledgerPath);
26
+ }
27
+ withLock(key, fn) {
28
+ const prev = this.locks.get(key) ?? Promise.resolve();
29
+ const run = prev.then(() => fn(), () => fn());
30
+ const tail = run.then(() => { }, () => { });
31
+ this.locks.set(key, tail);
32
+ void tail.then(() => {
33
+ if (this.locks.get(key) === tail)
34
+ this.locks.delete(key);
35
+ });
36
+ return run;
37
+ }
38
+ commit(ev, fsync, compactEvery) {
39
+ this.log.append(ev, fsync);
40
+ this.model.apply(this.rows, ev);
41
+ this.events++;
42
+ if (this.events >= compactEvery) {
43
+ try {
44
+ this.compact();
45
+ }
46
+ catch {
47
+ }
48
+ }
49
+ }
50
+ compact() {
51
+ const lines = [...this.rows.values()].map((r) => JSON.stringify(r)).join("\n");
52
+ atomicWriteFile(this.paths.tmpDir, this.paths.snapshotPath, lines.length ? `${lines}\n` : "");
53
+ this.log.closeForSwap();
54
+ atomicWriteFile(this.paths.tmpDir, this.paths.ledgerPath, "");
55
+ this.events = 0;
56
+ }
57
+ release() {
58
+ this.refs -= 1;
59
+ if (this.refs > 0)
60
+ return;
61
+ if (this.table.get(this.key) === this)
62
+ this.table.delete(this.key);
63
+ this.handle?.close();
64
+ }
65
+ get pendingEvents() {
66
+ return this.events;
67
+ }
68
+ get log() {
69
+ if (this.handle === undefined)
70
+ throw new Error("file ledger: used before bootstrap");
71
+ return this.handle;
72
+ }
73
+ }
74
+ export class SharedLedgerTable {
75
+ model;
76
+ live = new Map();
77
+ constructor(model) {
78
+ this.model = model;
79
+ }
80
+ acquire(paths) {
81
+ ensureDir(paths.dir);
82
+ ensureDir(paths.tmpDir);
83
+ const key = canonicalStoreKey(paths.dir);
84
+ const existing = this.live.get(key);
85
+ if (existing !== undefined) {
86
+ existing.refs += 1;
87
+ return existing;
88
+ }
89
+ const core = new LedgerCore(key, paths, this.model, this.live);
90
+ this.live.set(key, core);
91
+ core.bootstrap();
92
+ return core;
93
+ }
94
+ }
@@ -5,24 +5,12 @@ export interface FileWorkflowRunStoreOptions {
5
5
  compactEvery?: number;
6
6
  }
7
7
  export declare class FileWorkflowRunStore implements WorkflowRunStore {
8
- private readonly dir;
9
- private readonly tmpDir;
10
- private readonly ledgerPath;
11
- private readonly snapshotPath;
12
8
  private readonly fsyncEnabled;
13
9
  private readonly compactEvery;
14
- private readonly shared;
10
+ private readonly ledger;
15
11
  private closed;
16
- private readonly canonicalDir;
17
12
  private get runs();
18
- private get locks();
19
- private get ledgerEvents();
20
- private set ledgerEvents(value);
21
- private get log();
22
- private set log(value);
23
13
  constructor(root: string, opts?: FileWorkflowRunStoreOptions);
24
- private replay;
25
- private applyEvent;
26
14
  private withLock;
27
15
  private commit;
28
16
  put(id: string, run: WorkflowRun): Promise<void>;
@@ -39,7 +27,6 @@ export declare class FileWorkflowRunStore implements WorkflowRunStore {
39
27
  maxAgeMs?: number;
40
28
  keep?: number;
41
29
  }): Promise<number>;
42
- private compact;
43
30
  compactNow(): void;
44
31
  get size(): number;
45
32
  close(): void;
@@ -1,95 +1,40 @@
1
1
  import { join } from "node:path";
2
2
  import { WorkflowRunStoreError, isTerminalWorkflowStatus, summarizeWorkflowRun, } from "../../core/workflow-run-store.js";
3
- import { canonicalStoreKey, AppendLog, atomicWriteFile, ensureDir, readJsonlRecords } from "./fs-atomic.js";
4
- const sharedRunDirs = new Map();
3
+ import { SharedLedgerTable } from "./shared-ledger.js";
4
+ const runLedgers = new SharedLedgerTable({
5
+ keyOf: (run) => run.id,
6
+ apply: (runs, ev) => {
7
+ if (ev.t === "delete") {
8
+ runs.delete(ev.id);
9
+ return;
10
+ }
11
+ runs.set(ev.id, ev.run);
12
+ },
13
+ });
5
14
  export class FileWorkflowRunStore {
6
- dir;
7
- tmpDir;
8
- ledgerPath;
9
- snapshotPath;
10
15
  fsyncEnabled;
11
16
  compactEvery;
12
- shared;
17
+ ledger;
13
18
  closed = false;
14
- canonicalDir;
15
19
  get runs() {
16
- return this.shared.runs;
17
- }
18
- get locks() {
19
- return this.shared.locks;
20
- }
21
- get ledgerEvents() {
22
- return this.shared.ledgerEvents;
23
- }
24
- set ledgerEvents(v) {
25
- this.shared.ledgerEvents = v;
26
- }
27
- get log() {
28
- return this.shared.log;
29
- }
30
- set log(v) {
31
- this.shared.log = v;
20
+ return this.ledger.rows;
32
21
  }
33
22
  constructor(root, opts = {}) {
34
- this.dir = join(root, "workflow-runs");
35
- this.tmpDir = join(root, "tmp");
36
- this.ledgerPath = join(this.dir, "runs.jsonl");
37
- this.snapshotPath = join(this.dir, "runs.snapshot.jsonl");
38
23
  this.fsyncEnabled = opts.fsync !== false;
39
24
  this.compactEvery = opts.compactEvery ?? 1000;
40
- ensureDir(this.dir);
41
- ensureDir(this.tmpDir);
42
- const canonical = canonicalStoreKey(this.dir);
43
- this.canonicalDir = canonical;
44
- const existing = sharedRunDirs.get(canonical);
45
- if (existing !== undefined) {
46
- existing.refs += 1;
47
- this.shared = existing;
48
- return;
49
- }
50
- this.shared = { runs: new Map(), locks: new Map(), ledgerEvents: 0, log: undefined, refs: 1 };
51
- sharedRunDirs.set(canonical, this.shared);
52
- this.replay();
53
- this.log = new AppendLog(this.ledgerPath);
54
- }
55
- replay() {
56
- for (const snap of readJsonlRecords(this.snapshotPath)) {
57
- this.runs.set(snap.id, snap);
58
- }
59
- const events = readJsonlRecords(this.ledgerPath);
60
- this.ledgerEvents = events.length;
61
- for (const ev of events)
62
- this.applyEvent(ev);
63
- }
64
- applyEvent(ev) {
65
- if (ev.t === "delete") {
66
- this.runs.delete(ev.id);
67
- return;
68
- }
69
- this.runs.set(ev.id, ev.run);
25
+ const dir = join(root, "workflow-runs");
26
+ this.ledger = runLedgers.acquire({
27
+ dir,
28
+ tmpDir: join(root, "tmp"),
29
+ ledgerPath: join(dir, "runs.jsonl"),
30
+ snapshotPath: join(dir, "runs.snapshot.jsonl"),
31
+ });
70
32
  }
71
33
  withLock(id, fn) {
72
- const prev = this.locks.get(id) ?? Promise.resolve();
73
- const run = prev.then(() => fn(), () => fn());
74
- const tail = run.then(() => { }, () => { });
75
- this.locks.set(id, tail);
76
- void tail.then(() => {
77
- if (this.locks.get(id) === tail)
78
- this.locks.delete(id);
79
- });
80
- return run;
34
+ return this.ledger.withLock(id, fn);
81
35
  }
82
36
  commit(ev, fsync) {
83
- this.log.append(ev, fsync);
84
- this.applyEvent(ev);
85
- this.ledgerEvents++;
86
- if (this.ledgerEvents >= this.compactEvery) {
87
- try {
88
- this.compact();
89
- }
90
- catch {
91
- }
92
- }
37
+ this.ledger.commit(ev, fsync, this.compactEvery);
93
38
  }
94
39
  async put(id, run) {
95
40
  return this.withLock(id, () => {
@@ -161,15 +106,8 @@ export class FileWorkflowRunStore {
161
106
  }
162
107
  return n;
163
108
  }
164
- compact() {
165
- const lines = [...this.runs.values()].map((r) => JSON.stringify(r)).join("\n");
166
- atomicWriteFile(this.tmpDir, this.snapshotPath, lines.length ? `${lines}\n` : "");
167
- this.log.closeForSwap();
168
- atomicWriteFile(this.tmpDir, this.ledgerPath, "");
169
- this.ledgerEvents = 0;
170
- }
171
109
  compactNow() {
172
- this.compact();
110
+ this.ledger.compact();
173
111
  }
174
112
  get size() {
175
113
  return this.runs.size;
@@ -178,13 +116,6 @@ export class FileWorkflowRunStore {
178
116
  if (this.closed)
179
117
  return;
180
118
  this.closed = true;
181
- const st = sharedRunDirs.get(this.canonicalDir);
182
- if (st !== undefined && st === this.shared) {
183
- st.refs -= 1;
184
- if (st.refs > 0)
185
- return;
186
- sharedRunDirs.delete(this.canonicalDir);
187
- }
188
- this.log.close();
119
+ this.ledger.release();
189
120
  }
190
121
  }
@@ -0,0 +1,8 @@
1
+ export declare const BASH_READONLY_DEFAULT_ALLOW: readonly string[];
2
+ export declare function parseLeadingCommandName(command: string): {
3
+ name: string;
4
+ } | {
5
+ reject: string;
6
+ };
7
+ export declare function coarseReadonlyCheck(command: string, allow: ReadonlySet<string>): string | undefined;
8
+ export declare function classifyCompoundReadonly(command: string, allow: ReadonlySet<string>): string | undefined;
@@ -0,0 +1,166 @@
1
+ import { isBlockedDevicePath, normalizeAbsPathLexically } from "./safety.js";
2
+ export const BASH_READONLY_DEFAULT_ALLOW = [
3
+ "ls", "cat", "head", "tail", "wc", "pwd", "echo", "whoami", "uname",
4
+ "grep", "cut", "tr", "basename", "dirname", "stat", "du", "df", "which",
5
+ ];
6
+ const SHELL_OPERATORS = /[;&|<>$()`\n\r\\]/;
7
+ export function parseLeadingCommandName(command) {
8
+ const trimmed = command.trim();
9
+ if (!trimmed)
10
+ return { reject: "empty command" };
11
+ if (SHELL_OPERATORS.test(trimmed)) {
12
+ return {
13
+ reject: "shell operators (pipes, redirects, ;, &&, command substitution, subshells, variable expansion) are not allowed — run a single simple command",
14
+ };
15
+ }
16
+ const first = trimmed.split(/\s+/)[0];
17
+ if (first.includes("/"))
18
+ return { reject: "the command must be a bare name resolved via PATH (no path prefix)" };
19
+ if (first.includes("="))
20
+ return { reject: "leading environment-variable assignments are not allowed" };
21
+ if (/["'{}*?[\]~]/.test(first))
22
+ return { reject: "the command name must be a bare token (no quotes, braces, globs, or ~)" };
23
+ return { name: first };
24
+ }
25
+ export function coarseReadonlyCheck(command, allow) {
26
+ const parsed = parseLeadingCommandName(command);
27
+ if ("reject" in parsed)
28
+ return parsed.reject;
29
+ if (!allow.has(parsed.name))
30
+ return `command "${parsed.name}" is not in the read-only allowlist`;
31
+ return undefined;
32
+ }
33
+ const SHELL_SEGMENT_HARD_REJECT = /[<>$()`\n\r\\]/;
34
+ export function classifyCompoundReadonly(command, allow) {
35
+ const trimmed = command.trim();
36
+ if (!trimmed)
37
+ return "empty command";
38
+ if (SHELL_SEGMENT_HARD_REJECT.test(trimmed)) {
39
+ return "redirection, command/variable substitution, subshells, escapes, and line breaks are not allowed";
40
+ }
41
+ const source = trimmed.endsWith(";") ? trimmed.slice(0, -1) : trimmed;
42
+ const segments = [];
43
+ const pipeFed = [false];
44
+ let cur = "";
45
+ for (let i = 0; i < source.length; i++) {
46
+ const c = source[i];
47
+ if (c === "&") {
48
+ if (source[i + 1] === "&") {
49
+ segments.push(cur);
50
+ cur = "";
51
+ pipeFed.push(false);
52
+ i++;
53
+ }
54
+ else
55
+ return "`&` backgrounding is not allowed — a backgrounded process outlives the command";
56
+ }
57
+ else if (c === "|") {
58
+ if (source[i + 1] === "|") {
59
+ segments.push(cur);
60
+ cur = "";
61
+ pipeFed.push(false);
62
+ i++;
63
+ }
64
+ else {
65
+ segments.push(cur);
66
+ cur = "";
67
+ pipeFed.push(true);
68
+ }
69
+ }
70
+ else if (c === ";") {
71
+ segments.push(cur);
72
+ cur = "";
73
+ pipeFed.push(false);
74
+ }
75
+ else
76
+ cur += c;
77
+ }
78
+ segments.push(cur);
79
+ for (const segment of segments) {
80
+ const reason = coarseReadonlyCheck(segment, allow);
81
+ if (reason !== undefined)
82
+ return reason;
83
+ }
84
+ const HEAD_AUTO_ALLOW_MAX = 1_000_000;
85
+ const headBoundIsSmall = (name, toks) => {
86
+ if (name !== "head")
87
+ return false;
88
+ let byteBound = false;
89
+ for (let k = 1; k < toks.length; k++) {
90
+ const t = toks[k];
91
+ if (!t.startsWith("-") || t === "-")
92
+ continue;
93
+ const inline = /^-[A-Za-z]*c(\d+)$/.exec(t) ?? /^--bytes=(\d+)$/.exec(t);
94
+ if (inline) {
95
+ if (Number(inline[1]) > HEAD_AUTO_ALLOW_MAX)
96
+ return false;
97
+ byteBound = true;
98
+ continue;
99
+ }
100
+ if (/^-[A-Za-z]*c$/.test(t) || t === "--bytes") {
101
+ const v = toks[k + 1];
102
+ if (v === undefined || !/^\d+$/.test(v) || Number(v) > HEAD_AUTO_ALLOW_MAX)
103
+ return false;
104
+ byteBound = true;
105
+ k++;
106
+ continue;
107
+ }
108
+ return false;
109
+ }
110
+ return byteBound;
111
+ };
112
+ const STDIN_FILE_FLOOR = { cat: 1, grep: 2, head: 1, tail: 1, wc: 1, cut: 1, tr: Infinity };
113
+ for (let si = 0; si < segments.length; si++) {
114
+ const toks = segments[si].trim().split(/\s+/).filter((t) => t.length > 0);
115
+ if (toks.length === 0)
116
+ continue;
117
+ const name = toks[0];
118
+ const nonOption = toks.slice(1).filter((t) => !t.startsWith("-"));
119
+ if (!(pipeFed[si] ?? false)) {
120
+ const floor = STDIN_FILE_FLOOR[name];
121
+ const restArgs = toks.slice(1);
122
+ const grepPatternSuppliedByFlag = name === "grep" && restArgs.some((t) => t === "-e" || t.startsWith("-e") || t === "-f" || t.startsWith("--regexp") || t.startsWith("--file"));
123
+ let sawNonFlagOperand = false;
124
+ let hasStdinDash = false;
125
+ if (name !== "tr") {
126
+ for (let k = 0; k < restArgs.length; k++) {
127
+ const t = restArgs[k];
128
+ if (name === "cut" && (t === "-d" || t === "--delimiter")) {
129
+ k++;
130
+ continue;
131
+ }
132
+ if (name === "grep" && (t === "-f-" || t === "--file=-")) {
133
+ hasStdinDash = true;
134
+ break;
135
+ }
136
+ if (t.startsWith("-") && t !== "-")
137
+ continue;
138
+ const isGrepPatternPosition = name === "grep" && !grepPatternSuppliedByFlag && !sawNonFlagOperand;
139
+ sawNonFlagOperand = true;
140
+ if (isGrepPatternPosition)
141
+ continue;
142
+ if (t === "-") {
143
+ hasStdinDash = true;
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ if (floor !== undefined && hasStdinDash) {
149
+ return `"${name}" reads stdin via an explicit "-" argument and would block until the tool timeout — not auto-allowed`;
150
+ }
151
+ if (floor !== undefined && nonOption.length < floor) {
152
+ return `"${name}" with no file argument reads stdin and would block until the tool timeout — not auto-allowed`;
153
+ }
154
+ }
155
+ if (name === "tail" && toks.slice(1).some((t) => t === "--follow" || t.startsWith("--follow=") || /^[-+][^\s]*[fF]/.test(t))) {
156
+ return "`tail` in follow mode never terminates — not auto-allowed";
157
+ }
158
+ const GENERATOR_DEVICES = new Set(["/dev/zero", "/dev/random", "/dev/urandom", "/dev/full"]);
159
+ const deviceArgs = toks.slice(1).filter((t) => !t.startsWith("-")).map(normalizeAbsPathLexically).filter(isBlockedDevicePath);
160
+ const rescuedByHead = headBoundIsSmall(name, toks) && deviceArgs.every((d) => GENERATOR_DEVICES.has(d));
161
+ if (!rescuedByHead && deviceArgs.length > 0) {
162
+ return "an unbounded device source (/dev/zero, /dev/stdin, /proc/<pid>/fd/0, …) would block the pipeline until the tool timeout — not auto-allowed";
163
+ }
164
+ }
165
+ return undefined;
166
+ }
@@ -13,3 +13,8 @@ export declare function detectFileEncoding(bytes: Uint8Array): DetectedFileEncod
13
13
  export declare function decodeTextBytes(bytes: Uint8Array): DecodedTextFile;
14
14
  export declare function encodeTextForFile(text: string, encoding: DetectedFileEncoding, endings: DetectedLineEndings | "preserve"): string | Uint8Array;
15
15
  export declare function normalizeEditText(s: string): string;
16
+ export declare function splitLeadingBom(text: string): {
17
+ hadBom: boolean;
18
+ text: string;
19
+ };
20
+ export declare function normalizeFileText(s: string): string;
@@ -41,3 +41,9 @@ export function encodeTextForFile(text, encoding, endings) {
41
41
  export function normalizeEditText(s) {
42
42
  return s.replaceAll("\r\n", "\n");
43
43
  }
44
+ export function splitLeadingBom(text) {
45
+ return text.charCodeAt(0) === 0xfeff ? { hadBom: true, text: text.slice(1) } : { hadBom: false, text };
46
+ }
47
+ export function normalizeFileText(s) {
48
+ return normalizeEditText(splitLeadingBom(s).text);
49
+ }
@@ -0,0 +1,31 @@
1
+ import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
2
+ import { type TaskRegistry } from "../../core/task-registry.js";
3
+ import { type CwdRef } from "./fs-shared.js";
4
+ export declare function bashReversibilityProbe(allow?: readonly string[]): (args: unknown) => {
5
+ reversible: boolean;
6
+ };
7
+ export interface ExecClampOption {
8
+ deadlineMs: () => number | undefined;
9
+ onClamp?: (requestedSec: number, clampedSec: number) => void;
10
+ registerKillHandle?: (toolCallId: string, handle: {
11
+ pid: number;
12
+ kill: () => void;
13
+ }) => () => void;
14
+ }
15
+ export declare function bashExitOneInterpretation(command: string): string | undefined;
16
+ export declare function canAutoBackground(command: string): boolean;
17
+ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string, coAuthor?: string | false, cwdRef?: CwdRef, taskOpts?: {
18
+ taskRegistry?: TaskRegistry;
19
+ taskOwner?: string;
20
+ taskScope?: string;
21
+ sessionId?: string;
22
+ onTaskNotification?: (n: import("../../core/task-notification.js").TaskNotificationPayload, opts?: {
23
+ priority?: "now" | "next" | "later";
24
+ }) => void;
25
+ detachHub?: import("../../core/tool-detach.js").ToolDetachHub;
26
+ execClamp?: ExecClampOption;
27
+ autoBackgroundOnTimeout?: boolean;
28
+ }): AgentTool;
29
+ export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical: string, allow: ReadonlySet<string>, execClamp?: ExecClampOption): AgentTool;
30
+ export declare function createEnvTaskOutputTool(env: ExecutionEnv): AgentTool;
31
+ export declare function createEnvTaskStopTool(env: ExecutionEnv, registry?: TaskRegistry): AgentTool;