@sema-agent/core 7.38.3 → 7.39.1

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 (96) hide show
  1. package/CHANGELOG.md +191 -6
  2. package/dist/core/ask-origin.d.ts +16 -7
  3. package/dist/core/ask-origin.js +4 -2
  4. package/dist/core/auto-mode-directive.d.ts +114 -19
  5. package/dist/core/auto-mode-directive.js +67 -19
  6. package/dist/core/checkpoint-store.d.ts +72 -41
  7. package/dist/core/checkpoint-store.js +28 -48
  8. package/dist/core/classifier-unavailable-arm.js +2 -2
  9. package/dist/core/decision-receipt.d.ts +2 -0
  10. package/dist/core/decision-receipt.js +1 -1
  11. package/dist/core/engine-notice.d.ts +41 -17
  12. package/dist/core/engine-notice.js +26 -14
  13. package/dist/core/gate-outcome.d.ts +80 -42
  14. package/dist/core/gate-outcome.js +21 -6
  15. package/dist/core/governance-codes.d.ts +1 -3
  16. package/dist/core/governance-codes.js +0 -3
  17. package/dist/core/hooks.d.ts +1 -1
  18. package/dist/core/hooks.js +2 -2
  19. package/dist/core/human-input-projection.d.ts +12 -0
  20. package/dist/core/human-input-projection.js +12 -0
  21. package/dist/core/memory-engine/engine.js +11 -11
  22. package/dist/core/memory-engine/layout.d.ts +21 -2
  23. package/dist/core/memory-engine/layout.js +17 -5
  24. package/dist/core/mode-policy.d.ts +26 -0
  25. package/dist/core/mode-policy.js +37 -1
  26. package/dist/core/output-style.d.ts +21 -0
  27. package/dist/core/output-style.js +29 -0
  28. package/dist/core/real-approval.d.ts +25 -0
  29. package/dist/core/real-approval.js +27 -0
  30. package/dist/core/reminder-disclosure.d.ts +2 -2
  31. package/dist/core/reminder-mint.d.ts +6 -9
  32. package/dist/core/reminder-mint.js +3 -14
  33. package/dist/core/retired-keys.d.ts +2 -1
  34. package/dist/core/retired-keys.js +3 -1
  35. package/dist/core/runner/assemble-result.d.ts +2 -0
  36. package/dist/core/runner/assemble-result.js +1 -1
  37. package/dist/core/runner/compaction-restate.d.ts +45 -4
  38. package/dist/core/runner/compaction-restate.js +89 -11
  39. package/dist/core/runner/contracts.d.ts +6 -6
  40. package/dist/core/runner/denial-limit-arms.js +4 -4
  41. package/dist/core/runner/inherited-ask-grants.d.ts +4 -2
  42. package/dist/core/runner/inherited-ask-grants.js +0 -3
  43. package/dist/core/runner/permission-rule-lanes.d.ts +18 -0
  44. package/dist/core/runner/permission-rule-lanes.js +35 -25
  45. package/dist/core/runner/prepare-context-lane.d.ts +1 -0
  46. package/dist/core/runner/prepare-context-lane.js +6 -0
  47. package/dist/core/runner/prepare-inherited-gate.js +2 -9
  48. package/dist/core/runner/prepare-task.js +10 -24
  49. package/dist/core/runner/prepare-wiring-manifest.js +2 -2
  50. package/dist/core/runner/prompt-block.d.ts +9 -0
  51. package/dist/core/runner/prompt-block.js +8 -0
  52. package/dist/core/runner/resume-admission.js +4 -1
  53. package/dist/core/runner/resume-apply.js +28 -18
  54. package/dist/core/runner/resume-policy-outcome.js +10 -4
  55. package/dist/core/runner/run-attachment-seats.js +1 -1
  56. package/dist/core/runner/run-identity-wiring.js +9 -2
  57. package/dist/core/runner/run-leg.js +42 -31
  58. package/dist/core/runner/run-settle-and-teardown.js +4 -3
  59. package/dist/core/runner/run-turn-boundary.js +4 -3
  60. package/dist/core/runner/runtask.d.ts +4 -2
  61. package/dist/core/runner/steer-admission.d.ts +9 -0
  62. package/dist/core/runner/steer-admission.js +8 -0
  63. package/dist/core/runner/stream-steer-verb.js +16 -11
  64. package/dist/core/runner/synthetic-tools.d.ts +4 -0
  65. package/dist/core/runner/tool-turn-repair-seat.js +1 -1
  66. package/dist/core/runner/turn-attachments.d.ts +16 -22
  67. package/dist/core/runner/turn-attachments.js +3 -20
  68. package/dist/core/runner-deps.d.ts +1 -1
  69. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +8 -0
  70. package/dist/core/store-contracts/checkpoint-store-contract.js +23 -0
  71. package/dist/core/task-event.d.ts +7 -2
  72. package/dist/core/task-spec.d.ts +29 -7
  73. package/dist/core/terminal-cause.d.ts +44 -3
  74. package/dist/core/terminal-cause.js +5 -0
  75. package/dist/core/tool-policy.d.ts +55 -29
  76. package/dist/core/tool-policy.js +42 -31
  77. package/dist/core/types.d.ts +1 -1
  78. package/dist/core/types.js +1 -1
  79. package/dist/core/wiring-manifest.d.ts +17 -3
  80. package/dist/core/wiring-manifest.js +8 -3
  81. package/dist/engine/harness/agent-harness.d.ts +10 -0
  82. package/dist/engine/harness/agent-harness.js +12 -1
  83. package/dist/engine/llm/tool-turns.d.ts +15 -2
  84. package/dist/engine/llm/tool-turns.js +2 -2
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.js +4 -3
  87. package/dist/prompt-assembly/event-registry.js +2 -1
  88. package/dist/tools/fs/index.d.ts +2 -1
  89. package/dist/tools/fs/read-deny-home.d.ts +3 -2
  90. package/dist/tools/fs/read-deny-home.js +6 -4
  91. package/dist/tools/fs/shell-read-check.d.ts +10 -3
  92. package/dist/tools/fs/shell-read-check.js +2 -2
  93. package/package.json +1 -1
  94. package/test/export-surface.snapshot.json +21 -1
  95. package/wire-changes/core@7.39.0.json +999 -0
  96. package/wire-changes/core@7.39.1.json +74 -0
