@sema-agent/core 5.34.0 → 5.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/dist/agents/subagent.js +29 -2
  3. package/dist/core/auto-compaction.d.ts +23 -0
  4. package/dist/core/auto-compaction.js +8 -0
  5. package/dist/core/checkpoint-store.d.ts +49 -4
  6. package/dist/core/context-guard.d.ts +41 -0
  7. package/dist/core/context-guard.js +76 -0
  8. package/dist/core/hooks.d.ts +98 -3
  9. package/dist/core/hooks.js +146 -8
  10. package/dist/core/memory-engine/engine.js +1 -1
  11. package/dist/core/park-selfcheck.d.ts +161 -0
  12. package/dist/core/park-selfcheck.js +251 -0
  13. package/dist/core/runner/assemble-result.d.ts +3 -0
  14. package/dist/core/runner/assemble-result.js +3 -0
  15. package/dist/core/runner/git-status-frame.d.ts +219 -0
  16. package/dist/core/runner/git-status-frame.js +212 -0
  17. package/dist/core/runner/prepare-acquire-reconcile.d.ts +6 -0
  18. package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
  19. package/dist/core/runner/prepare-task.d.ts +28 -4
  20. package/dist/core/runner/prepare-task.js +86 -52
  21. package/dist/core/runner/runtask.d.ts +6 -1
  22. package/dist/core/runner/runtask.js +330 -19
  23. package/dist/core/task-registry-agent.d.ts +15 -0
  24. package/dist/core/task-registry-agent.js +9 -0
  25. package/dist/core/task-registry.d.ts +3 -0
  26. package/dist/core/task-registry.js +4 -1
  27. package/dist/core/tool-errors.d.ts +2 -2
  28. package/dist/core/tool-policy.d.ts +125 -0
  29. package/dist/core/tool-policy.js +35 -2
  30. package/dist/core/types.d.ts +98 -9
  31. package/dist/engine/harness/types.d.ts +65 -1
  32. package/dist/engine/harness/types.js +20 -0
  33. package/dist/engine/session/import-validate.js +10 -1
  34. package/dist/engine/session/session.d.ts +37 -1
  35. package/dist/engine/session/session.js +56 -1
  36. package/dist/index.d.ts +3 -2
  37. package/dist/index.js +3 -2
  38. package/dist/internal/harness-types.d.ts +1 -0
  39. package/dist/internal/harness.d.ts +2 -0
  40. package/dist/internal/harness.js +2 -0
  41. package/dist/orchestration/workflow.d.ts +1 -1
  42. package/dist/prompt-assembly/epoch.js +1 -1
  43. package/dist/prompt-assembly/event-registry.js +1 -0
  44. package/dist/prompts/default.d.ts +20 -7
  45. package/dist/prompts/default.js +2 -7
  46. package/package.json +1 -1
  47. package/test/export-surface.snapshot.json +17 -1
