@sema-agent/client-core 0.104.1 → 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 (175) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/README.md +36 -14
  3. package/dist/adapt/arms.js +23 -6
  4. package/dist/adapter/activeRunSelfHeal.d.ts +14 -2
  5. package/dist/adapter/activeRunSelfHeal.js +90 -12
  6. package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -5
  7. package/dist/adapter/downstream/eventToSdkMessage.js +24 -23
  8. package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
  9. package/dist/adapter/downstream/wiringManifestView.d.ts +7 -3
  10. package/dist/adapter/downstream/wiringManifestView.js +25 -1
  11. package/dist/adapter/runStream.js +8 -5
  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/cloudConfigWireCaps.js +3 -0
  21. package/dist/compensations.js +6 -6
  22. package/dist/coreValuePorts.d.ts +14 -1
  23. package/dist/coreValuePorts.js +14 -0
  24. package/dist/decideFailureNote.js +3 -1
  25. package/dist/decideReceipt.d.ts +9 -0
  26. package/dist/decideReceipt.js +29 -1
  27. package/dist/declared/supportFloorDeclared.js +2 -2
  28. package/dist/deviceRebind.d.ts +7 -0
  29. package/dist/deviceRebind.js +39 -5
  30. package/dist/displayUntrusted.d.ts +4 -2
  31. package/dist/displayUntrusted.js +45 -22
  32. package/dist/engineAddress.d.ts +3 -0
  33. package/dist/engineAddress.js +25 -0
  34. package/dist/engineDirectory.js +9 -17
  35. package/dist/engineErrorCodes.d.ts +8 -0
  36. package/dist/engineErrorCodes.js +8 -0
  37. package/dist/engineGenerationPort.d.ts +11 -2
  38. package/dist/engineGenerationPort.js +63 -9
  39. package/dist/engineHealthFacts.d.ts +5 -0
  40. package/dist/engineHealthFacts.js +11 -0
  41. package/dist/engineNoticeCodes.d.ts +26 -3
  42. package/dist/engineNoticeCodes.js +52 -7
  43. package/dist/engineRowRef.js +1 -1
  44. package/dist/errorSupplementRoles.js +2 -0
  45. package/dist/eventLeg.d.ts +3 -3
  46. package/dist/eventLeg.js +25 -4
  47. package/dist/fleet/fleetProjection.js +26 -5
  48. package/dist/generated/engineFactTables.d.ts +3 -1
  49. package/dist/generated/engineFactTables.js +4 -1
  50. package/dist/generated/engineNoticeTables.js +19 -4
  51. package/dist/generated/sdkFactTables.d.ts +5 -0
  52. package/dist/generated/sdkFactTables.js +24 -0
  53. package/dist/generated/settingsSchemaFns.d.ts +11 -0
  54. package/dist/generated/settingsSchemaFns.js +2 -1
  55. package/dist/generated/settingsSchemaSafetyMergeSpec.js +1 -1
  56. package/dist/generated/settingsSchemaSettingsSources.d.ts +1 -1
  57. package/dist/generated/settingsSchemaSettingsSources.js +22 -2
  58. package/dist/headlessReconnectWire.js +31 -4
  59. package/dist/hitl/approvalLiveFace.d.ts +17 -0
  60. package/dist/hitl/approvalLiveFace.js +35 -0
  61. package/dist/hitl/approvalQueueRow.d.ts +21 -0
  62. package/dist/hitl/approvalQueueRow.js +69 -0
  63. package/dist/hitl/approvalsFeed.d.ts +5 -4
  64. package/dist/hitl/approvalsFeed.js +38 -6
  65. package/dist/hitl/askGateWire.js +26 -2
  66. package/dist/hitl/askParkRowRouting.d.ts +4 -3
  67. package/dist/hitl/askParkRowRouting.js +15 -3
  68. package/dist/hitl/automaticAnswerVerdict.js +13 -10
  69. package/dist/hitl/crashConverged.d.ts +5 -1
  70. package/dist/hitl/decisionConflict.js +2 -1
  71. package/dist/hitl/decisionRefusal.js +1 -1
  72. package/dist/hitl/decisionSend.d.ts +1 -0
  73. package/dist/hitl/decisionSend.js +14 -2
  74. package/dist/hitl/frameRouter.d.ts +4 -0
  75. package/dist/hitl/frameRouter.js +18 -5
  76. package/dist/hitl/gateIdentity.d.ts +1 -0
  77. package/dist/hitl/gateIdentity.js +3 -0
  78. package/dist/hitl/hitlBridge.d.ts +1 -1
  79. package/dist/hitl/hitlBridge.js +26 -5
  80. package/dist/hitl/livePendingAsk.d.ts +5 -0
  81. package/dist/hitl/livePendingAsk.js +6 -1
  82. package/dist/hitl/planReviewCard.d.ts +23 -0
  83. package/dist/hitl/planReviewCard.js +250 -0
  84. package/dist/hitl/planReviewWire.d.ts +39 -14
  85. package/dist/hitl/planReviewWire.js +450 -163
  86. package/dist/hitl/sessionAllowRoute.d.ts +2 -0
  87. package/dist/hitl/sessionAllowRoute.js +7 -0
  88. package/dist/hitl/sessionPolicyWire.d.ts +1 -7
  89. package/dist/hitl/sessionPolicyWire.js +3 -43
  90. package/dist/hitl/subagentSource.d.ts +1 -0
  91. package/dist/hitl/subagentSource.js +13 -0
  92. package/dist/hitl/toolApprovalWire.d.ts +3 -2
  93. package/dist/hitl/toolApprovalWire.js +79 -37
  94. package/dist/hostSettingsSource.d.ts +5 -2
  95. package/dist/hostSettingsSource.js +19 -10
  96. package/dist/hostTurnSettings.d.ts +5 -6
  97. package/dist/hostTurnSettings.js +106 -26
  98. package/dist/i18n/messages.en.d.ts +85 -12
  99. package/dist/i18n/messages.en.js +85 -12
  100. package/dist/i18n/messages.zh.js +88 -12
  101. package/dist/index.d.ts +35 -4
  102. package/dist/index.js +19 -2
  103. package/dist/liveInitToolFace.js +0 -1
  104. package/dist/liveModelCatalog.d.ts +15 -0
  105. package/dist/liveModelCatalog.js +40 -0
  106. package/dist/mcpTransportKindsCapability.d.ts +18 -0
  107. package/dist/mcpTransportKindsCapability.js +75 -0
  108. package/dist/mcpWireCaps.d.ts +4 -1
  109. package/dist/mcpWireCaps.js +25 -14
  110. package/dist/modelCapabilityProbe.js +66 -33
  111. package/dist/notifications.d.ts +9 -0
  112. package/dist/notifications.js +11 -2
  113. package/dist/ownKey.d.ts +4 -1
  114. package/dist/ownKey.js +8 -2
  115. package/dist/permissionModeWriteCapability.d.ts +13 -0
  116. package/dist/permissionModeWriteCapability.js +35 -0
  117. package/dist/permissionsAllowGrantCapability.d.ts +3 -0
  118. package/dist/permissionsAllowGrantCapability.js +26 -3
  119. package/dist/permissionsDeclaration.d.ts +25 -3
  120. package/dist/permissionsDeclaration.js +69 -41
  121. package/dist/planReviewCardFacts.d.ts +116 -0
  122. package/dist/planReviewCardFacts.js +469 -0
  123. package/dist/projectSeed.d.ts +39 -0
  124. package/dist/projectSeed.js +80 -0
  125. package/dist/promptDelivery.js +1 -1
  126. package/dist/reattachWindow.d.ts +8 -1
  127. package/dist/reattachWindow.js +44 -12
  128. package/dist/replayBody.d.ts +7 -6
  129. package/dist/replayBody.js +62 -7
  130. package/dist/request/taskRequest.js +3 -4
  131. package/dist/ruleCompileVerdict.d.ts +0 -2
  132. package/dist/ruleCompileVerdict.js +0 -31
  133. package/dist/ruleRefusalSentence.js +0 -3
  134. package/dist/runFollow.d.ts +30 -0
  135. package/dist/runFollow.js +117 -0
  136. package/dist/runLedgerSnapshot.d.ts +20 -0
  137. package/dist/runLedgerSnapshot.js +272 -0
  138. package/dist/runPermissionModeSwitch.d.ts +57 -0
  139. package/dist/runPermissionModeSwitch.js +135 -0
  140. package/dist/runReattachPolicy.d.ts +16 -5
  141. package/dist/runReattachPolicy.js +72 -24
  142. package/dist/runTerminal.js +30 -8
  143. package/dist/sandboxWire.d.ts +0 -3
  144. package/dist/sandboxWire.js +0 -9
  145. package/dist/seam.d.ts +17 -7
  146. package/dist/seam.js +13 -1
  147. package/dist/seatContract.d.ts +57 -7
  148. package/dist/seatContract.js +133 -3
  149. package/dist/serverSupportRange.d.ts +18 -0
  150. package/dist/serverSupportRange.js +25 -0
  151. package/dist/sessionEngine.d.ts +156 -0
  152. package/dist/sessionEngine.js +464 -0
  153. package/dist/sessionMap.d.ts +11 -1
  154. package/dist/sessionMap.js +35 -9
  155. package/dist/sessionMemoryErase.d.ts +1 -1
  156. package/dist/sessionMemoryErase.js +5 -0
  157. package/dist/sessionMemoryStatus.d.ts +27 -1
  158. package/dist/sessionMemoryStatus.js +117 -0
  159. package/dist/sessionMemorySync.d.ts +31 -0
  160. package/dist/sessionMemorySync.js +95 -0
  161. package/dist/sessionRowTag.d.ts +4 -0
  162. package/dist/sessionRowTag.js +18 -0
  163. package/dist/settingsLayerAdmission.d.ts +1 -0
  164. package/dist/settingsLayerAdmission.js +4 -1
  165. package/dist/sseIdleTriage.d.ts +3 -1
  166. package/dist/sseIdleTriage.js +19 -4
  167. package/dist/subagent/engineSubagentTail.js +2 -0
  168. package/dist/subagentContentStore.d.ts +3 -0
  169. package/dist/subagentContentStore.js +10 -1
  170. package/dist/toolResult.js +1 -1
  171. package/dist/turnFailurePresentation.d.ts +5 -2
  172. package/dist/turnFailurePresentation.js +12 -4
  173. package/dist/wireRefusalCopy.js +2 -6
  174. package/docs/INTEGRATION-CLIENTS.md +1507 -18
  175. package/package.json +5 -5
