@sema-agent/client-core 0.106.0 → 0.108.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 (114) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +35 -26
  3. package/dist/adapter/activeRunSelfHeal.d.ts +10 -0
  4. package/dist/adapter/activeRunSelfHeal.js +68 -23
  5. package/dist/checkpointRowVerdict.d.ts +11 -0
  6. package/dist/checkpointRowVerdict.js +35 -0
  7. package/dist/cloudConfigWireCaps.d.ts +10 -0
  8. package/dist/cloudConfigWireCaps.js +33 -0
  9. package/dist/coreValuePorts.d.ts +17 -1
  10. package/dist/coreValuePorts.js +5 -0
  11. package/dist/decideReceipt.js +2 -1
  12. package/dist/displayUntrusted.d.ts +5 -3
  13. package/dist/displayUntrusted.js +50 -41
  14. package/dist/engineErrorCodes.d.ts +5 -0
  15. package/dist/engineErrorCodes.js +5 -0
  16. package/dist/engineNoticeCodes.d.ts +31 -1
  17. package/dist/engineNoticeCodes.js +182 -9
  18. package/dist/engineTargetSeat.js +1 -0
  19. package/dist/errorSupplementRoles.js +4 -1
  20. package/dist/eventLeg.js +1 -1
  21. package/dist/executionLaneCapability.js +3 -2
  22. package/dist/executionLaneWords.d.ts +1 -0
  23. package/dist/executionLaneWords.js +4 -0
  24. package/dist/gateOutcome.d.ts +10 -1
  25. package/dist/gateOutcome.js +48 -1
  26. package/dist/generated/engineFactTables.d.ts +1 -0
  27. package/dist/generated/engineFactTables.js +5 -0
  28. package/dist/generated/engineNoticeTables.js +12 -3
  29. package/dist/generated/settingsSchemaFns.d.ts +4 -2
  30. package/dist/generated/settingsSchemaFns.js +9 -3
  31. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -1
  32. package/dist/generated/settingsSchemaMcpRequestEntry.js +9 -18
  33. package/dist/generated/settingsSchemaReservedMemberName.d.ts +1 -0
  34. package/dist/generated/settingsSchemaReservedMemberName.js +7 -0
  35. package/dist/generated/settingsSchemaTables.d.ts +2 -0
  36. package/dist/generated/settingsSchemaTables.js +10 -0
  37. package/dist/generated/settingsSchemaWireName.d.ts +1 -0
  38. package/dist/generated/settingsSchemaWireName.js +8 -0
  39. package/dist/headlessReconnectWire.js +12 -7
  40. package/dist/hitl/approvalAskers.d.ts +13 -0
  41. package/dist/hitl/approvalAskers.js +79 -0
  42. package/dist/hitl/approvalResolution.d.ts +8 -1
  43. package/dist/hitl/approvalResolution.js +37 -2
  44. package/dist/hitl/approvalsFeed.js +5 -13
  45. package/dist/hitl/askParkRowRouting.js +17 -1
  46. package/dist/hitl/decisionRefusal.d.ts +1 -0
  47. package/dist/hitl/decisionRefusal.js +5 -2
  48. package/dist/hitl/decisionSend.d.ts +2 -0
  49. package/dist/hitl/decisionSend.js +22 -12
  50. package/dist/hitl/frameRouter.d.ts +3 -2
  51. package/dist/hitl/frameRouter.js +28 -6
  52. package/dist/hitl/gateLedger.d.ts +2 -0
  53. package/dist/hitl/gateLedger.js +3 -1
  54. package/dist/hitl/hitlBridge.d.ts +5 -1
  55. package/dist/hitl/hitlBridge.js +26 -2
  56. package/dist/hitl/livePendingAsk.js +4 -2
  57. package/dist/hitl/parkOwnership.js +4 -5
  58. package/dist/hitl/parkResolver.js +34 -21
  59. package/dist/hitl/parkRowBirthWait.d.ts +9 -0
  60. package/dist/hitl/parkRowBirthWait.js +8 -1
  61. package/dist/hitl/parkRowDelegation.d.ts +6 -0
  62. package/dist/hitl/parkRowDelegation.js +33 -0
  63. package/dist/hitl/persistedRulesWire.d.ts +19 -3
  64. package/dist/hitl/persistedRulesWire.js +118 -20
  65. package/dist/hitl/planReviewWire.js +16 -18
  66. package/dist/hitl/policyDenyCause.d.ts +3 -0
  67. package/dist/hitl/policyDenyCause.js +16 -0
  68. package/dist/hitl/sessionAllowRoute.d.ts +5 -0
  69. package/dist/hitl/sessionAllowRoute.js +42 -0
  70. package/dist/hitl/sessionPolicyDeliverable.js +1 -1
  71. package/dist/hitl/standingRows.js +10 -10
  72. package/dist/hitl/toolApprovalWire.d.ts +8 -2
  73. package/dist/hitl/toolApprovalWire.js +44 -11
  74. package/dist/hooksReaderGenerations.d.ts +1 -0
  75. package/dist/hooksReaderGenerations.js +12 -0
  76. package/dist/hooksWireCaps.js +8 -4
  77. package/dist/i18n/messages.en.d.ts +43 -0
  78. package/dist/i18n/messages.en.js +47 -4
  79. package/dist/i18n/messages.zh.js +53 -4
  80. package/dist/index.d.ts +7 -1
  81. package/dist/index.js +5 -1
  82. package/dist/injectionLaneNotice.js +2 -1
  83. package/dist/liveModelCatalog.d.ts +4 -2
  84. package/dist/liveModelCatalog.js +164 -26
  85. package/dist/model/catalogDefault.d.ts +25 -0
  86. package/dist/model/catalogDefault.js +44 -0
  87. package/dist/noticePresentation.js +2 -0
  88. package/dist/permissionWireCaps.d.ts +1 -0
  89. package/dist/permissionWireCaps.js +11 -0
  90. package/dist/pluginHooksWire.d.ts +5 -3
  91. package/dist/pluginHooksWire.js +116 -7
  92. package/dist/queuedDelivery.d.ts +1 -0
  93. package/dist/queuedDelivery.js +12 -0
  94. package/dist/resultFrameKeys.js +4 -2
  95. package/dist/resumeRefusalCopy.js +15 -8
  96. package/dist/runPermissionModeSwitch.d.ts +2 -1
  97. package/dist/runPermissionModeSwitch.js +16 -6
  98. package/dist/sdkWireTransit.d.ts +1 -1
  99. package/dist/seatContract.d.ts +64 -10
  100. package/dist/seatContract.js +167 -12
  101. package/dist/sessionEngine.d.ts +2 -1
  102. package/dist/sessionEngine.js +5 -0
  103. package/dist/settingsHooksSalvage.d.ts +5 -0
  104. package/dist/settingsHooksSalvage.js +54 -0
  105. package/dist/subagentContentStore.js +1 -1
  106. package/dist/supportFloor.js +2 -0
  107. package/dist/taskNotificationAdmission.d.ts +19 -0
  108. package/dist/taskNotificationAdmission.js +84 -0
  109. package/dist/toolResult.js +2 -1
  110. package/dist/webSearchWireCaps.js +9 -4
  111. package/dist/wireRefusalCopy.d.ts +7 -1
  112. package/dist/wireRefusalCopy.js +7 -1
  113. package/docs/INTEGRATION-CLIENTS.md +1189 -18
  114. package/package.json +5 -5
