@sema-agent/client-core 0.105.0 → 0.107.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 (152) hide show
  1. package/CHANGELOG.md +301 -0
  2. package/README.md +40 -21
  3. package/dist/adapt/arms.js +10 -1
  4. package/dist/adapter/activeRunSelfHeal.d.ts +21 -2
  5. package/dist/adapter/activeRunSelfHeal.js +129 -28
  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 +29 -0
  19. package/dist/checkpointRowVerdict.js +98 -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/displayUntrusted.d.ts +5 -3
  29. package/dist/displayUntrusted.js +50 -41
  30. package/dist/engineAddress.d.ts +3 -0
  31. package/dist/engineAddress.js +25 -0
  32. package/dist/engineDirectory.js +9 -17
  33. package/dist/engineErrorCodes.d.ts +8 -0
  34. package/dist/engineErrorCodes.js +8 -0
  35. package/dist/engineNoticeCodes.d.ts +38 -3
  36. package/dist/engineNoticeCodes.js +167 -11
  37. package/dist/engineRowRef.js +1 -1
  38. package/dist/errorSupplementRoles.js +4 -1
  39. package/dist/eventLeg.d.ts +2 -2
  40. package/dist/eventLeg.js +2 -1
  41. package/dist/executionLaneCapability.js +3 -2
  42. package/dist/executionLaneWords.d.ts +1 -0
  43. package/dist/executionLaneWords.js +3 -0
  44. package/dist/fleet/fleetProjection.js +26 -5
  45. package/dist/generated/engineFactTables.d.ts +2 -1
  46. package/dist/generated/engineFactTables.js +3 -1
  47. package/dist/generated/engineNoticeTables.js +28 -7
  48. package/dist/generated/sdkFactTables.d.ts +3 -0
  49. package/dist/generated/sdkFactTables.js +15 -0
  50. package/dist/generated/settingsSchemaFns.d.ts +11 -0
  51. package/dist/generated/settingsSchemaFns.js +2 -1
  52. package/dist/generated/settingsSchemaSafetyMergeSpec.js +1 -1
  53. package/dist/generated/settingsSchemaSettingsSources.d.ts +1 -1
  54. package/dist/generated/settingsSchemaSettingsSources.js +22 -2
  55. package/dist/hitl/approvalLiveFace.d.ts +17 -0
  56. package/dist/hitl/approvalLiveFace.js +35 -0
  57. package/dist/hitl/approvalQueueRow.d.ts +21 -0
  58. package/dist/hitl/approvalQueueRow.js +69 -0
  59. package/dist/hitl/approvalResolution.d.ts +5 -1
  60. package/dist/hitl/approvalResolution.js +18 -1
  61. package/dist/hitl/approvalsFeed.d.ts +5 -4
  62. package/dist/hitl/approvalsFeed.js +39 -6
  63. package/dist/hitl/askParkRowRouting.d.ts +4 -3
  64. package/dist/hitl/askParkRowRouting.js +27 -3
  65. package/dist/hitl/automaticAnswerVerdict.js +13 -10
  66. package/dist/hitl/crashConverged.d.ts +5 -1
  67. package/dist/hitl/decisionConflict.js +2 -1
  68. package/dist/hitl/decisionRefusal.js +1 -1
  69. package/dist/hitl/decisionSend.d.ts +3 -0
  70. package/dist/hitl/decisionSend.js +32 -6
  71. package/dist/hitl/frameRouter.js +4 -5
  72. package/dist/hitl/hitlBridge.d.ts +6 -2
  73. package/dist/hitl/hitlBridge.js +53 -7
  74. package/dist/hitl/livePendingAsk.d.ts +5 -0
  75. package/dist/hitl/livePendingAsk.js +10 -3
  76. package/dist/hitl/parkOwnership.js +4 -5
  77. package/dist/hitl/parkResolver.js +9 -5
  78. package/dist/hitl/parkRowBirthWait.d.ts +7 -0
  79. package/dist/hitl/parkRowBirthWait.js +1 -1
  80. package/dist/hitl/parkRowDelegation.d.ts +6 -0
  81. package/dist/hitl/parkRowDelegation.js +33 -0
  82. package/dist/hitl/persistedRulesWire.d.ts +15 -3
  83. package/dist/hitl/persistedRulesWire.js +102 -20
  84. package/dist/hitl/planReviewCard.d.ts +2 -0
  85. package/dist/hitl/planReviewCard.js +21 -0
  86. package/dist/hitl/planReviewWire.d.ts +2 -2
  87. package/dist/hitl/planReviewWire.js +105 -38
  88. package/dist/hitl/sessionAllowRoute.d.ts +7 -0
  89. package/dist/hitl/sessionAllowRoute.js +49 -0
  90. package/dist/hitl/sessionPolicyWire.d.ts +1 -7
  91. package/dist/hitl/sessionPolicyWire.js +3 -43
  92. package/dist/hitl/standingRows.js +10 -10
  93. package/dist/hitl/subagentSource.d.ts +1 -0
  94. package/dist/hitl/subagentSource.js +13 -0
  95. package/dist/hitl/toolApprovalWire.d.ts +3 -2
  96. package/dist/hitl/toolApprovalWire.js +100 -44
  97. package/dist/hostSettingsSource.d.ts +4 -1
  98. package/dist/hostSettingsSource.js +11 -6
  99. package/dist/hostTurnSettings.d.ts +4 -5
  100. package/dist/hostTurnSettings.js +80 -19
  101. package/dist/i18n/messages.en.d.ts +41 -12
  102. package/dist/i18n/messages.en.js +42 -13
  103. package/dist/i18n/messages.zh.js +45 -13
  104. package/dist/index.d.ts +22 -2
  105. package/dist/index.js +12 -2
  106. package/dist/injectionLaneNotice.js +2 -1
  107. package/dist/liveInitToolFace.js +0 -1
  108. package/dist/liveModelCatalog.d.ts +15 -0
  109. package/dist/liveModelCatalog.js +40 -0
  110. package/dist/mcpWireCaps.js +1 -1
  111. package/dist/modelCapabilityProbe.js +66 -33
  112. package/dist/noticePresentation.js +2 -0
  113. package/dist/ownKey.d.ts +0 -1
  114. package/dist/ownKey.js +0 -2
  115. package/dist/permissionsDeclaration.d.ts +2 -2
  116. package/dist/permissionsDeclaration.js +12 -14
  117. package/dist/projectSeed.d.ts +39 -0
  118. package/dist/projectSeed.js +80 -0
  119. package/dist/promptDelivery.js +1 -1
  120. package/dist/replayBody.d.ts +7 -6
  121. package/dist/replayBody.js +62 -7
  122. package/dist/request/taskRequest.js +3 -4
  123. package/dist/ruleCompileVerdict.d.ts +0 -2
  124. package/dist/ruleCompileVerdict.js +0 -31
  125. package/dist/ruleRefusalSentence.js +0 -3
  126. package/dist/runReattachPolicy.d.ts +3 -0
  127. package/dist/runReattachPolicy.js +15 -2
  128. package/dist/runTerminal.js +30 -8
  129. package/dist/sandboxWire.d.ts +0 -3
  130. package/dist/sandboxWire.js +0 -9
  131. package/dist/sdkWireTransit.d.ts +1 -1
  132. package/dist/seam.d.ts +14 -7
  133. package/dist/seam.js +12 -0
  134. package/dist/seatContract.d.ts +58 -7
  135. package/dist/seatContract.js +135 -4
  136. package/dist/serverSupportRange.d.ts +18 -0
  137. package/dist/serverSupportRange.js +25 -0
  138. package/dist/sessionEngine.d.ts +156 -0
  139. package/dist/sessionEngine.js +464 -0
  140. package/dist/sessionMap.d.ts +11 -1
  141. package/dist/sessionMap.js +35 -9
  142. package/dist/sessionMemorySync.d.ts +31 -0
  143. package/dist/sessionMemorySync.js +95 -0
  144. package/dist/sessionRowTag.d.ts +4 -0
  145. package/dist/sessionRowTag.js +18 -0
  146. package/dist/settingsLayerAdmission.d.ts +1 -0
  147. package/dist/settingsLayerAdmission.js +4 -1
  148. package/dist/toolResult.js +1 -1
  149. package/dist/wireRefusalCopy.d.ts +6 -1
  150. package/dist/wireRefusalCopy.js +9 -7
  151. package/docs/INTEGRATION-CLIENTS.md +1393 -18
  152. package/package.json +5 -5
