@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,13 +10,13 @@ import { guardedRead, readOwnDataValue } from '../ownKey.js';
10
10
  import { parkHeldCallIdOf } from '../engineRowRef.js';
11
11
  import { inheritFrameBinding } from '../frameBinding.js';
12
12
  import { surfaceForCurrentSession, surfaceReplayedApprovalListUnreadable } from './hitlHostSurface.js';
13
- import { liveCardHoldOf, noteEngineClosedApproval, settleEngineClosedApproval } from './approvalHolds.js';
13
+ import { liveCardHoldOf, settleEngineClosedApproval } from './approvalHolds.js';
14
14
  import { approvalCompleteParked } from './approvalCompleteParked.js';
15
15
  import { liveFrameCallKey } from './gateIdentity.js';
16
16
  import { readPolicyDenyCause } from './policyDenyCause.js';
17
17
  import { msg, renderMessage } from '../i18n/translate.js';
18
18
  import { SEMA_DENIAL_KIND_KEY, hasLedgerPositionKey } from './gateLedger.js';
19
- import { replayedApprovalClosesCardFrame, replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
19
+ import { replayedApprovalDisposition, settledApprovalDisposition } from './replayedApproval.js';
20
20
  import { settleWithinDeadline } from '../abortableSleep.js';
21
21
  import { ccToolDenialKindForSettledBy } from '../gateVocabulary.js';
22
22
  import { REJECT_MESSAGE } from '../adapt/wireShapes.js';
@@ -32,7 +32,11 @@ export function policyDenyReason(facts) {
32
32
  const one = typeof facts.toolName === 'string' && SENTENCE_TOOL_NAME.test(facts.toolName) ? `this one "${facts.toolName}" call` : 'this one call';
33
33
  const flag = typeof facts.allowRuleFlag === 'string' && SENTENCE_TOOL_NAME.test(facts.allowRuleFlag.replace(/^--?/, '')) ? ` (or ${facts.allowRuleFlag})` : '';
34
34
  const budgetKnob = typeof facts.budgetSetting === 'string' && SENTENCE_TOOL_NAME.test(facts.budgetSetting) ? facts.budgetSetting : undefined;
35
- const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'engine_ask_rule' || facts.cause === 'budget' ? facts.cause : 'other';
35
+ const cause = facts.cause === 'no_allow_rule' || facts.cause === 'ask_rule' || facts.cause === 'engine_ask_rule' || facts.cause === 'budget' || facts.cause === 'whole_tool_rule_only'
36
+ ? facts.cause
37
+ : 'other';
38
+ const wholeToolOnly = 'this client does not approve calls from the arguments shown to it, so here only an allow rule for the whole tool can approve calls like this ahead of time — ' +
39
+ 'a rule that names paths, commands or other arguments applies only where the engine checks it against the real arguments';
36
40
  if (facts.lane === 'dont_ask') {
37
41
  const mode = 'denied by the dontAsk permission mode';
38
42
  switch (cause) {
@@ -44,6 +48,8 @@ export function policyDenyReason(facts) {
44
48
  return `${mode}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, and this mode refuses anything that would ask — the user's settings cannot approve it; use another permission mode to answer it`;
45
49
  case 'budget':
46
50
  return `${mode}: ${one} needed approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls${budgetKnob !== undefined ? ` — raise ${budgetKnob} before launch to change the cap` : ''}`;
51
+ case 'whole_tool_rule_only':
52
+ return `${mode}: ${one} needed approval; ${wholeToolOnly} — and this mode refuses anything that would ask; use another permission mode to answer it`;
47
53
  default:
48
54
  return `${mode}: ${one} needed a person's approval that saved permission rules cannot approve here (the engine or your deployment requires a person for it, or it could not be checked against your rules), and this mode refuses anything that would ask — use another permission mode to answer it`;
49
55
  }
@@ -59,6 +65,8 @@ export function policyDenyReason(facts) {
59
65
  return `${lane}: a rule on the engine side, not one of the user's permission rules, asks about ${one}, so it needed a person's answer, and ${tail}; the user's settings cannot approve it - run the task interactively to answer it`;
60
66
  case 'budget':
61
67
  return `${lane}: ${one} needed a person's approval, and this session's automatic-approval budget is used up, so saved rules no longer approve calls; ${tail}${budgetKnob !== undefined ? `; raise ${budgetKnob} before launch to change the cap` : ''}`;
68
+ case 'whole_tool_rule_only':
69
+ return `${lane}: ${one} needed a person's approval; ${wholeToolOnly}; ${tail}; run the task interactively to answer it`;
62
70
  default:
63
71
  return `${lane}: ${one} needed a person's approval, and saved permission rules cannot approve it here (the engine or your deployment requires a person for it, or it could not be checked against your rules); ${tail}; run the task interactively to answer it`;
64
72
  }
@@ -341,7 +349,7 @@ async function routeToolApprovalFrame(ev, ctx) {
341
349
  return { kind: 'skip' };
342
350
  }
343
351
  const settledHere = settledApprovalDisposition(ev);
344
- if (settledHere !== undefined || (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId))) {
352
+ if (settledHere !== undefined || hasLedgerPositionKey(ev)) {
345
353
  const disposition = settledHere ?? (await judgeLedgerApprovalFrame(ev, ctx));
346
354
  if (disposition.kind !== 'live') {
347
355
  hostLog('debug', disposition.kind === 'decided' && disposition.settled !== undefined
@@ -349,8 +357,6 @@ async function routeToolApprovalFrame(ev, ctx) {
349
357
  : `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
350
358
  `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
351
359
  ' — not raised as a question, no decision sent');
352
- if (settledHere === undefined && replayedApprovalClosesCardFrame(disposition))
353
- noteEngineClosedApproval(ev.approvalId, false);
354
360
  if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
355
361
  surfaceReplayedApprovalListUnreadable();
356
362
  if (deps.onReplayedApproval !== undefined) {
@@ -408,7 +414,7 @@ async function judgeLedgerApprovalFrame(ev, ctx) {
408
414
  .finally(release);
409
415
  });
410
416
  const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
411
- return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals });
417
+ return replayedApprovalDisposition(ev, { approvals });
412
418
  }
413
419
  function routeToolStart(ev, callId, led) {
414
420
  led.noteStart(callId, ev);
@@ -503,17 +509,5 @@ export async function routeFrame(ev, ctx) {
503
509
  if (ev.type === 'failed') {
504
510
  return { kind: 'end', events: [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal }), ev] };
505
511
  }
506
- if (ev.type === 'approval_request') {
507
- try {
508
- if (!hasLedgerPositionKey(ev)) {
509
- const id = ev.approvalId;
510
- if (typeof id === 'string' && id !== '')
511
- led.notePreambleApproval(id);
512
- }
513
- }
514
- catch (e) {
515
- hostLog('debug', `liveHitlAskWire: approval_request frame unreadable — not counted as preamble evidence: ${String(e).slice(0, 120)}`);
516
- }
517
- }
518
512
  return { kind: 'yield', events: [ev] };
519
513
  }
@@ -18,14 +18,10 @@ export interface AskAnsweredOutput {
18
18
  export declare function durableSeqOf(ev: object): string | undefined;
19
19
  export declare const BODY_SEQ_OTHER_AXIS_EVENTS: readonly string[];
20
20
  export declare function hasLedgerPositionKey(ev: object): boolean;
21
- export declare const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
22
21
  export interface GateLedger {
23
22
  noteSeq(ev: AgentEvent): void;
24
23
  lastSeq(): string | undefined;
25
24
  forgetRunScopedState(): void;
26
- notePreambleApproval(approvalId: string): void;
27
- preambleApprovals(): ReadonlySet<string>;
28
- beginStreamSegment(): void;
29
25
  trackLiveCard(phaseOf: () => 'card' | 'respond', handled: Promise<void>): void;
30
26
  answeringLiveCards(): Promise<void>[];
31
27
  liveCardsSettled(): Promise<void>;
@@ -21,7 +21,6 @@ export function hasLedgerPositionKey(ev) {
21
21
  const { id, seq } = ev;
22
22
  return (id !== undefined && id !== null) || (seq !== undefined && seq !== null);
23
23
  }
24
- export const PREAMBLE_APPROVALS_MAX_ENTRIES = 4096;
25
24
  export const START_ARGS_MAX_ENTRIES = 256;
26
25
  function normalizeDenyAttribution(a) {
27
26
  if (a === undefined)
@@ -108,7 +107,6 @@ export function createGateLedger() {
108
107
  let denyStampNextFsEnd = false;
109
108
  let denyStampAttribution;
110
109
  let seq;
111
- const preambleApprovalIds = new Set();
112
110
  const liveCards = new Set();
113
111
  return {
114
112
  noteSeq(ev) {
@@ -125,23 +123,6 @@ export function createGateLedger() {
125
123
  startArgsByCall.clear();
126
124
  gatedStartCalls.clear();
127
125
  },
128
- notePreambleApproval(approvalId) {
129
- if (typeof approvalId !== 'string' || approvalId === '')
130
- return;
131
- preambleApprovalIds.delete(approvalId);
132
- preambleApprovalIds.add(approvalId);
133
- if (preambleApprovalIds.size > PREAMBLE_APPROVALS_MAX_ENTRIES) {
134
- const oldest = preambleApprovalIds.values().next().value;
135
- if (oldest !== undefined)
136
- preambleApprovalIds.delete(oldest);
137
- }
138
- },
139
- preambleApprovals() {
140
- return preambleApprovalIds;
141
- },
142
- beginStreamSegment() {
143
- preambleApprovalIds.clear();
144
- },
145
126
  trackLiveCard(phaseOf, handled) {
146
127
  const entry = { phaseOf, handled };
147
128
  liveCards.add(entry);
@@ -141,6 +141,10 @@ export type FindPendingOutcome = {
141
141
  export declare function findPendingForTask(client: HitlClientLike, taskId: string, matches: (toolName: string | undefined) => boolean, opts?: {
142
142
  signal?: AbortSignal;
143
143
  }, callId?: string, identityAccepts?: (row: PendingCheckpoint) => boolean): Promise<FindPendingOutcome>;
144
+ export declare function checkpointQueueReadingOf(row: object): {
145
+ readonly r: PendingCheckpoint;
146
+ readonly foreign: string | null;
147
+ } | undefined;
144
148
  export declare class HitlBridge {
145
149
  private readonly client;
146
150
  private readonly taskId;
@@ -156,6 +160,7 @@ export declare class HitlBridge {
156
160
  decision: 'approve';
157
161
  updatedInput?: unknown;
158
162
  remember?: 'session' | undefined;
163
+ feedback?: string | undefined;
159
164
  } | HitlDecideDenyOutcome, toolUseID?: string, opts?: {
160
165
  signal?: AbortSignal;
161
166
  operation?: DecideOperationSpan;
@@ -270,11 +270,8 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
270
270
  return { ok: false, reason: `approvals.list failed: ${t.reason}` };
271
271
  }
272
272
  const readings = rows.flatMap((r) => {
273
- const sort = checkpointQueueRowSortOf(r);
274
- if (sort === 'excluded')
275
- return [];
276
- const cp = r;
277
- return [{ r: cp, foreign: sort === 'unreadable' ? QUEUE_ROW_FAMILY_UNREADABLE_GATE : askParkForeignGateKind(cp) }];
273
+ const x = checkpointQueueReadingOf(r);
274
+ return x === undefined ? [] : [x];
278
275
  });
279
276
  const decidable = readings.filter((x) => x.foreign === null).map((x) => x.r);
280
277
  const identityRow = callId !== undefined
@@ -313,6 +310,13 @@ export async function findPendingForTask(client, taskId, matches, opts, callId,
313
310
  return { ok: false, reason: checkpointRowBarSentence(bar), code: 'row_unresumable', rowVerdict: bar };
314
311
  return { ok: true, pending, gatedCallId: pending.toolCallId ?? pending.boundCallId ?? undefined };
315
312
  }
313
+ export function checkpointQueueReadingOf(row) {
314
+ const sort = checkpointQueueRowSortOf(row);
315
+ if (sort === 'excluded')
316
+ return undefined;
317
+ const cp = row;
318
+ return { r: cp, foreign: sort === 'unreadable' ? QUEUE_ROW_FAMILY_UNREADABLE_GATE : askParkForeignGateKind(cp) };
319
+ }
316
320
  export class HitlBridge {
317
321
  client;
318
322
  taskId;
@@ -381,6 +385,7 @@ export class HitlBridge {
381
385
  ...this.bindingOf(pending),
382
386
  ...(outcome.updatedInput !== undefined ? { updatedInput: outcome.updatedInput } : {}),
383
387
  ...(outcome.remember !== undefined ? { remember: outcome.remember } : {}),
388
+ ...(outcome.feedback !== undefined ? { feedback: outcome.feedback } : {}),
384
389
  }
385
390
  : {
386
391
  decision: 'deny',
@@ -594,12 +599,11 @@ function bindingMismatchReading(e) {
594
599
  return 'not';
595
600
  const code = readWireErrorCodeReading(e);
596
601
  const nameReading = protocolErrorNameReading(e);
597
- const family = (code.state === 'code' && (code.code === APPROVAL_BINDING_MISMATCH || code.code === CHECKPOINT_INVALID_OUTCOME)) ||
598
- (nameReading.state === 'named' && nameReading.name === PROTOCOL_ERROR_NAME.ApprovalBindingMismatchError);
602
+ const codeReclassified = code.state === 'code' && code.code === CHECKPOINT_INVALID_OUTCOME;
603
+ const family = (code.state === 'code' && code.code === APPROVAL_BINDING_MISMATCH) ||
604
+ (!codeReclassified && nameReading.state === 'named' && nameReading.name === PROTOCOL_ERROR_NAME.ApprovalBindingMismatchError);
599
605
  if (family)
600
606
  return 'mismatch';
601
- if (nameReading.state === 'sdk-instance' && code.state !== 'code')
602
- return 'unreadable';
603
607
  if (code.state === 'unreadable')
604
608
  return 'unreadable';
605
609
  return 'not';
@@ -28,6 +28,7 @@ export declare const CANCEL_DENY_WARN_TEXT = "could not cancel the pending quest
28
28
  export declare const REMEMBER_NOT_APPLIED_WARN_TEXT = "this session-wide allow was not remembered by the worker \u2014 you will be asked again for this tool";
29
29
  export declare const EDIT_NOT_FORWARDED_WARN_TEXT = "your edited tool input was NOT forwarded \u2014 the tool is running with the ORIGINAL input you saw before editing";
30
30
  export declare function surfaceRememberNotApplied(): void;
31
+ export declare function surfaceAllowNoteNotCarried(sentence: string): void;
31
32
  export declare function surfaceEditNotForwarded(): void;
32
33
  export declare const APPROVAL_PARKING_WARN_TEXT = "This approval is being parked; answer it again on the parked card when it appears.";
33
34
  export declare function approvalParkingNoticeText(retryAfterMs?: number): string;
@@ -2,6 +2,7 @@ import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
2
2
  import { hostLog } from '../host.js';
3
3
  import { unrefTimer } from '../unrefTimer.js';
4
4
  import { decideFailureNote } from '../decideFailureNote.js';
5
+ import { editedApprovalRefusedLine } from './editedApprovalAcceptance.js';
5
6
  import { decisionRefusalSaysAskGone } from './decisionRefusal.js';
6
7
  const hostSurfaceByKey = createSessionSlot();
7
8
  const hostSurfaceMissesByKey = new Map();
@@ -73,6 +74,11 @@ const EDIT_NOT_FORWARDED_TIMEOUT_MS = 20_000;
73
74
  export function surfaceRememberNotApplied() {
74
75
  surfaceSelfClearingWarn(REMEMBER_NOT_APPLIED_KEY, REMEMBER_NOT_APPLIED_WARN_TEXT, REMEMBER_NOT_APPLIED_TIMEOUT_MS);
75
76
  }
77
+ const ALLOW_NOTE_NOT_CARRIED_KEY = 'hitl-allow-note-not-carried';
78
+ const ALLOW_NOTE_NOT_CARRIED_TIMEOUT_MS = 10_000;
79
+ export function surfaceAllowNoteNotCarried(sentence) {
80
+ surfaceSelfClearingWarn(ALLOW_NOTE_NOT_CARRIED_KEY, sentence, ALLOW_NOTE_NOT_CARRIED_TIMEOUT_MS);
81
+ }
76
82
  export function surfaceEditNotForwarded() {
77
83
  surfaceSelfClearingWarn(EDIT_NOT_FORWARDED_KEY, EDIT_NOT_FORWARDED_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
78
84
  }
@@ -100,8 +106,7 @@ const REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS = 10_000;
100
106
  export function surfaceReplayedApprovalListUnreadable() {
101
107
  surfaceSelfClearingWarn(REPLAYED_APPROVAL_LIST_UNREADABLE_KEY, REPLAYED_APPROVAL_LIST_UNREADABLE_WARN_TEXT, REPLAYED_APPROVAL_LIST_UNREADABLE_TIMEOUT_MS);
102
108
  }
103
- export const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT = 'That approval was NOT sent: this request does not expose its tool arguments here, so an edited approval cannot be applied. ' +
104
- 'The request is still waiting — approve or deny it as it is.';
109
+ export const EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT = editedApprovalRefusedLine();
105
110
  const EDIT_REFUSED_ON_BLIND_ASK_KEY = 'hitl-edit-refused-blind-ask';
106
111
  export function surfaceEditRefusedOnBlindAsk() {
107
112
  surfaceSelfClearingWarn(EDIT_REFUSED_ON_BLIND_ASK_KEY, EDIT_REFUSED_ON_BLIND_ASK_WARN_TEXT, EDIT_NOT_FORWARDED_TIMEOUT_MS);
@@ -0,0 +1,15 @@
1
+ import { type CheckpointRowBar } from '../checkpointRowVerdict.js';
2
+ export interface HostWithdrawal {
3
+ readonly toldBy: 'host';
4
+ readonly rowVerdict: CheckpointRowBar;
5
+ }
6
+ export declare function hostWithdrawalOf(v: object | null | undefined): HostWithdrawal | undefined;
7
+ export declare const PARK_QUESTION_WITHDRAW_REFUSALS: readonly ["malformed", "no_card", "not_withdrawable"];
8
+ export type ParkQuestionWithdrawRefusal = (typeof PARK_QUESTION_WITHDRAW_REFUSALS)[number];
9
+ export type ParkQuestionWithdrawal = {
10
+ readonly withdrawn: true;
11
+ } | {
12
+ readonly withdrawn: false;
13
+ readonly refusal: ParkQuestionWithdrawRefusal;
14
+ };
15
+ export declare function withdrawParkQuestion(questionId: string, withdrawal: HostWithdrawal): ParkQuestionWithdrawal;
@@ -0,0 +1,38 @@
1
+ import { slotReadOf } from '../wireFailureShape.js';
2
+ import { checkpointRowBarOf } from '../checkpointRowVerdict.js';
3
+ import { takeLocalQuestionWithdrawHook } from '../liveQuestionStore.js';
4
+ import { hostLog } from '../host.js';
5
+ export function hostWithdrawalOf(v) {
6
+ try {
7
+ if (typeof v !== 'object' || v === null)
8
+ return undefined;
9
+ const told = slotReadOf(v, 'toldBy');
10
+ if (told.presence !== 'present' || told.value !== 'host')
11
+ return undefined;
12
+ if (slotReadOf(v, 'rowVerdict').presence !== 'present')
13
+ return undefined;
14
+ const bar = checkpointRowBarOf(v);
15
+ return bar === undefined ? undefined : Object.freeze({ toldBy: 'host', rowVerdict: bar });
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ }
21
+ export const PARK_QUESTION_WITHDRAW_REFUSALS = Object.freeze(['malformed', 'no_card', 'not_withdrawable']);
22
+ const WITHDRAWN = Object.freeze({ withdrawn: true });
23
+ const refused = (refusal) => Object.freeze({ withdrawn: false, refusal });
24
+ export function withdrawParkQuestion(questionId, withdrawal) {
25
+ const told = hostWithdrawalOf(withdrawal);
26
+ if (told === undefined)
27
+ return refused('malformed');
28
+ const taken = takeLocalQuestionWithdrawHook(questionId);
29
+ if (taken === 'no_card' || taken === 'not_withdrawable')
30
+ return refused(taken);
31
+ try {
32
+ taken(told);
33
+ }
34
+ catch (e) {
35
+ hostLog('debug', `hostWithdrawal: withdraw-hook-threw card=${questionId} thrown=${e instanceof Error ? e.name : typeof e}`);
36
+ }
37
+ return WITHDRAWN;
38
+ }
@@ -1,10 +1,10 @@
1
1
  import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
2
- import { appendOwnElement } from '../ownKey.js';
2
+ import { appendOwnElement, readOwnDataNonEmptyString } from '../ownKey.js';
3
3
  import { readRequiresRealApproval } from './approvalCardReading.js';
4
4
  import { pendingRowIsOfSession } from './parkRowDelegation.js';
5
5
  import { approvalLiveFaceOf, liveFaceCarried } from './approvalLiveFace.js';
6
6
  import { THROWN_SLOT_UNREADABLE, readThrownSlot } from '../wireFailureShape.js';
7
- import { isToolApprovalFrame, surfaceToolApprovalFrameAndRespond, } from './toolApprovalWire.js';
7
+ import { isToolApprovalFrame, surfaceApprovalFrameWithId, } from './toolApprovalWire.js';
8
8
  function nonEmptyString(v) {
9
9
  return typeof v === 'string' && v !== '';
10
10
  }
@@ -24,7 +24,9 @@ function snapshotMembers(raw) {
24
24
  }
25
25
  return raw.length === length ? out : undefined;
26
26
  }
27
- function openingFrameOf(f) {
27
+ function openingFrameOf(f, approvalId) {
28
+ if (typeof f !== 'object' || f === null || readOwnDataNonEmptyString(f, 'approvalId') !== approvalId)
29
+ return undefined;
28
30
  if (isToolApprovalFrame(f))
29
31
  return f.type === 'tool_approval' ? f : undefined;
30
32
  return isApprovalRequestFrameV1(f) ? f : undefined;
@@ -54,19 +56,19 @@ export function readLivePendingRows(body) {
54
56
  continue;
55
57
  }
56
58
  const o = item;
57
- if (!nonEmptyString(o.approvalId) || !nonEmptyString(o.toolName) || !nonNegativeFinite(o.ts) || !nonNegativeFinite(o.expiresAtMs)) {
59
+ const approvalId = readOwnDataNonEmptyString(o, 'approvalId');
60
+ if (approvalId === undefined || !nonEmptyString(o.toolName) || !nonNegativeFinite(o.ts) || !nonNegativeFinite(o.expiresAtMs)) {
58
61
  dropped++;
59
62
  continue;
60
63
  }
61
- if (seen.has(o.approvalId)) {
64
+ if (seen.has(approvalId)) {
62
65
  dropped++;
63
66
  continue;
64
67
  }
65
- seen.add(o.approvalId);
66
- const opening = openingFrameOf(o.frame);
67
- const frame = opening !== undefined && opening.approvalId === o.approvalId ? opening : undefined;
68
- rows.push({
69
- approvalId: o.approvalId,
68
+ seen.add(approvalId);
69
+ const frame = openingFrameOf(o.frame, approvalId);
70
+ appendOwnElement(rows, {
71
+ approvalId,
70
72
  toolName: o.toolName,
71
73
  ts: o.ts,
72
74
  expiresAtMs: o.expiresAtMs,
@@ -147,13 +149,12 @@ export function livePendingRowToApprovalFrame(row) {
147
149
  };
148
150
  }
149
151
  export function surfaceSuspendedAskAndRespond(row, respond, signal, lane) {
150
- if (row.frame !== undefined) {
151
- return surfaceToolApprovalFrameAndRespond(row.frame, respond, undefined, signal, lane);
152
+ const frame = row.frame;
153
+ const approvalId = row.approvalId;
154
+ if (frame !== undefined) {
155
+ return surfaceApprovalFrameWithId(frame, approvalId, respond, undefined, signal, lane, undefined);
152
156
  }
153
- return surfaceToolApprovalFrameAndRespond(livePendingRowToApprovalFrame(row), respond, undefined, signal, {
154
- ...lane,
155
- argsUnavailable: true,
156
- });
157
+ return surfaceApprovalFrameWithId(livePendingRowToApprovalFrame(row), approvalId, respond, undefined, signal, { ...lane, argsUnavailable: true }, undefined);
157
158
  }
158
159
  const TRACKER_UNSEEN_CLAIMS_MAX = 512;
159
160
  export function createSuspendedAskTracker(filter) {
@@ -1,4 +1,5 @@
1
1
  import { reasonOfThrownWithSink } from '../hostCallbackGuard.js';
2
+ import { isWholeToolRuleValue } from './permissionRuleScope.js';
2
3
  const BASH_TOOL_NAME_FOR_RULES = 'Bash';
3
4
  export function parseLocalAllowRule(rule, expectedToolName, deps) {
4
5
  if (typeof rule !== 'string' || rule === '') {
@@ -14,7 +15,7 @@ export function parseLocalAllowRule(rule, expectedToolName, deps) {
14
15
  if (typeof value.toolName !== 'string' || value.toolName === '') {
15
16
  return { ok: false, error: 'rule text carries no tool name' };
16
17
  }
17
- if (value.ruleContent === undefined || value.ruleContent === '') {
18
+ if (isWholeToolRuleValue(value)) {
18
19
  return {
19
20
  ok: false,
20
21
  error: 'candidate is a whole-tool allow rule (broader than this ask) — not offered locally',
@@ -1,4 +1,4 @@
1
- export type NoAllowCause = 'no_allow_rule' | 'ask_rule' | 'engine_ask_rule' | 'budget' | 'other';
1
+ export type NoAllowCause = 'no_allow_rule' | 'ask_rule' | 'engine_ask_rule' | 'budget' | 'whole_tool_rule_only' | 'other';
2
2
  export interface RuleLadderNoAllowFacts {
3
3
  readonly allowed?: boolean;
4
4
  readonly denied?: boolean;
@@ -1,4 +1,4 @@
1
- import { automaticAnswerVerdict } from './automaticAnswerVerdict.js';
1
+ import { askLevelVerdict } from './automaticAnswerVerdict.js';
2
2
  function ladderSays(ladder, key) {
3
3
  if (typeof ladder !== 'object' || ladder === null)
4
4
  return false;
@@ -17,11 +17,13 @@ export function noAllowCauseOf(card, ladder) {
17
17
  return 'ask_rule';
18
18
  if (ladderSays(ladder, 'budgetExhausted'))
19
19
  return 'budget';
20
- const standing = automaticAnswerVerdict(card, 'standing_rule');
20
+ const standing = askLevelVerdict(card, 'standing_rule');
21
21
  if (standing.kind === 'must_ask' && (standing.askClass === 'ask_rule' || standing.origin === 'ask_rule')) {
22
22
  return ladderSays(ladder, 'noAskRuleMatched') || ladderSays(ladder, 'noRuleMatched') ? 'engine_ask_rule' : 'ask_rule';
23
23
  }
24
24
  if (ladderSays(ladder, 'noRuleMatched') && standing.kind === 'may_answer')
25
25
  return 'no_allow_rule';
26
+ if (standing.kind === 'may_answer')
27
+ return 'whole_tool_rule_only';
26
28
  return 'other';
27
29
  }
@@ -1,11 +1,36 @@
1
+ import { isApprovalCompleteFrameV1 } from '@sema-agent/sdk';
1
2
  import { APPROVAL_EXPIRED } from '../engineErrorCodes.js';
2
3
  import { approvalCompleteParked } from './approvalCompleteParked.js';
3
4
  export function classifyApprovalCompleteFrame(frame) {
4
5
  if (typeof frame !== 'object' || frame === null)
5
6
  return null;
6
7
  const f = frame;
7
- if (f.type !== 'tool_approval_complete')
8
+ const type = f.type;
9
+ if (type === 'tool_approval_complete')
10
+ return dispositionOf(frame, f);
11
+ if (type === 'approval_complete')
12
+ return presentedFamilyDisposition(frame);
13
+ return null;
14
+ }
15
+ function presentedFamilyDisposition(frame) {
16
+ try {
17
+ const snapshot = ownDataSnapshot(frame);
18
+ return isApprovalCompleteFrameV1(snapshot) ? dispositionOf(snapshot, snapshot) : null;
19
+ }
20
+ catch {
8
21
  return null;
22
+ }
23
+ }
24
+ function ownDataSnapshot(frame) {
25
+ const snapshot = Object.create(null);
26
+ for (const key of Object.getOwnPropertyNames(frame)) {
27
+ const d = Object.getOwnPropertyDescriptor(frame, key);
28
+ if (d !== undefined && Object.hasOwn(d, 'value'))
29
+ snapshot[key] = d.value;
30
+ }
31
+ return snapshot;
32
+ }
33
+ function dispositionOf(frame, f) {
9
34
  const outcome = f.outcome;
10
35
  if (outcome === 'allowed' || outcome === 'denied')
11
36
  return 'settled';
@@ -6,5 +6,7 @@ export interface ParkOwnershipDeps {
6
6
  isOwnRun?: (taskIdTail: string) => boolean;
7
7
  }
8
8
  export declare function pendingRowIsOwnedByThisSession(row: PendingCheckpoint, deps?: ParkOwnershipDeps): boolean;
9
+ export declare function pendingRowOwnershipOf(row: object, deps?: ParkOwnershipDeps): 'owned' | 'foreign' | 'unknown';
10
+ export declare function usableAskQuestions(value: unknown): AskQuestion[] | null;
9
11
  export declare function askQuestionsFromPending(pending: PendingCheckpoint): AskQuestion[] | null;
10
12
  export declare function isHitlSafetyErrorLike(e: unknown): boolean;
@@ -3,31 +3,71 @@ import { rowIdTail } from '../workflow.js';
3
3
  import { hostSessionFor } from '../host.js';
4
4
  import { DEFAULT_SESSION_KEY } from '../sessionSlot.js';
5
5
  import { pendingRowIsOfSession } from './parkRowDelegation.js';
6
+ import { slotReadOf } from '../wireFailureShape.js';
6
7
  export function pendingRowIsOwnedByThisSession(row, deps) {
8
+ return ownershipVerdictOf(row, deps) === 'owned';
9
+ }
10
+ export function pendingRowOwnershipOf(row, deps) {
11
+ try {
12
+ return ownershipVerdictOf(row, deps);
13
+ }
14
+ catch {
15
+ return 'unknown';
16
+ }
17
+ }
18
+ function ownershipVerdictOf(row, deps) {
7
19
  const sessionKey = deps?.sessionKey ?? DEFAULT_SESSION_KEY;
8
20
  const rowTaskId = typeof row.taskId === 'string' ? row.taskId : '';
9
21
  if (rowTaskId.length > 0) {
10
22
  const tail = rowIdTail(rowTaskId);
11
23
  if (deps?.isOwnRun !== undefined && deps.isOwnRun(tail))
12
- return true;
24
+ return 'owned';
13
25
  if (sessionKey === DEFAULT_SESSION_KEY && isOwnEngineRun(tail))
14
- return true;
26
+ return 'owned';
15
27
  }
16
28
  const currentSessionId = deps?.currentSessionId ?? (() => hostSessionFor(sessionKey)?.currentSessionId());
17
29
  const sessionId = currentSessionId();
18
30
  if (typeof sessionId !== 'string' || sessionId.length === 0)
31
+ return 'unknown';
32
+ if (pendingRowIsOfSession(row, sessionId))
33
+ return 'owned';
34
+ return rowIsProvablyForeign(row, sessionId) ? 'foreign' : 'unknown';
35
+ }
36
+ function rowIsProvablyForeign(row, selfSessionId) {
37
+ const own = slotReadOf(row, 'sessionId');
38
+ if (own.presence !== 'present' || typeof own.value !== 'string' || own.value === '' || own.value === selfSessionId)
39
+ return false;
40
+ const agent = slotReadOf(row, 'agent');
41
+ if (agent.presence === 'absent')
42
+ return true;
43
+ if (agent.presence !== 'present' || typeof agent.value !== 'object' || agent.value === null)
19
44
  return false;
20
- return pendingRowIsOfSession(row, sessionId);
45
+ for (const key of ['parentSessionId', 'rootSessionId']) {
46
+ const link = slotReadOf(agent.value, key);
47
+ if (link.presence === 'absent')
48
+ continue;
49
+ if (link.presence !== 'present' || typeof link.value !== 'string' || link.value === selfSessionId)
50
+ return false;
51
+ }
52
+ return true;
53
+ }
54
+ export function usableAskQuestions(value) {
55
+ if (!Array.isArray(value) || value.length === 0)
56
+ return null;
57
+ for (let i = 0; i < value.length; i++) {
58
+ if (!Object.hasOwn(value, i))
59
+ return null;
60
+ const q = value[i];
61
+ if (typeof q !== 'object' || q === null || typeof q.question !== 'string')
62
+ return null;
63
+ }
64
+ return value;
21
65
  }
22
66
  export function askQuestionsFromPending(pending) {
23
67
  const input = pending.input;
24
68
  if (typeof input !== 'object' || input === null)
25
69
  return null;
26
- const qs = input.questions;
27
- if (!Array.isArray(qs) || qs.length === 0)
28
- return null;
29
- const ok = qs.every(q => typeof q === 'object' && q !== null && typeof q.question === 'string');
30
- return ok ? qs : null;
70
+ return usableAskQuestions(input.questions);
31
71
  }
32
72
  export function isHitlSafetyErrorLike(e) {
33
73
  return typeof e === 'object' && e !== null && e.name === 'HitlSafetyError';