@@ -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) {
@@ -0,0 +1,23 @@
1
+ import { type PlanReviewCardFacts, type PlanReviewFactsTaken } from '../planReviewCardFacts.js';
2
+ import { type MessageRef } from '../i18n/translate.js';
3
+ import type { MessageKey } from '../i18n/messages.en.js';
4
+ import type { UiLanguage } from '../env/uiLanguage.js';
5
+ export declare function planReviewCardNoteRef(facts: PlanReviewCardFacts): MessageRef | undefined;
6
+ export declare function planReviewUnreadableNoteRef(): MessageRef;
7
+ export declare function planReviewCardNote(facts: PlanReviewCardFacts, lang?: UiLanguage): string | undefined;
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;
11
+ export interface PlanReviewLedgerSource {
12
+ readonly runRow: (taskId: string, signal: AbortSignal) => Promise<object>;
13
+ readonly frames: (taskId: string, signal: AbortSignal) => AsyncIterable<object>;
14
+ }
15
+ export type PlanReviewFactsTaking = {
16
+ readonly kind: 'now';
17
+ readonly taken: PlanReviewFactsTaken;
18
+ } | {
19
+ readonly kind: 'ledger';
20
+ readonly pending: Promise<PlanReviewFactsTaken | undefined>;
21
+ };
22
+ export declare function _resetPlanReviewFactsFlightsForTest(): void;
23
+ export declare function takePlanReviewFacts(sessionKey: string, taskId: string, checkpointId: string | undefined, source: () => PlanReviewLedgerSource | undefined, signal?: AbortSignal): PlanReviewFactsTaking;
@@ -0,0 +1,250 @@
1
+ import { currentPlanReviewGateOf, isPlanReviewClaimFrame, mintPlanReviewCardFacts, openPlanReviewLedgerFold, planGateRowReadOf, planReviewBodyOf, planReviewCardFactsFrom, planReviewLedgerClaimOf, planReviewRunGeneration, beginPlanReviewFactsRead, commitPlanReviewFacts, samePlanClaim, stepPlanReviewLedgerFold, unknownBody, } from '../planReviewCardFacts.js';
2
+ import { msg, renderMessage } from '../i18n/translate.js';
3
+ import { readRunLedgerSnapshot } from '../runLedgerSnapshot.js';
4
+ import { snapshotIsSubFlow, snapshotSegmentIdentity } from '../adapter/types.js';
5
+ import { settleWithinDeadline } from '../abortableSleep.js';
6
+ import { hostLog } from '../host.js';
7
+ export function planReviewCardNoteRef(facts) {
8
+ const body = facts.body;
9
+ switch (body.kind) {
10
+ case 'shown':
11
+ return body.markdown.trim() === '' ? msg('approval.planReview.note.empty') : undefined;
12
+ case 'marked':
13
+ return msg('approval.planReview.note.marked');
14
+ case 'too_large':
15
+ return msg('approval.planReview.note.tooLarge', { length: body.length });
16
+ case 'unknown':
17
+ return msg(body.cause === 'not_carried' ? 'approval.planReview.note.notCarried' : 'approval.planReview.note.unknown');
18
+ }
19
+ }
20
+ export function planReviewUnreadableNoteRef() {
21
+ return msg('approval.planReview.note.unknown');
22
+ }
23
+ export function planReviewCardNote(facts, lang) {
24
+ let ref;
25
+ try {
26
+ const read = planReviewCardFactsFrom(facts);
27
+ ref = read === undefined ? planReviewUnreadableNoteRef() : planReviewCardNoteRef(read);
28
+ }
29
+ catch {
30
+ ref = planReviewUnreadableNoteRef();
31
+ }
32
+ return ref === undefined ? undefined : renderMessage(ref, lang);
33
+ }
34
+ export function planReviewAskAgainKey(facts) {
35
+ const body = facts?.body;
36
+ if (body === undefined)
37
+ return 'approval.planReview.askAgain.unknown';
38
+ switch (body.kind) {
39
+ case 'shown':
40
+ return 'approval.planReview.askAgain.shown';
41
+ case 'marked':
42
+ return 'approval.planReview.askAgain.marked';
43
+ case 'too_large':
44
+ return 'approval.planReview.askAgain.tooLarge';
45
+ case 'unknown':
46
+ return body.cause === 'not_carried' ? 'approval.planReview.askAgain.notCarried' : 'approval.planReview.askAgain.unknown';
47
+ }
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
+ }
70
+ const PLAN_REVIEW_LEDGER_BUDGET_MS = 3_000;
71
+ const planReviewFactsFlights = new Map();
72
+ export function _resetPlanReviewFactsFlightsForTest() {
73
+ planReviewFactsFlights.clear();
74
+ }
75
+ export function takePlanReviewFacts(sessionKey, taskId, checkpointId, source, signal) {
76
+ const now = (facts, generation) => ({ kind: 'now', taken: { sessionKey, taskId, facts, generation } });
77
+ try {
78
+ const g0 = planReviewRunGeneration(sessionKey, taskId);
79
+ const gate = currentPlanReviewGateOf(sessionKey, taskId);
80
+ if (checkpointId !== undefined && gate !== undefined && gate.checkpointId === checkpointId)
81
+ return now(gate.facts, gate.generation);
82
+ const flightKey = `${sessionKey}\u0000${taskId}\u0000${checkpointId ?? ''}`;
83
+ const inFlight = planReviewFactsFlights.get(flightKey);
84
+ if (inFlight !== undefined)
85
+ return { kind: 'ledger', pending: inFlight };
86
+ let src;
87
+ try {
88
+ src = source();
89
+ }
90
+ catch {
91
+ src = undefined;
92
+ }
93
+ if (src === undefined)
94
+ return now(mintPlanReviewCardFacts(taskId, checkpointId, unknownBody('not_observed')), g0);
95
+ const reader = src;
96
+ const started = Date.now();
97
+ const ledger = (budgetMs) => readPlanReviewFactsFromLedger(beginPlanReviewFactsRead(sessionKey, taskId), checkpointId, reader, signal, budgetMs);
98
+ const viaRow = checkpointId === undefined && gate !== undefined
99
+ ? readPlanGateRowWithin(reader, taskId, signal, PLAN_REVIEW_LEDGER_BUDGET_MS).then((row) => {
100
+ const cur = currentPlanReviewGateOf(sessionKey, taskId);
101
+ if (row.ok && row.atPlanGate && row.checkpointId !== undefined && cur !== undefined && cur.checkpointId === row.checkpointId) {
102
+ return { sessionKey, taskId, facts: cur.facts, generation: cur.generation };
103
+ }
104
+ if (!row.ok)
105
+ return { sessionKey, taskId, facts: mintPlanReviewCardFacts(taskId, undefined, unknownBody('unreadable')), generation: g0 };
106
+ return ledger(Math.max(0, PLAN_REVIEW_LEDGER_BUDGET_MS - (Date.now() - started)));
107
+ })
108
+ : undefined;
109
+ const pending = (viaRow ?? ledger(PLAN_REVIEW_LEDGER_BUDGET_MS)).then((taken) => {
110
+ if (planReviewFactsFlights.get(flightKey) === pending)
111
+ planReviewFactsFlights.delete(flightKey);
112
+ return taken;
113
+ }, () => {
114
+ if (planReviewFactsFlights.get(flightKey) === pending)
115
+ planReviewFactsFlights.delete(flightKey);
116
+ return undefined;
117
+ });
118
+ planReviewFactsFlights.set(flightKey, pending);
119
+ return { kind: 'ledger', pending };
120
+ }
121
+ catch {
122
+ return now(mintPlanReviewCardFacts(taskId, checkpointId, unknownBody('unreadable')), Number.NaN);
123
+ }
124
+ }
125
+ const rowUnread = () => ({ ok: false, atPlanGate: false });
126
+ async function readPlanGateRow(source, taskId, signal) {
127
+ try {
128
+ return planGateRowReadOf(await source.runRow(taskId, signal));
129
+ }
130
+ catch {
131
+ return rowUnread();
132
+ }
133
+ }
134
+ async function readPlanGateRowWithin(source, taskId, outer, budgetMs) {
135
+ if (outer?.aborted === true)
136
+ return rowUnread();
137
+ let onOuterAbort;
138
+ try {
139
+ const aborted = new Promise((resolve) => {
140
+ onOuterAbort = () => resolve(rowUnread());
141
+ outer?.addEventListener('abort', onOuterAbort, { once: true });
142
+ });
143
+ const read = settleWithinDeadline(budgetMs, (deadline) => {
144
+ const ac = new AbortController();
145
+ const stop = () => ac.abort();
146
+ deadline.addEventListener('abort', stop, { once: true });
147
+ outer?.addEventListener('abort', stop, { once: true });
148
+ return readPlanGateRow(source, taskId, ac.signal).finally(() => {
149
+ deadline.removeEventListener('abort', stop);
150
+ outer?.removeEventListener('abort', stop);
151
+ });
152
+ }).then((settled) => (settled.kind === 'value' ? settled.value : rowUnread()));
153
+ return await Promise.race([read, aborted]);
154
+ }
155
+ catch {
156
+ return rowUnread();
157
+ }
158
+ finally {
159
+ if (onOuterAbort !== undefined)
160
+ outer?.removeEventListener('abort', onOuterAbort);
161
+ }
162
+ }
163
+ function foldLedgerFrame(fold, ev) {
164
+ if (typeof ev !== 'object' || ev === null)
165
+ return fold;
166
+ const snap = snapshotSegmentIdentity(ev);
167
+ return stepPlanReviewLedgerFold(fold, ev, snap === null || snapshotIsSubFlow(snap));
168
+ }
169
+ async function readPlanReviewFactsFromLedger(ticket, given, source, signal, budgetMs) {
170
+ const { sessionKey, taskId } = ticket;
171
+ const reads = [];
172
+ let claim;
173
+ let highWater;
174
+ try {
175
+ const snap = await readRunLedgerSnapshot(taskId, undefined, {
176
+ isParked: async (id, s) => {
177
+ const r = await readPlanGateRow(source, id, s);
178
+ reads.push(r);
179
+ const first = reads[0];
180
+ if (!r.ok || !r.atPlanGate || r.checkpointId === undefined)
181
+ return false;
182
+ if (given !== undefined && r.checkpointId !== given)
183
+ return false;
184
+ return reads.length === 1 || (first !== undefined && first.ok && first.checkpointId === r.checkpointId);
185
+ },
186
+ frames: (id, s) => source.frames(id, s),
187
+ budgetMs,
188
+ log: (msg) => {
189
+ hostLog('debug', `planReviewCard: ${msg}`);
190
+ },
191
+ fold: {
192
+ init: openPlanReviewLedgerFold,
193
+ step: foldLedgerFrame,
194
+ strictOrder: true,
195
+ substantive: isPlanReviewClaimFrame,
196
+ agree: (a, b) => {
197
+ const x = planReviewLedgerClaimOf(a);
198
+ const y = planReviewLedgerClaimOf(b);
199
+ return x === undefined || y === undefined ? x === y : samePlanClaim(x, y);
200
+ },
201
+ },
202
+ }, signal);
203
+ if (snap !== null && snap.folded !== undefined) {
204
+ claim = planReviewLedgerClaimOf(snap.folded);
205
+ if (claim !== undefined)
206
+ highWater = snap.highWater;
207
+ }
208
+ }
209
+ catch {
210
+ claim = undefined;
211
+ }
212
+ const first = reads[0];
213
+ const firstGate = first !== undefined && first.ok && first.atPlanGate ? first.checkpointId : undefined;
214
+ let facts;
215
+ if (claim !== undefined && firstGate !== undefined) {
216
+ facts = mintPlanReviewCardFacts(taskId, firstGate, claim.kind === 'text' ? planReviewBodyOf(claim.text) : unknownBody(claim.cause));
217
+ }
218
+ else {
219
+ const missed = ledgerMissOf(reads, given);
220
+ claim = { kind: 'cause', cause: missed.cause };
221
+ highWater = undefined;
222
+ facts = mintPlanReviewCardFacts(taskId, missed.checkpointId, unknownBody(missed.cause));
223
+ }
224
+ const out = commitPlanReviewFacts(ticket, facts, claim, highWater);
225
+ if (out === undefined) {
226
+ hostLog('debug', `planReviewCard: ledger-read-superseded task=${taskId}`);
227
+ return undefined;
228
+ }
229
+ return { sessionKey, taskId, facts: out, generation: ticket.generation };
230
+ }
231
+ function ledgerMissOf(reads, given) {
232
+ const first = reads[0];
233
+ if (first === undefined || !first.ok)
234
+ return { cause: 'unreadable', ...(given !== undefined ? { checkpointId: given } : {}) };
235
+ if (!first.atPlanGate || first.checkpointId === undefined)
236
+ return { cause: 'gate_unidentified' };
237
+ const asked = given ?? first.checkpointId;
238
+ if (given !== undefined && first.checkpointId !== given)
239
+ return { cause: 'gate_moved', checkpointId: asked };
240
+ const second = reads[1];
241
+ if (second !== undefined) {
242
+ if (!second.ok)
243
+ return { cause: 'unreadable', checkpointId: asked };
244
+ if (!second.atPlanGate || second.checkpointId === undefined)
245
+ return { cause: 'gate_unidentified' };
246
+ if (second.checkpointId !== first.checkpointId)
247
+ return { cause: 'gate_moved', checkpointId: asked };
248
+ }
249
+ return { cause: 'unreadable', checkpointId: asked };
250
+ }
@@ -2,34 +2,52 @@ import { type QuestionAnswer } from '../liveQuestionStore.js';
2
2
  import type { EngineWireInjection } from '../engineWireSdk.js';
3
3
  import { type PlanReviewModeAfter } from './hitlBridge.js';
4
4
  import type { AgentClient } from '@sema-agent/sdk';
5
+ import { isPlanReviewPark, type PlanReviewCardFacts } from '../planReviewCardFacts.js';
6
+ import type { UiLanguage } from '../env/uiLanguage.js';
5
7
  import type { ReopenCardVerdict } from '../adapter/activeRunSelfHeal.js';
6
8
  export declare const PLAN_REVIEW_APPROVE_LABEL = "Yes, approve and run the plan";
7
9
  export declare const PLAN_REVIEW_REJECT_LABEL = "No, reject it (keep planning)";
8
10
  export declare const PLAN_REVIEW_APPROVE_AUTO_LABEL = "Yes, and auto-accept edits";
9
11
  export declare const PLAN_REVIEW_APPROVE_MANUAL_LABEL = "Yes, and manually approve edits";
10
- export declare function planReviewDecisionFromAnswer(answer: QuestionAnswer | null | undefined, shownLabels?: readonly string[]): 'approve' | 'reject' | 'dismissed';
12
+ export type PlanReviewOptionChoice = 'approve' | 'approve_accept_edits' | 'approve_manual' | 'reject' | 'ask_again';
13
+ export interface PlanReviewOption {
14
+ readonly label: string;
15
+ readonly description: string;
16
+ readonly choice: PlanReviewOptionChoice;
17
+ }
18
+ type PlanReviewNonDecision = 'dismissed' | 'inadmissible';
19
+ export declare function planReviewDecisionFromAnswer(answer: QuestionAnswer | null | undefined, shownLabels?: readonly string[]): 'approve' | 'reject' | 'ask_again' | PlanReviewNonDecision;
11
20
  export type PlanReviewChoice = {
12
21
  decision: 'approve';
13
22
  permissionModeAfter?: PlanReviewModeAfter;
14
23
  } | {
15
24
  decision: 'reject';
16
25
  reason?: string;
26
+ } | {
27
+ decision: 'ask_again';
28
+ reason?: string;
17
29
  };
18
- export declare function planReviewChoiceFromAnswer(answer: QuestionAnswer | null | undefined, shownLabels?: readonly string[]): PlanReviewChoice | 'dismissed';
30
+ export declare function planReviewChoiceFromAnswer(answer: QuestionAnswer | null | undefined, shownLabels?: readonly string[]): PlanReviewChoice | PlanReviewNonDecision;
19
31
  export declare function planReviewCardOptions(offerModeAfter: boolean): Array<{
20
32
  label: string;
21
33
  description: string;
22
34
  }>;
35
+ export declare function planReviewOptionsOf(facts: PlanReviewCardFacts, opts?: {
36
+ readonly submittedInPlanMode?: boolean;
37
+ }): readonly PlanReviewOption[];
38
+ export interface PlanReviewCardView {
39
+ readonly facts?: PlanReviewCardFacts;
40
+ readonly options: readonly PlanReviewOption[];
41
+ readonly note?: string;
42
+ }
43
+ export declare function planReviewCardViewOf(frame: object | null | undefined, lang?: UiLanguage): PlanReviewCardView | undefined;
23
44
  export declare function notePlanReviewAnsweredIfDecisive(questionId: unknown, answer: QuestionAnswer | null | undefined): void;
24
- export declare function isPlanReviewPark(result: unknown): result is {
25
- taskId: string;
26
- sessionId?: string;
27
- };
45
+ export { isPlanReviewPark };
28
46
  export declare function _resetArmedPlanReviewsForTest(): void;
29
47
  export type PlanReviewReopenTrigger = 'user' | 'automatic';
30
48
  export type PlanReviewGateInstanceReader = (taskId: string) => Promise<ReadonlySet<string> | null>;
31
49
  export declare function clearPlanReviewUserDismissals(sessionKey?: string): void;
32
- export type PlanReviewOfferedMode = 'three_choice' | 'two_choice' | 'unknown';
50
+ export type PlanReviewOfferedMode = 'three_choice' | 'two_choice' | 'withheld' | 'unknown';
33
51
  export interface PlanReviewCardLike {
34
52
  readonly questionId?: string;
35
53
  readonly questions?: ReadonlyArray<{
@@ -45,8 +63,8 @@ export interface PlanReviewQuestionIdReading {
45
63
  export declare function planReviewTaskIdFromQuestionId(questionId: {} | null | undefined): PlanReviewQuestionIdReading | undefined;
46
64
  export declare function planReviewOfferedModeOf(card: string | PlanReviewCardLike): PlanReviewOfferedMode;
47
65
  export declare function planReviewShownLabelsOf(questionId: {} | null | undefined): readonly string[] | undefined;
48
- export declare function planReviewAnswerChoiceOf(questionId: {} | null | undefined, answer: QuestionAnswer | null | undefined): PlanReviewChoice | 'dismissed';
49
- export declare function planReviewAnswerDecisionOf(questionId: {} | null | undefined, answer: QuestionAnswer | null | undefined): 'approve' | 'reject' | 'dismissed';
66
+ export declare function planReviewAnswerChoiceOf(questionId: {} | null | undefined, answer: QuestionAnswer | null | undefined): PlanReviewChoice | PlanReviewNonDecision;
67
+ export declare function planReviewAnswerDecisionOf(questionId: {} | null | undefined, answer: QuestionAnswer | null | undefined): 'approve' | 'reject' | 'ask_again' | PlanReviewNonDecision;
50
68
  export interface ArmPlanReviewOptions {
51
69
  submittedInPlanMode?: boolean;
52
70
  readGateInstanceKeys?: PlanReviewGateInstanceReader;
@@ -58,14 +76,14 @@ export type PlanReviewDecisionEffect = 'took_effect' | 'advanced' | 'still_parke
58
76
  export interface PlanReviewOutcomeMeta {
59
77
  taskId: string;
60
78
  dispatchNo: number;
61
- decision: 'approve' | 'reject';
79
+ decision: 'approve' | 'reject' | 'ask_again';
62
80
  effect: PlanReviewDecisionEffect;
63
81
  cause?: 'stored_form_unsupported';
64
82
  }
65
83
  export declare function planReviewOutcomeTail(taskId: string, decision: 'approve' | 'reject', effect: PlanReviewDecisionEffect, cause?: 'stored_form_unsupported'): string;
66
84
  export type PlanReviewWireClient = {
67
85
  assistant: Pick<AgentClient['assistant'], 'planReview'>;
68
- runs: Pick<AgentClient['runs'], 'get'>;
86
+ runs: Pick<AgentClient['runs'], 'get'> & Partial<Pick<AgentClient['runs'], 'events'>>;
69
87
  };
70
88
  export interface PlanReviewWire extends EngineWireInjection<PlanReviewWireClient> {
71
89
  client: PlanReviewWireClient;
@@ -82,7 +100,7 @@ export interface PlanReviewDeliveryOptions {
82
100
  }
83
101
  export interface DecidePlanReviewOptions extends PlanReviewDeliveryOptions {
84
102
  reason?: string;
85
- parkedResult?: unknown;
103
+ card?: PlanReviewCardFacts;
86
104
  }
87
105
  export interface PlanReviewDecisionHandle {
88
106
  settle(): void;
@@ -95,17 +113,18 @@ export declare function notePlanReviewDecisionNotApplied(taskId: string): void;
95
113
  export declare function notePlanReviewGateLeft(taskId: string): void;
96
114
  export declare function latestPlanReviewDecisionMark(taskId: string): number | null;
97
115
  export declare function __resetPlanReviewDecisionLatchForTests(): void;
98
- export declare function decidePlanReview(taskId: string, decision: 'approve' | 'reject', permissionModeAfter?: string, opts?: DecidePlanReviewOptions): Promise<void>;
116
+ export declare function decidePlanReview(taskId: string, decision: 'approve' | 'reject' | 'ask_again', permissionModeAfter?: string, opts?: DecidePlanReviewOptions): Promise<void>;
99
117
  export interface ReopenPlanReviewSharedOpts {
100
118
  sessionKey?: string;
101
119
  presentationReceiptMs: number;
102
120
  trigger?: PlanReviewReopenTrigger;
103
121
  readGateInstanceKeys?: PlanReviewGateInstanceReader;
104
122
  pendingReadStartedAt?: number;
123
+ checkpointId?: string;
105
124
  }
106
125
  export interface ReopenPlanReviewFreshOpts extends ReopenPlanReviewSharedOpts {
107
126
  mintFreshQuestionId?: true;
108
- deliverDecision?: (taskId: string, decision: 'approve' | 'reject', reason?: string) => void | Promise<void>;
127
+ deliverDecision?: (taskId: string, decision: Parameters<typeof decidePlanReview>[1], reason: string | undefined, card: PlanReviewCardFacts) => void | Promise<void>;
109
128
  wire?: PlanReviewWire;
110
129
  onOutcome?: PlanReviewDeliveryOptions['onOutcome'];
111
130
  }
@@ -118,3 +137,9 @@ export interface ReopenPlanReviewReuseOpts extends ReopenPlanReviewSharedOpts {
118
137
  export type ReopenPlanReviewOpts = ReopenPlanReviewFreshOpts | ReopenPlanReviewReuseOpts;
119
138
  export declare function _resetActiveReopenRespondersForTest(): void;
120
139
  export declare function reopenPlanReviewCard(taskId: string, opts: ReopenPlanReviewOpts): Promise<ReopenCardVerdict>;
140
+ export interface ReadPlanReviewCardFactsOptions {
141
+ readonly checkpointId?: string;
142
+ readonly wire?: PlanReviewWire;
143
+ readonly signal?: AbortSignal;
144
+ }
145
+ export declare function readPlanReviewCardFacts(taskId: string, opts?: ReadPlanReviewCardFactsOptions): Promise<PlanReviewCardFacts>;