@sema-agent/client-core 0.109.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 (123) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +46 -22
  3. package/dist/adapt/arms.js +20 -7
  4. package/dist/adapter/downstream/eventToSdkMessage.js +11 -6
  5. package/dist/declared/supportFloorDeclared.js +1 -1
  6. package/dist/displayUntrusted.js +132 -14
  7. package/dist/engineErrorCodes.d.ts +3 -0
  8. package/dist/engineErrorCodes.js +3 -0
  9. package/dist/engineHealthFacts.d.ts +9 -0
  10. package/dist/engineHealthFacts.js +21 -1
  11. package/dist/engineInlineTaskStats.d.ts +4 -3
  12. package/dist/engineInlineTaskStats.js +18 -2
  13. package/dist/engineNoticeCodes.d.ts +10 -0
  14. package/dist/engineNoticeCodes.js +33 -1
  15. package/dist/engineRepoint.d.ts +140 -0
  16. package/dist/engineRepoint.js +285 -0
  17. package/dist/engineRowRef.js +2 -0
  18. package/dist/engineSessionMessages.d.ts +2 -1
  19. package/dist/engineSessionMessages.js +5 -0
  20. package/dist/errorSupplementRoles.js +2 -1
  21. package/dist/fleet/fleetProjection.js +3 -2
  22. package/dist/generated/engineFactTables.d.ts +2 -0
  23. package/dist/generated/engineFactTables.js +12 -0
  24. package/dist/generated/sdkFactTables.d.ts +1 -0
  25. package/dist/generated/sdkFactTables.js +4 -0
  26. package/dist/hitl/approvalAskers.d.ts +6 -0
  27. package/dist/hitl/approvalAskers.js +30 -0
  28. package/dist/hitl/approvalCardReasons.js +6 -3
  29. package/dist/hitl/approvalRequestFrameReading.d.ts +7 -0
  30. package/dist/hitl/approvalRequestFrameReading.js +11 -0
  31. package/dist/hitl/approvalResolution.d.ts +8 -1
  32. package/dist/hitl/approvalResolution.js +4 -0
  33. package/dist/hitl/askGateWire.js +0 -2
  34. package/dist/hitl/automaticAnswerVerdict.js +2 -2
  35. package/dist/hitl/decisionConflict.js +6 -3
  36. package/dist/hitl/decisionRefusal.js +1 -0
  37. package/dist/hitl/decisionSend.d.ts +3 -0
  38. package/dist/hitl/decisionSend.js +29 -11
  39. package/dist/hitl/editedApprovalAcceptance.d.ts +25 -0
  40. package/dist/hitl/editedApprovalAcceptance.js +26 -0
  41. package/dist/hitl/frameRouter.js +4 -18
  42. package/dist/hitl/gateLedger.d.ts +0 -4
  43. package/dist/hitl/gateLedger.js +0 -19
  44. package/dist/hitl/hitlBridge.d.ts +1 -0
  45. package/dist/hitl/hitlBridge.js +4 -4
  46. package/dist/hitl/hitlHostSurface.d.ts +1 -0
  47. package/dist/hitl/hitlHostSurface.js +7 -2
  48. package/dist/hitl/hostWithdrawal.d.ts +15 -0
  49. package/dist/hitl/hostWithdrawal.js +38 -0
  50. package/dist/hitl/parkResolver.js +25 -9
  51. package/dist/hitl/persistedRulesWire.js +2 -2
  52. package/dist/hitl/planReviewWire.js +14 -4
  53. package/dist/hitl/sessionPolicyWire.js +6 -4
  54. package/dist/hitl/standingRows.d.ts +1 -0
  55. package/dist/hitl/standingRows.js +15 -1
  56. package/dist/hitl/toolApprovalWire.d.ts +35 -8
  57. package/dist/hitl/toolApprovalWire.js +235 -33
  58. package/dist/hooksWireCaps.js +31 -1
  59. package/dist/hostTurnSettings.d.ts +10 -4
  60. package/dist/hostTurnSettings.js +112 -5
  61. package/dist/i18n/messages.en.d.ts +83 -0
  62. package/dist/i18n/messages.en.js +86 -2
  63. package/dist/i18n/messages.zh.js +96 -2
  64. package/dist/index.d.ts +21 -7
  65. package/dist/index.js +14 -4
  66. package/dist/inheritEnvCapability.js +2 -0
  67. package/dist/inputAuthor.d.ts +16 -0
  68. package/dist/inputAuthor.js +44 -0
  69. package/dist/interruptRefusalNote.d.ts +2 -0
  70. package/dist/interruptRefusalNote.js +17 -0
  71. package/dist/liveModelCatalog.d.ts +2 -0
  72. package/dist/liveModelCatalog.js +21 -7
  73. package/dist/liveQuestionStore.d.ts +4 -1
  74. package/dist/liveQuestionStore.js +12 -3
  75. package/dist/mcpEngineLeg.d.ts +3 -0
  76. package/dist/mcpEngineLeg.js +38 -1
  77. package/dist/mcpPanel.js +2 -0
  78. package/dist/mcpReconnect.d.ts +9 -1
  79. package/dist/mcpReconnect.js +121 -16
  80. package/dist/modelCapabilityProbe.js +4 -3
  81. package/dist/modsFace.d.ts +8 -1
  82. package/dist/modsFace.js +27 -0
  83. package/dist/notifications.d.ts +17 -6
  84. package/dist/notifications.js +67 -19
  85. package/dist/ownKey.d.ts +0 -5
  86. package/dist/ownKey.js +0 -7
  87. package/dist/peerFrames.d.ts +40 -2
  88. package/dist/peerFrames.js +158 -3
  89. package/dist/pluginHooksWire.d.ts +2 -1
  90. package/dist/pluginHooksWire.js +56 -1
  91. package/dist/replayBody.js +10 -2
  92. package/dist/request/taskRequest.d.ts +5 -0
  93. package/dist/request/taskRequest.js +138 -21
  94. package/dist/resumeRefusalCopy.d.ts +1 -3
  95. package/dist/resumeRefusalCopy.js +2 -1
  96. package/dist/retryStatus.d.ts +2 -2
  97. package/dist/retryStatus.js +2 -0
  98. package/dist/seam.d.ts +12 -1
  99. package/dist/seam.js +7 -1
  100. package/dist/seatContract.d.ts +86 -9
  101. package/dist/seatContract.js +196 -7
  102. package/dist/sessionBackground.js +2 -1
  103. package/dist/sessionBackgroundHandoff.js +2 -1
  104. package/dist/sessionEngine.d.ts +2 -0
  105. package/dist/sessionEngine.js +111 -0
  106. package/dist/sessionMemoryStatus.d.ts +2 -1
  107. package/dist/sessionSyncFileHistory.d.ts +4 -0
  108. package/dist/sessionSyncFileHistory.js +28 -0
  109. package/dist/sseIdleTriage.d.ts +13 -1
  110. package/dist/sseIdleTriage.js +24 -6
  111. package/dist/subagent/engineSubagentTail.js +6 -0
  112. package/dist/terminalStatuses.d.ts +1 -0
  113. package/dist/terminalStatuses.js +4 -1
  114. package/dist/turnFailurePresentation.js +9 -2
  115. package/dist/typePins.d.ts +11 -0
  116. package/dist/usageAnalytics.d.ts +17 -0
  117. package/dist/usageAnalytics.js +44 -2
  118. package/dist/wireFailureShape.d.ts +2 -5
  119. package/dist/wireFailureShape.js +10 -20
  120. package/dist/wireRefusalCopy.d.ts +10 -0
  121. package/dist/wireRefusalCopy.js +76 -0
  122. package/docs/INTEGRATION-CLIENTS.md +2126 -20
  123. package/package.json +3 -3
