@sema-agent/client-core 0.112.0 → 0.113.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 (182) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +37 -24
  3. package/dist/adapt/arms.js +7 -1
  4. package/dist/adapt/textStream.d.ts +2 -0
  5. package/dist/adapt/textStream.js +27 -1
  6. package/dist/adapter/activeRunSelfHeal.d.ts +7 -5
  7. package/dist/adapter/activeRunSelfHeal.js +38 -66
  8. package/dist/adapter/downstream/eventToSdkMessage.js +4 -0
  9. package/dist/adapter/downstream/terminalToSdkResult.d.ts +8 -0
  10. package/dist/adapter/downstream/terminalToSdkResult.js +36 -1
  11. package/dist/adapter/runStream.d.ts +2 -12
  12. package/dist/adapter/runStream.js +46 -19
  13. package/dist/agentSession/engineAgentRegistryRead.js +2 -1
  14. package/dist/agentSession/engineSessionBackgroundStop.js +2 -2
  15. package/dist/attachmentOutcome.d.ts +35 -0
  16. package/dist/attachmentOutcome.js +192 -0
  17. package/dist/attachmentPathPlan.d.ts +67 -0
  18. package/dist/attachmentPathPlan.js +203 -0
  19. package/dist/attachmentRoute.d.ts +9 -1
  20. package/dist/attachmentRoute.js +23 -2
  21. package/dist/declared/supportFloorDeclared.d.ts +1 -1
  22. package/dist/declared/supportFloorDeclared.js +1 -0
  23. package/dist/deviceRebind.js +2 -3
  24. package/dist/displayUntrusted.js +126 -19
  25. package/dist/engineAddress.d.ts +1 -1
  26. package/dist/engineAddress.js +9 -6
  27. package/dist/engineErrorCodes.d.ts +3 -0
  28. package/dist/engineErrorCodes.js +4 -0
  29. package/dist/engineNoticeCodes.d.ts +34 -4
  30. package/dist/engineNoticeCodes.js +133 -6
  31. package/dist/engineUnavailable.d.ts +4 -2
  32. package/dist/engineUnavailable.js +99 -17
  33. package/dist/fleet/fleetLedger.d.ts +9 -3
  34. package/dist/fleet/fleetLedger.js +112 -16
  35. package/dist/fleet/fleetProjection.d.ts +5 -0
  36. package/dist/fleet/fleetProjection.js +9 -1
  37. package/dist/gateVocabulary.d.ts +7 -0
  38. package/dist/gateVocabulary.js +40 -14
  39. package/dist/generated/engineFactTables.d.ts +4 -2
  40. package/dist/generated/engineFactTables.js +198 -2
  41. package/dist/generated/engineNoticeTables.js +56 -8
  42. package/dist/generated/settingsSchemaFns.d.ts +7 -0
  43. package/dist/generated/settingsSchemaFns.js +11 -0
  44. package/dist/generated/settingsSchemaHash.d.ts +1 -0
  45. package/dist/generated/settingsSchemaHash.js +58 -0
  46. package/dist/generated/settingsSchemaOwnKeyRead.js +6 -1
  47. package/dist/generated/settingsSchemaTables.d.ts +3 -2
  48. package/dist/generated/settingsSchemaTables.js +14 -0
  49. package/dist/generated/settingsSchemaTightenVerdict.d.ts +1 -0
  50. package/dist/generated/settingsSchemaTightenVerdict.js +125 -0
  51. package/dist/headlessPermissionModeWire.d.ts +7 -2
  52. package/dist/headlessPermissionModeWire.js +77 -8
  53. package/dist/hitl/approvalDecisionDoor.d.ts +24 -0
  54. package/dist/hitl/approvalDecisionDoor.js +77 -0
  55. package/dist/hitl/approvalDelivery.d.ts +6 -0
  56. package/dist/hitl/approvalDelivery.js +57 -7
  57. package/dist/hitl/approvalResolution.d.ts +6 -3
  58. package/dist/hitl/approvalResolution.js +24 -2
  59. package/dist/hitl/approvalsFeed.js +1 -1
  60. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  61. package/dist/hitl/decisionSend.d.ts +7 -4
  62. package/dist/hitl/decisionSend.js +90 -41
  63. package/dist/hitl/frameRouter.d.ts +3 -3
  64. package/dist/hitl/frameRouter.js +5 -0
  65. package/dist/hitl/hitlBridge.d.ts +1 -0
  66. package/dist/hitl/hitlBridge.js +17 -4
  67. package/dist/hitl/hitlHostSurface.d.ts +3 -2
  68. package/dist/hitl/hitlHostSurface.js +5 -3
  69. package/dist/hitl/legacyApprovalShapes.d.ts +142 -0
  70. package/dist/hitl/legacyApprovalShapes.js +1 -0
  71. package/dist/hitl/livePendingAsk.d.ts +4 -4
  72. package/dist/hitl/livePendingAsk.js +5 -3
  73. package/dist/hitl/parkResolver.js +1 -1
  74. package/dist/hitl/persistedRulesWire.js +2 -2
  75. package/dist/hitl/planReviewCard.d.ts +4 -2
  76. package/dist/hitl/planReviewCard.js +66 -1
  77. package/dist/hitl/planReviewWire.d.ts +34 -2
  78. package/dist/hitl/planReviewWire.js +326 -150
  79. package/dist/hitl/sessionPolicyWire.js +16 -7
  80. package/dist/hitl/subagentSource.d.ts +1 -0
  81. package/dist/hitl/subagentSource.js +11 -0
  82. package/dist/hitl/toolApprovalWire.d.ts +34 -10
  83. package/dist/hitl/toolApprovalWire.js +182 -33
  84. package/dist/hookMatcherVerdict.d.ts +2 -1
  85. package/dist/hookMatcherVerdict.js +3 -2
  86. package/dist/hooksReaderGenerations.d.ts +8 -0
  87. package/dist/hooksReaderGenerations.js +49 -0
  88. package/dist/hooksWireCaps.js +43 -18
  89. package/dist/hostSettingsSource.d.ts +1 -1
  90. package/dist/hostSettingsSource.js +2 -2
  91. package/dist/hostTurnSettings.d.ts +2 -2
  92. package/dist/hostTurnSettings.js +87 -47
  93. package/dist/humanTurn.d.ts +2 -1
  94. package/dist/humanTurn.js +44 -13
  95. package/dist/humanTurnRead.d.ts +7 -0
  96. package/dist/humanTurnRead.js +23 -14
  97. package/dist/humanTurnSentences.js +3 -0
  98. package/dist/i18n/messages.en.d.ts +93 -5
  99. package/dist/i18n/messages.en.js +100 -12
  100. package/dist/i18n/messages.zh.js +110 -15
  101. package/dist/index.d.ts +29 -15
  102. package/dist/index.js +17 -9
  103. package/dist/liveModelCatalog.js +1 -0
  104. package/dist/liveQuestionStore.d.ts +2 -0
  105. package/dist/liveQuestionStore.js +7 -2
  106. package/dist/localConfig/kernel.d.ts +2 -1
  107. package/dist/localConfig/kernel.js +12 -18
  108. package/dist/localConfig/mcp/edit.js +25 -27
  109. package/dist/localConfig/mcp/servers.js +5 -8
  110. package/dist/localConfig/modelPool.d.ts +6 -1
  111. package/dist/localConfig/modelPool.js +104 -27
  112. package/dist/localConfig/modelPoolWords.d.ts +2 -0
  113. package/dist/localConfig/modelPoolWords.js +1 -0
  114. package/dist/localConfig/settingsWrite.js +1 -14
  115. package/dist/localConfig/skills.js +9 -16
  116. package/dist/localEngineSpawn.d.ts +29 -1
  117. package/dist/localEngineSpawn.js +62 -1
  118. package/dist/mcpLiveness.d.ts +1 -1
  119. package/dist/mcpProbeWire.js +2 -2
  120. package/dist/mcpReconnect.js +2 -2
  121. package/dist/model/catalogLoader.js +3 -17
  122. package/dist/model/onboardJudgement.d.ts +8 -0
  123. package/dist/model/onboardJudgement.js +8 -3
  124. package/dist/model/onboardSteps.d.ts +19 -0
  125. package/dist/model/onboardSteps.js +76 -0
  126. package/dist/notifications.js +20 -12
  127. package/dist/ownKey.d.ts +4 -0
  128. package/dist/ownKey.js +61 -0
  129. package/dist/pathShape.d.ts +14 -0
  130. package/dist/pathShape.js +44 -0
  131. package/dist/planReviewCardFacts.d.ts +3 -0
  132. package/dist/planReviewCardFacts.js +20 -3
  133. package/dist/planReviewOutcomeCause.d.ts +2 -0
  134. package/dist/planReviewOutcomeCause.js +12 -0
  135. package/dist/pluginHooksWire.d.ts +20 -8
  136. package/dist/pluginHooksWire.js +204 -86
  137. package/dist/prepareRefusal.d.ts +2 -0
  138. package/dist/prepareRefusal.js +11 -1
  139. package/dist/request/taskRequest.js +3 -1
  140. package/dist/resultFrameKeys.d.ts +1 -0
  141. package/dist/resultFrameKeys.js +3 -0
  142. package/dist/resumeRefusalCopy.js +3 -1
  143. package/dist/retryStatus.d.ts +11 -1
  144. package/dist/retryStatus.js +15 -4
  145. package/dist/runTerminal.d.ts +1 -0
  146. package/dist/runTerminal.js +15 -1
  147. package/dist/sdkWireTransit.d.ts +1 -1
  148. package/dist/seatContract.d.ts +12 -3
  149. package/dist/seatContract.js +24 -13
  150. package/dist/sessionEngine.js +1 -1
  151. package/dist/sessionHooksRoster.js +8 -6
  152. package/dist/sessionIdSegment.d.ts +0 -1
  153. package/dist/sessionIdSegment.js +1 -3
  154. package/dist/sessionMemoryErase.js +2 -2
  155. package/dist/sessionMemoryStatus.js +2 -1
  156. package/dist/sessionOccupant.d.ts +29 -0
  157. package/dist/sessionOccupant.js +78 -0
  158. package/dist/settingsHooksSalvage.d.ts +4 -1
  159. package/dist/settingsHooksSalvage.js +22 -9
  160. package/dist/skillOverrideChain.d.ts +3 -0
  161. package/dist/skillOverrideChain.js +31 -4
  162. package/dist/skillsWireCaps.d.ts +16 -2
  163. package/dist/skillsWireCaps.js +47 -20
  164. package/dist/subagent/agentStateDetail.js +32 -8
  165. package/dist/subagent/engineCompactWire.js +48 -36
  166. package/dist/subagent/engineDelegatedPrompt.d.ts +3 -2
  167. package/dist/subagent/engineDelegatedPrompt.js +65 -28
  168. package/dist/subagentEditOutcomes.d.ts +9 -0
  169. package/dist/subagentEditOutcomes.js +43 -0
  170. package/dist/supportFloor.d.ts +2 -1
  171. package/dist/supportFloor.js +1 -1
  172. package/dist/toolResult.js +4 -2
  173. package/dist/toolRoster.d.ts +1 -0
  174. package/dist/toolRoster.js +1 -0
  175. package/dist/usageAnalytics.js +2 -1
  176. package/dist/userAttachmentEnvelope.d.ts +11 -0
  177. package/dist/userAttachmentEnvelope.js +7 -2
  178. package/dist/userAttachmentSeat.d.ts +7 -1
  179. package/dist/userAttachmentSeat.js +44 -7
  180. package/dist/wiringInstructionsAttribution.js +8 -2
  181. package/docs/INTEGRATION-CLIENTS.md +1654 -16
  182. package/package.json +5 -5
