@sema-agent/core 7.42.1 → 7.44.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 (115) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +3 -1
  4. package/dist/agents/subagent.js +5 -3
  5. package/dist/agents/teacher.js +1 -1
  6. package/dist/agents/verify.js +2 -0
  7. package/dist/brain/errors.d.ts +23 -2
  8. package/dist/brain/errors.js +26 -2
  9. package/dist/brain/stream-engine.d.ts +2 -1
  10. package/dist/brain/stream-engine.js +10 -9
  11. package/dist/core/allow-entry.d.ts +50 -31
  12. package/dist/core/allow-entry.js +100 -37
  13. package/dist/core/ask-origin.d.ts +16 -5
  14. package/dist/core/ask-origin.js +11 -3
  15. package/dist/core/ask-unresolvable-notice.d.ts +3 -2
  16. package/dist/core/ask-unresolvable-notice.js +1 -1
  17. package/dist/core/auto-mode.d.ts +5 -4
  18. package/dist/core/auto-mode.js +3 -1
  19. package/dist/core/checkpoint-store.d.ts +44 -20
  20. package/dist/core/checkpoint-store.js +10 -16
  21. package/dist/core/child-clamp-seats.d.ts +15 -1
  22. package/dist/core/child-clamp-seats.js +6 -2
  23. package/dist/core/classifier-unavailable-arm.d.ts +42 -23
  24. package/dist/core/classifier-unavailable-arm.js +14 -3
  25. package/dist/core/decision-receipt.js +2 -0
  26. package/dist/core/engine-notice.d.ts +16 -14
  27. package/dist/core/engine-notice.js +4 -3
  28. package/dist/core/gate-lanes.js +37 -29
  29. package/dist/core/governance-codes.d.ts +4 -3
  30. package/dist/core/governance-codes.js +1 -0
  31. package/dist/core/hooks.d.ts +6 -1
  32. package/dist/core/hooks.js +3 -0
  33. package/dist/core/memory-engine/engine.d.ts +2 -2
  34. package/dist/core/memory-engine/engine.js +7 -14
  35. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  36. package/dist/core/memory-engine/file-backend.js +5 -7
  37. package/dist/core/memory-engine/layout.d.ts +37 -4
  38. package/dist/core/memory-engine/layout.js +34 -5
  39. package/dist/core/mode-policy.d.ts +5 -4
  40. package/dist/core/mode-policy.js +4 -0
  41. package/dist/core/permission-rule-consent.d.ts +3 -2
  42. package/dist/core/permission-rule-consent.js +25 -20
  43. package/dist/core/permission-rule-model.d.ts +47 -7
  44. package/dist/core/permission-rule-model.js +80 -6
  45. package/dist/core/permission-rules.d.ts +8 -2
  46. package/dist/core/permission-rules.js +26 -9
  47. package/dist/core/runner/classifier-ladder.d.ts +10 -8
  48. package/dist/core/runner/classifier-ladder.js +3 -1
  49. package/dist/core/runner/contracts.d.ts +1 -16
  50. package/dist/core/runner/denial-limit-arms.d.ts +25 -8
  51. package/dist/core/runner/denial-limit-arms.js +24 -10
  52. package/dist/core/runner/gate-exit.js +2 -1
  53. package/dist/core/runner/permission-rule-lanes.d.ts +2 -2
  54. package/dist/core/runner/permission-rule-lanes.js +8 -2
  55. package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
  56. package/dist/core/runner/prepare-ask-lane.js +2 -1
  57. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -3
  58. package/dist/core/runner/prepare-caps-and-workflow.js +3 -2
  59. package/dist/core/runner/prepare-config-doors.d.ts +3 -0
  60. package/dist/core/runner/prepare-config-doors.js +6 -5
  61. package/dist/core/runner/prepare-gate-stations.js +1 -1
  62. package/dist/core/runner/prepare-inherited-gate.d.ts +7 -5
  63. package/dist/core/runner/prepare-inherited-gate.js +5 -3
  64. package/dist/core/runner/prepare-park-ask.d.ts +4 -4
  65. package/dist/core/runner/prepare-park-ask.js +2 -4
  66. package/dist/core/runner/prepare-policy-chain.d.ts +7 -1
  67. package/dist/core/runner/prepare-policy-chain.js +59 -38
  68. package/dist/core/runner/prepare-project-context.js +6 -2
  69. package/dist/core/runner/prepare-task.js +6 -6
  70. package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -5
  71. package/dist/core/runner/prepare-wiring-manifest.js +8 -9
  72. package/dist/core/runner/resume-checkpoint-screen.js +1 -1
  73. package/dist/core/runner/runtask.js +1 -5
  74. package/dist/core/runner/steer-admission.d.ts +10 -11
  75. package/dist/core/runner/steer-admission.js +6 -8
  76. package/dist/core/runner/stream-steer-verb.d.ts +5 -4
  77. package/dist/core/runner/stream-steer-verb.js +6 -8
  78. package/dist/core/runner/synthetic-tools.d.ts +3 -2
  79. package/dist/core/runner/synthetic-tools.js +1 -1
  80. package/dist/core/session-policy-store.d.ts +3 -3
  81. package/dist/core/session-policy-store.js +18 -4
  82. package/dist/core/skill-grant-binding.d.ts +44 -0
  83. package/dist/core/skill-grant-binding.js +22 -0
  84. package/dist/core/skill-grant.d.ts +59 -28
  85. package/dist/core/skill-grant.js +96 -23
  86. package/dist/core/steer-replay.d.ts +46 -0
  87. package/dist/core/steer-replay.js +22 -0
  88. package/dist/core/task-event.d.ts +3 -2
  89. package/dist/core/task-spec.d.ts +14 -1
  90. package/dist/core/task-stream.d.ts +12 -6
  91. package/dist/core/tighten-task-spec.d.ts +2 -0
  92. package/dist/core/tighten-task-spec.js +3 -0
  93. package/dist/core/tool-errors.d.ts +2 -2
  94. package/dist/core/tool-name-aliases.d.ts +35 -5
  95. package/dist/core/tool-name-aliases.js +29 -4
  96. package/dist/core/tool-policy.d.ts +10 -0
  97. package/dist/core/tool-policy.js +9 -2
  98. package/dist/core/tool-spec.d.ts +7 -0
  99. package/dist/core/types.d.ts +2 -2
  100. package/dist/core/types.js +1 -1
  101. package/dist/core/wiring-manifest.d.ts +56 -14
  102. package/dist/core/wiring-manifest.js +29 -4
  103. package/dist/index.d.ts +6 -4
  104. package/dist/index.js +5 -4
  105. package/dist/orchestration/run-workflow-tool.d.ts +4 -0
  106. package/dist/orchestration/run-workflow-tool.js +1 -1
  107. package/dist/orchestration/workflow-primitives.d.ts +3 -1
  108. package/dist/orchestration/workflow-primitives.js +6 -6
  109. package/dist/orchestration/workflow-types.d.ts +5 -0
  110. package/dist/orchestration/workflow.d.ts +3 -2
  111. package/dist/orchestration/workflow.js +14 -6
  112. package/package.json +1 -1
  113. package/test/export-surface.snapshot.json +21 -5
  114. package/wire-changes/core@7.43.0.json +985 -0
  115. package/wire-changes/core@7.44.0.json +469 -0
