@sema-agent/client-core 0.108.0 → 0.110.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 (179) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/README.md +77 -40
  3. package/dist/adapt/arms.js +41 -13
  4. package/dist/adapt/textStream.d.ts +2 -0
  5. package/dist/adapt/textStream.js +37 -16
  6. package/dist/adapter/activeRunSelfHeal.js +1 -1
  7. package/dist/adapter/downstream/eventToSdkMessage.js +11 -5
  8. package/dist/agentSession/engineAgentRegistryRead.js +2 -0
  9. package/dist/bashExitReading.d.ts +12 -0
  10. package/dist/bashExitReading.js +44 -0
  11. package/dist/checkpointRowVerdict.d.ts +1 -1
  12. package/dist/checkpointRowVerdict.js +4 -2
  13. package/dist/compensations.js +1 -1
  14. package/dist/declared/supportFloorDeclared.js +1 -1
  15. package/dist/displayUntrusted.js +132 -14
  16. package/dist/engineAgentAbsence.d.ts +2 -0
  17. package/dist/engineAgentAbsence.js +28 -0
  18. package/dist/engineErrorCodes.d.ts +7 -0
  19. package/dist/engineErrorCodes.js +7 -0
  20. package/dist/engineHealthFacts.d.ts +26 -0
  21. package/dist/engineHealthFacts.js +63 -4
  22. package/dist/engineInlineTaskStats.d.ts +4 -3
  23. package/dist/engineInlineTaskStats.js +18 -2
  24. package/dist/engineNoticeCodes.d.ts +138 -0
  25. package/dist/engineNoticeCodes.js +662 -55
  26. package/dist/engineRepoint.d.ts +140 -0
  27. package/dist/engineRepoint.js +285 -0
  28. package/dist/engineRowRef.js +2 -0
  29. package/dist/engineSessionMessages.d.ts +2 -1
  30. package/dist/engineSessionMessages.js +5 -0
  31. package/dist/errorSupplementRoles.js +5 -1
  32. package/dist/fleet/fleetProjection.js +3 -2
  33. package/dist/fleetTaskDesc.js +3 -3
  34. package/dist/generated/engineFactTables.d.ts +3 -0
  35. package/dist/generated/engineFactTables.js +18 -0
  36. package/dist/generated/engineNoticeTables.js +3 -0
  37. package/dist/generated/sdkFactTables.d.ts +1 -0
  38. package/dist/generated/sdkFactTables.js +4 -0
  39. package/dist/hitl/approvalAskers.d.ts +6 -0
  40. package/dist/hitl/approvalAskers.js +30 -0
  41. package/dist/hitl/approvalCardReasons.js +25 -4
  42. package/dist/hitl/approvalRequestFrameReading.d.ts +21 -0
  43. package/dist/hitl/approvalRequestFrameReading.js +90 -0
  44. package/dist/hitl/approvalResolution.d.ts +8 -1
  45. package/dist/hitl/approvalResolution.js +4 -0
  46. package/dist/hitl/approvalsFeed.js +1 -1
  47. package/dist/hitl/askGateWire.js +0 -2
  48. package/dist/hitl/askParkRowRouting.js +5 -2
  49. package/dist/hitl/automaticAnswerVerdict.d.ts +5 -2
  50. package/dist/hitl/automaticAnswerVerdict.js +18 -4
  51. package/dist/hitl/decisionConflict.d.ts +2 -1
  52. package/dist/hitl/decisionConflict.js +10 -4
  53. package/dist/hitl/decisionRefusal.js +1 -0
  54. package/dist/hitl/decisionSend.d.ts +3 -0
  55. package/dist/hitl/decisionSend.js +29 -11
  56. package/dist/hitl/editedApprovalAcceptance.d.ts +25 -0
  57. package/dist/hitl/editedApprovalAcceptance.js +26 -0
  58. package/dist/hitl/frameRouter.js +13 -19
  59. package/dist/hitl/gateLedger.d.ts +0 -4
  60. package/dist/hitl/gateLedger.js +0 -19
  61. package/dist/hitl/hitlBridge.d.ts +5 -0
  62. package/dist/hitl/hitlBridge.js +13 -9
  63. package/dist/hitl/hitlHostSurface.d.ts +1 -0
  64. package/dist/hitl/hitlHostSurface.js +7 -2
  65. package/dist/hitl/hostWithdrawal.d.ts +15 -0
  66. package/dist/hitl/hostWithdrawal.js +38 -0
  67. package/dist/hitl/livePendingAsk.js +17 -16
  68. package/dist/hitl/localAllowRule.js +2 -1
  69. package/dist/hitl/noAllowCause.d.ts +1 -1
  70. package/dist/hitl/noAllowCause.js +4 -2
  71. package/dist/hitl/parkExpiryJudgements.js +26 -1
  72. package/dist/hitl/parkOwnership.d.ts +2 -0
  73. package/dist/hitl/parkOwnership.js +48 -8
  74. package/dist/hitl/parkResolver.js +32 -16
  75. package/dist/hitl/parkedUnresumable.d.ts +13 -0
  76. package/dist/hitl/parkedUnresumable.js +80 -0
  77. package/dist/hitl/permissionRuleScope.d.ts +4 -0
  78. package/dist/hitl/permissionRuleScope.js +31 -0
  79. package/dist/hitl/persistedRulesWire.d.ts +2 -0
  80. package/dist/hitl/persistedRulesWire.js +13 -12
  81. package/dist/hitl/planReviewWire.js +14 -4
  82. package/dist/hitl/sessionAllowRoute.js +5 -4
  83. package/dist/hitl/sessionPolicyWire.js +6 -4
  84. package/dist/hitl/standingRows.d.ts +2 -5
  85. package/dist/hitl/standingRows.js +30 -15
  86. package/dist/hitl/toolApprovalWire.d.ts +42 -9
  87. package/dist/hitl/toolApprovalWire.js +267 -44
  88. package/dist/hooksWireCaps.js +35 -4
  89. package/dist/hostSettingsSource.js +2 -0
  90. package/dist/hostTurnSettings.d.ts +10 -4
  91. package/dist/hostTurnSettings.js +112 -5
  92. package/dist/i18n/messages.en.d.ts +251 -0
  93. package/dist/i18n/messages.en.js +254 -2
  94. package/dist/i18n/messages.zh.js +264 -2
  95. package/dist/index.d.ts +48 -12
  96. package/dist/index.js +29 -7
  97. package/dist/inheritEnvCapability.js +2 -0
  98. package/dist/inputAuthor.d.ts +16 -0
  99. package/dist/inputAuthor.js +44 -0
  100. package/dist/interruptRefusalNote.d.ts +2 -0
  101. package/dist/interruptRefusalNote.js +17 -0
  102. package/dist/liveModelCatalog.d.ts +2 -0
  103. package/dist/liveModelCatalog.js +21 -7
  104. package/dist/liveQuestionStore.d.ts +4 -1
  105. package/dist/liveQuestionStore.js +12 -3
  106. package/dist/localEngineJudge.d.ts +75 -0
  107. package/dist/localEngineJudge.js +242 -0
  108. package/dist/localEngineOccupancy.d.ts +88 -0
  109. package/dist/localEngineOccupancy.js +269 -0
  110. package/dist/localEngineSeatReading.d.ts +29 -0
  111. package/dist/localEngineSeatReading.js +294 -0
  112. package/dist/localEngineSpawn.d.ts +113 -0
  113. package/dist/localEngineSpawn.js +486 -0
  114. package/dist/localEngineStep.d.ts +286 -0
  115. package/dist/localEngineStep.js +1086 -0
  116. package/dist/localEngineStopLadder.d.ts +29 -0
  117. package/dist/localEngineStopLadder.js +139 -0
  118. package/dist/localEngineSwapCard.d.ts +43 -0
  119. package/dist/localEngineSwapCard.js +205 -0
  120. package/dist/localEngineTiming.d.ts +8 -0
  121. package/dist/localEngineTiming.js +8 -0
  122. package/dist/mcpEngineLeg.d.ts +3 -0
  123. package/dist/mcpEngineLeg.js +38 -1
  124. package/dist/mcpPanel.js +2 -0
  125. package/dist/mcpReconnect.d.ts +9 -1
  126. package/dist/mcpReconnect.js +132 -20
  127. package/dist/mcpWireCaps.js +2 -2
  128. package/dist/memoryEntriesWire.d.ts +1 -1
  129. package/dist/memoryEntriesWire.js +5 -0
  130. package/dist/modelCapabilityProbe.js +4 -3
  131. package/dist/modsFace.d.ts +8 -1
  132. package/dist/modsFace.js +27 -0
  133. package/dist/noticePresentation.d.ts +2 -0
  134. package/dist/noticePresentation.js +79 -1
  135. package/dist/notifications.d.ts +17 -6
  136. package/dist/notifications.js +67 -19
  137. package/dist/ownKey.d.ts +5 -0
  138. package/dist/ownKey.js +8 -1
  139. package/dist/peerFrames.d.ts +40 -2
  140. package/dist/peerFrames.js +158 -3
  141. package/dist/pluginHooksWire.d.ts +2 -1
  142. package/dist/pluginHooksWire.js +56 -1
  143. package/dist/replayBody.js +10 -2
  144. package/dist/request/taskRequest.d.ts +5 -0
  145. package/dist/request/taskRequest.js +138 -21
  146. package/dist/resumeRefusalCopy.d.ts +1 -3
  147. package/dist/resumeRefusalCopy.js +2 -1
  148. package/dist/retryStatus.d.ts +2 -2
  149. package/dist/retryStatus.js +2 -0
  150. package/dist/ruleReadings.js +1 -1
  151. package/dist/seam.d.ts +24 -2
  152. package/dist/seam.js +15 -2
  153. package/dist/seatContract.d.ts +125 -10
  154. package/dist/seatContract.js +373 -7
  155. package/dist/sessionBackground.js +4 -1
  156. package/dist/sessionBackgroundHandoff.js +2 -1
  157. package/dist/sessionEngine.d.ts +2 -0
  158. package/dist/sessionEngine.js +111 -0
  159. package/dist/sessionMemoryStatus.d.ts +2 -1
  160. package/dist/sessionSyncFileHistory.d.ts +4 -0
  161. package/dist/sessionSyncFileHistory.js +28 -0
  162. package/dist/sseIdleTriage.d.ts +13 -1
  163. package/dist/sseIdleTriage.js +24 -6
  164. package/dist/subagent/engineSubagentTail.js +6 -0
  165. package/dist/subagentContentStore.d.ts +12 -0
  166. package/dist/subagentContentStore.js +72 -11
  167. package/dist/terminalStatuses.d.ts +1 -0
  168. package/dist/terminalStatuses.js +4 -1
  169. package/dist/toolResult.js +18 -5
  170. package/dist/turnFailurePresentation.js +9 -2
  171. package/dist/typePins.d.ts +11 -0
  172. package/dist/usageAnalytics.d.ts +17 -0
  173. package/dist/usageAnalytics.js +44 -2
  174. package/dist/wireFailureShape.d.ts +22 -5
  175. package/dist/wireFailureShape.js +47 -26
  176. package/dist/wireRefusalCopy.d.ts +31 -1
  177. package/dist/wireRefusalCopy.js +167 -2
  178. package/docs/INTEGRATION-CLIENTS.md +3269 -21
  179. package/package.json +5 -5
