@sema-agent/core 7.35.2 → 7.36.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 (67) hide show
  1. package/CHANGELOG.md +61 -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-question.d.ts +0 -30
  12. package/dist/core/ask-question.js +3 -2
  13. package/dist/core/checkpoint-store.d.ts +5 -3
  14. package/dist/core/checkpoint-store.js +2 -1
  15. package/dist/core/context-edit.d.ts +4 -0
  16. package/dist/core/context-edit.js +5 -2
  17. package/dist/core/engine-notice.d.ts +7 -0
  18. package/dist/core/gate-lanes.js +12 -10
  19. package/dist/core/governance-codes.d.ts +1 -1
  20. package/dist/core/governance-codes.js +2 -0
  21. package/dist/core/hooks.d.ts +3 -0
  22. package/dist/core/hooks.js +4 -2
  23. package/dist/core/mode-policy.js +2 -1
  24. package/dist/core/permission-rule-model.d.ts +2 -2
  25. package/dist/core/permission-rule-model.js +2 -2
  26. package/dist/core/permission-rules.js +2 -5
  27. package/dist/core/runner/active-skill-scope.js +5 -5
  28. package/dist/core/runner/decide-continuation.js +2 -1
  29. package/dist/core/runner/permission-rule-lanes.d.ts +3 -0
  30. package/dist/core/runner/permission-rule-lanes.js +7 -6
  31. package/dist/core/runner/prepare-context-lane.d.ts +4 -0
  32. package/dist/core/runner/prepare-context-lane.js +7 -5
  33. package/dist/core/runner/prepare-gate-stations.js +17 -12
  34. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  35. package/dist/core/runner/prepare-park-ask.js +7 -6
  36. package/dist/core/runner/prepare-policy-chain.d.ts +2 -0
  37. package/dist/core/runner/prepare-policy-chain.js +10 -9
  38. package/dist/core/runner/prepare-safety-scan.js +9 -2
  39. package/dist/core/runner/prepare-task.js +3 -3
  40. package/dist/core/runner/resume-apply.js +2 -1
  41. package/dist/core/runner/run-attachment-seats.js +3 -1
  42. package/dist/core/runner/run-harness-handlers.js +6 -1
  43. package/dist/core/runner/run-leg.js +11 -9
  44. package/dist/core/runner/run-recovery-lanes.js +2 -0
  45. package/dist/core/runner/session-rule-policy.js +5 -4
  46. package/dist/core/runner/tool-call-id-fold-seat.d.ts +13 -0
  47. package/dist/core/runner/tool-call-id-fold-seat.js +31 -0
  48. package/dist/core/sensitive-path-policy.js +2 -2
  49. package/dist/core/side-query.d.ts +5 -1
  50. package/dist/core/side-query.js +5 -1
  51. package/dist/core/tool-name-readings.d.ts +366 -0
  52. package/dist/core/tool-name-readings.js +136 -0
  53. package/dist/core/tool-policy.d.ts +5 -5
  54. package/dist/core/tool-policy.js +15 -13
  55. package/dist/core/tool-registry.d.ts +1 -31
  56. package/dist/core/tool-registry.js +2 -22
  57. package/dist/core/tool-result-budget.d.ts +3 -0
  58. package/dist/core/tool-result-budget.js +5 -3
  59. package/dist/core/tool-roster.d.ts +30 -9
  60. package/dist/core/tool-roster.js +45 -5
  61. package/dist/engine/llm/types.d.ts +4 -0
  62. package/dist/tools/fs/file-read-check.d.ts +1 -1
  63. package/dist/tools/fs/file-read-check.js +4 -3
  64. package/dist/tools/fs/shell-read-check.d.ts +15 -1
  65. package/dist/tools/fs/shell-read-check.js +5 -2
  66. package/package.json +2 -1
  67. package/wire-changes/core@7.36.0.json +288 -0
@@ -11,13 +11,15 @@ import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdi
11
11
  import { readShellCommand } from "./shell-lexer.js";
12
12
  import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
13
13
  import { catalogRuleFaceOf } from "./tool-registry.js";
14
- import { callAnswersTo, callFaceSeat } from "./tool-roster.js";
14
+ import { callFaceSeat } from "./tool-roster.js";
15
+ import { heldBy, wireOf } from "./tool-name-readings.js";
15
16
  import { modeAcceptsFileCommand } from "./mode-policy.js";
16
17
  import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
17
18
  import { exitGate } from "./runner/gate-exit.js";
18
19
  import { traceHookCrash } from "./gate-crash-trace.js";
19
20
  import { createGateDeadline, awaitWriteProtectionJudgment, withGateTimeout } from "./gate-bounded-await.js";
20
21
  import { thrownText } from "./thrown-value.js";
