@sema-agent/core 5.7.0 → 5.9.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 (102) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/cascade.js +24 -0
  4. package/dist/agents/subagent.d.ts +7 -7
  5. package/dist/agents/subagent.js +74 -33
  6. package/dist/agents/teacher.js +3 -3
  7. package/dist/agents/team.d.ts +1 -1
  8. package/dist/agents/team.js +1 -1
  9. package/dist/agents/verify.js +1 -1
  10. package/dist/brain/anthropic.js +24 -35
  11. package/dist/brain/open-responses.d.ts +11 -0
  12. package/dist/brain/open-responses.js +721 -0
  13. package/dist/brain/openai.js +22 -33
  14. package/dist/brain/request-params.d.ts +1 -0
  15. package/dist/brain/request-params.js +16 -0
  16. package/dist/brain/stream-engine.d.ts +0 -2
  17. package/dist/brain/stream-engine.js +5 -47
  18. package/dist/brain/stream-shared.d.ts +0 -10
  19. package/dist/brain/stream-shared.js +0 -23
  20. package/dist/brain/terminal-cause.d.ts +0 -1
  21. package/dist/brain/terminal-cause.js +0 -3
  22. package/dist/brain/timeout.d.ts +4 -2
  23. package/dist/brain/timeout.js +4 -14
  24. package/dist/config/catalog.d.ts +3 -11
  25. package/dist/config/catalog.js +30 -69
  26. package/dist/config/defaults.d.ts +0 -3
  27. package/dist/config/defaults.js +0 -3
  28. package/dist/core/auto-compaction.d.ts +2 -11
  29. package/dist/core/auto-compaction.js +3 -67
  30. package/dist/core/checkpoint-store.d.ts +10 -8
  31. package/dist/core/checkpoint-store.js +7 -7
  32. package/dist/core/fs-write-gate-policy.js +2 -2
  33. package/dist/core/lsp-diagnostics.d.ts +3 -2
  34. package/dist/core/lsp-diagnostics.js +20 -7
  35. package/dist/core/memory-engine/file-backend.js +20 -17
  36. package/dist/core/memory-engine/layout.d.ts +5 -0
  37. package/dist/core/memory-engine/layout.js +72 -47
  38. package/dist/core/runner/assemble-result.d.ts +3 -8
  39. package/dist/core/runner/assemble-result.js +24 -18
  40. package/dist/core/runner/prepare-task.d.ts +27 -6
  41. package/dist/core/runner/prepare-task.js +209 -144
  42. package/dist/core/runner/runtask.d.ts +0 -2
  43. package/dist/core/runner/runtask.js +228 -266
  44. package/dist/core/runner/session-rule-policy.d.ts +1 -0
  45. package/dist/core/runner/session-rule-policy.js +4 -3
  46. package/dist/core/session-policy-store.d.ts +5 -0
  47. package/dist/core/session-policy-store.js +3 -1
  48. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -2
  49. package/dist/core/task-registry-agent.d.ts +2 -0
  50. package/dist/core/task-registry-agent.js +33 -1
  51. package/dist/core/task-registry-shared.d.ts +0 -1
  52. package/dist/core/task-registry.d.ts +2 -0
  53. package/dist/core/task-registry.js +9 -1
  54. package/dist/core/tool-errors.js +10 -2
  55. package/dist/core/tool-policy.d.ts +8 -0
  56. package/dist/core/tool-policy.js +11 -0
  57. package/dist/core/trace.d.ts +1 -10
  58. package/dist/core/types.d.ts +27 -30
  59. package/dist/core/usage-window-store.d.ts +39 -0
  60. package/dist/core/usage-window-store.js +115 -0
  61. package/dist/engine/execution-env/node-execution-env.js +0 -22
  62. package/dist/engine/harness/agent-harness.d.ts +1 -7
  63. package/dist/engine/harness/agent-harness.js +16 -53
  64. package/dist/engine/harness/types.d.ts +3 -15
  65. package/dist/engine/llm/types.d.ts +3 -75
  66. package/dist/engine/loop/agent-loop.d.ts +1 -1
  67. package/dist/engine/loop/agent-loop.js +7 -173
  68. package/dist/engine/loop/types.d.ts +1 -18
  69. package/dist/engine/session/repo-utils.d.ts +1 -2
  70. package/dist/engine/session/repo-utils.js +0 -7
  71. package/dist/index.d.ts +13 -7
  72. package/dist/index.js +10 -5
  73. package/dist/internal/llm.d.ts +1 -1
  74. package/dist/orchestration/goal.js +1 -5
  75. package/dist/orchestration/run-workflow-tool.d.ts +1 -1
  76. package/dist/orchestration/run-workflow-tool.js +1 -2
  77. package/dist/orchestration/workflow-governance.d.ts +6 -6
  78. package/dist/orchestration/workflow-governance.js +33 -47
  79. package/dist/orchestration/workflow.js +14 -5
  80. package/dist/prompt-assembly/event-registry.js +2 -3
  81. package/dist/stores/cc/lockfile.js +9 -11
  82. package/dist/stores/cc/mailbox-store.js +49 -38
  83. package/dist/stores/file/index.d.ts +3 -0
  84. package/dist/stores/file/index.js +4 -0
  85. package/dist/stores/file/usage-window-store.d.ts +9 -0
  86. package/dist/stores/file/usage-window-store.js +86 -0
  87. package/dist/tools/fs/fs-bash.d.ts +1 -10
  88. package/dist/tools/fs/fs-bash.js +20 -60
  89. package/dist/tools/fs/index.d.ts +0 -2
  90. package/dist/tools/fs/index.js +1 -2
  91. package/dist/tools/web.js +20 -19
  92. package/package.json +4 -5
  93. package/dist/bin/sema-tb.d.ts +0 -31
  94. package/dist/bin/sema-tb.js +0 -448
  95. package/dist/bin/tb-env.d.ts +0 -2
  96. package/dist/bin/tb-env.js +0 -17
  97. package/dist/brain/walltime.d.ts +0 -1
  98. package/dist/brain/walltime.js +0 -1
  99. package/dist/core/runner/call-cap.d.ts +0 -67
  100. package/dist/core/runner/call-cap.js +0 -145
  101. package/dist/core/runner/cut-kill.d.ts +0 -10
  102. package/dist/core/runner/cut-kill.js +0 -37
