@sema-agent/client-core 0.93.1 → 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 (155) hide show
  1. package/CHANGELOG.md +213 -0
  2. package/README.md +30 -19
  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.js +9 -1
  52. package/dist/hitl/approvalsFeed.js +5 -0
  53. package/dist/hitl/askGateWire.js +3 -0
  54. package/dist/hitl/automaticAnswerVerdict.d.ts +8 -2
  55. package/dist/hitl/automaticAnswerVerdict.js +69 -21
  56. package/dist/hitl/decisionRefusal.d.ts +19 -0
  57. package/dist/hitl/decisionRefusal.js +63 -0
  58. package/dist/hitl/frameRouter.d.ts +10 -0
  59. package/dist/hitl/frameRouter.js +59 -3
  60. package/dist/hitl/gateLedger.d.ts +3 -0
  61. package/dist/hitl/gateLedger.js +17 -0
  62. package/dist/hitl/hitlBridge.d.ts +2 -2
  63. package/dist/hitl/hitlBridge.js +8 -10
  64. package/dist/hitl/hitlHostSurface.d.ts +3 -0
  65. package/dist/hitl/hitlHostSurface.js +14 -4
  66. package/dist/hitl/localAllowRule.js +2 -1
  67. package/dist/hitl/noAllowCause.d.ts +9 -0
  68. package/dist/hitl/noAllowCause.js +26 -0
  69. package/dist/hitl/parkResolver.js +6 -3
  70. package/dist/hitl/parkRowBirthWait.js +2 -1
  71. package/dist/hitl/persistedRulesWire.d.ts +6 -1
  72. package/dist/hitl/persistedRulesWire.js +12 -4
  73. package/dist/hitl/planReviewWire.d.ts +0 -1
  74. package/dist/hitl/planReviewWire.js +30 -9
  75. package/dist/hitl/sessionPolicyDeliverable.d.ts +1 -1
  76. package/dist/hitl/sessionPolicyDeliverable.js +3 -8
  77. package/dist/hitl/sessionPolicyWire.js +1 -1
  78. package/dist/hitl/toolApprovalWire.d.ts +27 -2
  79. package/dist/hitl/toolApprovalWire.js +258 -23
  80. package/dist/humanTurnRead.d.ts +15 -1
  81. package/dist/humanTurnRead.js +28 -2
  82. package/dist/index.d.ts +18 -6
  83. package/dist/index.js +14 -5
  84. package/dist/limitsWire.d.ts +0 -1
  85. package/dist/limitsWire.js +2 -10
  86. package/dist/liveModelCatalog.d.ts +14 -0
  87. package/dist/liveModelCatalog.js +61 -0
  88. package/dist/mcpProbeCapability.js +14 -9
  89. package/dist/mcpReconnect.js +13 -8
  90. package/dist/memoryComplianceCapability.js +15 -10
  91. package/dist/memoryOriginCapability.js +15 -10
  92. package/dist/modelIdentityResolvability.d.ts +4 -2
  93. package/dist/modelIdentityResolvability.js +31 -4
  94. package/dist/outputStyleSeatCapability.d.ts +16 -0
  95. package/dist/outputStyleSeatCapability.js +47 -0
  96. package/dist/ownKey.d.ts +1 -0
  97. package/dist/ownKey.js +10 -0
  98. package/dist/peerLaneCapability.js +16 -11
  99. package/dist/permissionRuleIssue.js +1 -1
  100. package/dist/permissionRulesWriteCapability.js +16 -11
  101. package/dist/permissionsAllowGrantCapability.d.ts +2 -0
  102. package/dist/permissionsAllowGrantCapability.js +38 -16
  103. package/dist/permissionsDeclaration.d.ts +10 -1
  104. package/dist/permissionsDeclaration.js +97 -15
  105. package/dist/protocolSegment.d.ts +1 -0
  106. package/dist/protocolSegment.js +13 -1
  107. package/dist/request/taskRequest.d.ts +11 -3
  108. package/dist/request/taskRequest.js +13 -1
  109. package/dist/resultFrameKeys.d.ts +4 -0
  110. package/dist/resultFrameKeys.js +13 -0
  111. package/dist/resumeRefusalCopy.js +6 -16
  112. package/dist/retryStatus.js +6 -4
  113. package/dist/rewindArchiveCapability.js +13 -8
  114. package/dist/{hitl/rosterMountedNames.d.ts → rosterMountedNames.d.ts} +1 -1
  115. package/dist/runCancelContext.js +2 -1
  116. package/dist/sealedKeyCapability.js +19 -14
  117. package/dist/seatContract.d.ts +33 -5
  118. package/dist/seatContract.js +143 -2
  119. package/dist/sessionPolicyCapability.js +15 -10
  120. package/dist/sessionReadRedactionCapability.d.ts +16 -0
  121. package/dist/sessionReadRedactionCapability.js +47 -0
  122. package/dist/sessionSyncFileHistoryCapability.js +14 -9
  123. package/dist/skillToolListsCapability.js +14 -9
  124. package/dist/sqlEngineCapability.js +18 -13
  125. package/dist/steerPriorityCapability.js +21 -16
  126. package/dist/subagent/agentStateDetail.d.ts +1 -0
  127. package/dist/subagent/agentStateDetail.js +41 -0
  128. package/dist/subagent/engineCompactWire.d.ts +15 -0
  129. package/dist/subagent/engineCompactWire.js +75 -13
  130. package/dist/supportFloor.js +1 -1
  131. package/dist/taskRequestWords.d.ts +2 -4
  132. package/dist/taskRequestWords.js +3 -2
  133. package/dist/types/engineState.d.ts +1 -1
  134. package/dist/userAttachmentEnvelope.d.ts +22 -1
  135. package/dist/userAttachmentEnvelope.js +90 -2
  136. package/dist/userInvokedSkillsCapability.js +14 -9
  137. package/dist/webSearchBackendCapability.js +21 -16
  138. package/dist/wireErrorTriage.d.ts +11 -0
  139. package/dist/wireErrorTriage.js +35 -0
  140. package/dist/wireFailureShape.d.ts +6 -0
  141. package/dist/wireFailureShape.js +30 -3
  142. package/dist/wireRefusalCopy.d.ts +6 -1
  143. package/dist/wireRefusalCopy.js +22 -8
  144. package/dist/wiringInstructionsAttribution.d.ts +10 -0
  145. package/dist/wiringInstructionsAttribution.js +67 -0
  146. package/dist/workflowClient.js +22 -1
  147. package/dist/workflowsJournalCapability.js +14 -9
  148. package/dist/writeProtectionCapability.js +28 -23
  149. package/docs/INTEGRATION-CLIENTS.md +719 -45
  150. package/package.json +5 -5
  151. package/dist/askSurvivesStandingAnswer.d.ts +0 -13
  152. package/dist/askSurvivesStandingAnswer.js +0 -53
  153. package/dist/hitl/askSurvivesPosture.d.ts +0 -20
  154. package/dist/hitl/askSurvivesPosture.js +0 -48
  155. /package/dist/{hitl/rosterMountedNames.js → rosterMountedNames.js} +0 -0