22
+ const QUESTION_TOOL_NAMES = new Set([ASK_USER_QUESTION_TOOL_NAME]);
21
23
  function isPlainOwnRecord(x) {
22
24
  try {
23
25
  if (x === null || typeof x !== "object" || Array.isArray(x))
@@ -170,7 +172,7 @@ export async function runGateLanes(pass) {
170
172
  writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
171
173
  });
172
174
  const askOriginFacts = (org, ruleCallUnreadable) => ({
173
- contentQuestion: callAnswersTo(pass.req, ASK_USER_QUESTION_TOOL_NAME),
175
+ contentQuestion: heldBy("lanes.contentQuestionClass", QUESTION_TOOL_NAMES, pass.req),
174
176
  markedUnresolvable: markedUnresolvable(),
175
177
  org,
176
178
  ...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
@@ -195,7 +197,7 @@ export async function runGateLanes(pass) {
195
197
  const applyOrgLayer = async (current, args) => {
196
198
  if (input.orgRules === undefined)
197
199
  return { decision: current, org: undefined };
198
- if (toolName === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
200
+ if (wireOf("lanes.orgContentAskExemption", pass.req) === ASK_USER_QUESTION_TOOL_NAME && input.orgRules.contentAskToolMounted === true)
199
201
  return { decision: current, org: undefined };
200
202
  let decided = current;
201
203
  let pending;
@@ -271,7 +273,7 @@ export async function runGateLanes(pass) {
271
273
  return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(input.abortSignal !== undefined ? { signal: input.abortSignal } : {}), timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS });
272
274
  };
273
275
  const laneReq = { ...pass.req, ...(pass.policyRewrite !== undefined ? { args: pass.policyRewrite } : {}) };
274
- const laneAnswer = input.persistedRules !== undefined && pass.req.toolName !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
276
+ const laneAnswer = input.persistedRules !== undefined && wireOf("lanes.persistedLaneSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME ? await readPersistedRules(laneReq) : undefined;
275
277
  {
276
278
  const tightened = applyPersistedTightening(pass.decision, laneAnswer);
277
279
  pass.decision = tightened.decision;
@@ -299,7 +301,7 @@ export async function runGateLanes(pass) {
299
301
  pass.decision.action === "ask" &&
300
302
  pass.decision.requiresRealApproval !== true &&
301
303
  pass.decision.decisionReason !== "hook" &&
302
- pass.req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
304
+ wireOf("lanes.allowLayerQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
303
305
  !markedUnresolvable();
304
306
  if (input.persistedRules && allowLayerMayClear()) {
305
307
  const answer = laneAnswer ?? {};
@@ -356,7 +358,7 @@ export async function runGateLanes(pass) {
356
358
  classifyStanding(stamped, originFacts);
357
359
  pass.decision = stampAskOrigin(stamped, originFacts);
358
360
  }
359
- if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && pass.req.toolName === COMMAND_RULE_TOOL) {
361
+ if (allowLayerMayClear() && pass.decision.action === "ask" && ruleMayClear() && pass.probeBoundaryDeclined !== true && wireOf("lanes.readOnlyShellAllow", pass.req) === COMMAND_RULE_TOOL) {
360
362
  const commandParam = catalogRuleFaceOf(COMMAND_RULE_TOOL)?.primaryParams[0] ?? "command";
361
363
  const args = pass.req.args;
362
364
  const command = typeof args === "object" && args !== null && !Array.isArray(args) ? args[commandParam] : undefined;
@@ -435,7 +437,7 @@ export async function runGateLanes(pass) {
435
437
  }
436
438
  if (input.sandboxAdmission !== undefined &&
437
439
  pass.decision.action === "ask" &&
438
- pass.req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
440
+ wireOf("lanes.sandboxQuestionSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME &&
439
441
  input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
440
442
  pass.decision.requiresRealApproval !== true &&
441
443
  pass.decision.decisionReason !== "hook" &&
@@ -463,7 +465,7 @@ export async function runGateLanes(pass) {
463
465
  if (pass.decision.action === "ask" && input.permissionMode !== undefined && pass.askClassification !== undefined) {
464
466
  const standing = pass.decision;
465
467
  const mode = input.permissionMode();
466
- const fileCommandByWord = modeAcceptsFileCommand(mode, toolName, pass.policyRewrite ?? pass.currentInput);
468
+ const fileCommandByWord = modeAcceptsFileCommand(mode, wireOf("lanes.modeFileCommand", pass.req), pass.policyRewrite ?? pass.currentInput);
467
469
  const verdict = arbitrateByMode(mode, pass.askClassification, { ...(standing.origin !== undefined ? { origin: standing.origin } : {}), ...(standing.mandate !== undefined ? { mandate: standing.mandate } : {}) }, { fileCommand: pass.shellWritesPlaced === true && fileCommandByWord });
468
470
  if (verdict.kind === "ask" && fileCommandByWord && pass.shellWritesPlaced !== true && pass.shellUnplacedOptions !== undefined && pass.askClassification.classes.size === 1 && pass.askClassification.classes.has("ordinary")) {
469
471
  pass.decision = { ...standing, message: fileCommandOptionsSentence(pass.shellUnplacedOptions.program) };
@@ -528,7 +530,7 @@ export async function runGateLanes(pass) {
528
530
  return { suspend: suspended, preToolContext: pass.preToolContext };
529
531
  }
530
532
  }
531
- if (pass.decision.action === "ask" && pass.req.toolName === ASK_USER_QUESTION_TOOL_NAME) {
533
+ if (pass.decision.action === "ask" && wireOf("lanes.contentAskRoute", pass.req) === ASK_USER_QUESTION_TOOL_NAME) {
532
534
  const outcome = input.resolveContentAsk !== undefined ? await (callSignal !== undefined ? input.resolveContentAsk(pass.req, callSignal) : input.resolveContentAsk(pass.req)) : { kind: "unavailable", parkDeclined: false };
533
535
  if (outcome.kind === "answered") {
534
536
  pass.decision = { action: "allow", updatedInput: outcome.presentedInput };
@@ -692,7 +694,7 @@ export async function runGateLanes(pass) {
692
694
  break;
693
695
  }
694
696
  let editRuleCallUnreadable = false;
695
- if (input.persistedRules !== undefined && toolName !== ASK_USER_QUESTION_TOOL_NAME) {
697
+ if (input.persistedRules !== undefined && wireOf("lanes.persistedEditSkip", pass.req) !== ASK_USER_QUESTION_TOOL_NAME) {
696
698
  const editLane = await readPersistedRules({ toolName, args: editArgs, toolCallId, ...(pass.req.face !== undefined ? { face: pass.req.face } : {}) });
697
699
  editRuleCallUnreadable = ruleCallUnreadableOf(editLane);
698
700
  const editTightened = applyPersistedTightening(recheck, editLane);
@@ -135,7 +135,7 @@ export type NoticeAudience = "user" | "operator";
135
135
  * the union's catalog ({@link EngineNotice.code}). The groups below say only which family a row joins,
136
136
  * so the catalog is not written a second time here and the two cannot drift.
137
137
  */
138
- export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
138
+ export declare const ENGINE_NOTICE_CODES: readonly ["config.autocompact_window_clamped", "config.env_timeout_discarded", "config.materialize_env_discarded", "config.models_swapped", "config.context_settings_swapped", "config.read_face_swapped", "config.read_face_deployment_clamped", "config.write_face_swapped", "config.write_face_deployment_clamped", "config.tool_model_gate_removed", "config.tool_model_gate_unknown_class", "config.tool_model_gate_env_invalid", "config.artifact_host_invalid", "config.task_root_not_canonical", "config.write_protection_target_view_absent", "config.write_protection_unresolved", "config.execution_env_capability_invalid", "config.tool_card_undeclared", "config.tool_face_undeclared", "config.tool_face_invalid", "config.forward_sink_failed", "config.durable_gate_unavailable", "config.peer_admission_out_of_range", "config.peer_lane_unmounted", "config.secret_env_scrubbed", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable", "resume.legacy_mode_seed_migrated", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "classifier.fallback", "classifier.role_defaulted", "classifier.contract_broken", "classifier.unavailable", "checkpoint.execution_outcome_unrecorded", "delegation.transcript_integrity", "delegation.ask_unresolvable", "mcp.injection_dropped", "mcp.revocation_probe_failed", "mcp.server_redialed", "workflow.governance_key_stripped", "workflow.agent_option_ignored", "memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.content_class_declared", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "memory.consolidation_recommended", "memory.consolidation_committed", "memory.consolidation_conflict", "memory.consolidation_incomplete", "memory.consolidation_refused", "memory.consolidation_withheld", "memory.restore_withheld", "memory.project_scope_absent", "route.fallback_to_primary", "route.classifier_unroutable", "route.base_url_changed_key_unchanged", "route.response_model_mismatch", "route.tool_call_id_folded", "task.user_steer_undrained", "task.user_followup_undrained", "steering.parked_input_blocked", "task.turn_interrupted", "task.halt_unconsumed", "task.late_approval", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "skills.listing_truncated", "agents.listing_truncated", "tool_result.offload_put_failed"];
139
139
  /** A code this engine mints (see {@link ENGINE_NOTICE_CODES}). NOT the type of
140
140
  * `EngineNotice.code`, which stays `string` — a host forwarding its own notices through the same
141
141
  * sink is a supported shape, and narrowing that field would break it. */
@@ -178,6 +178,7 @@ export const ENGINE_NOTICE_CODES = [
178
178
  "route.classifier_unroutable",
179
179
  "route.base_url_changed_key_unchanged",
180
180
  "route.response_model_mismatch",
181
+ "route.tool_call_id_folded",
181
182
  "task.user_steer_undrained",
182
183
  "task.user_followup_undrained",
183
184
  "steering.parked_input_blocked",
@@ -269,6 +270,7 @@ const NOTICE_AUDIENCE_TABLE = {
269
270
  "route.classifier_unroutable": "operator",
270
271
  "route.base_url_changed_key_unchanged": "operator",
271
272
  "route.response_model_mismatch": "operator",
273
+ "route.tool_call_id_folded": "operator",
272
274
  "skills.listing_truncated": "operator",
273
275
  "agents.listing_truncated": "operator",
274
276
  "tool_result.offload_put_failed": "operator",
@@ -474,6 +474,9 @@ export interface PostToolUseFailureResult {
474
474
  /** One resolved call in a postToolBatch payload (CC PostToolBatchToolCall shape, thin projection —
475
475
  * `response` is a bounded text digest of the result, never the full details object). */
476
476
  export interface PostToolBatchCall {
477
+ /** The name the model spelled on the tool_use block (an alias stays an alias) — the reference's PostToolBatch payload
478
+ * names each block the same way, while the per-call post hooks receive the resolved tool's wire name. Correlate
479
+ * the two faces by `toolCallId`. */
477
480
  toolName: string;
478
481
  input: unknown;
479
482
  toolCallId: string;
@@ -11,6 +11,7 @@ import { isAskOrigin } from "./ask-origin.js";
11
11
  import { inlineUntrusted } from "./untrusted-text.js";
12
12
  export { formatHookFeedback } from "./reminder-mint.js";
13
13
  import { createSafeNotifier } from "./safe-notify.js";
14
+ import { wireOf } from "./tool-name-readings.js";
14
15
  export const DEFAULT_HOOK_TIMEOUT_MS = 600_000;
15
16
  export const MAX_HOOK_TIMEOUT_MS = 2_147_483_647;
16
17
  const POST_ABORT_GRACE_MS = 1_000;
@@ -247,9 +248,10 @@ export async function runToolGate(input) {
247
248
  const { event, preToolUse, adjudicate, resolveAsk, suspendAsk } = input;
248
249
  const { toolCallId, toolName } = event;
249
250
  const callSignal = input.callSignal;
251
+ const screenedName = wireOf("gate.preToolUse", event);
250
252
  const hookCtx = (seatSignal) => ({
251
253
  toolCallId,
252
- toolName,
254
+ toolName: screenedName,
253
255
  ...(seatSignal !== undefined ? { signal: seatSignal } : {}),
254
256
  ...(() => {
255
257
  const c = input.trackedCwd?.();
@@ -284,7 +286,7 @@ export async function runToolGate(input) {
284
286
  };
285
287
  const screening = preToolUse !== undefined
286
288
  ? {
287
- screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(toolName, args, hookCtx(sig))),
289
+ screen: (args) => runHookSeat("preToolUse", seatBound(true), (sig) => preToolUse(screenedName, args, hookCtx(sig))),
288
290
  screened: screenPreToolUseResult,
289
291
  crashReason: preToolUseCrashReason,
290
292
  unansweredReason: preToolUseUnansweredBlock,
@@ -1,6 +1,7 @@
1
1
  import { createFsWriteGatePolicy } from "./fs-write-gate-policy.js";
2
2
  import { isProtectedWrite } from "./tool-registry.js";
3
3
  import { rosterEntryNamed } from "./tool-roster.js";
4
+ import { wireOf } from "./tool-name-readings.js";
4
5
  import { clampPermissionMode } from "./ask-origin.js";
5
6
  import { argvOf, lexShellText } from "../shell/lex.js";
6
7
  import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
@@ -42,7 +43,7 @@ const ENGINE_ROSTER_SOURCES = new Set(["builtin", "synthetic"]);
42
43
  export function planRefusesWrite(req, roster, readOnlyDeclared) {
43
44
  if (isProtectedWrite(req))
44
45
  return true;
45
- const row = roster === undefined ? undefined : rosterEntryNamed(roster, req.toolName);
46
+ const row = roster === undefined ? undefined : rosterEntryNamed(roster, wireOf("mode.planRosterRow", req));
46
47
  return row !== undefined && !ENGINE_ROSTER_SOURCES.has(row.source) && row.effect !== "read" && !(row.effectProvenance === "default" && readOnlyDeclared?.(row.name) === true);
47
48
  }
48
49
  const planAsk = (message) => ({ action: "ask", message, decisionReason: "mode" });
@@ -141,8 +141,8 @@ export declare const READ_RULE_TOOL = "Read";
141
141
  * catalog's declarations, exactly as the protective fences' covered set does.
142
142
  */
143
143
  export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
144
- /** The names the persisted grammar judges a call under: every one of the call's rule names (`ruleNamesOf` — the wire
145
- * name, then the aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
144
+ /** The names the persisted grammar judges a call under: every name the call answers to (the model's spelling, the wire
145
+ * name, the other aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
146
146
  * tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
147
147
  export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
148
148
  /**
@@ -4,7 +4,7 @@ import { compileReadDeny } from "../tools/fs/read-deny.js";
4
4
  import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
5
5
  import { inlineUntrusted } from "./untrusted-text.js";
6
6
  import { pathTargetOf } from "./tool-registry.js";
7
- import { ruleNamesOf } from "./tool-roster.js";
7
+ import { heldValues } from "./tool-name-readings.js";
8
8
  import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
9
9
  import { readShellCommand } from "./shell-lexer.js";
10
10
  import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
@@ -32,7 +32,7 @@ export function ruleToolGrammarOf(toolName) {
32
32
  return pathTargetOf(toolName) !== undefined ? "path" : undefined;
33
33
  }
34
34
  export function laneToolNamesOf(req) {
35
- return ruleNamesOf(req).filter((n) => ruleToolGrammarOf(n) !== undefined);
35
+ return heldValues("policy.persistedGrammar", { get: (n) => (ruleToolGrammarOf(n) !== undefined ? n : undefined) }, req);
36
36
  }
37
37
  export const MAX_RULE_TEXT_CHARS = 512;
38
38
  export const BARE_INTERPRETER_NAMES = new Set([
@@ -6,7 +6,7 @@ import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/sa
6
6
  import { effectivePathTargetOf } from "./effective-path-target.js";
7
7
  import { protectivePathTargetOf } from "./tool-registry.js";
8
8
  import { ruleDenialSentence } from "./rule-denial-sentence.js";
9
- import { ruleNamesOf } from "./tool-roster.js";
9
+ import { heldValues } from "./tool-name-readings.js";
10
10
  export { parsePermissionRule };
11
11
  export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
12
12
  const DEFAULT_CAPS = {
@@ -381,10 +381,7 @@ export function createPermissionRulePolicy(rules, opts) {
381
381
  requiresRealApproval: true,
382
382
  });
383
383
  const entryFor = (req) => {
384
- const entries = ruleNamesOf(req).flatMap((n) => {
385
- const e = byTool.get(n);
386
- return e !== undefined ? [e] : [];
387
- });
384
+ const entries = heldValues("policy.permissionRules", byTool, req);
388
385
  if (entries.length <= 1)
389
386
  return entries[0];
390
387
  const bare = {};
@@ -3,7 +3,7 @@ import { pathTargetValue } from "../tool-registry.js";
3
3
  import { pathTargetBaseOf } from "../effective-path-target.js";
4
4
  import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
5
5
  import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
6
- import { ruleNamesOf } from "../tool-roster.js";
6
+ import { heldValues, wireOf } from "../tool-name-readings.js";
7
7
  export class ActiveSkillScope {
8
8
  frames = [];
9
9
  push(frame) {
@@ -23,7 +23,7 @@ export function createActiveSkillScopePolicy(opts) {
23
23
  const { scope, env, rootPath, toolEffects } = opts;
24
24
  return {
25
25
  async check(req, signal) {
26
- const names = ruleNamesOf(req);
26
+ const entriesFor = (byName) => heldValues("policy.skillScope", byName, req).flat();
27
27
  const frames = scope.active();
28
28
  if (frames.length === 0)
29
29
  return { action: "allow" };
@@ -48,7 +48,7 @@ export function createActiveSkillScopePolicy(opts) {
48
48
  }
49
49
  return { manifest: m, byName };
50
50
  });
51
- if (perFrame.length === 0 || perFrame.some((f) => !names.some((n) => f.byName.has(n)))) {
51
+ if (perFrame.length === 0 || perFrame.some((f) => entriesFor(f.byName).length === 0)) {
52
52
  const ids = manifests.map((m) => m.lineageId).join(", ");
53
53
  return {
54
54
  action: "deny",
@@ -57,7 +57,7 @@ export function createActiveSkillScopePolicy(opts) {
57
57
  };
58
58
  }
59
59
  for (const frame of perFrame) {
60
- const alternatives = names.flatMap((n) => frame.byName.get(n) ?? []);
60
+ const alternatives = entriesFor(frame.byName);
61
61
  const rejections = [];
62
62
  let admitted = false;
63
63
  for (const entry of alternatives) {
@@ -80,7 +80,7 @@ export function createActiveSkillScopePolicy(opts) {
80
80
  const declaredTarget = req.face?.pathTarget;
81
81
  const skillScopeEligible = declaredTarget !== undefined && declaredTarget.skillScopeEligible === true;
82
82
  if (pathConstrainingActive && !skillScopeEligible) {
83
- const eff = toolEffects?.get(req.toolName) ?? "write";
83
+ const eff = toolEffects?.get(wireOf("policy.skillScope.effect", req)) ?? "write";
84
84
  if (eff !== "read") {
85
85
  const ids = manifests.filter((m) => m.allowPaths?.length).map((m) => m.lineageId).join(", ");
86
86
  return {
@@ -3,6 +3,7 @@ import { boundInputHashOf } from "../canonical-json.js";
3
3
  import { CheckpointError } from "../checkpoint-store.js";
4
4
  import { formatHookFeedback } from "../hooks.js";
5
5
  import { delimitUntrusted, REVIEWER_NOTE_MAX_BODY } from "../untrusted-text.js";
6
+ import { wireOf } from "../tool-name-readings.js";
6
7
  export function resumeDecisionWasNegative(resume) {
7
8
  const o = resume.outcome;
8
9
  return o.decision === "reject" || o.decision === "deny";
@@ -49,7 +50,7 @@ export function sameWinner(incoming, persisted) {
49
50
  (persisted.reason === undefined || incoming.reason === persisted.reason));
50
51
  }
51
52
  export function pendingContentAskCallId(cp) {
52
- return cp.pendingAction.kind === "tool_approval" && cp.pendingAction.toolName === ASK_USER_QUESTION_TOOL_NAME
53
+ return cp.pendingAction.kind === "tool_approval" && wireOf("resume.contentAskRow", cp.pendingAction) === ASK_USER_QUESTION_TOOL_NAME
53
54
  ? cp.pendingAction.toolCallId
54
55
  : undefined;
55
56
  }
@@ -241,6 +241,9 @@ export declare function createRuleOffersOf(cfg: {
241
241
  shellGatedTools: {
242
242
  has(name: string): boolean;
243
243
  };
244
+ toolMarkSources: {
245
+ get(name: string): ReadonlySet<string> | undefined;
246
+ };
244
247
  taskRoot: string | undefined;
245
248
  /** The live tracked-cwd ref (read per call — the relative-cd resolution base). */
246
249
  cwdRef: {
@@ -3,7 +3,8 @@ import {} from "../hooks.js";
3
3
  import { adjudicatePersistedPathRules, adjudicatePersistedRules, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
4
4
  import { isAbsolutePathForm } from "../../tools/fs/safety.js";
5
5
  import { effectivePathTargetOf } from "../effective-path-target.js";
6
- import { anyNamed, declaredPathTargetOf, firstNamed, protectivePathTargetOf, ruleNamesOf } from "../tool-registry.js";
6
+ import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
7
+ import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
7
8
  import { orgRuleVerdictFor } from "../permission-rule-org.js";
8
9
  import { ownerStoreOf } from "../permission-rule-provider.js";
9
10
  import { thrownText } from "../thrown-value.js";
@@ -92,7 +93,7 @@ export function createPermissionRuleLanes(cfg) {
92
93
  const org = provider.partitions.org
93
94
  ? {
94
95
  adjudicate: async (req) => {
95
- if (req.toolName === cfg.questionToolName && cfg.questionToolMounted)
96
+ if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
96
97
  return { status: "available" };
97
98
  let resolution;
98
99
  try {
@@ -158,16 +159,16 @@ export function createPermissionRuleLanes(cfg) {
158
159
  }
159
160
  export function createRuleOffersOf(cfg) {
160
161
  return (call, args, ask) => {
161
- const toolName = call.toolName;
162
+ const toolName = wireOf("offer.laneTool", call);
162
163
  if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
163
164
  return {};
164
165
  if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
165
166
  return {};
166
- const tierSeat = firstNamed(cfg.irreversibilityTier, ruleNamesOf(call));
167
+ const tierSeat = seatOf("offer.marks", cfg.irreversibilityTier, call);
167
168
  const closedDoor = ruleClearanceOf(ask, {
168
- egress: anyNamed(cfg.egressTools, ruleNamesOf(call)),
169
+ egress: heldBy("offer.marks.egress", cfg.egressTools, call),
169
170
  irreversibility: tierSeat?.value,
170
- shellGated: tierSeat !== undefined && cfg.shellGatedTools.has(tierSeat.name),
171
+ shellGated: tierSeat?.shellGated(cfg.shellGatedTools, cfg.toolMarkSources) === true,
171
172
  }).word;
172
173
  if (closedDoor !== undefined)
173
174
  return { ruleOffersAbsence: closedDoor };
@@ -24,6 +24,7 @@ import { type RunnerDeps } from "../types.js";
24
24
  import type { Prepared, PreparedMicroCompact } from "./contracts.js";
25
25
  import { type GitStatusLaneRef } from "./git-status-frame.js";
26
26
  import type { ReadFileState } from "../../tools/fs/safety.js";
27
+ import { type ToolRosterDeltaSeat } from "../tool-roster.js";
27
28
  export interface PrepareContextLaneInput {
28
29
  /** borrowed-readonly — the task model: the budgets, the trigger-window geometry and `charsPerToken` are read off it. */
29
30
  model: Model;
@@ -39,6 +40,8 @@ export interface PrepareContextLaneInput {
39
40
  hostTaskId: string;
40
41
  /** borrowed-readonly — the run's offload store, or undefined (the caps' and the machine's persist seat). */
41
42
  offloadStore: ToolResultStore | undefined;
43
+ /** borrowed-readonly — the leg's live roster: a recorded tool name is judged as the wire name it resolves to. */
44
+ toolRosterDeltas: ToolRosterDeltaSeat;
42
45
  /** borrowed-readonly — the doors' RESOLVED microCompact knob (immutable values, never the deps bag). */
43
46
  microCompactKnob: {
44
47
  machine: MicroCompactMachine;
@@ -135,6 +138,7 @@ export interface GuardChainArgs {
135
138
  offloadStore: import("../tool-result-store.js").ToolResultStore | undefined;
136
139
  sessionId: string;
137
140
  taskId: string;
141
+ toolNameOf?: (recorded: string) => string;
138
142
  deps: RunnerDeps;
139
143
  trimPressureRef: {
140
144
  droppedMessages: boolean;
@@ -8,8 +8,10 @@ import { emitTrace } from "../trace.js";
8
8
  import { deliverEngineNotice } from "../types.js";
9
9
  import { applyGitFrameGuard } from "./git-status-frame.js";
10
10
  import { applyContextShrinkToReadFileState } from "../../tools/fs/index.js";
11
+ import { rosterToolName } from "../tool-roster.js";
11
12
  export function prepareContextLane(input) {
12
- const { model, deps, sessionId, runId, hostTaskId, offloadStore, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, readFileState } = input;
13
+ const { model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, readFileState } = input;
14
+ const toolNameOf = (recorded) => rosterToolName(toolRosterDeltas.current, recorded);
13
15
  const contextOverlay = input.contextOverlay ?? (() => undefined);
14
16
  const requestGeometry = () => {
15
17
  const current = harness.getModel();
@@ -27,7 +29,7 @@ export function prepareContextLane(input) {
27
29
  const replay = replayClearedProjection(healed, microCompact.ledger);
28
30
  const replayed = replay.messages;
29
31
  const capped = await capAggregateToolResults(replayed, {
30
- store: offloadStore,
32
+ store: offloadStore, toolNameOf,
31
33
  sessionId,
32
34
  onCapped: (info) => pendingProjectionObservations.push(() => emitTrace(deps.tracer, () => ({
33
35
  kind: "tool_result.capped",
@@ -49,7 +51,7 @@ export function prepareContextLane(input) {
49
51
  });
50
52
  let ccPass;
51
53
  const edited = microCompact.machine === "off" ? mediaCapped : clearStaleToolResults(mediaCapped, {
52
- budgetTokens: editAt,
54
+ budgetTokens: editAt, toolNameOf,
53
55
  clearSource: replayed,
54
56
  onCleared: (pass) => { ccPass = pass; },
55
57
  anchoredTotalTokens: estimateContextTokens(mediaCapped, cpt).tokens,
@@ -67,7 +69,7 @@ export function prepareContextLane(input) {
67
69
  }
68
70
  const chain = await runGuardChain({
69
71
  edited, replayed, index: replay.index, microCompact, guardAt, charsPerToken: cpt, offloadStore,
70
- sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal,
72
+ sessionId, taskId: hostTaskId, deps, trimPressureRef, recheck, signal, toolNameOf,
71
73
  pendingProjectionObservations,
72
74
  ...(readFileState !== undefined ? { readFileState } : {}),
73
75
  });
@@ -221,7 +223,7 @@ export async function runGuardChain(args) {
221
223
  if (microCompact.machine === "off" && anchoredWorking > guardAt) {
222
224
  let blockingPass;
223
225
  const blockingEdited = clearStaleToolResults(working, {
224
- budgetTokens: guardAt,
226
+ budgetTokens: guardAt, toolNameOf: args.toolNameOf,
225
227
  clearSource: args.replayed,
226
228
  onCleared: (pass) => { blockingPass = pass; },
227
229
  anchoredTotalTokens: anchoredWorking,
@@ -9,8 +9,10 @@ import { askUnresolvableNotice } from "../ask-unresolvable-notice.js";
9
9
  import { askReachesPerson } from "../wiring-manifest.js";
10
10
  import { classifierUnavailableSeat } from "./classifier-ladder.js";
11
11
  import { PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
12
- import { anyNamed, callAnswersTo, callFaceSeat, firstNamed, rosterToolName, ruleNamesOf, toolCallFaceOf } from "../tool-roster.js";
12
+ import { callFaceSeat, rosterToolName, toolCallFaceOf } from "../tool-roster.js";
13
+ import { heldBy, heldValues, seatOf, wireOf } from "../tool-name-readings.js";
13
14
  import { noteHostRewrite } from "../frame-settlement.js";
15
+ const PEER_MESSAGE_NAMES = new Set([SEND_MESSAGE_TOOL_NAME]);
14
16
  function screenGateResult(result) {
15
17
  if (result.suspend !== undefined)
16
18
  return { defects: [] };
@@ -88,12 +90,12 @@ export function prepareGateStations(input) {
88
90
  harness.on("tool_call", async (called) => {
89
91
  const e = { ...called, toolName: rosterToolName(toolRosterDeltas.current, called.toolName) };
90
92
  const call = { toolName: e.toolName, ...callFaceSeat(toolCallFaceOf(toolRosterDeltas.current, called.toolName)) };
91
- const names = ruleNamesOf(call);
92
- const tierSeat = firstNamed(irreversibilityTier, names);
93
+ const tierSeat = seatOf("gate.shellSeat", irreversibilityTier, call);
94
+ const markSources = new Set(heldValues("gate.markSources", toolMarkSources, call).flatMap((s) => [...s]));
93
95
  blockedToolCalls.delete(e.toolCallId);
94
96
  inheritedAskGrants.delete(e.toolCallId);
95
97
  {
96
- const complianceDeny = names.map((n) => complianceCallDenial(complianceDenies, n)).find((d) => d !== undefined);
98
+ const complianceDeny = heldValues("gate.compliance", { get: (n) => complianceCallDenial(complianceDenies, n) }, call)[0];
97
99
  if (complianceDeny !== undefined) {
98
100
  if (blockedTracked)
99
101
  blockedToolCalls.add(e.toolCallId);
@@ -122,19 +124,19 @@ export function prepareGateStations(input) {
122
124
  resolveAsk: resolveAskBound,
123
125
  suspendAsk,
124
126
  resolveContentAsk,
125
- egress: anyNamed(egressTools, names),
126
- peerMessage: callAnswersTo(call, SEND_MESSAGE_TOOL_NAME),
127
+ egress: heldBy("gate.egress", egressTools, call),
128
+ peerMessage: heldBy("gate.peerMessage", PEER_MESSAGE_NAMES, call),
127
129
  ...(writeProtectionCheck !== undefined ? { writeProtectionCheck } : {}),
128
130
  ...(shellReadCheck !== undefined ? { shellReadCheck } : {}),
129
131
  irreversibility: tierSeat?.value,
130
- reversibilityProbe: tierSeat !== undefined ? reversibilityProbes.get(tierSeat.name) : undefined,
132
+ reversibilityProbe: tierSeat?.read(reversibilityProbes),
131
133
  abortSignal: abortController.signal,
132
134
  ...(notifyPermissionDenied ? { permissionDenied: notifyPermissionDenied } : {}),
133
135
  onHookError: notifyHookError,
134
- shellGated: tierSeat !== undefined && shellGatedTools.has(tierSeat.name),
136
+ shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true,
135
137
  ...(autoModeDecider.current !== undefined ? { autoMode: { decider: autoModeDecider.current, ...(autoModeDenialTracking.current !== undefined ? { denialTracking: autoModeDenialTracking.current } : {}), unavailable: classifierUnavailableSeat({ station: "leg", ...(autoModeDecider.current.timeoutMs !== undefined ? { budgetMs: autoModeDecider.current.timeoutMs } : {}), approvalSeat: askReachesPerson(askRoute), onNotice: deps.onNotice, sessionId, runId }) } } : {}),
136
138
  permissionMode: () => permissionMode.current,
137
- ...(() => { const s = firstNamed(toolMarkSources, names)?.value; return s !== undefined ? { toolMarkSources: s } : {}; })(),
139
+ ...(markSources.size > 0 ? { toolMarkSources: markSources } : {}),
138
140
  ...(a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
139
141
  onModeReleased,
140
142
  ancestorArbitrationOf: (toolCallId) => ancestorArbitrations.get(toolCallId),
@@ -243,6 +245,7 @@ export function prepareGateStations(input) {
243
245
  }
244
246
  let content = e.content;
245
247
  let changed = false;
248
+ const post = { toolName: rosterToolName(toolRosterDeltas.current, e.toolName) };
246
249
  if (e.isError) {
247
250
  if (hooks?.postToolUseFailure) {
248
251
  let clonedDetails = e.details;
@@ -251,7 +254,8 @@ export function prepareGateStations(input) {
251
254
  }
252
255
  catch {
253
256
  }
254
- const failureSeat = await runHookSeat("postToolUseFailure", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUseFailure(e.toolName, e.input, {
257
+ const failedName = wireOf("hook.postToolUseFailure", post);
258
+ const failureSeat = await runHookSeat("postToolUseFailure", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUseFailure(failedName, e.input, {
255
259
  error: e.content
256
260
  .filter((c) => c.type === "text")
257
261
  .map((c) => c.text)
@@ -259,7 +263,7 @@ export function prepareGateStations(input) {
259
263
  isInterrupt: abortController.signal.aborted || spec.signal?.aborted === true,
260
264
  content: e.content.map((c) => ({ ...c })),
261
265
  details: clonedDetails,
262
- }, { toolCallId: e.toolCallId, toolName: e.toolName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
266
+ }, { toolCallId: e.toolCallId, toolName: failedName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
263
267
  if (failureSeat.expired) {
264
268
  notifyOwnHookCrash(hookSeatExpiredError("postToolUseFailure", hookTimeoutMs, failureSeat.cause, "the observation was abandoned and its additionalContext dropped; the failure result itself is unchanged"));
265
269
  }
@@ -271,7 +275,8 @@ export function prepareGateStations(input) {
271
275
  }
272
276
  }
273
277
  else if (hooks?.postToolUse) {
274
- const successSeat = await runHookSeat("postToolUse", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUse(e.toolName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: e.toolName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
278
+ const ranName = wireOf("hook.postToolUse", post);
279
+ const successSeat = await runHookSeat("postToolUse", { timeoutMs: hookTimeoutMs, signal: abortController.signal }, (sig) => hooks.postToolUse(ranName, e.input, { content: e.content, details: e.details, isError: e.isError }, { toolCallId: e.toolCallId, toolName: ranName, signal: sig, ...(handsCwdRef?.current !== undefined ? { cwd: handsCwdRef.current } : {}), ...(hookEnvFace !== undefined ? { env: hookEnvFace } : {}), identity: hookIdentity }));
275
280
  if (successSeat.expired) {
276
281
  notifyOwnHookCrash(hookSeatExpiredError("postToolUse", hookTimeoutMs, successSeat.cause, "the seat was abandoned; the tool result is delivered to the model UNMODIFIED (no updatedOutput, no additionalContext)"));
277
282
  }
@@ -116,6 +116,8 @@ export interface PrepareParkAskInput {
116
116
  shellGatedTools: ReadonlySet<string>;
117
117
  /** borrowed-readonly — the per-tool tier, read only for the name the call's tier seat is read under (as at the gate). */
118
118
  irreversibilityTier: IrreversibilityTier;
119
+ /** borrowed-readonly — who marked each name's seat (the descriptor's `shellGated` reads it as the gate does). */
120
+ toolMarkSources: ReadonlyMap<string, ReadonlySet<import("../ask-origin.js").ToolMarkSource>>;
119
121
  /** borrowed-readonly — the operator's shell floor (`"always"`), persisted on a shell-gated descriptor; undefined = no floor. */
120
122
  effectiveShellGate: ShellGateDoctrine | undefined;
121
123
  /** borrowed-readonly — the EFFECTIVE durable-approval opt-in (the caller's, or the synthesized mandate scope): picks the
@@ -15,7 +15,8 @@ import { checkpointScopeOf } from "./checkpoint-scope.js";
15
15
  import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
16
16
  import { DEFAULT_UNATTENDED_APPROVAL_TTL_MS, publishCommittedSuspend, sanitizedTtlMs } from "./park-commit.js";
17
17
  import { remoteEnvFailureNote } from "./remote-env-retry.js";
18
- import { callFaceSeat, firstNamed, parkedToolIdentity, parkedToolFace, ruleNamesOf } from "../tool-roster.js";
18
+ import { callFaceSeat, parkedToolIdentity, parkedToolFace } from "../tool-roster.js";
19
+ import { seatOf, wireOf } from "../tool-name-readings.js";
19
20
  class ParkRefusal extends Error {
20
21
  constructor(message, options) {
21
22
  super(message, options);
@@ -26,7 +27,7 @@ const wordlessDemand = (toolName) => {
26
27
  throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
27
28
  };
28
29
  export function prepareParkAsk(input) {
29
- const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
30
+ const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, toolMarkSources, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
30
31
  if (askLane === undefined || saga === undefined)
31
32
  return { parkAsk: undefined };
32
33
  const { composedCallSignal, approvalPreviewOf } = askLane;
@@ -114,7 +115,7 @@ export function prepareParkAsk(input) {
114
115
  };
115
116
  const suspendAsk = parkLaneArmed && checkpointStore !== undefined
116
117
  ? async (req, postHookArgs, safety, liveFaceUnavailable, realApproval, shadowedRule, askDecisionReason, probeReason, probeCause, segmentCoverage, matchedAskRule, probeMandated, carry) => {
117
- const syncFirstEligible = req.toolName === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
118
+ const syncFirstEligible = wireOf("park.syncFirstRoute", req) === ASK_USER_QUESTION_TOOL_NAME ? contentAskRoutable(req.toolCallId) : isLiveApproverSeat(onAsk);
118
119
  if (syncFirstEligible &&
119
120
  runtimeCaps?.forceDurableGate !== true &&
120
121
  liveFaceUnavailable !== true &&
@@ -234,7 +235,7 @@ export function prepareParkAsk(input) {
234
235
  remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
235
236
  }
236
237
  token = mintCheckpointToken();
237
- const tierSeat = firstNamed(irreversibilityTier, ruleNamesOf(req));
238
+ const tierSeat = seatOf("park.riskDescriptor", irreversibilityTier, req);
238
239
  const riskDescriptor = buildRiskDescriptor({
239
240
  toolName: req.toolName,
240
241
  args: parkedArgs,
@@ -242,7 +243,7 @@ export function prepareParkAsk(input) {
242
243
  ...(shadowedRule !== undefined ? { shadowedRule } : {}),
243
244
  ...(probeReason !== undefined ? { probeReason } : {}),
244
245
  ...(probeCause !== undefined ? { probeCause } : {}),
245
- shellGated: tierSeat !== undefined && shellGatedTools.has(tierSeat.name),
246
+ shellGated: tierSeat?.shellGated(shellGatedTools, toolMarkSources) === true,
246
247
  ...(effectiveShellGate !== undefined ? { shellGateDoctrine: effectiveShellGate } : {}),
247
248
  });
248
249
  gate =
@@ -303,7 +304,7 @@ export function prepareParkAsk(input) {
303
304
  kind: "tool_approval",
304
305
  toolCallId: req.toolCallId,
305
306
  toolName: req.toolName,
306
- ...parkedToolIdentity(toolRosterDeltas.current, req.toolName), ...parkedToolFace(toolRosterDeltas.current, req.toolName),
307
+ ...parkedToolIdentity(toolRosterDeltas.current, req.toolName), ...parkedToolFace(toolRosterDeltas.current, req),
307
308
  args: parkedArgs,
308
309
  ...(() => {
309
310
  const p = approvalPreviewOf(req.toolName, parkedArgs);
@@ -85,6 +85,8 @@ export interface PreparePolicyChainInput {
85
85
  envHandToolNames: ReadonlySet<string>;
86
86
  /** borrowed-readonly — the shell-seat provenance set (the offer factory's `shellGated` mandate term, read per call). */
87
87
  shellGatedTools: ReadonlySet<string>;
88
+ /** borrowed-readonly — who marked each name's seat (the offer factory's `shellGated` term reads it as the gate does). */
89
+ toolMarkSources: ReadonlyMap<string, ReadonlySet<import("../ask-origin.js").ToolMarkSource>>;
88
90
  /** borrowed-readonly — the tracked-cwd seat, read per call by the lanes and the offer factory (never written here). */
89
91
  handsCwdRef: CwdRef | undefined;
90
92
  /** borrowed-readonly — the compiled read-deny judge, or undefined (the offer factory's directory negative control). */