@sema-agent/core 7.35.2 → 7.37.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 (85) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/dist/brain/anthropic.js +6 -6
  3. package/dist/brain/errors.d.ts +1 -14
  4. package/dist/brain/errors.js +2 -6
  5. package/dist/brain/open-responses.js +7 -7
  6. package/dist/brain/openai.js +7 -7
  7. package/dist/brain/provider-echo-shape.d.ts +26 -0
  8. package/dist/brain/provider-echo-shape.js +12 -0
  9. package/dist/brain/tool-call-id.d.ts +27 -15
  10. package/dist/brain/tool-call-id.js +43 -0
  11. package/dist/core/ask-origin.d.ts +35 -5
  12. package/dist/core/ask-origin.js +23 -4
  13. package/dist/core/ask-question.d.ts +0 -30
  14. package/dist/core/ask-question.js +3 -2
  15. package/dist/core/checkpoint-store.d.ts +18 -6
  16. package/dist/core/checkpoint-store.js +2 -1
  17. package/dist/core/context-edit.d.ts +4 -0
  18. package/dist/core/context-edit.js +5 -2
  19. package/dist/core/decision-receipt.d.ts +21 -0
  20. package/dist/core/decision-receipt.js +63 -0
  21. package/dist/core/engine-notice.d.ts +15 -5
  22. package/dist/core/gate-fold.js +2 -1
  23. package/dist/core/gate-lanes.js +20 -23
  24. package/dist/core/gate-outcome.d.ts +7 -5
  25. package/dist/core/gate-outcome.js +5 -1
  26. package/dist/core/governance-codes.d.ts +1 -1
  27. package/dist/core/governance-codes.js +2 -0
  28. package/dist/core/hooks.d.ts +14 -2
  29. package/dist/core/hooks.js +7 -3
  30. package/dist/core/mode-policy.js +2 -1
  31. package/dist/core/park-selfcheck.js +2 -0
  32. package/dist/core/permission-rule-model.d.ts +2 -2
  33. package/dist/core/permission-rule-model.js +2 -2
  34. package/dist/core/permission-rules.js +2 -5
  35. package/dist/core/runner/active-skill-scope.js +5 -5
  36. package/dist/core/runner/decide-continuation.js +2 -1
  37. package/dist/core/runner/denial-limit-arms.d.ts +28 -18
  38. package/dist/core/runner/denial-limit-arms.js +11 -7
  39. package/dist/core/runner/inherited-ask-grants.d.ts +32 -31
  40. package/dist/core/runner/inherited-ask-grants.js +5 -0
  41. package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
  42. package/dist/core/runner/permission-rule-lanes.js +7 -6
  43. package/dist/core/runner/prepare-context-lane.d.ts +4 -0
  44. package/dist/core/runner/prepare-context-lane.js +7 -5
  45. package/dist/core/runner/prepare-gate-stations.js +17 -12
  46. package/dist/core/runner/prepare-inherited-gate.js +1 -1
  47. package/dist/core/runner/prepare-park-ask.d.ts +4 -2
  48. package/dist/core/runner/prepare-park-ask.js +8 -11
  49. package/dist/core/runner/prepare-policy-chain.d.ts +5 -8
  50. package/dist/core/runner/prepare-policy-chain.js +50 -31
  51. package/dist/core/runner/prepare-safety-scan.js +9 -2
  52. package/dist/core/runner/prepare-task.js +3 -3
  53. package/dist/core/runner/resume-apply.js +2 -1
  54. package/dist/core/runner/run-attachment-seats.js +3 -1
  55. package/dist/core/runner/run-harness-handlers.js +6 -1
  56. package/dist/core/runner/run-leg.js +11 -9
  57. package/dist/core/runner/run-recovery-lanes.js +2 -0
  58. package/dist/core/runner/session-rule-policy.js +5 -4
  59. package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
  60. package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
  61. package/dist/core/sensitive-path-policy.js +2 -2
  62. package/dist/core/side-query.d.ts +5 -1
  63. package/dist/core/side-query.js +5 -1
  64. package/dist/core/store-contracts/checkpoint-store-contract.js +18 -0
  65. package/dist/core/tool-name-readings.d.ts +370 -0
  66. package/dist/core/tool-name-readings.js +137 -0
  67. package/dist/core/tool-policy.d.ts +79 -89
  68. package/dist/core/tool-policy.js +86 -49
  69. package/dist/core/tool-registry.d.ts +1 -31
  70. package/dist/core/tool-registry.js +2 -22
  71. package/dist/core/tool-result-budget.d.ts +3 -0
  72. package/dist/core/tool-result-budget.js +5 -3
  73. package/dist/core/tool-roster.d.ts +30 -9
  74. package/dist/core/tool-roster.js +45 -5
  75. package/dist/engine/llm/types.d.ts +4 -0
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.js +1 -1
  78. package/dist/tools/fs/file-read-check.d.ts +1 -1
  79. package/dist/tools/fs/file-read-check.js +4 -3
  80. package/dist/tools/fs/shell-read-check.d.ts +15 -1
  81. package/dist/tools/fs/shell-read-check.js +5 -2
  82. package/package.json +2 -1
  83. package/test/export-surface.snapshot.json +7 -1
  84. package/wire-changes/core@7.36.0.json +288 -0
  85. package/wire-changes/core@7.37.0.json +405 -0
