@sema-agent/client-core 0.110.0 → 0.111.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 (176) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +50 -18
  3. package/dist/abortableSleep.d.ts +7 -0
  4. package/dist/abortableSleep.js +37 -0
  5. package/dist/adapt/arms.js +25 -4
  6. package/dist/adapt/toolCards.d.ts +3 -1
  7. package/dist/adapt/toolCards.js +4 -2
  8. package/dist/adapter/activeRunSelfHeal.d.ts +39 -0
  9. package/dist/adapter/activeRunSelfHeal.js +171 -3
  10. package/dist/adapter/downstream/eventToSdkMessage.js +10 -10
  11. package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -0
  12. package/dist/adapter/downstream/terminalToSdkResult.js +11 -0
  13. package/dist/adapter/runStream.d.ts +5 -0
  14. package/dist/adapter/runStream.js +15 -3
  15. package/dist/attachmentRoute.d.ts +20 -0
  16. package/dist/attachmentRoute.js +39 -0
  17. package/dist/attachmentsWireCaps.js +1 -1
  18. package/dist/builtinOutputStyles.d.ts +12 -0
  19. package/dist/builtinOutputStyles.js +147 -0
  20. package/dist/compactionStatus.d.ts +9 -0
  21. package/dist/compactionStatus.js +29 -5
  22. package/dist/controlRouter.js +2 -1
  23. package/dist/coreValuePorts.d.ts +39 -1
  24. package/dist/coreValuePorts.js +10 -0
  25. package/dist/displayUntrusted.d.ts +1 -0
  26. package/dist/displayUntrusted.js +6 -0
  27. package/dist/engineHealthFacts.d.ts +11 -0
  28. package/dist/engineHealthFacts.js +21 -0
  29. package/dist/engineHealthProbeVerdict.d.ts +23 -0
  30. package/dist/engineHealthProbeVerdict.js +115 -0
  31. package/dist/engineNoticeCodes.js +5 -7
  32. package/dist/engineRepoint.d.ts +12 -5
  33. package/dist/engineRepoint.js +44 -19
  34. package/dist/engineRowRef.d.ts +3 -0
  35. package/dist/engineRowRef.js +11 -1
  36. package/dist/engineSwapCrossing.d.ts +57 -0
  37. package/dist/engineSwapCrossing.js +297 -0
  38. package/dist/engineUnavailable.d.ts +44 -0
  39. package/dist/engineUnavailable.js +492 -0
  40. package/dist/eventLeg.js +3 -3
  41. package/dist/generated/sdkFactTables.d.ts +1 -1
  42. package/dist/generated/sdkFactTables.js +1 -0
  43. package/dist/generated/settingsSchemaTables.d.ts +4 -0
  44. package/dist/generated/settingsSchemaTables.js +1 -0
  45. package/dist/hitl/approvalDelivery.d.ts +1 -0
  46. package/dist/hitl/approvalDelivery.js +1 -1
  47. package/dist/hitl/approvalResolution.d.ts +1 -1
  48. package/dist/hitl/approvalResolution.js +2 -1
  49. package/dist/hitl/askGateWire.js +5 -6
  50. package/dist/hitl/decisionRefusal.d.ts +2 -0
  51. package/dist/hitl/decisionRefusal.js +7 -8
  52. package/dist/hitl/decisionRefusalCarrier.d.ts +3 -0
  53. package/dist/hitl/decisionRefusalCarrier.js +348 -0
  54. package/dist/hitl/decisionSend.d.ts +7 -0
  55. package/dist/hitl/decisionSend.js +34 -12
  56. package/dist/hitl/editedApprovalAcceptance.js +1 -1
  57. package/dist/hitl/elicitationRefusal.d.ts +4 -0
  58. package/dist/hitl/elicitationRefusal.js +23 -0
  59. package/dist/hitl/frameRouter.d.ts +2 -2
  60. package/dist/hitl/frameRouter.js +8 -15
  61. package/dist/hitl/gateIdentity.d.ts +5 -0
  62. package/dist/hitl/gateIdentity.js +23 -0
  63. package/dist/hitl/hitlBridge.d.ts +6 -0
  64. package/dist/hitl/hitlBridge.js +10 -4
  65. package/dist/hitl/hitlHostSurface.d.ts +2 -1
  66. package/dist/hitl/hitlHostSurface.js +7 -2
  67. package/dist/hitl/localAllowRedemption.d.ts +67 -0
  68. package/dist/hitl/localAllowRedemption.js +256 -0
  69. package/dist/hitl/parkResolver.js +7 -4
  70. package/dist/hitl/persistedRulesWire.d.ts +16 -0
  71. package/dist/hitl/persistedRulesWire.js +70 -0
  72. package/dist/hitl/planReviewWire.d.ts +3 -0
  73. package/dist/hitl/planReviewWire.js +23 -6
  74. package/dist/hitl/policyDenyCause.d.ts +1 -1
  75. package/dist/hitl/policyDenyCause.js +1 -1
  76. package/dist/hitl/resumeRunningCard.d.ts +1 -0
  77. package/dist/hitl/resumeRunningCard.js +1 -0
  78. package/dist/hitl/screenCardCarriers.d.ts +15 -0
  79. package/dist/hitl/screenCardCarriers.js +41 -0
  80. package/dist/hitl/screenDecisionCards.d.ts +98 -0
  81. package/dist/hitl/screenDecisionCards.js +362 -0
  82. package/dist/hitl/standingRows.d.ts +3 -1
  83. package/dist/hitl/standingRows.js +18 -0
  84. package/dist/hitl/toolApprovalWire.d.ts +30 -8
  85. package/dist/hitl/toolApprovalWire.js +264 -121
  86. package/dist/hooksWireCaps.js +3 -3
  87. package/dist/hostSettingNoticeArm.d.ts +3 -0
  88. package/dist/hostSettingNoticeArm.js +12 -0
  89. package/dist/hostTurnSettings.d.ts +3 -0
  90. package/dist/hostTurnSettings.js +38 -5
  91. package/dist/humanTurnRead.js +11 -1
  92. package/dist/i18n/messages.en.d.ts +129 -0
  93. package/dist/i18n/messages.en.js +135 -7
  94. package/dist/i18n/messages.zh.js +142 -6
  95. package/dist/i18n/translate.d.ts +3 -0
  96. package/dist/i18n/translate.js +78 -10
  97. package/dist/i18n/zh.d.ts +1 -0
  98. package/dist/i18n/zh.js +1 -0
  99. package/dist/index.d.ts +43 -17
  100. package/dist/index.js +30 -12
  101. package/dist/interactiveHalt.js +2 -1
  102. package/dist/interactiveInterrupt.js +20 -2
  103. package/dist/liveModelCatalog.js +2 -2
  104. package/dist/localEngineSeatReading.d.ts +2 -2
  105. package/dist/localEngineSeatReading.js +5 -3
  106. package/dist/localEngineSpawn.d.ts +0 -7
  107. package/dist/localEngineSpawn.js +0 -92
  108. package/dist/localEngineStep.d.ts +2 -2
  109. package/dist/localEngineStep.js +2 -0
  110. package/dist/mcpReconnect.js +3 -3
  111. package/dist/mcpWireCaps.js +1 -1
  112. package/dist/model/engineModelWords.d.ts +3 -0
  113. package/dist/model/engineModelWords.js +13 -0
  114. package/dist/model/subagentModelWord.d.ts +21 -2
  115. package/dist/model/subagentModelWord.js +100 -29
  116. package/dist/modelCapabilityProbe.js +3 -40
  117. package/dist/notifications.d.ts +19 -0
  118. package/dist/notifications.js +114 -12
  119. package/dist/ownKey.d.ts +6 -0
  120. package/dist/ownKey.js +31 -0
  121. package/dist/pendingUserRow.d.ts +4 -0
  122. package/dist/pendingUserRow.js +16 -0
  123. package/dist/permissionsDeclaration.d.ts +1 -0
  124. package/dist/permissionsDeclaration.js +11 -1
  125. package/dist/planReviewCardFacts.d.ts +3 -0
  126. package/dist/planReviewCardFacts.js +52 -2
  127. package/dist/pluginHooksWire.d.ts +9 -1
  128. package/dist/pluginHooksWire.js +40 -6
  129. package/dist/printToolResultFrame.js +4 -2
  130. package/dist/promptBlockedCopy.js +11 -6
  131. package/dist/promptSuggestionBatch.d.ts +7 -0
  132. package/dist/promptSuggestionBatch.js +9 -0
  133. package/dist/promptSuggestionsTail.d.ts +39 -0
  134. package/dist/promptSuggestionsTail.js +116 -0
  135. package/dist/request/taskRequest.js +10 -6
  136. package/dist/requestCoordinate.d.ts +1 -0
  137. package/dist/requestCoordinate.js +13 -0
  138. package/dist/rewindArchiveReading.js +4 -4
  139. package/dist/runCancelContext.d.ts +2 -1
  140. package/dist/runCancelContext.js +1 -0
  141. package/dist/runPermissionModeSwitch.js +2 -2
  142. package/dist/scenarioRefusal.js +11 -13
  143. package/dist/scenarioSkills.js +11 -6
  144. package/dist/seam.d.ts +16 -2
  145. package/dist/seam.js +10 -0
  146. package/dist/seatContract.d.ts +109 -9
  147. package/dist/seatContract.js +218 -15
  148. package/dist/sessionCostReading.d.ts +70 -0
  149. package/dist/sessionCostReading.js +150 -0
  150. package/dist/sessionHooksRoster.d.ts +4 -0
  151. package/dist/sessionHooksRoster.js +158 -0
  152. package/dist/sessionMemoryStatus.js +2 -2
  153. package/dist/sessionReadRedaction.d.ts +7 -0
  154. package/dist/sessionReadRedaction.js +26 -0
  155. package/dist/sessionSyncFileHistory.js +8 -9
  156. package/dist/settingsHooksSalvage.js +2 -1
  157. package/dist/sseIdleTriage.js +2 -1
  158. package/dist/storeDurabilityWord.d.ts +15 -0
  159. package/dist/storeDurabilityWord.js +18 -0
  160. package/dist/storePostureCapability.d.ts +7 -3
  161. package/dist/storePostureCapability.js +69 -39
  162. package/dist/subagent/engineSubagentResume.js +2 -2
  163. package/dist/subagent/engineSubagentTail.js +2 -1
  164. package/dist/subagentContentStore.d.ts +11 -0
  165. package/dist/subagentContentStore.js +28 -2
  166. package/dist/toolResult.d.ts +1 -0
  167. package/dist/toolResult.js +2 -0
  168. package/dist/userAttachmentEnvelope.d.ts +2 -1
  169. package/dist/userAttachmentEnvelope.js +10 -0
  170. package/dist/wireErrorTriage.js +2 -1
  171. package/dist/wireFailureShape.d.ts +4 -0
  172. package/dist/wireFailureShape.js +141 -19
  173. package/dist/wireRefusalCopy.d.ts +6 -5
  174. package/dist/wireRefusalCopy.js +49 -92
  175. package/docs/INTEGRATION-CLIENTS.md +2118 -17
  176. package/package.json +6 -2