@@ -1,3 +1,4 @@
1
+ import { type ProtocolNamespace } from "./protocol-table.js";
1
2
  /**
2
3
  * RB-476-A stage 2 (ruling 2026-08-02, RB-476 "no compatibility surface survives", replay
3
4
  * resolution included) — the durable-name machinery this module used to hold is RETIRED:
@@ -26,17 +27,23 @@
26
27
  * wider tool: writing it would deny more than the entry did, so it is no current name), or `removed — `.
27
28
  */
28
29
  export declare const RETIRED_TOOL_NAMES: ReadonlyMap<string, string>;
29
- /** The three answers {@link judgeRuleToolName} gives — a closed set. */
30
- export declare const RULE_TOOL_NAME_VERDICTS: readonly ["current", "retired", "unprefixed_namespace"];
30
+ /** The four answers {@link judgeRuleToolName} gives — a closed set. */
31
+ export declare const RULE_TOOL_NAME_VERDICTS: readonly ["current", "retired", "unprefixed_namespace", "unmintable_segment"];
31
32
  /**
32
33
  * Whether a tool name written in a rule is a stale name — one that, against `roster`, names no tool and never can:
33
- * - `current` — not a stale name: the roster mounts it, or it is neither retired nor a prefix-less `__` name. (Whether a
34
- * current name matches anything THIS run is a different question — an unknown name is the prepare audit's advisory.)
34
+ * - `current` — not a stale name: the roster mounts it, or it is none of the three below. (Whether a current name matches
35
+ * anything THIS run is a different question — an unknown name is the prepare audit's advisory.)
35
36
  * - `retired` — a name in {@link RETIRED_TOOL_NAMES} the roster does not mount; `guidance` is that table's line, and
36
37
  * `currentName` the 1:1 successor when there is one (see the module header's grammar).
37
38
  * - `unprefixed_namespace` — carries the `__` namespace separator with no protocol prefix. The roster cannot clear it:
38
39
  * a caller tool name or alias carrying `__` is refused at prepare, every engine tool name is `__`-free, and every
39
40
  * protocol tool is minted with its prefix.
41
+ * - `unmintable_segment` — carries a protocol prefix, but its peer segment (the server / remote agent) is not a spelling
42
+ * the mint produces for any peer: the name is read as written (peers compare exactly), and every mounted tool carries
43
+ * the minted spelling, so the name reaches none of them. `guidance` says so in one line; `currentName` is the whole name
44
+ * rewritten with the spelling the mint produces (`mcp__github.com__*` → `mcp__github_com__*`), absent when the peer
45
+ * segment is empty and there is nothing to rewrite. The tool segment after the peer is never judged: it is a literal
46
+ * tool name or a glob over one, and an unmatched one is the audit's advisory.
40
47
  *
41
48
  * `roster` = the tool names (aliases included) the reader knows are mounted: a run's assembled roster at prepare, the
42
49
  * names a deployment DECLARES it mounts its own tools under at a session-rule store (an empty set when it declares none).
@@ -49,6 +56,29 @@ export type RuleToolNameVerdict = {
49
56
  readonly currentName?: string;
50
57
  } | {
51
58
  readonly kind: "unprefixed_namespace";
59
+ } | {
60
+ readonly kind: "unmintable_segment";
61
+ readonly guidance: string;
62
+ readonly currentName?: string;
52
63
  };
53
- /** See {@link RuleToolNameVerdict}. Pure; the one answer to "is this rule's tool name a retired or prefix-less name". */
64
+ /**
65
+ * A name in ANY protocol namespace split into its owning namespace, its peer segment and its optional tool segment —
66
+ * CC `rP` (:70710) verbatim, re-anchored on the {@link protocolOf} lookup instead of CC's literal `"mcp"` first split element.
67
+ * `undefined` for a name that is not namespaced (no protocol prefix). The peer is the first segment — EMPTY for `mcp____x` and
68
+ * for the bare prefix, which name no peer; the tool segment is the REST of the split rejoined, so a tool whose minted segment
69
+ * itself carries the separator (charset normalization maps `get..issue` → `get__issue`) decomposes back to one tool name, and
70
+ * is absent when the name stops at the peer (`mcp__srv`). The one decomposition: the judgment, the covering matcher and the
71
+ * allow-entry grammar all read a namespaced name through it.
72
+ */
73
+ export declare function splitNamespacedName(name: string): {
74
+ readonly ns: ProtocolNamespace;
75
+ readonly peer: string;
76
+ readonly tool?: string;
77
+ } | undefined;
78
+ /** See {@link RuleToolNameVerdict}. Pure; the one answer to "is this rule's tool name a name that reaches no tool". */
54
79
  export declare function judgeRuleToolName(name: string, roster: ReadonlySet<string>): RuleToolNameVerdict;
80
+ /** The names a run mounts — every tool's primary name and its aliases: the roster {@link judgeRuleToolName} is given. */
81
+ export declare function mountedToolNames(tools: ReadonlyArray<{
82
+ readonly name: string;
83
+ readonly aliases?: readonly string[];
84
+ }>): Set<string>;
@@ -1,4 +1,5 @@
1
1
  import { carriesNamespaceSeparator, protocolOf } from "./protocol-table.js";
2
+ import { mintNamespacePrefix } from "./protocol-naming.js";
2
3
  export const RETIRED_TOOL_NAMES = new Map([
3
4
  ["read_file", 'renamed "Read"'],
4
5
  ["edit_file", 'renamed "Edit"'],
@@ -41,9 +42,29 @@ export const RETIRED_TOOL_NAMES = new Map([
41
42
  ["Recall", "removed — memory is file-based; read the memory directory with the ordinary file tools"],
42
43
  ["recall", "removed — memory is file-based; read the memory directory with the ordinary file tools"],
43
44
  ]);
44
- export const RULE_TOOL_NAME_VERDICTS = Object.freeze(["current", "retired", "unprefixed_namespace"]);
45
+ export const RULE_TOOL_NAME_VERDICTS = Object.freeze(["current", "retired", "unprefixed_namespace", "unmintable_segment"]);
45
46
  const CURRENT = Object.freeze({ kind: "current" });
46
47
  const UNPREFIXED = Object.freeze({ kind: "unprefixed_namespace" });
48
+ const NAMESPACE_SEPARATOR = "__";
49
+ export function splitNamespacedName(name) {
50
+ const ns = protocolOf(name);
51
+ if (ns === undefined)
52
+ return undefined;
53
+ const [peer = "", ...rest] = name.slice(ns.prefix.length).split(NAMESPACE_SEPARATOR);
54
+ return rest.length > 0 ? { ns, peer, tool: rest.join(NAMESPACE_SEPARATOR) } : { ns, peer };
55
+ }
56
+ function unmintableSegmentOf({ ns, peer, tool }) {
57
+ if (peer === "")
58
+ return { kind: "unmintable_segment", guidance: `the ${ns.id} name has an empty peer segment — no tool is minted under it` };
59
+ const minted = mintNamespacePrefix(ns, peer).slice(ns.prefix.length, -NAMESPACE_SEPARATOR.length);
60
+ if (minted === peer)
61
+ return undefined;
62
+ return {
63
+ kind: "unmintable_segment",
64
+ guidance: `the ${ns.id} peer segment ${JSON.stringify(peer)} is not a spelling the engine mints — its tools are named ${ns.prefix}${minted}${NAMESPACE_SEPARATOR}<tool>, and a rule's peer segment must be spelled the way its tools are`,
65
+ currentName: `${ns.prefix}${minted}${tool !== undefined ? `${NAMESPACE_SEPARATOR}${tool}` : ""}`,
66
+ };
67
+ }
47
68
  export function judgeRuleToolName(name, roster) {
48
69
  if (roster.has(name))
49
70
  return CURRENT;
@@ -52,7 +73,11 @@ export function judgeRuleToolName(name, roster) {
52
73
  const currentName = /^renamed "([^"]+)"/.exec(guidance)?.[1];
53
74
  return currentName !== undefined ? { kind: "retired", guidance, currentName } : { kind: "retired", guidance };
54
75
  }
55
- if (carriesNamespaceSeparator(name) && protocolOf(name) === undefined)
56
- return UNPREFIXED;
57
- return CURRENT;
76
+ const split = splitNamespacedName(name);
77
+ if (split === undefined)
78
+ return carriesNamespaceSeparator(name) ? UNPREFIXED : CURRENT;
79
+ return unmintableSegmentOf(split) ?? CURRENT;
80
+ }
81
+ export function mountedToolNames(tools) {
82
+ return new Set(tools.flatMap((t) => [t.name, ...(t.aliases ?? [])]));
58
83
  }
