@sema-agent/server 7.117.0 → 7.119.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 (159) hide show
  1. package/MIGRATION.md +182 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +58 -28
  5. package/deploy/sema-up/README.md +2 -0
  6. package/deploy/sema-up/chart/templates/minio.yaml +2 -1
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
  8. package/deploy/sema-up/chart/templates/server.yaml +1 -0
  9. package/deploy/sema-up/chart/values.yaml +7 -1
  10. package/deploy/sema-up/cluster-up.sh +20 -8
  11. package/deploy/sema-up/kube-up.sh +29 -16
  12. package/deploy/sema-up/lib/buckets.sh +21 -0
  13. package/deploy/sema-up/lib/health.sh +33 -0
  14. package/deploy/sema-up/sema-up.sh +14 -9
  15. package/deploy/sema-up/smoke.sh +16 -6
  16. package/deploy/sema-up/stack/compose.yaml +1 -1
  17. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
  18. package/dist/approval-ask-audit-store.js +2 -7
  19. package/dist/approval-card.d.ts +40 -10
  20. package/dist/approval-card.js +11 -1
  21. package/dist/approval.d.ts +55 -17
  22. package/dist/approval.js +74 -33
  23. package/dist/audit.d.ts +12 -1
  24. package/dist/audit.js +62 -2
  25. package/dist/auto-mode-face.d.ts +2 -3
  26. package/dist/bake-runner/main.js +1 -6
  27. package/dist/bake-runner/protocol.d.ts +18 -0
  28. package/dist/bake-runner/protocol.js +22 -2
  29. package/dist/bake-runner/runner.d.ts +12 -8
  30. package/dist/bake-runner/runner.js +5 -9
  31. package/dist/boot/config-center.js +1 -1
  32. package/dist/boot/coordinators.js +4 -1
  33. package/dist/boot/device-lane.d.ts +3 -3
  34. package/dist/boot/device-lane.js +1 -2
  35. package/dist/boot/leader.d.ts +4 -4
  36. package/dist/boot/leader.js +1 -2
  37. package/dist/boot/parked-revive-gate.d.ts +4 -6
  38. package/dist/boot/parked-revive-gate.js +2 -6
  39. package/dist/boot/resolve-spec.js +9 -11
  40. package/dist/boot/runner-deps.js +2 -2
  41. package/dist/boot/stage-07-capability-layer.js +7 -3
  42. package/dist/boot/stage-10-http-server.js +3 -2
  43. package/dist/boot/stores.js +12 -7
  44. package/dist/capabilities/center-plugins.d.ts +2 -0
  45. package/dist/capabilities/center-plugins.js +24 -6
  46. package/dist/capabilities/code-review-council.js +8 -11
  47. package/dist/capabilities/hands-lane.d.ts +25 -0
  48. package/dist/capabilities/hands-lane.js +1 -1
  49. package/dist/capabilities/mcp-probe.d.ts +2 -2
  50. package/dist/capabilities/scenarios.d.ts +34 -7
  51. package/dist/capabilities/scenarios.js +33 -6
  52. package/dist/config-catalog.js +3 -3
  53. package/dist/config.d.ts +14 -18
  54. package/dist/config.js +15 -26
  55. package/dist/deployment-governance.d.ts +5 -7
  56. package/dist/deployment-governance.js +4 -5
  57. package/dist/host-decision.d.ts +114 -31
  58. package/dist/host-decision.js +39 -2
  59. package/dist/http/admission.js +12 -6
  60. package/dist/http/approval-sweeps.js +1 -1
  61. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  62. package/dist/http/mcp-dial-deadline.js +21 -0
  63. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  64. package/dist/http/memory-compliance-failure-reply.js +17 -8
  65. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  66. package/dist/http/memory-store-refusal-reply.js +16 -5
  67. package/dist/http/resume-legs.js +8 -3
  68. package/dist/http/routes/approvals-assistant.d.ts +25 -3
  69. package/dist/http/routes/approvals-assistant.js +27 -26
  70. package/dist/http/routes/capabilities.js +6 -3
  71. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  72. package/dist/http/routes/mcp-reconnect.js +6 -1
  73. package/dist/http/routes/memory-bundle.js +1 -1
  74. package/dist/http/routes/memory-compliance.js +1 -1
  75. package/dist/http/routes/memory-origin.d.ts +1 -1
  76. package/dist/http/routes/memory-origin.js +1 -1
  77. package/dist/http/routes/runs.js +47 -33
  78. package/dist/http/routes/sessions.d.ts +1 -1
  79. package/dist/http/routes/sessions.js +23 -22
  80. package/dist/http/routes/workflows.js +2 -0
  81. package/dist/http/wire-types.d.ts +17 -13
  82. package/dist/images/manifest.d.ts +7 -1
  83. package/dist/images/manifest.js +18 -16
  84. package/dist/memory-store-refusal.d.ts +38 -18
  85. package/dist/memory-store-refusal.js +25 -7
  86. package/dist/model-unreachable-notice.d.ts +1 -1
  87. package/dist/namespaced-rule-name.d.ts +46 -41
  88. package/dist/namespaced-rule-name.js +36 -22
  89. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  90. package/dist/observability/corrupt-read-seat.js +1 -0
  91. package/dist/observability/fail-open.d.ts +19 -3
  92. package/dist/observability/fail-open.js +19 -3
  93. package/dist/operator-ask.d.ts +6 -2
  94. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  95. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  96. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  97. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  98. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  99. package/dist/orchestration/workflow-notify-journal.js +7 -2
  100. package/dist/outbound-dispatcher.d.ts +29 -20
  101. package/dist/outbound-dispatcher.js +105 -16
  102. package/dist/parked-decide.d.ts +43 -48
  103. package/dist/parked-decide.js +54 -16
  104. package/dist/permission-rule-vocab.d.ts +4 -3
  105. package/dist/permission-rule-vocab.js +1 -1
  106. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  107. package/dist/plugins/approval-ask-store-sql.js +0 -3
  108. package/dist/plugins/approval-exemption-store.d.ts +4 -0
  109. package/dist/plugins/approval-exemption-store.js +1 -0
  110. package/dist/plugins/blob-backend.d.ts +13 -0
  111. package/dist/plugins/blob-backend.js +26 -0
  112. package/dist/plugins/local-session-store.d.ts +78 -47
  113. package/dist/plugins/local-session-store.js +152 -122
  114. package/dist/plugins/one-time-migrations.d.ts +11 -0
  115. package/dist/plugins/one-time-migrations.js +128 -4
  116. package/dist/plugins/pg-pool.js +1 -1
  117. package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
  118. package/dist/plugins/snapshot-bucket-presence.js +40 -0
  119. package/dist/plugins/store-backend.d.ts +17 -6
  120. package/dist/plugins/store-backend.js +46 -16
  121. package/dist/plugins/store-open-wait.d.ts +33 -0
  122. package/dist/plugins/store-open-wait.js +73 -0
  123. package/dist/plugins/tidb-pool.js +1 -1
  124. package/dist/plugins/web-search.d.ts +20 -1
  125. package/dist/plugins/web-search.js +39 -10
  126. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  127. package/dist/plugins/workflow-run-store-sql.js +5 -7
  128. package/dist/request-key-closure.js +1 -1
  129. package/dist/request-value-closure.d.ts +1 -1
  130. package/dist/rule-entry-list.d.ts +35 -46
  131. package/dist/rule-entry-list.js +18 -26
  132. package/dist/rules-consent.js +3 -0
  133. package/dist/run-local.js +5 -3
  134. package/dist/runs.d.ts +1 -1
  135. package/dist/security.d.ts +5 -3
  136. package/dist/security.js +4 -0
  137. package/dist/session-policy-wire.d.ts +13 -12
  138. package/dist/session-policy-wire.js +11 -11
  139. package/dist/spec-fields.d.ts +13 -11
  140. package/dist/spec-fields.js +4 -7
  141. package/dist/standing-answer-grant.d.ts +14 -2
  142. package/dist/standing-answer-grant.js +8 -1
  143. package/dist/store-posture.d.ts +14 -6
  144. package/dist/store-posture.js +7 -4
  145. package/dist/task-settings.d.ts +27 -55
  146. package/dist/task-settings.js +13 -21
  147. package/dist/tool-approval.d.ts +133 -138
  148. package/dist/tool-approval.js +175 -66
  149. package/dist/trace/core-keyset-guard.d.ts +5 -5
  150. package/dist/trace/redact.d.ts +6 -0
  151. package/dist/trace/redact.js +9 -9
  152. package/dist/trace/self-minted-wire.d.ts +16 -2
  153. package/dist/trace/self-minted-wire.js +7 -0
  154. package/dist/user-input-ledger.d.ts +30 -14
  155. package/dist/user-input-ledger.js +25 -14
  156. package/package.json +2 -2
  157. package/scripts/drain-check-local.mjs +74 -3
  158. package/scripts/drain-check.mysql.sql +43 -17
  159. package/scripts/drain-check.pg.sql +46 -20