@@ -0,0 +1,212 @@
1
+ import { createHash } from "node:crypto";
2
+ import { GIT_SNAPSHOT_CC_PREAMBLE, buildGitSnapshot } from "../../prompts/default.js";
3
+ import { inlineUntrusted } from "../untrusted-text.js";
4
+ import { engineRegionCovers, protectGitFrame } from "../context-guard.js";
5
+ import { gitFrameContextVisible } from "../../internal/harness.js";
6
+ export const GIT_STATUS_FRAME_FORMAT_VERSION = 1;
7
+ export const GIT_STATUS_FRAME_PREAMBLE = "This is the git status observed while preparing this request. When the visible snapshot changes it will be re-sent in a later message — the most recent git status frame supersedes earlier ones.";
8
+ export const GIT_STATUS_UNAVAILABLE_BODY = "Git status is currently unavailable; the most recent git status frame above may be stale.";
9
+ export const GIT_STATUS_NON_REPO_BODY = "The working directory is no longer a git repository; earlier git status frames no longer apply.";
10
+ export const GIT_STATUS_ECHO_PREVIEW = {
11
+ full: "git status updated",
12
+ degraded: "git status updated (degraded: branch and dirtiness only)",
13
+ unavailable: "git status unavailable",
14
+ "non-repo": "git status: no longer a git repository",
15
+ };
16
+ function toWellFormedText(s) {
17
+ const native = s.toWellFormed;
18
+ if (typeof native === "function")
19
+ return native.call(s);
20
+ return s.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "�");
21
+ }
22
+ export function renderGitStatusFrameBody(outcome) {
23
+ switch (outcome.kind) {
24
+ case "full": {
25
+ const body = outcome.snapshot.startsWith(GIT_SNAPSHOT_CC_PREAMBLE)
26
+ ? GIT_STATUS_FRAME_PREAMBLE + outcome.snapshot.slice(GIT_SNAPSHOT_CC_PREAMBLE.length)
27
+ : `${GIT_STATUS_FRAME_PREAMBLE}\n\n${outcome.snapshot}`;
28
+ return toWellFormedText(body);
29
+ }
30
+ case "degraded": {
31
+ const lines = [GIT_STATUS_FRAME_PREAMBLE];
32
+ lines.push(`Current branch: ${inlineUntrusted(outcome.branch ?? "HEAD")}`);
33
+ if (outcome.dirty !== undefined) {
34
+ lines.push(`Git working tree: ${outcome.dirty ? "has uncommitted changes" : "clean"}`);
35
+ }
36
+ return toWellFormedText(lines.join("\n\n"));
37
+ }
38
+ case "unavailable":
39
+ return GIT_STATUS_UNAVAILABLE_BODY;
40
+ case "non-repo":
41
+ return GIT_STATUS_NON_REPO_BODY;
42
+ }
43
+ }
44
+ export function hashGitStatusFrame(kind, body, canonicalRoot) {
45
+ return `sha256:${createHash("sha256")
46
+ .update(`${GIT_STATUS_FRAME_FORMAT_VERSION}\u0000${canonicalRoot}\u0000${kind}\u0000${body}`)
47
+ .digest("hex")}`;
48
+ }
49
+ export function resolveGitStatusFrame(outcome, canonicalRoot, degradedMaterial) {
50
+ const body = renderGitStatusFrameBody(outcome);
51
+ const resolved = {
52
+ kind: outcome.kind,
53
+ body,
54
+ hash: hashGitStatusFrame(outcome.kind, body, canonicalRoot),
55
+ };
56
+ if (outcome.kind === "full") {
57
+ const shrunkBody = renderGitStatusFrameBody({ kind: "degraded", ...(degradedMaterial ?? {}) });
58
+ resolved.shrunk = { body: shrunkBody, hash: hashGitStatusFrame("degraded", shrunkBody, canonicalRoot) };
59
+ }
60
+ return resolved;
61
+ }
62
+ export async function probeGitStatusLane(args) {
63
+ const { executionEnv, envFacts, handsEnabled, taskRoot, onDegrade } = args;
64
+ const ref = {};
65
+ if (!handsEnabled)
66
+ return ref;
67
+ let outcome;
68
+ if (envFacts.isGitRepo === true) {
69
+ const SEP = "@@SEMA_ENV_GIT_SPLIT@@";
70
+ outcome = {
71
+ kind: "degraded",
72
+ ...(envFacts.gitBranch !== undefined ? { branch: envFacts.gitBranch } : {}),
73
+ ...(envFacts.gitDirty !== undefined ? { dirty: envFacts.gitDirty } : {}),
74
+ };
75
+ try {
76
+ const snap = await executionEnv.exec(`(m=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null); m=\${m#origin/}; for s in "$m" main master; do if [ -n "$s" ] && git show-ref --verify --quiet "refs/remotes/origin/$s"; then echo "$s"; break; fi; done) || true; echo "${SEP}"; (git config user.name 2>/dev/null || true); echo "${SEP}"; gs=$(git --no-optional-locks status --short) || exit 41; printf '%s\\n' "$gs"; echo "${SEP}"; gl=$(git --no-optional-locks log --oneline -n 5) || exit 42; printf '%s\\n' "$gl"`, { cwd: envFacts.cwd, timeout: 10 });
77
+ if (snap.ok && snap.value.exitCode === 0) {
78
+ const parts = snap.value.stdout.split(`${SEP}\n`);
79
+ if (parts.length === 4) {
80
+ outcome = {
81
+ kind: "full",
82
+ snapshot: buildGitSnapshot({
83
+ branch: envFacts.gitBranch ?? "HEAD",
84
+ mainBranch: parts[0].trim() || "main",
85
+ ...(parts[1].trim() ? { userName: parts[1].trim() } : {}),
86
+ status: parts[2],
87
+ log: parts[3],
88
+ }),
89
+ };
90
+ }
91
+ else {
92
+ onDegrade(`sentinel mis-split (${parts.length} sections, expected 4)`);
93
+ }
94
+ }
95
+ else {
96
+ onDegrade(snap.ok
97
+ ? snap.value.exitCode === 41
98
+ ? "git status failed (exit 41)"
99
+ : snap.value.exitCode === 42
100
+ ? "git log failed (exit 42)"
101
+ : `git exited ${snap.value.exitCode}`
102
+ : `exec failed: ${snap.error.message}`);
103
+ }
104
+ }
105
+ catch (err) {
106
+ onDegrade(err instanceof Error ? err.message : String(err));
107
+ }
108
+ }
109
+ else if (envFacts.isGitRepo === false) {
110
+ outcome = { kind: "non-repo" };
111
+ }
112
+ else {
113
+ outcome = { kind: "unavailable" };
114
+ }
115
+ const rootRaw = envFacts.gitWorktreeRoot ?? envFacts.cwd ?? taskRoot;
116
+ let canonicalRoot = rootRaw;
117
+ try {
118
+ const c = await executionEnv.canonicalPath(rootRaw);
119
+ if (c.ok)
120
+ canonicalRoot = c.value;
121
+ }
122
+ catch {
123
+ }
124
+ ref.canonicalRoot = canonicalRoot;
125
+ ref.frame = resolveGitStatusFrame(outcome, canonicalRoot, {
126
+ ...(envFacts.gitBranch !== undefined ? { branch: envFacts.gitBranch } : {}),
127
+ ...(envFacts.gitDirty !== undefined ? { dirty: envFacts.gitDirty } : {}),
128
+ });
129
+ return ref;
130
+ }
131
+ export function applyGitFrameGuard(args) {
132
+ const { before, trimmed, budgetTokens, ref, charsPerToken, onDegrade } = args;
133
+ if (ref.protectedText === undefined)
134
+ return trimmed;
135
+ const guarded = protectGitFrame(before, trimmed, budgetTokens, {
136
+ protectedText: ref.protectedText,
137
+ ...(ref.frame?.kind === "full" && ref.frame.shrunk !== undefined && ref.wrappedShrink !== undefined ? { substitute: ref.wrappedShrink } : {}),
138
+ }, charsPerToken);
139
+ if (guarded.action === "over_budget") {
140
+ ref.terminalCode = "irreducible_core_over_budget";
141
+ throw Object.assign(new Error("irreducible core over budget: the compaction summary plus the git status frame alone exceed the request budget — no trim can produce an honest request"), { code: "irreducible_core_over_budget" });
142
+ }
143
+ if (guarded.action === "shrunk" && !ref.overBudgetShrunk) {
144
+ ref.overBudgetShrunk = true;
145
+ if (ref.announced !== undefined)
146
+ ref.announced = { ...ref.announced, pending: true };
147
+ onDegrade("git status frame degraded under budget pressure: the full snapshot no longer fits the request budget — requests carry the branch+dirty residual; the degraded view will be re-announced");
148
+ }
149
+ return guarded.messages;
150
+ }
151
+ export function newestEngineGitFrame(branch) {
152
+ const positiveHead = `<system-reminder>\n${GIT_STATUS_FRAME_PREAMBLE}`;
153
+ const tombUnits = [`<system-reminder>\n${GIT_STATUS_UNAVAILABLE_BODY}\n</system-reminder>`, `<system-reminder>\n${GIT_STATUS_NON_REPO_BODY}\n</system-reminder>`];
154
+ const topLevel = (text, at) => {
155
+ if (at === 0)
156
+ return true;
157
+ const before = text.slice(0, at).replace(/\n+$/, "");
158
+ return before === "" || before.endsWith("</system-reminder>");
159
+ };
160
+ const allTopLevel = (text, needle, m) => {
161
+ const out = [];
162
+ for (let at = text.indexOf(needle); at !== -1; at = text.indexOf(needle, at + 1)) {
163
+ if (topLevel(text, at) && engineRegionCovers(m, at, needle.length))
164
+ out.push(at);
165
+ }
166
+ return out;
167
+ };
168
+ for (let i = branch.length - 1; i >= 0; i--) {
169
+ const entry = branch[i];
170
+ if (entry === undefined || entry.type !== "message")
171
+ continue;
172
+ const m = entry.message;
173
+ if (m.role !== "user")
174
+ continue;
175
+ const c = m.content;
176
+ const text = typeof c === "string" ? c : Array.isArray(c) && c.length >= 1 && c[0].type === "text" ? (c[0].text ?? "") : undefined;
177
+ if (text === undefined)
178
+ continue;
179
+ let best;
180
+ for (const at of allTopLevel(text, positiveHead, m)) {
181
+ if (best === undefined || at > best.at)
182
+ best = { at, positive: true };
183
+ }
184
+ for (const unit of tombUnits) {
185
+ for (const at of allTopLevel(text, unit, m)) {
186
+ if (best === undefined || at > best.at)
187
+ best = { at, positive: false };
188
+ }
189
+ }
190
+ if (best !== undefined)
191
+ return { entryId: entry.id, positive: best.positive };
192
+ }
193
+ return undefined;
194
+ }
195
+ export function stripGitStatusUnits(text) {
196
+ const head = `<system-reminder>\n${GIT_STATUS_FRAME_PREAMBLE}`;
197
+ const close = "</system-reminder>";
198
+ let out = text;
199
+ for (let at = out.indexOf(head); at !== -1; at = out.indexOf(head)) {
200
+ const end = out.indexOf(close, at);
201
+ if (end === -1)
202
+ break;
203
+ if (out.slice(at + head.length, end).includes("<system-reminder>"))
204
+ break;
205
+ out = out.slice(0, at) + out.slice(end + close.length);
206
+ }
207
+ return out;
208
+ }
209
+ export function branchCarriesVisiblePositiveGitFrame(branch) {
210
+ const newest = newestEngineGitFrame(branch);
211
+ return newest !== undefined && newest.positive && gitFrameContextVisible(branch, newest.entryId);
212
+ }
@@ -40,6 +40,12 @@ export interface PrepareAcquireReconcileInput {
40
40
  * {@link import("./prepare-safety-scan.js").PrepareSafetyScanResult}); reconcile classifies an