@@ -257,11 +257,11 @@ export declare function rosterEntryNamed(roster: ToolRoster, toolName: string):
257
257
  * an alias and one written against the wire name are one rule.
258
258
  */
259
259
  export declare function rosterToolName(roster: ToolRoster | undefined, name: string): string;
260
- export { anyNamed, callAnswersTo, firstNamed, ruleNamesOf, toolNameSetHas } from "./tool-registry.js";
260
+ export { ruleNamesOf } from "./tool-name-readings.js";
261
261
  /** design/388 B6 — the per-call face a gated request carries, read off a roster snapshot by name (aliases resolve).
262
262
  * `names` leads with the name the face was minted under (the model's spelling at the gate), then the wire name and the
263
- * other aliases: a reader that must pick ONE of the call's names (the protective path floor) picks the spelling first,
264
- * as the same call was judged before name resolution. */
263
+ * other aliases — the ONE order of a call's names: a reader that must pick one of them by position (a `member` seat)
264
+ * picks the spelling first, as the same call was judged before name resolution. */
265
265
  export declare function toolCallFaceOf(roster: ToolRoster | undefined, toolName: string): import("./tool-policy.js").ToolCallFace | undefined;
266
266
  /** design/388 B18 — the identity members a park row carries, read off a roster snapshot by name; an empty object when
267
267
  * the name is not on the snapshot (the row then reads pre-identity and the resume takes the name-only path). */
@@ -272,16 +272,30 @@ export declare function parkedToolIdentity(roster: ToolRoster | undefined, toolN
272
272
  implementationRevision?: string;
273
273
  rosterDigest?: string;
274
274
  };