@@ -303,6 +303,9 @@ export type PermissionResult = {
303
303
  * same safe direction as `matchedAskRule`); it cannot state a window of its own, because the
304
304
  * route it would be a window for has not been chosen yet. */
305
305
  denialLimitFallback?: import("./auto-mode.js").UnarmedDenialLimitFallback;
306
+ /** WHY a classifier station handed this call to a person: the classification did not rule (`error` / `timeout` — it could
307
+ * not run; `parse_error` — no verdict). Engine-stamped with `requiresRealApproval`; class `classifier_no_ruling`. */
308
+ classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
306
309
  /** The allow layer's DISCLOSURE: the members that MATCHED this call and could not clear the ask (it is a class
307
310
  * no allow rule clears — a mandated, governance, hook, ask-rule or mode question — or the call reached the layer
308
311
  * marked), one record per member ({@link AllowRuleShadow}). ENGINE-STAMPED by the allow layer, overwriting a
@@ -1201,6 +1204,10 @@ export interface AskRequest {
1201
1204
  * mint site added later is covered by construction); optional on the type because a deployment may
1202
1205
  * invoke its approver function directly. */
1203
1206
  readonly boundInputHash?: string;
1207
+ /** A call to the engine's `Skill` tool: the hash of {@link boundGrants} — the digest a parked approval of the same card is judged by, so a live card reconciles against its parked row by `(toolCallId, boundGrantDigest)` and an answer is remembered by "this tool + this binding". Present exactly on that call (whoever asked, however the question was worded), absent on every other card; `message` renders the same lists narrowed to what releases in the leg's mode. Reconciliation metadata, never adjudication input. */
1208
+ readonly boundGrantDigest?: string;
1209
+ /** The value {@link boundGrantDigest} hashes: `{ skill, allowedTools, disallowedTools }` in canonical spelling (de-duplicated, sorted), `skill: null` when no loaded skill is named; a fresh copy per ask; same presence. Display metadata (deployment text — render with contextual escaping). */
1210
+ readonly boundGrants?: import("./skill-grant-binding.js").BoundSkillGrants;
1204
1211
  /**
1205
1212
  * design/179 §4, re-formed by design/375 §3.1 (BREAKING — this seat REPLACES the retired
1206
1213
  * `ruleSuggestions` key, which is never emitted again; the new name is the safe failure posture for
@@ -1396,6 +1403,9 @@ export interface AskRequest {
1396
1403
  * that forwards a decision's member here does not compile, which is the point: the window is the
1397
1404
  * route's fact, and forwarding it silently is how a configured window becomes a wait with none. */
1398
1405
  readonly denialLimitFallback?: import("./auto-mode.js").DenialLimitFallback;
1406
+ /** The {@link PermissionResult} ask-arm member of the same name (class `classifier_no_ruling`), carried by the four mint
1407
+ * stations; the `message`'s second paragraph says the same for a client that does not read this. */
1408
+ readonly classifierNoRuling?: import("./auto-mode.js").AutoModeFallbackCause;
1399
1409
  /** (additive) WHICH AUTHORITY raised this ask — the wire twin of the {@link PermissionResult} ask-arm
1400
1410
  * member of the same name, one word from the closed {@link import("./ask-origin.js").AskOrigin} set.
1401
1411
  * The gate's own mint station copies the engine-stamped word; the three inherited-lane stations
@@ -259,7 +259,7 @@ const ALLOW = Object.freeze({ action: "allow" });
259
259
  const ALLOW_BY_RULE = Object.freeze({ action: "allow", decisionReason: "rule" });
260
260
  const RETIRED_TEXT_FIELD = "reason";
261
261
  export const DECISION_MEMBERS = Object.keys({
262
- action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, allowRuleShadowed: 0, probeReason: 0,
262
+ action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, classifierNoRuling: 0, allowRuleShadowed: 0, probeReason: 0,
263
263
  probeCause: 0, probeMandated: 0, mandated: 0, mandate: 0, probeReadRootCandidate: 0, matchedAskRule: 0, ruleEvidence: 0, segmentCoverage: 0,
264
264
  origin: 0, askClass: 0, classes: 0, guard: 0, ruleUnreadable: 0, authoredBy: 0, foldedAskClasses: 0,
265
265
  });
@@ -551,6 +551,7 @@ export function combinePolicies(...policies) {
551
551
  let probeMandateSeen = false;
552
552
  let mandateSeen = false;
553
553
  let fallbackSeen;
554
+ let noRulingSeen;
554
555
  let authoredBySeen = false;
555
556
  const contributors = [];
556
557
  const settleDeny = (d) => {
@@ -597,6 +598,8 @@ export function combinePolicies(...policies) {
597
598
  mandateSeen = true;
598
599
  if (d.action === "ask" && d.denialLimitFallback !== undefined && fallbackSeen === undefined)
599
600
  fallbackSeen = d.denialLimitFallback;
601
+ if (d.action === "ask" && d.classifierNoRuling !== undefined && noRulingSeen === undefined)
602
+ noRulingSeen = d.classifierNoRuling;
600
603
  if (d.action === "ask" && d.authoredBy === "deployment")
601
604
  authoredBySeen = true;
602
605
  if (d.action === "ask")
@@ -610,12 +613,16 @@ export function combinePolicies(...policies) {
610
613
  const withMark = probeMandateSeen && withRuleAsk.probeMandated !== true ? { ...withRuleAsk, probeMandated: true } : withRuleAsk;
611
614
  const withMandate = mandateSeen && withMark.mandated !== true ? { ...withMark, mandated: true } : withMark;
612
615
  const withFallback = fallbackSeen !== undefined && withMandate.denialLimitFallback === undefined ? { ...withMandate, denialLimitFallback: fallbackSeen, requiresRealApproval: true } : withMandate;
613
- const withAuthor = authoredBySeen && withFallback.authoredBy === undefined ? { ...withFallback, authoredBy: "deployment" } : withFallback;
616
+ const withNoRuling = noRulingSeen !== undefined && withFallback.classifierNoRuling === undefined ? { ...withFallback, classifierNoRuling: noRulingSeen, requiresRealApproval: true } : withFallback;
617
+ const withAuthor = authoredBySeen && withNoRuling.authoredBy === undefined ? { ...withNoRuling, authoredBy: "deployment" } : withNoRuling;
614
618
  const folded = new Set(withAuthor.foldedAskClasses ?? []);
615
619
  for (const c of contributors)
616
620
  if (c !== asked)
617
621
  for (const k of decisionAskClassesOf(c))
618
622
  folded.add(k);
623
+ if (withNoRuling !== withMandate)
624
+ for (const k of decisionAskClassesOf(asked))
625
+ folded.add(k);
619
626
  const withClasses = folded.size > 0 ? { ...withAuthor, foldedAskClasses: [...folded] } : withAuthor;
620
627
  const out = merged !== undefined ? { ...withClasses, updatedInput: merged } : withClasses;
621
628
  if (folded.size > 0)
@@ -952,6 +952,13 @@ export interface ToolExecuteContext {
952
952
  * is deliberately NOT inherited).
953
953
  */