41
41
  * orphaned call's retry-safety by it. */
42
42
  toolEffects: Map<string, ToolEffect>;
43
+ /** borrowed-readonly — design/252 G-6 sibling: the durable-park TOPOLOGY gap sentence for this
44
+ * deployment (`durableParkGapFor`), or `undefined` when the topology is whole / does not apply.
45
+ * Read ONLY on the fail-loud missing-session path below, where it turns a symptom into a named
46
+ * absent seat; nothing in the phase's control flow depends on it. Computed by the driver (which
47
+ * holds `deps`) rather than here, keeping this slice's input the narrow Pick its contract says. */
48
+ durableParkGap?: string;
43
49
  }
44
50
  /** The phase's outputs (design/238 相 API 规则件 four-class form). All five are fresh bindings —
45
51
  * the driver destructures them into consts, so a consumer moved ahead of this call is a lexical
@@ -39,7 +39,8 @@ export async function prepareAcquireReconcile(input) {
39
39
  }
40
40
  catch (err) {
41
41
  if (spec.requireExistingSession && err?.code === "not_found") {
42
- const e = new Error(`requireExistingSession: session "${spec.sessionId}" does not exist — refusing a silent fresh run (design/114 Phase3)`);
42
+ const e = new Error(`requireExistingSession: session "${spec.sessionId}" does not exist — refusing a silent fresh run (design/114 Phase3)` +
43
+ (input.durableParkGap !== undefined ? `. Note the assembly: ${input.durableParkGap}` : ""));
43
44
  e.code = "resume.session_not_found";
44
45
  throw e;
45
46
  }
@@ -17,6 +17,7 @@ import { CacheBreakDetector, type ToolFingerprintInput } from "../cache-break-de
17
17
  import { type BrainCallGuardrailRef } from "../../brain/timeout.js";
18
18
  import { type OutputRef, type BlockedRef, type SkillListingEntry } from "./synthetic-tools.js";
19
19
  import type { MemoryEngine } from "../memory-engine/engine.js";
20
+ import { type GitStatusLaneRef } from "./git-status-frame.js";
20
21
  import { type ToolManifestRow } from "../../prompt-assembly/tool-catalog.js";
21
22
  import type { ToolDisclosureManifest } from "../trace.js";
22
23
  import type { TaskNotificationPayload } from "../task-notification.js";
@@ -169,9 +170,14 @@ export interface Prepared {
169
170
  * emits; the tool_result-side delete in prepare-task never fires for immediate results). */
