@sema-agent/core 7.6.3 → 7.8.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 (164) hide show
  1. package/CHANGELOG.md +70 -10
  2. package/dist/agents/agent-transcript-tool.js +2 -2
  3. package/dist/agents/list-agents-tool.js +2 -3
  4. package/dist/agents/observer.js +2 -2
  5. package/dist/agents/peer-admission.d.ts +1 -1
  6. package/dist/agents/send-message-tool.js +2 -1
  7. package/dist/agents/subagent-steps.js +5 -4
  8. package/dist/agents/subagent.js +2 -3
  9. package/dist/brain/anthropic.js +8 -2
  10. package/dist/brain/open-responses.js +5 -3
  11. package/dist/brain/openai.js +31 -8
  12. package/dist/brain/reasoning.d.ts +32 -0
  13. package/dist/brain/reasoning.js +18 -0
  14. package/dist/core/ask-question.js +2 -2
  15. package/dist/core/auto-mode-defaults.d.ts +16 -0
  16. package/dist/core/auto-mode-defaults.js +1 -0
  17. package/dist/core/auto-mode.d.ts +19 -0
  18. package/dist/core/auto-mode.js +74 -56
  19. package/dist/core/checkpoint-execution-record.d.ts +110 -0
  20. package/dist/core/checkpoint-execution-record.js +49 -0
  21. package/dist/core/checkpoint-store.d.ts +114 -2
  22. package/dist/core/checkpoint-store.js +35 -2
  23. package/dist/core/context-edit.d.ts +0 -22
  24. package/dist/core/context-edit.js +2 -11
  25. package/dist/core/engine-notice.d.ts +17 -0
  26. package/dist/core/fs-write-gate-policy.d.ts +7 -1
  27. package/dist/core/fs-write-gate-policy.js +7 -7
  28. package/dist/core/gate-fold.js +2 -2
  29. package/dist/core/gate-lanes.js +4 -3
  30. package/dist/core/governance-codes.d.ts +1 -1
  31. package/dist/core/governance-codes.js +8 -0
  32. package/dist/core/hooks.d.ts +8 -2
  33. package/dist/core/hooks.js +1 -1
  34. package/dist/core/lsp.js +2 -2
  35. package/dist/core/mcp-server-spec.d.ts +17 -0
  36. package/dist/core/mcp.js +3 -1
  37. package/dist/core/memory-engine/tools.js +4 -12
  38. package/dist/core/permission-rules.d.ts +32 -2
  39. package/dist/core/permission-rules.js +43 -37
  40. package/dist/core/present-plan-tool.js +3 -4
  41. package/dist/core/runner/active-skill-scope.js +7 -4
  42. package/dist/core/runner/clock-and-limits.d.ts +117 -0
  43. package/dist/core/runner/clock-and-limits.js +118 -0
  44. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  45. package/dist/core/runner/contracts.d.ts +37 -3
  46. package/dist/core/runner/decide-continuation.d.ts +98 -0
  47. package/dist/core/runner/decide-continuation.js +133 -0
  48. package/dist/core/runner/execution-record.d.ts +26 -0
  49. package/dist/core/runner/execution-record.js +19 -0
  50. package/dist/core/runner/git-leg-delivery.d.ts +28 -0
  51. package/dist/core/runner/git-leg-delivery.js +94 -0
  52. package/dist/core/runner/initial-run-state.d.ts +14 -0
  53. package/dist/core/runner/initial-run-state.js +11 -0
  54. package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
  55. package/dist/core/runner/prepare-caps-and-workflow.d.ts +5 -4
  56. package/dist/core/runner/prepare-caps-and-workflow.js +33 -11
  57. package/dist/core/runner/prepare-defer-classify.d.ts +5 -26
  58. package/dist/core/runner/prepare-defer-classify.js +10 -10
  59. package/dist/core/runner/prepare-delegation-surface.d.ts +5 -4
  60. package/dist/core/runner/prepare-delegation-surface.js +15 -17
  61. package/dist/core/runner/prepare-gate-stations.d.ts +4 -1
  62. package/dist/core/runner/prepare-gate-stations.js +3 -1
  63. package/dist/core/runner/prepare-hands-readface.d.ts +3 -2
  64. package/dist/core/runner/prepare-hands-readface.js +3 -2
  65. package/dist/core/runner/prepare-inherited-gate.d.ts +1 -1
  66. package/dist/core/runner/prepare-inherited-gate.js +1 -0
  67. package/dist/core/runner/prepare-lsp.d.ts +4 -3
  68. package/dist/core/runner/prepare-lsp.js +3 -2
  69. package/dist/core/runner/prepare-memory-engine-session.d.ts +4 -4
  70. package/dist/core/runner/prepare-memory-engine-session.js +4 -2
  71. package/dist/core/runner/prepare-offload-wrappers.js +7 -5
  72. package/dist/core/runner/prepare-park-ask.d.ts +4 -0
  73. package/dist/core/runner/prepare-park-ask.js +4 -1
  74. package/dist/core/runner/prepare-project-context.d.ts +5 -23
  75. package/dist/core/runner/prepare-project-context.js +7 -5
  76. package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
  77. package/dist/core/runner/prepare-protocol-tools.d.ts +5 -1
  78. package/dist/core/runner/prepare-protocol-tools.js +38 -21
  79. package/dist/core/runner/prepare-question-face.d.ts +4 -3
  80. package/dist/core/runner/prepare-question-face.js +5 -3
  81. package/dist/core/runner/prepare-safety-scan.js +42 -2
  82. package/dist/core/runner/prepare-task.js +37 -19
  83. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +5 -2
  84. package/dist/core/runner/prepare-tool-disclosure-mount.js +3 -1
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +3 -8
  87. package/dist/core/runner/run-harness-handlers.d.ts +46 -0
  88. package/dist/core/runner/run-harness-handlers.js +345 -0
  89. package/dist/core/runner/run-turn-boundary.d.ts +69 -0
  90. package/dist/core/runner/run-turn-boundary.js +694 -0
  91. package/dist/core/runner/runtask.d.ts +0 -68
  92. package/dist/core/runner/runtask.js +66 -1486
  93. package/dist/core/runner/session-rule-policy.js +9 -4
  94. package/dist/core/runner/steer-admission.d.ts +17 -0
  95. package/dist/core/runner/steer-admission.js +17 -0
  96. package/dist/core/runner/synthetic-tools.js +5 -8
  97. package/dist/core/runner/tool-disclosure.d.ts +4 -0
  98. package/dist/core/runner/tool-disclosure.js +9 -5
  99. package/dist/core/runner/tool-end-body.d.ts +71 -0
  100. package/dist/core/runner/tool-end-body.js +75 -0
  101. package/dist/core/runner/tool-face-overlay.d.ts +40 -0
  102. package/dist/core/runner/tool-face-overlay.js +125 -0
  103. package/dist/core/runner/tool-output-projection.d.ts +10 -0
  104. package/dist/core/runner/tool-output-projection.js +33 -10
  105. package/dist/core/sensitive-path-policy.d.ts +3 -0
  106. package/dist/core/sensitive-path-policy.js +6 -5
  107. package/dist/core/shared-memory/tools.js +3 -6
  108. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -0
  109. package/dist/core/store-contracts/checkpoint-store-contract.js +108 -0
  110. package/dist/core/task-event.d.ts +10 -0
  111. package/dist/core/task-registry.js +4 -5
  112. package/dist/core/task-spec.d.ts +8 -0
  113. package/dist/core/tool-catalog-entries.d.ts +48 -0
  114. package/dist/core/tool-catalog-entries.js +174 -0
  115. package/dist/core/tool-conformance.d.ts +22 -0
  116. package/dist/core/tool-conformance.js +37 -0
  117. package/dist/core/tool-face.d.ts +171 -0
  118. package/dist/core/tool-face.js +25 -0
  119. package/dist/core/tool-policy.d.ts +24 -0
  120. package/dist/core/tool-policy.js +4 -2
  121. package/dist/core/tool-registry.d.ts +114 -0
  122. package/dist/core/tool-registry.js +114 -0
  123. package/dist/core/tool-result-budget.d.ts +0 -3
  124. package/dist/core/tool-result-budget.js +4 -3
  125. package/dist/core/tool-result-store.d.ts +0 -18
  126. package/dist/core/tool-result-store.js +2 -18
  127. package/dist/core/tool-roster.d.ts +450 -0
  128. package/dist/core/tool-roster.js +497 -0
  129. package/dist/core/tool-spec.d.ts +54 -3
  130. package/dist/core/tools.js +7 -0
  131. package/dist/core/trace.d.ts +24 -0
  132. package/dist/core/wiring-manifest.d.ts +13 -0
  133. package/dist/core/wiring-manifest.js +2 -1
  134. package/dist/core/write-protect.d.ts +2 -1
  135. package/dist/core/write-protect.js +5 -4
  136. package/dist/engine/compaction/utils.d.ts +6 -0
  137. package/dist/engine/compaction/utils.js +19 -19
  138. package/dist/engine/loop/types.d.ts +6 -0
  139. package/dist/index.d.ts +20 -8
  140. package/dist/index.js +19 -7
  141. package/dist/orchestration/goal.js +2 -1
  142. package/dist/orchestration/run-spec.js +3 -5
  143. package/dist/orchestration/run-workflow-tool.js +2 -2
  144. package/dist/prompt-assembly/event-registry.js +3 -3
  145. package/dist/prompt-assembly/tool-catalog.d.ts +2 -2
  146. package/dist/stores/file/checkpoint-store.d.ts +7 -0
  147. package/dist/stores/file/checkpoint-store.js +20 -1
  148. package/dist/tools/fs/fs-bash.d.ts +1 -1
  149. package/dist/tools/fs/fs-bash.js +7 -11
  150. package/dist/tools/fs/fs-read.js +2 -2
  151. package/dist/tools/fs/fs-search-tools.d.ts +0 -7
  152. package/dist/tools/fs/fs-search-tools.js +3 -16
  153. package/dist/tools/fs/fs-write.js +4 -6
  154. package/dist/tools/fs/repo-map.js +2 -2
  155. package/dist/tools/fs/safety.d.ts +2 -1
  156. package/dist/tools/fs/safety.js +6 -5
  157. package/dist/tools/monitor.js +2 -2
  158. package/dist/tools/scheduler-tools.js +5 -8
  159. package/dist/tools/task-list.js +5 -8
  160. package/dist/tools/todo.js +2 -2
  161. package/dist/tools/web.js +3 -6
  162. package/dist/tools/worktree.js +3 -4
  163. package/package.json +4 -1
  164. package/test/export-surface.snapshot.json +183 -7