954
954
  interactiveTools?: false;
955
+ /**
956
+ * The run's declaration that nobody on it can be asked to approve ({@link TaskSpec.approvalReach} `"none"`),
957
+ * Runner-filled read-only on the same trusted seat, present ONLY when declared. A delegation tool copies it onto every
958
+ * child spec (the clamp carrier): the child's own seat resolution would otherwise fall back to the deployment's live
959
+ * approver, because a declared-none parent hands down no approver of its own ({@link onAsk} is absent on its ctx).
960
+ */
961
+ approvalReach?: "none";
955
962
  /**
956
963
  * Read-only mirror of {@link "./types.js".TaskSpec.oneShot} (same trust tier/seat as
957
964
  * `principal`): this RUN has no later turn for an async background notification to land in (a
@@ -29,6 +29,6 @@ export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js"
29
29
  export type { BackgroundChildEvent, DelegationLifecycleEvent } from "./delegation-frames.js";
30
30
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
31
31
  export type { ProjectMemoryLoad, ResumePreflightInfo, ResumePreflightVerdict, RunnerDeps } from "./runner-deps.js";
32
- export type { EngineNotice, OwedUserInputAccount, UserSteerUndrainedCause } from "./engine-notice.js";
33
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
32
+ export type { EngineNotice, OwedUserInputAccount, UserInputUndrainedCause } from "./engine-notice.js";
33
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES } from "./engine-notice.js";
34
34
  export type { AgentTool, Model, StreamFn, ThinkingLevel, ToolInputValidationContext, ToolInputVerdict };
@@ -1,4 +1,4 @@
1
1
  export { MODEL_ROLES } from "./model-seat.js";
2
2
  export { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
3
3
  export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
4
- export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_STEER_UNDRAINED_CAUSES } from "./engine-notice.js";
4
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, owedUserInputCounts, parentLegIds, settleOwedUserInput, undrainedUserInputNotices, USER_INPUT_UNDRAINED_CAUSES } from "./engine-notice.js";
@@ -26,6 +26,7 @@ import { type StoreDurability } from "./checkpoint-store.js";
26
26
  import type { WiringManifestHookEntry } from "./hook-seats.js";
27
27
  import type { McpDelivered, McpFailureKind, McpServerLiveness } from "./mcp-failure.js";
28
28
  import type { WriteProtectionTargetView } from "./write-protect.js";
29
+ import type { OnAsk } from "./tool-policy.js";
29
30
  export type { StoreDurability };
30
31
  /** The manifest-face projection of {@link StoreDurability}: `"declared_durable"` (the name says it —
31
32
  * a DECLARATION relayed, not a verification) vs `"process_local"` (declared so, or fail-closed
@@ -37,11 +38,29 @@ export type ManifestDurability = "declared_durable" | "process_local";
37
38
  * child's manifest rides the CHILD's event stream and is deliberately not in the parent-forwarding
38
39
  * whitelist — a host that wants a child's assembly evidence subscribes to the child. */
39
40
  export type WiringLegKind = "root" | "child" | "resume";
40
- /** The shape of the resolved permission-ask seat (`spec.onAsk ?? deps.onAsk`): a live approver
41
- * callback, a blanket `"allow"`/`"deny"` policy setting, or nothing at all. `"deny"` (a deliberate
42
- * policy) and `"absent"` (a degrade to the headless auto-deny default) are DISTINCT on purpose —
43
- * design/173 */
44
- export type AskSeamForm = "callback" | "allow" | "deny" | "absent";
41
+ /** The shape of the resolved permission-ask seat ({@link resolveAskSeat}): a live approver callback, a blanket
42
+ * `"allow"`/`"deny"` policy setting, nothing at all, or `"declared_none"` — the run declared that nobody on it can be
43
+ * asked (`TaskSpec.approvalReach: "none"`), so a callback beside the declaration is not this run's approver. `"deny"` (a
44
+ * deliberate policy) and `"absent"` (no seat supplied a value — a degrade to the headless auto-deny default) are DISTINCT
45
+ * on purpose — design/173 — and so are `"absent"` and `"declared_none"`: one says nothing was wired, the other that
46
+ * the run said nobody can answer. */
47
+ export type AskSeamForm = "callback" | "allow" | "deny" | "absent" | "declared_none";
48
+ /**
49
+ * The closed set of a run's approval-reach declaration (`TaskSpec.approvalReach`): `"none"` — nobody on this run can be
50
+ * asked to approve. It removes the one seat that could be a person (a callback); it never approves or releases anything.
51
+ * There is no positive word: "a person is reachable" is the `interactionPosture: "interactive"` promise, which has its own
52
+ * door.
53
+ */
54
+ export declare const APPROVAL_REACH: readonly ["none"];
55
+ export type ApprovalReach = (typeof APPROVAL_REACH)[number];
56
+ /** Membership test for {@link ApprovalReach}. */
57
+ export declare function isApprovalReach(v: unknown): v is ApprovalReach;
58
+ /**
59
+ * The declaration's door: absent passes as absent, a member passes, anything else present is refused loudly
60
+ * (`config.approval_reach_invalid`) — a declaration nobody can read must not fold to "a person may be asked" (or to
61
+ * "nobody may"). Prepare, the static half and a resume's pre-claim screen call this one function.
62
+ */
63
+ export declare function screenApprovalReach(v: unknown): ApprovalReach | undefined;
45
64
  /** What a policy `ask` actually reaches under this assembly. `"unresolved"` = not computable from
46
65
  * the facts at hand (the static half's park-dependent arm). */
47
66
  export type AskEffective = "human_reachable" | "auto_allow" | "auto_deny" | "park_only" | "unresolved";
