@sema-agent/client-core 0.93.0 → 0.94.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 (156) hide show
  1. package/CHANGELOG.md +261 -0
  2. package/README.md +31 -18
  3. package/dist/adapt/arms.js +8 -5
  4. package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
  5. package/dist/agentSession/engineSessionBackgroundStop.js +2 -1
  6. package/dist/agentsWireCaps.d.ts +15 -0
  7. package/dist/agentsWireCaps.js +111 -1
  8. package/dist/approvalReachWire.d.ts +18 -0
  9. package/dist/approvalReachWire.js +58 -0
  10. package/dist/approvalsStreamLiveCapability.js +16 -11
  11. package/dist/callerCwdCapability.d.ts +16 -0
  12. package/dist/callerCwdCapability.js +47 -0
  13. package/dist/controlRouter.js +3 -10
  14. package/dist/coreValuePorts.d.ts +64 -1
  15. package/dist/coreValuePorts.js +30 -0
  16. package/dist/detachWire.d.ts +1 -4
  17. package/dist/detachWire.js +1 -1
  18. package/dist/deviceExecutorManagementCapability.js +39 -34
  19. package/dist/deviceFlowPolling.d.ts +5 -0
  20. package/dist/deviceFlowPolling.js +28 -0
  21. package/dist/displayUntrusted.js +652 -21
  22. package/dist/engineErrorCodes.d.ts +5 -1
  23. package/dist/engineErrorCodes.js +9 -12
  24. package/dist/engineNoticeCodes.d.ts +22 -2
  25. package/dist/engineNoticeCodes.js +73 -6
  26. package/dist/engineRefusedToolName.d.ts +1 -0
  27. package/dist/engineRefusedToolName.js +8 -0
  28. package/dist/executionLaneCapability.js +14 -9
  29. package/dist/fileHistoryCaptureCapability.js +14 -9
  30. package/dist/fileHistoryLegNotice.d.ts +11 -0
  31. package/dist/fileHistoryLegNotice.js +50 -13
  32. package/dist/fleet/fleetLedger.js +5 -0
  33. package/dist/gateOutcome.d.ts +2 -1
  34. package/dist/gateOutcome.js +3 -0
  35. package/dist/gateVocabulary.js +1 -1
  36. package/dist/generated/engineAskFaceTables.d.ts +3 -0
  37. package/dist/generated/engineAskFaceTables.js +12 -0
  38. package/dist/generated/engineFactTables.d.ts +4 -2
  39. package/dist/generated/engineFactTables.js +13 -0
  40. package/dist/generated/engineNoticeTables.d.ts +5 -0
  41. package/dist/generated/engineNoticeTables.js +7 -0
  42. package/dist/generated/sdkFactTables.d.ts +3 -0
  43. package/dist/generated/sdkFactTables.js +17 -0
  44. package/dist/generated/settingsSchemaTables.d.ts +3 -0
  45. package/dist/generated/settingsSchemaTables.js +3 -0
  46. package/dist/headlessPermissionModeWire.d.ts +2 -2
  47. package/dist/headlessPermissionModeWire.js +11 -21
  48. package/dist/hitl/approvalHolds.d.ts +36 -0
  49. package/dist/hitl/approvalHolds.js +89 -0
  50. package/dist/hitl/approvalOutcomeNote.js +2 -1
  51. package/dist/hitl/approvalResolution.d.ts +3 -2
  52. package/dist/hitl/approvalResolution.js +21 -0
  53. package/dist/hitl/approvalsFeed.js +5 -0
  54. package/dist/hitl/askGateWire.js +3 -0
  55. package/dist/hitl/automaticAnswerVerdict.d.ts +8 -2
  56. package/dist/hitl/automaticAnswerVerdict.js +69 -21
  57. package/dist/hitl/decisionRefusal.d.ts +19 -0
  58. package/dist/hitl/decisionRefusal.js +63 -0
  59. package/dist/hitl/frameRouter.d.ts +10 -0
  60. package/dist/hitl/frameRouter.js +59 -3
  61. package/dist/hitl/gateLedger.d.ts +3 -0
  62. package/dist/hitl/gateLedger.js +17 -0
  63. package/dist/hitl/hitlBridge.d.ts +2 -2
  64. package/dist/hitl/hitlBridge.js +8 -10
  65. package/dist/hitl/hitlHostSurface.d.ts +6 -0
  66. package/dist/hitl/hitlHostSurface.js +25 -4
  67. package/dist/hitl/localAllowRule.js +2 -1
  68. package/dist/hitl/noAllowCause.d.ts +9 -0
  69. package/dist/hitl/noAllowCause.js +26 -0
  70. package/dist/hitl/parkResolver.js +6 -3
  71. package/dist/hitl/parkRowBirthWait.js +2 -1
  72. package/dist/hitl/persistedRulesWire.d.ts +6 -1
  73. package/dist/hitl/persistedRulesWire.js +12 -4
  74. package/dist/hitl/planReviewWire.d.ts +0 -1
  75. package/dist/hitl/planReviewWire.js +30 -9
  76. package/dist/hitl/sessionPolicyDeliverable.d.ts +1 -1
  77. package/dist/hitl/sessionPolicyDeliverable.js +3 -8
  78. package/dist/hitl/sessionPolicyWire.js +1 -1
  79. package/dist/hitl/toolApprovalWire.d.ts +37 -2
  80. package/dist/hitl/toolApprovalWire.js +392 -13
  81. package/dist/humanTurnRead.d.ts +15 -1
  82. package/dist/humanTurnRead.js +28 -2
  83. package/dist/index.d.ts +18 -6
  84. package/dist/index.js +14 -5
  85. package/dist/limitsWire.d.ts +0 -1
  86. package/dist/limitsWire.js +2 -10
  87. package/dist/liveModelCatalog.d.ts +14 -0
  88. package/dist/liveModelCatalog.js +61 -0
  89. package/dist/mcpProbeCapability.js +14 -9
  90. package/dist/mcpReconnect.js +13 -8
  91. package/dist/memoryComplianceCapability.js +15 -10
  92. package/dist/memoryOriginCapability.js +15 -10
  93. package/dist/modelIdentityResolvability.d.ts +4 -2
  94. package/dist/modelIdentityResolvability.js +31 -4
  95. package/dist/outputStyleSeatCapability.d.ts +16 -0
  96. package/dist/outputStyleSeatCapability.js +47 -0
  97. package/dist/ownKey.d.ts +1 -0
  98. package/dist/ownKey.js +10 -0
  99. package/dist/peerLaneCapability.js +16 -11
  100. package/dist/permissionRuleIssue.js +1 -1
  101. package/dist/permissionRulesWriteCapability.js +16 -11
  102. package/dist/permissionsAllowGrantCapability.d.ts +2 -0
  103. package/dist/permissionsAllowGrantCapability.js +38 -16
  104. package/dist/permissionsDeclaration.d.ts +10 -1
  105. package/dist/permissionsDeclaration.js +97 -15
  106. package/dist/protocolSegment.d.ts +1 -0
  107. package/dist/protocolSegment.js +13 -1
  108. package/dist/request/taskRequest.d.ts +11 -3
  109. package/dist/request/taskRequest.js +13 -1
  110. package/dist/resultFrameKeys.d.ts +4 -0
  111. package/dist/resultFrameKeys.js +13 -0
  112. package/dist/resumeRefusalCopy.js +6 -16
  113. package/dist/retryStatus.js +6 -4
  114. package/dist/rewindArchiveCapability.js +13 -8
  115. package/dist/{hitl/rosterMountedNames.d.ts → rosterMountedNames.d.ts} +1 -1
  116. package/dist/runCancelContext.js +2 -1
  117. package/dist/sealedKeyCapability.js +19 -14
  118. package/dist/seatContract.d.ts +33 -5
  119. package/dist/seatContract.js +143 -2
  120. package/dist/sessionPolicyCapability.js +15 -10
  121. package/dist/sessionReadRedactionCapability.d.ts +16 -0
  122. package/dist/sessionReadRedactionCapability.js +47 -0
  123. package/dist/sessionSyncFileHistoryCapability.js +14 -9
  124. package/dist/skillToolListsCapability.js +14 -9
  125. package/dist/sqlEngineCapability.js +18 -13
  126. package/dist/steerPriorityCapability.js +21 -16
  127. package/dist/subagent/agentStateDetail.d.ts +1 -0
  128. package/dist/subagent/agentStateDetail.js +41 -0
  129. package/dist/subagent/engineCompactWire.d.ts +15 -0
  130. package/dist/subagent/engineCompactWire.js +75 -13
  131. package/dist/supportFloor.js +1 -1
  132. package/dist/taskRequestWords.d.ts +2 -4
  133. package/dist/taskRequestWords.js +3 -2
  134. package/dist/types/engineState.d.ts +1 -1
  135. package/dist/userAttachmentEnvelope.d.ts +22 -1
  136. package/dist/userAttachmentEnvelope.js +90 -2
  137. package/dist/userInvokedSkillsCapability.js +14 -9
  138. package/dist/webSearchBackendCapability.js +21 -16
  139. package/dist/wireErrorTriage.d.ts +11 -0
  140. package/dist/wireErrorTriage.js +35 -0
  141. package/dist/wireFailureShape.d.ts +6 -0
  142. package/dist/wireFailureShape.js +30 -3
  143. package/dist/wireRefusalCopy.d.ts +6 -1
  144. package/dist/wireRefusalCopy.js +22 -8
  145. package/dist/wiringInstructionsAttribution.d.ts +10 -0
  146. package/dist/wiringInstructionsAttribution.js +67 -0
  147. package/dist/workflowClient.js +22 -1
  148. package/dist/workflowsJournalCapability.js +14 -9
  149. package/dist/writeProtectionCapability.js +28 -23
  150. package/docs/INTEGRATION-CLIENTS.md +817 -45
  151. package/package.json +5 -5
  152. package/dist/askSurvivesStandingAnswer.d.ts +0 -13
  153. package/dist/askSurvivesStandingAnswer.js +0 -53
  154. package/dist/hitl/askSurvivesPosture.d.ts +0 -20
  155. package/dist/hitl/askSurvivesPosture.js +0 -48
  156. /package/dist/{hitl/rosterMountedNames.js → rosterMountedNames.js} +0 -0