@@ -1,5 +1,4 @@
1
1
  import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
2
- import type { ToolEffect } from "../../core/types.js";
3
2
  import type { ReadDenyMatcher } from "./read-deny.js";
4
3
  import type { ReadFace } from "./read-face.js";
5
4
  /**
@@ -13,9 +12,3 @@ import type { ReadFace } from "./read-face.js";
13
12
  export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number, structuredRows?: readonly import("./search.js").GrepRow[]): Record<string, unknown>;
14
13
  export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
15
14
  export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
16
- /** Static side-effect class of every hand tool, by name (design/44 §3). Used by prepare-task to (a) feed
17
- * wake/resume reconciliation and (b) drive the verifier read-only boundary. Every mutating hand tool is
18
- * `write` (RB-264 ⑥W1 folded `Write` back in — see below); `bash` is `write` (a command can do anything);
19
- * `bash_readonly` is `read` (so it survives the verifier boundary — but still goes through the policy
20
- * gate, council #7: effect:read is a redo-safety class, never a policy circumvention). */
21
- export declare const HAND_TOOL_EFFECTS: Readonly<Record<string, ToolEffect>>;
@@ -2,6 +2,7 @@ import { Type } from "typebox";
2
2
  import { defineTool, errorResult } from "../../core/tools.js";
