@sema-agent/client-core 0.95.0 → 0.96.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 (181) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +49 -25
  3. package/dist/adapt/arms.js +7 -1
  4. package/dist/adapt/toolCards.d.ts +1 -0
  5. package/dist/adapt/toolCards.js +16 -3
  6. package/dist/adapt/wireShapes.js +2 -16
  7. package/dist/adapter/activeRunSelfHeal.d.ts +4 -0
  8. package/dist/adapter/activeRunSelfHeal.js +30 -4
  9. package/dist/adapter/downstream/eventToSdkMessage.js +11 -3
  10. package/dist/adapter/downstream/terminalToSdkResult.js +8 -9
  11. package/dist/adapter/runStream.js +30 -47
  12. package/dist/agentSession/engineAgentRegistryRead.js +5 -4
  13. package/dist/agentSession/engineSessionBackgroundStop.js +22 -15
  14. package/dist/agentsWireCaps.js +13 -2
  15. package/dist/callerCwdCapability.d.ts +0 -4
  16. package/dist/callerCwdCapability.js +0 -6
  17. package/dist/centerWiringCapability.d.ts +0 -4
  18. package/dist/centerWiringCapability.js +0 -6
  19. package/dist/classifierVerdictWire.js +4 -16
  20. package/dist/clientSlice.js +1 -1
  21. package/dist/cloudConfigWireCaps.d.ts +3 -3
  22. package/dist/cloudConfigWireCaps.js +65 -45
  23. package/dist/coreValuePorts.d.ts +11 -7
  24. package/dist/coreValuePorts.js +5 -8
  25. package/dist/credentialProvenance.d.ts +28 -0
  26. package/dist/credentialProvenance.js +76 -0
  27. package/dist/decideFailureNote.js +3 -1
  28. package/dist/decideReceipt.js +7 -10
  29. package/dist/declared/supportFloorDeclared.js +3 -3
  30. package/dist/displayUntrusted.d.ts +7 -3
  31. package/dist/displayUntrusted.js +356 -60
  32. package/dist/engineCapsCache.d.ts +6 -0
  33. package/dist/engineCapsCache.js +226 -32
  34. package/dist/engineDirectory.d.ts +79 -0
  35. package/dist/engineDirectory.js +186 -0
  36. package/dist/engineErrorCodes.d.ts +14 -1
  37. package/dist/engineErrorCodes.js +37 -15
  38. package/dist/engineNoticeCodes.d.ts +17 -0
  39. package/dist/engineNoticeCodes.js +57 -3
  40. package/dist/engineWordRenames.d.ts +8 -0
  41. package/dist/engineWordRenames.js +13 -0
  42. package/dist/fileHistoryCaptureCapability.d.ts +14 -0
  43. package/dist/fileHistoryCaptureCapability.js +70 -0
  44. package/dist/fleet/bgSubagentRow.d.ts +15 -0
  45. package/dist/fleet/bgSubagentRow.js +67 -0
  46. package/dist/fleet/fleetLedger.d.ts +1 -0
  47. package/dist/fleet/fleetLedger.js +103 -89
  48. package/dist/fleet/fleetProjection.d.ts +1 -0
  49. package/dist/fleet/fleetProjection.js +10 -3
  50. package/dist/fleetTaskDesc.js +4 -17
  51. package/dist/gateOutcome.d.ts +12 -1
  52. package/dist/gateOutcome.js +58 -1
  53. package/dist/gateVocabulary.d.ts +7 -2
  54. package/dist/gateVocabulary.js +5 -1
  55. package/dist/generated/engineAskFaceTables.js +1 -1
  56. package/dist/generated/engineFactTables.d.ts +5 -3
  57. package/dist/generated/engineFactTables.js +17 -4
  58. package/dist/generated/engineNoticeTables.js +12 -3
  59. package/dist/generated/settingsSchemaFns.d.ts +3 -0
  60. package/dist/generated/settingsSchemaFns.js +53 -0
  61. package/dist/generated/toolNameTables.d.ts +1 -0
  62. package/dist/generated/toolNameTables.js +1 -0
  63. package/dist/hitl/approvalEndingCause.d.ts +2 -0
  64. package/dist/hitl/approvalEndingCause.js +31 -21
  65. package/dist/hitl/askParkRowRouting.js +1 -1
  66. package/dist/hitl/automaticAnswerVerdict.d.ts +1 -1
  67. package/dist/hitl/automaticAnswerVerdict.js +13 -8
  68. package/dist/hitl/decisionConflict.js +0 -2
  69. package/dist/hitl/decisionRefusal.d.ts +8 -0
  70. package/dist/hitl/decisionRefusal.js +48 -14
  71. package/dist/hitl/frameRouter.d.ts +0 -1
  72. package/dist/hitl/frameRouter.js +6 -14
  73. package/dist/hitl/hitlBridge.d.ts +6 -2
  74. package/dist/hitl/hitlBridge.js +28 -7
  75. package/dist/hitl/localAllowRule.d.ts +1 -0
  76. package/dist/hitl/localAllowRule.js +2 -2
  77. package/dist/hitl/noAllowCause.d.ts +2 -1
  78. package/dist/hitl/noAllowCause.js +3 -2
  79. package/dist/hitl/parkResolver.d.ts +1 -0
  80. package/dist/hitl/parkResolver.js +8 -54
  81. package/dist/hitl/parkRowBirthWait.d.ts +1 -0
  82. package/dist/hitl/parkRowBirthWait.js +2 -2
  83. package/dist/hitl/planReviewWire.d.ts +1 -0
  84. package/dist/hitl/planReviewWire.js +98 -20
  85. package/dist/hitl/replayedApproval.d.ts +1 -0
  86. package/dist/hitl/replayedApproval.js +14 -0
  87. package/dist/hitl/sessionPolicyDeliverable.js +8 -6
  88. package/dist/hitl/standingRows.d.ts +18 -0
  89. package/dist/hitl/standingRows.js +127 -0
  90. package/dist/hitl/toolApprovalWire.d.ts +15 -3
  91. package/dist/hitl/toolApprovalWire.js +238 -25
  92. package/dist/hookMatcherVerdict.d.ts +1 -9
  93. package/dist/hookMatcherVerdict.js +21 -61
  94. package/dist/hooksWireCaps.js +2 -2
  95. package/dist/hostCallbackGuard.d.ts +1 -0
  96. package/dist/hostCallbackGuard.js +6 -0
  97. package/dist/index.d.ts +21 -9
  98. package/dist/index.js +17 -8
  99. package/dist/mcpProbeCapability.d.ts +0 -4
  100. package/dist/mcpProbeCapability.js +0 -6
  101. package/dist/memoryComplianceCapability.d.ts +0 -4
  102. package/dist/memoryComplianceCapability.js +0 -6
  103. package/dist/memoryOriginCapability.d.ts +0 -4
  104. package/dist/memoryOriginCapability.js +0 -6
  105. package/dist/memorySpecWire.js +2 -2
  106. package/dist/modelIdentityResolvability.d.ts +2 -2
  107. package/dist/modelIdentityResolvability.js +12 -3
  108. package/dist/noticePresentation.d.ts +68 -0
  109. package/dist/noticePresentation.js +247 -0
  110. package/dist/outputStyleSeatCapability.d.ts +0 -1
  111. package/dist/outputStyleSeatCapability.js +0 -3
  112. package/dist/ownKey.d.ts +2 -0
  113. package/dist/ownKey.js +18 -0
  114. package/dist/peerFrames.js +13 -9
  115. package/dist/permissionModesCapability.d.ts +0 -4
  116. package/dist/permissionModesCapability.js +0 -6
  117. package/dist/permissionRuleIssue.js +0 -3
  118. package/dist/permissionsDeclaration.d.ts +2 -0
  119. package/dist/permissionsDeclaration.js +56 -41
  120. package/dist/printPermissionOutcome.d.ts +12 -0
  121. package/dist/printPermissionOutcome.js +50 -0
  122. package/dist/printToolResultFrame.d.ts +1 -0
  123. package/dist/printToolResultFrame.js +5 -9
  124. package/dist/promptBlockedCopy.d.ts +5 -0
  125. package/dist/promptBlockedCopy.js +35 -0
  126. package/dist/protocolSegment.d.ts +9 -6
  127. package/dist/protocolSegment.js +31 -21
  128. package/dist/providerRejection.d.ts +4 -0
  129. package/dist/providerRejection.js +75 -14
  130. package/dist/replayBody.d.ts +12 -0
  131. package/dist/replayBody.js +25 -0
  132. package/dist/resumeRefusalCopy.js +5 -21
  133. package/dist/retryStatus.d.ts +9 -1
  134. package/dist/retryStatus.js +9 -2
  135. package/dist/rewindArchiveCapability.d.ts +3 -13
  136. package/dist/rewindArchiveCapability.js +7 -58
  137. package/dist/rewindArchiveReading.d.ts +15 -0
  138. package/dist/rewindArchiveReading.js +56 -0
  139. package/dist/ruleCompileVerdict.d.ts +9 -1
  140. package/dist/ruleCompileVerdict.js +29 -4
  141. package/dist/runTerminal.d.ts +15 -1
  142. package/dist/runTerminal.js +56 -8
  143. package/dist/sandboxWire.js +13 -0
  144. package/dist/sealedKeyCapability.d.ts +0 -4
  145. package/dist/sealedKeyCapability.js +0 -6
  146. package/dist/seam.js +4 -2
  147. package/dist/seatContract.d.ts +73 -11
  148. package/dist/seatContract.js +195 -14
  149. package/dist/sessionReadRedactionCapability.d.ts +0 -4
  150. package/dist/sessionReadRedactionCapability.js +0 -6
  151. package/dist/sessionSyncFileHistory.d.ts +4 -0
  152. package/dist/sessionSyncFileHistory.js +66 -0
  153. package/dist/sessionSyncFileHistoryCapability.d.ts +0 -1
  154. package/dist/sessionSyncFileHistoryCapability.js +0 -3
  155. package/dist/skillToolListsCapability.d.ts +0 -1
  156. package/dist/skillToolListsCapability.js +0 -3
  157. package/dist/steerPriorityCapability.d.ts +0 -1
  158. package/dist/steerPriorityCapability.js +0 -3
  159. package/dist/storePostureCapability.d.ts +0 -4
  160. package/dist/storePostureCapability.js +0 -6
  161. package/dist/subagent/engineSubagentSteer.js +7 -5
  162. package/dist/subagentContentStore.js +4 -15
  163. package/dist/supportFloor.d.ts +1 -0
  164. package/dist/supportFloor.js +13 -9
  165. package/dist/systemReminderTag.d.ts +1 -0
  166. package/dist/systemReminderTag.js +8 -0
  167. package/dist/toolHistoryMismatch.d.ts +0 -3
  168. package/dist/toolHistoryMismatch.js +3 -7
  169. package/dist/toolResult.d.ts +1 -1
  170. package/dist/toolResult.js +2 -1
  171. package/dist/toolResultContent.d.ts +2 -0
  172. package/dist/toolResultContent.js +63 -0
  173. package/dist/usageAnalytics.js +40 -16
  174. package/dist/userInvokedSkillsCapability.d.ts +0 -1
  175. package/dist/userInvokedSkillsCapability.js +0 -3
  176. package/dist/wireErrorTriage.d.ts +2 -0
  177. package/dist/wireErrorTriage.js +34 -5
  178. package/dist/wireFailureShape.d.ts +19 -2
  179. package/dist/wireFailureShape.js +55 -50
  180. package/docs/INTEGRATION-CLIENTS.md +1262 -17
  181. package/package.json +5 -5