@@ -1,6 +1,7 @@
1
1
  import { contentKindOf } from "./approval-content-kind.js";
2
+ import { heldAskOf, liveSettledBy, readDecidedBy, retiredDecisionKeyFacts, DECIDED_BY_OUTSIDE_CLOSED_SET, POLICY_CANNOT_ALLOW_PERSON_ASK } from "./host-decision.js";
2
3
  import { AsyncLocalStorage } from "node:async_hooks";
3
- import { uuidv7, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
4
+ import { uuidv7, classifyAsk, foldArbitrationRow, policyCannotAnswer, screenAskClassMembers, MAX_RULE_TEXT_CHARS, READ_ROOT_CANDIDATE_DIR_MAX, parseRuleText, pathConfinableWriteToolNames } from "@sema-agent/core";
4
5
  import { precheckCardRuleText } from "./rules-consent.js";
5
6
  import { redactDeep, redactSecrets, redactSecretsWithArms } from "./trace/redact.js";
6
7
  import { hasBidiControls } from "./text-bidi.js";
@@ -9,11 +10,13 @@ import { createLogger } from "./observability/logger.js";
9
10
  import { recordFailOpen } from "./observability/fail-open.js";
10
11
  import { deriveAskId, deriveAskScopedBatchId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
11
12
  import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readDenialLimitFallback, readProbeCause, readProbeReason, readReadRootCandidate, readRuleEvidence, readAllowRuleShadowed, readRuleOffersAbsence, readRuleOffersAbsenceSeat, readParkRowTrueSeat, readAskClassPair, readClosedWordSeat, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
12
- import { MAX_SETTLED_BY_CHARS, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, settledByFitsColumn } from "./plugins/approval-ask-store-sql.js";
13
+ import { POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
13
14
  import { settleByPolicy } from "./approval-policy-settlement.js";
14
15
  import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
15
16
  import { governanceOriginOf } from "./runtime-governance.js";
16
- import { answerGrantsBeyondCall } from "./standing-answer-grant.js";
17
+ import { answerGrantsBeyondCall, grantBindingOf } from "./standing-answer-grant.js";
18
+ import { operatorAsk } from "./operator-ask.js";
19
+ import { APPROVAL_EXEMPTED_EVENT } from "./plugins/approval-exemption-store.js";
17
20
  const defaultLogger = createLogger();
18
21
  export const APPROVAL_GATE_KINDS = ["human", "irreversible_ask"];
19
22
  export function isApprovalGateKind(k) {
@@ -53,7 +56,7 @@ function durableRuleMaterial(material) {
53
56
  export const ASK_DECISION_CONSUMER_ABSENT = "absent";
54
57
  const MAX_INFLIGHT_TERMINAL_CLAIMS = 2;
55
58
  const DURABLE_CONVERGE_BACKOFF_MS = [50, 100, 200, 400, 800, 1600];
56
- const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
59
+ export const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2_000;
57
60
  const DURABLE_CALL_TIMEOUT_MS = 5_000;
58
61
  const TERMINAL_LADDER_WALL_MS = DURABLE_CALL_TIMEOUT_MS + DURABLE_CONVERGE_READ_TIMEOUT_MS + DURABLE_CONVERGE_BACKOFF_MS.reduce((a, b) => a + b, 0);
59
62
  const TERMINAL_CLAIM_CAP_POLL_MS = 50;
@@ -81,19 +84,68 @@ const FS_WRITE_TOOLS = pathConfinableWriteToolNames();
81
84
  function sessionAllowCategory(toolName) {
82
85
  return FS_WRITE_TOOLS.has(toolName) ? "fs-write" : `tool:${toolName}`;
83
86
  }
84
- function sessionAllowKey(owner, sessionId, category) {
85
- return JSON.stringify([owner, sessionId, category]);
87
+ function sessionAllowKey(owner, sessionId, category, binding) {
88
+ return JSON.stringify([owner, sessionId, category, binding.kind === "bound" ? binding.digest : null]);
86
89
  }
87
- export function standingAnswerMayRetire(facts) {
88
- return !facts.governanceForced && !facts.requiresRealApproval && !facts.mandated && !facts.grantsBeyondCall;
90
+ function askStandingAnswerFacts(req, governanceForced, deploymentListAsks) {
91
+ const classes = screenAskClassMembers(req.askClass, req.classes)?.classes;
92
+ return {
93
+ governanceForced,
94
+ requiresRealApproval: req.requiresRealApproval === true,
95
+ mandated: req.mandated === true,
96
+ grantBinding: grantBindingOf(req),
97
+ classes,
98
+ deploymentListQuestion: carriesDeploymentListQuestion(deploymentListAsks, classes),
99
+ };
100
+ }
101
+ const NO_GATE_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined, mandateMarks: {} };
102
+ const DEPLOYMENT_LIST_ASK_CLASSES = new Set(classifyAsk(operatorAsk("APPROVAL_REQUIRE", ""), NO_GATE_FACTS).classes);
103
+ export function carriesDeploymentListQuestion(deploymentListAsks, classes) {
104
+ return deploymentListAsks && classes !== undefined && classes.some((c) => DEPLOYMENT_LIST_ASK_CLASSES.has(c));
105
+ }
106
+ export function policyAskStandingAnswerFacts(req, ask, deploymentListQuestion) {
107
+ return {
108
+ governanceForced: false,
109
+ requiresRealApproval: ask.requiresRealApproval === true,
110
+ mandated: ask.mandated === true,
111
+ grantBinding: grantBindingOf(req),
112
+ classes: [...classifyAsk(ask, NO_GATE_FACTS).classes],
113
+ deploymentListQuestion,
114
+ };
89
115
  }
90
- export function parkRowStandingAnswerFacts(pending, gate, governance) {
116
+ export function standingAnswerHome(facts) {
117
+ if (facts.governanceForced || facts.requiresRealApproval || facts.mandated || facts.classes === undefined)
118
+ return "none";
119
+ if (facts.deploymentListQuestion) {
120
+ return facts.grantBinding.kind === "none" && facts.classes.every((c) => DEPLOYMENT_LIST_ASK_CLASSES.has(c)) ? "server_store" : "none";
121
+ }
122
+ return facts.grantBinding.kind !== "unreadable" && foldArbitrationRow(new Set(facts.classes)).ruleMayClear ? "seat" : "none";
123
+ }
124
+ export function standingAnswerDurablyRecordable(facts) {
125
+ const home = standingAnswerHome(facts);
126
+ switch (home) {
127
+ case "none":
128
+ return false;
129
+ case "seat":
130
+ return facts.grantBinding.kind === "none";
131
+ case "server_store":
132
+ return true;
133
+ default: {
134
+ const unhandled = home;
135
+ throw new Error(`unhandled standing-answer home: ${String(unhandled)}`);
136
+ }
137
+ }
138
+ }
139
+ export function parkRowStandingAnswerFacts(pending, gate, governance, deploymentListAsks) {
91
140
  const riskDescriptor = "riskDescriptor" in gate ? gate.riskDescriptor : undefined;
141
+ const classes = screenAskClassMembers(pending.askClass, pending.classes)?.classes;
92
142
  return {
93
143
  governanceForced: governanceOriginOf({ riskDescriptor }, governance) === true,
94
144
  requiresRealApproval: readParkRowTrueSeat(pending, "requiresRealApproval"),
95
145
  mandated: readParkRowTrueSeat(pending, "mandated") || mandatedFromSeat(readRuleOffersAbsenceSeat(pending)),
96
- grantsBeyondCall: answerGrantsBeyondCall(pending),
146
+ grantBinding: grantBindingOf(pending),
147
+ classes,
148
+ deploymentListQuestion: carriesDeploymentListQuestion(deploymentListAsks, classes),
97
149
  };
98
150
  }
99
151
  function pendingEntryStandingAnswerFacts(entry) {
@@ -101,7 +153,9 @@ function pendingEntryStandingAnswerFacts(entry) {
101
153
  governanceForced: entry.governanceForced === true,
102
154
  requiresRealApproval: entry.requiresRealApproval === true,
103
155
  mandated: entry.mandated === true,
104
- grantsBeyondCall: answerGrantsBeyondCall(entry),
156
+ grantBinding: entry.grantBinding,
157
+ classes: entry.classes,
158
+ deploymentListQuestion: entry.deploymentListQuestion === true,
105
159
  };
106
160
  }
107
161
  function mandatedFromSeat(seat) {
@@ -118,29 +172,15 @@ export const PERSIST_RULE_TEXT_ERROR = `persistRule.rule must be a non-empty str
118
172
  export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a boolean when present";
119
173
  export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
120
174
  export const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOfferIndex is mutually exclusive with persistRule.rule / persistRule.edited — send exactly one arm";
121
- export const SETTLED_BY_MUST_BE_STRING = "settledBy must be a string — the settlement source is one word from the engine's closed set, and this layer judges only the SHAPE (which word it is, the engine decides)";
122
- export function respondSettledBy(parsed) {
123
- return parsed.settledBy;
124
- }
125
- export const SETTLED_BY_DENY_ONLY = 'settledBy is only meaningful with decision "deny" — only a person approves an action that EXECUTES, so a self-reported settlement source on an allow is a contradiction the engine refuses; the refusal would arrive too late to undo this door\'s own side effects (a session allow-all grant, a persisted rule, a DECIDED row that replays as a plain approval), so it is refused here, before the decision';
126
- export const SETTLED_BY_TOO_WIDE = `settledBy must be 1-${MAX_SETTLED_BY_CHARS} characters — the settlement source is one word from the engine's closed set and lands in a fixed-width column on the decision row`;
127
- export function judgeSettledByBody(settledBy, decision, directDoorActive) {
128
- if (settledBy === undefined)
129
- return { kind: "absent" };
130
- if (directDoorActive)
131
- return { kind: "not_in_proof" };
132
- if (decision !== "deny")
133
- return { kind: "deny_only" };
134
- if (typeof settledBy !== "string")
135
- return { kind: "not_string" };
136
- if (!settledByFitsColumn(settledBy))
137
- return { kind: "too_wide" };
138
- return { kind: "accepted", settledBy };
139
- }
140
175
  export const READ_ROOT_GRANT_ALLOW_ONLY = 'readRootGrant is only meaningful with decision "allow" — it is the card\'s "Yes, and add directory" answer (allow this call and add the card\'s readRootCandidate.dir to this run\'s read directories)';
141
176
  export const READ_ROOT_GRANT_SHAPE = `readRootGrant must be the card's readRootCandidate.dir sent back byte for byte — a non-empty string of at most ${READ_ROOT_CANDIDATE_DIR_MAX} characters`;
142
177
  export const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope — the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
143
178
  export function parseToolApprovalResponse(body) {
179
+ const retired = retiredDecisionKeyFacts(body);
180
+ const parsed = parseToolApprovalFields(body, retired);
181
+ return parsed.ok || retired === undefined ? parsed : { ...parsed, ...retired.extra };
182
+ }
183
+ function parseToolApprovalFields(body, retired) {
144
184
  if (body === null || typeof body !== "object" || Array.isArray(body))
145
185
  return { ok: false, error: "body must be an object" };
146
186
  const d = body.decision;
@@ -183,23 +223,19 @@ export function parseToolApprovalResponse(body) {
183
223
  }
184
224
  note = rawNote;
185
225
  }
186
- const settledByVerdict = judgeSettledByBody(body.settledBy, d, false);
187
- let settledBy;
188
- switch (settledByVerdict.kind) {
189
- case "deny_only":
190
- return { ok: false, error: SETTLED_BY_DENY_ONLY };
191
- case "not_string":
192
- return { ok: false, error: SETTLED_BY_MUST_BE_STRING };
193
- case "too_wide":
194
- return { ok: false, error: SETTLED_BY_TOO_WIDE };
195
- case "absent":
196
- break;
226
+ if (retired !== undefined)
227
+ return { ok: false, error: retired.error };
228
+ const decidedByReading = readDecidedBy(body.decidedBy, false);
229
+ let decidedBy;
230
+ switch (decidedByReading.kind) {
231
+ case "outside_closed_set":
232
+ return { ok: false, error: DECIDED_BY_OUTSIDE_CLOSED_SET };
197
233
  case "accepted":
198
- settledBy = settledByVerdict.settledBy;
234
+ decidedBy = decidedByReading.decidedBy;
199
235
  break;
200
236
  default: {
201
- const unreachable = settledByVerdict;
202
- throw new Error(`unhandled settledBy verdict: ${JSON.stringify(unreachable)}`);
237
+ const unreachable = decidedByReading;
238
+ throw new Error(`unhandled decidedBy reading: ${JSON.stringify(unreachable)}`);
203
239
  }
204
240
  }
205
241
  const rawGrant = body.readRootGrant;
@@ -214,7 +250,7 @@ export function parseToolApprovalResponse(body) {
214
250
  ...(d !== "deny" && u !== undefined ? { updatedInput: u } : {}),
215
251
  ...(persistRule !== undefined ? { persistRule } : {}),
216
252
  ...(note !== undefined ? { note } : {}),
217
- ...(settledBy !== undefined ? { settledBy } : {}),
253
+ decidedBy,
218
254
  ...(readRootGrant !== undefined ? { readRootGrant } : {}),
219
255
  };
220
256
  }
@@ -310,9 +346,10 @@ function replayTerminalAskRow(row) {
310
346
  return false;
311
347
  }
312
348
  export function outcomeOfDecidedRow(row) {
313
- if (row.decision !== "approve")
314
- return row.settledBy !== null ? { allow: false, settledBy: row.settledBy } : false;
315
- return row.updatedInput !== undefined ? { allow: true, updatedInput: row.updatedInput } : true;
349
+ const allow = row.decision === "approve";
350
+ const source = row.settledBy !== null ? { settledBy: row.settledBy } : {};
351
+ const edit = allow && row.updatedInput !== undefined ? { updatedInput: row.updatedInput } : {};
352
+ return row.settledBy === null && !("updatedInput" in edit) ? allow : { allow, ...edit, ...source };
316
353
  }
317
354
  function settleArgsOfDecided(outcome) {
318
355
  if (typeof outcome === "boolean")
@@ -379,6 +416,7 @@ export class ToolApprovalCoordinator {
379
416
  parkedRedeem;
380
417
  askAudit;
381
418
  sessionGrants;
419
+ deploymentListAsks;
382
420
  shutdownDraining = false;
383
421
  asksInFlight = 0;
384
422
  parkCommits;
@@ -400,6 +438,10 @@ export class ToolApprovalCoordinator {
400
438
  this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
401
439
  this.askAudit = opts?.askAudit;
402
440
  this.sessionGrants = opts?.sessionGrants;
441
+ if (opts?.deploymentListAsks !== undefined && opts.sessionGrants === undefined) {
442
+ throw new Error("ToolApprovalCoordinator: deploymentListAsks needs sessionGrants — the deployment list's standing answers live in the session exemption store");
443
+ }
444
+ this.deploymentListAsks = opts?.deploymentListAsks;
403
445
  this.parkCommits = opts?.parkCommits;
404
446
  this.parkCommits?.onParkCommitted((calls) => this.noteParkCommitted(calls));
405
447
  }
@@ -751,19 +793,30 @@ export class ToolApprovalCoordinator {
751
793
  return (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
752
794
  }
753
795
  async standingGrantSettles(origin, req, governanceForced) {
754
- if (!standingAnswerMayRetire({ governanceForced, requiresRealApproval: req.requiresRealApproval === true, mandated: req.mandated === true, grantsBeyondCall: answerGrantsBeyondCall(req) }))
755
- return false;
796
+ const facts = askStandingAnswerFacts(req, governanceForced, this.deploymentListAsks?.(req) === true);
797
+ const home = standingAnswerHome(facts);
798
+ switch (home) {
799
+ case "seat":
800
+ break;
801
+ case "none":
802
+ case "server_store":
803
+ return false;
804
+ default: {
805
+ const unhandled = home;
806
+ throw new Error(`unhandled standing-answer home: ${String(unhandled)}`);
807
+ }
808
+ }
756
809
  const sessionId = origin.sessionId;
757
810
  if (!sessionId)
758
811
  return false;
759
- const live = this.allowAllSessions.has(sessionAllowKey(origin.owner, sessionId, sessionAllowCategory(req.toolName)));
760
- const durable = !live && (await this.durableGrantHas(sessionId, req.toolName));
812
+ const live = this.allowAllSessions.has(sessionAllowKey(origin.owner, sessionId, sessionAllowCategory(req.toolName), facts.grantBinding));
813
+ const durable = !live && !answerGrantsBeyondCall(req) && (await this.durableGrantHas(sessionId, req.toolName));
761
814
  if (!live && !durable)
762
815
  return false;
763
816
  if (!(await this.sessionGrantUncontestedByRow(origin, req)))
764
817
  return false;
765
818
  if (durable)
766
- defaultLogger.info("approval_exempted", { sessionId, toolName: req.toolName, seat: "approval_seat" });
819
+ defaultLogger.info(APPROVAL_EXEMPTED_EVENT, { sessionId, toolName: req.toolName, seat: "approval_seat" });
767
820
  return true;
768
821
  }
769
822
  async durableGrantHas(sessionId, canonicalToolName) {
@@ -1044,6 +1097,7 @@ export class ToolApprovalCoordinator {
1044
1097
  if (signal?.aborted)
1045
1098
  return false;
1046
1099
  const governanceForced = this.governanceForcedOf(origin, req);
1100
+ const deploymentListAsks = this.deploymentListAsks?.(req) === true;
1047
1101
  if (await this.standingGrantSettles(origin, req, governanceForced))
1048
1102
  return true;
1049
1103
  const aliveCtxs = ctxs.filter((c) => !c.abortSignal?.aborted);
@@ -1071,6 +1125,7 @@ export class ToolApprovalCoordinator {
1071
1125
  let effectiveGovernanceForced = governanceForced;
1072
1126
  let ruleLaneMaterial = this.buildRuleLaneMaterial(req, origin.owner, governanceForced, origin.sessionId);
1073
1127
  const mandateWord = readClosedWordSeat(req, "mandate");
1128
+ const noRulingCause = readClosedWordSeat(req, "classifierNoRuling");
1074
1129
  const originWord = typeof req.origin === "string" && req.origin.length > 0 ? req.origin : undefined;
1075
1130
  const askClassPair = readAskClassPair(req);
1076
1131
  const frame = {
@@ -1098,7 +1153,9 @@ export class ToolApprovalCoordinator {
1098
1153
  ...(governanceForced ? { governanceForced: true } : {}),
1099
1154
  ...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
1100
1155
  ...(req.mandated === true ? { mandated: true } : {}),
1101
- ...(standingAnswerMayRetire({ governanceForced, requiresRealApproval: req.requiresRealApproval === true, mandated: req.mandated === true, grantsBeyondCall: answerGrantsBeyondCall(req) }) ? {} : { rememberRefused: true }),
1156
+ ...(standingAnswerHome(askStandingAnswerFacts(req, governanceForced, deploymentListAsks)) === "none" ? { rememberRefused: true } : {}),
1157
+ ...(typeof req.boundGrantDigest === "string" ? { boundGrantDigest: req.boundGrantDigest, ...(req.boundGrants !== undefined ? { boundGrants: req.boundGrants } : {}) } : {}),
1158
+ ...(noRulingCause !== undefined ? { classifierNoRuling: noRulingCause } : {}),
1102
1159
  ...(mandateWord !== undefined ? { mandate: mandateWord } : {}),
1103
1160
  ...(originWord !== undefined ? { origin: originWord } : {}),
1104
1161
  ...(askClassPair ?? {}),
@@ -1306,11 +1363,12 @@ export class ToolApprovalCoordinator {
1306
1363
  return unattended === "unavailable" ? unattended : shapeDeny(unattended);
1307
1364
  }
1308
1365
  if (settled.allowed) {
1309
- if (settled.updatedInput === undefined && settled.readRootGrant === undefined)
1366
+ if (hostSettled === undefined && settled.updatedInput === undefined && settled.readRootGrant === undefined)
1310
1367
  return true;
1311
1368
  return {
1312
1369
  allow: true,
1313
1370
  ...(settled.updatedInput !== undefined ? { updatedInput: settled.updatedInput } : {}),
1371
+ ...(hostSettled !== undefined ? { settledBy: hostSettled } : {}),
1314
1372
  ...(settled.readRootGrant !== undefined ? { readRootGrant: settled.readRootGrant } : {}),
1315
1373
  };
1316
1374
  }
@@ -1623,12 +1681,17 @@ export class ToolApprovalCoordinator {
1623
1681
  return shapeOutcome(await allowedP);
1624
1682
  }
1625
1683
  }
1626
- const rememberRefused = !standingAnswerMayRetire(pendingEntryStandingAnswerFacts({
1684
+ const entryGrantBinding = grantBindingOf(req);
1685
+ const entryClasses = askClassPair?.classes;
1686
+ const entryDeploymentListQuestion = carriesDeploymentListQuestion(deploymentListAsks, entryClasses);
1687
+ const rememberRefused = standingAnswerHome(pendingEntryStandingAnswerFacts({
1627
1688
  ...(effectiveGovernanceForced ? { governanceForced: true } : {}),
1628
1689
  ...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
1629
1690
  ...(req.mandated === true ? { mandated: true } : {}),
1630
- toolName: req.toolName,
1631
- }));
1691
+ grantBinding: entryGrantBinding,
1692
+ classes: entryClasses,
1693
+ ...(entryDeploymentListQuestion ? { deploymentListQuestion: true } : {}),
1694
+ })) === "none";
1632
1695
  if (rememberRefused)
1633
1696
  frame.rememberRefused = true;
1634
1697
  else
@@ -1648,6 +1711,9 @@ export class ToolApprovalCoordinator {
1648
1711
  ...(origin.sessionId ? { sessionId: origin.sessionId } : {}),
1649
1712
  frame,
1650
1713
  toolName: req.toolName,
1714
+ grantBinding: entryGrantBinding,
1715
+ ...(entryClasses !== undefined ? { classes: entryClasses } : {}),
1716
+ ...(entryDeploymentListQuestion ? { deploymentListQuestion: true } : {}),
1651
1717
  registeredAtMs: Date.now(),
1652
1718
  expiresAtMs: persistedExpiresAtMs,
1653
1719
  ...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
@@ -1777,7 +1843,7 @@ export class ToolApprovalCoordinator {
1777
1843
  respond(id, principal, body, httpReq) {
1778
1844
  const parsed = parseToolApprovalResponse(body);
1779
1845
  if (!parsed.ok)
1780
- return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape" } };
1846
+ return { status: 400, body: { error: parsed.error, errorCode: "request.body_shape", ...(parsed.field !== undefined ? { field: parsed.field } : {}) } };
1781
1847
  const entry = this.pending.get(id);
1782
1848
  if (!entry || (entry.owner !== null && entry.owner !== principal)) {
1783
1849
  return this.respondLate(id, principal, parsed, httpReq);
@@ -1785,14 +1851,43 @@ export class ToolApprovalCoordinator {
1785
1851
  const preRule = this.precheckEditedRuleBeforeDecision(entry, parsed);
1786
1852
  if (preRule !== undefined)
1787
1853
  return preRule;
1854
+ if (parsed.value !== "deny" && policyCannotAnswer(heldAskOf(entry.requiresRealApproval === true), parsed.decidedBy)) {
1855
+ return { status: 400, body: { error: POLICY_CANNOT_ALLOW_PERSON_ASK, errorCode: "request.body_shape" } };
1856
+ }
1788
1857
  const ruleEligibility = { eligible: true };
1789
- const settled = this.askStore && entry.askId !== undefined && entry.batchId !== undefined
1858
+ const decided = this.askStore && entry.askId !== undefined && entry.batchId !== undefined
1790
1859
  ? this.respondWithCas(this.askStore, entry.askId, entry.batchId, id, entry, parsed, ruleEligibility)
1791
1860
  : this.finishRespond(id, entry, parsed);
1861
+ const settled = this.recordDeploymentListAnswer(decided, entry, parsed, principal, ruleEligibility);
1792
1862
  if (parsed.persistRule === undefined)
1793
1863
  return settled;
1794
1864
  return this.persistRuleAfterDecision(settled, entry, parsed.persistRule, parsed.updatedInput !== undefined, ruleEligibility);
1795
1865
  }
1866
+ recordDeploymentListAnswer(decided, entry, parsed, principal, eligibility) {
1867
+ const store = this.sessionGrants;
1868
+ const sessionId = entry.sessionId;
1869
+ if (parsed.value !== "allow_session" || !sessionId || store === undefined)
1870
+ return decided;
1871
+ if (standingAnswerHome(pendingEntryStandingAnswerFacts(entry)) !== "server_store")
1872
+ return decided;
1873
+ return (async () => {
1874
+ const result = await decided;
1875
+ if (result.status !== 200)
1876
+ return result;
1877
+ if (!eligibility.eligible)
1878
+ return { ...result, body: { ...result.body, rememberApplied: false } };
1879
+ let rememberApplied = false;
1880
+ try {
1881
+ await this.withStoreDeadline(store.grant(sessionId, entry.toolName, principal ?? entry.owner), "sessionGrants.grant(deployment-list)", DURABLE_CALL_TIMEOUT_MS);
1882
+ rememberApplied = true;
1883
+ defaultLogger.info("approval_exemption_granted", { sessionId, toolName: entry.toolName, grantedBy: principal ?? entry.owner });
1884
+ }
1885
+ catch (err) {
1886
+ defaultLogger.warn("approval_exemption_grant_failed", { sessionId, toolName: entry.toolName, error: String(err).slice(0, 200) });
1887
+ }
1888
+ return { ...result, body: { ...result.body, rememberApplied } };
1889
+ })();
1890
+ }
1796
1891
  precheckEditedRuleBeforeDecision(entry, parsed) {
1797
1892
  const persistRule = parsed.persistRule;
1798
1893
  if (persistRule === undefined || persistRule.kind !== "text" || !persistRule.edited)
@@ -1945,7 +2040,7 @@ export class ToolApprovalCoordinator {
1945
2040
  gateBoundInputHash: row.gateBoundInputHash,
1946
2041
  decision,
1947
2042
  ...(parsed.note !== undefined ? { note: parsed.note } : {}),
1948
- ...(parsed.settledBy !== undefined ? { settledBy: parsed.settledBy } : {}),
2043
+ decidedBy: parsed.decidedBy,
1949
2044
  ...(parsed.updatedInput !== undefined && parsed.value !== "deny" ? { updatedInput: parsed.updatedInput } : {}),
1950
2045
  ...(principal !== undefined ? { principal } : {}),
1951
2046
  ...(httpReq !== undefined ? { httpReq } : {}),
@@ -2183,7 +2278,7 @@ export class ToolApprovalCoordinator {
2183
2278
  decision: parsed.value === "deny" ? "deny" : "approve",
2184
2279
  ...(parsed.note !== undefined ? { decisionNote: parsed.note } : {}),
2185
2280
  ...(parsed.value !== "deny" && parsed.updatedInput !== undefined ? { updatedInput: parsed.updatedInput } : {}),
2186
- ...(parsed.settledBy !== undefined ? { settledBy: respondSettledBy(parsed) } : {}),
2281
+ ...((sb) => (sb !== undefined ? { settledBy: sb } : {}))(liveSettledBy(parsed.decidedBy)),
2187
2282
  }), "decideAsk", DURABLE_CALL_TIMEOUT_MS, (late) => {
2188
2283
  if (late.ok)
2189
2284
  this.notifyExternalDecision(askId, outcomeOfDecidedRow(late.row));
@@ -2235,7 +2330,21 @@ export class ToolApprovalCoordinator {
2235
2330
  }
2236
2331
  finishRespond(id, entry, parsed, opts) {
2237
2332
  let rememberApplied;
2238
- const grantable = standingAnswerMayRetire(pendingEntryStandingAnswerFacts(entry));
2333
+ const home = standingAnswerHome(pendingEntryStandingAnswerFacts(entry));
2334
+ let grantable;
2335
+ switch (home) {
2336
+ case "seat":
2337
+ grantable = true;
2338
+ break;
2339
+ case "none":
2340
+ case "server_store":
2341
+ grantable = false;
2342
+ break;
2343
+ default: {
2344
+ const unhandled = home;
2345
+ throw new Error(`unhandled standing-answer home: ${String(unhandled)}`);
2346
+ }
2347
+ }
2239
2348
  if (parsed.value === "allow_session")
2240
2349
  rememberApplied = Boolean(entry.sessionId) && grantable;
2241
2350
  if (parsed.value === "allow_session" && entry.sessionId && grantable) {
@@ -2244,10 +2353,10 @@ export class ToolApprovalCoordinator {
2244
2353
  if (oldest !== undefined)
2245
2354
  this.allowAllSessions.delete(oldest);
2246
2355
  }
2247
- this.allowAllSessions.add(sessionAllowKey(entry.owner, entry.sessionId, sessionAllowCategory(entry.toolName)));
2356
+ this.allowAllSessions.add(sessionAllowKey(entry.owner, entry.sessionId, sessionAllowCategory(entry.toolName), entry.grantBinding));
2248
2357
  }
2249
2358
  const allowed = parsed.value !== "deny";
2250
- const settleArgs = opts?.settleOutcome !== undefined ? settleArgsOfDecided(opts.settleOutcome) : { allowed, updatedInput: parsed.updatedInput, settledBy: respondSettledBy(parsed) };
2359
+ const settleArgs = opts?.settleOutcome !== undefined ? settleArgsOfDecided(opts.settleOutcome) : { allowed, updatedInput: parsed.updatedInput, settledBy: liveSettledBy(parsed.decidedBy) };
2251
2360
  const readRootGrant = opts?.settleOutcome === undefined || opts.settleOutcomeIsThisRequest === true ? parsed.readRootGrant : undefined;
2252
2361
  entry.settle(settleArgs.allowed, settleArgs.allowed ? "allowed" : "denied", settleArgs.updatedInput, settleArgs.settledBy, settleArgs.allowed ? undefined : parsed.note, readRootGrant);
2253
2362
  const updatedInputDelivered = opts?.updatedInputDelivered ?? true;
@@ -2279,7 +2388,7 @@ export class ToolApprovalCoordinator {
2279
2388
  ...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
2280
2389
  ...(e.governanceForced ? { governanceForced: true } : {}),
2281
2390
  ...(e.mandated ? { mandated: true } : {}),
2282
- ...(standingAnswerMayRetire(pendingEntryStandingAnswerFacts(e)) ? {} : { rememberRefused: true }),
2391
+ ...(standingAnswerHome(pendingEntryStandingAnswerFacts(e)) === "none" ? { rememberRefused: true } : {}),
2283
2392
  ...(e.mandate !== undefined ? { mandate: e.mandate } : {}),
2284
2393
  ...(e.origin !== undefined ? { origin: e.origin } : {}),
2285
2394
  ...(e.originTaskId !== undefined ? { fromSubagent: true, originTaskId: e.originTaskId } : {}),
@@ -43,7 +43,7 @@ type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNo
43
43
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
44
44
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
45
45
  type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
46
- type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "allowRuleShadowed" | "probeCause" | "probeReason" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate" | "askClass" | "classes";
46
+ type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "allowRuleShadowed" | "probeCause" | "probeReason" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate" | "askClass" | "classes" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling";
47
47
  type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "hasBidiControls" | "previewWithheld" | "execCwd" | "authoredBy";
48
48
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
49
49
  type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
@@ -136,7 +136,7 @@ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTrace
136
136
  /** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
137
137
  type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
138
138
  type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
139
- type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate";
139
+ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling";
140
140
  type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld" | "authoredBy";
141
141
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
142
142
  type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
@@ -144,11 +144,11 @@ type ParkArmKeys = keyof Extract<PendingAction, {
144
144
  kind: "tool_approval";
145
145
  }>;
146
146
  type ParkArmQueueProjected = "toolName" | "toolCallId" | "boundInputHash" | "ruleOffers" | "hasBidiControls" | keyof import("../approval-card.js").ParkRowFacts;
147
- type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "names" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "authoredBy" | "boundGrantDigest";
147
+ type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "names" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "authoredBy" | "boundGrantDigest" | "boundGrants" | "classifierNoRuling";
148
148
  type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueueProjected | ParkArmQueueExcluded>>;
149
149
  type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
150
150
  type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
151
- type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory" | "userInvokedSkills" | "allowedTools";
151
+ type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "approvalReach" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory" | "userInvokedSkills" | "allowedTools";
152
152
  type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence" | "outputStyle";
153
153
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
154
154
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
@@ -199,7 +199,7 @@ type WorkflowRunStripped = "sourceTaskId" | "originatingSessionId" | "effectiveA
199
199
  type _GuardWorkflowRun = AssertAllKeysHandled<Exclude<keyof WorkflowRun, WorkflowRunForwarded | WorkflowRunStripped>>;
200
200
  type _GuardWorkflowRunReverse = AssertAllKeysHandled<Exclude<WorkflowRunForwarded | WorkflowRunStripped, keyof WorkflowRun>>;
201
201
  type ParkRowKeys = keyof NonNullable<WorkflowRun["parks"]>[number];
202
- type ParkRowProjected = "callKey" | "sessionId" | "originRunId" | "originUnconfirmed";
202
+ type ParkRowProjected = "callKey" | "sessionId" | "originRunId" | "originUnconfirmed" | "parkedBy" | "generation";
203
203
  type ParkRowWithheld = "token";
204
204
  type _GuardParkRow = AssertAllKeysHandled<Exclude<ParkRowKeys, ParkRowProjected | ParkRowWithheld>>;
205
205
  type _NoDoubleBookParkRow = AssertAllKeysHandled<Extract<ParkRowProjected, ParkRowWithheld>>;
@@ -50,6 +50,7 @@ import { stripFormatCharacters } from "@sema-agent/core";
50
50
  * 现役消费方:`src/tool-approval.ts` 的 `durableRuleMaterial`(规则车道素材闸)与语料的 FP=0 格。
51
51
  */
52
52
  export declare const normalizeForRedaction: typeof stripFormatCharacters;
53
+ export declare const REDACTION_TOKEN_RE_SRC: string;
53
54
  export declare function armPoolBudgetForTest(): {
54
55
  spanCount: number;
55
56
  coveredPositions: number;
@@ -163,6 +164,11 @@ export declare function redactionSpansForTest(text: string, opts?: {
163
164
  * 让下游看到"这里有东西被省略了",而不是丢一个空对象或炸掉整条流。
164
165
  */
165
166
  export declare function redactDeep(value: unknown): unknown;
167
+ /** {@link redactDeep} 的**串叶处置可注入**形:同一只环 / 深度守卫的遍历,只有「一个串叶怎么脱」由调用方给。
168
+ * `redactDeep` 本身就是 `leaf = redactSecrets` 的那一档(行为逐字不变)。读面要的是另一档 —— 串叶走
169
+ * `redactModelText`(认出凭据形才改写字节,不含凭据形的串逐字节不变),遍历不该因此再写第二份。
170
+ * `leaf` 必须是纯的串→串函数且**自己不抛**(抛了就穿出本遍历)。 */
171
+ export declare function redactDeepWith(value: unknown, leaf: (s: string) => string): unknown;
166
172
  /**
167
173
  * A REDACTED, length-BOUNDED string preview of a structured value — for surfacing a pending tool call's `args`
168
174
  * in the assistant inbox ("what is this ask about") WITHOUT leaking secrets or dumping an unbounded payload (a
@@ -1,7 +1,7 @@
1
1
  import { displayUrlOf, stripFormatCharacters } from "@sema-agent/core";
2
2
  import { recordFailOpen } from "../observability/fail-open.js";
3
3
  export const normalizeForRedaction = stripFormatCharacters;
4
- const REDACTION_TOKEN_RE_SRC = String.raw `«redacted(?::[a-z-]{1,16}){0,2}»`;
4
+ export const REDACTION_TOKEN_RE_SRC = String.raw `«redacted(?::[a-z-]{1,16}){0,2}»`;
5
5
  const ARM_VALUE_CLASSES = [];
6
6
  const atLeast = (cls, min) => {
7
7
  ARM_VALUE_CLASSES.push(cls);
@@ -1797,11 +1797,14 @@ export function redactionSpansForTest(text, opts) {
1797
1797
  }
1798
1798
  const REDACT_MAX_DEPTH = 64;
1799
1799
  export function redactDeep(value) {
1800
- return redactDeepInner(value, 0, new WeakSet());
1800
+ return redactDeepWith(value, redactSecrets);
1801
1801
  }
1802
- function redactDeepInner(value, depth, seen) {
1802
+ export function redactDeepWith(value, leaf) {
1803
+ return redactDeepInner(value, 0, new WeakSet(), leaf);
1804
+ }
1805
+ function redactDeepInner(value, depth, seen, leaf) {
1803
1806
  if (typeof value === "string")
1804
- return redactSecrets(value);
1807
+ return leaf(value);
1805
1808
  if (!value || typeof value !== "object")
1806
1809
  return value;
1807
1810
  if (seen.has(value))
@@ -1811,11 +1814,8 @@ function redactDeepInner(value, depth, seen) {
1811
1814
  seen.add(value);
1812
1815
  try {
1813
1816
  if (Array.isArray(value))
1814
- return value.map((v) => redactDeepInner(v, depth + 1, seen));
1815
- const out = {};
1816
- for (const [k, v] of Object.entries(value))
1817
- out[k] = redactDeepInner(v, depth + 1, seen);
1818
- return out;
1817
+ return value.map((v) => redactDeepInner(v, depth + 1, seen, leaf));
1818
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactDeepInner(v, depth + 1, seen, leaf)]));
1819
1819
  }
1820
1820
  finally {
1821
1821
  seen.delete(value);
@@ -19,9 +19,9 @@ export declare const SELF_MINTED_WIRE: {
19
19
  };
20
20
  };
21
21
  /**
22
- * S-928(7.110.0):`engine_notice` `task.user_steer_undrained` 上**本仓铸**的那一臂。词表属主是 core(`USER_STEER_UNDRAINED_CAUSES`,
22
+ * S-928(7.110.0):`engine_notice` `task.user_steer_undrained` 上**本仓铸**的那一臂。词表属主是 core(`USER_INPUT_UNDRAINED_CAUSES`,两族共用;
23
23
  * `leg_end` 由引擎铸、逐字过境,不在本表);本表登记的是「这个值自哪一版起由本服务出现在 wire 上」。`satisfies Record<CrashedCause, …>`:
24
- * core 删掉 `crashed` ⇒ `CrashedCause` 成 never ⇒ 本行编译红。
24
+ * core 删掉 `crashed` ⇒ `CrashedCause` 成 never ⇒ 本行编译红。码按受理时的车道选(`user-input-ledger.ts` `acceptedInputKindOf`,车道 = core `injectionLaneOf`,码 = core `USER_INPUT_UNDRAINED_FAMILIES`):本行 = steer 车道。
25
25
  */
26
26
  readonly userSteerUndrainedServerCause: {
27
27
  readonly at: "engine_notice task.user_steer_undrained detail.cause(本服务铸的臂;下一条同会话 run 开通告口那一拍)";
@@ -32,6 +32,16 @@ export declare const SELF_MINTED_WIRE: {
32
32
  };
33
33
  };
34
34
  };
35
+ /** S-1122(7.118.0,core 7.44.0 #1362):同一臂在 follow-up 车道(`later` 档)上的那一族。7.110.0–7.117.x 的这类丢失报在 steer 一族里。 */
36
+ readonly userFollowupUndrainedServerCause: {
37
+ readonly at: "engine_notice task.user_followup_undrained detail.cause(本服务铸的臂;`later` 档受理的崩溃丢失,下一条同会话 run 开通告口那一拍)";
38
+ readonly members: {
39
+ crashed: {
40
+ attr: string;
41
+ since: string;
42
+ };
43
+ };
44
+ };
35
45
  /**
36
46
  * S-961(7.111.0):`wiring_manifest.instructions.selected[]` 的成员值 = 指令文件链的成员(相对 cwd 的拼写)。属主是 `project-memory.ts`
37
47
  * 的 `INSTRUCTION_FILE_CHAIN`([ref] 序:CLAUDE 族先、AGENTS 族后,首中即选)—— 链加 / 删一员 ⇒ 本行 `satisfies` 编译红,登记跟着动。
@@ -95,6 +105,10 @@ export declare const SELF_MINTED_WIRE: {
95
105
  readonly attr: 409;
96
106
  readonly since: "7.101.0";
97
107
  };
108
+ readonly "memory.scope_home_not_directory": {
109
+ readonly attr: 409;
110
+ readonly since: "7.118.0";
111
+ };
98
112
  readonly "memory.erasure_evidence_unavailable": {
99
113
  readonly attr: 409;
100
114
  readonly since: "7.108.0";
@@ -12,6 +12,12 @@ export const SELF_MINTED_WIRE = {
12
12
  crashed: { attr: "-", since: "7.110.0" },
13
13
  },
14
14
  },
15
+ userFollowupUndrainedServerCause: {
16
+ at: "engine_notice task.user_followup_undrained detail.cause(本服务铸的臂;`later` 档受理的崩溃丢失,下一条同会话 run 开通告口那一拍)",
17
+ members: {
18
+ crashed: { attr: "-", since: "7.118.0" },
19
+ },
20
+ },
15
21
  wiringInstructionsSelected: {
16
22
  at: "wiring_manifest instructions.selected[](本腿 loader 选中的链成员;段缺席 = 本腿无陈述)",
17
23
  members: {
@@ -34,6 +40,7 @@ export const SELF_MINTED_WIRE = {
34
40
  members: {
35
41
  "config.memory_erasure_request": { attr: 400, since: "7.52.0" },
36
42
  "memory.target_outside_store": { attr: 409, since: "7.101.0" },
43
+ "memory.scope_home_not_directory": { attr: 409, since: "7.118.0" },
37
44
  "memory.erasure_evidence_unavailable": { attr: 409, since: "7.108.0" },
38
45
  "memory.erasure_selector_mismatch": { attr: 409, since: "7.108.0" },
39
46
  "memory.erasure_census_incomplete": { attr: 409, since: "7.108.0" },