@@ -10,6 +10,7 @@ import { denyAttributionOf, flushHeldWithInterruptRewrite, isAskTool } from './f
10
10
  import { invokeHostCallbackIsolated } from '../hostCallbackGuard.js';
11
11
  import { inheritFrameBinding } from '../frameBinding.js';
12
12
  import { approvalCallKey, askGateQuestionId } from './gateIdentity.js';
13
+ import { usableAskQuestions } from './parkOwnership.js';
13
14
  import { readDecideReceipt } from '../decideReceipt.js';
14
15
  import { decideFailureNote } from '../decideFailureNote.js';
15
16
  import { wireFailureTextsOf } from '../wireFailureShape.js';
@@ -69,12 +70,11 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
69
70
  return { kind: 'not_sent', lane: 'ask', cause: found.code ?? 'fetch_failed', ...(found.reason !== '' ? { detail: found.reason } : {}), ...(found.rowVerdict !== undefined ? { rowVerdict: found.rowVerdict } : {}) };
70
71
  const { pending, gatedCallId } = found;
71
72
  const at = { lane: 'ask', ...(gatedCallId !== undefined ? { gatedCallId } : {}) };
72
- const fromArgs = gatedCallId
73
- ? askArgsByCall.get(gatedCallId)?.questions
74
- : undefined;
75
- const fromPending = pending.input?.questions;
76
- const questions = Array.isArray(fromArgs) && fromArgs.length > 0 ? fromArgs : fromPending;
77
- if (!Array.isArray(questions) || questions.length === 0) {
73
+ const rowQuestions = usableAskQuestions(pending.input?.questions);
74
+ const streamQuestions = usableAskQuestions(gatedCallId ? askArgsByCall.get(gatedCallId)?.questions : undefined);
75
+ const questionsFromModelCall = rowQuestions === null && streamQuestions !== null;
76
+ const questions = rowQuestions ?? streamQuestions;
77
+ if (questions === null) {
78
78
  const truncated = pending.input?.truncated === true;
79
79
  return {
80
80
  kind: 'not_sent',
@@ -86,11 +86,17 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
86
86
  };
87
87
  }
88
88
  const questionId = askGateQuestionId(approvalCallKey(gatedCallId, taskId));
89
+ let withdrawn;
89
90
  const answer = await new Promise(resolve => {
90
91
  const unregister = registerLocalQuestionResponder(questionId, async (_id, a) => {
91
92
  cleanup();
92
93
  resolve(a);
93
94
  return { ok: true };
95
+ }, (w) => {
96
+ withdrawn = w;
97
+ cleanup();
98
+ publishQuestionFrame({ type: 'question_complete', questionId });
99
+ resolve(null);
94
100
  });
95
101
  const onAbort = () => {
96
102
  cleanup();
@@ -107,8 +113,12 @@ async function surfaceGateAndDecide(deps, taskId, askArgsByCall, signal, parkGat
107
113
  }
108
114
  signal?.addEventListener('abort', onAbort, { once: true });
109
115
  onPresented?.();
110
- publishQuestionFrame({ type: 'question', questionId, questions });
116
+ publishQuestionFrame({ type: 'question', questionId, questions, ...(questionsFromModelCall ? { _sema_questionsFromModelCall: true } : {}) });
111
117
  });
118
+ if (withdrawn !== undefined) {
119
+ hostLog('debug', `liveHitlAskWire: question-withdrawn-by-host card=${questionId} verdict=${withdrawn.rowVerdict.kind}`);
120
+ return { kind: 'not_sent', ...at, cause: 'withdrawn', toldBy: withdrawn.toldBy, rowVerdict: withdrawn.rowVerdict };
121
+ }
112
122
  const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
113
123
  if (answer === null) {
114
124
  observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, undefined, pending), taskId);
@@ -155,7 +165,7 @@ function isFetchStepNoPending(outcome) {
155
165
  return outcome.kind === 'not_sent' && outcome.cause === 'no_pending' && outcome.gatedCallId === undefined;
156
166
  }
157
167
  function isHumanDisposition(r) {
158
- return r.kind === 'decided' || (r.kind === 'unsettled' && r.cause === 'interrupted') || (r.kind === 'not_sent' && r.cause === 'retracted');
168
+ return r.kind === 'decided' || (r.kind === 'unsettled' && r.cause === 'interrupted') || (r.kind === 'not_sent' && (r.cause === 'retracted' || r.cause === 'withdrawn'));
159
169
  }
160
170
  function failureReasonOf(r) {
161
171
  if (r.kind === 'decided')
@@ -282,7 +292,8 @@ export async function resolvePark(park, ctx) {
282
292
  if (outcome.kind !== 'decided') {
283
293
  hostLog('debug', `liveHitlAskWire: gate not decided (${outcome.kind}/${outcome.cause}: ${failureReasonOf(outcome)}) — fail-soft to suspended terminal`);
284
294
  const interrupted = outcome.kind === 'unsettled' && outcome.cause === 'interrupted';
285
- const failedReason = interrupted
295
+ const toldByHost = outcome.kind === 'not_sent' && outcome.cause === 'withdrawn';
296
+ const failedReason = interrupted || toldByHost
286
297
  ? undefined
287
298
  : outcome.kind === 'not_sent' && outcome.cause === 'retracted'
288
299
  ? 'the approval card was retracted by the host without a decision (nothing was sent; the run stays parked and remains decidable elsewhere)'
@@ -312,13 +323,19 @@ export async function resolvePark(park, ctx) {
312
323
  };
313
324
  }
314
325
  function failsoftWayOutOf(outcome) {
326
+ if (outcome === undefined)
327
+ return 'card';
328
+ if (typeof outcome !== 'object' || outcome === null)
329
+ return 'speak';
315
330
  if (outcome.kind === 'not_sent' && (outcome.cause === 'row_unresumable' || (outcome.cause === 'safety_stop' && outcome.safetyCode === 'binding_missing')))
316
331
  return 'reason';
317
- if (outcome.kind === 'unsettled' && outcome.cause === 'engine_refused' && refusedParkFromError(outcome.refusal) !== null)
332
+ if (outcome.kind === 'unsettled' && outcome.cause === 'engine_refused' && (refusedParkFromError(outcome.refusal) !== null || outcome.refusal?.conflict === 'row_integrity'))
318
333
  return 'cancel';
334
+ if (outcome.kind === 'unsettled' && outcome.cause === 'engine_refused')
335
+ return 'speak';
319
336
  return 'card';
320
337
  }
321
- function failsoftReasonSentence(park, failedReason, terminal, wayOut = 'card') {
338
+ function failsoftReasonSentence(park, failedReason, terminal, wayOut) {
322
339
  if (terminal !== undefined)
323
340
  return terminal.errorMessage;
324
341
  if (failedReason === undefined)
@@ -328,10 +345,9 @@ function failsoftReasonSentence(park, failedReason, terminal, wayOut = 'card') {
328
345
  return renderMessage(msg('approval.park.stillParked', { gate, reason: failedReason }));
329
346
  if (wayOut === 'cancel')
330
347
  return renderMessage(msg('approval.park.refusedNotResumable', { gate, reason: failedReason }));
331
- return (`${park.gate === 'fs' ? 'Tool approval' : 'AskUserQuestion'} gate could not be answered: ${failedReason}` +
332
- ' — the run is still parked on this approval: decide it on the card when it is shown again, or cancel the run.');
348
+ return renderMessage(msg(wayOut === 'speak' ? 'approval.park.resume.speak' : 'approval.park.resume.card', { gate, reason: failedReason }));
333
349
  }
334
- function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface = false, wayOut = 'card') {
350
+ function failsoftEvents(park, ctx, gateAbortedByUser, failedReason, terminal, noDecisionSurface, wayOut) {
335
351
  const events = [
336
352
  ...flushHeldWithInterruptRewrite(ctx.led, {
337
353
  terminal: park.pendingDone,
@@ -360,12 +376,12 @@ export function stalledTerminal(park, ctx, stalledRounds, lastStallReason, decid
360
376
  `stopped re-attaching. Your last decision was accepted; sema did not observe the run move on after it — check the ` +
361
377
  `transcript, decide the call again if the card comes back, or cancel the run.`;
362
378
  hostLog('debug', `liveHitlAskWire: ${errorMessage}`);
363
- return { kind: 'failsoft', events: failsoftEvents(park, ctx, false, undefined, { errorCode: 'hitl_stalled', errorMessage }) };
379
+ return { kind: 'failsoft', events: failsoftEvents(park, ctx, false, undefined, { errorCode: 'hitl_stalled', errorMessage }, false, failsoftWayOutOf(undefined)) };
364
380
  }
365
381
  const reason = `the run stayed parked across ${stalledRounds} consecutive re-attach rounds without any host progress ` +
366
382
  `(last: ${lastStallReason ?? 'the engine kept parking'})`;
367
383
  hostLog('debug', `liveHitlAskWire: ${reason} — fail-soft after the round committed`);
368
- return { kind: 'failsoft', events: failsoftEvents(park, ctx, false, reason) };
384
+ return { kind: 'failsoft', events: failsoftEvents(park, ctx, false, reason, undefined, false, failsoftWayOutOf(undefined)) };
369
385
  }
370
386
  function notifyParkReattach(ctx, attempt, max, reason) {
371
387
  invokeHostCallbackIsolated('liveHitlAskWire: onParkReattach', () => {
@@ -0,0 +1,13 @@
1
+ import { type CheckpointRowBar } from '../checkpointRowVerdict.js';
2
+ import type { UiLanguage } from '../env/uiLanguage.js';
3
+ import type { OwnedPendingListView } from './askParkRowRouting.js';
4
+ import { type ParkOwnershipDeps } from './parkOwnership.js';
5
+ export interface ParkedUnresumableRow {
6
+ readonly rowVerdict: CheckpointRowBar;
7
+ readonly rowKey?: string;
8
+ readonly taskId?: string;
9
+ readonly toolName?: string;
10
+ readonly rowDeadline?: 'armed' | 'none';
11
+ }
12
+ export declare function parkedUnresumableRowsOf(listed: OwnedPendingListView | null | undefined, deps?: ParkOwnershipDeps): readonly ParkedUnresumableRow[];
13
+ export declare function parkedUnresumableLine(row: ParkedUnresumableRow, lang?: UiLanguage | (string & {})): string;
@@ -0,0 +1,80 @@
1
+ import { checkpointDeadlineStateOf, checkpointRowBarOf, checkpointRowBarSentence, rowBarWayOutContent, } from '../checkpointRowVerdict.js';
2
+ import { slotReadOf } from '../wireFailureShape.js';
3
+ import { QUEUE_ROW_FAMILY_UNREADABLE_GATE } from './approvalQueueRow.js';
4
+ import { approvalCallKey } from './gateIdentity.js';
5
+ import { checkpointQueueReadingOf } from './hitlBridge.js';
6
+ import { pendingRowIsOwnedByThisSession } from './parkOwnership.js';
7
+ function ownNonEmpty(o, key) {
8
+ const r = slotReadOf(o, key);
9
+ return r.presence === 'present' && typeof r.value === 'string' && r.value !== '' ? r.value : undefined;
10
+ }
11
+ function memberOf(row, rowVerdict) {
12
+ const taskId = ownNonEmpty(row, 'taskId');
13
+ const callId = ownNonEmpty(row, 'toolCallId') ?? ownNonEmpty(row, 'boundCallId');
14
+ const rowKey = callId !== undefined || taskId !== undefined ? approvalCallKey(callId, taskId ?? '') : undefined;
15
+ const toolName = ownNonEmpty(row, 'toolName');
16
+ const deadline = checkpointDeadlineStateOf(row);
17
+ return Object.freeze({
18
+ rowVerdict,
19
+ ...(rowKey !== undefined ? { rowKey } : {}),
20
+ ...(taskId !== undefined ? { taskId } : {}),
21
+ ...(toolName !== undefined ? { toolName } : {}),
22
+ ...(deadline !== 'unknown' ? { rowDeadline: deadline } : {}),
23
+ });
24
+ }
25
+ export function parkedUnresumableRowsOf(listed, deps) {
26
+ if (typeof listed !== 'object' || listed === null || Array.isArray(listed)) {
27
+ throw new Error('approvals.list answered without a readable body — the rows this engine cannot continue are unknown');
28
+ }
29
+ const pending = listed.pending;
30
+ if (!Array.isArray(pending)) {
31
+ throw new Error('approvals.list answered without a readable `pending` array — the rows this engine cannot continue are unknown');
32
+ }
33
+ const out = [];
34
+ for (const row of pending) {
35
+ const member = parkedMemberOf(row, deps);
36
+ if (member !== undefined)
37
+ out.push(member);
38
+ }
39
+ return Object.freeze(out);
40
+ }
41
+ function parkedMemberOf(row, deps) {
42
+ try {
43
+ if (typeof row !== 'object' || row === null)
44
+ return undefined;
45
+ const reading = checkpointQueueReadingOf(row);
46
+ if (reading === undefined)
47
+ return undefined;
48
+ if (reading.foreign !== null && reading.foreign !== QUEUE_ROW_FAMILY_UNREADABLE_GATE)
49
+ return undefined;
50
+ const bar = checkpointRowBarOf(reading.r);
51
+ if (bar === undefined)
52
+ return undefined;
53
+ if (!pendingRowIsOwnedByThisSession(reading.r, deps))
54
+ return undefined;
55
+ return memberOf(reading.r, bar);
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ }
61
+ const UNREADABLE_BAR = Object.freeze({ kind: 'unreadable' });
62
+ export function parkedUnresumableLine(row, lang) {
63
+ let bar = UNREADABLE_BAR;
64
+ let state = 'unknown';
65
+ try {
66
+ if (typeof row === 'object' && row !== null) {
67
+ const v = slotReadOf(row, 'rowVerdict');
68
+ if (v.presence === 'present')
69
+ bar = checkpointRowBarOf({ rowVerdict: v.value }) ?? UNREADABLE_BAR;
70
+ const d = slotReadOf(row, 'rowDeadline');
71
+ if (d.presence === 'present' && (d.value === 'armed' || d.value === 'none'))
72
+ state = d.value;
73
+ }
74
+ }
75
+ catch {
76
+ bar = UNREADABLE_BAR;
77
+ state = 'unknown';
78
+ }
79
+ return `${checkpointRowBarSentence(bar, lang)} · ${rowBarWayOutContent(bar, state, lang)}`;
80
+ }
@@ -0,0 +1,4 @@
1
+ import type { PermissionRuleValue } from '@sema-agent/agent-types';
2
+ export declare function isWholeToolRuleValue(value: PermissionRuleValue): boolean;
3
+ export type AllowRuleScope = 'whole_tool' | 'content' | 'unreadable';
4
+ export declare function allowRuleScopeOf(value: object | null | undefined): AllowRuleScope;
@@ -0,0 +1,31 @@
1
+ import { readOwnDataValue } from '../ownKey.js';
2
+ import { inSlotReadOf } from '../wireFailureShape.js';
3
+ export function isWholeToolRuleValue(value) {
4
+ return value.ruleContent === undefined || value.ruleContent === '';
5
+ }
6
+ export function allowRuleScopeOf(value) {
7
+ try {
8
+ if (typeof value !== 'object' || value === null)
9
+ return 'unreadable';
10
+ const toolName = readOwnDataValue(value, 'toolName');
11
+ if (typeof toolName !== 'string' || toolName === '')
12
+ return 'unreadable';
13
+ let ruleContent;
14
+ const slot = inSlotReadOf(value, 'ruleContent');
15
+ if (slot.state === 'present') {
16
+ const raw = slot.value;
17
+ if (raw !== undefined) {
18
+ if (typeof raw !== 'string' || raw === '')
19
+ return 'unreadable';
20
+ ruleContent = raw;
21
+ }
22
+ }
23
+ else if (slot.state !== 'absent')
24
+ return 'unreadable';
25
+ const snapshot = Object.freeze(Object.assign(Object.create(null), ruleContent !== undefined ? { toolName, ruleContent } : { toolName }));
26
+ return isWholeToolRuleValue(snapshot) ? 'whole_tool' : 'content';
27
+ }
28
+ catch {
29
+ return 'unreadable';
30
+ }
31
+ }
@@ -33,6 +33,8 @@ export interface CcImportRedeemRuleKey {
33
33
  }
34
34
  export interface CcImportRedeemRefused {
35
35
  readonly key?: CcImportRedeemRuleKey;
36
+ readonly reason?: string;
37
+ readonly detail?: string;
36
38
  }
37
39
  export interface CcImportRedeemCounts {
38
40
  persisted: number;
@@ -1,13 +1,13 @@
1
1
  import { displayUntrusted, quoteUntrustedForSentence } from '../displayUntrusted.js';
2
2
  import { engineCapTrue } from '../engineCapsCache.js';
3
3
  import { CORE_RULE_BEHAVIORS } from '../generated/engineFactTables.js';
4
- import { slotReadOf, wireFailureShapeOf } from '../wireFailureShape.js';
4
+ import { inValueOf, slotReadOf, wireFailureShapeOf } from '../wireFailureShape.js';
5
5
  import { CAPABILITY_OPERATOR_PRINCIPALS_REQUIRED } from '../engineErrorCodes.js';
6
6
  import { hostLog } from '../host.js';
7
7
  import { msg, renderMessage } from '../i18n/translate.js';
8
8
  import { appendOwnElement, guardedRead, readOwnDataValue } from '../ownKey.js';
9
9
  import { observedPermissionRulesWrite, permissionRulesWriteAvailable } from '../permissionRulesWriteCapability.js';
10
- import { isToolApprovalRespondReplayed, readToolApprovalRespondAck } from './toolApprovalWire.js';
10
+ import { isToolApprovalRespondParkedRedeemed, isToolApprovalRespondReplayed, readToolApprovalRespondAck } from './toolApprovalWire.js';
11
11
  export function persistedRulesLaneAvailable(baseUrl) {
12
12
  return engineCapTrue(baseUrl, 'permissionRules');
13
13
  }
@@ -51,7 +51,7 @@ function ruleKeyOfRow(row) {
51
51
  const scope = keyObj === undefined ? undefined : ownNonEmpty(keyObj, 'scope');
52
52
  return behavior !== undefined && rule !== undefined && scope !== undefined ? Object.freeze({ behavior, rule, scope }) : undefined;
53
53
  }
54
- function indeterminateRowOf(row) {
54
+ function unsettledRowOf(row) {
55
55
  const key = ruleKeyOfRow(row);
56
56
  const reason = ownNonEmpty(row, 'reason');
57
57
  const d = slotReadOf(row, 'detail');
@@ -62,10 +62,6 @@ function indeterminateRowOf(row) {
62
62
  ...(shown !== undefined && shown.trim() !== '' ? { detail: shown } : {}),
63
63
  });
64
64
  }
65
- function refusedRowOf(row) {
66
- const key = ruleKeyOfRow(row);
67
- return Object.freeze(key !== undefined ? { key } : {});
68
- }
69
65
  const RECEIPT_ROW_ARMS = {
70
66
  persisted: (acc) => {
71
67
  acc.persisted += 1;
@@ -74,10 +70,10 @@ const RECEIPT_ROW_ARMS = {
74
70
  acc.deduped += 1;
75
71
  },
76
72
  refused: (acc, row) => {
77
- appendOwnElement(acc.refused, refusedRowOf(row));
73
+ appendOwnElement(acc.refused, unsettledRowOf(row));
78
74
  },
79
75
  indeterminate: (acc, row) => {
80
- appendOwnElement(acc.indeterminate, indeterminateRowOf(row));
76
+ appendOwnElement(acc.indeterminate, unsettledRowOf(row));
81
77
  },
82
78
  };
83
79
  function receiptCountsOf(rows) {
@@ -333,9 +329,14 @@ export function revokeTargetFromPersistedRule(rule, opts) {
333
329
  };
334
330
  }
335
331
  function readPrincipalOverride(carrier) {
336
- if (carrier === null || typeof carrier !== 'object' || !('principal' in carrier))
332
+ if (carrier === null || typeof carrier !== 'object')
337
333
  return { ok: true };
338
- const raw = carrier.principal;
334
+ const slot = inValueOf(carrier, 'principal');
335
+ if (slot.state === 'absent')
336
+ return { ok: true };
337
+ if (slot.state === 'unproven')
338
+ return { ok: false };
339
+ const raw = slot.value;
339
340
  if (typeof raw !== 'string' || raw.trim().length === 0)
340
341
  return { ok: false };
341
342
  return { ok: true, principal: raw };
@@ -636,7 +637,7 @@ export function classifySkippedReason(reason) {
636
637
  }
637
638
  export function readRulePersistOutcome(ack) {
638
639
  const parsed = readToolApprovalRespondAck(ack);
639
- if (parsed === undefined || isToolApprovalRespondReplayed(parsed))
640
+ if (parsed === undefined || isToolApprovalRespondReplayed(parsed) || isToolApprovalRespondParkedRedeemed(parsed))
640
641
  return { state: 'unknown' };
641
642
  if (parsed.rulePersisted === true)
642
643
  return { state: 'persisted' };
@@ -498,11 +498,15 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
498
498
  armedPlanReviews.set(taskId, instance);
499
499
  armedTaskId = taskId;
500
500
  let unregister = () => { };
501
+ const onWithdraw = () => {
502
+ hostLog('debug', `planReviewWire: card-withdrawn-by-host task=${taskId} card=${questionId}`);
503
+ retireArmedPlanReview(taskId, instance);
504
+ };
501
505
  const responder = async (_id, answer) => {
502
506
  const choice = planReviewAnswerChoiceOf(questionId, answer);
503
507
  if (choice === 'inadmissible') {
504
508
  if (armedPlanReviews.get(taskId) === instance && !hasLocalQuestionResponder(questionId))
505
- unregister = registerLocalQuestionResponder(questionId, responder);
509
+ unregister = registerLocalQuestionResponder(questionId, responder, onWithdraw);
506
510
  hostLog('debug', `planReviewWire: answer-inadmissible card=${questionId}`);
507
511
  return { ok: false, inadmissible: true };
508
512
  }
@@ -523,7 +527,7 @@ export function armPlanReviewApproval(result, sessionKey, opts) {
523
527
  void decidePlanReviewFor(taskId, decided, modeAfter, cardDelivery, facts);
524
528
  return { ok: true };
525
529
  };
526
- unregister = registerLocalQuestionResponder(questionId, responder);
530
+ unregister = registerLocalQuestionResponder(questionId, responder, onWithdraw);
527
531
  publishArmedPlanReviewCard(instance);
528
532
  hostLog('debug', `planReviewWire: approval card armed for parked plan (task ${taskId})`);
529
533
  }
@@ -1160,11 +1164,17 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1160
1164
  });
1161
1165
  const activeKey = `${sessionKey}\u0000${canonicalId}`;
1162
1166
  let unregister = () => { };
1167
+ const onWithdraw = () => {
1168
+ hostLog('debug', `planReviewWire: card-withdrawn-by-host task=${taskId} card=${questionId}`);
1169
+ if (activeReopenResponders.get(activeKey)?.questionId === questionId)
1170
+ activeReopenResponders.delete(activeKey);
1171
+ publishQuestionFrameFor(sessionKey, { type: 'question_complete', questionId });
1172
+ };
1163
1173
  const responder = async (_id, answer) => {
1164
1174
  const choice = planReviewAnswerChoiceOf(questionId, answer);
1165
1175
  if (choice === 'inadmissible') {
1166
1176
  if (activeReopenResponders.get(activeKey)?.questionId === questionId && !hasLocalQuestionResponder(questionId)) {
1167
- unregister = registerLocalQuestionResponder(questionId, responder);
1177
+ unregister = registerLocalQuestionResponder(questionId, responder, onWithdraw);
1168
1178
  }
1169
1179
  hostLog('debug', `planReviewWire: answer-inadmissible card=${questionId}`);
1170
1180
  return { ok: false, inadmissible: true };
@@ -1190,7 +1200,7 @@ function mintPlanReviewReopenCard(taskId, opts, sessionKey, receiptMs, taken) {
1190
1200
  return { ok: true };
1191
1201
  };
1192
1202
  const shape = cardShapeOf(facts, false);
1193
- unregister = registerLocalQuestionResponder(questionId, responder);
1203
+ unregister = registerLocalQuestionResponder(questionId, responder, onWithdraw);
1194
1204
  activeReopenResponders.set(activeKey, { questionId, unregister: () => unregister(), shape });
1195
1205
  const receipt = waitForGateArmedFor(sessionKey, [questionId], receiptMs);
1196
1206
  publishQuestionFrameFor(sessionKey, planReviewQuestionFrame(questionId, REOPEN_QUESTION, shape, facts));
@@ -1,3 +1,4 @@
1
+ import { inSlotReadOf } from '../wireFailureShape.js';
1
2
  export function sessionAllowCarried(leg, approvalId) {
2
3
  if (leg === 'frame')
3
4
  return true;
@@ -7,10 +8,10 @@ export function sessionAllowCarried(leg, approvalId) {
7
8
  }
8
9
  export function readCardUnreadableEvidence(card) {
9
10
  try {
10
- if (!('unreadableEvidence' in card))
11
+ const slot = inSlotReadOf(card, 'unreadableEvidence');
12
+ if (slot.state === 'absent')
11
13
  return 'absent';
12
- const d = Object.getOwnPropertyDescriptor(card, 'unreadableEvidence');
13
- const list = d !== undefined && Object.hasOwn(d, 'value') ? d.value : undefined;
14
+ const list = slot.state === 'present' ? slot.value : undefined;
14
15
  return Array.isArray(list) && list.every((k) => typeof k === 'string') ? list : 'unreadable';
15
16
  }
16
17
  catch {
@@ -21,7 +22,7 @@ export function sessionAllowCardReadable(card) {
21
22
  if (typeof card !== 'object' || card === null)
22
23
  return 'card_unreadable';
23
24
  try {
24
- if ('cardPayloadUnreadable' in card)
25
+ if (inSlotReadOf(card, 'cardPayloadUnreadable').state !== 'absent')
25
26
  return 'card_unreadable';
26
27
  }
27
28
  catch {
@@ -701,10 +701,12 @@ export function sessionPolicyRemovalNotice(outcome, deployment) {
701
701
  return 'This session’s rule record holds no entry naming a tool the engine refuses that the supplied roster does not mount, so nothing was changed. If runs still fail at startup over a refused tool name, that entry is set somewhere this record does not cover — the settings sent with the run, a parent session’s rule record, or the deployment’s own tool rules — or it is a name the engine refuses that this version does not yet recognise, or the failing run does not mount a tool the supplied roster has under that name.';
702
702
  case 'judge_failed':
703
703
  return 'Nothing was changed: this client could not check which entries in this session’s rule record name a tool the engine refuses (its copy of the engine’s tool-name check is unavailable or gave no clear answer), so it removed none rather than guess. Removing them needs that check available in this client.';
704
- case 'loosen_forbidden':
705
- if (deploymentHasNoOperator(deployment))
706
- return renderMessage(msg('rules.sessionPolicy.deployment.noOperator.removeLoosenForbidden'));
707
- return 'The engine refused to take these entries out of this session’s rule record: on this engine only an operator can remove entries from it, so the record is unchanged. Ask an operator to remove them, or continue in a new session.';
704
+ case 'loosen_forbidden': {
705
+ const stem = msg('rules.sessionPolicy.remove.loosenForbiddenStem');
706
+ return deploymentHasNoOperator(deployment)
707
+ ? renderMessage(msg('rules.sessionPolicy.deployment.noOperator.removeLoosenForbidden', { stem }))
708
+ : renderMessage(msg('rules.sessionPolicy.remove.loosenForbidden', { stem }));
709
+ }
708
710
  case 'conflict':
709
711
  return 'This session’s rule record was changed by someone else while the refused entries were being removed, twice in a row — nothing was changed. Try again once the other change has finished.';
710
712
  case 'read_failed':
@@ -18,6 +18,7 @@ export interface StandingRowsHostFacts {
18
18
  }
19
19
  export declare function standingRowsOf(card: object | null | undefined, host?: StandingRowsHostFacts | null): StandingRows;
20
20
  export declare function seatSessionRowOf(card: object | null | undefined, requestId: string | undefined): boolean;
21
+ export declare function seatAllowNoteCarriedOf(card: object | null | undefined, requestId: string | undefined): boolean;
21
22
  export type RememberOffer = 'offerable' | 'refused' | 'unknown';
22
23
  export interface RememberOfferable {
23
24
  readonly session: RememberOffer;
@@ -25,13 +26,9 @@ export interface RememberOfferable {
25
26
  readonly deploymentList: RememberOffer;
26
27
  }
27
28
  export declare function rememberOfferable(card: object | null | undefined, host?: StandingRowsHostFacts | null): RememberOfferable;
28
- export interface LocalAllowRuleHostFacts {
29
- readonly hostLadderEnforces?: boolean;
30
- }
31
29
  export interface LocalAllowRuleEnforcement {
32
30
  readonly enforceable: boolean;
33
- readonly by?: 'host_ladder' | 'engine_grant';
34
31
  readonly engine: 'sent' | 'kept' | 'unreadable';
35
32
  readonly keptReason?: PermissionsKeptReason;
36
33
  }
37
- export declare function localAllowRuleEnforceable(rule: string, caps: PermissionsAllowGrantReading, host?: LocalAllowRuleHostFacts | null): LocalAllowRuleEnforcement;
34
+ export declare function localAllowRuleEnforceable(rule: string, caps: PermissionsAllowGrantReading): LocalAllowRuleEnforcement;
@@ -1,7 +1,8 @@
1
1
  import { DEPLOYMENT_LIST_ASK_CLASSES, readAskClassPairState } from './askClassPair.js';
2
- import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
3
- import { readApprovalCardWord, readRuleOffers } from './toolApprovalWire.js';
2
+ import { askLevelVerdict } from './automaticAnswerVerdict.js';
3
+ import { answerCarriage, readApprovalCardWord, readRuleOffers } from './toolApprovalWire.js';
4
4
  import { CORE_SKILL_TOOL_NAME } from '../generated/toolNameTables.js';
5
+ import { inSlotReadOf, inValueOf } from '../wireFailureShape.js';
5
6
  import { toolPermissionRequestIdDomain } from '../seatContract.js';
6
7
  import { permissionsDeclarationReport } from '../permissionsDeclaration.js';
7
8
  import { readCardUnreadableEvidence, sessionAllowCardReadable, sessionAllowEligible } from './sessionAllowRoute.js';
@@ -15,9 +16,10 @@ function ownData(v, key) {
15
16
  return undefined;
16
17
  }
17
18
  }
18
- function hasKey(v, key) {
19
+ function candidateSlot(v, key) {
19
20
  try {
20
- return { inCard: key in v };
21
+ const s = inSlotReadOf(v, key);
22
+ return s.state === 'unproven' ? UNREADABLE : s;
21
23
  }
22
24
  catch {
23
25
  return UNREADABLE;
@@ -25,8 +27,10 @@ function hasKey(v, key) {
25
27
  }
26
28
  function present(v, key) {
27
29
  try {
28
- const inCard = key in v;
29
- return { inCard, value: inCard ? v[key] : undefined };
30
+ const s = inValueOf(v, key);
31
+ if (s.state === 'unproven')
32
+ return UNREADABLE;
33
+ return s.state === 'present' ? { inCard: true, value: s.value } : { inCard: false, value: undefined };
30
34
  }
31
35
  catch {
32
36
  return UNREADABLE;
@@ -42,12 +46,12 @@ function persistentRowOf(card, host, evidenceUnreadable) {
42
46
  if (evidenceUnreadable)
43
47
  return { row: 'none', why: 'evidence_unreadable' };
44
48
  for (const key of ['ruleOffers', 'ruleOffersReadOnly']) {
45
- const p = hasKey(card, key);
49
+ const p = candidateSlot(card, key);
46
50
  if (p === UNREADABLE)
47
51
  return UNREADABLE;
48
- if (!p.inCard)
52
+ if (p.state === 'absent')
49
53
  continue;
50
- const offers = readRuleOffers(ownData(card, key));
54
+ const offers = readRuleOffers(p.state === 'present' ? p.value : undefined);
51
55
  if (offers === undefined || offers.length === 0)
52
56
  return { row: 'none', why: 'offers_unreadable' };
53
57
  return { row: 'engine_offer', why: 'given' };
@@ -69,7 +73,7 @@ function persistentRowOf(card, host, evidenceUnreadable) {
69
73
  return { row: 'none', why: 'tool_name_unreadable' };
70
74
  if (tool === CORE_SKILL_TOOL_NAME)
71
75
  return { row: 'none', why: 'grant_binding' };
72
- if (automaticAnswerVerdict(card, 'standing_rule').kind !== 'may_answer')
76
+ if (askLevelVerdict(card, 'standing_rule').kind !== 'may_answer')
73
77
  return { row: 'none', why: 'rule_cannot_clear' };
74
78
  const face = readApprovalCardWord(card);
75
79
  if (face === undefined)
@@ -149,6 +153,20 @@ export function seatSessionRowOf(card, requestId) {
149
153
  return false;
150
154
  }
151
155
  }
156
+ export function seatAllowNoteCarriedOf(card, requestId) {
157
+ if (typeof card !== 'object' || card === null)
158
+ return false;
159
+ try {
160
+ if (Object.hasOwn(card, 'requestId'))
161
+ return false;
162
+ if (toolPermissionRequestIdDomain(requestId) !== 'durable:')
163
+ return false;
164
+ return answerCarriage(card).allowNote === 'model';
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ }
152
170
  const SESSION_OFFER = Object.freeze({
153
171
  given: 'offerable',
154
172
  card_unreadable: 'unknown',
@@ -178,8 +196,7 @@ export function rememberOfferable(card, host) {
178
196
  deploymentList: SESSION_OFFER[rows.why.deploymentList],
179
197
  });
180
198
  }
181
- export function localAllowRuleEnforceable(rule, caps, host) {
182
- const ladder = typeof host === 'object' && host !== null && ownData(host, 'hostLadderEnforces') === true;
199
+ export function localAllowRuleEnforceable(rule, caps) {
183
200
  let engine = 'unreadable';
184
201
  let keptReason;
185
202
  if (typeof rule === 'string' && rule.trim() !== '') {
@@ -199,10 +216,8 @@ export function localAllowRuleEnforceable(rule, caps, host) {
199
216
  engine = 'unreadable';
200
217
  }
201
218
  }
202
- const by = ladder ? 'host_ladder' : engine === 'sent' ? 'engine_grant' : undefined;
203
219
  return Object.freeze({
204
- enforceable: by !== undefined,
205
- ...(by !== undefined ? { by } : {}),
220
+ enforceable: engine === 'sent',
206
221
  engine,
207
222
  ...(keptReason !== undefined ? { keptReason } : {}),
208
223
  });