@@ -178,8 +197,8 @@ export interface WiringManifest {
178
197
  peerPresence?: import("../agents/peer-directory.js").PeerPresence;
179
198
  ask: {
180
199
  form: AskSeamForm;
181
- /** Absent iff `form === "absent"` (no seat supplied a value — a fabricated provenance would be
182
- * a false fact). */
200
+ /** Absent iff no seat supplied a value (`"absent"`, or `"declared_none"` with nothing wired) — a fabricated provenance
201
+ * would be a false fact. */
183
202
  provenance?: SeamProvenance;
184
203
  /** OMITTED on the static half (design/173 §8.4): the absent-form arm depends on the park lane,
185
204
  * which the static half cannot always resolve. Always present on the effective half. */
@@ -556,7 +575,7 @@ export interface WiringFacts {
556
575
  * single-source shapes, no parallel hand-copied interface). */
557
576
  export type StaticWiringDeps = Pick<RunnerDeps, "onAsk" | "onQuestion" | "interactionPosture" | "onElicit" | "checkpointStore" | "sessionStore" | "backgroundAgentStore" | "onBackgroundChildEvent" | "lockedConfig" | "compliancePostureResolver" | "memoryScopeAdmission" | "retentionPolicy" | "permissionRuleStore" | "permissionRuleSyncWired">;
558
577
  /** Named view of the spec seats the static half reads (a `Pick` of the real {@link TaskSpec}). */
559
- export type StaticWiringSpec = Pick<TaskSpec, "onAsk" | "onQuestion" | "checkpointStore" | "durableApproval" | "mcp" | "interactiveTools" | "interactionPosture">;
578
+ export type StaticWiringSpec = Pick<TaskSpec, "onAsk" | "approvalReach" | "onQuestion" | "checkpointStore" | "durableApproval" | "mcp" | "interactiveTools" | "interactionPosture">;
560
579
  /**
561
580
  * Read a store's {@link StoreDurability} declaration: absent ⇒ `"process-local"` (fail-closed —
562
581
  * under-promise, never over-promise), a declared value passes through, and an UNPARSEABLE value is
@@ -613,8 +632,9 @@ export declare function resolveSubagentTranscriptTier(agentStoreWired: boolean,
613
632
  * so on an armed park lane the ask parks and the honest report is `park_only` — reporting the
614
633
  * blanket as what the ask "reaches" would be a false fact of exactly the kind this module refuses.
615
634
  * Off the park lane the blanket is what happens: the deliberate auto policies;
616
- * - no seat at all ⇒ the park lane decides: armed ⇒ `park_only`, unresolved ⇒ `unresolved`,
617
- * else the headless degrade `auto_deny`.
635
+ * - no seat at all, or a run that declared nobody can be asked (`declared_none`) ⇒ the park lane decides: armed ⇒
636
+ * `park_only` (the deployment's park is its own statement that an answer can wait for a person), unresolved ⇒
637
+ * `unresolved`, else the headless degrade `auto_deny`.
618
638
  */
619
639
  export declare function deriveAskEffective(form: AskSeamForm, parkEffective: boolean | "unresolved", mode?: PermissionMode): AskEffective;
620
640
  /**
@@ -637,10 +657,32 @@ export declare function askReachesPerson(ask: {
637
657
  readonly effective?: AskEffective;
638
658
  }): boolean;
639
659
  export declare function deriveWiringManifest(facts: WiringFacts): WiringManifest;
640
- /** The one ask-seat resolution (`spec` wins over `deps`), shared with the effective half so the
641
- * form/provenance read cannot fork. Junk values (a JS caller passing something outside the OnAsk
642
- * union) are refused loudly — a manifest must not classify what it cannot name. */
643
- export declare function resolveAskSeamForm(spec: Pick<TaskSpec, "onAsk">, deps: Pick<RunnerDeps, "onAsk">): {
660
+ /** A run's approval seat as its one resolution froze it: the value every reader that needs the approver itself reads
661
+ * (`undefined` when nothing on this run can be asked), the form the manifest reports and every reader of the word derives
662
+ * from, the seat that supplied the value, and the declaration when the run made one. */
663
+ export interface AskSeat {
664
+ readonly value: OnAsk | undefined;
665
+ readonly form: AskSeamForm;
666
+ /** Absent iff no seat supplied a value. A callback the declaration set aside still names its seat. */
667
+ readonly provenance?: SeamProvenance;
668
+ readonly reach?: ApprovalReach;
669
+ }
670
+ /**
671
+ * THE approval-seat resolution — the one place a run's `spec.onAsk` / `deps.onAsk` are read. Prepare calls it once, at
672
+ * its config doors (before any resource is taken), and hands the frozen result down; the static half calls it for its
673
+ * own self-check. The seat is the spec's when the spec's key is PRESENT, else the deployment's (presence, not `??`: a
674
+ * spec `null` is a junk value, never a fall-through to the deployment). A value outside the `OnAsk` union is refused
675
+ * loudly (`config.interaction_wiring`), a declaration outside {@link APPROVAL_REACH} too (`config.approval_reach_invalid`).
676
+ *
677
+ * `approvalReach: "none"` is one sentence here: a blanket `"allow"` / `"deny"` stays what it is (a policy setting never
678
+ * claimed to be a person); a callback, or no seat, becomes `declared_none` with NO value — so the readers of the value
679
+ * (the approver lane, the park's sync-first decline, the durable-mandate source, a child's inherited seat) and the readers
680
+ * of the word ({@link deriveAskEffective}) cannot disagree about whether a person is there.
681
+ */
682
+ export declare function resolveAskSeat(spec: Pick<TaskSpec, "onAsk" | "approvalReach">, deps: Pick<RunnerDeps, "onAsk">): AskSeat;
683
+ /** The form and provenance half of {@link resolveAskSeat} — the projection the manifest's two halves and a server's
684
+ * per-leg judgment read (hand it the request's `approvalReach` beside the seat). */
685
+ export declare function resolveAskSeamForm(spec: Pick<TaskSpec, "onAsk" | "approvalReach">, deps: Pick<RunnerDeps, "onAsk">): {
644
686
  form: AskSeamForm;
645
687
  provenance?: SeamProvenance;
646
688
  };
@@ -5,6 +5,22 @@ import { canonicalize } from "./canonical-json.js";
5
5
  import { resolveCheckpointStore } from "./checkpoint-store.js";
6
6
  import { isLiveQuestionFace } from "./ask-question.js";
7
7
  import { AUTO_MODE_DENY_SOURCES, isAutoModeDenySource } from "./runtime-caps.js";
8
+ import { describeRejectedValue } from "./tool-search-setting.js";
9
+ export const APPROVAL_REACH = Object.freeze(["none"]);
10
+ const APPROVAL_REACH_SET = new Set(APPROVAL_REACH);
11
+ export function isApprovalReach(v) {
12
+ return APPROVAL_REACH_SET.has(v);
13
+ }
14
+ export function screenApprovalReach(v) {
15
+ if (v === undefined)
16
+ return undefined;
17
+ if (isApprovalReach(v))
18
+ return v;
19
+ const e = new Error(`approvalReach ${describeRejectedValue(v)} is not a recognized declaration (${APPROVAL_REACH.map((w) => JSON.stringify(w)).join(" | ")}) — ` +
20
+ `omit it when a person may be asked; an unreadable declaration is refused, never folded to either reading.`);
21
+ e.code = "config.approval_reach_invalid";
22
+ throw e;
23
+ }
8
24
  export const AUTO_MODE_ARM_REASONS = Object.freeze(["armed", "no_intent", "no_face", "denied", "resolver_fault"]);
9
25
  export const LSP_LANES = Object.freeze(["tool", "diagnostics", "tool_and_diagnostics"]);
10
26
  export function deriveHandsSeam(handsMounted) {
@@ -53,6 +69,7 @@ export function deriveAskEffective(form, parkEffective, mode) {
53
69
  case "deny":
54
70
  return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_deny";
55
71
  case "absent":
72
+ case "declared_none":
56
73
  return parkEffective === true ? "park_only" : parkEffective === "unresolved" ? "unresolved" : "auto_deny";
57
74
  default: {
58
75
  const _exhaustive = form;
@@ -211,10 +228,12 @@ export function deriveWiringManifest(facts) {
211
228
  }
212
229
  return manifest;
213
230
  }
214
- export function resolveAskSeamForm(spec, deps) {
215
- const seat = spec.onAsk !== undefined ? { value: spec.onAsk, provenance: "spec" } : deps.onAsk !== undefined ? { value: deps.onAsk, provenance: "deps" } : undefined;
231
+ export function resolveAskSeat(spec, deps) {
232
+ const specValue = spec.onAsk;
233
+ const seat = specValue !== undefined ? { value: specValue, provenance: "spec" } : ((v) => (v !== undefined ? { value: v, provenance: "deps" } : undefined))(deps.onAsk);
234
+ const reach = screenApprovalReach(spec.approvalReach);
216
235
  if (seat === undefined)
217
- return { form: "absent" };
236
+ return Object.freeze(reach === undefined ? { value: undefined, form: "absent" } : { value: undefined, form: "declared_none", reach });
218
237
  const v = seat.value;
219
238
  const form = typeof v === "function" ? "callback" : v === "allow" ? "allow" : v === "deny" ? "deny" : undefined;
220
239
  if (form === undefined) {
@@ -223,7 +242,13 @@ export function resolveAskSeamForm(spec, deps) {
223
242
  e.code = "config.interaction_wiring";
224
243
  throw e;
225
244
  }
226
- return { form, provenance: seat.provenance };
245
+ if (reach !== undefined && form === "callback")
246
+ return Object.freeze({ value: undefined, form: "declared_none", provenance: seat.provenance, reach });
247
+ return Object.freeze({ value: v, form, provenance: seat.provenance, ...(reach !== undefined ? { reach } : {}) });
248
+ }
249
+ export function resolveAskSeamForm(spec, deps) {
250
+ const seat = resolveAskSeat(spec, deps);
251
+ return { form: seat.form, ...(seat.provenance !== undefined ? { provenance: seat.provenance } : {}) };
227
252
  }
228
253
  export function resolveQuestionSeam(spec, deps) {
229
254
  const seat = spec.onQuestion !== undefined ? { value: spec.onQuestion, provenance: "spec" } : deps.onQuestion !== undefined ? { value: deps.onQuestion, provenance: "deps" } : undefined;
package/dist/index.d.ts CHANGED
@@ -42,7 +42,7 @@ export { StdioLspTransport, type LspChildProcess } from "./engine/lsp/stdio-lsp-
42
42
  export { encodeFrame, createFrameDecoder } from "./engine/lsp/frame-decoder.js";
43
43
  export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, MODEL_ROLES, type ModelRole, type RoleSpec, type ModelRoles, type ResolvedRole, type ModelMention, } from "./core/roles.js";
44
44
  export { selectModel, selectModelOrThrow, type ModelCriteria, type ModelTier } from "./core/select-model.js";
45
- export { BrainError, classifyHttp, isProviderEchoShape, PROVIDER_ECHO_SHAPE, type BrainErrorCode } from "./brain/errors.js";
45
+ export { BrainError, classifyHttp, isProviderEchoShape, PROVIDER_ECHO_SHAPE, PROXY_TUNNEL_REFUSED, type BrainErrorCode } from "./brain/errors.js";
46
46
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
47
47
  export type { RepetitionEvent, RepetitionInspection } from "./brain/repetition.js";
48
48
  export { computeCostMicroUsd, isModelPriced, modelCostToPricing, type ModelPricing, type TokenCounts, } from "./core/pricing.js";
@@ -63,7 +63,8 @@ export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, classify
63
63
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice, type McpInjectionDropReason, type McpInjectionDropFacts } from "./core/mcp-injection-drop.js";
64
64
  export { askUnresolvableNotice, type AskUnresolvableFacts } from "./core/ask-unresolvable-notice.js";
65
65
  export { ASK_UNRESOLVABLE_CAUSES } from "./core/ask-unresolvable-notice.js";
66
- export { USER_STEER_UNDRAINED_CAUSES, type UserSteerUndrainedCause } from "./core/engine-notice.js";
66
+ export { USER_INPUT_UNDRAINED_CAUSES, type UserInputUndrainedCause } from "./core/engine-notice.js";
67
+ export { STEER_REPLAY_FACETS, type SteerReplayFacet } from "./core/steer-replay.js";
67
68
  export { TOOL_TURN_INCOMPLETE_CAUSES, type ToolTurnIncompleteCause } from "./internal/llm.js";
68
69
  export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
69
70
  export { TERMINAL_CAUSE_IS_REPLAYABLE, type TerminalFailureDetail, type TerminalBlockedDetail, PROMPT_BLOCK_CAUSES, type PromptBlockCause, isPromptBlockCause } from "./core/terminal-cause.js";
@@ -162,7 +163,7 @@ export { parkedToolIdentity } from "./core/tool-roster.js";
162
163
  export { type ToolCallIdentity } from "./core/fs-write-gate-policy.js";
163
164
  export { type RuleFaceView, catalogRuleFaceView } from "./core/permission-rules.js";
164
165
  export { DURABLE_HISTORY_NAMES } from "./engine/compaction/utils.js";
165
- export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, type SubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, AUTO_MODE_ARM_REASONS, type AutoModeArmReason, type AutoModeArmFact, type WiringManifest, type WiringManifestMcpEntry, LSP_LANES, type LspLane, type WiringFacts, type WiringLegKind, type AskSeamForm, type AskEffective, type QuestionChannelState, type SeamProvenance, type ParkLaneReason, type ManifestDurability, type StaticWiringDeps, type StaticWiringSpec, } from "./core/wiring-manifest.js";
166
+ export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, type SubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, AUTO_MODE_ARM_REASONS, type AutoModeArmReason, type AutoModeArmFact, type WiringManifest, type WiringManifestMcpEntry, LSP_LANES, type LspLane, type WiringFacts, type WiringLegKind, type AskSeamForm, APPROVAL_REACH, type ApprovalReach, isApprovalReach, type AskEffective, type QuestionChannelState, type SeamProvenance, type ParkLaneReason, type ManifestDurability, type StaticWiringDeps, type StaticWiringSpec, } from "./core/wiring-manifest.js";
166
167
  export { probeParkRoundTrip, durableParkGapOf, durableParkGapFor, PARK_SELFCHECK_SCOPE_PREFIX, type ParkSelfCheckResult, type ParkProbeFinding, type ParkProbeFindingCode, } from "./core/park-selfcheck.js";
167
168
  export { type StoreDurability } from "./core/checkpoint-store.js";
168
169
  export { type StoreFidelity } from "./core/checkpoint-store.js";
@@ -192,7 +193,7 @@ export { createAllowDenyPolicy, createApprovalPolicy,
192
193
  * members that seat has no lane for (`"unavailable"`, a routing answer; `updatedInput`, an operator edit a
193
194
  * policy verdict cannot get re-adjudicated). Exported so a host can NAME it instead of deriving it through
194
195
  * `Parameters<typeof createApprovalPolicy>[0]["approve"]`. */
195
- type ApprovalSeatOutcome, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, receiveHookResult, isEngineRefusal, 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";
196
+ type ApprovalSeatOutcome, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, 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";
196
197
  export { type ReadRootGrantScreen } from "./core/read-root-grant-screen.js";
197
198
  export { type AskOrigin, ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, type PersistedRuleMandate, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
198
199
  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";
@@ -200,6 +201,7 @@ export { modePolicyOf, type ModePolicy, PERMISSION_MODE_FOLD_REASONS, type Permi
200
201
  export type { PermissionSeatInputs, PermissionSeatReading } from "./core/mode-policy.js";
201
202
  export type { SkillGateSeats, SkillGrantHit, SkillGrantLane, SkillGrantOrigin, SkillGrantRecord } from "./core/skill-grant.js";
202
203
  export { SKILL_GRANT_ORIGINS } from "./core/skill-grant.js";
204
+ export type { BoundSkillGrants } from "./core/skill-grant-binding.js";
203
205
  export { ALLOW_ENTRY_INERT_REASONS, compileAllowEntry, compileAllowList, type AllowEntryInertReason, type CompiledAllowEntry, type AllowEntryGateSeats, type AllowEntryLane, type AllowEntryHit } from "./core/allow-entry.js";
204
206
  export { ALLOW_RULE_MEMBERS, type AllowRuleMember, type AllowRuleShadow } from "./core/allow-rule-shadow.js";
205
207
  export { mintNamespacePrefix } from "./core/protocol-naming.js";
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ export { StdioLspTransport } from "./engine/lsp/stdio-lsp-transport.js";
29
29
  export { encodeFrame, createFrameDecoder } from "./engine/lsp/frame-decoder.js";
30
30
  export { resolveModel, resolveTaskModel, parseModelMention, expandTiers, DEFAULT_TIER_ORDER, CC_MODEL_TIER_ALIASES, ROLE_TIER_DEFAULTS, MODEL_ROLES, } from "./core/roles.js";
31
31
  export { selectModel, selectModelOrThrow } from "./core/select-model.js";
32
- export { BrainError, classifyHttp, isProviderEchoShape, PROVIDER_ECHO_SHAPE } from "./brain/errors.js";
32
+ export { BrainError, classifyHttp, isProviderEchoShape, PROVIDER_ECHO_SHAPE, PROXY_TUNNEL_REFUSED } from "./brain/errors.js";
33
33
  export { looksDegenerate, inspectDegenerate, trimDegenerateTail } from "./brain/repetition.js";
34
34
  export { computeCostMicroUsd, isModelPriced, modelCostToPricing, } from "./core/pricing.js";
35
35
  export { cacheFamilyOf, promptTokensOf, uncachedInputTokensOf } from "./core/runner/usage-accounting.js";
@@ -49,7 +49,8 @@ export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, MCP_LIVENESS_STATES, classify
49
49
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice } from "./core/mcp-injection-drop.js";
50
50
  export { askUnresolvableNotice } from "./core/ask-unresolvable-notice.js";
51
51
  export { ASK_UNRESOLVABLE_CAUSES } from "./core/ask-unresolvable-notice.js";
52
- export { USER_STEER_UNDRAINED_CAUSES } from "./core/engine-notice.js";
52
+ export { USER_INPUT_UNDRAINED_CAUSES } from "./core/engine-notice.js";
53
+ export { STEER_REPLAY_FACETS } from "./core/steer-replay.js";
53
54
  export { TOOL_TURN_INCOMPLETE_CAUSES } from "./internal/llm.js";
54
55
  export { DURABLE_MANDATE_SOURCES } from "./core/tool-policy.js";
55
56
  export { TERMINAL_CAUSE_IS_REPLAYABLE, PROMPT_BLOCK_CAUSES, isPromptBlockCause } from "./core/terminal-cause.js";
@@ -134,7 +135,7 @@ export { parkedToolIdentity } from "./core/tool-roster.js";
134
135
  export {} from "./core/fs-write-gate-policy.js";
135
136
  export { catalogRuleFaceView } from "./core/permission-rules.js";
136
137
  export { DURABLE_HISTORY_NAMES } from "./engine/compaction/utils.js";
137
- export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, AUTO_MODE_ARM_REASONS, LSP_LANES, } from "./core/wiring-manifest.js";
138
+ export { describeStaticWiring, deriveWiringManifest, deriveAskEffective, resolveDeclaredDurability, resolveSubagentTranscriptTier, resolveAskSeamForm, resolveQuestionSeam, countElicitOptIns, AUTO_MODE_ARM_REASONS, LSP_LANES, APPROVAL_REACH, isApprovalReach, } from "./core/wiring-manifest.js";
138
139
  export { probeParkRoundTrip, durableParkGapOf, durableParkGapFor, PARK_SELFCHECK_SCOPE_PREFIX, } from "./core/park-selfcheck.js";
139
140
  export {} from "./core/checkpoint-store.js";
140
141
  export {} from "./core/checkpoint-store.js";
@@ -157,7 +158,7 @@ export { hasScheduler, isValidCronExpr, SchedulerError } from "./core/scheduler.
157
158
  export { createSchedulerTools, SCHEDULE_WAKEUP_TOOL_NAME, AUTONOMOUS_LOOP_SENTINEL, AUTONOMOUS_LOOP_DYNAMIC_SENTINEL, } from "./tools/scheduler-tools.js";
158
159
  export { resolveAutonomousLoopPrompt, AUTONOMOUS_LOOP_PREAMBLE, AUTONOMOUS_LOOP_PREAMBLE_PERSISTENT, } from "./tools/loop-tick.js";
159
160
  export { tightenTaskSpec, TaskSpecTightenError } from "./core/tighten-task-spec.js";
160
- export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
161
+ export { createAllowDenyPolicy, createApprovalPolicy, COARSE_SHELL_TOOLS, createCoarseCommandNamePolicy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, findUnverifiableRecursiveDelete, combinePolicies, abortedSeatDeny, receiveHookResult, isEngineRefusal, wrapToolPolicy, decisionText, resolveAsk, toolPolicyNameSets, toolPolicyPathDenyRules, checkToolPolicyProjection, constraintChainEntryOf, constraintChainDigest, screenApproverAttribution, APPROVER_ATTRIBUTION_MAX_CHARS, ASK_EVIDENCE_ABSENCE_VALUES, } from "./core/tool-policy.js";
161
162
  export {} from "./core/read-root-grant-screen.js";
162
163
  export { ASK_ORIGINS, isAskOrigin, ORIGIN_IMPLIES_REAL_APPROVAL, PERSISTED_RULE_MANDATES, isPersistedRuleMandate, screenAskClassMembers } from "./core/ask-origin.js";
163
164
  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";
@@ -293,6 +293,10 @@ export interface RunWorkflowToolDeps {
293
293
  * `ToolExecuteContext.interactiveTools` seat — without it a headless-clamped host's workflow
294
294
  * children could re-mount the interactive tool the host run disabled. */
295
295
  parentInteractiveTools?: false;
296
+ /** The HOST task's declaration that nobody on it can be asked to approve (`TaskSpec.approvalReach: "none"`), same
297
+ * carriage rules as the two clamps above: only `"none"` travels, and the auto-mount's minimal ctx makes this dep its one
298
+ * carrier — without it a declared-none host's workflow children fell back to the deployment's live approver. */
299
+ parentApprovalReach?: "none";
296
300
  /** Call-time getter for the HOST task's RESOLVED model AND the word that chose it: a spawned agent whose
297
301
  * fold chain produced no model inherits the object on its spec and the word on its trusted channel
298
302
  * (the subagent lane's `ctx.model` / `ctx.modelRef` pair). Word-less ⇒ the object alone, i.e. a pin. */
@@ -509,7 +509,7 @@ export async function createRunWorkflowTool(d) {
509
509
  const childParkSeat = ctx.checkpointStoreForChildren ?? d.parentCheckpointStore;
510
510
  const parkStore = checkpointStoreOfSeat(childParkSeat);
511
511
  const scriptFn = (wfCtx) => {
512
- const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, childParkSeat, parentFaceSeat(ctx.readFace, d.parentReadFace), parentFaceSeat(ctx.writeFace, d.parentWriteFace), d.parentReadDenyPatterns, ctx.handsReadOnly === true || d.parentHandsReadOnly === true, ctx.interactiveTools === false || d.parentInteractiveTools === false);
512
+ const primitives = buildWorkflowPrimitives(wfCtx, runGovernance, d.onAgentSpawn, childParkSeat, parentFaceSeat(ctx.readFace, d.parentReadFace), parentFaceSeat(ctx.writeFace, d.parentWriteFace), d.parentReadDenyPatterns, ctx.handsReadOnly === true || d.parentHandsReadOnly === true, ctx.interactiveTools === false || d.parentInteractiveTools === false, ctx.approvalReach === "none" || d.parentApprovalReach === "none");
513
513
  return d.scriptRunner.run({ scriptSource: script, primitives, scriptArgs: effectiveArgs, signal: wfCtx.signal }).then((r) => r.result);
514
514
  };
515
515
  if (ctx.signal?.aborted) {
@@ -60,4 +60,6 @@ parentReadDenyPatterns?: () => TaskSpec["readDenyPatterns"],
60
60
  parentHandsReadOnly?: boolean,
61
61
  /** #345 ③ — the HOST task's hard-headless clamp is ON (`spec.interactiveTools === false`).
62
62
  * Same tighten-only injection; only the disabling value ever arrives here as `true`. */
63
- parentInteractiveToolsOff?: boolean): WorkflowPrimitives;
63
+ parentInteractiveToolsOff?: boolean,
64
+ /** The HOST task declared that nobody on it can be asked (`approvalReach: "none"`). Same tighten-only injection. */
65
+ parentApprovalReachNone?: boolean): WorkflowPrimitives;
@@ -1,5 +1,5 @@
1
1
  import { assertSupportedAgentIsolation } from "./workflow.js";
2
- import { childClampSeats } from "../core/child-clamp-seats.js";
2
+ import { childClampSeats, postureUnderClamps } from "../core/child-clamp-seats.js";
3
3
  import { WorkflowScriptError } from "./workflow-meta.js";
4
4
  import { buildGovernedChildSpec, emitIgnoredAgentOptionsNotice, overlayWorktreeBaseline, MAX_ANNOUNCED_KEYS, SPEC_KEY_ALIASES, WHITELIST_KEYS, } from "./workflow-governance.js";
5
5
  const ACCEPTED_AGENT_OPTION_KEYS = ["label", "phase", "schema", "agentType", "isolation"];
@@ -66,7 +66,7 @@ function formatResourceClampNote(notes) {
66
66
  const parts = notes.map((n) => `${n.field}: requested ${n.requested === undefined ? "unset" : n.requested} → applied ${n.applied}`);
67
67
  return `workflow governance tightened this agent's resource limits (${parts.join("; ")})`;
68
68
  }
69
- export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentCheckpointStore, parentReadFace, parentWriteFace, parentReadDenyPatterns, parentHandsReadOnly, parentInteractiveToolsOff) {
69
+ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentCheckpointStore, parentReadFace, parentWriteFace, parentReadDenyPatterns, parentHandsReadOnly, parentInteractiveToolsOff, parentApprovalReachNone) {
70
70
  const agent = (spec, opts) => {
71
71
  if (typeof spec === "string")
72
72
  spec = { objective: spec };
@@ -82,15 +82,15 @@ export function buildWorkflowPrimitives(ctx, governance, onAgentSpawn, parentChe
82
82
  const workflowChildClamps = childClampSeats({
83
83
  handsReadOnly: parentHandsReadOnly,
84
84
  interactiveTools: parentInteractiveToolsOff === true ? false : undefined,
85
+ approvalReach: parentApprovalReachNone === true ? "none" : undefined,
85
86
  readFace: parentReadFace?.() === "roots" ? "roots" : childSpec.readFace,
86
87
  writeFace: parentWriteFace?.() === "roots" ? "roots" : childSpec.writeFace,
87
88
  readDenyPatterns: pd !== undefined && pd.length > 0 ? [...(childSpec.readDenyPatterns ?? []), ...pd] : childSpec.readDenyPatterns,
88
89
  });
89
90
  Object.assign(childSpec, workflowChildClamps);
90
- if (parentInteractiveToolsOff === true) {
91
- if (childSpec.interactionPosture === "interactive")
92
- delete childSpec.interactionPosture;
93
- }
91
+ const posture = postureUnderClamps(childSpec.interactionPosture, { interactiveTools: parentInteractiveToolsOff === true ? false : undefined, approvalReach: parentApprovalReachNone === true ? "none" : undefined });
92
+ if (posture !== undefined)
93
+ childSpec.interactionPosture = posture;
94
94
  if (onAgentSpawn) {
95
95
  return ctx.agentStream(childSpec, agentOpts).then((handle) => {
96
96
  onAgentSpawn(handle);
@@ -421,6 +421,9 @@ export interface WorkflowRun {
421
421
  * read or written carrying this token's `parked` entry, so
422
422
  * absence is the confirmed state and an entry written before this member reads as confirmed, exactly as it did; the
423
423
  * engine's own material read identity-checks what it finds, so an unconfirmed origin degrades rather than mis-binds.
424
+ * `parkedBy` is the run standing on the park: a record's run holds it iff `entry.parkedBy === record.id` (never
425
+ * `originRunId`); inheritance keeps it. `generation` is the inherited entry's, else `0`, plus one per admission — the
426
+ * highest on a session is the chain's tail, a tie is a fork. Both or neither; an older entry has neither (no holder).
424
427
  */
425
428
  parks?: Array<{
426
429
  callKey: string;
@@ -428,6 +431,8 @@ export interface WorkflowRun {
428
431
  token: string;
429
432
  originRunId: string;
430
433
  originUnconfirmed?: true;
434
+ parkedBy?: string;
435
+ generation?: number;
431
436
  }>;
432
437
  /**
433
438
  * Set (`true`) on a resume run's record from its creation and REMOVED the moment its admission completes — so a record
@@ -223,8 +223,9 @@ export interface WorkflowAgentHandle {
223
223
  * legs of one steering ingress are equally replay-safe. ⚠️ ONE arm of that contract is NOT reachable through this
224
224
  * handle, deliberately: the idempotent-REPLAY arm ("same id, same payload ⇒ injects nothing"). Each call mints a
225
225
  * FRESH unguessable correlation marker into the framing it delivers, so a retry under the same id is a
226
- * same-id-DIFFERENT-instruction call and refuses typed `steering.duplicate_input_id`. What the key buys on this
227
- * lane is therefore AT-MOST-ONCE: a retried ingress is refused LOUDLY instead of injecting a second copy.
226
+ * same-id-DIFFERENT-instruction call and refuses typed `steering.duplicate_input_id` (`detail.differs` always names
227
+ * `text` on this lane). What the key buys on this lane is therefore AT-MOST-ONCE: a retried ingress is refused
228
+ * LOUDLY instead of injecting a second copy.
228
229
  */
229
230
  steer(content: string, opts?: {
230
231
  inputId?: string;