@@ -6,7 +6,7 @@ import { ruleToolNameVerdictOf } from '../protocolSegment.js';
6
6
  import { CORRUPT_ROW_ERROR_CODE, RULES_INERT_ENTRY, RULES_INVALID_SHAPE, isRulesRefusalCode } from '../engineErrorCodes.js';
7
7
  import { deniedEntryToolName } from '../engineToolDirectory.js';
8
8
  import { rosterMountedNames } from '../rosterMountedNames.js';
9
- import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS } from '../generated/engineFactTables.js';
9
+ import { CORE_ALLOW_ENTRY_INERT_REASONS, CORE_RULE_TOOL_NAME_VERDICTS, CORE_SESSION_RULES_GRAMMAR } from '../generated/engineFactTables.js';
10
10
  const EARLIER_GRAMMAR_REASON = 'earlier_grammar';
11
11
  const _inertReasonPin = true;
12
12
  void _inertReasonPin;
@@ -234,7 +234,7 @@ function receiptMismatch(sent, receipt, sentRev, receiptRev, sentGrammar, receip
234
234
  if (receiptRev <= sentRev) {
235
235
  return `policy write receipt did not advance the version stamp (sent with ${sentRev}, receipt says ${receiptRev})`;
236
236
  }
237
- if (sentGrammar !== undefined && (receiptGrammar === undefined || receiptGrammar < sentGrammar)) {
237
+ if (receiptGrammar === undefined || receiptGrammar < sentGrammar) {
238
238
  return `policy write receipt does not carry the rule-grammar stamp that was sent (sent ${sentGrammar}, receipt ${receiptGrammar === undefined ? 'carries none' : `says ${receiptGrammar}`}) — the stored entries may now read differently`;
239
239
  }
240
240
  for (const field of SESSION_POLICY_RULE_FIELDS) {
@@ -342,7 +342,14 @@ function earlierGrammarOutcome(failure) {
342
342
  };
343
343
  }
344
344
  function rulesWithStamp(rules, grammar) {
345
- return grammar === undefined ? rules : { ...rules, grammar };
345
+ return { ...rules, grammar };
346
+ }
347
+ const GRAMMAR_BEFORE_STAMP = 0;
348
+ function writeGrammarOf(read) {
349
+ const stamp = grammarStampOf(read);
350
+ if (stamp !== undefined)
351
+ return stamp;
352
+ return read.rev >= 1 ? GRAMMAR_BEFORE_STAMP : CORE_SESSION_RULES_GRAMMAR;
346
353
  }
347
354
  function grammarStampOf(read) {
348
355
  return Object.hasOwn(read, 'grammar') ? read.grammar : undefined;
@@ -361,9 +368,10 @@ async function attemptTighten(facade, req, opts) {
361
368
  if (earlier.length > 0)
362
369
  return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
363
370
  }
371
+ const declared = writeGrammarOf(read);
364
372
  let receipt;
365
373
  try {
366
- receipt = await facade.putPolicy(req.sessionId, { rules: rulesWithStamp(rules, stamp), expectedRev: read.rev }, opts);
374
+ receipt = await facade.putPolicy(req.sessionId, { rules: rulesWithStamp(rules, declared), expectedRev: read.rev }, opts);
367
375
  }
368
376
  catch (e) {
369
377
  const failure = classifySessionPolicyFailure(e);
@@ -389,7 +397,7 @@ async function attemptTighten(facade, req, opts) {
389
397
  if (!narrowed.ok) {
390
398
  return { done: { kind: 'unknown', why: 'write_receipt_unreadable', message: narrowed.detail } };
391
399
  }
392
- const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar);
400
+ const mismatch = receiptMismatch(rules, narrowed.rules, read.rev, narrowed.rev, declared, narrowed.grammar);
393
401
  if (mismatch !== null)
394
402
  return { done: { kind: 'unknown', why: 'write_receipt_mismatch', message: mismatch } };
395
403
  return { done: { kind: 'written', rev: narrowed.rev } };
@@ -626,9 +634,10 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
626
634
  if (earlier.length > 0)
627
635
  return { done: { kind: 'refused', why: 'earlier_grammar_rows', entries: earlier } };
628
636
  }
637
+ const declared = writeGrammarOf(read);
629
638
  let receipt;
630
639
  try {
631
- receipt = await facade.putPolicy(sessionId, { rules: rulesWithStamp(next, stamp), expectedRev: read.rev }, opts);
640
+ receipt = await facade.putPolicy(sessionId, { rules: rulesWithStamp(next, declared), expectedRev: read.rev }, opts);
632
641
  }
633
642
  catch (e) {
634
643
  const failure = classifySessionPolicyFailure(e);
@@ -651,7 +660,7 @@ async function attemptRemoval(facade, sessionId, mounted, opts) {
651
660
  }
652
661
  if (!narrowed.ok)
653
662
  return { done: { kind: 'write_unconfirmed', why: 'write_receipt_unreadable', message: narrowed.detail } };
654
- const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev, stamp, narrowed.grammar) ??
663
+ const mismatch = receiptMismatch(next, narrowed.rules, read.rev, narrowed.rev, declared, narrowed.grammar) ??
655
664
  (removed.some((e) => {
656
665
  const r = Object.hasOwn(narrowed.rules, e.field) ? narrowed.rules[e.field] : undefined;
657
666
  return r !== undefined && r.includes(e.rule);
@@ -1 +1,2 @@
1
1
  export declare function subagentSourceSessionOf(carrier: object | null | undefined): string | undefined;
2
+ export declare function livePendingRowSourceSessionOf(row: object | null | undefined): string | undefined;
@@ -11,3 +11,14 @@ export function subagentSourceSessionOf(carrier) {
11
11
  const retired = slotReadOf(carrier, 'sourceTaskId');
12
12
  return retired.presence === 'present' ? nonEmpty(retired.value) : undefined;
13
13
  }
14
+ export function livePendingRowSourceSessionOf(row) {
15
+ if (typeof row !== 'object' || row === null)
16
+ return undefined;
17
+ const current = slotReadOf(row, 'sourceSessionId');
18
+ if (current.presence === 'present')
19
+ return nonEmpty(current.value);
20
+ if (current.presence !== 'absent')
21
+ return undefined;
22
+ const retired = slotReadOf(row, 'originTaskId');
23
+ return retired.presence === 'present' ? nonEmpty(retired.value) : undefined;
24
+ }
@@ -6,6 +6,8 @@ import { type ApprovalAllowRuleShadow } from './allowRuleShadow.js';
6
6
  import { CORE_PERSISTED_RULE_MATCHES } from '../generated/engineFactTables.js';
7
7
  import type { UiLanguage } from '../env/uiLanguage.js';
8
8
  import type { SessionRuleInertReason } from './sessionPolicyWire.js';
9
+ import type { LegacyToolApprovalFrame, LegacyToolApprovalRespondAck, LegacyToolApprovalRespondApplied, LegacyToolApprovalRespondReplayed } from './legacyApprovalShapes.js';
10
+ import { type ApprovalDecisionDoorReading, type AskDoorDecisionBody } from './approvalDecisionDoor.js';
9
11
  import { type ApprovalCardClaim } from './approvalHolds.js';
10
12
  import { type ApprovalResolution } from './approvalResolution.js';
11
13
  import { type HostWithdrawal } from './hostWithdrawal.js';
@@ -15,7 +17,7 @@ import { type ApprovalDecidedByReading } from '../approvalDecidedByCapability.js
15
17
  import { type PolicyDenyCause } from './policyDenyCause.js';
16
18
  import { type ApprovalRequestFrameReading, type ApprovalRequestFrameUnreadableWhy } from './approvalRequestFrameReading.js';
17
19
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS } from '@sema-agent/sdk';
18
- import type { ApprovalDecidedBy, ApprovalCardDelegation, ToolApprovalDecision as SdkToolApprovalDecision, ToolApprovalRespondAck, ToolApprovalRespondReplayed, PersistedRuleAnchor, RuleOfferMatch, RuleOfferBatchMember, RuleOfferUncoveredDetail, RuleOfferUncoveredReason, AskOrigin, RuleOffersAbsence, DenialLimitKind, DenialLimitFallback, ToolApprovalFrame, ApprovalRequestFrame, RuleOffer as SdkRuleOffer } from '@sema-agent/sdk';
20
+ import type { ApprovalDecidedBy, ApprovalCardDelegation, PersistedRuleAnchor, RuleOfferMatch, RuleOfferBatchMember, RuleOfferUncoveredDetail, RuleOfferUncoveredReason, AskOrigin, RuleOffersAbsence, DenialLimitKind, DenialLimitFallback, ApprovalRequestFrame, RuleOffer as SdkRuleOffer } from '@sema-agent/sdk';
19
21
  export declare function toolNameIsFsWrite(name: string): boolean;
20
22
  export declare function toolNameIsShellExec(name: string): boolean;
21
23
  export interface ToolApprovalGateLike {
@@ -183,13 +185,15 @@ export declare function surfaceApprovalCard(req: ApprovalCardRequest): Promise<A
183
185
  declare const REMEMBER_REFUSAL_CODES: readonly ["remember_not_in_proof", "capability.store_required", "decide.workflow_remember_unsupported", "decide.parked_remember_unsupported", "remember_requires_binding"];
184
186
  export type RememberRefusalCode = (typeof REMEMBER_REFUSAL_CODES)[number];
185
187
  export declare function rememberRefusalOf(error: unknown): RememberRefusalCode | undefined;
188
+ export declare const FEEDBACK_REFUSAL_CODES: readonly ["feedback_not_in_proof"];
189
+ export declare function feedbackRefusalOf(error: unknown): boolean;
186
190
  export declare function surfaceFsApprovalAndDecide(deps: FsApprovalWireDeps, taskId: string, argsByCall: Map<string, unknown>, signal?: AbortSignal, parkGatedCallId?: string, parkToolName?: string): Promise<ApprovalResolution>;
187
191
  export interface BoundSkillGrantsWire {
188
192
  readonly skill: string | null;
189
193
  readonly allowedTools: readonly string[];
190
194
  readonly disallowedTools: readonly string[];
191
195
  }
192
- export type { ToolApprovalFrame };
196
+ export type { LegacyToolApprovalFrame, LegacyToolApprovalRespondAck };
193
197
  export interface ReadRootCandidateView {
194
198
  dir: string;
195
199
  clearsThisAsk: true;
@@ -208,8 +212,8 @@ export declare function readApprovalMandate(v: {
208
212
  } | undefined | null): ApprovalMandateWord | undefined;
209
213
  export { readApprovalCardWord };
210
214
  export declare function toolPermissionRequestAskBits(card: object | null | undefined, requestId?: string): Pick<ToolPermissionRequest, 'mandated' | 'mandate' | 'origin' | 'ruleOffersAbsence' | 'askClass' | 'classes' | 'sessionRow' | 'probeReason' | 'probeCause' | 'feedbackCapable' | 'requestedBy' | 'frameReading' | 'inputSource'>;
211
- export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
212
- export type ToolApprovalRespondDecision = SdkToolApprovalDecision;
215
+ export declare function isFromSubagent(frame: LegacyToolApprovalFrame): boolean;
216
+ export type ToolApprovalRespondDecision = (typeof RESPOND_DECISIONS)[number];
213
217
  export declare const RESPOND_DECISIONS: readonly ["allow", "allow_session", "deny"];
214
218
  export type AnswerPayloadLane = 'respond' | 'decide';
215
219
  export interface AnswerPayloadCaps {
@@ -259,8 +263,8 @@ export interface RespondToolApprovalOpts {
259
263
  settledBy?: 'policy';
260
264
  decidedBy?: Extract<ApprovalDecidedBy, 'policy'>;
261
265
  }
262
- export type RespondToolApprovalFn = (approvalId: string, decision: ToolApprovalRespondDecision, opts?: RespondToolApprovalOpts) => Promise<ToolApprovalRespondAck | void>;
263
- export type ToolApprovalRespondAckView = ToolApprovalRespondAck | ToolApprovalRespondRedeemedView;
266
+ export type RespondToolApprovalFn = (approvalId: string, decision: ToolApprovalRespondDecision, opts?: RespondToolApprovalOpts) => Promise<LegacyToolApprovalRespondAck | void>;
267
+ export type ToolApprovalRespondAckView = LegacyToolApprovalRespondAck | ToolApprovalRespondRedeemedView;
264
268
  export interface ToolApprovalRespondRedeemedView {
265
269
  readonly approvalId: string;
266
270
  readonly askId: string;
@@ -269,12 +273,24 @@ export interface ToolApprovalRespondRedeemedView {
269
273
  readonly decision?: string;
270
274
  readonly rememberApplied?: boolean;
271
275
  }
272
- export declare function isToolApprovalRespondReplayed(ack: ToolApprovalRespondAckView): ack is ToolApprovalRespondReplayed;
276
+ export declare function isToolApprovalRespondReplayed(ack: ToolApprovalRespondAckView): ack is LegacyToolApprovalRespondReplayed;
273
277
  export declare function isToolApprovalRespondParkedRedeemed(ack: object): ack is ToolApprovalRespondRedeemedView;
274
278
  export declare function replayedFirstDecisionOf(ack: ToolApprovalRespondAckView | undefined): 'allow' | 'deny' | undefined;
275
279
  export declare function settlementAckOf(read: ToolApprovalRespondAckView | undefined): ToolApprovalRespondAckView | undefined;
276
280
  export type { PersistedRuleAnchor };
277
281
  export declare function readToolApprovalRespondAck(v: unknown): ToolApprovalRespondAckView | undefined;
282
+ export type AskDoorDecisionBodyReading = {
283
+ readonly kind: 'receipt';
284
+ readonly ack: LegacyToolApprovalRespondApplied;
285
+ } | {
286
+ readonly kind: 'earlier';
287
+ readonly firstDecision: 'allow' | 'deny';
288
+ } | {
289
+ readonly kind: 'other_ask';
290
+ } | {
291
+ readonly kind: 'none';
292
+ };
293
+ export declare function readAskDoorDecisionBody(v: {} | null | undefined, askId: string, sent: ToolApprovalRespondDecision): AskDoorDecisionBodyReading;
278
294
  export interface ToolApprovalRespondParkingView {
279
295
  readonly approvalId: string;
280
296
  readonly retryAfterMs?: number;
@@ -312,7 +328,7 @@ export declare function decisionAttributionKeyOf(reading: ApprovalDecidedByReadi
312
328
  } | {
313
329
  settledBy: 'policy';
314
330
  } | Record<never, never>;
315
- export declare function isToolApprovalFrame(ev: unknown): ev is ToolApprovalFrame;
331
+ export declare function isToolApprovalFrame(ev: unknown): ev is LegacyToolApprovalFrame;
316
332
  export declare function askProbeReasonDetail(card: Pick<ApprovalCardRequest, 'probeReason' | 'probeCause'> | null | undefined, lang?: UiLanguage): string | undefined;
317
333
  export declare function readApprovalRememberRefused(v: unknown): boolean;
318
334
  export declare function readApprovalBoundGrants(v: unknown): BoundSkillGrantsWire | undefined;
@@ -332,6 +348,10 @@ export declare function approvalSupersedesDetail(card: Pick<ApprovalCardRequest,
332
348
  export declare function readDenialLimitFallback(v: unknown): DenialLimitFallback | undefined;
333
349
  export declare function readRuleOffers(v: unknown): RuleOfferReading[] | undefined;
334
350
  export declare function readRuleOfferSupply(offers: unknown): RuleOfferReading[] | undefined;
351
+ export declare function subagentBadgeFor(carrier: object | null | undefined): {
352
+ name: string;
353
+ color: string;
354
+ };
335
355
  export type ApprovalCardV1Facts = Pick<ApprovalCardRequest, 'toolName' | 'args'> & Pick<ApprovalCardRequest, 'toolCallId' | 'workerBadge' | 'wireNote' | 'argsUnavailable' | 'inputSource' | 'governanceForced' | 'delegation' | 'ruleOffersReadOnly' | 'inputHasBidi' | 'allowRuleShadow' | 'probeCause' | 'rememberRefused' | 'ruleEvidence' | 'requiresRealApproval' | 'ruleOffersAbsence' | 'denialLimitFallback' | 'askClass' | 'classes' | 'agentId' | 'askReasons' | 'grantBinding' | 'unreadableEvidence' | 'message' | 'approvalCard'>;
336
356
  export type ApprovalCardFacts = ApprovalCardV1Facts & Pick<ApprovalCardRequest, 'argsRewritten' | 'ruleOffers' | 'probeReason' | 'mandated' | 'mandate' | 'readRootCandidate' | 'origin' | 'boundGrants' | 'classifierNoRuling' | 'allowSessionCarried' | 'cardPayloadUnreadable' | 'cardPayloadUnreadableWhy' | 'requestedBy'>;
337
357
  export interface ApprovalCardFrameFactsOptions {
@@ -349,6 +369,7 @@ export interface ToolApprovalFrameLaneOpts {
349
369
  respondFreeFormRulesCapable?: boolean;
350
370
  respondBatchRuleOffersCapable?: boolean;
351
371
  approvalDecidedBy?: ApprovalDecidedByReading | undefined;
372
+ approvalDecisionDoor?: ApprovalDecisionDoorReading | undefined;
352
373
  windowIsCurrent?: boolean;
353
374
  argsUnavailable?: true;
354
375
  }
@@ -357,8 +378,11 @@ export interface ToolApprovalFrameRunBits {
357
378
  readonly sessionId?: string;
358
379
  readonly sourceGeneration?: number;
359
380
  }
360
- export declare function surfaceToolApprovalFrameAndRespond(frame: ToolApprovalFrame | ApprovalRequestFrame, respond: RespondToolApprovalFn, streamArgs: unknown | undefined, signal?: AbortSignal, lane?: ToolApprovalFrameLaneOpts, run?: ToolApprovalFrameRunBits): Promise<ApprovalResolution>;
361
- export declare function surfaceApprovalFrameWithId(frame: ToolApprovalFrame | ApprovalRequestFrame, approvalId: string, respond: RespondToolApprovalFn, streamArgs: Parameters<typeof surfaceToolApprovalFrameAndRespond>[2], signal: AbortSignal | undefined, lane: ToolApprovalFrameLaneOpts | undefined, run: ToolApprovalFrameRunBits | undefined): Promise<ApprovalResolution>;
381
+ export declare function surfaceToolApprovalFrameAndRespond(frame: LegacyToolApprovalFrame | ApprovalRequestFrame, respond: RespondToolApprovalFn, streamArgs: unknown | undefined, signal?: AbortSignal, lane?: ToolApprovalFrameLaneOpts, run?: ToolApprovalFrameRunBits): Promise<ApprovalResolution>;
382
+ export declare function surfaceApprovalFrameWithId(frame: LegacyToolApprovalFrame | ApprovalRequestFrame, approvalId: string, respond: RespondToolApprovalFn, streamArgs: Parameters<typeof surfaceToolApprovalFrameAndRespond>[2], signal: AbortSignal | undefined, lane: ToolApprovalFrameLaneOpts | undefined, run: ToolApprovalFrameRunBits | undefined): Promise<ApprovalResolution>;
383
+ export declare function askDoorBodyOf(decision: ToolApprovalRespondDecision, o: RespondToolApprovalOpts, askId: string): AskDoorDecisionBody & {
384
+ readonly settledBy?: 'policy';
385
+ };
362
386
  export interface ApprovalHolderIdentity {
363
387
  readonly approvalId?: string;
364
388
  readonly taskId?: string;
@@ -22,7 +22,8 @@ import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
22
22
  import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
23
23
  import { frameBindingOf } from '../frameBinding.js';
24
24
  import { carriesAttributionKey, deliverWithAttributionLadder } from './attributionLadder.js';
25
- import { deliverApprovalDecision, newSendTally } from './approvalDelivery.js';
25
+ import { askDoorParkingOfThrown, deliverApprovalDecision, isAborted, newSendTally, respondRouteGoneOf } from './approvalDelivery.js';
26
+ import { approvalDecisionDoorOf, approvalDecisionVerbOf, hitlAskIdempotencyKey, noteRespondDoorSwitch, ROUTE_MISSING_WIRING_HINT } from './approvalDecisionDoor.js';
26
27
  import { approvalDecisionHolds, approvalHoldFor, awaitApprovalHolder, claimApprovalCardFor, enterLiveRespondPhase, holdLiveCard, joinLiveCard, liveCardHoldOf, clearSettledApprovals, noteHolderSettledApproval, settledApprovalOf, settledForDurableLane, settledLiveCardOutcome } from './approvalHolds.js';
27
28
  import { directFailureVerdictOf, directSafetyStopOf, joinedResolution, resolutionOfDecisionFailure, toolPermissionSettledElsewhereOf } from './approvalResolution.js';
28
29
  import { hostWithdrawalOf } from './hostWithdrawal.js';
@@ -186,8 +187,8 @@ export function rememberRefusalOf(error) {
186
187
  function rememberRefusalIn(reading) {
187
188
  return reading.state === 'code' ? REMEMBER_REFUSAL_CODES.find((c) => c === reading.code) : undefined;
188
189
  }
189
- const FEEDBACK_REFUSAL_CODES = Object.freeze(['feedback_not_in_proof']);
190
- function feedbackRefusalOf(error) {
190
+ export const FEEDBACK_REFUSAL_CODES = Object.freeze(['feedback_not_in_proof']);
191
+ export function feedbackRefusalOf(error) {
191
192
  const reading = readWireErrorCodeReading(error);
192
193
  return reading.state === 'code' && FEEDBACK_REFUSAL_CODES.some((c) => c === reading.code);
193
194
  }
@@ -863,8 +864,6 @@ export function isFromSubagent(frame) {
863
864
  return frame.fromSubagent === true;
864
865
  }
865
866
  export const RESPOND_DECISIONS = Object.freeze(['allow', 'allow_session', 'deny']);
866
- const _respondDecisionPins = [true, true];
867
- void _respondDecisionPins;
868
867
  function isRespondDecision(v) {
869
868
  return typeof v === 'string' && RESPOND_DECISIONS.includes(v);
870
869
  }
@@ -985,6 +984,9 @@ export function readToolApprovalRespondAck(v) {
985
984
  return undefined;
986
985
  if (!isRespondDecision(decision))
987
986
  return undefined;
987
+ return appliedReceiptOf(s, approvalId, decision);
988
+ }
989
+ function appliedReceiptOf(s, approvalId, decision) {
988
990
  const rememberApplied = slotValueOf(s.rememberApplied);
989
991
  const updatedInputForwarded = slotValueOf(s.updatedInputForwarded);
990
992
  const rulePersisted = slotValueOf(s.rulePersisted);
@@ -996,7 +998,7 @@ export function readToolApprovalRespondAck(v) {
996
998
  const out = {
997
999
  approvalId,
998
1000
  delivery: 'applied',
999
- decision: decision,
1001
+ decision,
1000
1002
  ...(typeof rememberApplied === 'boolean' ? { rememberApplied } : {}),
1001
1003
  ...(typeof updatedInputForwarded === 'boolean' ? { updatedInputForwarded } : {}),
1002
1004
  ...(typeof rulePersisted === 'boolean' ? { rulePersisted } : {}),
@@ -1027,6 +1029,26 @@ export function readToolApprovalRespondAck(v) {
1027
1029
  }
1028
1030
  return out;
1029
1031
  }
1032
+ export function readAskDoorDecisionBody(v, askId, sent) {
1033
+ if (v === null || typeof v !== 'object')
1034
+ return Object.freeze({ kind: 'none' });
1035
+ const s = respondAckSlotsOf(v);
1036
+ const want = sent === 'deny' ? 'deny' : 'approve';
1037
+ const idOk = typeof askId === 'string' && askId !== '' && slotValueOf(s.askId) === askId;
1038
+ const dec = slotValueOf(s.decision);
1039
+ if (s.askId.presence !== 'absent' && !idOk)
1040
+ return Object.freeze({ kind: 'other_ask' });
1041
+ if (s.decision.presence !== 'absent' && dec !== want) {
1042
+ return idOk && (dec === 'approve' || dec === 'deny') ? Object.freeze({ kind: 'earlier', firstDecision: dec === 'approve' ? 'allow' : 'deny' }) : Object.freeze({ kind: 'other_ask' });
1043
+ }
1044
+ if (s.accepted.presence !== 'absent' || s.idempotent.presence !== 'absent' || s.delivery.presence !== 'absent')
1045
+ return Object.freeze({ kind: 'none' });
1046
+ if (idOk && dec === want) {
1047
+ const ack = appliedReceiptOf(s, askId, sent);
1048
+ return ack !== undefined ? Object.freeze({ kind: 'receipt', ack }) : Object.freeze({ kind: 'none' });
1049
+ }
1050
+ return Object.freeze({ kind: 'none' });
1051
+ }
1030
1052
  const RESPOND_ACK_SLOT_KEYS = [
1031
1053
  'accepted',
1032
1054
  'idempotent',
@@ -1120,6 +1142,7 @@ function ownIdempotentTrue(raw) {
1120
1142
  return false;
1121
1143
  }
1122
1144
  }
1145
+ const REPLAY_NONE = Object.freeze({ kind: 'none' });
1123
1146
  export function replayVerdictFrom(raw, parsed, approvalId) {
1124
1147
  if (parsed !== undefined && isToolApprovalRespondReplayed(parsed) && parsed.approvalId === approvalId)
1125
1148
  return { kind: 'replayed', firstDecision: parsed.decision, via: 'arm' };
@@ -1506,7 +1529,7 @@ const UNREADABLE_EVIDENCE_KEYS = Object.freeze({
1506
1529
  ]),
1507
1530
  v1: Object.freeze([
1508
1531
  ['rememberRefused', 'rememberRefused'], ['governanceForced', 'governanceForced'], ['requiresRealApproval', 'requiresRealApproval'],
1509
- ['ruleOffersAbsence', 'ruleOffersAbsence'], ['ruleOffers', 'ruleOffersReadOnly'],
1532
+ ['ruleOffersAbsence', 'ruleOffersAbsence'], ['ruleOffers', ['ruleOffersReadOnly', 'ruleOffers']],
1510
1533
  ['allowRuleShadowed', 'allowRuleShadow'], ['askClass', 'askClass'], ['classes', 'classes'],
1511
1534
  ]),
1512
1535
  generic: Object.freeze([['ruleOffersAbsence', 'ruleOffersAbsence'], ['askClass', 'askClass'], ['classes', 'classes']]),
@@ -1525,7 +1548,8 @@ function riskSubtreeUnreadable(card) {
1525
1548
  function withUnreadableEvidence(carrier, leg, req, reported = [], live) {
1526
1549
  const keys = new Set(reported);
1527
1550
  for (const [from, to] of UNREADABLE_EVIDENCE_KEYS[leg]) {
1528
- if (slotPresenceOf(carrier, from) !== 'absent' && !Object.hasOwn(req, to))
1551
+ const read = typeof to === 'string' ? Object.hasOwn(req, to) : to.some((t) => Object.hasOwn(req, t));
1552
+ if (slotPresenceOf(carrier, from) !== 'absent' && !read)
1529
1553
  keys.add(from);
1530
1554
  }
1531
1555
  if (live !== undefined) {
@@ -1715,6 +1739,7 @@ function noteCallOutcome(slot, r) {
1715
1739
  const unsettledFate = {
1716
1740
  engine_refused: 'not_accepted',
1717
1741
  parking_in_progress: 'not_accepted',
1742
+ route_missing: 'not_accepted',
1718
1743
  engine_error: 'unconfirmed',
1719
1744
  no_answer: 'unconfirmed',
1720
1745
  transport_exhausted: 'unconfirmed',
@@ -1921,17 +1946,18 @@ export function readRuleOffers(v) {
1921
1946
  export function readRuleOfferSupply(offers) {
1922
1947
  return readRuleOffers(offers);
1923
1948
  }
1924
- function subagentBadgeFor(frame) {
1925
- let name = typeof frame.sourceAgentName === 'string' && frame.sourceAgentName.length > 0 ? frame.sourceAgentName : '';
1926
- const source = subagentSourceSessionOf(frame);
1927
- if (!name && source !== undefined) {
1928
- name = source;
1929
- try {
1930
- const row = readEngineActiveBgTasks().tasks.find(t => t.id === source);
1931
- if (typeof row?.name === 'string' && row.name.length > 0)
1932
- name = row.name;
1933
- }
1934
- catch {
1949
+ export function subagentBadgeFor(carrier) {
1950
+ let name = guardedRead(() => readOwnDataNonEmptyString(carrier, 'sourceAgentName')) ?? '';
1951
+ if (!name) {
1952
+ const agentId = approvalAgentIdOf(carrier);
1953
+ if (agentId !== undefined) {
1954
+ try {
1955
+ const row = readEngineActiveBgTasks().tasks.find(t => t.id === agentId);
1956
+ if (typeof row?.name === 'string' && row.name.length > 0)
1957
+ name = row.name;
1958
+ }
1959
+ catch {
1960
+ }
1935
1961
  }
1936
1962
  }
1937
1963
  if (!name)
@@ -2011,7 +2037,7 @@ function v1Bit(stamp) {
2011
2037
  const SUSPENDED_ASK_ARGS_NOTE = "this request's tool arguments are not available on this surface — it was raised while no client was connected, " +
2012
2038
  'so you are deciding without seeing them; deny it if you are not sure what it will do. ' +
2013
2039
  'Edits are not accepted on this card: there is no original input to edit';
2014
- function cardFactsOf(carrier, leg, opts, liveFacts, approvalId, payloadWhy) {
2040
+ function cardFactsOf(carrier, leg, opts, liveFacts, approvalId, payloadWhy, rulesRedeemable) {
2015
2041
  const c = carrier;
2016
2042
  const full = leg !== 'generic';
2017
2043
  const live = leg === 'frame' ? carrier : leg === 'v1' ? liveFacts : undefined;
@@ -2093,7 +2119,7 @@ function cardFactsOf(carrier, leg, opts, liveFacts, approvalId, payloadWhy) {
2093
2119
  const offers = readRuleOffers(c.ruleOffers);
2094
2120
  if (offers === undefined)
2095
2121
  return;
2096
- if (leg === 'frame')
2122
+ if (leg === 'frame' || (leg === 'v1' && rulesRedeemable === true))
2097
2123
  out.ruleOffers = offers;
2098
2124
  else
2099
2125
  out.ruleOffersReadOnly = offers;
@@ -2263,12 +2289,95 @@ export async function surfaceApprovalFrameWithId(frame, approvalId, respond, str
2263
2289
  if (typeof approvalId !== 'string' || approvalId === '')
2264
2290
  return approvalIdUnreadableRefusal();
2265
2291
  const carriers = openingFrameCarriersOf(frame);
2292
+ const route = askDoorRouteOf(lane, carriers, frame, approvalId, respond);
2293
+ if (route.kind === 'refused')
2294
+ return route.resolution;
2266
2295
  const liveRun = liveCardRunOf(frame, run);
2267
2296
  const sourceGenerationRaw = guardedRead(() => readOwnDataValue(run, 'sourceGeneration'));
2268
2297
  const sourceGeneration = typeof sourceGenerationRaw === 'number' && Number.isFinite(sourceGenerationRaw) ? sourceGenerationRaw : undefined;
2269
- const entry = { frame, carriers, approvalId, identity: liveCardIdentityOf(approvalId, carriers, liveRun), respond, streamArgs, signal, lane, receivedAtMs, sourceGeneration };
2298
+ const askDoor = route.kind === 'target' ? route.target : undefined;
2299
+ const respondSwitch = route.kind === 'respond' ? route.switchTo : undefined;
2300
+ const entry = { frame, carriers, approvalId, identity: liveCardIdentityOf(approvalId, carriers, liveRun), respond, streamArgs, signal, lane, receivedAtMs, sourceGeneration, askDoor, respondSwitch };
2270
2301
  return enterLiveCard(entry);
2271
2302
  }
2303
+ const RESPOND_ROUTE = Object.freeze({ kind: 'respond' });
2304
+ function askDoorRouteOf(lane, carriers, frame, approvalId, respond) {
2305
+ const reading = guardedRead(() => readOwnDataValue(lane, 'approvalDecisionDoor'));
2306
+ const door = approvalDecisionDoorOf(reading);
2307
+ const refused = (cause, missing) => {
2308
+ const resolution = missing !== undefined ? { kind: 'not_sent', lane: 'respond', cause, door: missing } : { kind: 'not_sent', lane: 'respond', cause };
2309
+ return Object.freeze({ kind: 'refused', resolution });
2310
+ };
2311
+ let target;
2312
+ if (door === 'ask_decision') {
2313
+ const blindRow = guardedRead(() => readOwnDataValue(lane, 'argsUnavailable')) === true;
2314
+ const t = askDoorTargetOf(reading, carriers, frame, approvalId, blindRow);
2315
+ if (t === 'card_unavailable' || t === 'card_unreadable')
2316
+ return refused(t);
2317
+ target = t;
2318
+ }
2319
+ if (door === 'ask_decision' ? target === undefined : typeof respond !== 'function') {
2320
+ hostLog('error', door === 'ask_decision'
2321
+ ? `liveToolApprovalWire: ${approvalId} must be answered on the ask decision door, but this host has not wired that door (approvalDecisionDoor.verbs.decideAsk) — not raised as a card, nothing sent; the request is still waiting on the engine`
2322
+ : `liveToolApprovalWire: ${approvalId} must be answered on the respond door (the server is below the ask decision door version, or its version is unreadable), but the respond port this host passed is not a function — not raised as a card, nothing sent; the request is still waiting on the engine`);
2323
+ return refused('no_verb', door);
2324
+ }
2325
+ if (target !== undefined)
2326
+ return Object.freeze({ kind: 'target', target });
2327
+ const addr = askDoorAddressOf(carriers, frame, approvalId);
2328
+ const decide = 'missing' in addr ? undefined : approvalDecisionVerbOf(reading);
2329
+ if ('missing' in addr || decide === undefined)
2330
+ return RESPOND_ROUTE;
2331
+ return Object.freeze({ kind: 'respond', switchTo: Object.freeze({ target: Object.freeze({ taskId: addr.taskId, askId: addr.askId, decide }), reading }) });
2332
+ }
2333
+ function askDoorAddressOf(carriers, frame, approvalId) {
2334
+ if (carriers.leg === 'frame')
2335
+ return { missing: 'old_family' };
2336
+ const askId = guardedRead(() => readOwnDataNonEmptyString(frame, 'askId'));
2337
+ const taskId = guardedRead(() => readOwnDataNonEmptyString(frame, 'taskId'));
2338
+ if (askId === undefined)
2339
+ return { missing: 'askId' };
2340
+ if (taskId === undefined)
2341
+ return { missing: 'taskId' };
2342
+ if (askId !== approvalId)
2343
+ return { missing: 'mismatch' };
2344
+ return { taskId, askId };
2345
+ }
2346
+ function askDoorTargetOf(reading, carriers, frame, approvalId, blindRow) {
2347
+ const addr = askDoorAddressOf(carriers, frame, approvalId);
2348
+ if ('missing' in addr) {
2349
+ if (addr.missing === 'old_family' && blindRow) {
2350
+ hostLog('debug', `liveToolApprovalWire: ${approvalId} is a pending row whose card this host could not read, on a server that only takes the ask decision door — not raised as a card, nothing sent`);
2351
+ return 'card_unreadable';
2352
+ }
2353
+ if (addr.missing === 'old_family') {
2354
+ hostLog('debug', `liveToolApprovalWire: ${approvalId} is an old-family approval frame on a server that only takes the ask decision door — not raised as a card, nothing sent`);
2355
+ return 'card_unavailable';
2356
+ }
2357
+ hostLog('debug', `liveToolApprovalWire: ${approvalId} has no usable ask identity on its frame (${addr.missing === 'mismatch' ? 'askId differs from the approval id' : addr.missing}) — the ask decision door cannot be addressed; not raised as a card, nothing sent`);
2358
+ return 'card_unavailable';
2359
+ }
2360
+ const decide = approvalDecisionVerbOf(reading);
2361
+ return decide === undefined ? undefined : Object.freeze({ taskId: addr.taskId, askId: addr.askId, decide });
2362
+ }
2363
+ export function askDoorBodyOf(decision, o, askId) {
2364
+ const verdict = decision === 'deny' ? 'deny' : 'approve';
2365
+ const persistRule = o.persistRuleBatchOfferIndex !== undefined
2366
+ ? { batchOfferIndex: o.persistRuleBatchOfferIndex }
2367
+ : o.persistRule !== undefined
2368
+ ? { rule: o.persistRule, ...(o.persistRuleEdited === true ? { edited: true } : {}) }
2369
+ : undefined;
2370
+ return {
2371
+ decision: verdict,
2372
+ idempotencyKey: hitlAskIdempotencyKey(askId, verdict),
2373
+ ...(decision === 'allow_session' ? { remember: 'session' } : {}),
2374
+ ...(verdict === 'approve' && o.updatedInput !== undefined ? { updatedInput: o.updatedInput } : {}),
2375
+ ...(o.note !== undefined ? { note: o.note } : {}),
2376
+ ...(verdict === 'approve' && persistRule !== undefined ? { persistRule } : {}),
2377
+ ...(o.decidedBy !== undefined ? { decidedBy: o.decidedBy } : {}),
2378
+ ...(o.settledBy !== undefined ? { settledBy: o.settledBy } : {}),
2379
+ };
2380
+ }
2272
2381
  function openingFrameCarriersOf(frame) {
2273
2382
  if (frame.type !== 'approval_request')
2274
2383
  return { body: frame, live: frame, leg: 'frame' };
@@ -2292,7 +2401,7 @@ function enterLiveCard(e) {
2292
2401
  return joinOtherLaneHolder(e, other);
2293
2402
  return holdLiveCard(e.identity, signal, async (hold) => {
2294
2403
  const slot = {};
2295
- const out = await presentToolApprovalFrameAndRespond(e.frame, e.carriers, approvalId, e.respond, e.streamArgs, signal, e.lane, hold, slot, e.receivedAtMs, e.identity.taskId, e.sourceGeneration);
2404
+ const out = await presentToolApprovalFrameAndRespond(e.frame, e.carriers, approvalId, e.respond, e.streamArgs, signal, e.lane, hold, slot, e.receivedAtMs, e.identity.taskId, e.sourceGeneration, e.askDoor, e.respondSwitch);
2296
2405
  noteCallOutcome(slot, out);
2297
2406
  return out;
2298
2407
  });
@@ -2376,11 +2485,12 @@ function readApprovalCardClaimSignal(v) {
2376
2485
  export function claimApprovalCard(identity, options) {
2377
2486
  return claimApprovalCardFor(readApprovalHolderIdentity(identity), readApprovalCardClaimSignal(options));
2378
2487
  }
2379
- async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, respond, streamArgs, signal, lane, hold, ledgerSlot, receivedAtMs, cardTaskId, sourceGeneration) {
2488
+ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, respond, streamArgs, signal, lane, hold, ledgerSlot, receivedAtMs, cardTaskId, sourceGeneration, entryAskDoor, respondSwitch) {
2489
+ let askDoor = entryAskDoor;
2380
2490
  const bodyToolCallId = carriers.body.toolCallId;
2381
2491
  const frameToolCallId = typeof bodyToolCallId === 'string' && bodyToolCallId !== '' ? bodyToolCallId : undefined;
2382
2492
  const decidedByReading = decidedByReadingSnapshotOf(lane);
2383
- const facts = cardFactsOf(carriers.body, carriers.leg, { streamArgs, ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}) }, carriers.live, approvalId, carriers.why);
2493
+ const facts = cardFactsOf(carriers.body, carriers.leg, { streamArgs, ...(lane?.argsUnavailable === true ? { argsUnavailable: true } : {}) }, carriers.live, approvalId, carriers.why, askDoor !== undefined);
2384
2494
  const argsNotRealInput = facts.argsUnavailable === true;
2385
2495
  const editAcceptance = editedApprovalAcceptanceOf(facts);
2386
2496
  const ruleOffers = facts.ruleOffers;
@@ -2416,16 +2526,36 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2416
2526
  const p = readToolApprovalRespondParking(body);
2417
2527
  return p?.kind === 'parking' ? p.view : undefined;
2418
2528
  };
2419
- const deliverOnThisCard = (d, body, carrySignal, deliverySignal, ladder, onLadderStep) => deliverApprovalDecision({
2529
+ const noParkingBody = () => undefined;
2530
+ const parkingInProgress = (parking) => ({ kind: 'unsettled', lane: 'respond', cause: 'parking_in_progress', parking });
2531
+ const deliverVia = (door, d, body, carrySignal, deliverySignal, ladder, onLadderStep) => deliverApprovalDecision({
2420
2532
  approvalId,
2421
2533
  body,
2422
- send: (o, sig) => respond(approvalId, d, carrySignal && sig !== undefined ? { ...o, signal: sig } : o),
2534
+ send: (o, sig) => door !== undefined
2535
+ ? door.decide(door.taskId, door.askId, askDoorBodyOf(d, o, door.askId), carrySignal && sig !== undefined ? { signal: sig } : undefined)
2536
+ : respond(approvalId, d, carrySignal && sig !== undefined ? { ...o, signal: sig } : o),
2423
2537
  signal: deliverySignal,
2424
2538
  tally: newSendTally(),
2425
2539
  ladder,
2426
2540
  ...(onLadderStep !== undefined ? { onLadderStep } : {}),
2427
- parkingOf: respondParkingOf,
2541
+ parkingOf: door !== undefined ? noParkingBody : respondParkingOf,
2542
+ ...(door !== undefined ? { parkingOfThrown: (reading) => askDoorParkingOfThrown(reading, approvalId) } : {}),
2428
2543
  });
2544
+ const deliverOnThisCard = (d, body, carrySignal, deliverySignal, ladder, onLadderStep) => {
2545
+ const first = deliverVia(askDoor, d, body, carrySignal, deliverySignal, ladder, onLadderStep);
2546
+ const sw = respondSwitch;
2547
+ if (askDoor !== undefined || sw === undefined)
2548
+ return first;
2549
+ return first.then((delivery) => {
2550
+ if (delivery.kind !== 'thrown' || !respondRouteGoneOf(delivery))
2551
+ return delivery;
2552
+ noteRespondDoorSwitch(sw.reading, 'liveToolApprovalWire', approvalId);
2553
+ if (isAborted(deliverySignal))
2554
+ return delivery;
2555
+ askDoor = sw.target;
2556
+ return deliverVia(sw.target, d, body, carrySignal, deliverySignal, ladder, onLadderStep);
2557
+ });
2558
+ };
2429
2559
  if (card.kind === 'aborted') {
2430
2560
  enterLiveRespondPhase(hold);
2431
2561
  const interruptOpts = lane?.approvalDecisionNoteCapable === true ? { note: INTERRUPT_DENY_REASON } : {};
@@ -2579,7 +2709,7 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2579
2709
  }
2580
2710
  const attributionDropped = ladder.dropped;
2581
2711
  const attributionRenamed = ladder.renamed;
2582
- const parkingBody = readToolApprovalRespondParking(raw);
2712
+ const parkingBody = askDoor === undefined ? readToolApprovalRespondParking(raw) : undefined;
2583
2713
  if (parkingBody !== undefined) {
2584
2714
  const parking = parkingBody.kind === 'parking' && parkingBody.view.approvalId === approvalId ? parkingBody.view : undefined;
2585
2715
  if (parking !== undefined) {
@@ -2588,14 +2718,23 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2588
2718
  hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered 202 parking_in_progress${hint !== undefined ? ` (retryAfterMs=${hint})` : ''} — ` +
2589
2719
  `the answer did NOT take effect${decidePath !== undefined ? ' (the engine pointed at a decide path for the parked card)' : ''}; not reported as decided (the one invited re-send, if any, has already been made); the user is told to answer again on the parked card`);
2590
2720
  surfaceApprovalParking(hint);
2591
- return { kind: 'unsettled', lane: 'respond', cause: 'parking_in_progress', parking };
2721
+ return parkingInProgress(parking);
2592
2722
  }
2593
2723
  hostLog('error', `liveToolApprovalWire: 202 parking body for ${approvalId} is ${parkingBody.kind === 'parking' ? `for another approval (${parkingBody.view.approvalId})` : 'unreadable (approvalId is not usable)'} — ` +
2594
2724
  'reporting the answer as NOT settled (never as decided, never as parked); not re-sent');
2595
2725
  return { kind: 'unsettled', lane: 'respond', cause: 'unreadable' };
2596
2726
  }
2597
- const parsed = settlementAckOf(readToolApprovalRespondAck(raw));
2598
- const replayVerdict = replayVerdictFrom(raw, parsed, approvalId);
2727
+ const askDoorBody = askDoor !== undefined ? readAskDoorDecisionBody(raw, askDoor.askId, decision) : undefined;
2728
+ if (askDoorBody?.kind === 'earlier') {
2729
+ hostLog('debug', `liveToolApprovalWire: decision(${decision}) for ${approvalId} answered with a body that names this ask but records the other decision (${askDoorBody.firstDecision}) — reporting the earlier decision, never the one this client sent`);
2730
+ return replayedDecisionOf(askDoorBody.firstDecision, decision);
2731
+ }
2732
+ if (askDoorBody?.kind === 'other_ask') {
2733
+ hostLog('error', `liveToolApprovalWire: decision(${decision}) for ${approvalId} answered with a body that names another ask or cannot be matched to this one — reporting the answer as NOT settled (never as the decision this client sent); not re-sent`);
2734
+ return { kind: 'unsettled', lane: 'respond', cause: 'unreadable' };
2735
+ }
2736
+ const parsed = askDoor !== undefined ? (askDoorBody?.kind === 'receipt' ? askDoorBody.ack : undefined) : settlementAckOf(readToolApprovalRespondAck(raw));
2737
+ const replayVerdict = askDoor === undefined ? replayVerdictFrom(raw, parsed, approvalId) : REPLAY_NONE;
2599
2738
  if (replayVerdict.kind === 'replayed' && replayVerdict.via === 'claim') {
2600
2739
  hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered with a replay (body not in the full replay shape) — the approval was already decided ` +
2601
2740
  `(${replayVerdict.firstDecision}); this answer was not delivered, reporting the earlier decision`);
@@ -2690,14 +2829,24 @@ async function presentToolApprovalFrameAndRespond(frame, carriers, approvalId, r
2690
2829
  }
2691
2830
  catch (e) {
2692
2831
  const fromDelivery = deliveryFailure !== undefined && deliveryFailure.error === e ? deliveryFailure : undefined;
2832
+ const askDoorParking = askDoor !== undefined && fromDelivery !== undefined ? askDoorParkingOfThrown(fromDelivery.reading, approvalId) : undefined;
2833
+ if (askDoorParking !== undefined) {
2834
+ const hint = ownReadingSlot(askDoorParking, 'retryAfterMs');
2835
+ hostLog('debug', `liveToolApprovalWire: decision(${decision}) for ${approvalId} answered 425 parking.ask_retry${hint !== undefined ? ` (retryAfterMs=${hint})` : ''} — the answer did NOT take effect; ` +
2836
+ 'not reported as decided (the one invited re-send, if any, has already been made)');
2837
+ surfaceApprovalParking(hint);
2838
+ return parkingInProgress(askDoorParking);
2839
+ }
2693
2840
  const r = resolutionOfDecisionFailure(e, {
2694
2841
  lane: 'respond',
2695
- route: 'live_respond',
2842
+ route: askDoor !== undefined ? 'ask_decide' : 'live_respond',
2696
2843
  attributionCarried: lastSendCarriedAttribution,
2697
2844
  ...(fromDelivery?.transportExhausted === true ? { transportExhausted: true } : {}),
2698
2845
  ...(fromDelivery?.afterRetry === true ? { afterRetry: true } : {}),
2699
2846
  ...(fromDelivery !== undefined ? { delivered: fromDelivery.reading } : {}),
2700
2847
  });
2848
+ if (r.kind === 'unsettled' && r.cause === 'route_missing' && askDoor === undefined && respondSwitch === undefined)
2849
+ hostLog('warn', ROUTE_MISSING_WIRING_HINT);
2701
2850
  const refusal = r.kind === 'unsettled' ? r.refusal : undefined;
2702
2851
  hostLog('debug', `liveToolApprovalWire: respond(${decision}) failed for ${approvalId} ` +
2703
2852
  `(status=${refusal?.status ?? 'none'} errorCode=${logSafeErrorCode(refusal?.errorCode)} ` +
@@ -1,4 +1,5 @@
1
1
  import type { UiLanguage } from './env/uiLanguage.js';
2
+ import { type HooksWireSettingsContract } from './pluginHooksWire.js';
2
3
  export type HookMatcherUnjudgedReason = 'event_out_of_scope' | 'not_a_string' | 'oversize' | 'pattern_unsupported' | 'over_budget';
3
4
  export type HookMatcherRefusedReason = 'alias_without_canonical' | 'invalid_regex';
4
5
  export type HookMatcherVerdict = {
@@ -24,5 +25,5 @@ export type HookMatcherVerdict = {
24
25
  readonly kind: 'unjudged';
25
26
  readonly reason: HookMatcherUnjudgedReason;
26
27
  };
27
- export declare function hookMatcherVerdict(event: string, matcher: unknown): HookMatcherVerdict;
28
+ export declare function hookMatcherVerdict(event: string, matcher: unknown, contract: HooksWireSettingsContract): HookMatcherVerdict;
28
29
  export declare function hookMatcherUnjudgedWhy(reason: HookMatcherUnjudgedReason | string, lang?: UiLanguage): string;
@@ -1,6 +1,7 @@
1
1
  import { msg, renderMessage } from './i18n/translate.js';
2
2
  import { HOOK_TOOL_ALIASES } from './hookToolAliases.js';
3
3
  import { SETTINGS_TOOL_NAME_MATCHER_EVENTS } from './generated/settingsSchemaTables.js';
4
+ import { isEngineFiredHookEvent } from './pluginHooksWire.js';
4
5
  import { SETTINGS_HOOK_MATCHER_FORM, SETTINGS_HOOK_MATCHER_SELECTS } from './generated/settingsSchemaFns.js';
5
6
  const TOOL_NAME_MATCHER_EVENTS = new Set(SETTINGS_TOOL_NAME_MATCHER_EVENTS);
6
7
  const MATCHER_MAX_CHARS = 8192;
@@ -180,8 +181,8 @@ function patternBound(src, n) {
180
181
  throw e;
181
182
  }
182
183
  }
183
- export function hookMatcherVerdict(event, matcher) {
184
- if (typeof event !== 'string' || !TOOL_NAME_MATCHER_EVENTS.has(event))
184
+ export function hookMatcherVerdict(event, matcher, contract) {
185
+ if (typeof event !== 'string' || !TOOL_NAME_MATCHER_EVENTS.has(event) || !isEngineFiredHookEvent(event, contract))
185
186
  return unjudged('event_out_of_scope');
186
187
  if (matcher !== undefined && typeof matcher !== 'string')
187
188
  return unjudged('not_a_string');