@@ -1,5 +1,6 @@
1
1
  import { isApprovalRequestFrameV1 } from '@sema-agent/sdk';
2
2
  import { displayUntrusted } from '../displayUntrusted.js';
3
+ import { guardedRead, readOwnDataValue } from '../ownKey.js';
3
4
  export const APPROVAL_REQUEST_FRAME_UNREADABLE_WHYS = Object.freeze(['version_unknown', 'kind_unknown', 'unreadable_fields']);
4
5
  const KNOWN_FRAME_TYPE = 'approval_request';
5
6
  const KNOWN_SCHEMA_VERSION = 1;
@@ -60,6 +61,16 @@ export function approvalRequestFrameReadingOf(frame) {
60
61
  export function approvalUnreadableWhyWordOf(v) {
61
62
  return APPROVAL_REQUEST_FRAME_UNREADABLE_WHYS.find((w) => w === v);
62
63
  }
64
+ export function cardPayloadReadingOf(card) {
65
+ if (typeof card !== 'object' || card === null)
66
+ return Object.freeze({ payloadUnreadable: false });
67
+ const flag = guardedRead(() => readOwnDataValue(card, 'cardPayloadUnreadable'));
68
+ if (flag !== true)
69
+ return Object.freeze({ payloadUnreadable: false });
70
+ const word = guardedRead(() => readOwnDataValue(card, 'cardPayloadUnreadableWhy'));
71
+ const why = typeof word === 'string' ? approvalUnreadableWhyWordOf(word) : undefined;
72
+ return why === undefined ? Object.freeze({ payloadUnreadable: true }) : Object.freeze({ payloadUnreadable: true, why });
73
+ }
63
74
  export function approvalFrameUnreadableWhyOf(reading) {
64
75
  try {
65
76
  if (typeof reading !== 'object' || reading === null)
@@ -8,7 +8,7 @@ import { type DeliveredReading, type LadderSafetyStop } from './attributionLadde
8
8
  import { type CheckpointRowVerdictReading } from '../checkpointRowVerdict.js';
9
9
  export declare const APPROVAL_RESOLUTION_LANES: readonly ["respond", "decide", "ask"];
10
10
  export type ApprovalResolutionLane = (typeof APPROVAL_RESOLUTION_LANES)[number];
11
- export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "wrong_gate", "fetch_failed", "no_input", "card_unavailable", "safety_stop", "row_unresumable"];
11
+ export declare const APPROVAL_NOT_SENT_CAUSES: readonly ["retracted", "edit_refused", "no_pending", "wrong_gate", "fetch_failed", "no_input", "card_unavailable", "safety_stop", "row_unresumable", "withdrawn"];
12
12
  export type ApprovalNotSentCause = (typeof APPROVAL_NOT_SENT_CAUSES)[number];
13
13
  export declare const APPROVAL_UNSETTLED_CAUSES: readonly ["engine_refused", "engine_error", "no_answer", "transport_exhausted", "parking_in_progress", "interrupted", "orchestration", "unreadable"];
14
14
  export type ApprovalUnsettledCause = (typeof APPROVAL_UNSETTLED_CAUSES)[number];
@@ -18,6 +18,11 @@ export interface ApprovalDenyFacts {
18
18
  readonly policyCause?: PolicyDenyCause;
19
19
  readonly attributionKey?: 'dropped' | 'renamed';
20
20
  }
21
+ export interface ToolPermissionSettledElsewhere {
22
+ readonly firstDecision: 'allow' | 'deny';
23
+ readonly contrary: boolean;
24
+ }
25
+ export declare function toolPermissionSettledElsewhereOf(first: 'allow' | 'deny', sent: ToolApprovalRespondDecision | 'approve'): ToolPermissionSettledElsewhere;
21
26
  interface ApprovalResolutionBase {
22
27
  readonly lane: ApprovalResolutionLane;
23
28
  readonly gatedCallId?: string;
@@ -30,6 +35,7 @@ export interface ApprovalResolutionDecided extends ApprovalResolutionBase {
30
35
  readonly receipt?: DecideReceiptView;
31
36
  readonly ack?: ToolApprovalRespondAckView;
32
37
  readonly answered?: AskAnsweredOutput;
38
+ readonly settledElsewhere?: ToolPermissionSettledElsewhere;
33
39
  }
34
40
  export interface ApprovalResolutionNotSent extends ApprovalResolutionBase {
35
41
  readonly kind: 'not_sent';
@@ -38,6 +44,7 @@ export interface ApprovalResolutionNotSent extends ApprovalResolutionBase {
38
44
  readonly rowVerdict?: Exclude<CheckpointRowVerdictReading, {
39
45
  readonly kind: 'ok';
40
46
  }>;
47
+ readonly toldBy?: 'host';
41
48
  readonly detail?: string;
42
49
  }
43
50
  export interface ApprovalResolutionUnsettled extends ApprovalResolutionBase {
@@ -16,6 +16,7 @@ export const APPROVAL_NOT_SENT_CAUSES = Object.freeze([
16
16
  'card_unavailable',
17
17
  'safety_stop',
18
18
  'row_unresumable',
19
+ 'withdrawn',
19
20
  ]);
20
21
  export const APPROVAL_UNSETTLED_CAUSES = Object.freeze([
21
22
  'engine_refused',
@@ -27,6 +28,9 @@ export const APPROVAL_UNSETTLED_CAUSES = Object.freeze([
27
28
  'orchestration',
28
29
  'unreadable',
29
30
  ]);
31
+ export function toolPermissionSettledElsewhereOf(first, sent) {
32
+ return Object.freeze({ firstDecision: first, contrary: (first === 'deny') !== (sent === 'deny') });
33
+ }
30
34
  export function isApprovalDecided(r) {
31
35
  return typeof r === 'object' && r !== null && r.kind === 'decided';
32
36
  }
@@ -91,7 +91,6 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
91
91
  await abortableSleep(reopenAfterMs, opts?.signal ?? new AbortController().signal);
92
92
  if (!opts?.signal?.aborted) {
93
93
  const seq = led.lastSeq();
94
- led.beginStreamSegment();
95
94
  stream = deps.runsEvents(taskId.current, {
96
95
  ...(seq !== undefined ? { lastEventId: seq } : {}),
97
96
  ...(opts?.signal ? { signal: opts.signal } : {}),
@@ -170,7 +169,6 @@ export async function* bridgeAskUserQuestionGates(source, deps, opts) {
170
169
  led.forgetRunScopedState();
171
170
  }
172
171
  const seq = handoff !== undefined ? undefined : led.lastSeq();
173
- led.beginStreamSegment();
174
172
  stream = deps.runsEvents(taskId.current, {
175
173
  ...(seq !== undefined ? { lastEventId: seq } : {}),
176
174
  ...(opts?.signal ? { signal: opts.signal } : {}),
@@ -61,13 +61,13 @@ export function foldModeArbitrationRow(classes, askFace) {
61
61
  return ORDINARY_ROW;
62
62
  return Object.freeze({ bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor });
63
63
  }
64
- const REAL_APPROVAL_CLASSES = new Set(CORE_REAL_APPROVAL_ASK_CLASSES);
64
+ const HOOK_BLOCKING_CLASSES = new Set([...CORE_REAL_APPROVAL_ASK_CLASSES, ORIGIN_IMPLIES_CLASS.content_question]);
65
65
  function classBlocks(lane, c, face) {
66
66
  if (lane === 'mode_posture')
67
67
  return bypassImmuneOn(c, face);
68
68
  if (lane === 'standing_rule')
69
69
  return !TABLE[c].ruleMayClear;
70
- return REAL_APPROVAL_CLASSES.has(c);
70
+ return HOOK_BLOCKING_CLASSES.has(c);
71
71
  }
72
72
  function mustAsk(road, extra) {
73
73
  return Object.freeze({ kind: 'must_ask', road, ...(extra?.askClass !== undefined ? { askClass: extra.askClass } : {}), ...(extra?.origin !== undefined ? { origin: extra.origin } : {}) });
@@ -1,4 +1,4 @@
1
- import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_UNSUPPORTED_VERSION, RESUME_OUTCOME_INVALID } from '../engineErrorCodes.js';
1
+ import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_INVALID_OUTCOME, CHECKPOINT_UNSUPPORTED_VERSION, RESUME_OUTCOME_INVALID } from '../engineErrorCodes.js';
2
2
  import { msg, renderMessage } from '../i18n/translate.js';
3
3
  import { ownDataField } from '../ownKey.js';
4
4
  import { SDK_DECIDE_BINDING_KEYS } from '../generated/sdkFactTables.js';
@@ -28,16 +28,19 @@ export function decisionConflictOf(err, captured, protocolNameRead) {
28
28
  return undefined;
29
29
  }
30
30
  }
31
- const protocolName = namedProtocolOf(protocolNameRead ?? protocolErrorNameReading(err, extra));
31
+ const protocolName = namedProtocolOf(protocolNameRead ?? protocolErrorNameReading(err));
32
32
  if (code === CHECKPOINT_UNSUPPORTED_VERSION)
33
33
  return 'format_version';
34
34
  const bindingFamily = code === APPROVAL_BINDING_MISMATCH || (code === undefined && protocolName === PROTOCOL_ERROR_NAME.ApprovalBindingMismatchError);
35
- if (!bindingFamily && code !== RESUME_OUTCOME_INVALID)
35
+ const rowOwnFamily = code === CHECKPOINT_INVALID_OUTCOME || (code === undefined && protocolName === PROTOCOL_ERROR_NAME.CheckpointInvalidOutcomeError);
36
+ if (!bindingFamily && !rowOwnFamily && code !== RESUME_OUTCOME_INVALID)
36
37
  return undefined;
37
38
  const slots = readRefusalSlots(err, REFUSAL_WORD_KEYS, extra.bag);
38
39
  const field = slots.field.word;
39
40
  if (code === RESUME_OUTCOME_INVALID)
40
41
  return field === ANSWER_FIELD ? 'answer_invalid' : field === GRANT_DIGEST_FIELD ? 'grant_changed' : undefined;
42
+ if (rowOwnFamily)
43
+ return field === undefined && !slots.field.present && slots.reason.word !== undefined ? 'row_integrity' : undefined;
41
44
  if (field === undefined) {
42
45
  if (slots.field.present)
43
46
  return undefined;
@@ -226,6 +226,7 @@ export function mintDecisionRefusalReading(err, context, attributionCarried, del
226
226
  const acceptableForm = errorCode === RESUME_CONSTRAINT_REJECTED && obj !== undefined ? isolated(() => readErrorPresent(obj, 'acceptableForm')) : undefined;
227
227
  const transient = inRange &&
228
228
  conflict !== 'grant_changed' &&
229
+ conflict !== 'row_integrity' &&
229
230
  !unrecoverable &&
230
231
  (TRANSIENT_STATUSES.has(status) || retriable || (status === 404 && obj !== undefined && isolated(() => notPendingCauseReading(obj)) === 'other_replica'));
231
232
  return Object.freeze({
@@ -1,5 +1,6 @@
1
1
  import type { AgentClient, ElicitResponse } from '@sema-agent/sdk';
2
2
  import type { UiLanguage } from '../env/uiLanguage.js';
3
+ import type { ToolPermissionSettledElsewhere } from './approvalResolution.js';
3
4
  import { type AskGoneDetail } from '../decideReceipt.js';
4
5
  import type { ApprovalDecidedByReading } from '../approvalDecidedByCapability.js';
5
6
  import { type DecisionNoteAudit } from './approvalDecisionNoteAudit.js';
@@ -64,6 +65,7 @@ interface HitlDecisionOutcomeBase {
64
65
  readonly attempts: number;
65
66
  readonly fallback?: 'approval';
66
67
  readonly noteDropped?: true;
68
+ readonly allowNoteDropped?: true;
67
69
  readonly rememberDropped?: true;
68
70
  readonly allowSessionCarried?: true;
69
71
  readonly attributionDropped?: true;
@@ -115,5 +117,6 @@ export interface HitlDecisionSendTiming {
115
117
  export declare function __setHitlDecisionSendTimingForTests(t?: HitlDecisionSendTiming): void;
116
118
  export declare function hitlAskIdempotencyKey(askId: string, verdict: 'approve' | 'deny'): string;
117
119
  export declare function sendHitlDecision(request: HitlDecisionSendRequest, deps: HitlDecisionSendDeps): Promise<HitlDecisionSendOutcome>;
120
+ export declare function toolPermissionSettledLine(fact: ToolPermissionSettledElsewhere | null | undefined, lang?: UiLanguage): string | undefined;
118
121
  export declare function hitlDecisionOutcomeContent(outcome: HitlDecisionSendOutcome, lang?: UiLanguage): string | undefined;
119
122
  export {};
@@ -14,7 +14,7 @@ import { ASK_DECIDED_CODE, classifyDeliveredDecisionRefusal, decisionRefusalClos
14
14
  import { sessionAllowEligible } from './sessionAllowRoute.js';
15
15
  import { addDecisionOpKeys, closeDecisionOp, decisionLedgerKeysOf, decisionOpsWithKey, openDecisionOp } from './decisionLedger.js';
16
16
  import { hostHoldIsActive, takeHostHoldLedgerOp, tryHoldApprovalNow } from './approvalHolds.js';
17
- import { answerPayloadFor, decisionAttributionKeyOf, isToolApprovalRespondReplayed, readApprovalDenySettledBy, readToolApprovalRespondAck, readToolApprovalRespondParking, } from './toolApprovalWire.js';
17
+ import { allowNoteReadingOf, answerPayloadFor, decisionAttributionKeyOf, readApprovalDenySettledBy, readToolApprovalRespondAck, readToolApprovalRespondParking, replayedFirstDecisionOf, settlementAckOf, } from './toolApprovalWire.js';
18
18
  export const HITL_DECISION_FAMILIES = Object.freeze(['approval', 'ask', 'elicitation']);
19
19
  export const HITL_DECISION_NOT_APPLIED_CAUSES = Object.freeze(['gone', 'parked', 'parking', 'lane_disabled', 'refused', 'idempotency_crossed']);
20
20
  export const HITL_DECISION_UNCONFIRMED_CAUSES = Object.freeze(['transport', 'server_error', 'local_error', 'aborted', 'gone_after_retry']);
@@ -163,9 +163,10 @@ async function sendApprovalFamily(respond, approvalId, answer, ctx) {
163
163
  const hint = view !== undefined ? ownReadingSlot(view, 'retryAfterMs') : undefined;
164
164
  return { ...base, kind: 'not_applied', cause: 'parking', ...(view !== undefined ? { parking: view, ...(hint !== undefined ? { retryAfterMs: hint } : {}) } : {}) };
165
165
  }
166
- const parsed = readToolApprovalRespondAck(raw);
167
- if (parsed !== undefined && isToolApprovalRespondReplayed(parsed)) {
168
- const first = parsed.approvalId === approvalId ? verdictOf(parsed.decision) : undefined;
166
+ const parsed = settlementAckOf(readToolApprovalRespondAck(raw));
167
+ const replayWord = replayedFirstDecisionOf(parsed);
168
+ if (parsed !== undefined && replayWord !== undefined) {
169
+ const first = parsed.approvalId === approvalId ? verdictOf(replayWord) : undefined;
169
170
  return {
170
171
  ...base,
171
172
  kind: 'settled_elsewhere',
@@ -183,6 +184,7 @@ function bodyFacts(answer, payload, rememberUnsupported) {
183
184
  return {
184
185
  ...(reason !== undefined && reason.trim() !== '' && payload.note === undefined ? { noteDropped: true } : {}),
185
186
  ...(rememberUnsupported && answer.kind === 'allow' && answer.allowSession === true ? { rememberDropped: true } : {}),
187
+ ...(answer.kind === 'allow' && allowNoteReadingOf(answer).kind === 'text' ? { allowNoteDropped: true } : {}),
186
188
  };
187
189
  }
188
190
  function answerGateCannotTake(out) {
@@ -361,8 +363,10 @@ function snapshotAnswer(raw) {
361
363
  if (kind === 'allow') {
362
364
  const allowSession = guardedRead(() => readOwnDataValue(raw, 'allowSession')) === true;
363
365
  const edited = guardedRead(() => readOwnDataValue(raw, 'updatedInput'));
366
+ const noteRaw = guardedRead(() => readOwnDataValue(raw, 'note'));
367
+ const note = typeof noteRaw === 'string' && noteRaw.trim() !== '' ? noteRaw : undefined;
364
368
  if (edited === undefined)
365
- return Object.freeze({ kind: 'allow', allowSession });
369
+ return Object.freeze({ kind: 'allow', allowSession, ...(note !== undefined ? { note } : {}) });
366
370
  let copy;
367
371
  try {
368
372
  const text = JSON.stringify(edited);
@@ -373,7 +377,7 @@ function snapshotAnswer(raw) {
373
377
  catch {
374
378
  return undefined;
375
379
  }
376
- return Object.freeze({ kind: 'allow', allowSession, updatedInput: copy });
380
+ return Object.freeze({ kind: 'allow', allowSession, ...(note !== undefined ? { note } : {}), updatedInput: copy });
377
381
  }
378
382
  if (kind === 'deny') {
379
383
  const reason = guardedRead(() => readOwnDataValue(raw, 'reason'));
@@ -560,6 +564,24 @@ export async function sendHitlDecision(request, deps) {
560
564
  ownHold.release(out.kind === 'applied' || out.kind === 'settled_elsewhere' ? 'answered' : undefined);
561
565
  return Object.freeze(out);
562
566
  }
567
+ function settledElsewhereContraryContent(first, lang) {
568
+ return first === 'deny'
569
+ ? renderMessage(msg('approval.decision.settledElsewhereContrary.deniedFirst'), lang)
570
+ : renderMessage(msg('approval.decision.settledElsewhereContrary.approvedFirst'), lang);
571
+ }
572
+ export function toolPermissionSettledLine(fact, lang) {
573
+ try {
574
+ if (typeof fact !== 'object' || fact === null)
575
+ return undefined;
576
+ if (readOwnDataValue(fact, 'contrary') !== true)
577
+ return undefined;
578
+ const first = readOwnDataValue(fact, 'firstDecision');
579
+ return first === 'allow' || first === 'deny' ? settledElsewhereContraryContent(first, lang) : undefined;
580
+ }
581
+ catch {
582
+ return undefined;
583
+ }
584
+ }
563
585
  export function hitlDecisionOutcomeContent(outcome, lang) {
564
586
  try {
565
587
  if (typeof outcome !== 'object' || outcome === null)
@@ -569,11 +591,7 @@ export function hitlDecisionOutcomeContent(outcome, lang) {
569
591
  if (readOwnDataValue(outcome, 'contrary') !== true)
570
592
  return undefined;
571
593
  const first = readOwnDataValue(outcome, 'firstDecision');
572
- if (first === 'deny')
573
- return renderMessage(msg('approval.decision.settledElsewhereContrary.deniedFirst'), lang);
574
- if (first === 'approve')
575
- return renderMessage(msg('approval.decision.settledElsewhereContrary.approvedFirst'), lang);
576
- return undefined;
594
+ return first === 'deny' || first === 'approve' ? settledElsewhereContraryContent(first, lang) : undefined;
577
595
  }
578
596
  if (kind === 'unconfirmed' && readOwnDataValue(outcome, 'cause') === 'gone_after_retry')
579
597
  return renderMessage(msg('approval.decision.unconfirmedAfterRetry'), lang);
@@ -0,0 +1,25 @@
1
+ import type { UiLanguage } from '../env/uiLanguage.js';
2
+ export declare const EDITED_APPROVAL_STATES: readonly ["accepts", "refuses", "unknown"];
3
+ export type EditedApprovalState = (typeof EDITED_APPROVAL_STATES)[number];
4
+ export declare const EDITED_APPROVAL_BASES: Readonly<{
5
+ accepts: readonly ["args_available"];
6
+ refuses: readonly ["args_unavailable"];
7
+ unknown: readonly ["card_unreadable", "slot_unreadable", "slot_malformed"];
8
+ }>;
9
+ interface EditedApprovalAccepts {
10
+ readonly state: 'accepts';
11
+ readonly basis: (typeof EDITED_APPROVAL_BASES.accepts)[number];
12
+ }
13
+ interface EditedApprovalRefuses {
14
+ readonly state: 'refuses';
15
+ readonly basis: (typeof EDITED_APPROVAL_BASES.refuses)[number];
16
+ readonly askStaysPending: true;
17
+ }
18
+ interface EditedApprovalUnknown {
19
+ readonly state: 'unknown';
20
+ readonly basis: (typeof EDITED_APPROVAL_BASES.unknown)[number];
21
+ }
22
+ export type EditedApprovalAcceptance = EditedApprovalAccepts | EditedApprovalRefuses | EditedApprovalUnknown;
23
+ export declare function editedApprovalAcceptanceOf(card: {} | null | undefined): EditedApprovalAcceptance;
24
+ export declare function editedApprovalRefusedLine(lang?: UiLanguage): string;
25
+ export {};
@@ -0,0 +1,26 @@
1
+ import { msg, renderMessage } from '../i18n/translate.js';
2
+ import { slotReadOf } from '../wireFailureShape.js';
3
+ export const EDITED_APPROVAL_STATES = Object.freeze(['accepts', 'refuses', 'unknown']);
4
+ export const EDITED_APPROVAL_BASES = Object.freeze({
5
+ accepts: Object.freeze(['args_available']),
6
+ refuses: Object.freeze(['args_unavailable']),
7
+ unknown: Object.freeze(['card_unreadable', 'slot_unreadable', 'slot_malformed']),
8
+ });
9
+ const ACCEPTS = Object.freeze({ state: 'accepts', basis: 'args_available' });
10
+ const REFUSES = Object.freeze({ state: 'refuses', basis: 'args_unavailable', askStaysPending: true });
11
+ const UNKNOWN_CARD = Object.freeze({ state: 'unknown', basis: 'card_unreadable' });
12
+ const UNKNOWN_SLOT = Object.freeze({ state: 'unknown', basis: 'slot_unreadable' });
13
+ const UNKNOWN_MALFORMED = Object.freeze({ state: 'unknown', basis: 'slot_malformed' });
14
+ export function editedApprovalAcceptanceOf(card) {
15
+ if (typeof card !== 'object' || card === null)
16
+ return UNKNOWN_CARD;
17
+ const read = slotReadOf(card, 'argsUnavailable');
18
+ if (read.presence === 'unreadable')
19
+ return UNKNOWN_SLOT;
20
+ if (read.presence === 'absent' || read.value === undefined)
21
+ return ACCEPTS;
22
+ return read.value === true ? REFUSES : UNKNOWN_MALFORMED;
23
+ }
24
+ export function editedApprovalRefusedLine(lang) {
25
+ return renderMessage(msg('approval.editRefusal.line'), lang);
26
+ }
@@ -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';
@@ -349,7 +349,7 @@ async function routeToolApprovalFrame(ev, ctx) {
349
349
  return { kind: 'skip' };
350
350
  }
351
351
  const settledHere = settledApprovalDisposition(ev);
352
- if (settledHere !== undefined || (hasLedgerPositionKey(ev) && !led.preambleApprovals().has(ev.approvalId))) {
352
+ if (settledHere !== undefined || hasLedgerPositionKey(ev)) {
353
353
  const disposition = settledHere ?? (await judgeLedgerApprovalFrame(ev, ctx));
354
354
  if (disposition.kind !== 'live') {
355
355
  hostLog('debug', disposition.kind === 'decided' && disposition.settled !== undefined
@@ -357,8 +357,6 @@ async function routeToolApprovalFrame(ev, ctx) {
357
357
  : `liveHitlAskWire: tool_approval frame ${ev.approvalId} read back from the run event log is ${disposition.kind}` +
358
358
  `${disposition.kind === 'unknown' ? ` (${disposition.why})` : disposition.cause !== undefined ? ` (${disposition.cause})` : ''}` +
359
359
  ' — not raised as a question, no decision sent');
360
- if (settledHere === undefined && replayedApprovalClosesCardFrame(disposition))
361
- noteEngineClosedApproval(ev.approvalId, false);
362
360
  if (disposition.kind === 'unknown' && ctx.signal?.aborted !== true)
363
361
  surfaceReplayedApprovalListUnreadable();
364
362
  if (deps.onReplayedApproval !== undefined) {
@@ -416,7 +414,7 @@ async function judgeLedgerApprovalFrame(ev, ctx) {
416
414
  .finally(release);
417
415
  });
418
416
  const approvals = got.kind === 'value' ? { read: 'ok', body: got.value } : { read: 'failed' };
419
- return replayedApprovalDisposition(ev, { preamble: ctx.led.preambleApprovals(), approvals });
417
+ return replayedApprovalDisposition(ev, { approvals });
420
418
  }
421
419
  function routeToolStart(ev, callId, led) {
422
420
  led.noteStart(callId, ev);
@@ -511,17 +509,5 @@ export async function routeFrame(ev, ctx) {
511
509
  if (ev.type === 'failed') {
512
510
  return { kind: 'end', events: [...flushHeldWithInterruptRewrite(led, { terminal: ev, signal: ctx.signal }), ev] };
513
511
  }
514
- if (ev.type === 'approval_request') {
515
- try {
516
- if (!hasLedgerPositionKey(ev)) {
517
- const id = ev.approvalId;
518
- if (typeof id === 'string' && id !== '')
519
- led.notePreambleApproval(id);
520
- }
521
- }
522
- catch (e) {
523
- hostLog('debug', `liveHitlAskWire: approval_request frame unreadable — not counted as preamble evidence: ${String(e).slice(0, 120)}`);
524
- }
525
- }
526
512
  return { kind: 'yield', events: [ev] };
527
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);
@@ -160,6 +160,7 @@ export declare class HitlBridge {
160
160
  decision: 'approve';
161
161
  updatedInput?: unknown;
162
162
  remember?: 'session' | undefined;
163
+ feedback?: string | undefined;
163
164
  } | HitlDecideDenyOutcome, toolUseID?: string, opts?: {
164
165
  signal?: AbortSignal;
165
166
  operation?: DecideOperationSpan;
@@ -385,6 +385,7 @@ export class HitlBridge {
385
385
  ...this.bindingOf(pending),
386
386
  ...(outcome.updatedInput !== undefined ? { updatedInput: outcome.updatedInput } : {}),
387
387
  ...(outcome.remember !== undefined ? { remember: outcome.remember } : {}),
388
+ ...(outcome.feedback !== undefined ? { feedback: outcome.feedback } : {}),
388
389
  }
389
390
  : {
390
391
  decision: 'deny',
@@ -598,12 +599,11 @@ function bindingMismatchReading(e) {
598
599
  return 'not';
599
600
  const code = readWireErrorCodeReading(e);
600
601
  const nameReading = protocolErrorNameReading(e);
601
- const family = (code.state === 'code' && (code.code === APPROVAL_BINDING_MISMATCH || code.code === CHECKPOINT_INVALID_OUTCOME)) ||
602
- (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);
603
605
  if (family)
604
606
  return 'mismatch';
605
- if (nameReading.state === 'sdk-instance' && code.state !== 'code')
606
- return 'unreadable';
607
607
  if (code.state === 'unreadable')
608
608
  return 'unreadable';
609
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
+ }