@@ -1,9 +1,10 @@
1
- export type NoAllowCause = 'no_allow_rule' | 'ask_rule' | 'budget' | 'other';
1
+ export type NoAllowCause = 'no_allow_rule' | 'ask_rule' | 'engine_ask_rule' | 'budget' | 'other';
2
2
  export interface RuleLadderNoAllowFacts {
3
3
  readonly allowed?: boolean;
4
4
  readonly denied?: boolean;
5
5
  readonly askRuleMatched?: boolean;
6
6
  readonly budgetExhausted?: boolean;
7
7
  readonly noRuleMatched?: boolean;
8
+ readonly noAskRuleMatched?: boolean;
8
9
  }
9
10
  export declare function noAllowCauseOf(card: object | null | undefined, ladder: RuleLadderNoAllowFacts | null | undefined): NoAllowCause | undefined;
@@ -18,8 +18,9 @@ export function noAllowCauseOf(card, ladder) {
18
18
  if (ladderSays(ladder, 'budgetExhausted'))
19
19
  return 'budget';
20
20
  const standing = automaticAnswerVerdict(card, 'standing_rule');
21
- if (standing.kind === 'must_ask' && (standing.askClass === 'ask_rule' || standing.origin === 'ask_rule'))
22
- return 'ask_rule';
21
+ if (standing.kind === 'must_ask' && (standing.askClass === 'ask_rule' || standing.origin === 'ask_rule')) {
22
+ return ladderSays(ladder, 'noAskRuleMatched') || ladderSays(ladder, 'noRuleMatched') ? 'engine_ask_rule' : 'ask_rule';
23
+ }
23
24
  if (ladderSays(ladder, 'noRuleMatched') && standing.kind === 'may_answer')
24
25
  return 'no_allow_rule';
25
26
  return 'other';
@@ -28,6 +28,7 @@ export type GateOutcome = {
28
28
  reason: string;
29
29
  code?: GateFailureCode | undefined;
30
30
  errorCode?: string | undefined;
31
+ blockedBy?: string | undefined;
31
32
  retriable?: true;
32
33
  retryExhausted?: true;
33
34
  currentPending?: GateCurrentPending | undefined;
@@ -1,8 +1,7 @@
1
1
  import { putOwnKey, readOwnDataValue } from '../ownKey.js';
2
- import { ApprovalStaleError, NotFoundError, ParkedResumeStartupError } from '@sema-agent/sdk';
3
- import { DecideTransportRetryExhaustedError, GATE_FAILURE_CODES, HitlBridge, NO_QUESTION_OVERLAY_REASON, findPendingForTask, hitlSafetyCodeOf, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
4
- import { ASK_GONE, CHECKPOINT_UNSUPPORTED_VERSION, DECIDE_WORKFLOW_PARK_FORKED, DECIDE_WORKFLOW_PARKS_UNREADABLE, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isDecideAlreadyResolvedCode, isDecideParkMovedCode, refusalSaysStillPending } from '../engineErrorCodes.js';
5
- import { isHitlSafetyErrorLike } from './parkOwnership.js';
2
+ import { DecideTransportRetryExhaustedError, GATE_FAILURE_CODES, HitlBridge, NO_QUESTION_OVERLAY_REASON, findPendingForTask, hitlSafetyCodeOf, isNoDecisionSurfaceReason, readDecideCurrentPending, readWireCodes, } from './hitlBridge.js';
3
+ import { DECIDE_WORKFLOW_PARK_FORKED, DECIDE_WORKFLOW_PARKS_UNREADABLE, PARKED_RESUME_STARTUP_FAILED, RESUME_POLICY_FOLD_CODE, isRefusedParkCode, refusalSaysStillPending } from '../engineErrorCodes.js';
4
+ import { readDecisionRefusalAsk } from './decisionRefusal.js';
6
5
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
7
6
  import { hostLog } from '../host.js';
8
7
  import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
@@ -35,62 +34,17 @@ function alreadyResolvedReasonToken(reason) {
35
34
  }
36
35
  export function isAlreadyResolvedFailure(failure) {
37
36
  try {
38
- return alreadyResolvedFailureVerdict(failure);
37
+ const r = readDecisionRefusalAsk(failure);
38
+ return r.reading === 'settled' || (r.reading === 'not_settled' && (r.gateMoved || r.wordingHint));
39
39
  }
40
40
  catch {
41
41
  return false;
42
42
  }
43
43
  }
44
- const ALREADY_RESOLVED_TYPED_ERROR_NAMES = Object.freeze([ApprovalStaleError.name, NotFoundError.name]);
45
- const PARKED_RESUME_STARTUP_TYPED_ERROR_NAME = ParkedResumeStartupError.name;
46
- function decideFailureFactsOf(failure) {
47
- if (typeof failure !== 'object' || failure === null)
48
- return undefined;
49
- const o = failure;
50
- const ec = o.errorCode;
51
- const errorCode = typeof ec === 'string' && ec.length > 0 ? ec : undefined;
52
- const retriable = refusalSaysStillPending(failure);
53
- if (Object.hasOwn(failure, 'kind') && o.kind === 'failed') {
54
- const safety = isGateFailureCode(o.safetyCode) ? o.safetyCode : undefined;
55
- const code = isGateFailureCode(o.code) ? o.code : safety;
56
- return { code, safetySignal: safety !== undefined, errorCode, retriable, typedErrorName: undefined, reason: o.reason };
57
- }
58
- const name = o.name;
59
- return {
60
- code: isGateFailureCode(o.code) ? o.code : undefined,
61
- safetySignal: isHitlSafetyErrorLike(failure),
62
- errorCode,
63
- retriable,
64
- typedErrorName: typeof name === 'string' ? name : undefined,
65
- reason: String(failure),
66
- };
67
- }
68
- function alreadyResolvedFailureVerdict(failure) {
69
- const f = decideFailureFactsOf(failure);
70
- if (f === undefined)
71
- return false;
72
- if (f.code !== undefined)
73
- return f.code === 'no_pending';
74
- if (f.safetySignal)
75
- return false;
76
- if (isDecideAlreadyResolvedCode(f.errorCode))
77
- return true;
78
- if (f.typedErrorName !== undefined && ALREADY_RESOLVED_TYPED_ERROR_NAMES.includes(f.typedErrorName))
79
- return true;
80
- if (f.typedErrorName === PARKED_RESUME_STARTUP_TYPED_ERROR_NAME && !isDecideParkMovedCode(f.errorCode))
81
- return false;
82
- if (isCodeClassifiedGateFailure(f.errorCode, f.retriable))
83
- return false;
84
- if (isDecideParkMovedCode(f.errorCode))
85
- return true;
86
- if (f.errorCode === ASK_GONE)
87
- return true;
88
- return isAlreadyResolvedGateReason(f.reason);
89
- }
90
44
  export function isGateStandingErrorCode(code, retriable) {
91
45
  return (code === RESUME_POLICY_FOLD_CODE ||
92
46
  code === 'corrupt' ||
93
- (code === CHECKPOINT_UNSUPPORTED_VERSION && retriable === true) ||
47
+ (isRefusedParkCode(code) && retriable === true) ||
94
48
  code === DECIDE_WORKFLOW_PARKS_UNREADABLE ||
95
49
  code === DECIDE_WORKFLOW_PARK_FORKED);
96
50
  }
@@ -193,7 +147,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
193
147
  catch (e) {
194
148
  const code = hitlSafetyCodeOf(e);
195
149
  const currentPending = readDecideCurrentPending(e);
196
- const wireCode = readWireErrorCode(e);
150
+ const wireCodes = readWireCodes(e);
197
151
  const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
198
152
  const failureTexts = wireFailureTextsOf(e);
199
153
  hostLog('debug', `liveHitlAskWire: decide failed: ${failureTexts.log}`);
@@ -205,7 +159,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
205
159
  ...(code !== undefined ? { code } : {}),
206
160
  ...(thrownIsInstanceOf(e, DecideTransportRetryExhaustedError) ? { retryExhausted: true } : {}),
207
161
  ...(currentPending !== undefined ? { currentPending } : {}),
208
- ...(wireCode !== undefined ? { errorCode: wireCode } : {}),
162
+ ...wireCodes,
209
163
  ...(wireRetriable ? { retriable: true } : {}),
210
164
  };
211
165
  }
@@ -35,5 +35,6 @@ export interface ParkRowBirthWaitDeps<Row> {
35
35
  now?: () => number;
36
36
  sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
37
37
  signal?: AbortSignal;
38
+ onFailure?: (log: string) => void;
38
39
  }
39
40
  export declare function waitForParkRowBirth<Row>(deps: ParkRowBirthWaitDeps<Row>): Promise<ParkRowWaitVerdict<Row>>;
@@ -1,5 +1,5 @@
1
1
  import { abortableSleep } from '../abortableSleep.js';
2
- import { wireFailureTextsOf } from '../wireFailureShape.js';
2
+ import { reasonOfThrownWithSink } from '../hostCallbackGuard.js';
3
3
  function parkRowPollDelay(ms, signal) {
4
4
  return abortableSleep(ms, signal ?? new AbortController().signal);
5
5
  }
@@ -17,7 +17,7 @@ export async function waitForParkRowBirth(deps) {
17
17
  return await deps.probe(attempt, signal);
18
18
  }
19
19
  catch (e) {
20
- return { kind: 'unborn', reason: `probe threw: ${wireFailureTextsOf(e).reason}` };
20
+ return { kind: 'unborn', reason: `probe threw: ${reasonOfThrownWithSink(e, (log) => deps.onFailure?.(log))}` };
21
21
  }
22
22
  };
23
23
  const probeWithinRemaining = async (attempt, remainingMs) => {
@@ -62,6 +62,7 @@ export interface PlanReviewDeliveryOptions {
62
62
  }
63
63
  export interface DecidePlanReviewOptions extends PlanReviewDeliveryOptions {
64
64
  reason?: string;
65
+ parkedResult?: unknown;
65
66
  }
66
67
  export interface PlanReviewDecisionHandle {
67
68
  settle(): void;
@@ -13,7 +13,7 @@ import { denyReasonForWire, isPlanReviewModeAfter } from './hitlBridge.js';
13
13
  import { planReviewQuestionId, REOPEN_ID_TAIL, taskIdFromPlanReviewQuestionId } from './gateIdentity.js';
14
14
  import { notePlanReviewAnswered, notePlanReviewAnsweredFor, planReviewArmedKeyFor, registerArmedGateFor, waitForGateArmedFor, wasGateArmedFor, } from './armedGateRegistry.js';
15
15
  import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
16
- import { PLAN_REVIEW_GATE_KIND } from '../adapter/activeRunSelfHeal.js';
16
+ import { PLAN_REVIEW_GATE_KIND, durableDecideLegOf } from '../adapter/activeRunSelfHeal.js';
17
17
  import { readRunTerminal, runTerminalGateKind } from '../runTerminal.js';
18
18
  const PLAN_REVIEW_RESUME_TIMEOUT_MS = 6 * 60 * 60_000;
19
19
  export const PLAN_REVIEW_APPROVE_LABEL = 'Yes, approve and run the plan';
@@ -130,6 +130,18 @@ function pausedCheckpointIdOf(carrier) {
130
130
  return undefined;
131
131
  }
132
132
  }
133
+ function pausedGateKindOf(carrier) {
134
+ try {
135
+ const read = readRunTerminal(carrier);
136
+ if (read === null || read.kind !== 'paused')
137
+ return undefined;
138
+ const kind = runTerminalGateKind(read);
139
+ return typeof kind === 'string' && kind.length > 0 ? kind : undefined;
140
+ }
141
+ catch {
142
+ return undefined;
143
+ }
144
+ }
133
145
  const armedPlanReviewTaskIds = new Set();
134
146
  const offerModeAfterByTask = new Map();
135
147
  export function _resetArmedPlanReviewsForTest() {
@@ -470,7 +482,53 @@ export function __resetPlanReviewDecisionLatchForTests() {
470
482
  planReviewDecisionOps.length = 0;
471
483
  }
472
484
  export async function decidePlanReview(taskId, decision, permissionModeAfter, opts) {
473
- return decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, undefined);
485
+ return decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, parkedGateIdentityOf(opts));
486
+ }
487
+ function parkedGateIdentityOf(opts) {
488
+ try {
489
+ return opts !== null && typeof opts === 'object' ? pausedCheckpointIdOf(opts.parkedResult) : undefined;
490
+ }
491
+ catch {
492
+ return undefined;
493
+ }
494
+ }
495
+ function planReviewSettledVerdict(r) {
496
+ const status = r.postStatus ?? r.bodyStatus;
497
+ const gateMoved = r.gateBefore !== undefined && r.gateAfter !== undefined && r.gateAfter !== r.gateBefore;
498
+ if (r.reparked)
499
+ return status !== 'completed' && !gateMoved ? { effect: 'still_parked', proven: true } : { effect: 'unconfirmed', proven: false };
500
+ if (status === 'completed')
501
+ return { effect: 'took_effect', proven: false };
502
+ const newGateHasCard = () => {
503
+ const leg = durableDecideLegOf({ gateKind: r.gateKindAfter ?? null });
504
+ return leg === 'approval' || leg === 'plan_review';
505
+ };
506
+ if (status === 'suspended' && r.postStatus === 'suspended' && newGateHasCard())
507
+ return { effect: 'advanced', proven: false };
508
+ if (status === 'needs_review' && r.postStatus === 'needs_review' && r.gateBefore !== undefined && r.gateAfter !== undefined) {
509
+ if (r.gateAfter === r.gateBefore)
510
+ return { effect: 'still_parked', proven: true };
511
+ if (newGateHasCard())
512
+ return { effect: 'advanced', proven: false };
513
+ }
514
+ return { effect: 'unconfirmed', proven: false };
515
+ }
516
+ function bodyWordOf(body, key) {
517
+ try {
518
+ const v = body !== null && typeof body === 'object' ? body[key] : undefined;
519
+ return typeof v === 'string' && v.length > 0 ? v : undefined;
520
+ }
521
+ catch {
522
+ return undefined;
523
+ }
524
+ }
525
+ function bodySaysReparked(body) {
526
+ try {
527
+ return body !== null && typeof body === 'object' && body.retriable === true;
528
+ }
529
+ catch {
530
+ return false;
531
+ }
474
532
  }
475
533
  async function decidePlanReviewFor(taskId, decision, permissionModeAfter, opts, gateBefore) {
476
534
  if (typeof taskId === 'string' && planReviewDecisionOps.some((op) => op.taskId === taskId && op.phase === 'posting')) {
@@ -586,6 +644,7 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
586
644
  hostLog('debug', `planReviewWire: ${decision} → ok (dispatch #${dispatchNo}) ${JSON.stringify(body).slice(0, 200)}`);
587
645
  let postStatus;
588
646
  let gateAfter;
647
+ let gateKindAfter;
589
648
  try {
590
649
  const probe = clients.repull();
591
650
  if (probe) {
@@ -601,35 +660,54 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
601
660
  else {
602
661
  post = (await probe.runs.get(taskId));
603
662
  }
604
- postStatus = typeof post?.status === 'string' ? post.status : undefined;
663
+ postStatus = bodyWordOf(post, 'status');
605
664
  gateAfter = pausedCheckpointIdOf(post?.result);
665
+ gateKindAfter = pausedGateKindOf(post?.result);
606
666
  }
607
667
  }
608
668
  catch (e) {
609
669
  hostLog('debug', `planReviewWire: post-decide status re-pull failed (fail-soft): ${String(e)}`);
610
670
  }
611
- const effective = postStatus ?? (typeof body?.status === 'string' ? body.status : undefined);
671
+ const bodyStatus = bodyWordOf(body, 'status');
672
+ const effective = postStatus ?? bodyStatus;
612
673
  statusSeen = effective;
613
- const gateMoved = postStatus === 'needs_review' && gateBefore !== undefined && gateAfter !== undefined && gateAfter !== gateBefore;
614
- if (effective === 'needs_review' && gateMoved) {
615
- effect = 'advanced';
616
- outcome = planReviewAdvancedOutcome(decision, 'needs_review');
617
- }
618
- else if (effective === 'needs_review') {
619
- effect = 'still_parked';
620
- stillParkedProven = postStatus === 'needs_review' && gateBefore !== undefined && gateAfter !== undefined && gateAfter === gateBefore;
674
+ const verdict = planReviewSettledVerdict({ postStatus, bodyStatus, reparked: bodySaysReparked(body), gateBefore, gateAfter, gateKindAfter });
675
+ effect = verdict.effect;
676
+ stillParkedProven = verdict.proven;
677
+ const code = bodyWordOf(body, 'errorCode');
678
+ if (effect === 'still_parked' && postStatus === 'needs_review' && !bodySaysReparked(body)) {
621
679
  outcome =
622
680
  `The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
623
681
  `(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
624
682
  `Tell the user plainly that the ${decision} did not go through; the reliable exits today are approving the plan or cancelling the task.`;
625
683
  }
626
- else if (effective === 'suspended') {
627
- effect = 'advanced';
628
- outcome = planReviewAdvancedOutcome(decision, 'suspended');
684
+ else if (effect === 'still_parked') {
685
+ outcome =
686
+ `The plan_review ${decision} was NOT applied: the engine could not continue the task and put the plan back to wait for a decision` +
687
+ `${code !== undefined ? ` (engine code: ${code})` : ''}. Tell the user plainly that the ${decision} did not go through.`;
688
+ }
689
+ else if (effect === 'advanced') {
690
+ outcome = planReviewAdvancedOutcome(decision, effective === 'needs_review' ? 'needs_review' : 'suspended');
691
+ }
692
+ else if (effect === 'took_effect') {
693
+ outcome =
694
+ `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: ${effective} ` +
695
+ `(${postStatus !== undefined ? 'post-decide re-checked: task left the review gate' : "from the decision's own reply; the post-decide re-check was unavailable"}).`;
696
+ }
697
+ else if (effective === 'needs_review' && (gateBefore === undefined || gateAfter === undefined || postStatus !== 'needs_review')) {
698
+ outcome =
699
+ `The plan_review ${decision} returned HTTP 200 and the task is waiting at a plan review gate again (post-decide status: needs_review), ` +
700
+ `but it could not be told whether this is the same review (the ${decision} did not take effect) or a new plan written after it (it did).`;
701
+ }
702
+ else if (effective === 'suspended' || effective === 'needs_review') {
703
+ outcome =
704
+ `The plan_review ${decision} was accepted by the engine (HTTP 200) and the task is paused again (post-decide status: ${effective}), ` +
705
+ `${gateKindAfter !== undefined ? `at a '${gateKindAfter}' pause that is not an approval card` : 'but the pause it stopped at was not reported'} — what the ${decision} led to could not be confirmed.`;
629
706
  }
630
707
  else if (effective !== undefined) {
631
- effect = 'took_effect';
632
- outcome = `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: ${effective} (post-decide re-checked: task left the review gate).`;
708
+ outcome =
709
+ `The plan_review ${decision} was accepted by the engine (HTTP 200), but the task then reported status ${effective}` +
710
+ `${code !== undefined ? ` (engine code: ${code})` : ''} — not a completion; what the ${decision} led to could not be confirmed.`;
633
711
  }
634
712
  else {
635
713
  outcome = `The plan_review ${decision} was accepted by the engine (HTTP 200) but its effect is unconfirmed — final status: unknown (post-decide verification unavailable). Do not treat this as ${decision === 'approve' ? 'an approval that resumed the task' : 'a discarded plan'}.`;
@@ -678,10 +756,10 @@ async function decidePlanReviewInner(taskId, decision, permissionModeAfter, disp
678
756
  outcome = `This ${decision} was not applied. ${corruptStoredRowContent(where)}`;
679
757
  }
680
758
  else if (typeof status === 'number') {
681
- const msg = e instanceof Error ? e.message : String(e);
682
- hostLog('debug', `planReviewWire: ${decision} → ${status} ${msg.slice(0, 200)}`);
759
+ const failureTexts = wireFailureTextsOf(e);
760
+ hostLog('debug', `planReviewWire: ${decision} → ${status} ${failureTexts.log.slice(0, 200)}`);
683
761
  effect = (status >= 400 && status < 500) || (typeof refusedCode === 'string' && isUnavailableFaceCode(refusedCode)) ? 'not_applied' : 'unconfirmed';
684
- outcome = `${`The plan_review decision failed: HTTP ${status} ${msg}`.trim()}${decideFailureNote(e)}`;
762
+ outcome = `${`The plan_review decision failed: HTTP ${status} ${failureTexts.reason}`.trim()}${decideFailureNote(e)}`;
685
763
  }
686
764
  else if (noAnswer === 'time-limit' || noAnswer === 'deadline') {
687
765
  hostLog('debug', `planReviewWire: ${decision} sent but no answer within the request time limit (task ${taskId})`);
@@ -24,6 +24,7 @@ export interface ReplayPendingBasis {
24
24
  }
25
25
  export declare function settledApprovalDisposition(frame: object, settled: ReplayPendingBasis['settled']): ReplayedApprovalDisposition | undefined;
26
26
  export declare function replayedApprovalDisposition(frame: object, basis: ReplayPendingBasis): ReplayedApprovalDisposition;
27
+ export declare function replayedApprovalClosesCardFrame(disposition: ReplayedApprovalDisposition | null | undefined): boolean;
27
28
  export declare function replayedApprovalSentence(disposition: ReplayedApprovalDisposition | undefined, opts?: {
28
29
  readonly toolName?: string;
29
30
  }): string | undefined;
@@ -2,6 +2,7 @@ import { APPROVAL_SETTLED_HOWS, hasLedgerPositionKey } from './gateLedger.js';
2
2
  import { readLivePendingRows } from './livePendingAsk.js';
3
3
  import { projectCrashConverged } from './crashConverged.js';
4
4
  import { quoteUntrustedForSentence } from '../displayUntrusted.js';
5
+ import { readOwnDataValue } from '../ownKey.js';
5
6
  const live = (basis) => Object.freeze({ kind: 'live', basis });
6
7
  const unknown = (why) => Object.freeze({ kind: 'unknown', why });
7
8
  const decided = (row) => Object.freeze(row === undefined
@@ -162,6 +163,19 @@ const SETTLED_PHRASE = Object.freeze({
162
163
  });
163
164
  const settledRepeat = (what, how) => `${sentenceInitial(what)} was ${SETTLED_PHRASE[how]} earlier, so this repeated copy of it was not raised as a new question and no decision was sent for it.`;
164
165
  const decidedPlain = (what) => `${sentenceInitial(what)} is no longer listed by the engine as waiting for a decision, so no decision was sent for it.`;
166
+ export function replayedApprovalClosesCardFrame(disposition) {
167
+ try {
168
+ if (typeof disposition !== 'object' || disposition === null)
169
+ return false;
170
+ if (readOwnDataValue(disposition, 'kind') !== 'decided')
171
+ return false;
172
+ const cause = readOwnDataValue(disposition, 'cause');
173
+ return typeof cause === 'string' && Object.hasOwn(STOPPED_BY_CAUSE, cause);
174
+ }
175
+ catch {
176
+ return false;
177
+ }
178
+ }
165
179
  const unconfirmed = (what) => `Whether ${what} is still waiting for a decision could not be confirmed, so no decision was sent for it.`;
166
180
  function guardedGet(o, key) {
167
181
  try {
@@ -1,7 +1,7 @@
1
1
  import { CONFIG_LEGACY_TOOL_NAME, RULES_LEGACY_TOOL_NAME } from '../engineErrorCodes.js';
2
2
  import { rosterMountedNames } from '../rosterMountedNames.js';
3
3
  import { ruleCompileVerdict } from '../ruleCompileVerdict.js';
4
- import { protocolSegmentOf } from '../protocolSegment.js';
4
+ import { ruleToolNameVerdictOf } from '../protocolSegment.js';
5
5
  import { PREFIXES_IN_PROSE, REFUSED_NAME_CLASSES, REFUSED_NAME_FIX, RULES_LEGACY_TOOL_NAME_WRITE_NOTE } from './refusedToolNameProse.js';
6
6
  import { CORE_PROTOCOL_PREFIXES, CORE_RETIRED_TOOL_CURRENT_NAMES } from '../generated/toolNameTables.js';
7
7
  import { isEngineRefusedToolName } from '../engineRefusedToolName.js';
@@ -96,18 +96,20 @@ export function sessionPolicyDeliverable(behavior, rules, opts) {
96
96
  }
97
97
  let why = withheldWhyOf(r);
98
98
  if (why === undefined) {
99
- const grammar = ruleCompileVerdict(r, 'deny');
99
+ const grammar = ruleCompileVerdict(r, 'deny').kind;
100
100
  if (grammar === 'unknown')
101
101
  return unreadable();
102
102
  if (grammar === 'refused')
103
103
  why = 'not_a_tool_name';
104
104
  }
105
105
  if (why === undefined && COVERING_NAMESPACE_PREFIXES.some((p) => r.startsWith(p))) {
106
- const segment = protocolSegmentOf(r);
107
- if (segment.kind === 'empty' || segment.kind === 'unminted')
108
- why = 'not_a_tool_name';
109
- else if (segment.kind === 'unknown')
106
+ const verdict = ruleToolNameVerdictOf(r);
107
+ if (verdict.kind === 'unknown')
110
108
  return unreadable();
109
+ if (verdict.kind === 'unmintable_segment')
110
+ why = 'not_a_tool_name';
111
+ else if (verdict.kind === 'retired' || verdict.kind === 'unprefixed_namespace')
112
+ why = 'legacy_tool_name';
111
113
  }
112
114
  if (why === undefined && mounted !== undefined && !mounted.has(r))
113
115
  why = 'not_in_roster';
@@ -0,0 +1,18 @@
1
+ export type StandingRowWhy = 'given' | 'card_unreadable' | 'remember_refused' | 'remember_unreadable' | 'route_cannot_carry' | 'classes_unreadable' | 'not_deployment_list_question' | 'evidence_unreadable' | 'offers_unreadable' | 'engine_lane_closed' | 'args_unavailable' | 'grant_binding' | 'tool_name_unreadable' | 'rule_cannot_clear' | 'host_fact_missing' | 'face_not_generic' | 'rule_not_sent';
2
+ export type StandingPersistentRow = 'engine_offer' | 'local_whole_tool' | 'none';
3
+ export interface StandingRowsWhy {
4
+ readonly session: StandingRowWhy;
5
+ readonly persistent: StandingRowWhy;
6
+ readonly deploymentList: StandingRowWhy;
7
+ }
8
+ export interface StandingRows {
9
+ readonly sessionRow: boolean;
10
+ readonly persistentRow: StandingPersistentRow;
11
+ readonly deploymentListSessionRow: boolean;
12
+ readonly why: StandingRowsWhy;
13
+ }
14
+ export interface StandingRowsHostFacts {
15
+ readonly cardFace?: string;
16
+ readonly wholeToolRuleSent?: boolean;
17
+ }
18
+ export declare function standingRowsOf(card: object | null | undefined, host?: StandingRowsHostFacts | null): StandingRows;
@@ -0,0 +1,127 @@
1
+ import { readAskClassPairState } from './askClassPair.js';
2
+ import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
3
+ import { readRuleOffers } from './toolApprovalWire.js';
4
+ const DEPLOYMENT_LIST_ASK_CLASSES = Object.freeze(['ask_rule']);
5
+ const UNREADABLE = Symbol('unreadable');
6
+ function ownData(v, key) {
7
+ try {
8
+ const d = Object.getOwnPropertyDescriptor(v, key);
9
+ return d !== undefined && 'value' in d ? d.value : undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
15
+ function hasKey(v, key) {
16
+ try {
17
+ return { inCard: key in v };
18
+ }
19
+ catch {
20
+ return UNREADABLE;
21
+ }
22
+ }
23
+ function present(v, key) {
24
+ try {
25
+ const inCard = key in v;
26
+ return { inCard, value: inCard ? v[key] : undefined };
27
+ }
28
+ catch {
29
+ return UNREADABLE;
30
+ }
31
+ }
32
+ function freezeRows(sessionRow, persistentRow, deploymentListSessionRow, why) {
33
+ return Object.freeze({ sessionRow, persistentRow, deploymentListSessionRow, why: Object.freeze({ ...why }) });
34
+ }
35
+ function allClosed(why) {
36
+ return freezeRows(false, 'none', false, { session: why, persistent: why, deploymentList: why });
37
+ }
38
+ function persistentRowOf(card, host, evidenceUnreadable) {
39
+ if (evidenceUnreadable)
40
+ return { row: 'none', why: 'evidence_unreadable' };
41
+ for (const key of ['ruleOffers', 'ruleOffersReadOnly']) {
42
+ const p = hasKey(card, key);
43
+ if (p === UNREADABLE)
44
+ return UNREADABLE;
45
+ if (!p.inCard)
46
+ continue;
47
+ const offers = readRuleOffers(ownData(card, key));
48
+ if (offers === undefined || offers.length === 0)
49
+ return { row: 'none', why: 'offers_unreadable' };
50
+ return { row: 'engine_offer', why: 'given' };
51
+ }
52
+ const absence = present(card, 'ruleOffersAbsence');
53
+ if (absence === UNREADABLE)
54
+ return UNREADABLE;
55
+ if (absence.inCard)
56
+ return { row: 'none', why: 'engine_lane_closed' };
57
+ const blind = present(card, 'argsUnavailable');
58
+ if (blind === UNREADABLE)
59
+ return UNREADABLE;
60
+ if (blind.inCard)
61
+ return { row: 'none', why: 'args_unavailable' };
62
+ if (ownData(card, 'grantBinding') !== 'none')
63
+ return { row: 'none', why: 'grant_binding' };
64
+ const tool = ownData(card, 'toolName');
65
+ if (typeof tool !== 'string' || tool.length === 0)
66
+ return { row: 'none', why: 'tool_name_unreadable' };
67
+ if (automaticAnswerVerdict(card, 'standing_rule').kind !== 'may_answer')
68
+ return { row: 'none', why: 'rule_cannot_clear' };
69
+ if (typeof host !== 'object' || host === null)
70
+ return { row: 'none', why: 'host_fact_missing' };
71
+ const face = ownData(host, 'cardFace');
72
+ if (typeof face !== 'string')
73
+ return { row: 'none', why: 'host_fact_missing' };
74
+ if (face !== 'generic')
75
+ return { row: 'none', why: 'face_not_generic' };
76
+ const sent = ownData(host, 'wholeToolRuleSent');
77
+ if (typeof sent !== 'boolean')
78
+ return { row: 'none', why: 'host_fact_missing' };
79
+ if (!sent)
80
+ return { row: 'none', why: 'rule_not_sent' };
81
+ return { row: 'local_whole_tool', why: 'given' };
82
+ }
83
+ export function standingRowsOf(card, host) {
84
+ if (typeof card !== 'object' || card === null)
85
+ return allClosed('card_unreadable');
86
+ try {
87
+ const refused = present(card, 'rememberRefused');
88
+ if (refused === UNREADABLE)
89
+ return allClosed('card_unreadable');
90
+ if (refused.inCard)
91
+ return allClosed(refused.value === true ? 'remember_refused' : 'remember_unreadable');
92
+ const evidence = present(card, 'unreadableEvidence');
93
+ if (evidence === UNREADABLE)
94
+ return allClosed('card_unreadable');
95
+ let evidenceUnreadable = false;
96
+ if (evidence.inCard) {
97
+ const list = ownData(card, 'unreadableEvidence');
98
+ if (!Array.isArray(list) || !list.every((k) => typeof k === 'string'))
99
+ return allClosed('card_unreadable');
100
+ if (list.includes('rememberRefused'))
101
+ return allClosed('remember_unreadable');
102
+ evidenceUnreadable = list.length > 0;
103
+ }
104
+ const sessionRow = ownData(card, 'allowSessionCarried') === true;
105
+ const sessionWhy = sessionRow ? 'given' : 'route_cannot_carry';
106
+ let deploymentList;
107
+ if (!sessionRow) {
108
+ deploymentList = 'route_cannot_carry';
109
+ }
110
+ else {
111
+ const pair = readAskClassPairState(card);
112
+ deploymentList =
113
+ pair.state !== 'pair'
114
+ ? 'classes_unreadable'
115
+ : pair.classes.every((c) => DEPLOYMENT_LIST_ASK_CLASSES.includes(c))
116
+ ? 'given'
117
+ : 'not_deployment_list_question';
118
+ }
119
+ const persistent = persistentRowOf(card, host, evidenceUnreadable);
120
+ if (persistent === UNREADABLE)
121
+ return allClosed('card_unreadable');
122
+ return freezeRows(sessionRow, persistent.row, deploymentList === 'given', { session: sessionWhy, persistent: persistent.why, deploymentList });
123
+ }
124
+ catch {
125
+ return allClosed('card_unreadable');
126
+ }
127
+ }
@@ -40,8 +40,9 @@ export type FsApprovalOutcome = {
40
40
  stage: HitlFailureStage;
41
41
  gatedCallId?: string | undefined;
42
42
  reason: string;
43
- code?: 'no_pending' | undefined;
43
+ code?: 'no_pending' | 'wrong_gate' | undefined;
44
44
  errorCode?: string | undefined;
45
+ blockedBy?: string | undefined;
45
46
  retriable?: true;
46
47
  safetyCode?: HitlSafetyCode | undefined;
47
48
  retryExhausted?: true;
@@ -68,7 +69,6 @@ export type DenialLimitKind = SdkDenialLimitKind;
68
69
  export type DenialLimitFallback = SdkDenialLimitFallback;
69
70
  export { RULE_OFFERS_ABSENCE_REASONS };
70
71
  export declare const ASK_ORIGIN_WORDS: readonly string[];
71
- export declare const APPROVAL_MANDATE_WORDS: readonly ["operator_always", "tool_marks", "probe_mandate", "probe_unanswered", "write_protection", "write_protection_unresolved"];
72
72
  export type WireRuleOffer = {
73
73
  kind: 'single';
74
74
  rule: string;
@@ -141,6 +141,7 @@ export interface ApprovalCardRequest {
141
141
  probeCause?: unknown;
142
142
  probeReason?: string;
143
143
  rememberRefused?: true;
144
+ allowSessionCarried?: true;
144
145
  ruleEvidence?: unknown;
145
146
  requiresRealApproval?: true;
146
147
  irreversibleParkGate?: true;
@@ -157,6 +158,8 @@ export interface ApprovalCardRequest {
157
158
  askClass?: string;
158
159
  classes?: readonly string[];
159
160
  boundGrants?: BoundSkillGrantsWire;
161
+ grantBinding?: ApprovalGrantBinding;
162
+ unreadableEvidence?: readonly string[];
160
163
  classifierNoRuling?: string;
161
164
  priorCardsForCall?: readonly ApprovalPriorCard[];
162
165
  }
@@ -245,7 +248,7 @@ export declare function approvalIsMandated(v: ApprovalMandatedFacts | undefined
245
248
  export declare function readApprovalMandate(v: {
246
249
  readonly mandate?: string;
247
250
  } | undefined | null): ApprovalMandateWord | undefined;
248
- export declare function toolPermissionRequestAskBits(card: object | null | undefined): Pick<ToolPermissionRequest, 'mandated' | 'mandate' | 'origin' | 'ruleOffersAbsence' | 'askClass' | 'classes'>;
251
+ export declare function toolPermissionRequestAskBits(card: object | null | undefined): Pick<ToolPermissionRequest, 'mandated' | 'mandate' | 'origin' | 'ruleOffersAbsence' | 'askClass' | 'classes' | 'rememberRefused'>;
249
252
  export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
250
253
  export type ToolApprovalRespondDecision = 'allow' | 'allow_session' | 'deny';
251
254
  export interface RespondToolApprovalOpts {
@@ -299,6 +302,12 @@ interface ToolApprovalRespondRefusalFields {
299
302
  status?: number;
300
303
  errorCode?: string;
301
304
  message?: string;
305
+ retriable?: true;
306
+ extra?: {
307
+ readonly cause?: string;
308
+ readonly field?: string;
309
+ };
310
+ unreadable?: true;
302
311
  }
303
312
  export type ToolApprovalRespondRefusal = (ToolApprovalRespondRefusalFields & {
304
313
  status: number;
@@ -312,6 +321,7 @@ export declare function isToolApprovalFrame(ev: unknown): ev is ToolApprovalFram
312
321
  export declare function askProbeReasonDetail(card: Pick<ApprovalCardRequest, 'probeReason' | 'probeCause'> | null | undefined): string | undefined;
313
322
  export declare function readApprovalRememberRefused(v: unknown): boolean;
314
323
  export declare function readApprovalBoundGrants(v: unknown): BoundSkillGrantsWire | undefined;
324
+ export type ApprovalGrantBinding = 'bound' | 'none' | 'unreadable';
315
325
  export declare function readApprovalClassifierNoRuling(v: unknown): string | undefined;
316
326
  export declare function classifierNoRulingDetail(word: string | undefined): string;
317
327
  export declare function approvalBoundGrantsDetail(grants: BoundSkillGrantsWire | null | undefined): string | undefined;
@@ -319,6 +329,8 @@ export declare function approvalPriorCardsDetail(card: Pick<ApprovalCardRequest,
319
329
  export declare function readDenialLimitFallback(v: unknown): DenialLimitFallback | undefined;
320
330
  export declare function readRuleOffers(v: unknown): RuleOffer[] | undefined;
321
331
  export declare function readRuleOfferSupply(offers: unknown): RuleOffer[] | undefined;
332
+ export type ApprovalCardV1Facts = Pick<ApprovalCardRequest, 'toolName' | 'args'> & Pick<ApprovalCardRequest, 'toolCallId' | 'workerBadge' | 'wireNote' | 'argsUnavailable' | 'governanceForced' | 'delegation' | 'ruleOffersReadOnly' | 'inputHasBidi' | 'allowRuleShadow' | 'probeCause' | 'rememberRefused' | 'ruleEvidence' | 'requiresRealApproval' | 'ruleOffersAbsence' | 'denialLimitFallback' | 'askClass' | 'classes' | 'grantBinding' | 'unreadableEvidence'>;
333
+ export declare function approvalCardFactsOfV1(card: object | null | undefined): Readonly<ApprovalCardV1Facts> | undefined;
322
334
  export interface ToolApprovalFrameLaneOpts {
323
335
  approvalDecisionNoteCapable?: boolean;
324
336
  respondFreeFormRulesCapable?: boolean;