@sema-agent/client-core 0.105.0 → 0.106.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 (132) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/README.md +26 -11
  3. package/dist/adapt/arms.js +10 -1
  4. package/dist/adapter/activeRunSelfHeal.d.ts +13 -2
  5. package/dist/adapter/activeRunSelfHeal.js +80 -7
  6. package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -5
  7. package/dist/adapter/downstream/eventToSdkMessage.js +20 -19
  8. package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
  9. package/dist/adapter/downstream/wiringManifestView.d.ts +1 -3
  10. package/dist/adapter/downstream/wiringManifestView.js +0 -1
  11. package/dist/adapter/runStream.js +3 -4
  12. package/dist/approvalReachWire.d.ts +1 -0
  13. package/dist/approvalReachWire.js +4 -1
  14. package/dist/approvalSeat.d.ts +21 -0
  15. package/dist/approvalSeat.js +73 -0
  16. package/dist/assistantInbox.d.ts +2 -0
  17. package/dist/assistantInbox.js +3 -0
  18. package/dist/checkpointRowVerdict.d.ts +23 -0
  19. package/dist/checkpointRowVerdict.js +89 -0
  20. package/dist/coreValuePorts.d.ts +12 -1
  21. package/dist/coreValuePorts.js +10 -0
  22. package/dist/decideFailureNote.js +3 -1
  23. package/dist/decideReceipt.d.ts +9 -0
  24. package/dist/decideReceipt.js +29 -1
  25. package/dist/declared/supportFloorDeclared.js +2 -2
  26. package/dist/deviceRebind.d.ts +7 -0
  27. package/dist/deviceRebind.js +39 -5
  28. package/dist/engineAddress.d.ts +3 -0
  29. package/dist/engineAddress.js +25 -0
  30. package/dist/engineDirectory.js +9 -17
  31. package/dist/engineErrorCodes.d.ts +8 -0
  32. package/dist/engineErrorCodes.js +8 -0
  33. package/dist/engineNoticeCodes.d.ts +26 -3
  34. package/dist/engineNoticeCodes.js +52 -7
  35. package/dist/engineRowRef.js +1 -1
  36. package/dist/errorSupplementRoles.js +2 -0
  37. package/dist/eventLeg.d.ts +2 -2
  38. package/dist/eventLeg.js +2 -1
  39. package/dist/fleet/fleetProjection.js +26 -5
  40. package/dist/generated/engineFactTables.d.ts +2 -1
  41. package/dist/generated/engineFactTables.js +3 -1
  42. package/dist/generated/engineNoticeTables.js +19 -4
  43. package/dist/generated/sdkFactTables.d.ts +3 -0
  44. package/dist/generated/sdkFactTables.js +15 -0
  45. package/dist/generated/settingsSchemaFns.d.ts +11 -0
  46. package/dist/generated/settingsSchemaFns.js +2 -1
  47. package/dist/generated/settingsSchemaSafetyMergeSpec.js +1 -1
  48. package/dist/generated/settingsSchemaSettingsSources.d.ts +1 -1
  49. package/dist/generated/settingsSchemaSettingsSources.js +22 -2
  50. package/dist/hitl/approvalLiveFace.d.ts +17 -0
  51. package/dist/hitl/approvalLiveFace.js +35 -0
  52. package/dist/hitl/approvalQueueRow.d.ts +21 -0
  53. package/dist/hitl/approvalQueueRow.js +69 -0
  54. package/dist/hitl/approvalsFeed.d.ts +5 -4
  55. package/dist/hitl/approvalsFeed.js +38 -6
  56. package/dist/hitl/askParkRowRouting.d.ts +4 -3
  57. package/dist/hitl/askParkRowRouting.js +15 -3
  58. package/dist/hitl/automaticAnswerVerdict.js +13 -10
  59. package/dist/hitl/crashConverged.d.ts +5 -1
  60. package/dist/hitl/decisionConflict.js +2 -1
  61. package/dist/hitl/decisionRefusal.js +1 -1
  62. package/dist/hitl/decisionSend.d.ts +1 -0
  63. package/dist/hitl/decisionSend.js +14 -2
  64. package/dist/hitl/frameRouter.js +4 -5
  65. package/dist/hitl/hitlBridge.d.ts +1 -1
  66. package/dist/hitl/hitlBridge.js +26 -5
  67. package/dist/hitl/livePendingAsk.d.ts +5 -0
  68. package/dist/hitl/livePendingAsk.js +6 -1
  69. package/dist/hitl/planReviewCard.d.ts +2 -0
  70. package/dist/hitl/planReviewCard.js +21 -0
  71. package/dist/hitl/planReviewWire.d.ts +2 -2
  72. package/dist/hitl/planReviewWire.js +101 -36
  73. package/dist/hitl/sessionAllowRoute.d.ts +2 -0
  74. package/dist/hitl/sessionAllowRoute.js +7 -0
  75. package/dist/hitl/sessionPolicyWire.d.ts +1 -7
  76. package/dist/hitl/sessionPolicyWire.js +3 -43
  77. package/dist/hitl/subagentSource.d.ts +1 -0
  78. package/dist/hitl/subagentSource.js +13 -0
  79. package/dist/hitl/toolApprovalWire.d.ts +3 -2
  80. package/dist/hitl/toolApprovalWire.js +79 -37
  81. package/dist/hostSettingsSource.d.ts +4 -1
  82. package/dist/hostSettingsSource.js +11 -6
  83. package/dist/hostTurnSettings.d.ts +4 -5
  84. package/dist/hostTurnSettings.js +80 -19
  85. package/dist/i18n/messages.en.d.ts +35 -12
  86. package/dist/i18n/messages.en.js +36 -13
  87. package/dist/i18n/messages.zh.js +38 -13
  88. package/dist/index.d.ts +21 -2
  89. package/dist/index.js +11 -2
  90. package/dist/liveInitToolFace.js +0 -1
  91. package/dist/liveModelCatalog.d.ts +15 -0
  92. package/dist/liveModelCatalog.js +40 -0
  93. package/dist/mcpWireCaps.js +1 -1
  94. package/dist/modelCapabilityProbe.js +66 -33
  95. package/dist/ownKey.d.ts +0 -1
  96. package/dist/ownKey.js +0 -2
  97. package/dist/permissionsDeclaration.d.ts +2 -2
  98. package/dist/permissionsDeclaration.js +12 -14
  99. package/dist/projectSeed.d.ts +39 -0
  100. package/dist/projectSeed.js +80 -0
  101. package/dist/promptDelivery.js +1 -1
  102. package/dist/replayBody.d.ts +7 -6
  103. package/dist/replayBody.js +62 -7
  104. package/dist/request/taskRequest.js +3 -4
  105. package/dist/ruleCompileVerdict.d.ts +0 -2
  106. package/dist/ruleCompileVerdict.js +0 -31
  107. package/dist/ruleRefusalSentence.js +0 -3
  108. package/dist/runReattachPolicy.d.ts +3 -0
  109. package/dist/runReattachPolicy.js +15 -2
  110. package/dist/runTerminal.js +30 -8
  111. package/dist/sandboxWire.d.ts +0 -3
  112. package/dist/sandboxWire.js +0 -9
  113. package/dist/seam.d.ts +14 -7
  114. package/dist/seam.js +12 -0
  115. package/dist/seatContract.d.ts +57 -7
  116. package/dist/seatContract.js +132 -3
  117. package/dist/serverSupportRange.d.ts +18 -0
  118. package/dist/serverSupportRange.js +25 -0
  119. package/dist/sessionEngine.d.ts +156 -0
  120. package/dist/sessionEngine.js +464 -0
  121. package/dist/sessionMap.d.ts +11 -1
  122. package/dist/sessionMap.js +35 -9
  123. package/dist/sessionMemorySync.d.ts +31 -0
  124. package/dist/sessionMemorySync.js +95 -0
  125. package/dist/sessionRowTag.d.ts +4 -0
  126. package/dist/sessionRowTag.js +18 -0
  127. package/dist/settingsLayerAdmission.d.ts +1 -0
  128. package/dist/settingsLayerAdmission.js +4 -1
  129. package/dist/toolResult.js +1 -1
  130. package/dist/wireRefusalCopy.js +2 -6
  131. package/docs/INTEGRATION-CLIENTS.md +1022 -16
  132. package/package.json +5 -5