170
171
  blockedToolCalls: Set<string>;
171
172
  /**
172
- * What ended the approval a gated call was waiting on, keyed by tool-call id written ONLY by the
173
- * tool gate, at the one exit where an ask resolved, and read once when that call's `tool_end` frame
174
- * is minted (the reader deletes on read; a call the gate never settled has no entry).
173
+ * WHAT ended the approval a gated call was waiting on and design/252 G-7WHOSE settlement it
174
+ * was, keyed by tool-call id: written ONLY by the tool gate, at the one exit where an ask resolved,
175
+ * and read once when that call's `tool_end` frame is minted (the reader deletes on read; a call the
176
+ * gate never settled has no entry, and an entry never names neither fact).
177
+ *
178
+ * ONE record rather than two parallel maps because they are one observation: an attribution without
179
+ * the settlement kind beside it is unreadable ("alice" — approved? her window elapsed?), and two maps
180
+ * keyed alike are two chances to drain one and leak the other.
175
181
  *
176
182
  * It is a sideband and not a field on the tool RESULT because a result is not a trustworthy carrier
177
183
  * for this: `details` is arbitrary tool-authored data that post-tool hooks may also replace, so a
@@ -180,7 +186,10 @@ export interface Prepared {
180
186
  * adjudicating layer can write. Same reason the entries are keyed by CALL id: the gate adjudicated
181
187
  * that exact call, and the frame that reads it is that call's own.
182
188
  */
183
- approvalSettledBy: Map<string, import("../tool-policy.js").ApprovalSettledBy>;
189
+ approvalSettlement: Map<string, {
190
+ settledBy?: import("../tool-policy.js").ApprovalSettledBy;
191
+ approver?: string;
192
+ }>;
184
193
  /** Summed usage of nested sub-runs (sub-agents) spawned by this task's tools. */
185
194
  nestedStats: NestedUsageAccum;
186
195
  /** RB-430-a: prepare-time rewind disclosures (conversation-only branch / no snapshot backend / no file
@@ -739,6 +748,12 @@ export interface Prepared {
739
748
  skills?: readonly string[];
740
749
  models?: readonly string[];
741
750
  };
751
+ /** env-tail migration (#254 shape) — the git-status frame lane's run-local state: this leg's
752
+ * resolved frame (probe outcome rendered + hashed at prepare), the announced `(kind, hash)`
753
+ * mirror the checkpoint serializer reads, the trim-protection slot the request-build context
754
+ * handler matches on, and the re-assert closure the compaction landing + boundary retry call.
755
+ * Always present (empty object on a hands-less leg — the lane is then out of scope). */
756
+ gitStatusRef: GitStatusLaneRef;
742
757
  /** G1 通告层 — narrow post-compact getter over the process task registry: THIS run's visible
743
758
  * pending/running background tasks (same owner/scope/session identity the TaskOutput/TaskStop tools
744
759
  * use), as a bounded display projection (id/description/status — never handles/env/abort). Called by
@@ -1236,6 +1251,15 @@ export interface RunInternals {
1236
1251
  * stamping. TRUSTED run-scoped channel (never a {@link TaskSpec} field).
1237
1252
  */
1238
1253
  delegationTaskType?: import("../types.js").DelegationTaskType;