@@ -1,17 +1,21 @@
1
1
  import { APPROVAL_CARD_PORT_KEY_MISMATCH_REASON, DEFAULT_DENY_REASON, DecideTransportRetryExhaustedError, HitlBridge, NO_APPROVAL_CARD_PORT_REASON, HitlSafetyError, denyReasonForWire, findPendingForTask, readDecideCurrentPending, readWireErrorCode, } from './hitlBridge.js';
2
2
  import { askParkRowArm } from './askParkRowRouting.js';
3
3
  import { hostLog } from '../host.js';
4
- import { readOwnDataValue, readOwnNonEmptyString } from '../ownKey.js';
4
+ import { readOwnDataNonEmptyString, readOwnDataValue, readOwnNonEmptyString } from '../ownKey.js';
5
5
  import { readAskClassPair } from './askClassPair.js';
6
6
  import { legacyShadowedRuleTextOf, readAllowRuleShadowFromWire } from './allowRuleShadow.js';
7
7
  import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
8
8
  import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
9
- import { observeCancelByDeny, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
9
+ import { INTERRUPT_DENY_REASON, observeCancelByDeny, surfaceApprovalParking, surfaceRememberNotApplied, surfaceEditNotForwarded, surfaceEditRefusedOnBlindAsk, surfaceRuleArmNotSent, surfaceRuleArmRejected } from './hitlHostSurface.js';
10
10
  import { approvalCallKey, liveFrameCallKey } from './gateIdentity.js';
11
+ import { displayUntrusted } from '../displayUntrusted.js';
12
+ import { approvalDecisionHolds, holdLiveCard, joinLiveCard, liveCardHoldOf } from './approvalHolds.js';
13
+ import { approvalResolutionOf } from './approvalResolution.js';
11
14
  import { isApprovalMandateWord } from '../gateVocabulary.js';
12
15
  import { denyAttributionRefusalFromError } from '../wireRefusalCopy.js';
13
16
  import { approvalDecidedByAdmitted } from '../approvalDecidedByCapability.js';
14
17
  import { decideFailureNote } from '../decideFailureNote.js';
18
+ import { wireFailureTextsOf } from '../wireFailureShape.js';
15
19
  import { refusalSaysStillPending } from '../engineErrorCodes.js';
16
20
  import { readDecideReceipt } from '../decideReceipt.js';
17
21
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS, ASK_ORIGINS, PERSISTED_RULE_MANDATES, } from '@sema-agent/sdk';
@@ -110,6 +114,7 @@ export function approvalCardPortFor(sessionKey) {
110
114
  export function _resetApprovalCardPortForTest() {
111
115
  cardPortByKey.clear();
112
116
  cardPortMissesByKey.clear();
117
+ callCardLedger.clear();
113
118
  }
114
119
  export function surfaceApprovalCard(req) {
115
120
  const cardPort = approvalCardPortFor(DEFAULT_SESSION_KEY);
@@ -170,11 +175,122 @@ function isParkRowInputSizeMarker(v) {
170
175
  const o = v;
171
176
  return o.truncated === true && typeof o.bytes === 'number' && Number.isFinite(o.bytes) && o.bytes >= 0;
172
177
  }
178
+ function durableHoldKey(row, callKey) {
179
+ const createdAt = typeof row.createdAt === 'number' && Number.isFinite(row.createdAt) ? row.createdAt : null;
180
+ return JSON.stringify([typeof row.sessionId === 'string' ? row.sessionId : null, typeof row.taskId === 'string' ? row.taskId : null, callKey, createdAt]);
181
+ }
182
+ function openDurableRead() {
183
+ const mark = ++approvalDecisionHolds.clock;
184
+ approvalDecisionHolds.openReads.add(mark);
185
+ return mark;
186
+ }
187
+ function closeDurableRead(mark) {
188
+ const h = approvalDecisionHolds;
189
+ h.openReads.delete(mark);
190
+ let oldest = Infinity;
191
+ for (const m of h.openReads)
192
+ if (m < oldest)
193
+ oldest = m;
194
+ for (const [key, s] of h.settled)
195
+ if (s.at <= oldest)
196
+ h.settled.delete(key);
197
+ }
198
+ function heldDurableDecision(holdKey, readMark) {
199
+ const live = approvalDecisionHolds.inFlight.get(holdKey);
200
+ if (live !== undefined)
201
+ return live.lane === 'durable' ? live : undefined;
202
+ const recent = approvalDecisionHolds.settled.get(holdKey);
203
+ return recent !== undefined && recent.at > readMark ? recent.hold : undefined;
204
+ }
205
+ function holdDurableDecision(holdKey, callerSignal, decide) {
206
+ const h = approvalDecisionHolds;
207
+ const interruption = new AbortController();
208
+ let settled = false;
209
+ const interrupt = () => {
210
+ if (!settled && !interruption.signal.aborted)
211
+ interruption.abort();
212
+ };
213
+ if (callerSignal?.aborted === true)
214
+ interrupt();
215
+ else
216
+ callerSignal?.addEventListener('abort', interrupt, { once: true });
217
+ let resolveOutcome = () => { };
218
+ let rejectOutcome = () => { };
219
+ const outcome = new Promise((resolve, reject) => {
220
+ resolveOutcome = resolve;
221
+ rejectOutcome = reject;
222
+ });
223
+ const hold = { lane: 'durable', outcome, presented: false, interrupt, cardSignal: interruption.signal };
224
+ h.inFlight.set(holdKey, hold);
225
+ const release = () => {
226
+ if (h.inFlight.get(holdKey) === hold)
227
+ h.inFlight.delete(holdKey);
228
+ if (h.openReads.size > 0)
229
+ h.settled.set(holdKey, { at: ++h.clock, hold });
230
+ };
231
+ const settle = (give) => {
232
+ settled = true;
233
+ callerSignal?.removeEventListener('abort', interrupt);
234
+ const trailing = hold.trailing;
235
+ if (trailing === undefined) {
236
+ release();
237
+ give();
238
+ return;
239
+ }
240
+ give();
241
+ trailing.then(release, release);
242
+ };
243
+ decide(hold).then((o) => settle(() => resolveOutcome(o)), (e) => settle(() => rejectOutcome(e)));
244
+ return outcome;
245
+ }
246
+ async function joinDurableDecision(hold, deps, signal) {
247
+ if (signal?.aborted === true)
248
+ hold.interrupt();
249
+ else
250
+ signal?.addEventListener('abort', hold.interrupt, { once: true });
251
+ let o;
252
+ try {
253
+ o = await hold.outcome;
254
+ }
255
+ finally {
256
+ signal?.removeEventListener('abort', hold.interrupt);
257
+ }
258
+ if (hold.presented) {
259
+ try {
260
+ deps.onPresented?.();
261
+ }
262
+ catch {
263
+ }
264
+ }
265
+ return { ...o, joinedInFlight: true };
266
+ }
173
267
  export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal, parkGatedCallId) {
174
268
  const decidedByReading = decidedByReadingSnapshotOf(deps);
175
- const found = await findPendingForTask(deps.client, taskId, (toolName) => toolName !== undefined && (toolNameIsFsWrite(toolName) || toolNameIsShellExec(toolName)), signal ? { signal } : undefined, parkGatedCallId, (row) => askParkRowArm(row) === 'tool-gate');
269
+ const readMark = openDurableRead();
270
+ let found;
271
+ let held;
272
+ try {
273
+ found = await findPendingForTask(deps.client, taskId, (toolName) => toolName !== undefined && (toolNameIsFsWrite(toolName) || toolNameIsShellExec(toolName)), signal ? { signal } : undefined, parkGatedCallId, (row) => askParkRowArm(row) === 'tool-gate');
274
+ if (found.ok)
275
+ held = heldDurableDecision(durableHoldKey(found.pending, approvalCallKey(found.gatedCallId, taskId)), readMark);
276
+ }
277
+ finally {
278
+ closeDurableRead(readMark);
279
+ }
176
280
  if (!found.ok)
177
281
  return { kind: 'failed', stage: 'fetch', reason: found.reason, ...(found.code !== undefined ? { code: found.code } : {}) };
282
+ if (held !== undefined)
283
+ return joinDurableDecision(held, deps, signal);
284
+ const callKey = approvalCallKey(found.gatedCallId, taskId);
285
+ const fetched = found;
286
+ return holdDurableDecision(durableHoldKey(fetched.pending, callKey), signal, async (hold) => {
287
+ const slot = {};
288
+ const out = await presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, fetched, decidedByReading, callKey, hold, slot);
289
+ noteCallOutcome(slot, out);
290
+ return out;
291
+ });
292
+ }
293
+ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, found, decidedByReading, callKey, hold, ledgerSlot) {
178
294
  const { pending, gatedCallId } = found;
179
295
  const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
180
296
  const streamArgs = gatedCallId !== undefined ? argsByCall.get(gatedCallId) : undefined;
@@ -191,18 +307,18 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
191
307
  return { kind: 'failed', stage: 'input', gatedCallId, reason: 'gate has no tool input payload' };
192
308
  }