@@ -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 { sessionAllowEligible } 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) {
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
  }
@@ -374,6 +386,14 @@ function snapshotAnswer(raw) {
374
386
  }
375
387
  return typeof kind === 'string' ? Object.freeze({ kind }) : undefined;
376
388
  }
389
+ function sessionGatedAnswer(answer, card) {
390
+ if (answer.kind !== 'allow' || answer.allowSession !== true)
391
+ return { answer };
392
+ const why = card === undefined ? 'card_absent' : typeof card === 'object' && card !== null ? sessionAllowEligible(card) : 'card_unreadable';
393
+ if (why === 'given')
394
+ return { answer };
395
+ return { answer: Object.freeze({ ...answer, allowSession: false }), dropped: why };
396
+ }
377
397
  function readRequest(req) {
378
398
  if (typeof req !== 'object' || req === null)
379
399
  return undefined;
@@ -387,17 +407,19 @@ function readRequest(req) {
387
407
  const rawAnswer = guardedRead(() => readOwnDataValue(req, 'answer'));
388
408
  if (typeof rawAnswer !== 'object' || rawAnswer === null)
389
409
  return undefined;
390
- const answer = snapshotAnswer(rawAnswer);
391
- if (answer === undefined)
410
+ const snapshot = snapshotAnswer(rawAnswer);
411
+ if (snapshot === undefined)
392
412
  return undefined;
413
+ const { answer, dropped } = sessionGatedAnswer(snapshot, guardedRead(() => readOwnDataValue(req, 'card')));
414
+ const droppedFact = dropped !== undefined ? { sessionAllowDropped: dropped } : {};
393
415
  if (family === 'approval') {
394
416
  const approvalId = s('approvalId');
395
- return approvalId !== undefined ? { family, approvalId, taskId: s('taskId'), toolCallId: s('toolCallId'), answer } : undefined;
417
+ return approvalId !== undefined ? { family, approvalId, taskId: s('taskId'), toolCallId: s('toolCallId'), answer, ...droppedFact } : undefined;
396
418
  }
397
419
  if (family === 'ask') {
398
420
  const taskId = s('taskId');
399
421
  const askId = s('askId');
400
- return taskId !== undefined && askId !== undefined ? { family, taskId, askId, approvalId: s('approvalId'), toolCallId: s('toolCallId'), answer } : undefined;
422
+ return taskId !== undefined && askId !== undefined ? { family, taskId, askId, approvalId: s('approvalId'), toolCallId: s('toolCallId'), answer, ...droppedFact } : undefined;
401
423
  }
402
424
  return undefined;
403
425
  }
@@ -507,6 +529,10 @@ export async function sendHitlDecision(request, deps) {
507
529
  out = outcomeOfThrown(e, 'elicitation', undefined, signal, ctx.tally, false);
508
530
  }
509
531
  }