@@ -13,6 +13,8 @@ import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from
13
13
  import { liveCardHoldOf, noteEngineClosedApproval, settleEngineClosedApproval } from './approvalHolds.js';
14
14
  import { approvalCompleteParked } from './approvalCompleteParked.js';
15
15
  import { liveFrameCallKey } from './gateIdentity.js';
16
+ import { readPolicyDenyCause } from './policyDenyCause.js';
17
+ import { msg, renderMessage } from '../i18n/translate.js';
16
18
  import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
17
19
  import { replayedApprovalClosesCardFrame, replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
18
20
  import { settleWithinDeadline } from '../abortableSleep.js';
@@ -64,10 +66,33 @@ export function policyDenyReason(facts) {
64
66
  function denyOutputForRender(attribution) {
65
67
  if (attribution?.settledBy !== 'policy')
66
68
  return HITL_REJECT_MESSAGE;
69
+ const head = policyDenyHeadFor(readPolicyDenyCause(attribution));
67
70
  const reason = attribution.reason;
68
71
  return reason !== undefined && reason.trim() !== ''
69
- ? `${HITL_POLICY_DENY_MESSAGE}\n${reason}`
70
- : HITL_POLICY_DENY_MESSAGE;
72
+ ? `${head}\n${reason}`
73
+ : head;
74
+ }
75
+ function policyDenyHeadFor(cause) {
76
+ const tail = msg('approval.policyDeny.tail');
77
+ switch (cause) {
78
+ case 'dont_ask':
79
+ return renderMessage(msg('approval.policyDeny.dontAsk', { tail }));
80
+ case 'deny_rule':
81
+ return renderMessage(msg('approval.policyDeny.denyRule', { tail }));
82
+ case 'engine_floor':
83
+ return renderMessage(msg('approval.policyDeny.engineFloor', { tail }));
84
+ case 'no_approval_ui':
85
+ case undefined:
86
+ return HITL_POLICY_DENY_MESSAGE;
87
+ }
88
+ }
89
+ export function denyAttributionOf(deny) {
90
+ const policyCause = readPolicyDenyCause(deny);
91
+ return {
92
+ ...(deny?.settledBy !== undefined ? { settledBy: deny.settledBy } : {}),
93
+ ...(deny?.reason !== undefined ? { reason: deny.reason } : {}),
94
+ ...(policyCause !== undefined ? { policyCause } : {}),
95
+ };
71
96
  }
72
97
  function denialKindForTranscript(attribution) {
73
98
  return ccToolDenialKindForSettledBy(attribution?.settledBy);
@@ -354,10 +379,7 @@ function settleLiveCardOutcome(ev, outcome, ctx, fromSubagent, gatedCallId) {
354
379
  invokeHostCallbackIsolated('liveHitlAskWire: onToolApprovalOutcome', () => deps.onToolApprovalOutcome?.(ev, outcome));
355
380
  }
356
381
  if (outcome.kind === 'decided' && outcome.decision === 'deny' && !fromSubagent) {
357
- const attribution = {
358
- ...(outcome.deny?.settledBy !== undefined ? { settledBy: outcome.deny.settledBy } : {}),
359
- ...(outcome.deny?.reason !== undefined ? { reason: outcome.deny.reason } : {}),
360
- };
382
+ const attribution = denyAttributionOf(outcome.deny);
361
383
  if (gatedCallId !== undefined)
362
384
  led.markDenied(gatedCallId, attribution);
363
385
  else
@@ -1,9 +1,11 @@
1
1
  import type { AgentEvent } from '@sema-agent/sdk';
2
+ import { type PolicyDenyCause } from './policyDenyCause.js';
2
3
  export declare const SEMA_COLLATERAL_ABORT_KEY = "_sema_collateral_abort";
3
4
  export declare const SEMA_DENIAL_KIND_KEY = "_sema_denial_kind";
4
5
  export interface GateDenyAttribution {
5
6
  settledBy?: 'human' | 'policy';
6
7
  reason?: string;
8
+ policyCause?: PolicyDenyCause;
7
9
  }
8
10
  export interface AskAnsweredOutput {
9
11
  type: 'ask-user-question';
@@ -1,5 +1,6 @@
1
1
  import { parkedToolEndRoleOf } from '../engineRowRef.js';
2
2
  import { inheritFrameBinding } from '../frameBinding.js';
3
+ import { readPolicyDenyCause } from './policyDenyCause.js';
3
4
  export const SEMA_COLLATERAL_ABORT_KEY = '_sema_collateral_abort';
4
5
  export const SEMA_DENIAL_KIND_KEY = '_sema_denial_kind';
5
6
  export function durableSeqOf(ev) {
@@ -27,9 +28,10 @@ function normalizeDenyAttribution(a) {
27
28
  return undefined;
28
29
  const settledBy = a.settledBy === 'human' || a.settledBy === 'policy' ? a.settledBy : undefined;
29
30
  const reason = typeof a.reason === 'string' && a.reason.trim() !== '' ? a.reason : undefined;
31
+ const policyCause = settledBy === 'policy' ? readPolicyDenyCause(a) : undefined;
30
32
  if (settledBy === undefined && reason === undefined)
31
33
  return undefined;
32
- return { ...(settledBy !== undefined ? { settledBy } : {}), ...(reason !== undefined ? { reason } : {}) };
34
+ return { ...(settledBy !== undefined ? { settledBy } : {}), ...(reason !== undefined ? { reason } : {}), ...(policyCause !== undefined ? { policyCause } : {}) };
33
35
  }
34
36
  export function createGateLedger() {
35
37
  const startedCalls = new Set();
@@ -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;
@@ -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;
@@ -11,6 +11,7 @@ import { suspendedReopenOf } from './suspendedReopen.js';
11
11
  import { closeDecisionOp, decisionLedgerKeysOf, openDecisionOp } from './decisionLedger.js';
12
12
  import { askParkBlockGeneration, noteAskParkDecideSettled } from './askParkAutoReopenBlock.js';
13
13
  import { checkpointQueueRowSortOf, QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
14
+ import { checkpointDeadlineStateOf, checkpointRowBarOf, checkpointRowBarSentence, unboundWayOutContent } from '../checkpointRowVerdict.js';
14
15
  export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
15
16
  export const MAX_DENY_REASON_CHARS = 4096;
16
17
  export function denyReasonForWire(reason, tag) {
@@ -283,6 +284,9 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
283
284
  ? readings.find((x) => x.r.taskId === taskId && (x.r.boundCallId === callId || x.r.toolCallId === callId) && x.foreign !== null)
284
285
  : undefined;
285
286
  if (identityForeign !== undefined) {
287
+ const identityBar = identityForeign.foreign === QUEUE_ROW_FAMILY_UNREADABLE_GATE ? checkpointRowBarOf(identityForeign.r) : undefined;
288
+ if (identityBar !== undefined)
289
+ return { ok: false, reason: checkpointRowBarSentence(identityBar), code: 'row_unresumable', rowVerdict: identityBar };
286
290
  return {
287
291
  ok: false,
288
292
  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`,
@@ -294,8 +298,19 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
294
298
  const pending = (identityConflicts ? undefined : identityRow) ??
295
299
  pool.find((r) => r.taskId === taskId && matches(typeof r.toolName === 'string' ? r.toolName : undefined)) ??
296
300
  pool.find((r) => r.taskId === taskId);
297
- if (!pending)
301
+ if (!pending) {
302
+ for (const x of readings) {
303
+ if (x.r.taskId !== taskId || x.foreign !== QUEUE_ROW_FAMILY_UNREADABLE_GATE)
304
+ continue;
305
+ const unreadableBar = checkpointRowBarOf(x.r);
306
+ if (unreadableBar !== undefined)
307
+ return { ok: false, reason: checkpointRowBarSentence(unreadableBar), code: 'row_unresumable', rowVerdict: unreadableBar };
308
+ }
298
309
  return { ok: false, reason: 'no pending checkpoint for this run (resolved/expired?)', code: 'no_pending' };
310
+ }
311
+ const bar = checkpointRowBarOf(pending);
312
+ if (bar !== undefined)
313
+ return { ok: false, reason: checkpointRowBarSentence(bar), code: 'row_unresumable', rowVerdict: bar };
299
314
  return { ok: true, pending, gatedCallId: pending.toolCallId ?? pending.boundCallId ?? undefined };
300
315
  }
301
316
  export class HitlBridge {
@@ -349,7 +364,7 @@ export class HitlBridge {
349
364
  if (typeof boundCallId !== 'string' || boundCallId.length === 0 || typeof boundInputHash !== 'string' || boundInputHash.length === 0) {
350
365
  throw new HitlSafetyError(`pending checkpoint ${p.id ?? '<no-id>'} is missing D-1 binding fields ` +
351
366
  `(boundCallId=${String(boundCallId)}, boundInputHash=${String(boundInputHash)}) — ` +
352
- 'pre-D-1 workers are no longer supported (server 5.x requires the verbatim two-tuple echo)', 'binding_missing');
367
+ `the row itself carries no binding to echo, so ${unboundWayOutContent(checkpointDeadlineStateOf(p))}`, 'binding_missing');
353
368
  }
354
369
  return { boundCallId, boundInputHash };
355
370
  }
@@ -358,6 +373,8 @@ export class HitlBridge {
358
373
  if (!pending) {
359
374
  throw new HitlSafetyError('no pending checkpoint to decide (resolved/expired under the human) — refetch + re-present', 'no_pending');
360
375
  }
376
+ if (preResolvedPending !== undefined)
377
+ assertPendingRowResumable(preResolvedPending);
361
378
  const decision = outcome.decision === 'approve'
362
379
  ? {
363
380
  decision: 'approve',
@@ -383,6 +400,8 @@ export class HitlBridge {
383
400
  if (!pending) {
384
401
  throw new HitlSafetyError('no pending AskUserQuestion checkpoint to answer — refetch + re-present', 'no_pending');
385
402
  }
403
+ if (preResolvedPending !== undefined)
404
+ assertPendingRowResumable(preResolvedPending);
386
405
  const decision = {
387
406
  decision: 'approve',
388
407
  answer: { answers: answers.map((a) => marshalAnswer(a)) },
@@ -541,6 +560,11 @@ export function makeHitlCanUseTool(bridge, prompt) {
541
560
  return decision;
542
561
  };
543
562
  }
563
+ function assertPendingRowResumable(pending) {
564
+ const bar = checkpointRowBarOf(pending);
565
+ if (bar !== undefined)
566
+ throw new Error(checkpointRowBarSentence(bar));
567
+ }
544
568
  function assertAnswersSendable(answers) {
545
569
  if (!Array.isArray(answers) || answers.length === 0) {
546
570
  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 { 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';
4
5
  import { approvalLiveFaceOf, liveFaceCarried } from './approvalLiveFace.js';
5
6
  import { THROWN_SLOT_UNREADABLE, readThrownSlot } from '../wireFailureShape.js';
6
7
  import { isToolApprovalFrame, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
@@ -124,7 +125,8 @@ export function countApprovalsAwaitingDecision(snapshot, filter) {
124
125
  if (snapshot.kind === 'unknown')
125
126
  return { durable: null, live: null, suspendedSubagent: null };
126
127
  const bySession = (rows) => scope.kind === 'session' ? rows.filter(r => r.sessionId === scope.sessionId) : [...rows];
127
- 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;
128
130
  if (!Array.isArray(snapshot.livePending))
129
131
  return { durable, live: null, suspendedSubagent: null };
130
132
  if ((snapshot.livePendingDropped ?? 0) > 0)
@@ -138,7 +140,7 @@ export function livePendingRowToApprovalFrame(row) {
138
140
  approvalId: row.approvalId,
139
141
  toolName: row.toolName,
140
142
  ...(row.fromSubagent === true ? { fromSubagent: true } : {}),
141
- ...(row.originTaskId !== undefined ? { sourceTaskId: row.originTaskId } : {}),
143
+ ...(row.originTaskId !== undefined ? { sourceSessionId: row.originTaskId } : {}),
142
144
  ...(row.governanceForced === true ? { governanceForced: true } : {}),
143
145
  ...(readRequiresRealApproval(row) ? { requiresRealApproval: true } : {}),
144
146
  ...liveFaceCarried(approvalLiveFaceOf(row)),
@@ -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;
@@ -1,17 +1,20 @@
1
1
  import { putOwnKey, readOwnDataValue } from '../ownKey.js';
2
2
  import { HitlBridge, NO_QUESTION_OVERLAY_REASON, findPendingForTask, isNoDecisionSurfaceReason, } from './hitlBridge.js';
3
- import { approvalResolutionDetail, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
3
+ import { approvalResolutionDetail, approvalResolutionStatusTag, directSafetyStopOf, resolutionOfDecisionFailure } from './approvalResolution.js';
4
4
  import { readDecisionRefusalAsk } from './decisionRefusal.js';
5
5
  import { publishQuestionFrame, registerLocalQuestionResponder, hasQuestionOverlay, } from '../liveQuestionStore.js';
6
6
  import { hostLog } from '../host.js';
7
7
  import { surfaceFsApprovalAndDecide } from './toolApprovalWire.js';
8
8
  import { INTERRUPT_DENY_REASON, observeCancelByDeny } from './hitlHostSurface.js';
9
- import { flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
9
+ import { denyAttributionOf, flushHeldWithInterruptRewrite, isAskTool } from './frameRouter.js';
10
+ import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
10
11
  import { inheritFrameBinding } from '../frameBinding.js';
11
12
  import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
12
13
  import { readDecideReceipt } from '../decideReceipt.js';
13
14
  import { decideFailureNote } from '../decideFailureNote.js';
14
15
  import { wireFailureTextsOf } from '../wireFailureShape.js';
16
+ import { msg, renderMessage } from '../i18n/translate.js';
17
+ import { refusedParkFromError } from '../resumeRefusalCopy.js';
15
18
  export const MAX_GATE_HOPS = 2;
16
19
  export function nextHopBudget(prev, round) {
17
20
  const total = (prev?.total ?? 0) + 1;
@@ -63,7 +66,7 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
63
66
  }
64
67
  const found = await findPendingForTask(deps.client, taskId, isAskTool, signal ? { signal } : undefined, parkGatedCallId, (row) => isAskTool(typeof row.toolName === 'string' ? row.toolName : undefined));
65
68
  if (!found.ok)
66
- return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}) };
69
+ return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
67
70
  const { pending, gatedCallId } = found;
68
71
  const at = { lane: 'ask', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
69
72
  const fromArgs = gatedCallId
@@ -159,6 +162,10 @@ function failureReasonOf(r) {
159
162
  return '';
160
163
  return r.detail ?? approvalResolutionDetail(r) ?? r.cause;
161
164
  }
165
+ function failsoftReasonOf(r) {
166
+ const tag = approvalResolutionStatusTag(r);
167
+ return tag === '' ? failureReasonOf(r) : `${failureReasonOf(r)} ${tag}`;
168
+ }
162
169
  function orchestrationEnd(park, gatedCallId, detail) {
163
170
  return { kind: 'unsettled', lane: park.gate === 'fs' ? 'decide' : 'ask', cause: 'orchestration', ...(gatedCallId !== undefined ? { gatedCallId } : {}), detail };
164
171
  }
@@ -179,10 +186,7 @@ async function surfaceParkGate(park, ctx, gatedCallId, onPresented) {
179
186
  ...(decidedByReading !== undefined ? { approvalDecidedBy: decidedByReading } : {}),
180
187
  }, taskId, argsByCallOf(led), ctx.signal, gatedCallId, park.toolName);
181
188
  if (outcome.kind === 'decided' && outcome.decision === 'deny' && outcome.gatedCallId) {
182
- led.markDenied(outcome.gatedCallId, {
183
- ...(outcome.deny?.settledBy !== undefined ? { settledBy: outcome.deny.settledBy } : {}),
184
- ...(outcome.deny?.reason !== undefined ? { reason: outcome.deny.reason } : {}),
185
- });
189
+ led.markDenied(outcome.gatedCallId, denyAttributionOf(outcome.deny));
186
190
  }
187
191
  return outcome;
188
192
  }
@@ -282,9 +286,9 @@ export async function resolvePark(park, ctx) {
282
286
  ? undefined
283
287
  : outcome.kind === 'not_sent' && outcome.cause === 'retracted'
284
288
  ? 'the approval card was retracted by the host without a decision (nothing was sent; the run stays parked and remains decidable elsewhere)'
285
- : failureReasonOf(outcome);
289
+ : failsoftReasonOf(outcome);
286
290
  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) };
291
+ return { kind: 'failsoft', events: failsoftEvents(park, ctx, interrupted, failedReason, undefined, noDecisionSurface, failsoftWayOutOf(outcome)) };
288
292
  }
289
293
  led.dropHeldForDecidedPark(outcome.gatedCallId, park.gatedCallId);
290
294
  if (outcome.gatedCallId) {
@@ -307,15 +311,27 @@ export async function resolvePark(park, ctx) {
307
311
  ...(handoffTaskId !== undefined ? { handoffTaskId } : {}),
308
312
  };
309
313
  }
310
- function failsoftReasonSentence(park, failedReason, terminal) {
314
+ function failsoftWayOutOf(outcome) {
315
+ if (outcome.kind === 'not_sent' && (outcome.cause === 'row_unresumable' || (outcome.cause === 'safety_stop' && outcome.safetyCode === 'binding_missing')))
316
+ return 'reason';
317
+ if (outcome.kind === 'unsettled' && outcome.cause === 'engine_refused' && refusedParkFromError(outcome.refusal) !== null)
318
+ return 'cancel';
319
+ return 'card';
320
+ }
321
+ function failsoftReasonSentence(park, failedReason, terminal, wayOut = 'card') {
311
322
  if (terminal !== undefined)
312
323
  return terminal.errorMessage;
313
324
  if (failedReason === undefined)
314
325
  return undefined;
326
+ const gate = park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion';
327
+ if (wayOut === 'reason')
328
+ return renderMessage(msg('approval.park.stillParked', { gate, reason: failedReason }));
329
+ if (wayOut === 'cancel')
330
+ return renderMessage(msg('approval.park.refusedNotResumable', { gate, reason: failedReason }));
315
331
  return (`${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
316
332
  ' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.');
317
333
  }
318
- function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false) {
334
+ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false, wayOut = 'card') {
319
335
  const events = [
320
336
  ...flushHeldWithInterruptRewrite(ctx.led, {
321
337
  terminal: park.pendingDone,
@@ -323,7 +339,7 @@ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, no
323
339
  gateAbortedByUser,
324
340
  }),
325
341
  ];
326
- const reason = failsoftReasonSentence(park, failedReason, terminal);
342
+ const reason = failsoftReasonSentence(park, failedReason, terminal, wayOut);
327
343
  if (park.pendingDone) {
328
344
  events.push(reason === undefined || noDecisionSurface ? park.pendingDone : inheritFrameBinding(park.pendingDone, { ...park.pendingDone, _sema_park_failed_reason: reason }));
329
345
  }
@@ -352,15 +368,12 @@ export function stalledTerminal(park, ctx, stalledRounds, lastStallReason, decid
352
368
  return { kind: 'failsoft', events: failsoftEvents(park, ctx, false, reason) };
353
369
  }
354
370
  function notifyParkReattach(ctx, attempt, max, reason) {
355
- const cb = ctx.deps.onParkReattach;
356
- if (typeof cb !== 'function')
357
- return;
358
- try {
359
- cb({ attempt, max, reason });
360
- }
361
- catch (e) {
362
- hostLog('debug', `liveHitlAskWire: onParkReattach threw (${String(e)}) — ignored`);
363
- }
371
+ invokeHostCallbackIsolated('liveHitlAskWire: onParkReattach', () => {
372
+ const cb = ctx.deps.onParkReattach;
373
+ if (typeof cb !== 'function' || ctx.signal?.aborted === true)
374
+ return;
375
+ return cb.call(ctx.deps, { attempt, max, reason });
376
+ });
364
377
  }
365
378
  function transportReasonToken(reason) {
366
379
  const m = /(ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|EAI_AGAIN|TimeoutError|AbortError|fetch failed|network)/i.exec(reason);
@@ -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,10 @@ export type ParkRowProbe<Row> = {
7
8
  } | {
8
9
  kind: 'settled';
9
10
  reason: string;
11
+ rowVerdict?: Exclude<CheckpointRowVerdictReading, {
12
+ readonly kind: 'ok';
13
+ }>;
14
+ rowTaskId?: string;
10
15
  };
11
16
  export type ParkRowWaitVerdict<Row> = {
12
17
  kind: 'row';
@@ -23,6 +28,10 @@ export type ParkRowWaitVerdict<Row> = {
23
28
  reason: string;
24
29
  waitedMs: number;
25
30
  probes: number;
31
+ rowVerdict?: Exclude<CheckpointRowVerdictReading, {
32
+ readonly kind: 'ok';
33
+ }>;
34
+ rowTaskId?: string;
26
35
  } | {
27
36
  kind: 'aborted';
28
37
  waitedMs: number;
@@ -103,7 +103,14 @@ 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 {
107
+ kind: 'settled',
108
+ reason: probed.reason,
109
+ waitedMs: now() - startedAt,
110
+ probes,
111
+ ...(probed.rowVerdict !== undefined ? { rowVerdict: probed.rowVerdict } : {}),
112
+ ...(probed.rowTaskId !== undefined ? { rowTaskId: probed.rowTaskId } : {}),
113
+ };
107
114
  }
108
115
  lastReason = probed.reason;
109
116
  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,33 @@ 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
+ }
34
+ export interface CcImportRedeemRefused {
35
+ readonly key?: CcImportRedeemRuleKey;
36
+ }
24
37
  export interface CcImportRedeemCounts {
25
38
  persisted: number;
26
39
  deduped: number;
40
+ indeterminate: readonly CcImportRedeemIndeterminate[];
41
+ refused?: readonly CcImportRedeemRefused[];
27
42
  }
28
- export declare function readCcImportRedeemCounts(result: unknown): CcImportRedeemCounts | null;
43
+ export type CcImportReceiptOutcome = 'persisted' | 'no-op' | 'refused' | 'indeterminate';
44
+ export declare function readCcImportRedeemCounts(body: unknown): CcImportRedeemCounts | null;
29
45
  export type RulesFailure = {
30
46
  kind: 'lane-unavailable';
31
47
  message: string;