275
- /** design/388 B6 × B18 (merge-seam adversarial round H1) — the FACE members a park row carries beside its identity:
276
- * the display family and the declared path target the parked call was judged under. A recheck that runs before
277
- * the leg's roster exists (the pre-CAS resume-edit re-adjudication) rebuilds the call face from the row through
278
- * {@link parkedCallFaceOf}, so it judges the same object the live gate judged. An empty object when the name is
279
- * not on the snapshot (the row then reads pre-face and the recheck falls to the catalog for the name). */
280
- export declare function parkedToolFace(roster: ToolRoster | undefined, toolName: string): {
275
+ /** design/388 B6 × B18 — the FACE members a park row carries beside its identity: the display family, the declared
276
+ * path target and the names the parked call was judged under. `names` is copied from the face the gate stamped on the
277
+ * request (the model's spelling first — the row carries the judged fact, it is not re-derived); a request with no face
278
+ * takes the roster's names for its row. A recheck that runs before the leg's roster exists (the pre-CAS resume-edit
279
+ * re-adjudication) rebuilds the call face from the row through {@link parkedCallFaceOf}, so it judges the same object
280
+ * the live gate judged. An empty object when the name is not on the snapshot (the row then reads pre-face and the
281
+ * recheck falls to the catalog for the name). */
282
+ export declare function parkedToolFace(roster: ToolRoster | undefined, req: {
283
+ readonly toolName: string;
284
+ readonly face?: {
285
+ readonly names?: readonly string[];
286
+ } | undefined;
287
+ }): {
281
288
  family?: ToolFamily;
282
289
  pathTarget?: ToolRosterEntry["pathTarget"];
283
290
  names?: readonly string[];
284
291
  };
292
+ /** The live face a resumed park row is re-judged under: minted under the row's recorded spelling (`names[0]`) when that
293
+ * spelling still resolves to the row's own tool on this roster, else under the row's wire name — a spelling that no
294
+ * longer resolves, or that another row now owns, never lends the row another tool's face. */
295
+ export declare function parkedRowLiveFace(roster: ToolRoster | undefined, row: {
296
+ readonly toolName: string;
297
+ readonly names?: readonly string[];
298
+ }): import("./tool-policy.js").ToolCallFace | undefined;
285
299
  /** The call face a park row carries — present iff the row was minted with BOTH its face members and its four
286
300
  * identity members (one mint writes all of them; a row missing any is pre-388 and yields undefined, never a
287
301
  * partial face). This is the face every recheck of the row judges under, roster or no roster. */
@@ -443,6 +457,13 @@ export declare class RosterBuilder {
443
457
  * yielded instance's position and never a row per object. */
444
458
  snapshot(facts: RosterSnapshotFacts): ToolRoster;
445
459
  }
460
+ /**
461
+ * The roster's one declaration rule on engine names: a row whose names (its wire name and the aliases that resolve to it,
462
+ * {@link namesResolvingTo}) belong to TWO or more engine tools is refused — one tool answering as two engine tools would
463
+ * have every per-call seat keyed on engine names choose between two definitions of the call. A row answering to one engine
464
+ * tool under several of that tool's names, and an alias another row owns, are not counted twice. Undefined ⇒ none.
465
+ */
466
+ export declare function rosterEngineNamesProblem(roster: ToolRoster): string | undefined;
446
467
  /** Which source a `TaskSpec.tools` entry mounts as: a definition carrying a CORE contract is a built-in the host chose
447
468
  * to mount; anything else is the caller's own tool. Identity follows the object, never the name. */
448
469
  export declare function callerMountSource(tool: AgentTool): RosterSource;
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { Type } from "typebox";
3
3
  import { getToolContract } from "../prompt-assembly/tool-catalog.js";
4
4
  import { protocolOf } from "./protocol-table.js";
5
+ import { describeToolCatalog } from "./tool-registry.js";
5
6
  import { RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_CHARS, RENDER_HINT_MAX_LIST, TOOL_APPROVAL_CARDS, TOOL_CARD_ID_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_FAMILIES, TOOL_KEYS_MAX, TOOL_KEY_MAX_CHARS, TOOL_MOUNT_TAGS, TOOL_WIRE_NAME_MAX_CHARS, TOOL_PATH_ABSENCES, TOOL_PATH_BASES, } from "./tool-face.js";
6
7
  function lit(values) {
7
8
  return Type.Union(values.map((v) => Type.Literal(v)));
@@ -80,7 +81,7 @@ export function rosterToolName(roster, name) {
80
81
  function namesResolvingTo(roster, e) {
81
82
  return [e.name, ...e.aliases.filter((a) => a !== e.name && rosterToolName(roster, a) === e.name)];
82
83
  }
83
- export { anyNamed, callAnswersTo, firstNamed, ruleNamesOf, toolNameSetHas } from "./tool-registry.js";
84
+ export { ruleNamesOf } from "./tool-name-readings.js";
84
85
  export function toolCallFaceOf(roster, toolName) {
85
86
  if (roster === undefined)
86
87
  return undefined;
@@ -106,13 +107,19 @@ export function parkedToolIdentity(roster, toolName) {
106
107
  return {};
107
108
  return { capabilityId: e.capabilityId, contractId: e.contract.contractId, shapeDigest: e.shapeDigest, implementationRevision: e.contract.implementationRevision, rosterDigest: roster.digest };
108
109
  }
109
- export function parkedToolFace(roster, toolName) {
110
+ export function parkedToolFace(roster, req) {
110
111
  if (roster === undefined)
111
112
  return {};
112
- const e = rosterEntryNamed(roster, toolName);
113
+ const e = rosterEntryNamed(roster, req.toolName);
113
114
  if (e === undefined)
114
115
  return {};
115
- return { family: e.family, ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}), names: namesResolvingTo(roster, e) };
116
+ const judged = req.face?.names;
117
+ return { family: e.family, ...(e.pathTarget !== undefined ? { pathTarget: e.pathTarget } : {}), names: judged !== undefined && judged.includes(req.toolName) ? [...judged] : namesResolvingTo(roster, e) };
118
+ }
119
+ export function parkedRowLiveFace(roster, row) {
120
+ const spelled = row.names?.[0];
121
+ const under = spelled !== undefined && roster !== undefined && rosterToolName(roster, spelled) === row.toolName ? spelled : row.toolName;
122
+ return toolCallFaceOf(roster, under);
116
123
  }
117
124
  export function parkedCallFaceOf(pending) {
118
125
  if (pending.family === undefined || pending.capabilityId === undefined || pending.contractId === undefined || pending.shapeDigest === undefined || pending.implementationRevision === undefined)
@@ -369,8 +376,41 @@ export class RosterBuilder {
369
376
  const meta = this.#meta.get(survivor) ?? { source: "caller", mountedBy: "scenario" };
370
377
  entries.push(mintRosterEntry(survivor, meta, facts));
371
378
  }
372
- return { schemaVersion: 1, count: entries.length, entries, digest: rosterDigestOf(entries) };
379
+ const roster = { schemaVersion: 1, count: entries.length, entries, digest: rosterDigestOf(entries) };
380
+ const worn = rosterEngineNamesProblem(roster);
381
+ if (worn !== undefined)
382
+ throw configError("config.tool_face_invalid", worn);
383
+ return roster;
384
+ }
385
+ }
386
+ let catalogOwners;
387
+ function catalogOwnerOf(name) {
388
+ if (catalogOwners === undefined) {
389
+ const owners = new Map();
390
+ for (const e of describeToolCatalog()) {
391
+ owners.set(e.name, e.name);
392
+ for (const a of e.face.aliases ?? [])
393
+ if (!owners.has(a))
394
+ owners.set(a, e.name);
395
+ }
396
+ catalogOwners = owners;
373
397
  }
398
+ return catalogOwners.get(name);
399
+ }
400
+ export function rosterEngineNamesProblem(roster) {
401
+ for (const e of roster.entries) {
402
+ const resolving = namesResolvingTo(roster, e);
403
+ const worn = new Map();
404
+ for (const n of resolving) {
405
+ const owner = catalogOwnerOf(n);
406
+ if (owner !== undefined && !worn.has(owner))
407
+ worn.set(owner, n);
408
+ }
409
+ if (worn.size >= 2) {
410
+ return `tool ${JSON.stringify(e.name)} answers to the names of ${worn.size} engine tools (${[...worn.values()].map((n) => JSON.stringify(n)).join(", ")}): a tool may answer to at most one engine tool's names — keep one of those aliases`;
411
+ }
412
+ }
413
+ return undefined;
374
414
  }
375
415
  export function callerMountSource(tool) {
376
416
  return getToolContract(tool, "caller").origin === "core" ? "builtin" : "caller";
@@ -242,6 +242,10 @@ export interface DocumentContent {
242
242
  export interface ToolCall {
243
243
  type: "toolCall";
244
244
  id: string;
245
+ /** The provider's own spelling of {@link id}, present only when the brain folded it (`brain/tool-call-id.ts`);
246
+ * handed back to the provider for the call and its result, persisted with the message. Upstream bytes: render
247
+ * {@link id}. */
248
+ wireId?: string;
245
249
  name: string;
246
250
  arguments: Record<string, unknown>;
247
251
  executionMode?: "sequential" | "parallel";
package/dist/index.d.ts CHANGED
@@ -192,7 +192,7 @@ export { createAllowDenyPolicy, createApprovalPolicy,
192
192
  type ApprovalSeatOutcome, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, type ToolPolicyNameSets, type NamedToolPolicy, type ToolPolicyProjection, type ToolPolicyProjectionComponent, type ConstraintChainEntry, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, type ToolPolicy, type ToolCallRequest, type PermissionResult, type DecisionReason, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, type OnAsk, type AskOutcome, type ResolvedAsk, type AskRequest, type AskDelegationProvenance, type AskRuleEvidence, type AskEvidenceAbsence, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
193
193
  export { type ReadRootGrantScreen } from "./core/read-root-grant-screen.js";
194
194
  export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, type PersistedRuleMandate, isPersistedRuleMandate } from "./core/ask-origin.js";
195
- export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, RELEASABLE_ASK_CLASSES, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
195
+ export { PERMISSION_MODES, type PermissionMode, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, type AskClass, isAskClass, MODE_ARBITRATION, type ModeArbitrationRow, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, type ModeReleaseReason, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, type AskClassification, type AskClassFacts, type ToolMarkSource, arbitrateByMode, type Arbitration, fileCommandOptionsSentence } from "./core/ask-origin.js";
196
196
  export { modePolicyOf, type ModePolicy, PERMISSION_MODE_FOLD_REASONS, type PermissionModeFoldReason, type PermissionModeReceipt, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
197
197
  export { readDenyClassifierRules, renderClassifierDenyRules, type DerivedDenyRule } from "./core/auto-mode-prompt.js";
198
198
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource, type AutoModeDenySource } from "./core/runtime-caps.js";
package/dist/index.js CHANGED
@@ -157,7 +157,7 @@ export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.
157
157
  export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
158
158
  export {} from "./core/read-root-grant-screen.js";
159
159
  export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate } from "./core/ask-origin.js";
160
- export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, RELEASABLE_ASK_CLASSES, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
160
+ export { PERMISSION_MODES, isPermissionMode, PERMISSION_MODE_RANK, clampPermissionMode, ASK_CLASSES, isAskClass, MODE_ARBITRATION, RELEASABLE_ASK_CLASSES, MODE_RELEASE_REASONS, isModeReleaseReason, RRA_EXPLAINERS, classifyAsk, foldArbitrationRow, arbitrateByMode, fileCommandOptionsSentence } from "./core/ask-origin.js";
161
161
  export { modePolicyOf, PERMISSION_MODE_FOLD_REASONS, ACCEPT_EDITS_FILE_COMMANDS, modeAcceptsFileCommand } from "./core/mode-policy.js";
162
162
  export { readDenyClassifierRules, renderClassifierDenyRules } from "./core/auto-mode-prompt.js";
163
163
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./core/runtime-caps.js";
@@ -15,7 +15,7 @@
15
15
  import type { ExecutionEnv } from "../../internal/harness-types.js";
16
16
  import type { ContainmentFace } from "./containment-face.js";
17
17
  import { type ReadDenyMatcher } from "./read-deny.js";
18
- import type { ShellReadCheck } from "./shell-read-check.js";
18
+ import { type ShellReadCheck } from "./shell-read-check.js";
19
19
  export interface FileReadStation {
20
20
  readonly check: ShellReadCheck;
21
21
  /** The station's membership: the tool names whose declared read target it judges. */
@@ -1,7 +1,8 @@
1
1
  import { effectivePathSpellingOf } from "../../core/effective-path-target.js";
2
- import { firstNamed, pathTargetOf, ruleNamesOf } from "../../core/tool-registry.js";
2
+ import { pathTargetOf } from "../../core/tool-registry.js";
3
3
  import { fitMiddle, READ_DENY_SENTENCE_PART_MAX } from "./read-deny.js";
4
4
  import { resolveKey, violationDetails } from "./safety.js";
5
+ import { readStationMemberOf } from "./shell-read-check.js";
5
6
  import { grantOfferFor, parentDirLexically } from "./shell-read-judge.js";
6
7
  const ADMISSION_MAX = 1024;
7
8
  const VOUCH = { disposition: "vouch", finding: "none" };
@@ -17,7 +18,7 @@ export function createFileReadStation(opts) {
17
18
  };
18
19
  const check = {
19
20
  judge(call, args, cwd, signal, callOpts) {
20
- const member = ruleNamesOf(call).find((n) => opts.tools.has(n));
21
+ const member = readStationMemberOf(call, opts.tools)?.name;
21
22
  if (member === undefined)
22
23
  return null;
23
24
  const toolName = call.toolName;
@@ -71,7 +72,7 @@ export function composeReadChecks(stations) {
71
72
  for (const n of s.tools)
72
73
  if (!byName.has(n))
73
74
  byName.set(n, s);
74
- const stationOf = (call) => firstNamed(byName, ruleNamesOf(call))?.value;
75
+ const stationOf = (call) => readStationMemberOf(call, byName)?.value;
75
76
  return {
76
77
  judge: (call, args, cwd, signal, callOpts) => stationOf(call)?.check.judge(call, args, cwd, signal, callOpts) ?? null,
77
78
  unanswered: (call, cause) => (stationOf(call) ?? stations[0]).check.unanswered(call, cause),
@@ -22,7 +22,7 @@ import { violationDetails } from "./safety.js";
22
22
  import { type ShellReadPlan } from "./shell-read-plan.js";
23
23
  import { type EstablishedRead, type ShellReadBoundary, type ShellReadDisposition, type ShellReadFinding, type ShellReadSeat } from "./shell-read-judge.js";
24
24
  import type { ReadRootGrantCandidate } from "../../core/tool-spec.js";
25
- import { type ToolCallLike } from "../../core/tool-registry.js";
25
+ import type { ToolCallLike } from "../../core/tool-registry.js";
26
26
  /** The ONE mint of the containment card — the targets a finding established OUTSIDE the roots (an out-of-root read, a
27
27
  * target that resolves elsewhere by the spawn). */
28
28
  declare function containmentDetails(paths: readonly string[]): {
@@ -105,6 +105,20 @@ export interface ShellReadCheck {
105
105
  }
106
106
  /** The call a read station judges: its wire name and the face the gate stamped (whose `names` the station reads). */
107
107
  export type ReadStationCall = Pick<ToolCallLike, "toolName" | "face">;
108
+ /** The read station a call belongs to — the first of its names (the spelling first) a station's membership holds; every
109
+ * station reads the call's slot and seat under that one name. */
110
+ export declare function readStationMemberOf<V>(call: ReadStationCall, members: {
111
+ get(name: string): V | undefined;
112
+ }): {
113
+ readonly name: string;
114
+ readonly value: V;
115
+ } | undefined;
116
+ export declare function readStationMemberOf(call: ReadStationCall, members: {
117
+ has(name: string): boolean;
118
+ }): {
119
+ readonly name: string;
120
+ readonly value: true;
121
+ } | undefined;
108
122
  /** The pre-spawn admission the shell tools call: re-establishes what the station established for this
109
123
  * call and refuses when any canonical target moved. `undefined` ⇒ spawn. A call the station never judged
110
124
  * (no gate ran) has nothing to re-establish and spawns. */
@@ -8,7 +8,7 @@ import { planShellReads } from "./shell-read-plan.js";
8
8
  import { createShellReadEstablisher, establishShellReadsLexically } from "./shell-read-establish.js";
9
9
  import { programArgs, spellsOption } from "./shell-read-write-targets.js";
10
10
  import { allFindings, decideShellRead, denyRowAtOrBelow, foldShellReadFindings, judgeEstablishedPlan, readRootGrantCandidateOf, } from "./shell-read-judge.js";
11
- import { ruleNamesOf } from "../../core/tool-registry.js";
11
+ import { seatOf } from "../../core/tool-name-readings.js";
12
12
  import { normalizeReadRootCandidate } from "../../core/read-root-candidate.js";
13
13
  import { argvOf, lexShellText } from "../../shell/lex.js";
14
14
  function containmentDetails(paths) {
@@ -178,6 +178,9 @@ async function writeGuardsOf(judged, boundary, placed, env, signal) {
178
178
  }
179
179
  return { unplaced, writeProtected };
180
180
  }
181
+ export function readStationMemberOf(call, members) {
182
+ return seatOf("fold.readStation", members, call);
183
+ }
181
184
  const ADMISSION_MAX = 1024;
182
185
  function shellCommandOf(args) {
183
186
  const command = args?.command;
@@ -360,7 +363,7 @@ export function createShellReadStation(opts) {
360
363
  }
361
364
  admissions.set(toolCallId, a);
362
365
  };
363
- const memberOf = (call) => ruleNamesOf(call).find((n) => opts.tools.has(n));
366
+ const memberOf = (call) => readStationMemberOf(call, opts.tools)?.name;
364
367
  const check = {
365
368
  judge(call, args, cwd, signal, callOpts) {
366
369
  const member = memberOf(call);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.35.2",
3
+ "version": "7.37.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -83,6 +83,7 @@
83
83
  "gate:contract-sentences": "node scripts/verify-contract-sentences.mjs",
84
84
  "gate:tool-catalog": "node scripts/verify-tool-catalog.mjs",
85
85
  "gate:tool-names": "node scripts/verify-tool-name-tables.mjs",
86
+ "gate:name-readings": "node scripts/verify-name-readings.mjs",
86
87
  "gate:tool-docs": "node scripts/gen-tool-spec-fields.mjs --check",
87
88
  "gate:backlog-ledger": "node scripts/verify-backlog-ledger.mjs",
88
89
  "gate:child-clamp-seats": "node scripts/verify-child-clamp-seats.mjs",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
3
3
  "_tierComment": "#435 v1 — machine-readable layering of the public surface: stable = demonstrated by README.md / src/examples; internal = an `Internal`-marked name or a runner/engine deep-subtree declaration (the model seam src/engine/llm is excluded — it is the BYOM contract, not an engine internal); advanced = a supported export the front door does not walk you through. THIS IS AN INITIAL HEURISTIC, derived mechanically and expected to be refined ticket by ticket: no human reviewed these 1700+ entries one by one, and nothing here claims otherwise. Known bias: a short or English-word export name (ok, err, Result, Usage) can match ordinary prose in README.md and land `stable` on a coincidence. Every export MUST carry a tier — a new export with no row fails the gate in export-surface.test.ts.",
4
- "count": 2537,
4
+ "count": 2540,
5
5
  "exports": {
6
6
  "A2ATaskState": "type",
7
7
  "A2ATaskStateReversal": "type",
@@ -713,6 +713,7 @@
713
713
  "MEMORY_SEARCH_TOOL_NAME": "variable",
714
714
  "MODEL_ROLES": "variable",
715
715
  "MODE_ARBITRATION": "variable",
716
+ "MODE_RELEASE_REASONS": "variable",
716
717
  "MONITOR_BATCH_WINDOW_MS": "variable",
717
718
  "MONITOR_DEFAULT_TIMEOUT_MS": "variable",
718
719
  "MONITOR_MAX_BATCHES_PER_MINUTE": "variable",
@@ -822,6 +823,7 @@
822
823
  "ModePolicy": "interface",
823
824
  "ModeRefusal": "interface",
824
825
  "ModeRelease": "interface",
826
+ "ModeReleaseReason": "type",
825
827
  "Model": "interface",
826
828
  "ModelCriteria": "interface",
827
829
  "ModelMention": "interface",
@@ -2094,6 +2096,7 @@
2094
2096
  "isGateKind": "function",
2095
2097
  "isInstructionEntry": "function",
2096
2098
  "isIsolated": "function",
2099
+ "isModeReleaseReason": "function",
2097
2100
  "isModelGatedForClass": "function",
2098
2101
  "isModelPriced": "function",
2099
2102
  "isNamespacedCoveringRuleName": "function",
@@ -3252,6 +3255,7 @@
3252
3255
  "MEMORY_SEARCH_TOOL_NAME": "advanced",
3253
3256
  "MODEL_ROLES": "advanced",
3254
3257
  "MODE_ARBITRATION": "advanced",
3258
+ "MODE_RELEASE_REASONS": "advanced",
3255
3259
  "MONITOR_BATCH_WINDOW_MS": "advanced",
3256
3260
  "MONITOR_DEFAULT_TIMEOUT_MS": "advanced",
3257
3261
  "MONITOR_MAX_BATCHES_PER_MINUTE": "advanced",
@@ -3361,6 +3365,7 @@
3361
3365
  "ModePolicy": "advanced",
3362
3366
  "ModeRefusal": "advanced",
3363
3367
  "ModeRelease": "advanced",
3368
+ "ModeReleaseReason": "advanced",
3364
3369
  "Model": "stable",
3365
3370
  "ModelCriteria": "advanced",
3366
3371
  "ModelMention": "advanced",
@@ -4633,6 +4638,7 @@
4633
4638
  "isGateKind": "advanced",
4634
4639
  "isInstructionEntry": "advanced",
4635
4640
  "isIsolated": "advanced",
4641
+ "isModeReleaseReason": "advanced",
4636
4642
  "isModelGatedForClass": "advanced",
4637
4643
  "isModelPriced": "advanced",
4638
4644
  "isNamespacedCoveringRuleName": "advanced",
@@ -0,0 +1,288 @@
1
+ {
2
+ "repo": "core",
3
+ "version": "7.36.0",
4
+ "from": "df0162002e59d5f355bda182d867378859405254",
5
+ "to": "e033cec051a178169a17a3f55d5fdaac98bedf2d",
6
+ "added": [],
7
+ "removed": [],
8
+ "changed": [],
9
+ "narrowed": [],
10
+ "closedSetMembers": [
11
+ {
12
+ "set": "ENGINE_NOTICE_CODES",
13
+ "at": [
14
+ "src/core/governance-codes.ts:344"
15
+ ],
16
+ "newSet": false,
17
+ "added": [
18
+ "route.tool_call_id_folded"
19
+ ],
20
+ "removed": [],
21
+ "breaking": false,
22
+ "criteriaCovered": true
23
+ },
24
+ {
25
+ "set": "ToolCallIdFoldReason",
26
+ "at": [
27
+ "src/brain/tool-call-id.ts:65"
28
+ ],
29
+ "newSet": true,
30
+ "added": [
31
+ "charset",
32
+ "length"
33
+ ],
34
+ "removed": [],
35
+ "breaking": false,
36
+ "criteriaCovered": false
37
+ }
38
+ ],
39
+ "closedSetsUnreadable": [],
40
+ "typeKeys": [
41
+ {
42
+ "type": "AggregateBudgetOptions",
43
+ "at": [
44
+ "src/core/tool-result-budget.ts:86"
45
+ ],
46
+ "added": [
47
+ "toolNameOf"
48
+ ],
49
+ "addedDetail": [
50
+ {
51
+ "key": "toolNameOf",
52
+ "optional": true
53
+ }
54
+ ],
55
+ "addedRequired": [],
56
+ "removed": [],
57
+ "breaking": false,
58
+ "criteriaCovered": false
59
+ },
60
+ {
61
+ "type": "ContextEditOptions",
62
+ "at": [
63
+ "src/core/context-edit.ts:240"
64
+ ],
65
+ "added": [
66
+ "toolNameOf"
67
+ ],
68
+ "addedDetail": [
69
+ {
70
+ "key": "toolNameOf",
71
+ "optional": true
72
+ }
73
+ ],
74
+ "addedRequired": [],
75
+ "removed": [],
76
+ "breaking": false,
77
+ "criteriaCovered": false
78
+ },
79
+ {
80
+ "type": "ToolCall",
81
+ "at": [
82
+ "src/engine/llm/types.ts:309"
83
+ ],
84
+ "added": [
85
+ "wireId"
86
+ ],
87
+ "addedDetail": [
88
+ {
89
+ "key": "wireId",
90
+ "optional": true
91
+ }
92
+ ],
93
+ "addedRequired": [],
94
+ "removed": [],
95
+ "breaking": false,
96
+ "criteriaCovered": true
97
+ }
98
+ ],
99
+ "typeKeysUnreadableArms": [],
100
+ "typeKeysFlattenedCount": 219,
101
+ "declarationMoves": [
102
+ {
103
+ "name": "PROVIDER_ECHO_SHAPE",
104
+ "kind": "variable",
105
+ "onSurface": true,
106
+ "from": [
107
+ "src/brain/errors.ts",
108
+ "src/index.ts"
109
+ ],
110
+ "to": [
111
+ "src/brain/errors.ts",
112
+ "src/brain/provider-echo-shape.ts",
113
+ "src/index.ts"
114
+ ]
115
+ },
116
+ {
117
+ "name": "isProviderEchoShape",
118
+ "kind": "function",
119
+ "onSurface": true,
120
+ "from": [
121
+ "src/brain/errors.ts",
122
+ "src/index.ts"
123
+ ],
124
+ "to": [
125
+ "src/brain/errors.ts",
126
+ "src/brain/provider-echo-shape.ts",
127
+ "src/index.ts"
128
+ ]
129
+ },
130
+ {
131
+ "name": "ruleNamesOf",
132
+ "kind": "function",
133
+ "onSurface": true,
134
+ "from": [
135
+ "src/core/tool-registry.ts",
136
+ "src/core/tool-roster.ts",
137
+ "src/index.ts"
138
+ ],
139
+ "to": [
140
+ "src/core/tool-name-readings.ts",
141
+ "src/core/tool-roster.ts",
142
+ "src/index.ts"
143
+ ]
144
+ }
145
+ ],
146
+ "mintMoves": [],
147
+ "criteriaObserves": [
148
+ {
149
+ "kind": "engineNoticeCode",
150
+ "name": "route.tool_call_id_folded",
151
+ "assertions": [
152
+ "G8.b"
153
+ ]
154
+ },
155
+ {
156
+ "kind": "taskEventField",
157
+ "name": "message_committed.toolCallId",
158
+ "assertions": [
159
+ "G8.a"
160
+ ]
161
+ },
162
+ {
163
+ "kind": "taskEventField",
164
+ "name": "tool_end.toolCallId",
165
+ "assertions": [
166
+ "G7.a",
167
+ "G8.a",
168
+ "G9.a",
169
+ "G10.a",
170
+ "G10.b"
171
+ ]
172
+ },
173
+ {
174
+ "kind": "taskEventField",
175
+ "name": "tool_end.toolName",
176
+ "assertions": [
177
+ "G4.b"
178
+ ]
179
+ },
180
+ {
181
+ "kind": "taskEventField",
182
+ "name": "tool_start.toolCallId",
183
+ "assertions": [
184
+ "G7.a",
185
+ "G7.b",
186
+ "G8.a",
187
+ "G9.a"
188
+ ]
189
+ },
190
+ {
191
+ "kind": "taskEventField",
192
+ "name": "tool_start.toolName",
193
+ "assertions": [
194
+ "G2.a"
195
+ ]
196
+ },
197
+ {
198
+ "kind": "typeMember",
199
+ "name": "AskRequest.riskAxes",
200
+ "assertions": [
201
+ "G1.a",
202
+ "G1.b"
203
+ ]
204
+ },
205
+ {
206
+ "kind": "typeMember",
207
+ "name": "HookToolContext.toolName",
208
+ "assertions": [
209
+ "G2.a",
210
+ "G2.b"
211
+ ]
212
+ },
213
+ {
214
+ "kind": "typeMember",
215
+ "name": "PostToolBatchCall.toolName",
216
+ "assertions": [
217
+ "G2.a"
218
+ ]
219
+ },
220
+ {
221
+ "kind": "typeMember",
222
+ "name": "SideQueryResult.toolCalls",
223
+ "assertions": [
224
+ "G8.d"
225
+ ]
226
+ },
227
+ {
228
+ "kind": "typeMember",
229
+ "name": "TaskResult.terminal",
230
+ "assertions": [
231
+ "G3.a",
232
+ "G3.b"
233
+ ]
234
+ },
235
+ {
236
+ "kind": "typeMember",
237
+ "name": "ToolCall.id",
238
+ "assertions": [
239
+ "G7.b",
240
+ "G8.a"
241
+ ]
242
+ },
243
+ {
244
+ "kind": "typeMember",
245
+ "name": "ToolCall.wireId",
246
+ "assertions": [
247
+ "G7.a",
248
+ "G8.a",
249
+ "G8.c",
250
+ "G10.a",
251
+ "G10.c"
252
+ ]
253
+ },
254
+ {
255
+ "kind": "typeMember",
256
+ "name": "ToolCallFace.names",
257
+ "assertions": [
258
+ "G4.a",
259
+ "G4.b",
260
+ "G5.a"
261
+ ]
262
+ },
263
+ {
264
+ "kind": "typeMember",
265
+ "name": "ToolSpec.effect",
266
+ "assertions": [
267
+ "G6.a"
268
+ ]
269
+ }
270
+ ],
271
+ "consumersHint": {
272
+ "cli": [
273
+ "ContextEditOptions",
274
+ "ENGINE_NOTICE_CODES",
275
+ "ToolCall"
276
+ ],
277
+ "server": [
278
+ "ContextEditOptions",
279
+ "ENGINE_NOTICE_CODES"
280
+ ],
281
+ "sdk": [
282
+ "ENGINE_NOTICE_CODES"
283
+ ],
284
+ "note": "scanned cli@6b6025f7 server@25d6b7a6 sdk@e8f6f0d — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it. Ledger files (**/docs/core-consumption/**, **/docs/wire-consumption/**, **/*LEDGER*.json) are not evidence of use — server: 2 name(s) matched ONLY in bookkeeping files and were set aside (AggregateBudgetOptions, ToolCall)"
285
+ },
286
+ "breakingCount": 0,
287
+ "breakingItems": []
288
+ }