532
+ if (req.family !== 'elicitation' && req.sessionAllowDropped !== undefined) {
533
+ hostLog('debug', `decisionSend: session-allow-not-eligible ${req.family}=${req.family === 'ask' ? req.askId : req.approvalId} why=${req.sessionAllowDropped} via=allow-once`);
534
+ out = { ...out, rememberDropped: true };
535
+ }
510
536
  const ambiguous = out.kind === 'unconfirmed' || (out.kind === 'not_applied' && (out.cause === 'parking' || out.cause === 'refused' || out.cause === 'idempotency_crossed'));
511
537
  if (ambiguous && out.cause !== 'aborted') {
512
538
  const gate = req.family === 'elicitation'
@@ -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;
@@ -3,6 +3,7 @@ import type { PermissionResult } from '@sema-agent/agent-types';
3
3
  import { type CapturedLocalCode, type CauseSlotRead } from '../engineWireFor.js';
4
4
  import type { ApprovalsListEnvelope } from './crashConverged.js';
5
5
  import { type SuspendedReopenReading } from './suspendedReopen.js';
6
+ import type { CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
6
7
  export declare const DEFAULT_DENY_REASON = "The user rejected this tool use";
7
8
  export declare const MAX_DENY_REASON_CHARS = 4096;
8
9
  export declare function denyReasonForWire(reason: string | undefined, tag: string): string | undefined;
@@ -26,7 +27,7 @@ export interface AssistantResourceLike {
26
27
  planReview(taskId: string, req: PlanReviewRequest, opts?: {
27
28
  signal?: AbortSignal;
28
29
  }): Promise<AssistantTaskStatus>;
29
- resume(taskId: string, opts?: {
30
+ resume(taskId: string, body?: undefined, opts?: {
30
31
  signal?: AbortSignal;
31
32
  }): Promise<AssistantTaskStatus>;
32
33
  }
@@ -132,7 +133,10 @@ export type FindPendingOutcome = {
132
133
  } | {
133
134
  ok: false;
134
135
  reason: string;
135
- code?: 'no_pending' | 'wrong_gate';
136
+ code?: 'no_pending' | 'wrong_gate' | 'row_unresumable';
137
+ rowVerdict?: Exclude<CheckpointRowVerdictReading, {
138
+ readonly kind: 'ok';
139
+ }>;
136
140
  };
137
141
  export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
138
142
  signal?: AbortSignal;
@@ -10,6 +10,9 @@ 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';
14
+ import { checkpointRowBarOf, checkpointRowBarSentence } from '../checkpointRowVerdict.js';
15
+ import { msg, renderMessage } from '../i18n/translate.js';
13
16
  export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
14
17
  export const MAX_DENY_REASON_CHARS = 4096;
15
18
  export function denyReasonForWire(reason, tag) {
@@ -28,6 +31,15 @@ export function denyReasonForWire(reason, tag) {
28
31
  `(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
32
  return cut;
30
33
  }
34
+ function resumeTakesRestatementBody(client) {
35
+ if (typeof Reflect.get(client.approvals, 'decidePeerInbound') === 'function')
36
+ return true;
37
+ return client.assistant.resume.length >= 3;
38
+ }
39
+ const _realClientFitsHitlSlice = true;
40
+ void _realClientFitsHitlSlice;
41
+ const _bindingKeysEchoed = true;
42
+ void _bindingKeysEchoed;
31
43
  export const PLAN_REVIEW_MODE_AFTER_WORDS = Object.freeze(['default', 'acceptEdits']);
32
44
  export function isPlanReviewModeAfter(v) {
33
45
  return v !== undefined && PLAN_REVIEW_MODE_AFTER_WORDS.includes(v);
@@ -258,7 +270,13 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
258
270
  hostLog('debug', `hitlBridge: approvals.list failed: ${t.log}`);
259
271
  return { ok: false, reason: `approvals.list failed: ${t.reason}` };
260
272
  }
261
- const readings = rows.map((r) => ({ r, foreign: askParkForeignGateKind(r) }));
273
+ const readings = rows.flatMap((r) => {
274
+ const sort = checkpointQueueRowSortOf(r);
275
+ if (sort === 'excluded')
276
+ return [];
277
+ const cp = r;
278
+ return [{ r: cp, foreign: sort === 'unreadable' ? QUEUE_ROW_FAMILY_UNREADABLE_GATE : askParkForeignGateKind(cp) }];
279
+ });
262
280
  const decidable = readings.filter((x) => x.foreign === null).map((x) => x.r);
263
281
  const identityRow = callId !== undefined
264
282
  ? decidable.find((r) => r.taskId === taskId && (r.boundCallId === callId || r.toolCallId === callId))
@@ -267,6 +285,9 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
267
285
  ? readings.find((x) => x.r.taskId === taskId && (x.r.boundCallId === callId || x.r.toolCallId === callId) && x.foreign !== null)
268
286
  : undefined;
269
287
  if (identityForeign !== undefined) {
288
+ const identityBar = identityForeign.foreign === QUEUE_ROW_FAMILY_UNREADABLE_GATE ? checkpointRowBarOf(identityForeign.r) : undefined;
289
+ if (identityBar !== undefined)
290
+ return { ok: false, reason: checkpointRowBarSentence(identityBar), code: 'row_unresumable', rowVerdict: identityBar };
270
291
  return {
271
292
  ok: false,
272
293
  reason: `the pending row this park names (call ${callId}) is parked on a '${String(identityForeign.foreign)}' gate — not an approval this leg decides; refusing to decide a different row`,
@@ -278,8 +299,19 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
278
299
  const pending = (identityConflicts ? undefined : identityRow) ??
279
300
  pool.find((r) => r.taskId === taskId && matches(typeof r.toolName === 'string' ? r.toolName : undefined)) ??
280
301
  pool.find((r) => r.taskId === taskId);
281
- if (!pending)
302
+ if (!pending) {
303
+ for (const x of readings) {
304
+ if (x.r.taskId !== taskId || x.foreign !== QUEUE_ROW_FAMILY_UNREADABLE_GATE)
305
+ continue;
306
+ const unreadableBar = checkpointRowBarOf(x.r);
307
+ if (unreadableBar !== undefined)
308
+ return { ok: false, reason: checkpointRowBarSentence(unreadableBar), code: 'row_unresumable', rowVerdict: unreadableBar };
309
+ }
282
310
  return { ok: false, reason: 'no pending checkpoint for this run (resolved/expired?)', code: 'no_pending' };
311
+ }
312
+ const bar = checkpointRowBarOf(pending);
313
+ if (bar !== undefined)
314
+ return { ok: false, reason: checkpointRowBarSentence(bar), code: 'row_unresumable', rowVerdict: bar };
283
315
  return { ok: true, pending, gatedCallId: pending.toolCallId ?? pending.boundCallId ?? undefined };
284
316
  }
285
317
  export class HitlBridge {
@@ -328,18 +360,22 @@ export class HitlBridge {
328
360
  throw new Error(found.reason);
329
361
  }
330
362
  bindingOf(p) {
331
- if (p.boundCallId === undefined || p.boundInputHash === undefined) {
363
+ const boundCallId = readOwnDataValue(p, 'boundCallId');
364
+ const boundInputHash = readOwnDataValue(p, 'boundInputHash');
365
+ if (typeof boundCallId !== 'string' || boundCallId.length === 0 || typeof boundInputHash !== 'string' || boundInputHash.length === 0) {
332
366
  throw new HitlSafetyError(`pending checkpoint ${p.id ?? '<no-id>'} is missing D-1 binding fields ` +
333
- `(boundCallId=${String(p.boundCallId)}, boundInputHash=${String(p.boundInputHash)}) — ` +
334
- 'pre-D-1 workers are no longer supported (server 5.x requires the verbatim two-tuple echo)', 'binding_missing');
367
+ `(boundCallId=${String(boundCallId)}, boundInputHash=${String(boundInputHash)}) — ` +
368
+ `the row itself carries no binding to echo, so ${renderMessage(msg('approval.decide.unboundWayOut'))}`, 'binding_missing');
335
369
  }
336
- return { boundCallId: p.boundCallId, boundInputHash: p.boundInputHash };
370
+ return { boundCallId, boundInputHash };
337
371
  }
338
372
  async decideTool(outcome, toolUseID, opts, preResolvedPending) {
339
373
  const pending = preResolvedPending ?? (await this.pendingForDecide(toolUseID, wireOptsOf(opts)));
340
374
  if (!pending) {
341
375
  throw new HitlSafetyError('no pending checkpoint to decide (resolved/expired under the human) — refetch + re-present', 'no_pending');
342
376
  }
377
+ if (preResolvedPending !== undefined)
378
+ assertPendingRowResumable(preResolvedPending);
343
379
  const decision = outcome.decision === 'approve'
344
380
  ? {
345
381
  decision: 'approve',
@@ -365,6 +401,8 @@ export class HitlBridge {
365
401
  if (!pending) {
366
402
  throw new HitlSafetyError('no pending AskUserQuestion checkpoint to answer — refetch + re-present', 'no_pending');
367
403
  }
404
+ if (preResolvedPending !== undefined)
405
+ assertPendingRowResumable(preResolvedPending);
368
406
  const decision = {
369
407
  decision: 'approve',
370
408
  answer: { answers: answers.map((a) => marshalAnswer(a)) },
@@ -406,7 +444,10 @@ export class HitlBridge {
406
444
  if (kind && kind !== 'resource_limit') {
407
445
  throw new HitlSafetyError(`cannot resume a "${kind}" gate — "continue" must never bypass a human approval; use decide`, 'wrong_gate');
408
446
  }
409
- return this.client.assistant.resume(this.taskId, opts);
447
+ const assistant = this.client.assistant;
448
+ if (resumeTakesRestatementBody(this.client))
449
+ return assistant.resume(this.taskId, undefined, opts);
450
+ return Reflect.apply(assistant.resume, assistant, [this.taskId, opts]);
410
451
  }
411
452
  async decideRaw(sessionId, decision, optsIn) {
412
453
  const opts = wireOptsOf(optsIn);
@@ -520,6 +561,11 @@ export function makeHitlCanUseTool(bridge, prompt) {
520
561
  return decision;
521
562
  };
522
563
  }
564
+ function assertPendingRowResumable(pending) {
565
+ const bar = checkpointRowBarOf(pending);
566
+ if (bar !== undefined)
567
+ throw new Error(checkpointRowBarSentence(bar));
568
+ }
523
569
  function assertAnswersSendable(answers) {
524
570
  if (!Array.isArray(answers) || answers.length === 0) {
525
571
  throw new HitlSafetyError('refusing to send an EMPTY AskUserQuestion answer set — `{answers:[]}` is the wire\'s DENY/NO_HUMAN form, ' +
@@ -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,8 @@
1
1
  import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
2
2
  import { appendOwnElement } from '../ownKey.js';
3
3
  import { readRequiresRealApproval } from './approvalCardReading.js';
4
+ import { pendingRowIsOfSession } from './parkRowDelegation.js';
5
+ import { approvalLiveFaceOf, liveFaceCarried } from './approvalLiveFace.js';
4
6
  import { THROWN_SLOT_UNREADABLE, readThrownSlot } from '../wireFailureShape.js';
5
7
  import { isToolApprovalFrame, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
6
8
  function nonEmptyString(v) {
@@ -61,6 +63,8 @@ export function readLivePendingRows(body) {
61
63
  continue;
62
64
  }
63
65
  seen.add(o.approvalId);
66
+ const opening = openingFrameOf(o.frame);
67
+ const frame = opening !== undefined && opening.approvalId === o.approvalId ? opening : undefined;
64
68
  rows.push({
65
69
  approvalId: o.approvalId,
66
70
  toolName: o.toolName,
@@ -71,7 +75,8 @@ export function readLivePendingRows(body) {
71
75
  ...(o.governanceForced === true ? { governanceForced: true } : {}),
72
76
  ...(o.fromSubagent === true ? { fromSubagent: true } : {}),
73
77
  ...(nonEmptyString(o.originTaskId) ? { originTaskId: o.originTaskId } : {}),
74
- ...((f) => (f !== undefined && f.approvalId === o.approvalId ? { frame: f } : {}))(openingFrameOf(o.frame)),
78
+ ...(frame !== undefined ? { frame } : {}),
79
+ ...(frame === undefined ? liveFaceCarried(approvalLiveFaceOf(o)) : {}),
75
80
  });
76
81
  }
77
82
  if (members.length > 0 && rows.length === 0)
@@ -120,7 +125,8 @@ export function countApprovalsAwaitingDecision(snapshot, filter) {
120
125
  if (snapshot.kind === 'unknown')
121
126
  return { durable: null, live: null, suspendedSubagent: null };
122
127
  const bySession = (rows) => scope.kind === 'session' ? rows.filter(r => r.sessionId === scope.sessionId) : [...rows];
123
- const durable = (snapshot.pendingDropped ?? 0) > 0 ? null : Array.isArray(snapshot.pending) ? bySession(snapshot.pending).length : null;
128
+ const durableOfSession = (rows) => scope.kind === 'session' ? rows.filter(r => pendingRowIsOfSession(r, scope.sessionId)).length : rows.length;
129
+ const durable = (snapshot.pendingDropped ?? 0) > 0 ? null : Array.isArray(snapshot.pending) ? durableOfSession(snapshot.pending) : null;
124
130
  if (!Array.isArray(snapshot.livePending))
125
131
  return { durable, live: null, suspendedSubagent: null };
126
132
  if ((snapshot.livePendingDropped ?? 0) > 0)
@@ -134,9 +140,10 @@ export function livePendingRowToApprovalFrame(row) {
134
140
  approvalId: row.approvalId,
135
141
  toolName: row.toolName,
136
142
  ...(row.fromSubagent === true ? { fromSubagent: true } : {}),
137
- ...(row.originTaskId !== undefined ? { sourceTaskId: row.originTaskId } : {}),
143
+ ...(row.originTaskId !== undefined ? { sourceSessionId: row.originTaskId } : {}),
138
144
  ...(row.governanceForced === true ? { governanceForced: true } : {}),
139
145
  ...(readRequiresRealApproval(row) ? { requiresRealApproval: true } : {}),
146
+ ...liveFaceCarried(approvalLiveFaceOf(row)),
140
147
  };
141
148
  }
142
149
  export function surfaceSuspendedAskAndRespond(row, respond, signal, lane) {
@@ -2,6 +2,7 @@ import { isOwnEngineRun } from '../subagentContentStore.js';
2
2
  import { rowIdTail } from '../workflow.js';
3
3
  import { hostSessionFor } from '../host.js';
4
4
  import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
5
+ import { pendingRowIsOfSession } from './parkRowDelegation.js';
5
6
  export function pendingRowIsOwnedByThisSession(row, deps) {
6
7
  const sessionKey = deps?.sessionKey ?? DEFAULT_SESSION_KEY;
7
8
  const rowTaskId = typeof row.taskId === 'string' ? row.taskId : '';
@@ -14,11 +15,9 @@ export function pendingRowIsOwnedByThisSession(row, deps) {
14
15
  }
15
16
  const currentSessionId = deps?.currentSessionId ?? (() => hostSessionFor(sessionKey)?.currentSessionId());
16
17
  const sessionId = currentSessionId();
17
- const rowSessionId = typeof row.sessionId === 'string' ? row.sessionId : '';
18
- return (typeof sessionId === 'string' &&
19
- sessionId.length > 0 &&
20
- rowSessionId.length > 0 &&
21
- rowSessionId === sessionId);
18
+ if (typeof sessionId !== 'string' || sessionId.length === 0)
19
+ return false;
20
+ return pendingRowIsOfSession(row, sessionId);
22
21
  }
23
22
  export function askQuestionsFromPending(pending) {
24
23
  const input = pending.input;
@@ -12,6 +12,7 @@ import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
12
12
  import { readDecideReceipt } from '../decideReceipt.js';
13
13
  import { decideFailureNote } from '../decideFailureNote.js';
14
14
  import { wireFailureTextsOf } from '../wireFailureShape.js';
15
+ import { msg, renderMessage } from '../i18n/translate.js';
15
16
  export const MAX_GATE_HOPS = 2;
16
17
  export function nextHopBudget(prev, round) {
17
18
  const total = (prev?.total ?? 0) + 1;
@@ -63,7 +64,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
63
64
  }
64
65
  const found = await findPendingForTask(deps.client, taskId, isAskTool, signal ? { signal } : undefined, parkGatedCallId, (row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
65
66
  if (!found.ok)
66
- return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
67
+ return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
67
68
  const { pending, gatedCallId } = found;
68
69
  const at = { lane: 'ask', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
69
70
  const fromArgs = gatedCallId
@@ -284,7 +285,8 @@ export async function resolvePark(park, ctx) {
284
285
  ? 'the approval card was retracted by the host without a decision (nothing was sent; the run stays parked and remains decidable elsewhere)'
285
286
  : failureReasonOf(outcome);
286
287
  const noDecisionSurface = outcome.kind === 'not_sent' && outcome.cause === 'card_unavailable' && outcome.detail !== undefined && isNoDecisionSurfaceReason(outcome.detail);
287
- return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface) };
288
+ const undecidableHere = outcome.kind === 'not_sent' && (outcome.cause === 'row_unresumable' || (outcome.cause === 'safety_stop' && outcome.safetyCode === 'binding_missing'));
289
+ return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface, undecidableHere) };
288
290
  }
289
291
  led.dropHeldForDecidedPark(outcome.gatedCallId, park.gatedCallId);
290
292
  if (outcome.gatedCallId) {
@@ -307,15 +309,17 @@ export async function resolvePark(park, ctx) {
307
309
  ...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
308
310
  };
309
311
  }
310
- function failsoftReasonSentence(park, failedReason, terminal) {
312
+ function failsoftReasonSentence(park, failedReason, terminal, reasonCarriesWayOut = false) {
311
313
  if (terminal !== undefined)
312
314
  return terminal.errorMessage;
313
315
  if (failedReason === undefined)
314
316
  return undefined;
317
+ if (reasonCarriesWayOut)
318
+ return renderMessage(msg('approval.park.stillParked', { gate: park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion', reason: failedReason }));
315
319
  return (`${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
316
320
  ' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.');
317
321
  }
318
- function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false) {
322
+ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false, reasonCarriesWayOut = false) {
319
323
  const events = [
320
324
  ...flushHeldWithInterruptRewrite(ctx.led, {
321
325
  terminal: park.pendingDone,
@@ -323,7 +327,7 @@ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, no
323
327
  gateAbortedByUser,
324
328
  }),
325
329
  ];
326
- const reason = failsoftReasonSentence(park, failedReason, terminal);
330
+ const reason = failsoftReasonSentence(park, failedReason, terminal, reasonCarriesWayOut);
327
331
  if (park.pendingDone) {
328
332
  events.push(reason === undefined || noDecisionSurface ? park.pendingDone : inheritFrameBinding(park.pendingDone, { ...park.pendingDone, _sema_park_failed_reason: reason }));
329
333
  }
@@ -1,3 +1,4 @@
1
+ import type { CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
1
2
  export type ParkRowProbe<Row> = {
2
3
  kind: 'row';
3
4
  row: Row;
@@ -7,6 +8,9 @@ export type ParkRowProbe<Row> = {
7
8
  } | {
8
9
  kind: 'settled';
9
10
  reason: string;
11
+ rowVerdict?: Exclude<CheckpointRowVerdictReading, {
12
+ readonly kind: 'ok';
13
+ }>;
10
14
  };
11
15
  export type ParkRowWaitVerdict<Row> = {
12
16
  kind: 'row';
@@ -23,6 +27,9 @@ export type ParkRowWaitVerdict<Row> = {
23
27
  reason: string;
24
28
  waitedMs: number;
25
29
  probes: number;
30
+ rowVerdict?: Exclude<CheckpointRowVerdictReading, {
31
+ readonly kind: 'ok';
32
+ }>;
26
33
  } | {
27
34
  kind: 'aborted';
28
35
  waitedMs: number;
@@ -103,7 +103,7 @@ export async function waitForParkRowBirth(deps) {
103
103
  if (probed.kind === 'row')
104
104
  return { kind: 'row', row: probed.row, waitedMs: now() - startedAt, probes };
105
105
  if (probed.kind === 'settled') {
106
- return { kind: 'settled', reason: probed.reason, waitedMs: now() - startedAt, probes };
106
+ return { kind: 'settled', reason: probed.reason, waitedMs: now() - startedAt, probes, ...(probed.rowVerdict !== undefined ? { rowVerdict: probed.rowVerdict } : {}) };
107
107
  }
108
108
  lastReason = probed.reason;
109
109
  const remainingMs = budgetMs - (now() - startedAt);
@@ -0,0 +1,6 @@
1
+ export interface ParkRowDelegationLinks {
2
+ readonly parentSessionId?: string;
3
+ readonly rootSessionId?: string;
4
+ }
5
+ export declare function pendingRowIsOfSession(row: object, sessionId: string): boolean;
6
+ export declare function parkRowDelegationLinksOf(row: object | null | undefined): ParkRowDelegationLinks | undefined;
@@ -0,0 +1,33 @@
1
+ import { slotReadOf } from '../wireFailureShape.js';
2
+ export function pendingRowIsOfSession(row, sessionId) {
3
+ if (sessionId.length === 0)
4
+ return false;
5
+ const rowSessionId = typeof row.sessionId === 'string' ? row.sessionId : '';
6
+ if (rowSessionId.length > 0 && rowSessionId === sessionId)
7
+ return true;
8
+ const links = parkRowDelegationLinksOf(row);
9
+ return links !== undefined && (links.parentSessionId === sessionId || links.rootSessionId === sessionId);
10
+ }
11
+ export function parkRowDelegationLinksOf(row) {
12
+ try {
13
+ if (typeof row !== 'object' || row === null)
14
+ return undefined;
15
+ const a = slotReadOf(row, 'agent');
16
+ if (a.presence !== 'present' || typeof a.value !== 'object' || a.value === null)
17
+ return undefined;
18
+ const agent = a.value;
19
+ const link = (key) => {
20
+ const r = slotReadOf(agent, key);
21
+ return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
22
+ };
23
+ const parentSessionId = link('parentSessionId');
24
+ const rootSessionId = link('rootSessionId');
25
+ return Object.freeze({
26
+ ...(parentSessionId !== undefined ? { parentSessionId } : {}),
27
+ ...(rootSessionId !== undefined ? { rootSessionId } : {}),
28
+ });
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ }
@@ -1,4 +1,4 @@
1
- import type { CcImportLayer, CcImportPrepareResult, CcImportRedeemResult, PersistedRule, RuleBehavior, RuleListParams, RuleListResult, RuleRevokeRequest, RemovalLiveness, RuleRevokeResult, RuleWriteRequest, RuleWriteResult } from '@sema-agent/sdk';
1
+ import type { CcImportLayer, CcImportPrepareResult, PersistedRule, RuleBehavior, RuleListParams, RuleListResult, RuleRevokeRequest, RemovalLiveness, RuleRevokeResult, RuleWriteRequest, RuleWriteResult, RulesResource } from '@sema-agent/sdk';
2
2
  import type { UiLanguage } from '../env/uiLanguage.js';
3
3
  export interface RulesFacade {
4
4
  list(params?: RuleListParams, opts?: {
@@ -15,17 +15,29 @@ export interface RulesFacade {
15
15
  }): Promise<CcImportPrepareResult>;
16
16
  ccImportRedeem(ticket: string, opts?: {
17
17
  signal?: AbortSignal;
18
- }): Promise<CcImportRedeemResult>;
18
+ }): ReturnType<RulesResource['ccImportRedeem']>;
19
19
  }
20
20
  export type RulesListFacade = Pick<RulesFacade, 'list'>;
21
21
  export declare function persistedRulesLaneAvailable(baseUrl: string | undefined): boolean;
22
22
  export declare function persistedRulesGovernanceAvailable(baseUrl: string | undefined): boolean;
23
23
  export declare function persistedRulesWriteAvailable(baseUrl: string | undefined): boolean;
24
+ export interface CcImportRedeemIndeterminate {
25
+ readonly key?: CcImportRedeemRuleKey;
26
+ readonly reason?: string;
27
+ readonly detail?: string;
28
+ }
29
+ export interface CcImportRedeemRuleKey {
30
+ readonly behavior: string;
31
+ readonly rule: string;
32
+ readonly scope: string;
33
+ }
24
34
  export interface CcImportRedeemCounts {
25
35
  persisted: number;
26
36
  deduped: number;
37
+ indeterminate: readonly CcImportRedeemIndeterminate[];
27
38
  }
28
- export declare function readCcImportRedeemCounts(result: unknown): CcImportRedeemCounts | null;
39
+ export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'indeterminate';
40
+ export declare function readCcImportRedeemCounts(body: unknown): CcImportRedeemCounts | null;
29
41
  export type RulesFailure = {
30
42
  kind: 'lane-unavailable';
31
43
  message: string;