3
3
  import { resolveKey, violationText, violationDetails } from "./safety.js";
4
4
  import { runGrepDetailed, runGlobDetailed, splitAbsoluteGlobPattern, invalidGlobTokens } from "./search.js";
5
+ import { toolFace } from "../../core/tool-catalog-entries.js";
5
6
  export function grepDetailFields(text, mode, offset, structuredRows) {
6
7
  const structuredIdentityComplete = structuredRows !== undefined && structuredRows.every((r) => r.path !== undefined || r.text === "--");
7
8
  const structuredPaths = structuredIdentityComplete && structuredRows !== undefined ? [...new Set(structuredRows.filter((r) => r.path !== undefined).map((r) => r.path))] : undefined;
@@ -74,7 +75,7 @@ export function grepDetailFields(text, mode, offset, structuredRows) {
74
75
  export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, readFace) {
75
76
  return defineTool({
76
77
  name: "Grep",
77
- contract: { contractId: "core.grep@1", implementationRevision: "1" },
78
+ ...toolFace("grep"),
78
79
  description: "Content search built on ripgrep. Prefer this over `grep`/`rg` via Bash — results integrate with the permission UI and file links.\n" +
79
80
  "\n" +
80
81
  '- Full regex syntax (e.g. "log.*Error", "function\\s+\\w+"). Ripgrep, not grep — escape literal braces (`interface\\{\\}`).\n' +
@@ -107,7 +108,6 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
107
108
  })),
108
109
  offset: Type.Optional(Type.Number({ description: "Skip the first N output lines (pagination partner of head_limit)." })),
109
110
  }),
110
- effect: "read",
111
111
  execute: async (args, ctx) => {
112
112
  const retired = args;
113
113
  for (const [name, repair] of [
@@ -175,7 +175,7 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
175
175
  export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, readFace) {
176
176
  return defineTool({
177
177
  name: "Glob",
178
- contract: { contractId: "core.glob@1", implementationRevision: "1" },
178
+ ...toolFace("glob"),
179
179
  description: 'Fast file pattern matching. Supports glob patterns like "**/*.js" or "src/**/*.ts". Returns matching file paths sorted by modification time.\n' +
180
180
  "\n" +
181
181
  "- On environments that do not report modification times, results fall back to alphabetical order\n" +
@@ -194,7 +194,6 @@ export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, re
194
194
  })),
195
195
  max_results: Type.Optional(Type.Number({ description: "Cap results (default 500)." })),
196
196
  }),
197
- effect: "read",
198
197
  execute: async (args, ctx) => {
199
198
  let { pattern, path } = args;
200
199
  const { max_results } = args;
@@ -222,15 +221,3 @@ export function createGlobTool(env, rootCanonical, additionalRoots, readDeny, re
222
221
  },
223
222
  });
224
223
  }
225
- export const HAND_TOOL_EFFECTS = {
226
- Read: "read",
227
- Edit: "write",
228
- Write: "write",
229
- NotebookEdit: "write",
230
- Grep: "read",
231
- Glob: "read",
232
- RepoMap: "read",
233
- Bash: "write",
234
- TaskOutput: "read",
235
- TaskStop: "write",
236
- };
@@ -4,6 +4,7 @@ import { defineTool, errorResult } from "../../core/tools.js";
4
4
  import { sha256, resolveKey, violationText, violationDetails, requireRead, checkStale, checkEditMatch, checkNoChange, fileArgPath, resolveQuoteMatch, adaptNewStringQuotes, resolveEscapeMatch, adaptNewStringEscapes, escapeMatchWasAttempted, ESCAPE_MATCH_MISS_NOTE, deletionOldString, countOccurrences, READ_REFUSED_ESCAPE_HINT, } from "./safety.js";