@@ -2,10 +2,17 @@ import { askParkForeignGateKind, durableDecideLegOf } from '../adapter/activeRun
2
2
  import { isAskTool, questionCardVerdict } from './frameRouter.js';
3
3
  import { approvalCallKey } from './gateIdentity.js';
4
4
  import { askQuestionsFromPending, pendingRowIsOwnedByThisSession } from './parkOwnership.js';
5
+ import { approvalQueueRowFamilyOf, checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
5
6
  export const ASK_PARK_ROW_WAIT_MS = 15000;
6
7
  export const ASK_PARK_ROW_POLL_MS = 500;
7
- export function classifyAskParkRows(rows, taskId, deps) {
8
+ export function classifyAskParkRows(queueRows, taskId, deps) {
8
9
  const isOwned = deps?.isOwned ?? ((row) => pendingRowIsOwnedByThisSession(row, deps?.ownership));
10
+ const sorted = queueRows.flatMap((r) => {
11
+ const sort = checkpointQueueRowSortOf(r);
12
+ return sort === 'excluded' ? [] : [{ r: r, unreadable: sort === 'unreadable' }];
13
+ });
14
+ const rows = sorted.map((x) => x.r);
15
+ const familyUnreadable = new Set(sorted.filter((x) => x.unreadable).map((x) => x.r));
9
16
  const own = rows.find(r => r.taskId === taskId);
10
17
  const sole = rows.length === 1 ? rows[0] : undefined;
11
18
  const soleUnowned = sole !== undefined && !isOwned(sole);
@@ -21,7 +28,7 @@ export function classifyAskParkRows(rows, taskId, deps) {
21
28
  : `${String(rows.length)} pending rows but none for task ${taskId} — ambiguous queue, refusing to surface an unrelated row`,
22
29
  };
23
30
  }
24
- const foreignKind = askParkForeignGateKind(pending);
31
+ const foreignKind = familyUnreadable.has(pending) ? QUEUE_ROW_FAMILY_UNREADABLE_GATE : askParkForeignGateKind(pending);
25
32
  if (foreignKind !== null) {
26
33
  return {
27
34
  kind: 'unborn',
@@ -120,7 +127,7 @@ export function pendingRowIsOwnedAndDecidable(row, deps) {
120
127
  const leg = durableDecideLegOf(row);
121
128
  if (leg === 'plan_review')
122
129
  return true;
123
- if (leg !== 'approval' && leg !== 'absent')
130
+ if (leg !== 'approval')
124
131
  return false;
125
132
  return askParkRowArm(row) === 'question' ? askQuestionsFromPending(row) !== null : true;
126
133
  }
@@ -137,6 +144,11 @@ export function countOwnedPendingRows(listed, deps) {
137
144
  if (typeof row !== 'object' || row === null) {
138
145
  throw new Error('approvals.list answered with a pending row that is not an object — the owned pending count is unknown');
139
146
  }
147
+ const family = approvalQueueRowFamilyOf(row);
148
+ if (family === 'unreadable')
149
+ throw new Error('approvals.list answered with a pending row whose family (`kind`) cannot be read — the owned pending count is unknown');
150
+ if (family !== 'checkpoint')
151
+ continue;
140
152
  if (pendingRowIsOwnedAndDecidable(row, deps))
141
153
  n++;
142
154
  }
@@ -125,6 +125,17 @@ function evidenceUnreadable(card) {
125
125
  const nested = evidenceSlotOf(risk.value, 'requiresRealApproval');
126
126
  return nested.kind !== 'data' || typeof nested.value !== 'boolean';
127
127
  }
128
+ function registryUnreadable(card) {
129
+ const slot = evidenceSlotOf(card, 'unreadableEvidence');
130
+ if (slot.kind === 'absent' || (slot.kind === 'data' && slot.value === undefined))
131
+ return false;
132
+ if (slot.kind === 'unreadable')
133
+ return true;
134
+ const list = slot.value;
135
+ if (!Array.isArray(list) || list.some((k) => typeof k !== 'string'))
136
+ return true;
137
+ return list.some((k) => k !== 'rememberRefused' && k !== 'ruleOffers');
138
+ }
128
139
  function readHostAskRuleMatched(facts) {
129
140
  if (typeof facts !== 'object' || facts === null)
130
141
  return undefined;
@@ -137,17 +148,9 @@ export function automaticAnswerVerdict(card, lane, facts) {
137
148
  try {
138
149
  if (typeof card !== 'object' || card === null)
139
150
  return UNKNOWN.card_unreadable;
151
+ const unreadableAtEntry = registryUnreadable(card) || evidenceUnreadable(card) || (retiredAllowRuleShadowKeyPresent(card, 'frame') && !approvalAllowRuleShadowed(card));
140
152
  if (card.cardPayloadUnreadable === true)
141
153
  return UNKNOWN.card_unreadable;
142
- const unreadable = card.unreadableEvidence;
143
- if (unreadable !== undefined) {
144
- if (!Array.isArray(unreadable) || unreadable.some((k) => typeof k !== 'string'))
145
- return UNKNOWN.card_unreadable;
146
- if (unreadable.some((k) => k !== 'rememberRefused' && k !== 'ruleOffers'))
147
- return UNKNOWN.card_unreadable;
148
- }
149
- if (!approvalAllowRuleShadowed(card) && retiredAllowRuleShadowKeyPresent(card, 'frame'))
150
- return UNKNOWN.card_unreadable;
151
154
  const face = lane === 'mode_posture' ? askFaceOfCard(card) : undefined;
152
155
  const origin = readAskOrigin(evidenceView(card, 'origin'));
153
156
  if (origin !== undefined && Object.hasOwn(ORIGIN_IMPLIES_CLASS, origin)) {
@@ -175,7 +178,7 @@ export function automaticAnswerVerdict(card, lane, facts) {
175
178
  return mustAsk('allow_rule_shadow');
176
179
  }
177
180
  }
178
- if (evidenceUnreadable(card))
181
+ if (unreadableAtEntry)
179
182
  return UNKNOWN.card_unreadable;
180
183
  const pair = readAskClassPairState(card);
181
184
  if (pair.state === 'malformed')
@@ -4,8 +4,12 @@ export interface CrashConvergedRowReading extends Readonly<CrashConvergedRow> {
4
4
  readonly needsHumanReason?: 'approved_then_interrupted' | 'pending_unsafe' | 'unstable_row' | 'contradictory';
5
5
  }
6
6
  export declare const isCrashConvergedCause: (v: unknown) => v is CrashConvergedRowReading["cause"];
7
+ export interface ApprovalQueueKindedRow {
8
+ readonly kind: string;
9
+ }
10
+ export type ApprovalQueueRow = PendingCheckpoint | ApprovalQueueKindedRow;
7
11
  export interface ApprovalsListEnvelope {
8
- pending: PendingCheckpoint[];
12
+ pending: ApprovalQueueRow[];
9
13
  livePending?: unknown[];
10
14
  crashConverged?: CrashConvergedRowReading[];
11
15
  }
@@ -1,8 +1,9 @@
1
1
  import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_UNSUPPORTED_VERSION, RESUME_OUTCOME_INVALID } from '../engineErrorCodes.js';
2
2
  import { msg, renderMessage } from '../i18n/translate.js';
3
3
  import { ownDataField } from '../ownKey.js';
4
+ import { SDK_DECIDE_BINDING_KEYS } from '../generated/sdkFactTables.js';
4
5
  import { isSlotCarrier, PROTOCOL_ERROR_NAME, protocolErrorNameReading, readRefusalSlots, readThrownExtraSnapshot } from '../wireFailureShape.js';
5
- const BINDING_SEAT_FIELDS = new Set(['boundCallId', 'boundInputHash']);
6
+ const BINDING_SEAT_FIELDS = new Set(SDK_DECIDE_BINDING_KEYS);
6
7
  const ANSWER_FIELD = 'answer';
7
8
  const REFUSAL_WORD_KEYS = Object.freeze(['field', 'reason']);
8
9
  const GRANT_DIGEST_FIELD = 'boundGrantDigest';
@@ -146,7 +146,7 @@ export function readDecisionRefusalAsk(failure, answer, read) {
146
146
  if (name === 'HitlSafetyError')
147
147
  return facts('not_settled');
148
148
  const code = typeof errorCode === 'string' && errorCode.length > 0 ? errorCode : undefined;
149
- if (code === RESUME_PREFLIGHT_REJECTED && (preflightSettledBitsPresent(carrier) || retriable !== true))
149
+ if (code === RESUME_PREFLIGHT_REJECTED && preflightSettledBitsPresent(carrier))
150
150
  return facts('not_settled', true);
151
151
  if (isCodeClassifiedGateFailure(code, retriable))
152
152
  return facts('not_settled');
@@ -63,6 +63,7 @@ interface HitlDecisionOutcomeBase {
63
63
  readonly fallback?: 'approval';
64
64
  readonly noteDropped?: true;
65
65
  readonly rememberDropped?: true;
66
+ readonly allowSessionCarried?: true;
66
67
  readonly attributionDropped?: true;
67
68
  readonly attributionRenamed?: true;
68
69
  readonly pending?: LiveAskPendingReading;
@@ -10,6 +10,7 @@ import { readDecisionNoteAudit } from './approvalDecisionNoteAudit.js';
10
10
  import { carrierReadOf, deliverWithAttributionLadder, isLocalSafetyStop } from './attributionLadder.js';
11
11
  import { budgetSpent, deliverApprovalDecision, deliveryTiming, isAborted, newSendTally, PARKING_WAIT_CAP_MS, setDeliveryTimingForTests, sleepFor, settledTransportReadingOf, withTransportRetry, deliveryReadingOf, } from './approvalDelivery.js';
12
12
  import { ASK_DECIDED_CODE, classifyDeliveredDecisionRefusal, decisionRefusalClosesOut, notPendingAfterRetry } from './decisionRefusal.js';
13
+ import { sessionAllowCarried } from './sessionAllowRoute.js';
13
14
  import { addDecisionOpKeys, closeDecisionOp, decisionLedgerKeysOf, decisionOpsWithKey, openDecisionOp } from './decisionLedger.js';
14
15
  import { hostHoldIsActive, takeHostHoldLedgerOp, tryHoldApprovalNow } from './approvalHolds.js';
15
16
  import { answerPayloadFor, decisionAttributionKeyOf, isToolApprovalRespondReplayed, readApprovalDenySettledBy, readToolApprovalRespondAck, readToolApprovalRespondParking, } from './toolApprovalWire.js';
@@ -183,6 +184,9 @@ function bodyFacts(answer, payload, rememberUnsupported) {
183
184
  ...(rememberUnsupported && answer.kind === 'allow' && answer.allowSession === true ? { rememberDropped: true } : {}),
184
185
  };
185
186
  }
187
+ function answerGateCannotTake(out) {
188
+ return out.kind === 'not_applied' && (out.cause === 'gone' || out.cause === 'parking' || out.cause === 'lane_disabled');
189
+ }
186
190
  function ownTrue(raw, key) {
187
191
  return typeof raw === 'object' && raw !== null && guardedRead(() => readOwnDataValue(raw, key)) === true;
188
192
  }
@@ -190,6 +194,14 @@ async function sendAskFamily(respondForFallback, decideAsk, req, ctx) {
190
194
  const payload = answerPayloadFor(req.answer, 'respond', { approvalDecisionNoteCapable: ctx.noteCapable });
191
195
  if (payload === undefined)
192
196
  return { family: 'ask', attempts: ctx.tally.attempts, kind: 'not_sent', cause: 'not_an_answer' };
197
+ let fallbackRespond = respondForFallback;
198
+ if (req.answer.kind === 'allow' && req.answer.allowSession === true && respondForFallback !== undefined && req.approvalId !== undefined && sessionAllowCarried('v1', req.approvalId)) {
199
+ const viaAnswerGate = await sendApprovalFamily(respondForFallback, req.approvalId, req.answer, ctx);
200
+ if (!answerGateCannotTake(viaAnswerGate))
201
+ return { ...viaAnswerGate, family: 'ask', allowSessionCarried: true };
202
+ hostLog('debug', `decisionSend: session-allow-fallback ask=${req.askId} cause=${String(viaAnswerGate.cause)} via=ask-decide-once`);
203
+ fallbackRespond = undefined;
204
+ }
193
205
  const verdict = verdictOf(payload.decision) ?? 'deny';
194
206
  const attribution = req.answer.kind === 'deny' ? decisionAttributionKeyOf(ctx.decidedBy, readApprovalDenySettledBy(req.answer)) : {};
195
207
  const body = {
@@ -232,8 +244,8 @@ async function sendAskFamily(respondForFallback, decideAsk, req, ctx) {
232
244
  }
233
245
  const out = outcomeOfThrown(e, 'ask', verdict, ctx.signal, ctx.tally, ctx.noteCapable, settled);
234
246
  if (out.kind === 'not_applied' && out.cause === 'lane_disabled' && code === ASK_DECISION_LANE_DISABLED && req.approvalId !== undefined) {
235
- if (respondForFallback !== undefined && ctx.signal?.aborted !== true) {
236
- const fb = await sendApprovalFamily(respondForFallback, req.approvalId, req.answer, ctx);
247
+ if (fallbackRespond !== undefined && ctx.signal?.aborted !== true) {
248
+ const fb = await sendApprovalFamily(fallbackRespond, req.approvalId, req.answer, ctx);
237
249
  return { ...fb, family: 'ask', fallback: 'approval' };
238
250
  }
239
251
  }
@@ -20,6 +20,7 @@ import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
20
20
  import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
21
21
  import { RUN_CANCELLED_CODE } from '../runCancelContext.js';
22
22
  import { durableDecideLegOf } from '../adapter/activeRunSelfHeal.js';
23
+ import { subagentSourceSessionOf } from './subagentSource.js';
23
24
  export const HITL_REJECT_MESSAGE = REJECT_MESSAGE;
24
25
  export const HITL_POLICY_DENY_MESSAGE = 'Permission for this tool use was denied: it requires interactive approval, and permission prompts are ' +
25
26
  'not available in this session. The action was NOT performed. Do not claim it succeeded, and do not retry ' +
@@ -135,13 +136,11 @@ export function parkGateArmOf(gate) {
135
136
  return null;
136
137
  }
137
138
  const leg = durableDecideLegOf(typeof kind === 'string' ? { gateKind: kind } : null);
138
- if (leg !== 'approval' && leg !== 'absent')
139
+ if (leg !== 'approval')
139
140
  return null;
140
141
  if (questionCardVerdict(gate) ?? isAskToolLoose(toolName))
141
142
  return 'ask';
142
- if (leg === 'approval')
143
- return 'fs';
144
- return isFsOrShellToolName(toolName) ? 'fs' : null;
143
+ return 'fs';
145
144
  }
146
145
  const ENGINE_GATE_PARKED_ERROR_CODE = GATE_PARKED_ERROR_CODE;
147
146
  function isEngineAbortToolEnd(ev) {
@@ -336,7 +335,7 @@ async function routeToolApprovalFrame(ev, ctx) {
336
335
  }
337
336
  }
338
337
  const fromSubagent = isFromSubagent(ev);
339
- hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} tool=${String(ev.toolName)}${fromSubagent ? ` from-subagent=${String(ev.sourceTaskId ?? ev.sourceAgentName ?? 'explicit')}` : ''}`);
338
+ hostLog('debug', `liveHitlAskWire: tool_approval frame ${ev.approvalId} tool=${String(ev.toolName)}${fromSubagent ? ` from-subagent=${String(subagentSourceSessionOf(ev) ?? ev.sourceAgentName ?? 'explicit')}` : ''}`);
340
339
  const frameCallId = !fromSubagent && typeof ev.toolCallId === 'string' && ev.toolCallId !== '' ? ev.toolCallId : undefined;
341
340
  const gatedCallId = fromSubagent ? undefined : frameCallId ?? led.lastPendingFsCall();
342
341
  const argsCallId = frameCallId === undefined ? gatedCallId : typeof ev.toolName === 'string' && toolNameIsFsWrite(ev.toolName) ? frameCallId : undefined;
@@ -26,7 +26,7 @@ export interface AssistantResourceLike {
26
26
  planReview(taskId: string, req: PlanReviewRequest, opts?: {
27
27
  signal?: AbortSignal;
28
28
  }): Promise<AssistantTaskStatus>;
29
- resume(taskId: string, opts?: {
29
+ resume(taskId: string, body?: undefined, opts?: {
30
30
  signal?: AbortSignal;
31
31
  }): Promise<AssistantTaskStatus>;
32
32
  }
@@ -10,6 +10,7 @@ import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_INVALID_OUTCOME, RESUME_POLICY_FO
10
10
  import { suspendedReopenOf } from './suspendedReopen.js';
11
11
  import { closeDecisionOp, decisionLedgerKeysOf, openDecisionOp } from './decisionLedger.js';
12
12
  import { askParkBlockGeneration, noteAskParkDecideSettled } from './askParkAutoReopenBlock.js';
13
+ import { checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
13
14
  export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
14
15
  export const MAX_DENY_REASON_CHARS = 4096;
15
16
  export function denyReasonForWire(reason, tag) {
@@ -28,6 +29,15 @@ export function denyReasonForWire(reason, tag) {
28
29
  `(server caps approval reason at ${MAX_DENY_REASON_CHARS}; over-cap is a 413 reason_too_large that would drop the DECISION, not just the reason)`);
29
30
  return cut;
30
31
  }
32
+ function resumeTakesRestatementBody(client) {
33
+ if (typeof Reflect.get(client.approvals, 'decidePeerInbound') === 'function')
34
+ return true;
35
+ return client.assistant.resume.length >= 3;
36
+ }
37
+ const _realClientFitsHitlSlice = true;
38
+ void _realClientFitsHitlSlice;
39
+ const _bindingKeysEchoed = true;
40
+ void _bindingKeysEchoed;
31
41
  export const PLAN_REVIEW_MODE_AFTER_WORDS = Object.freeze(['default', 'acceptEdits']);
32
42
  export function isPlanReviewModeAfter(v) {
33
43
  return v !== undefined && PLAN_REVIEW_MODE_AFTER_WORDS.includes(v);
@@ -258,7 +268,13 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
258
268
  hostLog('debug', `hitlBridge: approvals.list failed: ${t.log}`);
259
269
  return { ok: false, reason: `approvals.list failed: ${t.reason}` };
260
270
  }
261
- const readings = rows.map((r) => ({ r, foreign: askParkForeignGateKind(r) }));
271
+ const readings = rows.flatMap((r) => {
272
+ const sort = checkpointQueueRowSortOf(r);
273
+ if (sort === 'excluded')
274
+ return [];
275
+ const cp = r;
276
+ return [{ r: cp, foreign: sort === 'unreadable' ? QUEUE_ROW_FAMILY_UNREADABLE_GATE : askParkForeignGateKind(cp) }];
277
+ });
262
278
  const decidable = readings.filter((x) => x.foreign === null).map((x) => x.r);
263
279
  const identityRow = callId !== undefined
264
280
  ? decidable.find((r) => r.taskId === taskId && (r.boundCallId === callId || r.toolCallId === callId))
@@ -328,12 +344,14 @@ export class HitlBridge {
328
344
  throw new Error(found.reason);
329
345
  }
330
346
  bindingOf(p) {
331
- if (p.boundCallId === undefined || p.boundInputHash === undefined) {
347
+ const boundCallId = readOwnDataValue(p, 'boundCallId');
348
+ const boundInputHash = readOwnDataValue(p, 'boundInputHash');
349
+ if (typeof boundCallId !== 'string' || boundCallId.length === 0 || typeof boundInputHash !== 'string' || boundInputHash.length === 0) {
332
350
  throw new HitlSafetyError(`pending checkpoint ${p.id ?? '<no-id>'} is missing D-1 binding fields ` +
333
- `(boundCallId=${String(p.boundCallId)}, boundInputHash=${String(p.boundInputHash)}) — ` +
351
+ `(boundCallId=${String(boundCallId)}, boundInputHash=${String(boundInputHash)}) — ` +
334
352
  'pre-D-1 workers are no longer supported (server 5.x requires the verbatim two-tuple echo)', 'binding_missing');
335
353
  }
336
- return { boundCallId: p.boundCallId, boundInputHash: p.boundInputHash };
354
+ return { boundCallId, boundInputHash };
337
355
  }
338
356
  async decideTool(outcome, toolUseID, opts, preResolvedPending) {
339
357
  const pending = preResolvedPending ?? (await this.pendingForDecide(toolUseID, wireOptsOf(opts)));
@@ -406,7 +424,10 @@ export class HitlBridge {
406
424
  if (kind && kind !== 'resource_limit') {
407
425
  throw new HitlSafetyError(`cannot resume a "${kind}" gate — "continue" must never bypass a human approval; use decide`, 'wrong_gate');
408
426
  }
409
- return this.client.assistant.resume(this.taskId, opts);
427
+ const assistant = this.client.assistant;
428
+ if (resumeTakesRestatementBody(this.client))
429
+ return assistant.resume(this.taskId, undefined, opts);
430
+ return Reflect.apply(assistant.resume, assistant, [this.taskId, opts]);
410
431
  }
411
432
  async decideRaw(sessionId, decision, optsIn) {
412
433
  const opts = wireOptsOf(optsIn);
@@ -1,6 +1,7 @@
1
1
  import { type ApprovalRequestFrame } from '@sema-agent/sdk';
2
2
  import type { ApprovalResolution } from './approvalResolution.js';
3
3
  import type { ApprovalsFeedEmission } from './approvalsFeed.js';
4
+ import type { ApprovalMandateWord } from '../gateVocabulary.js';
4
5
  import { type RespondToolApprovalFn, type ToolApprovalFrame, type ToolApprovalFrameLaneOpts } from './toolApprovalWire.js';
5
6
  export interface LivePendingAskView {
6
7
  approvalId: string;
@@ -13,6 +14,10 @@ export interface LivePendingAskView {
13
14
  fromSubagent?: true;
14
15
  originTaskId?: string;
15
16
  frame?: ToolApprovalFrame | ApprovalRequestFrame;
17
+ mandated?: true;
18
+ mandate?: ApprovalMandateWord;
19
+ origin?: string;
20
+ liveFactsUnknown?: true;
16
21
  }
17
22
  export type LivePendingReading = {
18
23
  kind: 'not_reported';
@@ -1,6 +1,7 @@
1
1
  import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
2
2
  import { appendOwnElement } from '../ownKey.js';
3
3
  import { readRequiresRealApproval } from './approvalCardReading.js';
4
+ import { approvalLiveFaceOf, liveFaceCarried } from './approvalLiveFace.js';
4
5
  import { THROWN_SLOT_UNREADABLE, readThrownSlot } from '../wireFailureShape.js';
5
6
  import { isToolApprovalFrame, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
6
7
  function nonEmptyString(v) {
@@ -61,6 +62,8 @@ export function readLivePendingRows(body) {
61
62
  continue;
62
63
  }
63
64
  seen.add(o.approvalId);
65
+ const opening = openingFrameOf(o.frame);
66
+ const frame = opening !== undefined && opening.approvalId === o.approvalId ? opening : undefined;
64
67
  rows.push({
65
68
  approvalId: o.approvalId,
66
69
  toolName: o.toolName,
@@ -71,7 +74,8 @@ export function readLivePendingRows(body) {
71
74
  ...(o.governanceForced === true ? { governanceForced: true } : {}),
72
75
  ...(o.fromSubagent === true ? { fromSubagent: true } : {}),
73
76
  ...(nonEmptyString(o.originTaskId) ? { originTaskId: o.originTaskId } : {}),
74
- ...((f) => (f !== undefined && f.approvalId === o.approvalId ? { frame: f } : {}))(openingFrameOf(o.frame)),
77
+ ...(frame !== undefined ? { frame } : {}),
78
+ ...(frame === undefined ? liveFaceCarried(approvalLiveFaceOf(o)) : {}),
75
79
  });
76
80
  }
77
81
  if (members.length > 0 && rows.length === 0)
@@ -137,6 +141,7 @@ export function livePendingRowToApprovalFrame(row) {
137
141
  ...(row.originTaskId !== undefined ? { sourceTaskId: row.originTaskId } : {}),
138
142
  ...(row.governanceForced === true ? { governanceForced: true } : {}),
139
143
  ...(readRequiresRealApproval(row) ? { requiresRealApproval: true } : {}),
144
+ ...liveFaceCarried(approvalLiveFaceOf(row)),
140
145
  };
141
146
  }
142
147
  export function surfaceSuspendedAskAndRespond(row, respond, signal, lane) {
@@ -6,6 +6,8 @@ export declare function planReviewCardNoteRef(facts: PlanReviewCardFacts): Messa
6
6
  export declare function planReviewUnreadableNoteRef(): MessageRef;
7
7
  export declare function planReviewCardNote(facts: PlanReviewCardFacts, lang?: UiLanguage): string | undefined;
8
8
  export declare function planReviewAskAgainKey(facts: PlanReviewCardFacts | undefined): MessageKey;
9
+ export type PlanReviewDecisionReason = 'card_missing' | 'card_withholds' | 'gate_moved' | 'gate_untrusted';
10
+ export declare function planReviewDecisionReasonRef(reason: PlanReviewDecisionReason, card?: PlanReviewCardFacts): MessageRef;
9
11
  export interface PlanReviewLedgerSource {
10
12
  readonly runRow: (taskId: string, signal: AbortSignal) => Promise<object>;
11
13
  readonly frames: (taskId: string, signal: AbortSignal) => AsyncIterable<object>;
@@ -46,6 +46,27 @@ export function planReviewAskAgainKey(facts) {
46
46
  return body.cause === 'not_carried' ? 'approval.planReview.askAgain.notCarried' : 'approval.planReview.askAgain.unknown';
47
47
  }
48
48
  }
49
+ export function planReviewDecisionReasonRef(reason, card) {
50
+ switch (reason) {
51
+ case 'card_missing':
52
+ return msg('approval.planReview.decision.cardMissing');
53
+ case 'card_withholds': {
54
+ let ref;
55
+ try {
56
+ const read = card === undefined ? undefined : planReviewCardFactsFrom(card);
57
+ ref = read === undefined ? undefined : planReviewCardNoteRef(read);
58
+ }
59
+ catch {
60
+ ref = undefined;
61
+ }
62
+ return ref ?? planReviewUnreadableNoteRef();
63
+ }
64
+ case 'gate_moved':
65
+ return msg('approval.planReview.decision.gateMoved');
66
+ case 'gate_untrusted':
67
+ return msg('approval.planReview.decision.gateUntrusted');
68
+ }
69
+ }
49
70
  const PLAN_REVIEW_LEDGER_BUDGET_MS = 3_000;
50
71
  const planReviewFactsFlights = new Map();
51
72
  export function _resetPlanReviewFactsFlightsForTest() {
@@ -100,7 +100,7 @@ export interface PlanReviewDeliveryOptions {
100
100
  }
101
101
  export interface DecidePlanReviewOptions extends PlanReviewDeliveryOptions {
102
102
  reason?: string;
103
- parkedResult?: unknown;
103
+ card?: PlanReviewCardFacts;
104
104
  }
105
105
  export interface PlanReviewDecisionHandle {
106
106
  settle(): void;
@@ -124,7 +124,7 @@ export interface ReopenPlanReviewSharedOpts {
124
124
  }
125
125
  export interface ReopenPlanReviewFreshOpts extends ReopenPlanReviewSharedOpts {
126
126
  mintFreshQuestionId?: true;
127
- deliverDecision?: (taskId: string, decision: Parameters<typeof decidePlanReview>[1], reason?: string) => void | Promise<void>;
127
+ deliverDecision?: (taskId: string, decision: Parameters<typeof decidePlanReview>[1], reason: string | undefined, card: PlanReviewCardFacts) => void | Promise<void>;
128
128
  wire?: PlanReviewWire;
129
129
  onOutcome?: PlanReviewDeliveryOptions['onOutcome'];
130
130
  }