@@ -211,7 +211,7 @@ export interface FrameReceipt {
211
211
  text: string;
212
212
  /** Which frame this receipt settles. A boundary collector must not render a listing whose receipt is
213
213
  * still pending (the frame is queued, not yet in the transcript). */
214
- source: "git_status" | "agent_listing" | "skills_listing" | "date_change";
214
+ source: "git_status" | "agent_listing" | "skills_listing" | "date_change" | "output_style";
215
215
  /** Runs inside the `message_end` walk (synchronous, never awaited): advances the run-local record
216
216
  * and parks any session write for the next serialization point. */
217
217
  commit: (entryId: string) => void;
@@ -283,8 +283,10 @@ export interface Prepared {
283
283
  /** Whether the full shell is on this leg's roster (the inherited-gate phase's bit, asserted against the mount). */
284
284
  fullShellReachable: boolean;
285
285
  /** The run's approval-posture facts as prepared — the effective permission mode and the ask seam's effective
286
- * answer. The first user turn renders them into the engine's own marked reminder; no gate decision keys on them. */
286
+ * answer. The posture composition renders them into the engine's own marked reminders; no gate decision keys on them. */
287
287
  approvalPosture: import("../auto-mode-directive.js").ApprovalPostureFacts;
288
+ /** The output-style frame body, rendered once at prepare from the screened `TaskSpec.outputStyle` (absent = no seat). */
289
+ outputStyleFrame?: string;
288
290
  /** The leg's live permission-mode seat: the effective mode every mode consumer reads, the steer verb writes. */
289
291
  permissionMode: PermissionModeController;
290
292
  /** The ISOLATION-AWARE working-tree root for this task (a worktree's cwd when `isolation: "worktree"`, else
@@ -1179,10 +1181,8 @@ export interface Prepared {
1179
1181
  * handler matches on, and the re-assert closure the compaction landing + boundary retry call.
1180
1182
  * Always present (empty object on a hands-less leg — the lane is then out of scope). */
1181
1183
  gitStatusRef: GitStatusLaneRef;
1182
- /** The posture directive's newest engine carrier (replace-by-key, the git frame's protection slot twin). */
1183
- postureFrameRef: {
1184
- protectedText?: string;
1185
- };
1184
+ /** The posture composition's run-local half (the newest carrier's protection slot; the row last composed for). */
1185
+ postureFrameRef: import("../auto-mode-directive.js").PostureFrameRef;
1186
1186
  /** G1 通告层 — narrow post-compact getter over the process task registry: THIS run's visible
1187
1187
  * pending/running background tasks (same owner/scope/session identity the TaskOutput/TaskStop tools
1188
1188
  * use), as a bounded display projection (id/description/status — never handles/env/abort). Called by
@@ -2,7 +2,7 @@ import { markTightenMessage } from "../safety-axis-vocab.js";
2
2
  import { classifierParseFailureDenyMessage, createAutoModeDenialTracker, denialLimitFallbackMessage, denialLimitSentence, unarmedWindow } from "../auto-mode.js";
3
3
  import { sanitizeAutoModeArmingRecipe, tightenDenialLimit } from "../auto-mode-arming.js";
4
4
  import { classifierUnavailableArm } from "../classifier-unavailable-arm.js";
5
- import { attestClassifierDenyCause, attestClassifierRound } from "../tool-policy.js";
5
+ import { attestClassifierDenyCause, attestClassifierRound, carryEngineAttestations } from "../tool-policy.js";
6
6
  import { SETTLEMENT_NOBODY_JUDGED } from "../gate-outcome.js";
7
7
  import { deliverEngineNotice } from "../types.js";
8
8
  import { inlineUntrusted } from "../untrusted-text.js";
@@ -84,8 +84,8 @@ export const inheritedAskFallbackMessage = (toolName) => `approval required for
84
84
  export async function judgeInheritedClassifier(opts) {
85
85
  const { autoMode, req } = opts;
86
86
  const facts = opts.facts ?? INHERITED_STATION_FACTS, m = facts.mandateMarks;
87
- const frames = m.shellGated === true ? [] : [...(m.egress === true ? [markTightenMessage(req.toolName, "egress")] : []), ...(m.irreversibility === "always" ? [markTightenMessage(req.toolName, "irreversible")] : [])];
88
- const ask = (frames.length > 0 ? { ...opts.ask, message: [opts.ask.message ?? inheritedAskFallbackMessage(req.toolName), ...frames].join(" — ") } : opts.ask);
87
+ const frames = [...(m.egress === true ? [markTightenMessage(req.toolName, "egress")] : []), ...(m.irreversibility === "always" ? [markTightenMessage(req.toolName, "irreversible")] : [])];
88
+ const ask = (frames.length > 0 ? carryEngineAttestations(opts.ask, { ...opts.ask, message: [opts.ask.message ?? inheritedAskFallbackMessage(req.toolName), ...frames].join(" — ") }) : opts.ask);
89
89
  const origin = askOriginOf(ask, facts);
90
90
  const classification = classifyAsk(ask, facts);
91
91
  const classifierStandsDown = () => {
@@ -123,7 +123,7 @@ export async function judgeInheritedClassifier(opts) {
123
123
  return { kind: "deny", result: attributed(verdict.kind === "parse_error" ? attestClassifierDenyCause(denied, "parse_error", req) : denied) };
124
124
  }
125
125
  const fallback = tracked.fallback;
126
- const minted = { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback };
126
+ const minted = carryEngineAttestations(ask, { ...ask, message: denialLimitFallbackMessage(fallback, reason || category || req.toolName), decisionReason: "classifier", requiresRealApproval: true, denialLimitFallback: fallback });
127
127
  return { kind: "resolve", ask: minted, fallback, mintedHere: true, origin: askOriginOf(minted, facts), classification: classifyAsk(minted, facts) };
128
128
  }
129
129
  export function headlessDenyAtFold(r) {
@@ -31,8 +31,10 @@ export declare function askGrantShapeOf(args: unknown): string | undefined;
31
31
  * the gate's own judges add (a protected target, an out-of-root read) was not on the card. Not covered ⇒ the gate
32
32
  * asks its own question and the grant stays (it answers what it answered; the call-site sweep ends it). The class
33
33
  * set is also the AUTHORITY test (a hook's ask carries `hook`, a fallback `denial_limit_fallback`, a marked tool
34
- * `operator_forced`, a protected write `write_protection`, an out-of-root read `probe`; the shell seat's classify-tier
35
- * hesitation is `ordinary`, the one question a yes on the same bytes answers) — the ask's `decisionReason` is not read.
34
+ * `operator_forced`, a protected write `write_protection`, an out-of-root read `probe`, the operator's shell floor
35
+ * `shell_floor` — a class no inherited station mints from its facts, so no station card answers it; the shell seat's classify-tier
36
+ * hesitation is `ordinary`, the one question a yes on the same bytes answers) — neither the ask's `decisionReason` nor
37
+ * its `mandate` word is read.
36
38
  * · LEDGER: the ONE real consultation (timed on the wrapper arm) is booked here — one frame happened, one is booked.
37
39
  * The grant lives within one gate evaluation (consumed on reuse, swept at the call site): a REOPENED checkpoint's
38
40
  * re-adjudication never sees a stale grant.
@@ -1,4 +1,3 @@
1
- import { askMandateMember } from "../ask-origin.js";
2
1
  import { primaryActivityArg } from "../arg-summary.js";
3
2
  import { askApproverIdentity, carryEngineAttestations } from "../tool-policy.js";
4
3
  export function askGrantCovers(granted, asked) {
@@ -18,8 +17,6 @@ export function consumeInheritedAskGrant(grants, onAsk, humanReviewRef, decision
18
17
  return undefined;
19
18
  if (!askGrantCovers(grant.classes, decision.action === "ask" ? decision.classes : undefined))
20
19
  return undefined;
21
- if (decision.action === "ask" && askMandateMember(decision).mandate === "operator_always")
22
- return undefined;
23
20
  grants.delete(req.toolCallId);
24
21
  humanReviewRef.count += 1;
25
22
  humanReviewRef.totalWaitMs += grant.waitMs;
@@ -120,6 +120,24 @@ export interface PermissionRuleLanes {
120
120
  adjudicate: (req: ToolCallRequest) => Promise<OrgGateVerdict>;
121
121
  } | undefined;
122
122
  }
123
+ /** The ORG lane: the org's verdict about one call, off the store view `read` supplies — the gate's `orgRules` face over the
124
+ * per-call handoff ({@link createPermissionRuleLanes}), and {@link orgVerdictBeforeClaim} over a view of its own. */
125
+ export declare function orgRuleLane(cfg: {
126
+ read: (toolCallId: string) => Promise<EffectivePermissionRules>;
127
+ questionToolMounted: boolean;
128
+ questionToolName: string;
129
+ onRevisionDefect: (message: string) => void;
130
+ }): NonNullable<PermissionRuleLanes["org"]>;
131
+ /** The org's CURRENT verdict about a parked call, read by the resume entry before the claim (the row's session and principal,
132
+ * bounded like the gate's wait; unreadable ⇒ unavailable). The mount and a revision defect are the resumed leg's facts, so
133
+ * the reserved name is exempted and the defect left to that leg: this read can leave a question unasked, never invent one. */
134
+ export declare function orgVerdictBeforeClaim(cfg: {
135
+ provider: PermissionRuleStoreProvider;
136
+ principal: string | undefined;
137
+ localOwnerDeclared: boolean;
138
+ sessionId: string;
139
+ signal: AbortSignal | undefined;
140
+ }, req: ToolCallRequest): Promise<OrgGateVerdict>;
123
141
  /**
124
142
  * Build the gate's two permission-rule lanes over the unified store, resolved once per task.
125
143
  *
@@ -5,7 +5,8 @@ import { isAbsolutePathForm } from "../../tools/fs/safety.js";
5
5
  import { effectivePathTargetOf } from "../effective-path-target.js";
6
6
  import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
7
7
  import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
8
- import { orgRuleVerdictFor } from "../permission-rule-org.js";
8
+ import { orgRuleVerdictFor, settleOrgVerdictWithin } from "../permission-rule-org.js";
9
+ import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
9
10
  import { ownerStoreOf } from "../permission-rule-provider.js";
10
11
  import { thrownText } from "../thrown-value.js";
11
12
  import { strongestAnswer } from "../persisted-rule-arms.js";
@@ -46,6 +47,38 @@ export function pathRuleLaneAnswer(table, req, ctx) {
46
47
  return undefined;
47
48
  return persistedRuleAnswerOf(adjudicatePersistedPathRules(table, { tool: req.toolName, cwd: ctx.root, sessionId: ctx.sessionId }, { ...(tighten !== undefined ? { tighten } : {}), ...(allow !== undefined ? { allow } : {}) }, bases));
48
49
  }
50
+ export function orgRuleLane(cfg) {
51
+ return {
52
+ adjudicate: async (req) => {
53
+ if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
54
+ return { status: "available" };
55
+ let resolution;
56
+ try {
57
+ resolution = (await cfg.read(req.toolCallId)).org;
58
+ }
59
+ catch (err) {
60
+ return { status: "unavailable", disclosures: [`the permission-rule store threw while resolving the org partition: ${thrownText(err)}`] };
61
+ }
62
+ if (resolution.status === "unavailable" || resolution.status === "ungoverned") {
63
+ return { status: "unavailable", disclosures: resolution.status === "ungoverned" ? ["the store reports no org partition while the provider declares one — unreadable governance, never an empty one"] : resolution.disclosures };
64
+ }
65
+ const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
66
+ const command = req.args?.command;
67
+ if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
68
+ return { status: "available", ...revisionCell };
69
+ const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
70
+ return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
71
+ },
72
+ };
73
+ }
74
+ export function orgVerdictBeforeClaim(cfg, req) {
75
+ const unreadable = { status: "unavailable", disclosures: [] };
76
+ const read = () => {
77
+ const store = ownerStoreOf(cfg.provider, cfg.principal, cfg.localOwnerDeclared);
78
+ return orgRuleLane({ read: () => store.effective({ sessionId: cfg.sessionId }), questionToolMounted: true, questionToolName: ASK_USER_QUESTION_TOOL_NAME, onRevisionDefect: () => undefined }).adjudicate(req);
79
+ };
80
+ return settleOrgVerdictWithin(Promise.resolve().then(read).catch(() => unreadable), unreadable, { timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS, ...(cfg.signal !== undefined ? { signal: cfg.signal } : {}) });
81
+ }
49
82
  export function createPermissionRuleLanes(cfg) {
50
83
  const provider = cfg.provider;
51
84
  if (cfg.home !== undefined && !isAbsolutePathForm(cfg.home)) {
@@ -90,30 +123,7 @@ export function createPermissionRuleLanes(cfg) {
90
123
  handed.delete(toolCallId);
91
124
  return p;
92
125
  };
93
- const org = provider.partitions.org
94
- ? {
95
- adjudicate: async (req) => {
96
- if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
97
- return { status: "available" };
98
- let resolution;
99
- try {
100
- resolution = (await hand(req.toolCallId)).org;
101
- }
102
- catch (err) {
103
- return { status: "unavailable", disclosures: [`the permission-rule store threw while resolving the org partition: ${thrownText(err)}`] };
104
- }
105
- if (resolution.status === "unavailable" || resolution.status === "ungoverned") {
106
- return { status: "unavailable", disclosures: resolution.status === "ungoverned" ? ["the store reports no org partition while the provider declares one — unreadable governance, never an empty one"] : resolution.disclosures };
107
- }
108
- const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
109
- const command = req.args?.command;
110
- if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
111
- return { status: "available", ...revisionCell };
112
- const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
113
- return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
114
- },
115
- }
116
- : undefined;
126
+ const org = provider.partitions.org ? orgRuleLane({ read: hand, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect }) : undefined;
117
127
  const personalAdjudicate = async (req) => {
118
128
  if (anonymous && !cfg.localOwnerDeclared)
119
129
  return undefined;
@@ -62,6 +62,7 @@ export interface PrepareContextLaneInput {
62
62
  /** borrowed-mutable — the posture directive's newest engine carrier (the first-turn mint and each post-compaction restatement write it); the clip guard reads it. */
63
63
  postureFrameRef: {
64
64
  protectedText?: string;
65
+ unwritten?: string;
65
66
  };
66
67
  /** borrowed-mutable — the per-request lossy-projection flag. Writers here: the handler re-derives it at every request
67
68
  * build, and the repair sink raises it when the provider boundary's law left recorded content out of the same request
@@ -105,6 +105,12 @@ export function prepareContextLane(input) {
105
105
  if (kept.action === "kept" || kept.action === "reinserted")
106
106
  trimmed = kept.messages;
107
107
  }
108
+ if (postureFrameRef.unwritten !== undefined) {
109
+ let at = trimmed.length;
110
+ while (at > 0 && trimmed[at - 1].role === "user")
111
+ at--;
112
+ trimmed = [...trimmed.slice(0, at), { role: "user", content: [{ type: "text", text: postureFrameRef.unwritten }], timestamp: Date.now(), engineMinted: true }, ...trimmed.slice(at)];
113
+ }
108
114
  requestLossyRef.current =
109
115
  replayed !== healed ||
110
116
  capped !== replayed ||
@@ -8,7 +8,7 @@ import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
8
8
  import { deliverEngineNotice } from "../types.js";
9
9
  import { PermissionModeController, modeRequestAfterResume } from "../mode-policy.js";
10
10
  import { SessionModeSeats } from "./session-mode-seats.js";
11
- import { migrateLegacyModeSeed } from "../checkpoint-store.js";
11
+ import { recordedModeSeed } from "../checkpoint-store.js";
12
12
  import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
13
13
  import { asThrownError } from "../thrown-value.js";
14
14
  export function prepareInheritedGate(input) {
@@ -32,14 +32,7 @@ export function prepareInheritedGate(input) {
32
32
  const extras = liveRules.filter((l) => !seedRules.some((s) => s.sessionId === l.sessionId && s.principal === l.principal));
33
33
  return [...merged, ...extras];
34
34
  })();
35
- const modeSeed = migrateLegacyModeSeed(seedInheritedGate);
36
- if (modeSeed.migrated !== undefined) {
37
- deliverEngineNotice(deps.onNotice, {
38
- code: "resume.legacy_mode_seed_migrated",
39
- message: `the resumed checkpoint row carried pre-mode keys (${modeSeed.migrated.droppedKeys.join(", ")}); read once into ${modeSeed.migrated.permissionMode !== undefined ? `permissionMode: "${modeSeed.migrated.permissionMode}"` : "no mode of its own (the chain's ceiling stands)"}${modeSeed.shellGate !== undefined ? ' with the shell floor "always" kept' : ""} — the next suspend writes the one key; this migration is kept through 7.38.x and removed in 7.39.0, which refuses a row still carrying these keys (resume or abandon such rows before upgrading)`,
40
- detail: { migrated: { ...(modeSeed.migrated.permissionMode !== undefined ? { permissionMode: modeSeed.migrated.permissionMode } : {}), droppedKeys: [...modeSeed.migrated.droppedKeys] }, sessionId, runId },
41
- });
42
- }
35
+ const modeSeed = recordedModeSeed(seedInheritedGate);
43
36
  const inheritedShellGate = liveInheritedGate?.shellGate ?? modeSeed.shellGate;
44
37
  const inheritedParentConstraints = attachRebuiltDenialTrackers(liveInheritedGate?.parentConstraints, deps.autoMode?.denialLimit);
45
38
  const parentSeat = liveInheritedGate?.permissionModeCeiling;
@@ -11,10 +11,10 @@ import { callerToolMembership } from "../../agents/inherited-tool-pool.js";
11
11
  import { toolPolicyPathDenyRules, toolPolicyWriteTargetGuard } from "../tool-policy.js";
12
12
  import { loadAnnounceOnceLedger } from "./announce-once-ledger.js";
13
13
  import { isValidReminderMark, mintReminderMark } from "../reminder-mint.js";
14
- import { clampPermissionMode, isPermissionMode, PERMISSION_MODES } from "../ask-origin.js";
15
- import { modePolicyOf, modeRequestAfterResume } from "../mode-policy.js";
16
- import { assertNoRetiredKeys, RETIRED_TASK_SPEC_KEYS } from "../retired-keys.js";
17
- import { migrateLegacyModeSeed } from "../checkpoint-store.js";
14
+ import { clampPermissionMode } from "../ask-origin.js";
15
+ import { modePolicyOf, modeRequestAfterResume, refusePermissionModeDoors } from "../mode-policy.js";
16
+ import { resolveOutputStyleFrame } from "../output-style.js";
17
+ import { recordedModeSeed } from "../checkpoint-store.js";
18
18
  import { emitTrace } from "../trace.js";
19
19
  import { persistedPathRowsOfTask, ruleStoreUnreadableNotice } from "./persisted-path-rows.js";
20
20
  import { reservedCollisions, reservedFor } from "../../brain/request-params.js";
@@ -81,6 +81,7 @@ import { selfOrchestrationFailClosedReason } from "../../orchestration/workflow-
81
81
  import { remainingBudgetMicroUsd, resolveCheckpointStore } from "../checkpoint-store.js";
82
82
  import { checkpointSeatOf } from "../checkpoint-seat.js";
83
83
  import { handsWriteHalfMounts } from "../../tools/fs/index.js";
84
+ import { askSeatEffective } from "../wiring-manifest.js";
84
85
  import { durableParkGapFor } from "../park-selfcheck.js";
85
86
  import { deliverEngineNotice } from "../types.js";
86
87
  import { ToolRosterDeltaSeat } from "../tool-roster.js";
@@ -266,22 +267,6 @@ function refuseRegistryScopeMismatch(spec, internals) {
266
267
  function validateInputFaultNotice(onError, sessionId) {
267
268
  return ({ toolName, toolCallId, error }) => onError?.(new Error(`tool input pre-validation (validateInput) threw for ${toolName} (call ${toolCallId}) — read as no verdict; the call proceeded to the gate: ${thrownText(error)}`), { phase: "hook", sessionId });
268
269
  }
269
- function refusePermissionModeDoors(spec) {
270
- assertNoRetiredKeys(spec, RETIRED_TASK_SPEC_KEYS, "TaskSpec");
271
- const mode = spec.permissionMode;
272
- if (mode !== undefined && !isPermissionMode(mode)) {
273
- const e = new Error(`permissionMode must be one of ${PERMISSION_MODES.map((w) => JSON.stringify(w)).join(", ")} when present (got ${JSON.stringify(mode)}) — omit it for the default mode; an unrecognized word is refused rather than read as any mode.`);
274
- e.code = "config.permission_mode_invalid";
275
- throw e;
276
- }
277
- const floor = spec.shellGate;
278
- if (floor !== undefined && floor !== "always") {
279
- const e = new Error(`shellGate must be the floor word "always" when present (got ${JSON.stringify(floor)}) — the doctrine values "off" / "classify" are retired: the shell's shape tier is installed on every full shell and the permission mode (permissionMode) decides what its asks become; omit shellGate for no operator floor.`);
280
- e.code = "config.shell_gate_value_retired";
281
- throw e;
282
- }
283
- return mode;
284
- }
285
270
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf, runIdSink, holdSessionLock) {
286
271
  const runId = uuidv7();
287
272
  if (runIdSink !== undefined)
@@ -291,8 +276,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
291
276
  spec = doors.spec;
292
277
  const checkpointStore = resolveCheckpointStore(spec, deps), checkpointSeat = checkpointSeatOf(spec, checkpointStore);
293
278
  const permissionModeSeat = refusePermissionModeDoors(spec);
279
+ const outputStyleFrame = resolveOutputStyleFrame(spec.outputStyle);
294
280
  const declaredModeAtDoor = (() => {
295
- const requested = modeRequestAfterResume(permissionModeSeat, migrateLegacyModeSeed(resume?.seed.inheritedGate), resume?.outcome);
281
+ const requested = modeRequestAfterResume(permissionModeSeat, recordedModeSeed(resume?.seed.inheritedGate), resume?.outcome);
296
282
  const ceiling = internals?.inheritedGate?.permissionModeCeiling?.current ?? internals?.inheritedGate?.permissionMode;
297
283
  return ceiling === undefined ? (requested ?? "default") : clampPermissionMode(requested, ceiling).effective;
298
284
  })();
@@ -754,13 +740,13 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
754
740
  const { saga } = prepareSuspendSaga({ gateMachineryActive, abortController, harness, checkpointStore, deps, sessionId, hostTaskId, taskScope, liveSpendRef, resume, nestedStats, internals, activeTools, outputRef, readFileStateForCheckpoint, faceCheckpointSection, writeFaceCheckpointSection, reminderMark, handsCwdRef, worktreeSessionRef, undeliveredContinuationRef, inheritedParentConstraints, seedInheritedGate, ancestorRulesNow, inheritedShellGate, permissionMode, inheritedAdmittedOrgScopes, ownOrgVerdictRef, orgGovernedProvenance, announcedListingsRef, gitStatusRef, hookIdentity, placementRootResolved, externalContentTargetActive, remoteEnvFailures, memoryEngineSession, suspendLoopRef, ownedEnv, incompleteSuspendAdapter });
755
741
  const { suspendForResource, suspendForPlatformLimit, suspendForReview } = prepareBoundaryParks({ saga, spec, checkpointStore, abortController, harness, session, sessions, sessionId, deps, priorLedger, maxSlices, maxSuspends, resourceTotal, priorSuspendCount, suspendChainBase, humanReviewRef, liveSpendRef, now, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, pausedRef, remoteEnvFailures, resourceSuspendEligible, durableSuspendInfraReady, incompleteSuspendAdapter });
756
742
  const { parkAsk } = prepareParkAsk({ askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas: rosterSeat, 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 });
757
- prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: wiringManifest.ask, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
758
- const postureFrameRef = {};
743
+ prepareGateStations({ askLane, parkAsk, tools, toolRosterDeltas: rosterSeat, toolEffects, deps, toolCallGateArmedRef, effectivePolicy, hooks, egressTools, irreversibleTools, spec, complianceDenies, harness, blockedToolCalls, inheritedAskGrants, inheritedUnavailableAsks, foldAskClasses, ancestorSandboxAdmissions, preToolContexts, gateOutcomes, batchHaltRef, blockedTracked, hookIdentity, reminderMark, hostTaskId, sessionId, ownGatePreToolUse, hookTimeoutMs, handsCwdRef, readDenyInForce: () => ({ tiers: handsReadFace.readDenyBuiltinTiersActive, rows: [...readDenyAdditionsNormalized.map((e) => e.pattern), ...handsReadFaceInput.pathDenyRules.filter((r) => r.faces.includes("read")).map((r) => r.rule)] }), hookEnvFace, writeProtectionCheck, shellReadCheck, irreversibilityTier, reversibilityProbes, abortController, shellGatedTools, autoModeDecider: autoModeDeciderRef, autoModeDenialTracking: autoModeDenialTrackingRef, askRoute: { ...wiringManifest.ask, effective: askSeatEffective(wiringManifest) }, permissionMode, toolMarkSources, ...(deps.a2a?.isolatePeers === true ? { a2aIsolatePeers: true } : {}), ancestorArbitrations, runId, stopForDenialLimit, permissionRuleLane, permissionRuleOrgLane, questionToolMounted, sandboxAdmissionArmed, sandboxBoundaryCapable, emitSandboxAdmitted, delegation, notifyOwnHookCrash, frameSettlement });
744
+ const postureFrameRef = { report: (err) => deps.onError?.(err, { phase: "degraded", sessionId }) };
759
745
  const { microCompact, charsPerToken } = prepareContextLane({ model, deps, sessionId, runId, hostTaskId, offloadStore, toolRosterDeltas: rosterSeat, microCompactKnob, harness, batchHaltRef, gitStatusRef, postureFrameRef, requestLossyRef, trimPressureRef, contextOverlay: legContextOverlay, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}) });
760
746
  const preparedHolder = {};
761
747
  const { cacheBreakDetector, cacheFingerprint, promptOverheadTokens, readTaskFile, normalizeAttachmentPath, isDedupStubResult, recentlyReadFiles, onCompactionApplied, detectExternalChanges, listBackgroundTasks, centerCompactionCandidate, effectiveReadFaceObserved, effectiveWriteFaceObserved, effectiveReadDenyObserved } = prepareTurnWiring({ deps, systemPromptSeat, harnessTools, fpRef, model, epochArtifactDigestForSnapshot, promptProfile, fableMitigations, systemBlocks, thinking, spec, sessionId, turnSnapshotRef, promptManifest, charsPerToken, handsEnabled, executionEnv, attachmentRootCanonical, additionalRootsCanonical, additionalReadRootsCanonical, readDenyMatcher, resolvedReadFace, handsCwdRef, readFileStateForCheckpoint, denyNarrowingPolicy, abortController, hostTaskId, taskScope, buildAssembleInputs, centerAdoptionRef, harnessRef, epochDeclaredSections, carrierReadFace, carrierWriteFace, readDenyAdditionsNormalized, preparedHolder });
762
748
  await onceLedger.settle(session, announcedListingsRef);
763
- const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: wiringManifest.ask.effective ?? "unresolved" }, permissionMode, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, endModeCeiling, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
749
+ const buildPrepared = () => ({ harness, session, sessionId, runId, registryAccess: { owner: hostTaskId, scope: taskScope }, reminderMark, reminderDisclosureCounts, fullShellReachable, approvalPosture: { get mode() { return permissionMode.current; }, askEffective: askSeatEffective(wiringManifest) }, ...(outputStyleFrame !== undefined ? { outputStyleFrame } : {}), permissionMode, editedFilesSnapshot, taskRootPath: taskRootFinal, model, thinking, compModel: effectiveCompModel, mcp, ...(mcpRedial !== undefined ? { mcpRedial } : {}), ...(a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, gateOutcomes, batchHaltRef, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(fileHistoryBoundary !== undefined ? { fileHistoryBoundary } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved, effectiveWriteFace: effectiveWriteFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), effectiveMemoryScopes: memoryEffectiveScopes, cwdRef: handsCwdRef, toolProgressRef, ...(advertisedWritableDirs !== undefined ? { advertisedWritableDirs } : {}), ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), ...(sealReadStateSeat !== undefined ? { sealReadStateSeat } : {}), denyNarrowingPolicy, ...(permissionRuleLane !== undefined ? { persistedRuleLane: permissionRuleLane } : {}), ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, endModeCeiling, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, pausedRef, undeliveredContinuationRef, suspendProgressRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, workCutRef, brainCallGuardrailRef, gateStopRef, stopForDenialLimit, suspendForReview, ...(readFileStateForCheckpoint !== undefined ? { readFileState: readFileStateForCheckpoint } : {}), resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolRoster, toolRosterDeltas: rosterSeat, structuredProjector, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, microCompact, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), stopRequestedRef, announcedSnapshotRecovered: onceLedger.recovered, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(projectInstructionContent !== undefined ? { projectInstructionContent } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, frameReceipts: { pending: [], listingSnapshotOwed: false }, frameSettlement, ...(peerLaneRefs.boundaryRound !== undefined ? { peerBoundaryRound: peerLaneRefs.boundaryRound } : {}), gitStatusRef, postureFrameRef, listBackgroundTasks, hookIdentity, ...(sessionBoxBound !== undefined ? { sessionBox: sessionBoxBound } : {}), hookTimeoutMs, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}), ...(shellReadCheck !== undefined ? { shellReadCheck } : {}), ...(toolDisclosureSeat !== undefined ? { toolDisclosure: toolDisclosureSeat } : {}) });
764
750
  rollback.commit();
765
751
  commitModeSeats(abortController.signal);
766
752
  const prepared = buildPrepared();
@@ -11,7 +11,7 @@ import { inlineUntrusted } from "../untrusted-text.js";
11
11
  import { mountWriteProtection } from "./write-protection-station.js";
12
12
  import { autoRunOnRecommendationEffective } from "./memory-consolidation-driver.js";
13
13
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs, windowsGovernCost } from "../usage-window-store.js";
14
- import { LSP_CONTRACT_ID, countElicitOptIns, deriveHandsSeam, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
14
+ import { LSP_CONTRACT_ID, askSeatEffective, countElicitOptIns, deriveHandsSeam, deriveLspSeam, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam, resolveSubagentTranscriptTier } from "../wiring-manifest.js";
15
15
  import { DEFAULT_IRREVERSIBLE_SCOPE } from "./checkpoint-scope.js";
16
16
  export function mcpManifestEntries(entries, statuses) {
17
17
  return (entries ?? []).map((entry) => {
@@ -222,7 +222,7 @@ export function prepareWiringManifest(input) {
222
222
  hands: deriveHandsSeam(handsEnabled),
223
223
  fileHistory: fileHistoryFact,
224
224
  });
225
- peerLaneRefs.askEffective = wiringManifest.ask.effective;
225
+ peerLaneRefs.askEffective = askSeatEffective(wiringManifest);
226
226
  peerLaneRefs.permissionMode = () => permissionMode.current;
227
227
  const sessionBoxBound = sessionBox !== undefined && internals?.onTaskNotification !== undefined ? sessionBox : undefined;
228
228
  if (sessionBoxBound !== undefined && internals?.onTaskNotification !== undefined) {
@@ -0,0 +1,9 @@
1
+ import type { PromptBlockCause } from "../terminal-cause.js";
2
+ import type { BlockedRef } from "./synthetic-tools.js";
3
+ /**
4
+ * Hold the task's first prompt back: `ref.reason` is `text` in the engine's reminder frame, `ref.detail` names `cause`.
5
+ * `text` is the hook's own words when `cause` is `"block"` — they also become `detail.hookReason`, folded to one line,
6
+ * bounded, envelope tags defused, with no reminder frame and no session mark; a reason that folds to nothing leaves the
7
+ * key out. For every other cause `text` is the engine's sentence about the hook's ending and `hookReason` is never minted.
8
+ */
9
+ export declare function holdPrompt(ref: BlockedRef, mark: string | undefined, cause: PromptBlockCause, text: string): void;
@@ -0,0 +1,8 @@
1
+ import { formatHookFeedback } from "../hooks.js";
2
+ import { inlineUntrusted } from "../untrusted-text.js";
3
+ const HOOK_REASON_MAX = 240;
4
+ export function holdPrompt(ref, mark, cause, text) {
5
+ ref.reason = formatHookFeedback(text, mark);
6
+ const hookReason = cause === "block" ? inlineUntrusted(String(text), HOOK_REASON_MAX) : "";
7
+ ref.detail = { kind: "prompt_blocked_by_hook", cause, ...(hookReason !== "" ? { hookReason } : {}) };
8
+ }
@@ -1,4 +1,5 @@
1
- import { CheckpointError, readPendingSteerQueue, resolveCheckpointStore, validatePendingSteer } from "../checkpoint-store.js";
1
+ import { CheckpointError, readPendingSteerQueue, refuseRetiredModeSeed, resolveCheckpointStore, validatePendingSteer } from "../checkpoint-store.js";
2
+ import { refuseResumeDoors } from "../mode-policy.js";
2
3
  import { describeSuppliedValue } from "./decide-continuation.js";
3
4
  export async function resumeAdmission(input) {
4
5
  const { token, outcome, taskConfig, runner, parentConstraintRegistry } = input;
@@ -36,6 +37,8 @@ export async function resumeAdmission(input) {
36
37
  const outcomeGate = outcome.gate;
37
38
  const resumeSignal = config.signal;
38
39
  let suppliedMessage;
40
+ refuseRetiredModeSeed(cp.state);
41
+ refuseResumeDoors(config, cp.state.inheritedGate);
39
42
  const parkedSteers = readPendingSteerQueue(cp.state);
40
43
  if (outcomeGate === "wake") {
41
44
  suppliedMessage = outcome.message;
@@ -2,12 +2,13 @@ import { snapshotActorAssertion } from "../../internal/llm.js";
2
2
  import { isAskOrigin } from "../ask-origin.js";
3
3
  import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
4
4
  import { CheckpointError, remainingBudgetMicroUsd } from "../checkpoint-store.js";
5
- import { settledByPerson } from "../gate-outcome.js";
5
+ import { settledByDecider } from "../gate-outcome.js";
6
6
  import { cloneObserverInput, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
7
- import { checkToolPolicyProjection, refuseOutOfContractDecision } from "../tool-policy.js";
7
+ import { checkToolPolicyProjection, policyCannotAnswer, refuseOutOfContractDecision } from "../tool-policy.js";
8
8
  import { callFaceSeat, parkedCallFaceOf } from "../tool-roster.js";
9
9
  import { inlineUntrusted } from "../untrusted-text.js";
10
10
  import { deepJsonEqual, describeSuppliedValue } from "./decide-continuation.js";
11
+ import { orgVerdictBeforeClaim } from "./permission-rule-lanes.js";
11
12
  import { mintGateOutcome } from "./gate-exit.js";
12
13
  import { asThrownError, thrownText } from "../thrown-value.js";
13
14
  import { wireOf } from "../tool-name-readings.js";
@@ -75,6 +76,19 @@ export async function resumeApply(input) {
75
76
  await recheckGovernanceWindow();
76
77
  }
77
78
  }
79
+ const editSignal = config.signal;
80
+ const raceEditAbort = async (p) => {
81
+ if (editSignal === undefined)
82
+ return p;
83
+ if (editSignal.aborted) {
84
+ throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the call before the claim — the checkpoint stays pending and is resumable with a live signal");
85
+ }
86
+ return new Promise((resolve, reject) => {
87
+ const onAbort = () => reject(new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the call before the claim — the checkpoint stays pending and is resumable with a live signal"));
88
+ editSignal.addEventListener("abort", onAbort, { once: true });
89
+ p.then((v) => { editSignal.removeEventListener("abort", onAbort); resolve(v); }, (e) => { editSignal.removeEventListener("abort", onAbort); reject(e); });
90
+ });
91
+ };
78
92
  if (plainPolicyOutcome !== undefined &&
79
93
  plainPolicyOutcome.decision === "allow" &&
80
94
  plainPolicyOutcome.updatedInput !== undefined &&
@@ -94,19 +108,6 @@ export async function resumeApply(input) {
94
108
  ...callFaceSeat(parkedCallFaceOf(cp.pendingAction)),
95
109
  ...(cp.state.handsCwd !== undefined ? { cwd: cp.state.handsCwd } : {}),
96
110
  };
97
- const editSignal = config.signal;
98
- const raceEditAbort = async (p) => {
99
- if (editSignal === undefined)
100
- return p;
101
- if (editSignal.aborted) {
102
- throw new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the approver's edit — the checkpoint stays pending and is resumable with a live signal");
103
- }
104
- return new Promise((resolve, reject) => {
105
- const onAbort = () => reject(new CheckpointError("checkpoint.resume_aborted", "the resume was aborted while re-adjudicating the approver's edit — the checkpoint stays pending and is resumable with a live signal"));
106
- editSignal.addEventListener("abort", onAbort, { once: true });
107
- p.then((v) => { editSignal.removeEventListener("abort", onAbort); resolve(v); }, (e) => { editSignal.removeEventListener("abort", onAbort); reject(e); });
108
- });
109
- };
110
111
  const ig = cp.state.inheritedGate;
111
112
  if (ig?.requiresParentConstraint === true) {
112
113
  const chain = ig.constraintChain;
@@ -148,6 +149,8 @@ export async function resumeApply(input) {
148
149
  throw new CheckpointError("resume.constraint_rejected", `the approver EDITED this call's input and the deployment's resume-edit policy denies the edited args${rechecked.message ? `: ${rechecked.message}` : ""}. ` +
149
150
  "Refused pre-CAS (the checkpoint stays pending and remains decidable)");
150
151
  }
152
+ if (policyCannotAnswer(rechecked, plainPolicyOutcome.hostDecision.decidedBy))
153
+ throw new CheckpointError("checkpoint.invalid_outcome", `the approver EDITED this call's input, the edited args raise a question that requires real approval, and the decision is the deployment's policy — a policy's answer is not a person's (decide the edit as "person", or approve the shown input); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
151
154
  if (rechecked.updatedInput !== undefined && !deepJsonEqual(rechecked.updatedInput, editedArgs)) {
152
155
  throw new CheckpointError("resume.constraint_rejected", `the deployment's resume-edit policy REWROTE the approver's edited args to a different value than was approved — the durable resume cannot execute either the un-adjudicated edit or an unapproved rewrite; ` +
153
156
  `refused pre-CAS (the checkpoint stays pending); re-submit the edited action so the rewrite is adjudicated and approved synchronously`);
@@ -169,6 +172,15 @@ export async function resumeApply(input) {
169
172
  }
170
173
  }
171
174
  }
175
+ const orgProvider = runner.deps.permissionRuleStore;
176
+ if (plainPolicyOutcome?.decision === "allow" && plainPolicyOutcome.hostDecision.decidedBy !== "person" && orgProvider?.partitions.org === true && cp.pendingAction.kind === "tool_approval") {
177
+ const call = { toolName: cp.pendingAction.toolName, args: plainPolicyOutcome.updatedInput !== undefined ? plainPolicyOutcome.updatedInput : cp.pendingAction.args, toolCallId: cp.pendingAction.toolCallId };
178
+ const orgNow = await raceEditAbort(orgVerdictBeforeClaim({ provider: orgProvider, principal: config.principal || cp.principal || undefined, localOwnerDeclared: runner.deps.localOwnerRules === true, sessionId: cp.sessionId, signal: editSignal }, call));
179
+ if (policyCannotAnswer(orgNow, plainPolicyOutcome.hostDecision.decidedBy)) {
180
+ const rule = orgNow.status === "available" ? orgNow.verdict?.rule : undefined;
181
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "${plainPolicyOutcome.hostDecision.decidedBy}" on a call an organization policy rule (${rule}) requires a person's approval for — a policy's answer is not a person's (decide it as "person", or refuse it as "policy"); refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
182
+ }
183
+ }
172
184
  if (plainPolicyOutcome !== undefined) {
173
185
  const parkedOrigin = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction.origin : undefined;
174
186
  if (!isAskOrigin(parkedOrigin)) {
@@ -177,9 +189,7 @@ export async function resumeApply(input) {
177
189
  const facts = plainPolicyOutcome.hostDecision;
178
190
  const settlement = facts.decidedBy === "sla_timeout"
179
191
  ? { kind: "park_sla_expired", who: { party: "host", ...(facts.approver !== undefined ? { approver: facts.approver } : {}) }, when: Date.now() }
180
- : facts.decidedBy === "policy"
181
- ? { kind: "policy_refused", who: { party: "none" }, when: Date.now() }
182
- : settledByPerson(plainPolicyOutcome.decision === "allow" ? "human_allowed" : "human_refused", facts.approver, plainPolicyOutcome.decision === "deny" ? plainPolicyOutcome.reason : undefined);
192
+ : settledByDecider(facts.decidedBy, plainPolicyOutcome.decision === "allow" ? "allowed" : "refused", facts.approver, plainPolicyOutcome.decision === "deny" ? plainPolicyOutcome.reason : undefined);
183
193
  plainPolicyOutcome = { ...plainPolicyOutcome, gateOutcome: mintGateOutcome({ ...(plainPolicyOutcome.decision === "deny" ? { deniedBy: "ask_resolution" } : {}), settled: { settlement, origin: parkedOrigin } }) };
184
194
  }
185
195
  const outcomeForStore = plainPolicyOutcome ?? plainReviewOutcome ?? plainParkOutcome;
@@ -1,6 +1,6 @@
1
1
  import { canonicalizeCapturedPlainData, classifyQuestionOutcome, isLiveQuestionFace } from "../ask-question.js";
2
2
  import { CHECKPOINT_FEATURES, CheckpointError, checkpointVersionOf, winnerFromOutcome } from "../checkpoint-store.js";
3
- import { screenApproverAttribution } from "../tool-policy.js";
3
+ import { policyCannotAnswer, screenApproverAttribution } from "../tool-policy.js";
4
4
  import { sanitizeUntrustedText } from "../untrusted-text.js";
5
5
  import { assertOutcomeText, describeSuppliedValue, pendingContentAskCallId, sameWinner } from "./decide-continuation.js";
6
6
  const REFUSED_DECIDE_KEYS = [
@@ -55,9 +55,15 @@ export function resumePolicyOutcome(input) {
55
55
  if (decidedBy !== "person" && decidedBy !== "sla_timeout" && decidedBy !== "policy") {
56
56
  throw new CheckpointError("checkpoint.invalid_outcome", `resume hostDecision.decidedBy "${describeSuppliedValue(decidedBy)}" is outside the closed set — it is exactly "person", "sla_timeout" or "policy"; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
57
57
  }
58
- if (decidedBy !== "person" && decision === "allow") {
59
- throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "${decidedBy}" — only a person approves an action that EXECUTES, and that word names a decision nobody made; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
60
- }
58
+ const cannotAllow = decision !== "allow"
59
+ ? undefined
60
+ : decidedBy === "sla_timeout"
61
+ ? ` — that word names a wait that ended with nobody answering, and it cannot approve an action that EXECUTES (an allow is decided by "person" or "policy")`
62
+ : policyCannotAnswer(cp, decidedBy)
63
+ ? ` on a row that requires real approval — a policy's answer is not a person's, and this row demands a person (decide it as "person", or refuse it as "policy")`
64
+ : undefined;
65
+ if (cannotAllow !== undefined)
66
+ throw new CheckpointError("checkpoint.invalid_outcome", `resume carries decision "allow" decided by "${decidedBy}"${cannotAllow}; refusing pre-CAS, the checkpoint stays pending`, { field: "hostDecision" });
61
67
  const attribution = screenApproverAttribution(hostDecisionRaw.approver);
62
68
  if (attribution.defect !== undefined) {
63
69
  throw new CheckpointError("checkpoint.invalid_outcome", `resume carries an approver attribution this engine refuses: ${attribution.defect}; refusing pre-CAS, the checkpoint stays pending`, { field: "approver" });
@@ -15,7 +15,7 @@ export async function runAttachmentSeats(input) {
15
15
  rs.attach.skillsListingOn = rs.attach.attachmentsCfg?.skillsListing !== false && eventDefaultOn("skills_listing");
16
16
  const listingsLive = (rs.attach.agentListingOn && prepared.agentListing !== undefined) || (rs.attach.skillsListingOn && prepared.skillsListing !== undefined);
17
17
  const backgroundTasksLive = (rs.attach.attachmentsCfg?.backgroundTasks ?? eventDefaultOn("background_tasks")) === true;
18
- rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive ? createAttachmentState() : undefined;
18
+ rs.attach.attachState = rs.attach.attachmentsCfg !== undefined || listingsLive || backgroundTasksLive || prepared.outputStyleFrame !== undefined ? createAttachmentState() : undefined;
19
19
  rs.attach.dateState = prepared.dateChange !== undefined ? { announcedDate: prepared.dateChange.anchor?.current?.announcedDate ?? prepared.dateChange.legDate } : undefined;
20
20
  rs.attach.instrProbe = runner.deps.probeInstructionSources;
21
21
  rs.attach.instrState =
@@ -2,7 +2,9 @@ import { uuidv7 } from "../../internal/harness.js";
2
2
  import { createSafeNotifier, observeThenableRejection } from "../safe-notify.js";
3
3
  import { recordFrameReceipt, toolResultReceiptKey } from "../frame-settlement.js";
4
4
  import { discloseDroppedPending, renderTaskNotificationXml, taskNotificationDedupKey } from "../task-notification.js";
5
- import { deliverDelegationLifecycle, deliverEngineNotice, undrainedUserInputNotices } from "../types.js";
5
+ import { buildAppliedHumanInputEvent } from "../human-input-projection.js";
6
+ import { announceAcceptance, nextHumanInputSeq } from "./steer-admission.js";
7
+ import { deliverDelegationLifecycle, deliverEngineNotice, parentLegIds, undrainedUserInputNotices } from "../types.js";
6
8
  export function runIdentityWiring(input) {
7
9
  const { spec, queue, prepared, internals, taskIdRef, onReady, manualCompactRef, notifyRef, captureOptOutRef, mcpRedialRef, injectTaskNotification, deliveredAtTurnOpen, peerInboundChainRef, notificationLane, pendingSessionNotifications, runner } = input;
8
10
  notificationLane.harness = prepared.harness;
@@ -59,7 +61,7 @@ export function runIdentityWiring(input) {
59
61
  undrainedUserAtEnd = counts;
60
62
  return;
61
63
  }
62
- for (const notice of undrainedUserInputNotices(counts, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId)) {
64
+ for (const notice of undrainedUserInputNotices(counts, spec.taskId ?? prepared.sessionId, prepared.sessionId, prepared.runId, parentLegIds(internals))) {
63
65
  deliverEngineNotice(runner.deps.onNotice, notice);
64
66
  }
65
67
  };
@@ -71,6 +73,11 @@ export function runIdentityWiring(input) {
71
73
  if (notificationLane.sessionId !== undefined && typeof payload?.task_id === "string" && typeof payload.status === "string")
72
74
  pendingSessionNotifications.markDelivered(notificationLane.sessionId, taskNotificationDedupKey(payload));
73
75
  };
76
+ prepared.harness.onUserInputConsumed = (record) => {
77
+ const steer = record;
78
+ const identity = announceAcceptance(steer);
79
+ queue.push({ ...buildAppliedHumanInputEvent({ carrier: "steer", source: "steer", sessionSeq: nextHumanInputSeq(prepared.harness), inputId: steer.inputId, actor: steer.actor, principal: spec.principal }), eventId: uuidv7(), ...identity });
80
+ };
74
81
  prepared.harness.onFrameCommitted = (carrier, entryId) => {
75
82
  const seam = prepared.frameSettlement;
76
83
  if (seam.awaiting.size === 0)