@@ -89,6 +89,8 @@ function policyDenyHeadFor(cause) {
89
89
  return renderMessage(msg('approval.policyDeny.denyRule', { tail }));
90
90
  case 'engine_floor':
91
91
  return renderMessage(msg('approval.policyDeny.engineFloor', { tail }));
92
+ case 'hook':
93
+ return renderMessage(msg('approval.policyDeny.hook'));
92
94
  case 'no_approval_ui':
93
95
  case undefined:
94
96
  return HITL_POLICY_DENY_MESSAGE;
@@ -117,20 +119,6 @@ function denyStampedEnd(ev, attribution) {
117
119
  export const HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE = '[Request interrupted by user for tool use]';
118
120
  const RUN_CANCELLED_ERROR_CODE = RUN_CANCELLED_CODE;
119
121
  export const ENGINE_ABORT_TOOL_RESULT = 'Operation aborted';
120
- const bridgeRunHandleFallbacks = new WeakMap();
121
- export function setBridgeRunHandleFallback(deps, read) {
122
- bridgeRunHandleFallbacks.set(deps, read);
123
- }
124
- export function bridgeRunHandleFallbackOf(deps) {
125
- try {
126
- const read = bridgeRunHandleFallbacks.get(deps);
127
- const v = read?.();
128
- return typeof v === 'string' && v !== '' ? v : undefined;
129
- }
130
- catch {
131
- return undefined;
132
- }
133
- }
134
122
  function isAskToolLoose(name) {
135
123
  return typeof name === 'string' && name.replace(/[\s_-]+/g, '').toLowerCase() === 'askuserquestion';
136
124
  }
@@ -371,7 +359,12 @@ async function routeToolApprovalFrame(ev, ctx) {
371
359
  const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
372
360
  const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
373
361
  const runTaskId = ctx.taskId.current;
374
- const launched = surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane, typeof runTaskId === 'string' && runTaskId !== '' ? { taskId: runTaskId } : undefined);
362
+ const streamGeneration = ctx.streamGeneration;
363
+ const runFacts = {
364
+ ...(typeof runTaskId === 'string' && runTaskId !== '' ? { taskId: runTaskId } : {}),
365
+ ...(streamGeneration !== undefined ? { sourceGeneration: streamGeneration } : {}),
366
+ };
367
+ const launched = surfaceToolApprovalFrameAndRespond(ev, deps.respondToolApproval, argsOfGatedStart(led, argsCallId), ctx.signal, deps.approvalLane, Object.keys(runFacts).length > 0 ? runFacts : undefined);
375
368
  const hold = liveCardHoldOf(liveFrameCallKey(ev.approvalId));
376
369
  const handled = launched.then((outcome) => settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId), (e) => {
377
370
  hostLog('debug', `liveHitlAskWire: tool_approval card for ${ev.approvalId} failed: ${String(e).slice(0, 160)}`);
@@ -1,9 +1,14 @@
1
1
  export declare const PLAN_REVIEW_QUESTION_ID_PREFIX = "plan-review:";
2
2
  export declare const REOPEN_ID_TAIL = "#reopen-";
3
+ export declare const SCREEN_CARD_ORIGINS: readonly ["live-approval", "park-approval", "park-question", "plan-review", "host-reopen-question", "host-choice", "local-permission"];
4
+ export type ScreenCardOrigin = (typeof SCREEN_CARD_ORIGINS)[number];
3
5
  export declare function approvalCallKey(gatedCallId: string | undefined, taskId: string): string;
4
6
  export declare function askGateQuestionId(callKey: string): string;
5
7
  export declare function planReviewQuestionId(taskId: string): string;
6
8
  export declare function freshPlanReviewQuestionId(taskId: string, suffix: string): string;
9
+ export declare function hostReopenQuestionId(rowTaskId: string, suffix: string): string;
10
+ export declare function hostRunChoiceQuestionId(taskId: string, suffix: string): string;
11
+ export declare function questionCardOriginOf(questionId: {} | null | undefined): Extract<ScreenCardOrigin, 'park-question' | 'plan-review' | 'host-reopen-question' | 'host-choice'> | undefined;
7
12
  export declare function liveFrameCallKey(approvalId: string): string;
8
13
  export declare function taskIdFromPlanReviewQuestionId(questionId: string): string | undefined;
9
14
  export declare function armedKeyFromQuestionId(questionId: string): string;
@@ -2,6 +2,9 @@ const HITL_ASK_QUESTION_ID_PREFIX = 'hitl-ask:';
2
2
  export const PLAN_REVIEW_QUESTION_ID_PREFIX = 'plan-review:';
3
3
  const HITL_FRAME_CALL_KEY_PREFIX = 'hitl-frame:';
4
4
  export const REOPEN_ID_TAIL = '#reopen-';
5
+ const HOST_REOPEN_QUESTION_ID_PREFIX = 'ask-park:';
6
+ const HOST_RUN_CHOICE_QUESTION_ID_PREFIX = 'active-run:';
7
+ export const SCREEN_CARD_ORIGINS = Object.freeze(['live-approval', 'park-approval', 'park-question', 'plan-review', 'host-reopen-question', 'host-choice', 'local-permission']);
5
8
  export function approvalCallKey(gatedCallId, taskId) {
6
9
  return gatedCallId ?? taskId;
7
10
  }
@@ -14,6 +17,26 @@ export function planReviewQuestionId(taskId) {
14
17
  export function freshPlanReviewQuestionId(taskId, suffix) {
15
18
  return `${planReviewQuestionId(taskId)}${REOPEN_ID_TAIL}${suffix}`;
16
19
  }
20
+ export function hostReopenQuestionId(rowTaskId, suffix) {
21
+ return `${HOST_REOPEN_QUESTION_ID_PREFIX}${rowTaskId}${REOPEN_ID_TAIL}${suffix}`;
22
+ }
23
+ export function hostRunChoiceQuestionId(taskId, suffix) {
24
+ return `${HOST_RUN_CHOICE_QUESTION_ID_PREFIX}${taskId}${REOPEN_ID_TAIL}${suffix}`;
25
+ }
26
+ export function questionCardOriginOf(questionId) {
27
+ if (typeof questionId !== 'string')
28
+ return undefined;
29
+ const has = (prefix) => questionId.startsWith(prefix) && questionId.length > prefix.length;
30
+ if (has(HITL_ASK_QUESTION_ID_PREFIX))
31
+ return 'park-question';
32
+ if (has(PLAN_REVIEW_QUESTION_ID_PREFIX))
33
+ return 'plan-review';
34
+ if (has(HOST_REOPEN_QUESTION_ID_PREFIX))
35
+ return 'host-reopen-question';
36
+ if (has(HOST_RUN_CHOICE_QUESTION_ID_PREFIX))
37
+ return 'host-choice';
38
+ return undefined;
39
+ }
17
40
  export function liveFrameCallKey(approvalId) {
18
41
  return `${HITL_FRAME_CALL_KEY_PREFIX}${approvalId}`;
19
42
  }
@@ -138,6 +138,12 @@ export type FindPendingOutcome = {
138
138
  readonly kind: 'ok';
139
139
  }>;
140
140
  };
141
+ export interface PendingRowCallIdentity {
142
+ readonly taskId?: string | null;
143
+ readonly toolCallId?: string | null;
144
+ readonly boundCallId?: string | null;
145
+ }
146
+ export declare function pendingRowAnswersCall(row: PendingRowCallIdentity, taskId: string, callId: string): boolean;
141
147
  export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
142
148
  signal?: AbortSignal;
143
149
  }, callId?: string, identityAccepts?: (row: PendingCheckpoint) => boolean): Promise<FindPendingOutcome>;
@@ -8,6 +8,7 @@ import { guardedRead, ownDataField, readOwnDataValue } from '../ownKey.js';
8
8
  import { PROTOCOL_ERROR_NAME, isSlotCarrier, protocolErrorNameReading, readBlockedBy, readThrownSlot, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
9
9
  import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_INVALID_OUTCOME, RESUME_POLICY_FOLD_CODE } from '../engineErrorCodes.js';
10
10
  import { suspendedReopenOf } from './suspendedReopen.js';
11
+ import { decisionConflictOf } from './decisionConflict.js';
11
12
  import { closeDecisionOp, decisionLedgerKeysOf, openDecisionOp } from './decisionLedger.js';
12
13
  import { askParkBlockGeneration, noteAskParkDecideSettled } from './askParkAutoReopenBlock.js';
13
14
  import { checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
@@ -259,6 +260,9 @@ function notifyDecideObserver(observer, ev) {
259
260
  invokeHostCallbackIsolated(`hitlBridge: onDecideOperation(${ev.phase})`, () => observer(ev));
260
261
  }
261
262
  const ANY_TOOL_FAMILY = () => true;
263
+ export function pendingRowAnswersCall(row, taskId, callId) {
264
+ return row.taskId === taskId && (row.boundCallId === callId || row.toolCallId === callId);
265
+ }
262
266
  export async function findPendingForTask(client, taskId, matches, opts, callId, identityAccepts = (r) => matches(typeof r.toolName === 'string' ? r.toolName : undefined)) {
263
267
  let rows;
264
268
  try {
@@ -275,10 +279,10 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
275
279
  });
276
280
  const decidable = readings.filter((x) => x.foreign === null).map((x) => x.r);
277
281
  const identityRow = callId !== undefined
278
- ? decidable.find((r) => r.taskId === taskId && (r.boundCallId === callId || r.toolCallId === callId))
282
+ ? decidable.find((r) => pendingRowAnswersCall(r, taskId, callId))
279
283
  : undefined;
280
284
  const identityForeign = callId !== undefined && identityRow === undefined
281
- ? readings.find((x) => x.r.taskId === taskId && (x.r.boundCallId === callId || x.r.toolCallId === callId) && x.foreign !== null)
285
+ ? readings.find((x) => pendingRowAnswersCall(x.r, taskId, callId) && x.foreign !== null)
282
286
  : undefined;
283
287
  if (identityForeign !== undefined) {
284
288
  const identityBar = identityForeign.foreign === QUEUE_ROW_FAMILY_UNREADABLE_GATE ? checkpointRowBarOf(identityForeign.r) : undefined;
@@ -602,8 +606,10 @@ function bindingMismatchReading(e) {
602
606
  const codeReclassified = code.state === 'code' && code.code === CHECKPOINT_INVALID_OUTCOME;
603
607
  const family = (code.state === 'code' && code.code === APPROVAL_BINDING_MISMATCH) ||
604
608
  (!codeReclassified && nameReading.state === 'named' && nameReading.name === PROTOCOL_ERROR_NAME.ApprovalBindingMismatchError);
605
- if (family)
606
- return 'mismatch';
609
+ if (family) {
610
+ const captured = code.state === 'code' ? { state: 'read', wire: code.code } : code.state === 'absent' ? { state: 'read' } : { state: 'unreadable' };
611
+ return decisionConflictOf(e, captured, nameReading) === 'row_integrity' ? 'not' : 'mismatch';
612
+ }
607
613
  if (code.state === 'unreadable')
608
614
  return 'unreadable';
609
615
  return 'not';
@@ -22,7 +22,8 @@ export declare function hitlHostSurfaceMissesFor(sessionKey: string): number;
22
22
  export declare function hitlHostSurfaceFor(sessionKey: string): HitlHostSurface | null;
23
23
  export declare function _resetHitlHostSurfaceForTest(): void;
24
24
  export declare function surfaceForCurrentSession(): HitlHostSurface | null;
25
- export declare const CANCEL_DENY_BUDGET_MS = 2000;
25
+ export declare const CANCEL_DENY_BUDGET_MS = 8000;
26
+ export declare function interruptDenyLifetime(): AbortSignal;
26
27
  export declare const INTERRUPT_DENY_REASON = "Interrupted by user";
27
28
  export declare const CANCEL_DENY_WARN_TEXT = "could not cancel the pending question \u2014 the session may stay locked; the run may need engine-side recovery";
28
29
  export declare const REMEMBER_NOT_APPLIED_WARN_TEXT = "this session-wide allow was not remembered by the worker \u2014 you will be asked again for this tool";
@@ -4,6 +4,7 @@ import { unrefTimer } from '../unrefTimer.js';
4
4
  import { decideFailureNote } from '../decideFailureNote.js';
5
5
  import { editedApprovalRefusedLine } from './editedApprovalAcceptance.js';
6
6
  import { decisionRefusalSaysAskGone } from './decisionRefusal.js';
7
+ import { deliveryTiming, RETRY_BUDGET_MS } from './approvalDelivery.js';
7
8
  const hostSurfaceByKey = createSessionSlot();
8
9
  const hostSurfaceMissesByKey = new Map();
9
10
  export function installHitlHostSurface(surface) {
@@ -39,7 +40,10 @@ function surfaceForSession(sessionKey) {
39
40
  }
40
41
  return hostSurface;
41
42
  }
42
- export const CANCEL_DENY_BUDGET_MS = 2000;
43
+ export const CANCEL_DENY_BUDGET_MS = RETRY_BUDGET_MS;
44
+ export function interruptDenyLifetime() {
45
+ return AbortSignal.timeout(Math.max(1, deliveryTiming().budgetMs));
46
+ }
43
47
  export const INTERRUPT_DENY_REASON = 'Interrupted by user';
44
48
  export const CANCEL_DENY_WARN_TEXT = 'could not cancel the pending question — the session may stay locked; the run may need engine-side recovery';
45
49
  const CANCEL_DENY_WARN_KEY = 'hitl-cancel-deny-warn';
@@ -140,8 +144,9 @@ export function surfaceRuleArmRejected(why) {
140
144
  }
141
145
  export function observeCancelByDeny(settle, taskId) {
142
146
  let timer;
147
+ const budgetMs = deliveryTiming().budgetMs;
143
148
  const budget = new Promise((_, reject) => {
144
- timer = setTimeout(() => reject(new Error(`cancel-by-deny did not settle within ${CANCEL_DENY_BUDGET_MS}ms`)), CANCEL_DENY_BUDGET_MS);
149
+ timer = setTimeout(() => reject(new Error(`cancel-by-deny did not settle within ${budgetMs}ms`)), budgetMs);
145
150
  unrefTimer(timer);
146
151
  });
147
152
  void Promise.race([settle, budget])
@@ -0,0 +1,67 @@
1
+ import type { EngineCapState } from '../engineCapsCache.js';
2
+ import type { HostSettingsLayer, WorkspaceTrustState } from '../hostSettingsSource.js';
3
+ import { type LocalAllowRuleDeps } from './localAllowRule.js';
4
+ import { type ListAllPersistedRulesOutcome, type RulesFacade } from './persistedRulesWire.js';
5
+ export declare const LOCAL_ALLOW_NOT_REDEEMABLE_REASONS: readonly ["managed_only", "tighten_only", "no_scope_root", "no_consent", "road_closed"];
6
+ export type LocalAllowNotRedeemableReason = (typeof LOCAL_ALLOW_NOT_REDEEMABLE_REASONS)[number];
7
+ export declare const LOCAL_ALLOW_UNKNOWN_CAUSES: readonly ["input_unreadable", "policy_unreadable", "consent_unreadable", "road_unknown"];
8
+ export type LocalAllowUnknownCause = (typeof LOCAL_ALLOW_UNKNOWN_CAUSES)[number];
9
+ export type LocalAllowRedeemability = {
10
+ readonly kind: 'redeemable';
11
+ readonly via: 'layer' | 'consent';
12
+ } | {
13
+ readonly kind: 'not_redeemable';
14
+ readonly reason: LocalAllowNotRedeemableReason;
15
+ } | {
16
+ readonly kind: 'unknown';
17
+ readonly cause: LocalAllowUnknownCause;
18
+ };
19
+ export interface LocalAllowOfferedFacts {
20
+ readonly managedRulesOnly: boolean | 'unreadable';
21
+ readonly scopeRoot: string | undefined;
22
+ readonly road: EngineCapState;
23
+ }
24
+ export type LocalAllowRedeemabilityInput = ({
25
+ readonly subject: 'offered';
26
+ } & LocalAllowOfferedFacts) | {
27
+ readonly subject: 'declared';
28
+ readonly source: HostSettingsLayer;
29
+ readonly workspace: WorkspaceTrustState | 'unreadable';
30
+ readonly managedRulesOnly: boolean | 'unreadable';
31
+ readonly rule: string;
32
+ readonly scopeRoot: string | undefined;
33
+ readonly store: ListAllPersistedRulesOutcome;
34
+ };
35
+ export declare function localAllowRedeemability(input: LocalAllowRedeemabilityInput): LocalAllowRedeemability;
36
+ export declare const LOCAL_ALLOW_REDEMPTION_REFUSALS: readonly ["managed_only", "no_scope_root", "rule_shape", "request_refused", "preview_mismatch", "engine_refused", "ticket_dead"];
37
+ export type LocalAllowRedemptionRefusal = (typeof LOCAL_ALLOW_REDEMPTION_REFUSALS)[number];
38
+ export declare const LOCAL_ALLOW_REDEMPTION_UNAVAILABLE_CAUSES: readonly ["road_closed", "road_unknown", "policy_unreadable", "input_unreadable", "prepare_failed"];
39
+ export type LocalAllowRedemptionUnavailableCause = (typeof LOCAL_ALLOW_REDEMPTION_UNAVAILABLE_CAUSES)[number];
40
+ export declare const LOCAL_ALLOW_REDEMPTION_UNKNOWN_CAUSES: readonly ["redeem_unsettled", "receipt_unreadable"];
41
+ export type LocalAllowRedemptionUnknownCause = (typeof LOCAL_ALLOW_REDEMPTION_UNKNOWN_CAUSES)[number];
42
+ export type LocalAllowRedemptionReceipt = {
43
+ readonly kind: 'applied';
44
+ readonly outcome: 'persisted' | 'already_live';
45
+ readonly rule: string;
46
+ readonly scope: string;
47
+ } | {
48
+ readonly kind: 'refused';
49
+ readonly reason: LocalAllowRedemptionRefusal;
50
+ readonly detail?: string;
51
+ } | {
52
+ readonly kind: 'unavailable';
53
+ readonly cause: LocalAllowRedemptionUnavailableCause;
54
+ } | {
55
+ readonly kind: 'unknown';
56
+ readonly cause: LocalAllowRedemptionUnknownCause;
57
+ };
58
+ export type LocalAllowRedemptionFacade = Pick<RulesFacade, 'ccImportPrepare' | 'ccImportRedeem'>;
59
+ export interface ReadOnlyRuleOfferRedemption extends LocalAllowOfferedFacts {
60
+ readonly rule: string;
61
+ readonly expectedToolName?: string;
62
+ readonly settingsPath: string;
63
+ readonly deps: LocalAllowRuleDeps;
64
+ }
65
+ export declare function redeemReadOnlyRuleOffer(facade: LocalAllowRedemptionFacade, input: ReadOnlyRuleOfferRedemption, opts?: {
66
+ readonly signal?: AbortSignal;
67
+ }): Promise<LocalAllowRedemptionReceipt>;
@@ -0,0 +1,256 @@
1
+ import { displayUntrusted } from '../displayUntrusted.js';
2
+ import { readOwnDataValue } from '../ownKey.js';
3
+ import { layerAdmissionWord } from '../settingsLayerAdmission.js';
4
+ import { parseLocalAllowRule } from './localAllowRule.js';
5
+ import { classifyRulesFailure, classifySkippedReason, readCcImportRedeemCounts, } from './persistedRulesWire.js';
6
+ export const LOCAL_ALLOW_NOT_REDEEMABLE_REASONS = Object.freeze(['managed_only', 'tighten_only', 'no_scope_root', 'no_consent', 'road_closed']);
7
+ export const LOCAL_ALLOW_UNKNOWN_CAUSES = Object.freeze(['input_unreadable', 'policy_unreadable', 'consent_unreadable', 'road_unknown']);
8
+ function verdict(v) {
9
+ return Object.freeze({ ...v });
10
+ }
11
+ const notRedeemable = (reason) => verdict({ kind: 'not_redeemable', reason });
12
+ const unknownBecause = (cause) => verdict({ kind: 'unknown', cause });
13
+ const PROJECT_SCOPE_PREFIX = 'project:';
14
+ const ALLOW_BEHAVIOR = 'allow';
15
+ const MANAGED_LAYER = 'policySettings';
16
+ const DETAIL_SHOWN_MAX = 400;
17
+ function policyStep(v) {
18
+ if (v === true)
19
+ return notRedeemable('managed_only');
20
+ if (v === false)
21
+ return undefined;
22
+ return unknownBecause('policy_unreadable');
23
+ }
24
+ function scopeRootOf(v) {
25
+ return typeof v === 'string' && v !== '' ? v : undefined;
26
+ }
27
+ function storeRowConsents(row, rule, scopeRoot) {
28
+ if (typeof row !== 'object' || row === null)
29
+ return false;
30
+ const behavior = readOwnDataValue(row, 'behavior');
31
+ const text = readOwnDataValue(row, 'rule');
32
+ const scope = readOwnDataValue(row, 'scope');
33
+ return behavior === ALLOW_BEHAVIOR && text === rule && (scope === 'global' || scope === PROJECT_SCOPE_PREFIX + scopeRoot);
34
+ }
35
+ function consentRecorded(store, rule, scopeRoot) {
36
+ if (typeof store !== 'object' || store === null)
37
+ return undefined;
38
+ const ok = readOwnDataValue(store, 'ok');
39
+ if (ok !== true)
40
+ return undefined;
41
+ const rows = readOwnDataValue(store, 'rules');
42
+ if (!Array.isArray(rows))
43
+ return undefined;
44
+ for (let i = 0; i < rows.length; i++) {
45
+ if (storeRowConsents(readOwnDataValue(rows, String(i)), rule, scopeRoot))
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ function roadStep(road) {
51
+ if (road === 'true')
52
+ return verdict({ kind: 'redeemable', via: 'consent' });
53
+ if (road === 'false' || road === 'absent')
54
+ return notRedeemable('road_closed');
55
+ return unknownBecause('road_unknown');
56
+ }
57
+ export function localAllowRedeemability(input) {
58
+ try {
59
+ if (typeof input !== 'object' || input === null)
60
+ return unknownBecause('input_unreadable');
61
+ const subject = readOwnDataValue(input, 'subject');
62
+ if (subject !== 'offered' && subject !== 'declared')
63
+ return unknownBecause('input_unreadable');
64
+ const source = readOwnDataValue(input, 'source');
65
+ if (!(subject === 'declared' && source === MANAGED_LAYER)) {
66
+ const policy = policyStep(readOwnDataValue(input, 'managedRulesOnly'));
67
+ if (policy !== undefined)
68
+ return policy;
69
+ }
70
+ const scopeRoot = scopeRootOf(readOwnDataValue(input, 'scopeRoot'));
71
+ if (subject === 'offered') {
72
+ if (scopeRoot === undefined)
73
+ return notRedeemable('no_scope_root');
74
+ return roadStep(readOwnDataValue(input, 'road'));
75
+ }
76
+ const workspace = readOwnDataValue(input, 'workspace');
77
+ let word;
78
+ try {
79
+ word = layerAdmissionWord(source, workspace, 'permissions', ALLOW_BEHAVIOR);
80
+ }
81
+ catch {
82
+ return unknownBecause('input_unreadable');
83
+ }
84
+ if (word === 'admitted')
85
+ return verdict({ kind: 'redeemable', via: 'layer' });
86
+ if (word === 'tighten_only')
87
+ return notRedeemable('tighten_only');
88
+ if (scopeRoot === undefined)
89
+ return notRedeemable('no_scope_root');
90
+ const rule = readOwnDataValue(input, 'rule');
91
+ if (typeof rule !== 'string' || rule === '')
92
+ return unknownBecause('input_unreadable');
93
+ const consent = consentRecorded(readOwnDataValue(input, 'store'), rule, scopeRoot);
94
+ if (consent === undefined)
95
+ return unknownBecause('consent_unreadable');
96
+ return consent ? verdict({ kind: 'redeemable', via: 'consent' }) : notRedeemable('no_consent');
97
+ }
98
+ catch {
99
+ return unknownBecause('input_unreadable');
100
+ }
101
+ }
102
+ export const LOCAL_ALLOW_REDEMPTION_REFUSALS = Object.freeze([
103
+ 'managed_only',
104
+ 'no_scope_root',
105
+ 'rule_shape',
106
+ 'request_refused',
107
+ 'preview_mismatch',
108
+ 'engine_refused',
109
+ 'ticket_dead',
110
+ ]);
111
+ export const LOCAL_ALLOW_REDEMPTION_UNAVAILABLE_CAUSES = Object.freeze(['road_closed', 'road_unknown', 'policy_unreadable', 'input_unreadable', 'prepare_failed']);
112
+ export const LOCAL_ALLOW_REDEMPTION_UNKNOWN_CAUSES = Object.freeze(['redeem_unsettled', 'receipt_unreadable']);
113
+ function singleRuleLayerContent(canonical) {
114
+ return JSON.stringify({ permissions: { allow: [canonical] } });
115
+ }
116
+ function receipt(r) {
117
+ return Object.freeze({ ...r });
118
+ }
119
+ function detailOf(text) {
120
+ if (typeof text !== 'string')
121
+ return {};
122
+ const shown = displayUntrusted(text, { max: DETAIL_SHOWN_MAX });
123
+ return shown.trim() !== '' ? { detail: shown } : {};
124
+ }
125
+ const JUDGED_RECEIPT = {
126
+ managed_only: { kind: 'refused', reason: 'managed_only' },
127
+ no_scope_root: { kind: 'refused', reason: 'no_scope_root' },
128
+ road_closed: { kind: 'unavailable', cause: 'road_closed' },
129
+ road_unknown: { kind: 'unavailable', cause: 'road_unknown' },
130
+ policy_unreadable: { kind: 'unavailable', cause: 'policy_unreadable' },
131
+ input_unreadable: { kind: 'unavailable', cause: 'input_unreadable' },
132
+ tighten_only: { kind: 'unavailable', cause: 'input_unreadable' },
133
+ no_consent: { kind: 'unavailable', cause: 'input_unreadable' },
134
+ consent_unreadable: { kind: 'unavailable', cause: 'input_unreadable' },
135
+ };
136
+ function previewSingle(prep, scopeRoot) {
137
+ const preview = typeof prep === 'object' && prep !== null ? readOwnDataValue(prep, 'preview') : undefined;
138
+ if (typeof preview !== 'object' || preview === null)
139
+ return { mismatch: true };
140
+ const skipped = readOwnDataValue(preview, 'skipped');
141
+ if (Array.isArray(skipped) && skipped.length > 0) {
142
+ const first = readOwnDataValue(skipped, '0');
143
+ const reason = typeof first === 'object' && first !== null ? readOwnDataValue(first, 'reason') : undefined;
144
+ return { mismatch: true, skipped: typeof reason === 'string' ? classifySkippedReason(reason).text : '' };
145
+ }
146
+ if (!Array.isArray(skipped))
147
+ return { mismatch: true };
148
+ const candidates = readOwnDataValue(preview, 'candidates');
149
+ if (!Array.isArray(candidates) || candidates.length !== 1)
150
+ return { mismatch: true };
151
+ const c = readOwnDataValue(candidates, '0');
152
+ if (typeof c !== 'object' || c === null)
153
+ return { mismatch: true };
154
+ const behavior = readOwnDataValue(c, 'behavior');
155
+ const rule = readOwnDataValue(c, 'rule');
156
+ const scope = readOwnDataValue(c, 'scope');
157
+ const kind = typeof scope === 'object' && scope !== null ? readOwnDataValue(scope, 'kind') : undefined;
158
+ const root = typeof scope === 'object' && scope !== null ? readOwnDataValue(scope, 'root') : undefined;
159
+ if (behavior !== ALLOW_BEHAVIOR || typeof rule !== 'string' || kind !== 'project' || root !== scopeRoot)
160
+ return { mismatch: true };
161
+ return { rule };
162
+ }
163
+ export async function redeemReadOnlyRuleOffer(facade, input, opts) {
164
+ let canonical;
165
+ let offered;
166
+ let scopeRoot;
167
+ let settingsPath;
168
+ try {
169
+ if (typeof input !== 'object' || input === null)
170
+ return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
171
+ const judged = localAllowRedeemability({
172
+ subject: 'offered',
173
+ managedRulesOnly: readOwnDataValue(input, 'managedRulesOnly'),
174
+ scopeRoot: readOwnDataValue(input, 'scopeRoot'),
175
+ road: readOwnDataValue(input, 'road'),
176
+ });
177
+ if (judged.kind !== 'redeemable')
178
+ return receipt(JUDGED_RECEIPT[judged.kind === 'not_redeemable' ? judged.reason : judged.cause]);
179
+ scopeRoot = readOwnDataValue(input, 'scopeRoot');
180
+ const path = readOwnDataValue(input, 'settingsPath');
181
+ if (typeof path !== 'string' || path === '')
182
+ return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
183
+ settingsPath = path;
184
+ const rule = readOwnDataValue(input, 'rule');
185
+ const expected = readOwnDataValue(input, 'expectedToolName');
186
+ const deps = readOwnDataValue(input, 'deps');
187
+ if (typeof rule !== 'string' || deps === undefined || (expected !== undefined && typeof expected !== 'string'))
188
+ return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
189
+ const parsed = parseLocalAllowRule(rule, expected, deps);
190
+ if (!parsed.ok)
191
+ return receipt({ kind: 'refused', reason: 'rule_shape', ...detailOf(parsed.error) });
192
+ canonical = parsed.canonical;
193
+ offered = rule;
194
+ }
195
+ catch {
196
+ return receipt({ kind: 'unavailable', cause: 'input_unreadable' });
197
+ }
198
+ const signal = opts?.signal;
199
+ let prep;
200
+ try {
201
+ prep = await facade.ccImportPrepare([{ layer: 'localSettings', path: settingsPath, root: scopeRoot, content: singleRuleLayerContent(canonical) }], signal !== undefined ? { signal } : undefined);
202
+ }
203
+ catch (e) {
204
+ const f = classifyRulesFailure(e);
205
+ if (f.kind === 'lane-unavailable' || f.kind === 'route-missing')
206
+ return receipt({ kind: 'unavailable', cause: 'road_closed' });
207
+ if (f.kind === 'body-shape' || f.kind === 'field-refused' || f.kind === 'too-many-candidates' || f.kind === 'forbidden' || f.kind === 'operators-unconfigured') {
208
+ return receipt({ kind: 'refused', reason: 'request_refused' });
209
+ }
210
+ return receipt({ kind: 'unavailable', cause: 'prepare_failed' });
211
+ }
212
+ let ticket;
213
+ let storedRule;
214
+ try {
215
+ const single = previewSingle(prep, scopeRoot);
216
+ if ('mismatch' in single) {
217
+ return receipt(single.skipped !== undefined ? { kind: 'refused', reason: 'engine_refused', ...detailOf(single.skipped) } : { kind: 'refused', reason: 'preview_mismatch' });
218
+ }
219
+ if (single.rule !== canonical && single.rule !== offered)
220
+ return receipt({ kind: 'refused', reason: 'preview_mismatch' });
221
+ storedRule = single.rule;
222
+ const t = readOwnDataValue(prep, 'ticket');
223
+ if (typeof t !== 'string' || t === '')
224
+ return receipt({ kind: 'refused', reason: 'preview_mismatch' });
225
+ ticket = t;
226
+ }
227
+ catch {
228
+ return receipt({ kind: 'refused', reason: 'preview_mismatch' });
229
+ }
230
+ let body;
231
+ try {
232
+ body = await facade.ccImportRedeem(ticket, signal !== undefined ? { signal } : undefined);
233
+ }
234
+ catch (e) {
235
+ const f = classifyRulesFailure(e);
236
+ if (f.kind === 'ticket-dead')
237
+ return receipt({ kind: 'refused', reason: 'ticket_dead' });
238
+ if (f.kind === 'lane-unavailable')
239
+ return receipt({ kind: 'unavailable', cause: 'road_closed' });
240
+ return receipt({ kind: 'unknown', cause: 'redeem_unsettled' });
241
+ }
242
+ const counts = readCcImportRedeemCounts(body);
243
+ if (counts === null)
244
+ return receipt({ kind: 'unknown', cause: 'receipt_unreadable' });
245
+ const refusedRow = counts.refused?.[0];
246
+ if (refusedRow !== undefined)
247
+ return receipt({ kind: 'refused', reason: 'engine_refused', ...detailOf(refusedRow.detail) });
248
+ if (counts.indeterminate.length > 0)
249
+ return receipt({ kind: 'unknown', cause: 'redeem_unsettled' });
250
+ const scope = PROJECT_SCOPE_PREFIX + scopeRoot;
251
+ if (counts.persisted === 1 && counts.deduped === 0)
252
+ return receipt({ kind: 'applied', outcome: 'persisted', rule: storedRule, scope });
253
+ if (counts.deduped === 1 && counts.persisted === 0)
254
+ return receipt({ kind: 'applied', outcome: 'already_live', rule: storedRule, scope });
255
+ return receipt({ kind: 'unknown', cause: 'receipt_unreadable' });
256
+ }
@@ -1,15 +1,16 @@
1
- import { putOwnKey, readOwnDataValue } from '../ownKey.js';
1
+ import { guardedRead, putOwnKey, readOwnDataValue } from '../ownKey.js';
2
2
  import { HitlBridge, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, } from './hitlBridge.js';
3
3
  import { approvalResolutionDetail, approvalResolutionStatusTag, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
4
4
  import { readDecisionRefusalAsk } from './decisionRefusal.js';
5
5
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
6
6
  import { hostLog } from '../host.js';
7
7
  import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
8
- import { INTERRUPT_DENY_REASON, observeCancelByDeny } from './hitlHostSurface.js';
8
+ import { INTERRUPT_DENY_REASON, interruptDenyLifetime, observeCancelByDeny } from './hitlHostSurface.js';
9
9
  import { denyAttributionOf, flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
10
10
  import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
11
11
  import { inheritFrameBinding } from '../frameBinding.js';
12
12
  import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
13
+ import { parkQuestionFrameBits, sourceGenerationNow } from './screenCardCarriers.js';
13
14
  import { usableAskQuestions } from './parkOwnership.js';
14
15
  import { readDecideReceipt } from '../decideReceipt.js';
15
16
  import { decideFailureNote } from '../decideFailureNote.js';
@@ -65,6 +66,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
65
66
  if (!hasQuestionOverlay()) {
66
67
  return { kind: 'not_sent', lane: 'ask', cause: 'card_unavailable', detail: NO_QUESTION_OVERLAY_REASON };
67
68
  }
69
+ const sourceGeneration = sourceGenerationNow(deps);
68
70
  const found = await findPendingForTask(deps.client, taskId, isAskTool, signal ? { signal } : undefined, parkGatedCallId, (row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
69
71
  if (!found.ok)
70
72
  return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
@@ -113,7 +115,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
113
115
  }
114
116
  signal?.addEventListener('abort', onAbort, { once: true });
115
117
  onPresented?.();
116
- publishQuestionFrame({ type: 'question', questionId, questions, ...(questionsFromModelCall ? { _sema_questionsFromModelCall: true } : {}) });
118
+ publishQuestionFrame({ type: 'question', questionId, questions, ...(questionsFromModelCall ? { _sema_questionsFromModelCall: true } : {}), ...parkQuestionFrameBits(taskId, gatedCallId, sourceGeneration) });
117
119
  });
118
120
  if (withdrawn !== undefined) {
119
121
  hostLog('debug', `liveHitlAskWire: question-withdrawn-by-host card=${questionId} verdict=${withdrawn.rowVerdict.kind}`);
@@ -121,7 +123,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
121
123
  }
122
124
  const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
123
125
  if (answer === null) {
124
- observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, undefined, pending), taskId);
126
+ observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, { signal: interruptDenyLifetime() }, pending), taskId);
125
127
  return { kind: 'unsettled', ...at, cause: 'interrupted' };
126
128
  }
127
129
  let declinedReceipt;
@@ -194,6 +196,7 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
194
196
  ...(onPresented ? { onPresented } : {}),
195
197
  ...(deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : {}),
196
198
  ...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
199
+ ...((read) => (typeof read === 'function' ? { engineGeneration: () => read.call(deps) } : {}))(guardedRead(() => deps.engineGeneration)),
197
200
  }, taskId, argsByCallOf(led), ctx.signal, gatedCallId, park.toolName);
198
201
  if (outcome.kind === 'decided' && outcome.decision === 'deny' && outcome.gatedCallId) {
199
202
  led.markDenied(outcome.gatedCallId, denyAttributionOf(outcome.deny));
@@ -44,6 +44,11 @@ export interface CcImportRedeemCounts {
44
44
  }
45
45
  export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'refused' | 'indeterminate';
46
46
  export declare function readCcImportRedeemCounts(body: unknown): CcImportRedeemCounts | null;
47
+ export interface CcImportRefusedReading {
48
+ readonly count: number;
49
+ readonly rows: readonly CcImportRedeemRefused[];
50
+ }
51
+ export declare function refusedRowOf(counts: CcImportRedeemCounts | null | undefined): CcImportRefusedReading | null;
47
52
  export type RulesFailure = {
48
53
  kind: 'lane-unavailable';
49
54
  message: string;
@@ -189,6 +194,17 @@ export declare function classifySkippedReason(reason: string): {
189
194
  code?: string;
190
195
  text: string;
191
196
  };
197
+ export interface CcImportSkippedGroup {
198
+ readonly entries: readonly string[];
199
+ readonly reason: string;
200
+ readonly code?: string;
201
+ }
202
+ export interface CcImportSkippedReading {
203
+ readonly shape: 'absent' | 'malformed' | 'read';
204
+ readonly groups: readonly CcImportSkippedGroup[];
205
+ readonly unreadable: number;
206
+ }
207
+ export declare function ccImportSkippedRowsOf(skipped: {} | null | undefined): CcImportSkippedReading;
192
208
  export type RulePersistOutcome = {
193
209
  state: 'unknown';
194
210
  } | {