@@ -9,6 +9,7 @@ import { FileToolResultStore } from "./tool-result-store.js";
9
9
  import { FileSessionPolicyStore } from "./session-policy-store.js";
10
10
  import { FileFileSnapshotStore } from "./file-snapshot-store.js";
11
11
  import { FileWorkflowJournalStore } from "./workflow-journal-store.js";
12
+ import { FileUsageWindowStore } from "./usage-window-store.js";
12
13
  export { FileCheckpointStore } from "./checkpoint-store.js";
13
14
  export { FileMemoryStore } from "./memory-store.js";
14
15
  export { FileSessionRepo } from "./session-store.js";
@@ -16,6 +17,7 @@ export { FileToolResultStore } from "./tool-result-store.js";
16
17
  export { FileSessionPolicyStore } from "./session-policy-store.js";
17
18
  export { FileFileSnapshotStore } from "./file-snapshot-store.js";
18
19
  export { FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult } from "./workflow-journal-store.js";
20
+ export { FileUsageWindowStore } from "./usage-window-store.js";
19
21
  export { resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock } from "./fs-atomic.js";
20
22
  export { atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog } from "./fs-atomic.js";
21
23
  export class FileStorageBackend {
@@ -27,6 +29,7 @@ export class FileStorageBackend {
27
29
  sessionPolicyStore;
28
30
  fileSnapshotStore;
29
31
  workflowJournalStore;
32
+ usageWindowStore;
30
33
  consolidationLock;
31
34
  lock;
32
35
  fileCheckpoints;
@@ -53,6 +56,7 @@ export class FileStorageBackend {
53
56
  this.fileSnapshotStore = new FileFileSnapshotStore(this.root, opts.snapshotBounds, corruptRead);
54
57
  this.fileWorkflowJournal = new FileWorkflowJournalStore(this.root);
55
58
  this.workflowJournalStore = this.fileWorkflowJournal;
59
+ this.usageWindowStore = new FileUsageWindowStore(this.root);
56
60
  this.consolidationLock = createFileConsolidationLock(join(this.root, "consolidation-locks"));
57
61
  }
58
62
  catch (err) {
@@ -0,0 +1,9 @@
1
+ import { type UsageWindow, type UsageWindowReading, type UsageWindowStore } from "../../core/usage-window-store.js";
2
+ export declare class FileUsageWindowStore implements UsageWindowStore {
3
+ private readonly dir;
4
+ constructor(root: string);
5
+ private pathFor;
6
+ private loadRecord;
7
+ charge(key: string, tokens: number, at: number, windows: readonly UsageWindow[]): Promise<void>;
8
+ read(key: string, windows: readonly UsageWindow[], now: number): Promise<readonly UsageWindowReading[]>;
9
+ }
@@ -0,0 +1,86 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { chargeUsageRecord, readUsageRecord, EMPTY_USAGE_WINDOW_RECORD, } from "../../core/usage-window-store.js";
4
+ import { atomicWriteFile, ensureDir, sanitizeScope } from "./fs-atomic.js";
5
+ export class FileUsageWindowStore {
6
+ dir;
7
+ constructor(root) {
8
+ this.dir = join(root, "usage-windows");
9
+ ensureDir(this.dir);
10
+ }
11
+ pathFor(key) {
12
+ return join(this.dir, `${sanitizeScope(key)}.json`);
13
+ }
14
+ loadRecord(key) {
15
+ const path = this.pathFor(key);
16
+ let raw;
17
+ try {
18
+ raw = readFileSync(path, "utf8");
19
+ }
20
+ catch (err) {
21
+ if (err.code === "ENOENT")
22
+ return EMPTY_USAGE_WINDOW_RECORD;
23
+ throw err;
24
+ }
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(raw);
28
+ }
29
+ catch {
30
+ throw corrupt(path, "unparseable JSON (torn/zero-byte write?)");
31
+ }
32
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
33
+ throw corrupt(path, "not a JSON object");
34
+ const rec = parsed;
35
+ const slots = validateSlots(rec.slots, path);
36
+ const buckets = validateBuckets(rec.buckets, path);
37
+ return { slots, buckets };
38
+ }
39
+ async charge(key, tokens, at, windows) {
40
+ const next = chargeUsageRecord(this.loadRecord(key), tokens, at, windows);
41
+ atomicWriteFile(join(this.dir, "tmp"), this.pathFor(key), JSON.stringify(next));
42
+ }
43
+ async read(key, windows, now) {
44
+ return readUsageRecord(this.loadRecord(key), windows, now);
45
+ }
46
+ }
47
+ function corrupt(path, reason) {
48
+ const e = new Error(`usage-window ledger at ${path} is unreadable (${reason}). Refused rather than treated as empty: an unreadable ceiling would grant the key an unlimited, silent allowance.`);
49
+ e.code = "usage_window.store_corrupt";
50
+ return e;
51
+ }
52
+ function validateSlots(value, path) {
53
+ if (value === undefined)
54
+ return [];
55
+ if (!Array.isArray(value))
56
+ throw corrupt(path, "`slots` is not an array");
57
+ return value.map((s) => {
58
+ if (s === null || typeof s !== "object")
59
+ throw corrupt(path, "`slots` entry is not an object");
60
+ const { at, tokens } = s;
61
+ if (typeof at !== "number" || !Number.isFinite(at) || typeof tokens !== "number" || !Number.isFinite(tokens)) {
62
+ throw corrupt(path, "`slots` entry has a non-numeric `at`/`tokens`");
63
+ }
64
+ return { at, tokens };
65
+ });
66
+ }
67
+ function validateBuckets(value, path) {
68
+ if (value === undefined)
69
+ return [];
70
+ if (!Array.isArray(value))
71
+ throw corrupt(path, "`buckets` is not an array");
72
+ return value.map((b) => {
73
+ if (b === null || typeof b !== "object")
74
+ throw corrupt(path, "`buckets` entry is not an object");
75
+ const { windowMs, openedAt, tokens } = b;
76
+ if (typeof windowMs !== "number" ||
77
+ !Number.isFinite(windowMs) ||
78
+ typeof openedAt !== "number" ||
79
+ !Number.isFinite(openedAt) ||
80
+ typeof tokens !== "number" ||
81
+ !Number.isFinite(tokens)) {
82
+ throw corrupt(path, "`buckets` entry has a non-numeric `windowMs`/`openedAt`/`tokens`");
83
+ }
84
+ return { windowMs, openedAt, tokens };
85
+ });
86
+ }
@@ -5,14 +5,6 @@ import { type BashReadonlyRootBoundary } from "./bash-readonly-classifier.js";
5
5
  export declare function bashReversibilityProbe(allow?: readonly string[], boundary?: BashReadonlyRootBoundary | (() => BashReadonlyRootBoundary | undefined)): (args: unknown) => {
6
6
  reversible: boolean;
7
7
  };
8
- export interface ExecClampOption {
9
- deadlineMs: () => number | undefined;
10
- onClamp?: (requestedSec: number, clampedSec: number) => void;
11
- registerKillHandle?: (toolCallId: string, handle: {
12
- pid: number;
13
- kill: () => void;
14
- }) => () => void;
15
- }
16
8
  export declare function bashExitOneInterpretation(command: string): string | undefined;
17
9
  export declare function canAutoBackground(command: string, depth?: number): boolean;
18
10
  export declare function createBashTool(env: ExecutionEnv, rootCanonical: string, coAuthor?: string | false, cwdRef?: CwdRef, taskOpts?: {
@@ -24,14 +16,13 @@ export declare function createBashTool(env: ExecutionEnv, rootCanonical: string,
24
16
  priority?: "now" | "next" | "later";
25
17
  }) => void;
26
18
  detachHub?: import("../../core/tool-detach.js").ToolDetachHub;
27
- execClamp?: ExecClampOption;
28
19
  autoBackgroundOnTimeout?: boolean;
29
20
  oneShot?: boolean;
30
21
  additionalRoots?: readonly string[];
31
22
  bashDefaultTimeoutMs?: number;
32
23
  bashMaxTimeoutMs?: number;
33
24
  }): AgentTool;
34
- export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical: string, allow: ReadonlySet<string>, execClamp?: ExecClampOption, opts?: {
25
+ export declare function createBashReadonlyTool(env: ExecutionEnv, rootCanonical: string, allow: ReadonlySet<string>, opts?: {
35
26
  bashDefaultTimeoutMs?: number;
36
27
  bashMaxTimeoutMs?: number;
37
28
  additionalRoots?: readonly string[];
@@ -231,61 +231,27 @@ function msTimeoutToRequestedSec(timeoutMs) {
231
231
  return undefined;
232
232
  return Math.max(1, Math.round(timeoutMs / 1000));
233
233
  }
234
- async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, cwdRef, detach, execClamp, toolCallId, readOnly, containmentRoots) {
234
+ async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, cwdRef, detach, readOnly, containmentRoots) {
235
235
  const requestedSec = Math.max(1, Math.floor(timeoutSec ?? caps.defaultSec));
236
236
  let timeout = Math.min(caps.maxSec, requestedSec);
237
237
  const cappedByMaxTimeout = requestedSec > caps.maxSec;
238
- let clampedByDeadline = false;
239
- let deadlineExhausted = false;
240
- if (execClamp) {
241
- const deadline = execClamp.deadlineMs();
242
- if (deadline !== undefined) {
243
- const rawRemainSec = Math.floor((deadline - Date.now()) / 1000);
244
- const maxSec = Math.max(1, rawRemainSec);
245
- if (maxSec < timeout) {
246
- execClamp.onClamp?.(requestedSec, maxSec);
247
- timeout = maxSec;
248
- clampedByDeadline = true;
249
- deadlineExhausted = rawRemainSec < 1;
250
- }
251
- }
252
- }
253
238
  const effectiveCwd = cwdRef ? cwdRef.current : cwd;
254
239
  const toRun = cwdRef
255
240
  ? `${command}\n__cc_rc=$?; printf '%s%s%s' '${CWD_SENTINEL}' "$(pwd)" '${CWD_SENTINEL}' 1>&2; exit $__cc_rc`
256
241
  : command;
257
242
  const execEnv = cwdRef ? { env: { PWD: effectiveCwd } } : undefined;
258
- let deregisterKill;
259
- const registerKillHandle = execClamp?.registerKillHandle;
260
- let res;
261
- try {
262
- res = await env.exec(toRun, {
263
- cwd: effectiveCwd,
264
- timeout,
265
- abortSignal: signal,
266
- ...execEnv,
267
- ...(detach
268
- ? {
269
- detachSignal: detach.signal,
270
- onDetachAdopted: () => {
271
- deregisterKill?.();
272
- deregisterKill = undefined;
273
- },
274
- autoBackgroundOnTimeout: detach.autoBackgroundEligible === true && !clampedByDeadline,
275
- }
276
- : {}),
277
- ...(registerKillHandle !== undefined && toolCallId !== undefined
278
- ? {
279
- onSpawn: (handle) => {
280
- deregisterKill = registerKillHandle(toolCallId, handle);
281
- },
282
- }
283
- : {}),
284
- });
285
- }
286
- finally {
287
- deregisterKill?.();
288
- }
243
+ const res = await env.exec(toRun, {
244
+ cwd: effectiveCwd,
245
+ timeout,
246
+ abortSignal: signal,
247
+ ...execEnv,
248
+ ...(detach
249
+ ? {
250
+ detachSignal: detach.signal,
251
+ autoBackgroundOnTimeout: detach.autoBackgroundEligible === true,
252
+ }
253
+ : {}),
254
+ });
289
255
  if (!res.ok) {
290
256
  if (res.error.code === "spawn_error" || res.error.code === "shell_unavailable") {
291
257
  const diagnosis = await diagnoseDeadCwd(env, toolName, effectiveCwd, cwd, cwdRef);
@@ -321,13 +287,9 @@ async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, c
321
287
  const pStdout = clipShellOutput(rawStdout);
322
288
  const pStderr = clipShellOutput(rawStderr);
323
289
  const headline = res.error.code === "timeout"
324
- ? clampedByDeadline
325
- ? deadlineExhausted
326
- ? `Command was cut off after ${timeout}s NOT its requested ${requestedSec}s timeout: the task's wall-clock deadline has already been reached, so the command only got a ${timeout}s grace window. The process was killed; output produced before the cutoff is shown below. Do NOT retry this foreground command there is no time left. Use run_in_background (exempt from this clamp) if the work must continue, or write out your results now.`
327
- : `Command was cut off after ${timeout}s — NOT its requested ${requestedSec}s timeout: the task is near its wall-clock deadline, so the foreground time budget was clamped to the ${timeout}s remaining. The process was killed; output produced before the cutoff is shown below. Do NOT retry this foreground command — the next attempt gets even less time. Use run_in_background (exempt from this clamp) if the work must continue, or write out your results now.`
328
- : cappedByMaxTimeout
329
- ? `Command timed out after ${timeout}s — requested ${requestedSec}s, capped at ${caps.maxSec}s (engine ceiling: requests above ${caps.maxSec}s are reduced to it). The process was killed; output produced before the cutoff is shown below. Re-running with a larger timeout gets the same ${caps.maxSec}s ceiling — use run_in_background for work that needs longer, a narrower command, or resume from the partial progress below.`
330
- : `Command timed out after ${timeout}s (timeout limit: ${timeout}s). The process was killed; output produced before the cutoff is shown below. Re-running the same command will likely time out again — consider run_in_background for long commands, a narrower command, or resuming from the partial progress below.`
290
+ ? cappedByMaxTimeout
291
+ ? `Command timed out after ${timeout}s — requested ${requestedSec}s, capped at ${caps.maxSec}s (engine ceiling: requests above ${caps.maxSec}s are reduced to it). The process was killed; output produced before the cutoff is shown below. Re-running with a larger timeout gets the same ${caps.maxSec}s ceiling — use run_in_background for work that needs longer, a narrower command, or resume from the partial progress below.`
292
+ : `Command timed out after ${timeout}s (timeout limit: ${timeout}s). The process was killed; output produced before the cutoff is shown below. Re-running the same command will likely time out again consider run_in_background for long commands, a narrower command, or resuming from the partial progress below.`
331
293
  : res.error.code === "aborted"
332
294
  ? `Command was interrupted (aborted) before completion. Output produced before the interrupt is shown below.`
333
295
  : `Command started but its completion could not be observed — a host-side output-stream callback failed mid-run and the process was terminated (${res.error.message}). Output captured before the cut is shown below; its effects up to that point may have landed. Verify before assuming it needs a full re-run.`;
@@ -350,9 +312,7 @@ async function runShell(env, cwd, toolName, command, timeoutSec, caps, signal, c
350
312
  ? {
351
313
  timedOut: true,
352
314
  timeoutSec: timeout,
353
- ...(clampedByDeadline || cappedByMaxTimeout ? { requestedTimeoutSec: requestedSec } : {}),
354
- ...(clampedByDeadline ? { clampedByDeadline: true } : {}),
355
- ...(cappedByMaxTimeout ? { cappedToMaxTimeoutSec: caps.maxSec } : {}),
315
+ ...(cappedByMaxTimeout ? { requestedTimeoutSec: requestedSec, cappedToMaxTimeoutSec: caps.maxSec } : {}),
356
316
  }
357
317
  : res.error.code === "aborted"
358
318
  ? { aborted: true }
@@ -696,7 +656,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
696
656
  : undefined;
697
657
  let res;
698
658
  try {
699
- res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, cwdRef, detachChain, taskOpts.execClamp, ctx.toolCallId, false, [rootCanonical, ...(taskOpts.additionalRoots ?? [])]);
659
+ res = await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, cwdRef, detachChain, false, [rootCanonical, ...(taskOpts.additionalRoots ?? [])]);
700
660
  }
701
661
  finally {
702
662
  taskOpts.detachHub?.gc(ctx.toolCallId);
@@ -941,7 +901,7 @@ function createCanonicalReadBoundary(env, readRoots) {
941
901
  },
942
902
  };
943
903
  }
944
- export function createBashReadonlyTool(env, rootCanonical, allow, execClamp, opts) {
904
+ export function createBashReadonlyTool(env, rootCanonical, allow, opts) {
945
905
  const timeoutCaps = resolveBashTimeoutCaps(opts);
946
906
  const timeoutCapsSecView = bashTimeoutCapsSec(timeoutCaps);
947
907
  const sample = [...allow].slice(0, 6).join(", ");
@@ -996,7 +956,7 @@ export function createBashReadonlyTool(env, rootCanonical, allow, execClamp, opt
996
956
  return errorResult(`Error (Bash): a path this command reads resolves through a symlink to ${quoted}, outside the allowed directories for this session: ${readRoots.join(", ")} — not auto-allowed. ` +
997
957
  "bash_readonly is confined to the workspace roots; it has no approval path, so the call is refused rather than escalated.", { code: "readonly_out_of_root", paths: resolved.escaping });
998
958
  }
999
- return await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, undefined, undefined, execClamp, ctx.toolCallId, true);
959
+ return await runShell(env, rootCanonical, "Bash", command, msTimeoutToRequestedSec(timeout), timeoutCapsSecView, ctx.signal, undefined, undefined, true);
1000
960
  },
1001
961
  });
1002
962
  }
@@ -11,7 +11,6 @@ export * from "./fs-search-tools.js";
11
11
  export * from "./bash-readonly-classifier.js";
12
12
  export * from "./fs-bash.js";
13
13
  import { type CwdRef, type ReadImageDownsamplerOption } from "./fs-shared.js";
14
- import { type ExecClampOption } from "./fs-bash.js";
15
14
  export interface HandsToolkitOptions {
16
15
  additionalRoots?: readonly string[];
17
16
  includeShell?: boolean;
@@ -28,7 +27,6 @@ export interface HandsToolkitOptions {
28
27
  priority?: "now" | "next" | "later";
29
28
  }) => void;
30
29
  detachHub?: import("../../core/tool-detach.js").ToolDetachHub;
31
- execClamp?: ExecClampOption;
32
30
  bashDefaultTimeoutMs?: number;
33
31
  bashMaxTimeoutMs?: number;
34
32
  oneShot?: boolean;
@@ -35,7 +35,7 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
35
35
  tools.push(createGrepTool(env, rootCanonical, additionalRoots), createGlobTool(env, rootCanonical, additionalRoots), createRepoMapTool(env, rootCanonical, additionalRoots));
36
36
  if (includeShell) {
37
37
  tools.push(readOnly
38
- ? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), opts.execClamp, {
38
+ ? createBashReadonlyTool(env, rootCanonical, new Set(bashReadonlyAllow ?? BASH_READONLY_DEFAULT_ALLOW), {
39
39
  ...(opts.bashDefaultTimeoutMs !== undefined ? { bashDefaultTimeoutMs: opts.bashDefaultTimeoutMs } : {}),
40
40
  ...(opts.bashMaxTimeoutMs !== undefined ? { bashMaxTimeoutMs: opts.bashMaxTimeoutMs } : {}),
41
41
  ...(additionalRoots !== undefined ? { additionalRoots } : {}),
@@ -47,7 +47,6 @@ export function createHandsToolkit(env, readFileState, rootCanonical, opts = {})
47
47
  ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),
48
48
  onTaskNotification: opts.taskNotification,
49
49
  detachHub: opts.detachHub,
50
- execClamp: opts.execClamp,
51
50
  ...(opts.autoBackgroundOnTimeout !== undefined ? { autoBackgroundOnTimeout: opts.autoBackgroundOnTimeout } : {}),
52
51
  ...(opts.oneShot !== undefined ? { oneShot: opts.oneShot } : {}),
53
52
  ...(additionalRoots !== undefined ? { additionalRoots } : {}),
package/dist/tools/web.js CHANGED
@@ -3,11 +3,28 @@ import { defineTool, errorResult } from "../core/tools.js";
3
3
  import { delimitUntrusted, inlineUntrusted } from "../core/untrusted-text.js";
4
4
  import { redactSecrets } from "../core/untrusted-egress.js";
5
5
  import { isPrivateHost } from "../core/runner/image.js";
6
+ import { binaryMagicFormat } from "./fs/safety.js";
7
+ import { pdfMagicMatches } from "./fs/pdf.js";
6
8
  const MAX_REDIRECTS = 10;
7
9
  const MAX_URL_LENGTH = 2048;
8
10
  const WEBFETCH_ACCEPT = "text/markdown, text/html, */*";
9
11
  const DEFAULT_WEBFETCH_USER_AGENT = "Sema-User (@sema-agent/core WebFetch)";
10
12
  const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;
13
+ function resolveWebMaxBytes(value) {
14
+ if (value === undefined)
15
+ return DEFAULT_MAX_BYTES;
16
+ if (value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) {
17
+ const e = new Error(`WebFetch maxBytes cannot be ${value === Number.POSITIVE_INFINITY ? "Infinity" : "-Infinity"} — the byte cap cannot be turned off, only widened; pass a large finite value instead (got ${String(value)})`);
18
+ e.code = "config.web_max_bytes_invalid";
19
+ throw e;
20
+ }
21
+ if (!Number.isFinite(value) || value <= 0) {
22
+ const e = new Error(`WebFetch maxBytes must be a positive finite number of bytes (got ${String(value)})`);
23
+ e.code = "config.web_max_bytes_invalid";
24
+ throw e;
25
+ }
26
+ return value;
27
+ }
11
28
  const DEFAULT_FETCH_TIMEOUT_MS = 30_000;
12
29
  const ERROR_BODY_EXCERPT_CHARS = 2048;
13
30
  const ERROR_BODY_CONVERT_MAX_CHARS = 64 * 1024;
@@ -126,25 +143,9 @@ function isBinaryContentType(contentType) {
126
143
  return true;
127
144
  }
128
145
  function sniffBinarySignature(b) {
129
- const at = (i) => b[i] ?? -1;
130
- const ascii = (str, off = 0) => [...str].every((ch, i) => at(off + i) === ch.charCodeAt(0));
131
- if (ascii("%PDF"))
146
+ if (pdfMagicMatches(b))
132
147
  return "PDF";
133
- if (at(0) === 0x89 && ascii("PNG", 1))
134
- return "PNG";
135
- if (at(0) === 0xff && at(1) === 0xd8 && at(2) === 0xff)
136
- return "JPEG";
137
- if (ascii("GIF8"))
138
- return "GIF";
139
- if (at(0) === 0x1f && at(1) === 0x8b)
140
- return "gzip";
141
- if (ascii("PK") && ((at(2) === 3 && at(3) === 4) || (at(2) === 5 && at(3) === 6)))
142
- return "zip";
143
- if (at(0) === 0x7f && ascii("ELF", 1))
144
- return "ELF binary";
145
- if (at(0) === 0x00 && ascii("asm", 1))
146
- return "WebAssembly";
147
- return undefined;
148
+ return binaryMagicFormat(b);
148
149
  }
149
150
  function isPermittedRedirectHop(from, to) {
150
151
  const upgrade = from.protocol === "http:" && to.protocol === "https:";
@@ -209,7 +210,7 @@ async function fetchAllowlisted(doFetch, start, allowHosts, userAgent, signal, o
209
210
  return { ok: false, error: "too many redirects" };
210
211
  }
211
212
  export function webFetchToolSpec(config = {}) {
212
- const maxBytes = config.maxBytes ?? DEFAULT_MAX_BYTES;
213
+ const maxBytes = resolveWebMaxBytes(config.maxBytes);
213
214
  const doFetch = config.fetchImpl ?? globalThis.fetch;
214
215
  const canSummarize = config.summarize !== undefined;
215
216
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.7.0",
3
+ "version": "5.9.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -20,9 +20,6 @@
20
20
  ],
21
21
  "main": "./dist/index.js",
22
22
  "types": "./dist/index.d.ts",
23
- "bin": {
24
- "sema-tb": "dist/bin/sema-tb.js"
25
- },
26
23
  "exports": {
27
24
  ".": {
28
25
  "types": "./dist/index.d.ts",
@@ -64,7 +61,9 @@
64
61
  "gate:message-branching": "node scripts/verify-no-message-branching.mjs",
65
62
  "gate:failloud": "node scripts/verify-failloud.mjs",
66
63
  "gate:domain-lexicon": "node scripts/verify-domain-lexicon.mjs",
67
- "gate:single-mint": "node scripts/verify-single-mint.mjs"
64
+ "gate:single-mint": "node scripts/verify-single-mint.mjs",
65
+ "gate:nuia": "node scripts/verify-nuia-baseline.mjs",
66
+ "gate:field-liveness": "node scripts/verify-field-liveness.mjs"
68
67
  },
69
68
  "dependencies": {
70
69
  "@modelcontextprotocol/sdk": "1.30.0",
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
- import { type Model } from "../index.js";
3
- interface Args {
4
- model: string;
5
- objectiveFile: string | undefined;
6
- jsonl: string | undefined;
7
- }
8
- export type ParsedArgs = {
9
- args: Args;
10
- } | {
11
- help: true;
12
- } | {
13
- usageError: string;
14
- };
15
- export declare function parseArgs(argv: string[], env?: Record<string, string | undefined>): ParsedArgs;
16
- export type InstructionResolution = {
17
- instruction: string;
18
- } | {
19
- error: string;
20
- };
21
- export declare function resolveInstruction(args: Pick<Args, "objectiveFile">, readInput: (source: string | 0) => string): InstructionResolution;
22
- type TbCost = Model["cost"];
23
- export type TbCostResolution = {
24
- cost: TbCost;
25
- source: "tb-cost-env" | "model-family" | "provider-default" | "disabled";
26
- warning?: string;
27
- } | {
28
- error: string;
29
- };
30
- export declare function resolveTbCost(modelId: string, provider: string, baseUrlOverridden: boolean, tbCostEnv: string | undefined): TbCostResolution;
31
- export {};