193
309
  const bridge = new HitlBridge(deps.client, taskId, deps.onDecideOperation ? { onDecideOperation: deps.onDecideOperation } : undefined);
194
- const callKey = approvalCallKey(gatedCallId, taskId);
195
310
  const ruleOffersReadOnly = readRuleOffers(pending.ruleOffers);
196
311
  const durableProbeCause = pending.riskDescriptor?.probeCause;
197
312
  const durableProbeReason = readProbeReason(pending.riskDescriptor);
198
313
  const durableShadow = readAllowRuleShadowFromWire(pending, 'row');
199
314
  const durableRuleOffersAbsence = pending.ruleOffersAbsence;
315
+ hold.presented = true;
200
316
  deps.onPresented?.();
201
- const card = await surfaceApprovalCard({
317
+ const card = await surfaceCallTrackedCard(gatedCallId, undefined, ledgerSlot, {
202
318
  toolName,
203
319
  args,
204
320
  callKey,
205
- ...(signal ? { signal } : {}),
321
+ signal: hold.cardSignal,
206
322
  ...(rowArgsGap !== undefined
207
323
  ? {
208
324
  argsUnavailable: true,
@@ -245,7 +361,11 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
245
361
  hostLog('debug', `liveHitlAskWire: approval card retracted without a decision for ${gatedCallId ?? '(no call)'} — nothing sent`);
246
362
  return { kind: 'retracted', gatedCallId };
247
363
  case 'aborted':
248
- observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: 'Interrupted by user' }, gatedCallId, undefined, pending), taskId);
364
+ {
365
+ const cancelDeny = bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, undefined, pending);
366
+ hold.trailing = cancelDeny;
367
+ observeCancelByDeny(cancelDeny, taskId);
368
+ }
249
369
  return { kind: 'aborted', gatedCallId };
250
370
  case 'allow':
251
371
  try {
@@ -283,11 +403,13 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
283
403
  const wireCode = readWireErrorCode(e);
284
404
  const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
285
405
  const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
406
+ const failureTexts = wireFailureTextsOf(e);
407
+ hostLog('debug', `liveToolApprovalWire: decide(approve) failed: ${failureTexts.log}`);
286
408
  return {
287
409
  kind: 'failed',
288
410
  stage: 'decide',
289
411
  gatedCallId,
290
- reason: `decide(approve) failed: ${String(e)}${decideFailureNote(e, 'approve')}`,
412
+ reason: `decide(approve) failed: ${failureTexts.reason}${decideFailureNote(e, 'approve')}`,
291
413
  ...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
292
414
  ...(currentPending !== undefined ? { currentPending } : {}),
293
415
  ...(wireCode !== undefined ? { errorCode: wireCode } : {}),
@@ -368,11 +490,13 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
368
490
  const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
369
491
  const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
370
492
  const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
493
+ const failureTexts = wireFailureTextsOf(e);
494
+ hostLog('debug', `liveToolApprovalWire: decide(deny) failed: ${failureTexts.log}`);
371
495
  return {
372
496
  kind: 'failed',
373
497
  stage: 'decide',
374
498
  gatedCallId,
375
- reason: `decide(deny) failed: ${String(e)}${decideFailureNote(e, 'deny')}`,
499
+ reason: `decide(deny) failed: ${failureTexts.reason}${decideFailureNote(e, 'deny')}`,
376
500
  ...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
377
501
  ...(currentPending !== undefined ? { currentPending } : {}),
378
502
  ...(wireCode !== undefined ? { errorCode: wireCode } : {}),
@@ -546,6 +670,36 @@ export function readToolApprovalRespondAck(v) {
546
670
  }
547
671
  return out;
548
672
  }
673
+ export function readToolApprovalRespondParking(v) {
674
+ if (typeof v !== 'object' || v === null)
675
+ return undefined;
676
+ const read = (fn) => {
677
+ try {
678
+ return fn();
679
+ }
680
+ catch {
681
+ return undefined;
682
+ }
683
+ };
684
+ if (read(() => readOwnDataNonEmptyString(v, 'reason')) !== 'parking_in_progress')
685
+ return undefined;
686
+ const approvalId = read(() => readOwnDataNonEmptyString(v, 'approvalId'));
687
+ if (approvalId === undefined)
688
+ return { kind: 'claimed_unreadable' };
689
+ const hint = read(() => readOwnDataValue(v, 'retryAfterMs'));
690
+ const retryAfterMs = typeof hint === 'number' && Number.isFinite(hint) && hint >= 0 ? hint : undefined;
691
+ const decidePath = read(() => readOwnDataNonEmptyString(v, 'decidePath'));
692
+ const message = read(() => readOwnDataNonEmptyString(v, 'message'));
693
+ return {
694
+ kind: 'parking',
695
+ view: {
696
+ approvalId,
697
+ ...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
698
+ ...(decidePath !== undefined ? { decidePath } : {}),
699
+ ...(message !== undefined ? { message } : {}),
700
+ },
701
+ };
702
+ }
549
703
  function isNonEmptyStringArray(v) {
550
704
  return Array.isArray(v) && v.length > 0 && v.every(x => typeof x === 'string' && x !== '');
551
705
  }
@@ -699,13 +853,27 @@ function readProbeReason(v) {
699
853
  try {
700
854
  if (typeof v !== 'object' || v === null || !Object.hasOwn(v, 'probeReason'))
701
855
  return undefined;
702
- const raw = v.probeReason;
703
- return typeof raw === 'string' && raw.trim() !== '' ? raw : undefined;
856
+ return usableProbeReason(v.probeReason);
704
857
  }
705
858
  catch {
706
859
  return undefined;
707
860
  }
708
861
  }
862
+ function usableProbeReason(raw) {
863
+ return typeof raw === 'string' && raw.trim() !== '' ? raw : undefined;
864
+ }
865
+ const PROBE_CAUSE_CODE_WORD = /^[a-z][a-z0-9_]{0,31}(?:\.[a-z][a-z0-9_]{0,31}){1,3}$/;
866
+ export function askProbeReasonDetail(card) {
867
+ const reason = usableProbeReason(ownDataValue(card, 'probeReason'));
868
+ if (reason !== undefined)
869
+ return displayUntrusted(reason);
870
+ const cause = ownDataValue(card, 'probeCause');
871
+ if (!isWireRecordCarrier(cause))
872
+ return undefined;
873
+ const code = ownDataValue(cause, 'code');
874
+ const head = 'An automatic check could not clear this call, so it is asking you; the engine did not say the specific reason';
875
+ return typeof code === 'string' && PROBE_CAUSE_CODE_WORD.test(code) ? `${head} (check code: ${code}).` : `${head}.`;
876
+ }
709
877
  function readRememberRefused(v) {
710
878
  try {
711
879
  return typeof v === 'object' && v !== null && Object.hasOwn(v, 'rememberRefused') && v.rememberRefused === true;
@@ -714,6 +882,167 @@ function readRememberRefused(v) {
714
882
  return false;
715
883
  }
716
884
  }
885
+ function ownDataValue(v, key) {
886
+ if (typeof v !== 'object' || v === null)
887
+ return undefined;
888
+ try {
889
+ const d = Object.getOwnPropertyDescriptor(v, key);
890
+ return d !== undefined && 'value' in d ? d.value : undefined;
891
+ }
892
+ catch {
893
+ return undefined;
894
+ }
895
+ }
896
+ function ownStringList(v) {
897
+ if (!Array.isArray(v))
898
+ return undefined;
899
+ try {
900
+ const length = v.length;
901
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length < 0)
902
+ return undefined;
903
+ const out = [];
904
+ for (let i = 0; i < length; i++) {
905
+ const d = Object.getOwnPropertyDescriptor(v, i);
906
+ if (d === undefined || !('value' in d) || typeof d.value !== 'string' || d.value === '')
907
+ return undefined;
908
+ out.push(d.value);
909
+ }
910
+ return v.length === length ? Object.freeze(out) : undefined;
911
+ }
912
+ catch {
913
+ return undefined;
914
+ }
915
+ }
916
+ export function readApprovalBoundGrants(v) {
917
+ const g = ownDataValue(v, 'boundGrants');
918
+ if (typeof g !== 'object' || g === null || Array.isArray(g))
919
+ return undefined;
920
+ const skill = ownDataValue(g, 'skill');
921
+ if (!(skill === null || (typeof skill === 'string' && skill !== '')))
922
+ return undefined;
923
+ const allowedTools = ownStringList(ownDataValue(g, 'allowedTools'));
924
+ const disallowedTools = ownStringList(ownDataValue(g, 'disallowedTools'));
925
+ if (allowedTools === undefined || disallowedTools === undefined)
926
+ return undefined;
927
+ return Object.freeze({ skill, allowedTools, disallowedTools });
928
+ }
929
+ const CAUSE_WORD = /^[a-z][a-z0-9_]{0,63}$/;
930
+ export function readApprovalClassifierNoRuling(v) {
931
+ const w = ownDataValue(v, 'classifierNoRuling');
932
+ return typeof w === 'string' && CAUSE_WORD.test(w) ? w : undefined;
933
+ }
934
+ const CLASSIFIER_NO_RULING_REASONS = Object.freeze({
935
+ error: 'could not run for this call (it failed)',
936
+ timeout: 'could not run for this call (it timed out)',
937
+ parse_error: 'answered for this call but gave no verdict it could use',
938
+ });
939
+ const _noRulingPin = [true, true];
940
+ void _noRulingPin;
941
+ export function classifierNoRulingDetail(word) {
942
+ if (typeof word === 'string' && Object.hasOwn(CLASSIFIER_NO_RULING_REASONS, word)) {
943
+ return `The auto-mode classifier ${CLASSIFIER_NO_RULING_REASONS[word]}, so it is asking you instead.`;
944
+ }
945
+ if (typeof word === 'string' && CAUSE_WORD.test(word))
946
+ return `The auto-mode classifier did not rule on this call (reason: ${word}), so it is asking you instead.`;
947
+ return 'The auto-mode classifier did not rule on this call, so it is asking you instead.';
948
+ }
949
+ const entries = (n) => (n === 1 ? '1 tool entry' : `${String(n)} tool entries`);
950
+ export function approvalBoundGrantsDetail(grants) {
951
+ const g = readApprovalBoundGrants({ boundGrants: grants });
952
+ if (g === undefined)
953
+ return undefined;
954
+ if (g.skill === null)
955
+ return 'This call names no loaded skill, so approving it grants nothing beyond this call.';
956
+ const a = g.allowedTools.length;
957
+ const d = g.disallowedTools.length;
958
+ if (a === 0 && d === 0)
959
+ return 'Approving this activates the skill shown on the card; it lists no tools to run without asking and none to refuse.';
960
+ const allow = `${entries(a)} it lists ${a === 1 ? 'runs' : 'run'} without asking once it is active`;
961
+ const deny = `${entries(d)} ${d === 1 ? 'is' : 'are'} refused for the rest of this run`;
962
+ return `Approving this activates the skill shown on the card: ${allow}, and ${deny}.`;
963
+ }
964
+ const CALL_LEDGER_MAX_CALLS = 256;
965
+ const CALL_LEDGER_MAX_ASKS_PER_CALL = 8;
966
+ const callCardLedger = new Map();
967
+ function askSignatureOf(req) {
968
+ return JSON.stringify([req.classes !== undefined ? [...req.classes] : null, req.origin ?? null, req.mandate ?? null]);
969
+ }
970
+ function priorCardView(a) {
971
+ return Object.freeze({
972
+ ...(a.approvalId !== undefined ? { approvalId: a.approvalId } : {}),
973
+ ...(a.origin !== undefined ? { origin: a.origin } : {}),
974
+ ...(a.mandate !== undefined ? { mandate: a.mandate } : {}),
975
+ ...(a.outcome !== undefined ? { outcome: a.outcome } : {}),
976
+ });
977
+ }
978
+ function noteCardOnCall(callId, approvalId, req) {
979
+ const sig = askSignatureOf(req);
980
+ let asks = callCardLedger.get(callId);
981
+ if (asks === undefined) {
982
+ asks = [];
983
+ callCardLedger.set(callId, asks);
984
+ while (callCardLedger.size > CALL_LEDGER_MAX_CALLS) {
985
+ const oldest = callCardLedger.keys().next().value;
986
+ if (oldest === undefined)
987
+ break;
988
+ callCardLedger.delete(oldest);
989
+ }
990
+ }
991
+ let at = asks.findIndex((a) => a.sig === sig);
992
+ if (at < 0) {
993
+ asks.push({ sig, ...(approvalId !== undefined ? { approvalId } : {}), ...(req.origin !== undefined ? { origin: req.origin } : {}), ...(req.mandate !== undefined ? { mandate: req.mandate } : {}) });
994
+ while (asks.length > CALL_LEDGER_MAX_ASKS_PER_CALL)
995
+ asks.shift();
996
+ at = asks.length - 1;
997
+ }
998
+ else if (asks[at].approvalId === undefined && approvalId !== undefined) {
999
+ asks[at].approvalId = approvalId;
1000
+ }
1001
+ const entry = asks[at];
1002
+ const prior = asks.slice(0, at);
1003
+ return { entry, ...(prior.length > 0 ? { prior: Object.freeze(prior.map(priorCardView)) } : {}) };
1004
+ }
1005
+ async function surfaceCallTrackedCard(callId, approvalId, slot, req) {
1006
+ if (callId === undefined || callId === '')
1007
+ return surfaceApprovalCard(req);
1008
+ const { entry, prior } = noteCardOnCall(callId, approvalId, req);
1009
+ slot.entry = entry;
1010
+ return surfaceApprovalCard(prior !== undefined ? { ...req, priorCardsForCall: prior } : req);
1011
+ }
1012
+ function noteCallOutcome(slot, outcome) {
1013
+ const entry = slot.entry;
1014
+ if (entry === undefined)
1015
+ return;
1016
+ const r = approvalResolutionOf(outcome);
1017
+ if (r.kind === 'decided')
1018
+ entry.outcome = r.decision === 'deny' ? 'denied' : 'allowed';
1019
+ }
1020
+ export function approvalPriorCardsDetail(card) {
1021
+ const prior = ownDataValue(card, 'priorCardsForCall');
1022
+ let isList = false;
1023
+ try {
1024
+ isList = Array.isArray(prior);
1025
+ }
1026
+ catch {
1027
+ return undefined;
1028
+ }
1029
+ if (!isList)
1030
+ return undefined;
1031
+ const length = ownDataValue(prior, 'length');
1032
+ if (typeof length !== 'number' || !Number.isSafeInteger(length) || length <= 0 || length > CALL_LEDGER_MAX_ASKS_PER_CALL)
1033
+ return undefined;
1034
+ const items = [];
1035
+ for (let i = 0; i < length; i++)
1036
+ items.push(ownDataValue(prior, String(i)));
1037
+ const outcome = ownDataValue(items[length - 1], 'outcome');
1038
+ const earlier = outcome === 'allowed' ? 'you already approved an earlier one' : outcome === 'denied' ? 'an earlier one was denied' : 'an earlier one was shown first';
1039
+ const fromRule = items.some((p) => ownDataValue(p, 'origin') === 'ask_rule');
1040
+ const thisFromRule = ownDataValue(card, 'origin') === 'ask_rule';
1041
+ if (fromRule && !thisFromRule) {
1042
+ return `This is a second approval for the same call: ${earlier}, which your ask rule asked for. This one is a separate check, not from your ask rule, and needs its own answer.`;
1043
+ }
1044
+ return `This is a second approval for the same call: ${earlier}. This one is a separate check and needs its own answer.`;
1045
+ }
717
1046
  function isNonNegativeFinite(v) {
718
1047
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
719
1048
  }
@@ -881,6 +1210,18 @@ function subagentBadgeFor(frame) {
881
1210
  const MAX_RESPOND_NOTE_CHARS = 2048;
882
1211
  export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamArgs, signal, lane) {
883
1212
  const approvalId = frame.approvalId;
1213
+ const key = liveFrameCallKey(approvalId);
1214
+ const held = liveCardHoldOf(key);
1215
+ if (held !== undefined)
1216
+ return joinLiveCard(held, signal);
1217
+ return holdLiveCard(key, signal, async (hold) => {
1218
+ const slot = {};
1219
+ const out = await presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, slot);
1220
+ noteCallOutcome(slot, out);
1221
+ return out;
1222
+ });
1223
+ }
1224
+ async function presentToolApprovalFrameAndRespond(frame, approvalId, respond, streamArgs, signal, lane, hold, ledgerSlot) {
884
1225
  const frameToolCallId = typeof frame.toolCallId === 'string' && frame.toolCallId !== '' ? frame.toolCallId : undefined;
885
1226
  const decidedByReading = decidedByReadingSnapshotOf(lane);
886
1227
  const toolName = typeof frame.toolName === 'string' ? frame.toolName : 'Write';
@@ -910,14 +1251,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
910
1251
  const ruleOffers = readRuleOffers(frame.ruleOffers);
911
1252
  const denialLimitFallback = readDenialLimitFallback(frame.denialLimitFallback);
912
1253
  const frameShadow = readAllowRuleShadowFromWire(frame, 'frame');
913
- const card = await surfaceApprovalCard({
1254
+ const card = await surfaceCallTrackedCard(frameToolCallId, approvalId, ledgerSlot, {
914
1255
  toolName,
915
1256
  args: args,
916
1257
  callKey: liveFrameCallKey(approvalId),
917
1258
  ...(frameToolCallId !== undefined
918
1259
  ? { toolCallId: frameToolCallId }
919
1260
  : {}),
920
- ...(signal ? { signal } : {}),
1261
+ signal: hold.cardSignal,
921
1262
  ...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
922
1263
  ...(wireNote !== undefined ? { wireNote } : {}),
923
1264
  ...(argsNotRealInput ? { argsUnavailable: true } : {}),
@@ -953,7 +1294,16 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
953
1294
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
954
1295
  ...((o) => (o !== undefined ? { origin: o } : {}))(readOwnNonEmptyString(frame, 'origin')),
955
1296
  ...(readAskClassPair(frame) ?? {}),
1297
+ ...((g) => (g !== undefined ? { boundGrants: g } : {}))(readApprovalBoundGrants(frame)),
1298
+ ...((w) => (w !== undefined ? { classifierNoRuling: w } : {}))(readApprovalClassifierNoRuling(frame)),
956
1299
  });
1300
+ if (hold.superseded !== undefined) {
1301
+ hostLog('debug', `liveToolApprovalWire: ${approvalId} was settled by the engine while its card was up${hold.superseded.parked ? ' (parked)' : ''} — card withdrawn; ` +
1302
+ `the card came back ${card.kind}; nothing sent`);
1303
+ if (hold.superseded.parked && (card.kind === 'allow' || card.kind === 'deny'))
1304
+ surfaceApprovalParking();
1305
+ return { decision: 'unresolved', retracted: true };
1306
+ }
957
1307
  if (argsNotRealInput && card.kind === 'allow' && card.updatedInput !== undefined) {
958
1308
  hostLog('error', `liveToolApprovalWire: ${approvalId} card returned an edited approval on a card whose arguments are not the tool's real input (${lane?.argsUnavailable === true ? 'args-unavailable ask' : 'arguments omitted or absent on the frame'}) — nothing sent (the ask stays pending)`);
959
1309
  surfaceEditRefusedOnBlindAsk();
@@ -963,6 +1313,22 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
963
1313
  hostLog('debug', `liveToolApprovalWire: ${approvalId} card retracted without a decision${card.reason ? ` (${card.reason})` : ''} — nothing sent`);
964
1314
  return { decision: 'unresolved', retracted: true };
965
1315
  }
1316
+ if (card.kind === 'aborted') {
1317
+ hold.phase = 'respond';
1318
+ const interruptOpts = lane?.approvalDecisionNoteCapable === true ? { note: INTERRUPT_DENY_REASON } : {};
1319
+ let interruptDeny;
1320
+ try {
1321
+ interruptDeny = Promise.resolve(respond(approvalId, 'deny', interruptOpts));
1322
+ }
1323
+ catch (e) {
1324
+ interruptDeny = Promise.reject(e);
1325
+ }
1326
+ hold.trailing = interruptDeny.then(() => undefined, () => undefined);
1327
+ observeCancelByDeny(interruptDeny, approvalId);
1328
+ hostLog('debug', `liveToolApprovalWire: ${approvalId} card aborted — one tool-level deny issued to settle the pending ask`);
1329
+ return { decision: 'unresolved', aborted: true };
1330
+ }
1331
+ hold.phase = 'respond';
966
1332
  const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
967
1333
  const denySettledByForWire = card.kind === 'deny' ? readApprovalDenySettledBy(card) : undefined;
968
1334
  const attributionKeys = decision === 'deny' ? decisionAttributionKeyOf(decidedByReading, denySettledByForWire) : {};
@@ -1101,6 +1467,19 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1101
1467
  throw first;
1102
1468
  }
1103
1469
  }
1470
+ const parkingBody = readToolApprovalRespondParking(raw);
1471
+ if (parkingBody !== undefined) {
1472
+ const parking = parkingBody.kind === 'parking' && parkingBody.view.approvalId === approvalId ? parkingBody.view : undefined;
1473
+ if (parking !== undefined) {
1474
+ hostLog('debug', `liveToolApprovalWire: respond(${decision}) for ${approvalId} answered 202 parking_in_progress${parking.retryAfterMs !== undefined ? ` (retryAfterMs=${parking.retryAfterMs})` : ''} — ` +
1475
+ `the answer did NOT take effect${parking.decidePath !== undefined ? ' (the engine pointed at a decide path for the parked card)' : ''}; not re-sent, not reported as decided; the user is told to answer again on the parked card`);
1476
+ surfaceApprovalParking(parking.retryAfterMs);
1477
+ return { decision: 'unresolved', parking };
1478
+ }
1479
+ hostLog('error', `liveToolApprovalWire: 202 parking body for ${approvalId} is ${parkingBody.kind === 'parking' ? `for another approval (${parkingBody.view.approvalId})` : 'unreadable (approvalId is not usable)'} — ` +
1480
+ 'reporting the answer as NOT settled (never as decided, never as parked); not re-sent');
1481
+ return { decision: 'unresolved' };
1482
+ }
1104
1483
  const parsed = readToolApprovalRespondAck(raw);
1105
1484
  let ack = parsed;
1106
1485
  if (parsed !== undefined && (parsed.approvalId !== approvalId || parsed.decision !== decision)) {
@@ -1,5 +1,5 @@
1
1
  import type { ImageInput } from './imagesWireCaps.js';
2
- import type { UserAttachmentDescriptor, UserAttachmentNotIncludedReason, UserAttachmentSource } from './userAttachmentEnvelope.js';
2
+ import type { UserAttachmentDescriptor, UserAttachmentMcpResourcePart, UserAttachmentNotIncludedReason, UserAttachmentSource } from './userAttachmentEnvelope.js';
3
3
  import type { HumanTurnAttachment, HumanTurnEncoding, HumanTurnInput, HumanTurnMissing } from './humanTurn.js';
4
4
  import type { ImageVision } from './liveModelCatalog.js';
5
5
  export interface CellView {
@@ -55,6 +55,20 @@ export type DescriptorView = {
55
55
  path: string;
56
56
  sizeBytes: number | null;
57
57
  reason: UserAttachmentNotIncludedReason;
58
+ } | {
59
+ kind: 'not-included';
60
+ target: 'mcp-resource';
61
+ server: string;
62
+ uri: string;
63
+ name: string;
64
+ reason: UserAttachmentNotIncludedReason;
65
+ } | {
66
+ kind: 'mcp-resource';
67
+ server: string;
68
+ uri: string;
69
+ name: string;
70
+ parts: UserAttachmentMcpResourcePart[];
71
+ truncated: boolean;
58
72
  };
59
73
  export declare function readDescriptor(x: UserAttachmentDescriptor | HumanTurnAttachment['descriptor'], imageDelivery: 'required' | 'ignored'): DescriptorView | undefined;
60
74
  type Reason = Exclude<Extract<HumanTurnEncoding, {
@@ -40,6 +40,23 @@ const strArray = (v) => ownArray(v).map(str);
40
40
  const sizeOf = (v) => (v === null ? null : typeof v === 'number' ? v : bad());
41
41
  const posInt = (v) => (typeof v === 'number' && Number.isSafeInteger(v) && v >= 1 ? v : bad());
42
42
  const natInt = (v) => (typeof v === 'number' && Number.isSafeInteger(v) && v >= 0 ? v : bad());
43
+ const NOT_INCLUDED_REASON_LIST = ['too-large', 'unreadable', 'read-denied', 'approval-required', 'server-not-connected', 'resource-not-listed', 'read-failed'];
44
+ const notIncludedReasonsExhaustive = true;
45
+ void notIncludedReasonsExhaustive;
46
+ const NOT_INCLUDED_REASONS = new Set(NOT_INCLUDED_REASON_LIST);
47
+ function readMcpPart(x) {
48
+ if (!isPlainObject(x))
49
+ bad();
50
+ const o = x;
51
+ const kind = ownData(o, 'kind');
52
+ if (kind === 'text')
53
+ return { kind, text: str(ownData(o, 'text')) };
54
+ if (kind === 'binary') {
55
+ const mt = ownData(o, 'mimeType');
56
+ return { kind, mimeType: mt === null ? null : str(mt) };
57
+ }
58
+ return bad();
59
+ }
43
60
  function sourceOf(v) {
44
61
  return v === 'at-mention' || v === 'compact-restore' ? v : bad();
45
62
  }
@@ -120,9 +137,12 @@ function readDescriptorAny(x, imageDelivery) {
120
137
  case 'not-included': {
121
138
  const target = ownData(o, 'target');
122
139
  const reason = ownData(o, 'reason');
123
- if (target !== 'file' && target !== 'directory' && target !== 'path')
140
+ if (!NOT_INCLUDED_REASONS.has(reason))
124
141
  bad();
125
- if (reason !== 'too-large' && reason !== 'unreadable' && reason !== 'read-denied')
142
+ if (target === 'mcp-resource') {
143
+ return { kind, target, server: str(ownData(o, 'server')), uri: str(ownData(o, 'uri')), name: name(), reason: reason };
144
+ }
145
+ if (target !== 'file' && target !== 'directory' && target !== 'path')
126
146
  bad();
127
147
  return {
128
148
  kind,
@@ -133,6 +153,12 @@ function readDescriptorAny(x, imageDelivery) {
133
153
  reason: reason,
134
154
  };
135
155
  }
156
+ case 'mcp-resource': {
157
+ const truncated = ownData(o, 'truncated');
158
+ if (typeof truncated !== 'boolean')
159
+ bad();
160
+ return { kind, server: str(ownData(o, 'server')), uri: str(ownData(o, 'uri')), name: name(), parts: ownArray(ownData(o, 'parts')).map(readMcpPart), truncated: truncated };
161
+ }
136
162
  default:
137
163
  return undefined;
138
164
  }