@@ -6,7 +6,7 @@ import { type DenyAttributionRefusalDetail } from '../wireRefusalCopy.js';
6
6
  import { type ApprovalDecidedByReading } from '../approvalDecidedByCapability.js';
7
7
  import { type DecideReceiptView } from '../decideReceipt.js';
8
8
  import { RULE_OFFER_MATCHES, RULE_OFFER_BATCH_MEMBER_KINDS, RULE_OFFER_UNCOVERED_REASONS, RULE_OFFERS_ABSENCE_REASONS } from '@sema-agent/sdk';
9
- import type { ToolApprovalRespondAck, PersistedRuleAnchor as SdkPersistedRuleAnchor, RuleOfferMatch as SdkRuleOfferMatch, RuleOfferBatchMember as SdkRuleOfferBatchMember, RuleOfferUncoveredDetail as SdkRuleOfferUncoveredDetail, RuleOfferUncoveredReason as SdkRuleOfferUncoveredReason, AskOrigin as SdkAskOrigin, RuleOffersAbsence as SdkRuleOffersAbsence, DenialLimitKind as SdkDenialLimitKind, DenialLimitFallback as SdkDenialLimitFallback } from '@sema-agent/sdk';
9
+ import type { ApprovalDecidedBy, ToolApprovalRespondAck, PersistedRuleAnchor as SdkPersistedRuleAnchor, RuleOfferMatch as SdkRuleOfferMatch, RuleOfferBatchMember as SdkRuleOfferBatchMember, RuleOfferUncoveredDetail as SdkRuleOfferUncoveredDetail, RuleOfferUncoveredReason as SdkRuleOfferUncoveredReason, AskOrigin as SdkAskOrigin, RuleOffersAbsence as SdkRuleOffersAbsence, DenialLimitKind as SdkDenialLimitKind, DenialLimitFallback as SdkDenialLimitFallback } from '@sema-agent/sdk';
10
10
  export declare function isFsApprovalGate(gate: {
11
11
  kind?: unknown;
12
12
  toolName?: unknown;
@@ -157,6 +157,15 @@ export interface ApprovalCardRequest {
157
157
  origin?: string;
158
158
  askClass?: string;
159
159
  classes?: readonly string[];
160
+ boundGrants?: BoundSkillGrantsWire;
161
+ classifierNoRuling?: string;
162
+ priorCardsForCall?: readonly ApprovalPriorCard[];
163
+ }
164
+ export interface ApprovalPriorCard {
165
+ readonly approvalId?: string;
166
+ readonly origin?: string;
167
+ readonly mandate?: ApprovalMandateWord;
168
+ readonly outcome?: 'allowed' | 'denied';
160
169
  }
161
170
  export type ApprovalCardPort = (req: ApprovalCardRequest) => Promise<ApprovalCardDecision>;
162
171
  export declare function installApprovalCardPort(port: ApprovalCardPort | null): () => void;
@@ -244,7 +253,7 @@ export interface RespondToolApprovalOpts {
244
253
  persistRuleBatchOfferIndex?: number;
245
254
  note?: string;
246
255
  settledBy?: 'policy';
247
- decidedBy?: 'policy';
256
+ decidedBy?: Extract<ApprovalDecidedBy, 'policy'>;
248
257
  }
249
258
  export type RespondToolApprovalFn = (approvalId: string, decision: ToolApprovalRespondDecision, opts?: RespondToolApprovalOpts) => Promise<ToolApprovalRespondAck | void>;
250
259
  export type ToolApprovalRespondAckView = ToolApprovalRespondAck;
@@ -254,7 +263,15 @@ export interface ToolApprovalRespondParkingView {
254
263
  readonly approvalId: string;
255
264
  readonly retryAfterMs?: number;
256
265
  readonly decidePath?: string;
266
+ readonly message?: string;
257
267
  }
268
+ export type ToolApprovalParkingBodyReading = {
269
+ readonly kind: 'parking';
270
+ readonly view: ToolApprovalRespondParkingView;
271
+ } | {
272
+ readonly kind: 'claimed_unreadable';
273
+ };
274
+ export declare function readToolApprovalRespondParking(v: unknown): ToolApprovalParkingBodyReading | undefined;
258
275
  export declare function decisionAttributionKeyOf(reading: ApprovalDecidedByReading | undefined, denySettledBy: ApprovalDenySettledBy | undefined): {
259
276
  decidedBy: 'policy';
260
277
  } | {
@@ -266,6 +283,8 @@ export interface ToolApprovalFrameOutcome {
266
283
  respondRefusal?: ToolApprovalRespondRefusal;
267
284
  editRefused?: true;
268
285
  retracted?: true;
286
+ aborted?: true;
287
+ joinedInFlight?: true;
269
288
  denySettledBy?: ApprovalDenySettledBy;
270
289
  denyReason?: string;
271
290
  denyAttributionRefusal?: DenyAttributionRefusalDetail;
@@ -287,6 +306,12 @@ export type ToolApprovalRespondRefusal = (ToolApprovalRespondRefusalFields & {
287
306
  });
288
307
  export declare function readToolApprovalRespondRefusal(err: unknown): ToolApprovalRespondRefusal | undefined;
289
308
  export declare function isToolApprovalFrame(ev: unknown): ev is ToolApprovalFrame;
309
+ export declare function askProbeReasonDetail(card: Pick<ApprovalCardRequest, 'probeReason' | 'probeCause'> | null | undefined): string | undefined;
310
+ export declare function readApprovalBoundGrants(v: unknown): BoundSkillGrantsWire | undefined;
311
+ export declare function readApprovalClassifierNoRuling(v: unknown): string | undefined;
312
+ export declare function classifierNoRulingDetail(word: string | undefined): string;
313
+ export declare function approvalBoundGrantsDetail(grants: BoundSkillGrantsWire | null | undefined): string | undefined;
314
+ export declare function approvalPriorCardsDetail(card: Pick<ApprovalCardRequest, 'priorCardsForCall' | 'origin'> | null | undefined): string | undefined;
290
315
  export declare function readDenialLimitFallback(v: unknown): DenialLimitFallback | undefined;
291
316
  export declare function readRuleOffers(v: unknown): RuleOffer[] | undefined;
292
317
  export declare function readRuleOfferSupply(offers: unknown): RuleOffer[] | undefined;
@@ -6,12 +6,16 @@ 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, surfaceApprovalParking, 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,17 +175,17 @@ 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
  }
173
- const durableDecisionHolds = { inFlight: new Map(), settled: new Map(), openReads: new Set(), clock: 0 };
174
178
  function durableHoldKey(row, callKey) {
175
- return JSON.stringify([typeof row.sessionId === 'string' ? row.sessionId : null, typeof row.taskId === 'string' ? row.taskId : null, 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]);
176
181
  }
177
182
  function openDurableRead() {
178
- const mark = ++durableDecisionHolds.clock;
179
- durableDecisionHolds.openReads.add(mark);
183
+ const mark = ++approvalDecisionHolds.clock;
184
+ approvalDecisionHolds.openReads.add(mark);
180
185
  return mark;
181
186
  }
182
187
  function closeDurableRead(mark) {
183
- const h = durableDecisionHolds;
188
+ const h = approvalDecisionHolds;
184
189
  h.openReads.delete(mark);
185
190
  let oldest = Infinity;
186
191
  for (const m of h.openReads)
@@ -191,14 +196,14 @@ function closeDurableRead(mark) {
191
196
  h.settled.delete(key);
192
197
  }
193
198
  function heldDurableDecision(holdKey, readMark) {
194
- const live = durableDecisionHolds.inFlight.get(holdKey);
199
+ const live = approvalDecisionHolds.inFlight.get(holdKey);
195
200
  if (live !== undefined)
196
- return live;
197
- const recent = durableDecisionHolds.settled.get(holdKey);
201
+ return live.lane === 'durable' ? live : undefined;
202
+ const recent = approvalDecisionHolds.settled.get(holdKey);
198
203
  return recent !== undefined && recent.at > readMark ? recent.hold : undefined;
199
204
  }
200
205
  function holdDurableDecision(holdKey, callerSignal, decide) {
201
- const h = durableDecisionHolds;
206
+ const h = approvalDecisionHolds;
202
207
  const interruption = new AbortController();
203
208
  let settled = false;
204
209
  const interrupt = () => {
@@ -215,7 +220,7 @@ function holdDurableDecision(holdKey, callerSignal, decide) {
215
220
  resolveOutcome = resolve;
216
221
  rejectOutcome = reject;
217
222
  });
218
- const hold = { outcome, presented: false, interrupt, cardSignal: interruption.signal };
223
+ const hold = { lane: 'durable', outcome, presented: false, interrupt, cardSignal: interruption.signal };
219
224
  h.inFlight.set(holdKey, hold);
220
225
  const release = () => {
221
226
  if (h.inFlight.get(holdKey) === hold)
@@ -278,9 +283,14 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
278
283
  return joinDurableDecision(held, deps, signal);
279
284
  const callKey = approvalCallKey(found.gatedCallId, taskId);
280
285
  const fetched = found;
281
- return holdDurableDecision(durableHoldKey(fetched.pending, callKey), signal, (hold) => presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, fetched, decidedByReading, callKey, hold));
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
+ });
282
292
  }
283
- async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, found, decidedByReading, callKey, hold) {
293
+ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, found, decidedByReading, callKey, hold, ledgerSlot) {
284
294
  const { pending, gatedCallId } = found;
285
295
  const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
286
296
  const streamArgs = gatedCallId !== undefined ? argsByCall.get(gatedCallId) : undefined;
@@ -304,7 +314,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
304
314
  const durableRuleOffersAbsence = pending.ruleOffersAbsence;
305
315
  hold.presented = true;
306
316
  deps.onPresented?.();
307
- const card = await surfaceApprovalCard({
317
+ const card = await surfaceCallTrackedCard(gatedCallId, undefined, ledgerSlot, {
308
318
  toolName,
309
319
  args,
310
320
  callKey,
@@ -352,7 +362,7 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
352
362
  return { kind: 'retracted', gatedCallId };
353
363
  case 'aborted':
354
364
  {
355
- const cancelDeny = bridge.decideTool({ decision: 'deny', reason: 'Interrupted by user' }, gatedCallId, undefined, pending);
365
+ const cancelDeny = bridge.decideTool({ decision: 'deny', reason: INTERRUPT_DENY_REASON }, gatedCallId, undefined, pending);
356
366
  hold.trailing = cancelDeny;
357
367
  observeCancelByDeny(cancelDeny, taskId);
358
368
  }
@@ -393,11 +403,13 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
393
403
  const wireCode = readWireErrorCode(e);
394
404
  const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
395
405
  const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
406
+ const failureTexts = wireFailureTextsOf(e);
407
+ hostLog('debug', `liveToolApprovalWire: decide(approve) failed: ${failureTexts.log}`);
396
408
  return {
397
409
  kind: 'failed',
398
410
  stage: 'decide',
399
411
  gatedCallId,
400
- reason: `decide(approve) failed: ${String(e)}${decideFailureNote(e, 'approve')}`,
412
+ reason: `decide(approve) failed: ${failureTexts.reason}${decideFailureNote(e, 'approve')}`,
401
413
  ...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
402
414
  ...(currentPending !== undefined ? { currentPending } : {}),
403
415
  ...(wireCode !== undefined ? { errorCode: wireCode } : {}),
@@ -478,11 +490,13 @@ async function presentFsApprovalAndDecide(deps, taskId, argsByCall, signal, foun
478
490
  const wireRetriable = typeof e === 'object' && e !== null && refusalSaysStillPending(e);
479
491
  const safetyCode = e instanceof HitlSafetyError ? e.code : undefined;
480
492
  const attributionRefusal = denyAttributionRefusalFromError(e, lastSendCarriedAttribution);
493
+ const failureTexts = wireFailureTextsOf(e);
494
+ hostLog('debug', `liveToolApprovalWire: decide(deny) failed: ${failureTexts.log}`);
481
495
  return {
482
496
  kind: 'failed',
483
497
  stage: 'decide',
484
498
  gatedCallId,
485
- reason: `decide(deny) failed: ${String(e)}${decideFailureNote(e, 'deny')}`,
499
+ reason: `decide(deny) failed: ${failureTexts.reason}${decideFailureNote(e, 'deny')}`,
486
500
  ...(e instanceof DecideTransportRetryExhaustedError ? { retryExhausted: true } : {}),
487
501
  ...(currentPending !== undefined ? { currentPending } : {}),
488
502
  ...(wireCode !== undefined ? { errorCode: wireCode } : {}),
@@ -656,7 +670,7 @@ export function readToolApprovalRespondAck(v) {
656
670
  }
657
671
  return out;
658
672
  }
659
- function readToolApprovalRespondParking(v) {
673
+ export function readToolApprovalRespondParking(v) {
660
674
  if (typeof v !== 'object' || v === null)
661
675
  return undefined;
662
676
  const read = (fn) => {
@@ -675,7 +689,16 @@ function readToolApprovalRespondParking(v) {
675
689
  const hint = read(() => readOwnDataValue(v, 'retryAfterMs'));
676
690
  const retryAfterMs = typeof hint === 'number' && Number.isFinite(hint) && hint >= 0 ? hint : undefined;
677
691
  const decidePath = read(() => readOwnDataNonEmptyString(v, 'decidePath'));
678
- return { kind: 'parking', view: { approvalId, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(decidePath !== undefined ? { 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
+ };
679
702
  }
680
703
  function isNonEmptyStringArray(v) {
681
704
  return Array.isArray(v) && v.length > 0 && v.every(x => typeof x === 'string' && x !== '');
@@ -830,13 +853,27 @@ function readProbeReason(v) {
830
853
  try {
831
854
  if (typeof v !== 'object' || v === null || !Object.hasOwn(v, 'probeReason'))
832
855
  return undefined;
833
- const raw = v.probeReason;
834
- return typeof raw === 'string' && raw.trim() !== '' ? raw : undefined;
856
+ return usableProbeReason(v.probeReason);
835
857
  }
836
858
  catch {
837
859
  return undefined;
838
860
  }
839
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
+ }
840
877
  function readRememberRefused(v) {
841
878
  try {
842
879
  return typeof v === 'object' && v !== null && Object.hasOwn(v, 'rememberRefused') && v.rememberRefused === true;
@@ -845,6 +882,167 @@ function readRememberRefused(v) {
845
882
  return false;
846
883
  }
847
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
+ }
848
1046
  function isNonNegativeFinite(v) {
849
1047
  return typeof v === 'number' && Number.isFinite(v) && v >= 0;
850
1048
  }
@@ -1012,6 +1210,18 @@ function subagentBadgeFor(frame) {
1012
1210
  const MAX_RESPOND_NOTE_CHARS = 2048;
1013
1211
  export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamArgs, signal, lane) {
1014
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) {
1015
1225
  const frameToolCallId = typeof frame.toolCallId === 'string' && frame.toolCallId !== '' ? frame.toolCallId : undefined;
1016
1226
  const decidedByReading = decidedByReadingSnapshotOf(lane);
1017
1227
  const toolName = typeof frame.toolName === 'string' ? frame.toolName : 'Write';
@@ -1041,14 +1251,14 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1041
1251
  const ruleOffers = readRuleOffers(frame.ruleOffers);
1042
1252
  const denialLimitFallback = readDenialLimitFallback(frame.denialLimitFallback);
1043
1253
  const frameShadow = readAllowRuleShadowFromWire(frame, 'frame');
1044
- const card = await surfaceApprovalCard({
1254
+ const card = await surfaceCallTrackedCard(frameToolCallId, approvalId, ledgerSlot, {
1045
1255
  toolName,
1046
1256
  args: args,
1047
1257
  callKey: liveFrameCallKey(approvalId),
1048
1258
  ...(frameToolCallId !== undefined
1049
1259
  ? { toolCallId: frameToolCallId }
1050
1260
  : {}),
1051
- ...(signal ? { signal } : {}),
1261
+ signal: hold.cardSignal,
1052
1262
  ...(isFromSubagent(frame) ? { workerBadge: subagentBadgeFor(frame) } : {}),
1053
1263
  ...(wireNote !== undefined ? { wireNote } : {}),
1054
1264
  ...(argsNotRealInput ? { argsUnavailable: true } : {}),
@@ -1084,7 +1294,16 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1084
1294
  ...(denialLimitFallback !== undefined ? { denialLimitFallback } : {}),
1085
1295
  ...((o) => (o !== undefined ? { origin: o } : {}))(readOwnNonEmptyString(frame, 'origin')),
1086
1296
  ...(readAskClassPair(frame) ?? {}),
1297
+ ...((g) => (g !== undefined ? { boundGrants: g } : {}))(readApprovalBoundGrants(frame)),
1298
+ ...((w) => (w !== undefined ? { classifierNoRuling: w } : {}))(readApprovalClassifierNoRuling(frame)),
1087
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
+ }
1088
1307
  if (argsNotRealInput && card.kind === 'allow' && card.updatedInput !== undefined) {
1089
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)`);
1090
1309
  surfaceEditRefusedOnBlindAsk();
@@ -1094,6 +1313,22 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
1094
1313
  hostLog('debug', `liveToolApprovalWire: ${approvalId} card retracted without a decision${card.reason ? ` (${card.reason})` : ''} — nothing sent`);
1095
1314
  return { decision: 'unresolved', retracted: true };
1096
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';
1097
1332
  const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
1098
1333
  const denySettledByForWire = card.kind === 'deny' ? readApprovalDenySettledBy(card) : undefined;
1099
1334
  const attributionKeys = decision === 'deny' ? decisionAttributionKeyOf(decidedByReading, denySettledByForWire) : {};
@@ -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
  }