1254
+ /**
1255
+ * #258 — the registry row's stop-cycle generation this run executes as (fresh spawn = 1, a
1256
+ * revival's bumped counter), threaded by the BACKGROUND delegation lanes from the registry's own
1257
+ * `cycleSeq` so every `task_progress` tick the run mints carries it as `seq` (same axis as
1258
+ * `TaskNotificationPayload.seq` / `BackgroundChildEvent.seq`). Absent for runs with no `a*` row
1259
+ * (sync children, workflow agents, top-level) — same absence-is-a-fact posture as
1260
+ * {@link delegationTaskType} above. TRUSTED run-scoped channel (never a TaskSpec field).
1261
+ */
1262
+ cycleSeq?: number;
1239
1263
  /** δ 批 [1498]⑦/A-3 — the ROOT host session of the whole delegation tree (fixed point: the
1240
1264
  * spawner passes its own `ctx.rootSessionId ?? ctx.sessionId`, so depth 1 gets the host session
1241
1265
  * and every deeper level inherits it verbatim). `parentSessionId` is the IMMEDIATE spawner —
@@ -20,7 +20,7 @@ import { createSubagentWorktreeHelper, forkGovernanceDenial } from "../../agents
20
20
  import { createAgentTranscriptTool, AGENT_TRANSCRIPT_TOOL_NAME } from "../../agents/agent-transcript-tool.js";
21
21
  import { createSendMessageTool, SEND_MESSAGE_TOOL_NAME } from "../../agents/send-message-tool.js";
22
22
  import { SubagentRetainLedger } from "../../agents/retain-ledger.js";
23
- import { askApproverIdentity, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
23
+ import { askApproverIdentity, checkToolPolicyProjection, combinePolicies, constraintChainDigest, constraintChainEntryOf, isApprovalSettledBy, screenApproverAttribution, createTranscriptIntegrityPolicy, createUnverifiableDeletePolicy, describeThrown, refuseOutOfContractDecision, resolveAsk, toolPolicyNameSets, tryCloneArgs } from "../tool-policy.js";
24
24
  const PERSISTED_RULE_TOOL = "Bash";
25
25
  import { findAdmittingRule, suggestRulesForCommand } from "../permission-rule-model.js";
26
26
  import { ActiveSkillScope, createActiveSkillScopePolicy } from "./active-skill-scope.js";
@@ -56,7 +56,8 @@ import { limitConfigError, prepareConfigDoors } from "./prepare-config-doors.js"
56
56
  import { NAMESPACED_NAME_SHAPES, prepareSafetyScan } from "./prepare-safety-scan.js";
57
57
  import { prepareAcquireReconcile } from "./prepare-acquire-reconcile.js";
58
58
  import { prepareWorkspaceRestore, rebaseWorkspacePath, remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./prepare-workspace-restore.js";
59
- import { defaultPromptProvider, buildEnvironmentContext, buildGitSnapshot, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
59
+ import { defaultPromptProvider, buildEnvironmentContext, formatLocalDate, isValidTimeZone, PROJECT_CONTEXT_FRAMING } from "../../prompts/default.js";
60
+ import { applyGitFrameGuard, probeGitStatusLane } from "./git-status-frame.js";
60
61
  import { assemblePrompt } from "../../prompt-assembly/assemble.js";
61
62
  import { auditToolCollisions, getToolContract, projectToolManifest } from "../../prompt-assembly/tool-catalog.js";
62
63
  import { resolveEpochAgainstBundled } from "../../prompt-assembly/epoch.js";
@@ -88,6 +89,7 @@ import { wholeFileRecordsFromTranscript } from "./session-file-state-replay.js";
88
89
  import { BINDING_CHECKPOINT_VERSION, mintCheckpointToken, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, FACE_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, buildRiskDescriptor, debitLedger, encodeAtFidelity, remainingBudgetMicroUsd, resolveCheckpointStore, resolveDeclaredFidelity, samePlainValue, } from "../checkpoint-store.js";
89
90
  import { boundInputHashOf } from "../canonical-json.js";
90
91
  import { countElicitOptIns, deriveWiringManifest, resolveAskSeamForm, resolveDeclaredDurability, resolveElicitSeam, resolveQuestionSeam } from "../wiring-manifest.js";
92
+ import { durableParkGapFor } from "../park-selfcheck.js";
91
93
  import { GLOBAL_USAGE_KEY, usageRetryAfterMs } from "../usage-window-store.js";
92
94
  import { deliverEngineNotice } from "../types.js";
93
95
  const announcedMaterializeEnv = new Set();
@@ -192,6 +194,46 @@ async function forgetQuietly(sessions, sessionId) {
192
194
  catch {
193
195
  }
194
196
  }
197
+ function screenGateSettlement(result, settling) {
198
+ const defects = [];
199
+ const reportedBy = result.settledBy;
200
+ let settledBy;
201
+ if (reportedBy !== undefined) {
202
+ if (!isApprovalSettledBy(reportedBy) || (!settling && reportedBy !== "human")) {
203
+ defects.push(`a tool-gate settlement reported settledBy "${String(reportedBy)}" on ${settling ? "a blocked" : "an executing"} call — ` +
204
+ `it is one of "human" / "timeout" / "aborted", and only "human" can be the source of a call that runs; the frame carries no source`);
205
+ }
206
+ else
207
+ settledBy = reportedBy;
208
+ }
209
+ const attribution = screenApproverAttribution(result.approver);
210
+ if (attribution.defect !== undefined) {
211
+ defects.push(`a tool-gate settlement reported an attribution this engine refuses: ${attribution.defect}; the frame carries no approver`);
212
+ }
213
+ const record = {
214
+ ...(settledBy !== undefined ? { settledBy } : {}),
215
+ ...(attribution.approver !== undefined ? { approver: attribution.approver } : {}),
216
+ };
217
+ return { ...(settledBy !== undefined || attribution.approver !== undefined ? { record } : {}), defects };
218
+ }
219
+ function inheritedAskRuleEvidence(deps) {
220
+ const org = deps.permissionRuleOrg === undefined ? "not_wired" : "not_adjudicated";
221
+ const personal = deps.permissionRuleStore === undefined ? "not_wired" : "not_adjudicated";
222
+ return Object.freeze({ orgRevisionAbsent: org, orgRuleAbsent: org, personalRuleDotsAbsent: personal });
223
+ }
224
+ function orgRevisionEvidenceOf(resolution, onDefect) {
225
+ const reported = resolution.revision;
226
+ if (reported === undefined)
227
+ return {};
228
+ if (typeof reported === "number" && Number.isFinite(reported))
229
+ return { revision: reported };
230
+ onDefect(`the org rule overlay reported revision ${JSON.stringify(reported)} — a snapshot revision is a finite number; ` +
231
+ `the adjudication stands, but the ask carries no revision evidence for this call`);
232
+ return {};
233
+ }
234
+ function persistedRuleHitOf(admitting) {
235
+ return admitting === undefined ? undefined : { rule: admitting.rule, dots: admitting.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) };
236
+ }
195
237
  export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
196
238
  const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
197
239
  spec = doors.spec;
@@ -204,7 +246,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
204
246
  e.code = "resume.session_not_found";
205
247
  throw e;
206
248
  }
207
- const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects });
249
+ const { acquired, session, conflictRef, wakeRecovered, resumeAtBeforeParentId } = await prepareAcquireReconcile({ sessions, spec, resume, toolEffects, ...(() => { const g = durableParkGapFor(deps, spec); return g !== undefined ? { durableParkGap: g } : {}; })() });
208
250
  const sessionId = acquired.sessionId;
209
251
  const hostTaskId = spec.taskId ?? sessionId;
210
252
  if (compModel !== undefined) {
@@ -1834,38 +1876,14 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
1834
1876
  }
1835
1877
  catch {
1836
1878
  }
1837
- if (envFacts.isGitRepo === true) {
1838
- const SEP = "@@SEMA_ENV_GIT_SPLIT@@";
1839
- try {
1840
- const snap = await executionEnv.exec(`(m=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null); m=\${m#origin/}; for s in "$m" main master; do if [ -n "$s" ] && git show-ref --verify --quiet "refs/remotes/origin/$s"; then echo "$s"; break; fi; done) || true; echo "${SEP}"; (git config user.name 2>/dev/null || true); echo "${SEP}"; gs=$(git --no-optional-locks status --short) || exit 41; printf '%s\\n' "$gs"; echo "${SEP}"; gl=$(git --no-optional-locks log --oneline -n 5) || exit 42; printf '%s\\n' "$gl"`, { cwd: envFacts.cwd, timeout: 10 });
1841
- if (snap.ok && snap.value.exitCode === 0) {
1842
- const parts = snap.value.stdout.split(`${SEP}\n`);
1843
- if (parts.length === 4) {
1844
- envFacts.gitSnapshot = buildGitSnapshot({
1845
- branch: envFacts.gitBranch ?? "HEAD",
1846
- mainBranch: parts[0].trim() || "main",
1847
- ...(parts[1].trim() ? { userName: parts[1].trim() } : {}),
1848
- status: parts[2],
1849
- log: parts[3],
1850
- });
1851
- }
1852
- }
1853
- else {
1854
- const reason = snap.ok
1855
- ? snap.value.exitCode === 41
1856
- ? "git status failed (exit 41)"
1857
- : snap.value.exitCode === 42
1858
- ? "git log failed (exit 42)"
1859
- : `git exited ${snap.value.exitCode}`
1860
- : `exec failed: ${snap.error.message}`;
1861
- deps.onError?.(new Error(`env git snapshot skipped — ${reason}`), { phase: "degraded", sessionId, classification: "env-git-snapshot" });
1862
- }
1863
- }
1864
- catch (err) {
1865
- deps.onError?.(new Error(`env git snapshot skipped — ${err instanceof Error ? err.message : String(err)}`), { phase: "degraded", sessionId, classification: "env-git-snapshot" });
1866
- }
1867
- }
1868
1879
  }
1880
+ const gitStatusRef = await probeGitStatusLane({
1881
+ executionEnv,
1882
+ envFacts,
1883
+ handsEnabled,
1884
+ taskRoot: taskRootFinal,
1885
+ onDegrade: (reason) => deps.onError?.(new Error(`env git snapshot degraded — ${reason}`), { phase: "degraded", sessionId, classification: "env-git-snapshot" }),
1886
+ });
1869
1887
  if (toolFaceSnapshot.exclude !== undefined && toolFaceSnapshot.exclude.length > 0) {
1870
1888
  const excluded = new Set(toolFaceSnapshot.exclude);
1871
1889
  for (let i = tools.length - 1; i >= 0; i--)
@@ -2778,6 +2796,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2778
2796
  return {};
2779
2797
  return { riskAxes: { ...(irreversible !== undefined ? { irreversible } : {}), ...(egress !== undefined ? { egress } : {}) } };
2780
2798
  };
2799
+ const inheritedAskEvidence = inheritedAskRuleEvidence(deps);
2781
2800
  const permissionRuleLane = (() => {
2782
2801
  const provider = deps.permissionRuleStore;
2783
2802
  const localOwnerDeclared = deps.localOwnerRules === true;
@@ -2817,9 +2836,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2817
2836
  message: err instanceof Error ? err.message : String(err),
2818
2837
  ts: Date.now(),
2819
2838
  }));
2820
- return undefined;
2839
+ return { unreadable: true };
2821
2840
  }
2822
- return findAdmittingRule(listed.rules, { tool: req.toolName, command, cwd: root })?.rule;
2841
+ return persistedRuleHitOf(findAdmittingRule(listed.rules, { tool: req.toolName, command, cwd: root }));
2823
2842
  },
2824
2843
  };
2825
2844
  })();
@@ -2840,11 +2859,12 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2840
2859
  }
2841
2860
  if (resolution.status === "unavailable")
2842
2861
  return { status: "unavailable", disclosures: resolution.disclosures };
2862
+ const revisionCell = orgRevisionEvidenceOf(resolution, (message) => deps.onError?.(new Error(message), { phase: "config", sessionId }));
2843
2863
  const command = req.args?.command;
2844
2864
  if (req.toolName !== PERSISTED_RULE_TOOL || typeof command !== "string")
2845
- return { status: "available" };
2865
+ return { status: "available", ...revisionCell };
2846
2866
  const verdict = orgRuleVerdictFor(resolution.rules, { tool: req.toolName, command });
2847
- return verdict === undefined ? { status: "available" } : { status: "available", verdict };
2867
+ return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
2848
2868
  },
2849
2869
  };
2850
2870
  })();
@@ -2926,6 +2946,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
2926
2946
  ...riskAxesOf(creq.toolName),
2927
2947
  ...(re.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
2928
2948
  ...(re.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: re.persistedRuleShadowed } : {}),
2949
+ ruleEvidence: inheritedAskEvidence,
2929
2950
  }, onAskOf, csignal ?? abortController.signal);
2930
2951
  if (rr.action !== "allow")
2931
2952
  return rr;
@@ -3011,6 +3032,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3011
3032
  ...riskAxesOf(creq.toolName),
3012
3033
  ...(first.action === "ask" && first.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
3013
3034
  ...(first.action === "ask" && first.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: first.persistedRuleShadowed } : {}),
3035
+ ruleEvidence: inheritedAskEvidence,
3014
3036
  }, pc.onAsk, csignal ?? abortController.signal);
3015
3037
  const askWaitMs = Math.max(0, now() - askT0);
3016
3038
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
@@ -3098,6 +3120,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3098
3120
  ...riskAxesOf(creq.toolName),
3099
3121
  ...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
3100
3122
  ...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
3123
+ ruleEvidence: inheritedAskEvidence,
3101
3124
  }, pc.onAsk, csignal ?? abortController.signal);
3102
3125
  const askWaitMs = Math.max(0, now() - askT0);
3103
3126
  if (resolved.action === "deny" && resolved.approverUnavailable === true) {
@@ -3239,7 +3262,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3239
3262
  }
3240
3263
  const preToolContexts = new Map();
3241
3264
  const blockedToolCalls = new Set();
3242
- const approvalSettledBy = new Map();
3265
+ const approvalSettlement = new Map();
3243
3266
  const blockedTracked = Boolean(hooks?.postToolUse || hooks?.preToolUse || hooks?.postToolUseFailure || hooks?.postToolBatch);
3244
3267
  const restoreSurfaceGap = ownedEnv !== undefined && isRemoteExecutionEnv(ownedEnv) && ownedEnv.capabilities.suspendable ? missingRestoreSurface(ownedEnv) : [];
3245
3268
  const incompleteSuspendAdapter = restoreSurfaceGap.length > 0 ? restoreSurfaceGap : undefined;
@@ -3413,6 +3436,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3413
3436
  ...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
3414
3437
  ...(decision.action === "ask" && decision.probeReason !== undefined ? { probeReason: decision.probeReason } : {}),
3415
3438
  ...(decision.action === "ask" && decision.probeCause !== undefined ? { probeCause: decision.probeCause } : {}),
3439
+ ...(decision.action === "ask" && decision.ruleEvidence !== undefined ? { ruleEvidence: decision.ruleEvidence } : {}),
3416
3440
  }, onAsk, abortController.signal);
3417
3441
  const waitMs = Math.max(0, now() - t0);
3418
3442
  if (resolved.approverUnavailable !== true) {
@@ -3530,6 +3554,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
3530
3554
  ...(announcedListingsRef.models !== undefined ? { models: [...announcedListingsRef.models] } : {}),
3531
3555
  }
3532
3556
  : undefined,
3557
+ gitAnnouncement: gitStatusRef.announced !== undefined ? { ...gitStatusRef.announced } : undefined,
3533
3558
  delegationProvenance: internals?.delegationProvenance !== undefined ? { ...internals.delegationProvenance.ref.current } : undefined,
3534
3559
  });
3535
3560
  const commitSuspendSaga = async (token, cp, remoteEnv, remoteHandle) => {
@@ -4230,17 +4255,11 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4230
4255
  if (blockedTracked && (result.block || result.suspend)) {
4231
4256
  blockedToolCalls.add(e.toolCallId);
4232
4257
  }
4233
- const reported = result.settledBy;
4234
- if (reported !== undefined) {
4235
- const settling = result.block === true;
4236
- if (!isApprovalSettledBy(reported) || (!settling && reported !== "human")) {
4237
- deps.onError?.(new Error(`a tool-gate settlement reported settledBy "${String(reported)}" on ${settling ? "a blocked" : "an executing"} call — ` +
4238
- `it is one of "human" / "timeout" / "aborted", and only "human" can be the source of a call that runs; the frame carries no source`), { phase: "config", sessionId });
4239
- }
4240
- else {
4241
- approvalSettledBy.set(e.toolCallId, reported);
4242
- }
4243
- }
4258
+ const settlement = screenGateSettlement(result, result.block === true);
4259
+ for (const defect of settlement.defects)
4260
+ deps.onError?.(new Error(defect), { phase: "config", sessionId });
4261
+ if (settlement.record !== undefined)
4262
+ approvalSettlement.set(e.toolCallId, settlement.record);
4244
4263
  return result.block
4245
4264
  ? { block: true, reason: result.reason }
4246
4265
  : result.updatedInput !== undefined
@@ -4347,6 +4366,20 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4347
4366
  ts: Date.now(),
4348
4367
  }));
4349
4368
  }
4369
+ trimmed = applyGitFrameGuard({
4370
+ before: edited,
4371
+ trimmed,
4372
+ budgetTokens: guardAt,
4373
+ ref: gitStatusRef,
4374
+ charsPerToken,
4375
+ onDegrade: (message) => {
4376
+ try {
4377
+ deps.onError?.(new Error(message), { phase: "degraded", sessionId, classification: "env-git-snapshot" });
4378
+ }
4379
+ catch {
4380
+ }
4381
+ },
4382
+ });
4350
4383
  const swept = dropOrphanToolResults(trimmed);
4351
4384
  if (swept.dropped.length > 0) {
4352
4385
  try {
@@ -4363,7 +4396,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4363
4396
  mediaCapped !== capped ||
4364
4397
  edited !== mediaCapped ||
4365
4398
  trimDroppedMessages ||
4366
- swept.dropped.length > 0;
4399
+ swept.dropped.length > 0 ||
4400
+ gitStatusRef.overBudgetShrunk === true;
4367
4401
  return { messages: swept.messages };
4368
4402
  });
4369
4403
  const cacheBreakDetector = deps.cacheBreakDetection === false ? undefined : new CacheBreakDetector();
@@ -4552,7 +4586,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
4552
4586
  const effectiveReadFaceObserved = carrierReadFace();
4553
4587
  const effectiveReadDenyObserved = readDenyAdditionsNormalized.length > 0 ? readDenyAdditionsNormalized.map((e) => ({ ...e })) : undefined;
4554
4588
  const preparedHolder = {};
4555
- const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettledBy, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
4589
+ const buildPrepared = () => ({ harness, session, sessionId, taskRootPath: taskRootFinal, model, thinking, compModel, mcp: mcp, ...(a2a !== undefined && a2a.tools.length > 0 ? { a2a } : {}), blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, approvalSettlement, nestedStats, ...(rewindNotes.length > 0 ? { rewindNotes } : {}), ...(effectiveReadFaceObserved !== undefined ? { effectiveReadFace: effectiveReadFaceObserved } : {}), ...(effectiveReadDenyObserved !== undefined ? { effectiveReadDenyPatterns: effectiveReadDenyObserved } : {}), cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), ...(permissionRuleOrgLane !== undefined ? { permissionRuleOrg: permissionRuleOrgLane } : {}), releaseSignal, settleContentAskBindings, cacheBreakDetector, cacheFingerprint, wiringManifest, promptManifest, epochDeclaredSections, activeTools, ...(deferred.size > 0 ? { deferredToolNames: deferred } : {}), toolMaterializeStatic, deferDirectCall, ...(staticFaceForRef.current !== undefined ? { staticFaceFor: staticFaceForRef.current } : {}), ownedEnv, suspendRef, suspendProgressRef, reviewRef, remoteEnvFailures, reviewRequestRef, suspendLoopRef, suspendForResource, ...(suspendForPlatformLimit !== undefined ? { suspendForPlatformLimit } : {}), ...(envLifetimeSuspendAt !== undefined ? { envLifetimeSuspendAt } : {}), ...(usageGovernance !== undefined ? { usageGovernance } : {}), callIssuedAtRef, brainCallGuardrailRef, suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, wakeRecovered, promptOverheadTokens, lastBrainContext, readTaskFile, recentlyReadFiles, normalizeAttachmentPath, isDedupStubResult, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit, runIdent: lspRunIdent } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(workflowSizeGuideline ? { workflowSizeGuideline } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, gitStatusRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
4556
4590
  const prepared = buildPrepared();
4557
4591
  preparedHolder.current = prepared;
4558
4592
  return prepared;
@@ -80,13 +80,18 @@ declare function toolEndBodyFrom(result: unknown, isError: boolean,
80
80
  * a parameter and never derived from `result`: a tool's own `details` (which post-tool hooks may
81
81
  * also replace) is writable by layers that adjudicate nothing, so reading provenance out of it would
82
82
  * let a failing tool claim a person approved it. Omitted ⇒ this call settled no approval. */
83
- settledBy?: ApprovalSettledBy): {
83
+ settledBy?: ApprovalSettledBy,
84
+ /** design/252 G-7 — WHOSE settlement, from the same caller and the same channel as `settledBy`, and
85
+ * for the same reason it is a parameter: an attribution read out of a tool's own result would let a
86
+ * tool name the person who approved it. Omitted ⇒ this call's settlement named nobody. */
87
+ approver?: string): {
84
88
  output?: unknown;
85
89
  truncated?: boolean;
86
90
  totalChars?: number;
87
91
  structured?: unknown;
88
92
  errorCode?: string;
89
93
  settledBy?: ApprovalSettledBy;
94
+ approver?: string;
90
95
  };
91
96
  /**
92
97
  * scan-1/A1 — the BODY of the synthetic `tool_end` that closes a reconcile-recovered orphan. ONE