5
5
  import { decodeTextBytes, encodeTextForFile, normalizeEditText, normalizeFileText } from "./encoding.js";
6
6
  import { MAX_EDIT_BYTES, decodeEditBytes, tooLargeToEditMessage, truncatedUtf16BodyMessage, persistedTextOf, notReadRefusalText, enoentMessage, FILE_STATE_TRAILER, FILE_PATH_PARAMS, ipynbRedirect, countLines, applyRecordedEdit, } from "./fs-shared.js";
7
+ import { toolFace } from "../../core/tool-catalog-entries.js";
7
8
  async function envFinalWrite(env, key, content, signal, opts) {
8
9
  const fireEdited = () => {
9
10
  if (opts?.edited?.hook === undefined)
@@ -88,7 +89,7 @@ async function gateToolWrite(hook, tool, path, key, content) {
88
89
  export function createEditFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
89
90
  return defineTool({
90
91
  name: "Edit",
91
- contract: { contractId: "core.edit@1", implementationRevision: "1" },
92
+ ...toolFace("edit"),
92
93
  description: "Performs exact string replacement in a file.\n" +
93
94
  "\n" +
94
95
  "- You must Read the file in this conversation before editing, or the call will fail.\n" +
@@ -101,7 +102,6 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
101
102
  new_string: Type.Optional(Type.String({ description: "The text to replace it with (must be different from old_string)" })),
102
103
  replace_all: Type.Optional(Type.Boolean({ description: "Replace all occurrences of old_string (default false)" })),
103
104
  }),
104
- effect: "write",
105
105
  validateInput: async (args, ctx) => {
106
106
  const a = args;
107
107
  if (Array.isArray(a.edits) && a.edits.length > 0)
@@ -327,7 +327,7 @@ export function createEditFileTool(env, state, rootCanonical, cwdRef, additional
327
327
  export function createWriteFileTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
328
328
  return defineTool({
329
329
  name: "Write",
330
- contract: { contractId: "core.write@1", implementationRevision: "1" },
330
+ ...toolFace("write"),
331
331
  description: "Writes a file to the local filesystem, overwriting if one exists.\n" +
332
332
  "\n" +
333
333
  "When to use: creating a new file, or fully replacing one you've already Read. Overwriting an existing " +
@@ -337,7 +337,6 @@ export function createWriteFileTool(env, state, rootCanonical, cwdRef, additiona
337
337
  ...FILE_PATH_PARAMS,
338
338
  content: Type.String({ description: "The content to write to the file" }),
339
339
  }),
340
- effect: "write",
341
340
  validateInput: async (args, ctx) => {
342
341
  const path = fileArgPath(args);
343
342
  if (path === undefined)
@@ -427,7 +426,7 @@ const NOTEBOOK_CELL_ID_REQUIRED_MESSAGE = `Error (NotebookEdit): cell_id is requ
427
426
  export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additionalRoots, beforeWrite, trackEdit, onEdited) {
428
427
  return defineTool({
429
428
  name: "NotebookEdit",
430
- contract: { contractId: "core.notebook_edit@1", implementationRevision: "1" },
429
+ ...toolFace("notebook-edit"),
431
430
  description: "Replaces, inserts, or deletes a single cell in a Jupyter notebook (.ipynb file).\n" +
432
431
  "\n" +
433
432
  "Usage:\n" +
@@ -449,7 +448,6 @@ export function createNotebookEditTool(env, state, rootCanonical, cwdRef, additi
449
448
  description: "The type of edit to make (replace, insert, delete). Defaults to replace.",
450
449
  })),
451
450
  }),
452
- effect: "write",
453
451
  validateInput: async (args, ctx) => {
454
452
  const a = args;
455
453
  const mode = a.edit_mode ?? "replace";
@@ -2,6 +2,7 @@ import { Type } from "typebox";
2
2
  import { defineTool, errorResult } from "../../core/tools.js";
3
3
  import { resolveKey, violationText, violationDetails } from "./safety.js";
4
4
  import { buildIgnore, denyWithheldNote, walk, walkIsPartial } from "./search.js";
5
+ import { toolFace } from "../../core/tool-catalog-entries.js";
5
6
  const DEFAULT_MAX_CHARS = 16_000;
6
7
  const DEFAULT_MAX_FILES = 400;
7
8
  const FILE_SCAN_MAX_BYTES = 256 * 1024;
@@ -82,7 +83,7 @@ function rankEntries(a, b) {
82
83
  export function createRepoMapTool(env, rootCanonical, additionalRoots, readDeny, readFace) {
83
84
  return defineTool({
84
85
  name: "RepoMap",
85
- contract: { contractId: "core.repo_map@1", implementationRevision: "1" },
86
+ ...toolFace("repo-map"),
86
87
  description: "Get a compact map of the repository: each source file with its top-level symbols (functions, " +
87
88
  "classes, types). Use this FIRST to orient in an unfamiliar codebase before grepping — it shows " +
88
89
  "what exists and where, so you can jump to the relevant files. Token-budgeted and honest about " +
@@ -92,7 +93,6 @@ export function createRepoMapTool(env, rootCanonical, additionalRoots, readDeny,
92
93
  max_chars: Type.Optional(Type.Number({ description: `Token budget for the rendered map in chars (default ${DEFAULT_MAX_CHARS}; a very small value yields only a truncation note).` })),
93
94
  max_files: Type.Optional(Type.Number({ description: `Max files to scan (default ${DEFAULT_MAX_FILES}).` })),
94
95
  }),
95
- effect: "read",
96
96
  execute: async (args, ctx) => {
97
97
  const a = args;
98
98
  const signal = ctx.signal;
@@ -1,4 +1,5 @@
1
1
  import type { ExecutionEnv } from "../../internal/harness-types.js";
2
+ import { protectivePathTargetOf, type ToolCallLike } from "../../core/tool-registry.js";
2
3
  /**
3
4
  * batch-B (CC 2.1.187 parity): Read/Edit/Write take the file path as `file_path`. Read the target the same way
4
5
  * EVERYWHERE the path is consumed from a tool-call's args — not just the tool body but ALSO every gate that
@@ -28,7 +29,7 @@ export declare function fileArgPath(args: unknown): string | undefined;
28
29
  * this module stays dependency-free of the alias table — "NotebookEdit" is the canonical CC name and
29
30
  * old→new normalization is identity for it).
30
31
  */
31
- export declare function writeTargetPath(canonicalName: string, args: unknown): string | undefined;
32
+ export declare function writeTargetPath(call: ToolCallLike, pt?: ReturnType<typeof protectivePathTargetOf>): string | undefined;
32
33
  /**
33
34
  * The "hand" file tools (design/44) — safety invariants ported from CC's `FileEditTool` (design/43 Rule
34
35
  * 7): without these a weak model blind-edits / overwrites files (P0 safety, not polish). This module is
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { sliceHeadSafe } from "../../core/surrogate-safe-slice.js";
3
+ import { pathTargetValue, protectivePathTargetOf } from "../../core/tool-registry.js";
3
4
  export function fileArgPath(args) {
4
5
  const a = args;
5
6
  const fp = a?.file_path;
@@ -8,12 +9,12 @@ export function fileArgPath(args) {
8
9
  const p = a?.path;
9
10
  return typeof p === "string" && p.length > 0 ? p : undefined;
10
11
  }
11
- export function writeTargetPath(canonicalName, args) {
12
- const np = args?.notebook_path;
12
+ export function writeTargetPath(call, pt = protectivePathTargetOf(call)) {
13
+ if (pt !== undefined)
14
+ return pathTargetValue(call.args, pt);
15
+ const np = call.args?.notebook_path;
13
16
  const notebook = typeof np === "string" && np.length > 0 ? np : undefined;
14
- if (canonicalName === "NotebookEdit")
15
- return notebook ?? fileArgPath(args);
16
- return fileArgPath(args) ?? notebook;
17
+ return fileArgPath(call.args) ?? notebook;
17
18
  }
18
19
  export function sha256(content) {
19
20
  return createHash("sha256").update(content, "utf8").digest("hex");
@@ -2,6 +2,7 @@ import { Type } from "typebox";
2
2
  import { defineTool, errorResult } from "../core/tools.js";
3
3
  import { hasBackgroundShell } from "../core/background-shell.js";
4
4
  import { MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, } from "../core/task-registry.js";
5
+ import { toolFace } from "../core/tool-catalog-entries.js";
5
6
  const MONITOR_DESCRIPTION = "Run a shell command in the background and watch its stdout as a stream of events.\n" +
6
7
  "\n" +
7
8
  "- Each line the command writes to stdout becomes a notification event delivered to you between turns; " +
@@ -42,7 +43,7 @@ function envBackgroundWallSec(caps, requestedSec) {
42
43
  export function createMonitorTool(env, opts) {
43
44
  return defineTool({
44
45
  name: "Monitor",
45
- contract: { contractId: "core.monitor@1", implementationRevision: "1" },
46
+ ...toolFace("monitor"),
46
47
  description: MONITOR_DESCRIPTION,
47
48
  parameters: Type.Object({
48
49
  command: Type.String({
@@ -58,7 +59,6 @@ export function createMonitorTool(env, opts) {
58
59
  description: "Set to true for a session-resident monitor with no watch timeout of its own (default false). It keeps watching until the execution environment's background time budget expires, until you stop it with TaskStop, or until the session ends.",
59
60
  })),
60
61
  }),
61
- effect: "write",
62
62
  execute: async (args, ctx) => {
63
63
  const { command, description, timeout_ms, persistent } = args;
64
64
  if (!hasBackgroundShell(env)) {
@@ -1,6 +1,7 @@
1
1
  import { Type } from "typebox";
2
2
  import { defineTool, errorResult } from "../core/tools.js";
3
3
  import { hasScheduler, isValidCronExpr, } from "../core/scheduler.js";
4
+ import { toolFace } from "../core/tool-catalog-entries.js";
4
5
  export const SCHEDULE_WAKEUP_TOOL_NAME = "ScheduleWakeup";
5
6
  export const AUTONOMOUS_LOOP_SENTINEL = "<<autonomous-loop>>";
6
7
  export const AUTONOMOUS_LOOP_DYNAMIC_SENTINEL = "<<autonomous-loop-dynamic>>";
@@ -199,7 +200,7 @@ Every user who asks for "9am" gets \`0 9\`, and every user who asks for "hourly"
199
200
  Only use minute 0 or 30 when the user names that exact time and clearly means it ("at 9:00 sharp", "at half past", coordinating with a meeting). When in doubt, nudge a few minutes early or late — the user will not notice, and the fleet will.`;
200
201
  const cronCreate = defineTool({
201
202
  name: "CronCreate",
202
- contract: { contractId: "core.cron_create@1", implementationRevision: "1" },
203
+ ...toolFace("cron-create"),
203
204
  description: cronCreateDescription,
204
205
  parameters: Type.Object({
205
206
  prompt: Type.String({ description: "Self-contained instructions for the future task (it runs unattended)." }),
@@ -222,7 +223,6 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
222
223
  })),
223
224
  label: Type.Optional(Type.String({ description: "Short label (also a dedup key with the schedule)." })),
224
225
  }),
225
- effect: "write",
226
226
  execute: async (args) => serializedCronCreateOp(async () => {
227
227
  const a = args;
228
228
  if (a.schedule !== undefined && a.cron !== undefined) {
@@ -305,10 +305,9 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
305
305
  };
306
306
  const cronCancel = defineTool({
307
307
  name: "CronDelete",
308
- contract: { contractId: "core.cron_delete@1", implementationRevision: "1" },
308
+ ...toolFace("cron-delete"),
309
309
  description: "Cancel a cron job previously scheduled with CronCreate. Idempotent; ids also appear in CronList.",
310
310
  parameters: Type.Object({ id: Type.String({ description: "Job ID returned by CronCreate." }) }),
311
- effect: "idempotent",
312
311
  execute: async (args) => {
313
312
  const { id } = args;
314
313
  const taskId = id;
@@ -329,10 +328,9 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
329
328
  });
330
329
  const cronList = defineTool({
331
330
  name: "CronList",
332
- contract: { contractId: "core.cron_list@1", implementationRevision: "1" },
331
+ ...toolFace("cron-list"),
333
332
  description: "List all cron jobs scheduled via CronCreate (id, when, label).",
334
333
  parameters: Type.Object({}),
335
- effect: "read",
336
334
  execute: async () => {
337
335
  const rows = [];
338
336
  const seen = new Set();
@@ -409,7 +407,7 @@ MOUNT NOTE: this host has no resident wakeup leg, so ${SCHEDULE_WAKEUP_TOOL_NAME
409
407
  MOUNT NOTE: this host does not vouch for session-scoped scheduling, so a wakeup scheduled here is PERSISTENT — contrary to the paragraph above, it can outlive this session instead of ending with it. End the loop explicitly with \`stop: true\` when the work is done; do not rely on the session ending to cancel it.`;
410
408
  const scheduleWakeup = defineTool({
411
409
  name: SCHEDULE_WAKEUP_TOOL_NAME,
412
- contract: { contractId: "core.schedule_wakeup@1", implementationRevision: "1" },
410
+ ...toolFace("schedule-wakeup"),
413
411
  description: `${SCHEDULE_WAKEUP_PROMPT}${wakeupMountNote}`,
414
412
  parameters: Type.Object({
415
413
  delaySeconds: Type.Optional(Type.Number({
@@ -428,7 +426,6 @@ MOUNT NOTE: this host does not vouch for session-scoped scheduling, so a wakeup
428
426
  description: "true = nothing changed (you checked and there is nothing to report). false = something happened worth keeping (edited a file, posted a message, advanced state, surfaced a finding). Consecutive noop:true ticks are collapsed in the user's terminal view and tracked as a streak. Required unless `stop` is true.",
429
427
  })),
430
428
  }),
431
- effect: "write",
432
429
  execute: async (args) => serializedWakeupOp(async () => {
433
430
  const a = args;
434
431
  if (a.stop === true) {
@@ -1,4 +1,5 @@
1
1
  import { Type } from "typebox";
2
+ import { toolFace } from "../core/tool-catalog-entries.js";
2
3
  const STATUS = Type.Union([
3
4
  Type.Literal("pending"),
4
5
  Type.Literal("in_progress"),
@@ -96,7 +97,7 @@ export function createTaskListTools(store) {
96
97
  const tasks = store ?? createMemoryTaskListStore();
97
98
  const taskCreate = {
98
99
  name: "TaskCreate",
99
- contract: { contractId: "core.task_create@1", implementationRevision: "1" },
100
+ ...toolFace("task-create"),
100
101
  description: "Use this tool to create a structured task list for your current session. This helps you track progress, organize complex tasks, and demonstrate thoroughness.\n" +
101
102
  "It also helps the user understand the progress of the task and overall progress of their requests.\n" +
102
103
  "\n" +
@@ -144,7 +145,6 @@ export function createTaskListTools(store) {
144
145
  activeForm: Type.Optional(Type.String({ description: 'Present continuous form shown when in_progress (e.g., "Running tests")' })),
145
146
  metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Arbitrary metadata to attach to the task" })),
146
147
  }),
147
- effect: "write",
148
148
  execute: (args) => serialized(tasks, async (tx) => {
149
149
  const a = args;
150
150
  if (a.metadata)
@@ -169,7 +169,7 @@ export function createTaskListTools(store) {
169
169
  };
170
170
  const taskGet = {
171
171
  name: "TaskGet",
172
- contract: { contractId: "core.task_get@1", implementationRevision: "1" },
172
+ ...toolFace("task-get"),
173
173
  description: "Use this tool to retrieve a task by its ID from the task list.\n" +
174
174
  "\n" +
175
175
  "## When to Use This Tool\n" +
@@ -189,7 +189,6 @@ export function createTaskListTools(store) {
189
189
  parameters: Type.Object({
190
190
  taskId: Type.String({ description: "The ID of the task to retrieve" }),
191
191
  }),
192
- effect: "read",
193
192
  execute: (args) => serialized(tasks, async (tx) => {
194
193
  const id = String(args.taskId);
195
194
  const t = await tx.get(id);
@@ -201,7 +200,7 @@ export function createTaskListTools(store) {
201
200
  };
202
201
  const taskUpdate = {
203
202
  name: "TaskUpdate",
204
- contract: { contractId: "core.task_update@1", implementationRevision: "1" },
203
+ ...toolFace("task-update"),
205
204
  description: "Use this tool to update a task in the task list.\n" +
206
205
  "\n" +
207
206
  "## When to Use This Tool\n" +
@@ -285,7 +284,6 @@ export function createTaskListTools(store) {
285
284
  addBlocks: Type.Optional(Type.Array(Type.String(), { description: "Task IDs that this task blocks" })),
286
285
  addBlockedBy: Type.Optional(Type.Array(Type.String(), { description: "Task IDs that block this task" })),
287
286
  }),
288
- effect: "idempotent",
289
287
  execute: (args) => serialized(tasks, async (tx) => {
290
288
  const a = args;
291
289
  const id = String(a.taskId);
@@ -395,7 +393,7 @@ export function createTaskListTools(store) {
395
393
  };
396
394
  const taskList = {
397
395
  name: "TaskList",
398
- contract: { contractId: "core.task_list@1", implementationRevision: "1" },
396
+ ...toolFace("task-list"),
399
397
  description: "Use this tool to list all tasks in the task list.\n" +
400
398
  "\n" +
401
399
  "## When to Use This Tool\n" +
@@ -420,7 +418,6 @@ export function createTaskListTools(store) {
420
418
  "\n" +
421
419
  "Use TaskGet with a specific task ID to view full details including description and metadata.",
422
420
  parameters: Type.Object({}),
423
- effect: "read",
424
421
  execute: () => serialized(tasks, async (tx) => {
425
422
  const all = await tx.list();
426
423
  if (all.length === 0) {
@@ -1,10 +1,11 @@
1
1
  import { Type } from "typebox";
2
2
  import { errorResult } from "../core/tools.js";
3
+ import { toolFace } from "../core/tool-catalog-entries.js";
3
4
  export function createTodoWriteTool() {
4
5
  let todos = [];
5
6
  return {
6
7
  name: "TodoWrite",
7
- contract: { contractId: "core.todo_write@1", implementationRevision: "1" },
8
+ ...toolFace("todo-write"),
8
9
  description: "Create and update a task list for the current session. The list is rendered to the user as your working plan.\n" +
9
10
  "\n" +
10
11
  '- Each todo has `content`, `status` ("pending" | "in_progress" | "completed"), and `activeForm` (present-tense label shown while in progress).\n' +
@@ -80,7 +81,6 @@ export function createTodoWriteTool() {
80
81
  activeForm: Type.String({ minLength: 1, description: "Present-continuous form shown while in progress (e.g. \"Running the tests\")." }),
81
82
  })),
82
83
  }),
83
- effect: "idempotent",
84
84
  execute: (args) => {
85
85
  const shape = (t) => ({ content: t.content, status: t.status, activeForm: t.activeForm });
86
86
  const oldTodos = todos.map(shape);
package/dist/tools/web.js CHANGED
@@ -7,6 +7,7 @@ import { isPrivateHost } from "../core/runner/image.js";
7
7
  import { DEFAULT_CHARS_PER_TOKEN } from "../internal/harness.js";
8
8
  import { binaryMagicFormat } from "./fs/safety.js";
9
9
  import { pdfMagicMatches } from "./fs/pdf.js";
10
+ import { toolFace } from "../core/tool-catalog-entries.js";
10
11
  const MAX_REDIRECTS = 10;
11
12
  const MAX_URL_LENGTH = 2048;
12
13
  const WEBFETCH_ACCEPT = "text/markdown, text/html, */*";
@@ -262,7 +263,7 @@ export function webFetchToolSpec(config = {}) {
262
263
  const canSummarize = config.summarize !== undefined;
263
264
  return {
264
265
  name: "WebFetch",
265
- contract: { contractId: "core.web_fetch@1", implementationRevision: "1" },
266
+ ...toolFace("web-fetch"),
266
267
  description: (canSummarize
267
268
  ? "Fetches a URL, converts the page to markdown, and answers `prompt` against it with a sub-model.\n"
268
269
  : "Fetches a URL, converts the page to markdown, and returns the markdown. This deployment has no " +
@@ -278,8 +279,6 @@ export function webFetchToolSpec(config = {}) {
278
279
  ? { prompt: Type.Optional(Type.String({ description: "The prompt to run on the fetched content (answered by the configured sub-model)." })) }
279
280
  : {}),
280
281
  }),
281
- effect: "read",
282
- contentOrigin: "external",
283
282
  execute: async (args, ctx) => {
284
283
  const { url, prompt } = args;
285
284
  const startedAt = Date.now();
@@ -844,7 +843,7 @@ export function createWebSearchTool(config) {
844
843
  const currentMonthYear = new Date().toLocaleString("en-US", { month: "long", year: "numeric" });
845
844
  return {
846
845
  name: "WebSearch",
847
- contract: { contractId: "core.web_search@1", implementationRevision: "1" },
846
+ ...toolFace("web-search"),
848
847
  description: "Search the web. Returns result blocks with titles and URLs.\n" +
849
848
  "\n" +
850
849
  `- The current month is ${currentMonthYear} — use this when searching for recent information.\n` +
@@ -857,8 +856,6 @@ export function createWebSearchTool(config) {
857
856
  allowed_domains: Type.Optional(Type.Array(Type.String(), { description: "Only include search results from these domains" })),
858
857
  blocked_domains: Type.Optional(Type.Array(Type.String(), { description: "Never include search results from these domains" })),
859
858
  }),
860
- effect: "read",
861
- contentOrigin: "external",
862
859
  execute: async (args, ctx) => {
863
860
  const { query, allowed_domains, blocked_domains } = args;
864
861
  const startedAt = Date.now();
@@ -2,6 +2,7 @@ import { resolve } from "node:path";
2
2
  import { Type } from "typebox";
3
3
  import { defineTool, errorResult } from "../core/tools.js";
4
4
  import { addWorktree, pruneWorktrees, WORKTREE_PARENT } from "../core/git-worktree-env.js";
5
+ import { toolFace } from "../core/tool-catalog-entries.js";
5
6
  const ENTER_DESCRIPTION = "Use this tool ONLY when explicitly instructed to work in a worktree — either by the user directly, or by " +
6
7
  "project instructions. This tool creates an isolated git worktree and switches the current session into it.\n" +
7
8
  "\n" +
@@ -108,7 +109,7 @@ export function createWorktreeTools(env, opts) {
108
109
  const session = opts.session ?? {};
109
110
  const enter = defineTool({
110
111
  name: "EnterWorktree",
111
- contract: { contractId: "core.enter_worktree@1", implementationRevision: "1" },
112
+ ...toolFace("enter-worktree"),
112
113
  description: ENTER_DESCRIPTION,
113
114
  parameters: Type.Object({
114
115
  name: Type.Optional(Type.String({
@@ -121,7 +122,6 @@ export function createWorktreeTools(env, opts) {
121
122
  description: '"fresh" (default) bases the worktree on the default branch tip (origin default branch, else local main/master, else the current HEAD with a note); "head" bases it on the current local HEAD (unpushed commits present).',
122
123
  })),
123
124
  }),
124
- effect: "write",
125
125
  execute: async (args) => {
126
126
  const { name, path, baseRef } = args;
127
127
  if (path !== undefined && name !== undefined) {
@@ -238,7 +238,7 @@ export function createWorktreeTools(env, opts) {
238
238
  });
239
239
  const exit = defineTool({
240
240
  name: "ExitWorktree",
241
- contract: { contractId: "core.exit_worktree@1", implementationRevision: "1" },
241
+ ...toolFace("exit-worktree"),
242
242
  description: EXIT_DESCRIPTION,
243
243
  parameters: Type.Object({
244
244
  keep: Type.Optional(Type.Boolean({
@@ -254,7 +254,6 @@ export function createWorktreeTools(env, opts) {
254
254
  description: 'Set true to remove the worktree even when it has uncommitted files or unmerged commits — they are permanently discarded. Without it a changed worktree is KEPT (the exit still completes and the reply lists the changes plus this escape), never refused.',
255
255
  })),
256
256
  }),
257
- effect: "write",
258
257
  execute: async (args) => {
259
258
  const raw = args;
260
259
  const keep = raw.keep ?? (raw.action === "keep" ? true : undefined);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.6.3",
3
+ "version": "7.8.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -79,6 +79,9 @@
79
79
  "gate:closed-sets": "node scripts/verify-closed-sets.mjs",
80
80
  "gate:agent-returns": "node scripts/verify-agent-returns.mjs",
81
81
  "gate:contract-sentences": "node scripts/verify-contract-sentences.mjs",
82
+ "gate:tool-catalog": "node scripts/verify-tool-catalog.mjs",
83
+ "gate:tool-names": "node scripts/verify-tool-name-tables.mjs",
84
+ "gate:tool-docs": "node scripts/gen-tool-spec-fields.mjs --check",
82
85
  "gate:all": "node scripts/gate-all.mjs"
83
86
  },
84
87
  "dependencies": {