@sema-agent/client-core 0.99.0 → 0.100.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 (128) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/README.md +32 -19
  3. package/dist/adapt/arms.js +60 -36
  4. package/dist/adapt/panelTasks.d.ts +2 -1
  5. package/dist/adapt/panelTasks.js +32 -9
  6. package/dist/adapt/textStream.d.ts +1 -1
  7. package/dist/adapt/textStream.js +51 -28
  8. package/dist/adapter/activeRunSelfHeal.d.ts +7 -0
  9. package/dist/adapter/activeRunSelfHeal.js +58 -11
  10. package/dist/adapter/downstream/eventToSdkMessage.d.ts +25 -6
  11. package/dist/adapter/downstream/eventToSdkMessage.js +125 -9
  12. package/dist/adapter/downstream/terminalToSdkResult.js +10 -0
  13. package/dist/adapter/downstream/wiringManifestView.d.ts +3 -1
  14. package/dist/adapter/downstream/wiringManifestView.js +1 -0
  15. package/dist/adapter/runStream.js +32 -5
  16. package/dist/adapter/types.d.ts +2 -0
  17. package/dist/agentsWireCaps.d.ts +7 -0
  18. package/dist/agentsWireCaps.js +100 -15
  19. package/dist/cloudConfigWireCaps.d.ts +10 -1
  20. package/dist/cloudConfigWireCaps.js +156 -62
  21. package/dist/compactionStatus.d.ts +34 -0
  22. package/dist/compactionStatus.js +196 -0
  23. package/dist/decideFailureNote.js +3 -3
  24. package/dist/displayUntrusted.js +150 -5
  25. package/dist/engineAgentPanelStore.d.ts +7 -4
  26. package/dist/engineErrorCodes.d.ts +9 -1
  27. package/dist/engineErrorCodes.js +9 -5
  28. package/dist/engineRowRef.js +1 -0
  29. package/dist/engineToolDirectory.d.ts +3 -0
  30. package/dist/engineToolDirectory.js +4 -2
  31. package/dist/eventLeg.d.ts +1 -7
  32. package/dist/eventLeg.js +7 -8
  33. package/dist/fileHistoryCaptureCapability.d.ts +0 -3
  34. package/dist/fileHistoryCaptureCapability.js +0 -4
  35. package/dist/generated/engineFactTables.d.ts +1 -0
  36. package/dist/generated/engineFactTables.js +27 -0
  37. package/dist/generated/engineNoticeTables.js +3 -0
  38. package/dist/generated/settingsSchemaFns.d.ts +12 -0
  39. package/dist/generated/settingsSchemaFns.js +13 -0
  40. package/dist/generated/settingsSchemaMcpRequestEntry.d.ts +1 -0
  41. package/dist/generated/settingsSchemaMcpRequestEntry.js +162 -0
  42. package/dist/generated/settingsSchemaTables.d.ts +1 -0
  43. package/dist/generated/settingsSchemaTables.js +5 -0
  44. package/dist/generated/settingsSchemaTypes.d.ts +28 -0
  45. package/dist/hitl/allowRuleShadow.js +2 -2
  46. package/dist/hitl/approvalCardReasons.js +2 -2
  47. package/dist/hitl/approvalHolds.d.ts +14 -0
  48. package/dist/hitl/approvalHolds.js +41 -0
  49. package/dist/hitl/askClassPair.d.ts +2 -2
  50. package/dist/hitl/askClassPair.js +2 -2
  51. package/dist/hitl/attributionLadder.d.ts +23 -0
  52. package/dist/hitl/attributionLadder.js +67 -0
  53. package/dist/hitl/decisionLedger.d.ts +55 -0
  54. package/dist/hitl/decisionLedger.js +156 -0
  55. package/dist/hitl/decisionRefusal.d.ts +1 -0
  56. package/dist/hitl/decisionRefusal.js +1 -1
  57. package/dist/hitl/decisionSend.d.ts +113 -0
  58. package/dist/hitl/decisionSend.js +579 -0
  59. package/dist/hitl/hitlBridge.js +18 -6
  60. package/dist/hitl/planReviewWire.js +45 -76
  61. package/dist/hitl/sessionPolicyDeliverable.js +3 -6
  62. package/dist/hitl/toolApprovalWire.js +43 -109
  63. package/dist/hostTurnSettings.d.ts +46 -7
  64. package/dist/hostTurnSettings.js +408 -7
  65. package/dist/humanTurn.d.ts +2 -1
  66. package/dist/humanTurn.js +3 -3
  67. package/dist/i18n/messages.en.d.ts +131 -0
  68. package/dist/i18n/messages.en.js +143 -12
  69. package/dist/i18n/messages.zh.js +148 -12
  70. package/dist/index.d.ts +18 -2
  71. package/dist/index.js +10 -1
  72. package/dist/inheritEnvCapability.d.ts +1 -0
  73. package/dist/inheritEnvCapability.js +9 -2
  74. package/dist/inheritEnvWire.js +2 -2
  75. package/dist/injectionLaneNotice.d.ts +5 -0
  76. package/dist/injectionLaneNotice.js +46 -0
  77. package/dist/mcpPanel.js +2 -0
  78. package/dist/mcpReconnect.js +2 -2
  79. package/dist/mcpToolToggles.d.ts +6 -0
  80. package/dist/mcpToolToggles.js +46 -0
  81. package/dist/mcpWireCaps.d.ts +10 -4
  82. package/dist/mcpWireCaps.js +106 -71
  83. package/dist/mcpWireTransport.d.ts +4 -0
  84. package/dist/mcpWireTransport.js +18 -0
  85. package/dist/modelCapabilityProbe.d.ts +2 -1
  86. package/dist/modelCapabilityProbe.js +10 -9
  87. package/dist/modelIdentityResolvability.d.ts +2 -0
  88. package/dist/modelIdentityResolvability.js +23 -5
  89. package/dist/ownKey.d.ts +26 -0
  90. package/dist/ownKey.js +75 -0
  91. package/dist/panelReduce.d.ts +5 -0
  92. package/dist/panelReduce.js +45 -13
  93. package/dist/panelStateHolder.d.ts +1 -0
  94. package/dist/panelStateHolder.js +10 -1
  95. package/dist/permissionRuleIssue.d.ts +2 -1
  96. package/dist/permissionRuleIssue.js +22 -21
  97. package/dist/pluginHooksWire.js +12 -10
  98. package/dist/prepareRefusal.d.ts +8 -0
  99. package/dist/prepareRefusal.js +72 -0
  100. package/dist/request/taskRequest.d.ts +10 -10
  101. package/dist/request/taskRequest.js +106 -65
  102. package/dist/resumeRefusalCopy.d.ts +3 -1
  103. package/dist/resumeRefusalCopy.js +22 -4
  104. package/dist/retryStatus.d.ts +18 -0
  105. package/dist/retryStatus.js +76 -19
  106. package/dist/ruleRefusalSentence.d.ts +4 -0
  107. package/dist/ruleRefusalSentence.js +58 -0
  108. package/dist/sandboxWire.d.ts +2 -1
  109. package/dist/sandboxWire.js +55 -21
  110. package/dist/scenarioRefusal.d.ts +2 -1
  111. package/dist/scenarioRefusal.js +5 -5
  112. package/dist/seam.d.ts +24 -9
  113. package/dist/seam.js +44 -4
  114. package/dist/seatContract.d.ts +16 -8
  115. package/dist/seatContract.js +21 -6
  116. package/dist/settingsCredentialKeyPaths.d.ts +8 -0
  117. package/dist/settingsCredentialKeyPaths.js +219 -0
  118. package/dist/skillsWireCaps.d.ts +0 -3
  119. package/dist/skillsWireCaps.js +1 -2
  120. package/dist/subagentContentStore.d.ts +1 -0
  121. package/dist/subagentContentStore.js +135 -27
  122. package/dist/toolsFlagNames.d.ts +8 -0
  123. package/dist/toolsFlagNames.js +110 -0
  124. package/dist/turnFailurePresentation.js +15 -7
  125. package/dist/wireRefusalCopy.d.ts +51 -17
  126. package/dist/wireRefusalCopy.js +298 -135
  127. package/docs/INTEGRATION-CLIENTS.md +423 -26
  128. package/package.json +4 -4
@@ -0,0 +1,579 @@
1
+ import { abortableSleep, settleWithinDeadline } from '../abortableSleep.js';
2
+ import { hostLog } from '../host.js';
3
+ import { askGoneFromError } from '../decideReceipt.js';
4
+ import { guardedRead, readOwnDataNonEmptyString, readOwnDataValue } from '../ownKey.js';
5
+ import { effectiveWireErrorCode, isWireTransportError } from '../wireErrorTriage.js';
6
+ import { ASK_DECISION_LANE_DISABLED, ASK_GONE, ASK_GONE_PARKED, ASK_IDEMPOTENCY_CONFLICT, ASK_NOT_FOUND, ASK_PARKING_RETRY } from '../engineErrorCodes.js';
7
+ import { readErrorNumber, readErrorString, readThrownSlot, readThrownStatusSlot, THROWN_SLOT_UNREADABLE } from '../wireFailureShape.js';
8
+ import { readDecisionNoteAudit } from './approvalDecisionNoteAudit.js';
9
+ import { deliverWithAttributionLadder } from './attributionLadder.js';
10
+ import { ASK_DECIDED_CODE, classifyDecisionRefusal, decisionRefusalClosesOut } from './decisionRefusal.js';
11
+ import { addDecisionOpKeys, closeDecisionOp, decisionLedgerKeysOf, decisionOpsWithKey, openDecisionOp } from './decisionLedger.js';
12
+ import { hostHoldIsActive, takeHostHoldLedgerOp, tryHoldApprovalNow } from './approvalHolds.js';
13
+ import { answerPayloadFor, decisionAttributionKeyOf, isToolApprovalRespondReplayed, readApprovalDenySettledBy, readToolApprovalRespondAck, readToolApprovalRespondParking, } from './toolApprovalWire.js';
14
+ export const HITL_DECISION_FAMILIES = Object.freeze(['approval', 'ask', 'elicitation']);
15
+ export const HITL_DECISION_NOT_APPLIED_CAUSES = Object.freeze(['gone', 'parked', 'parking', 'lane_disabled', 'refused', 'idempotency_crossed']);
16
+ export const HITL_DECISION_UNCONFIRMED_CAUSES = Object.freeze(['transport', 'server_error', 'local_error', 'aborted', 'gone_after_retry']);
17
+ export const HITL_DECISION_NOT_SENT_CAUSES = Object.freeze(['aborted', 'duplicate_in_flight', 'invalid_request', 'no_verb', 'not_an_answer']);
18
+ const RETRY_DELAYS_MS = Object.freeze([500, 1_000, 2_000]);
19
+ const RETRY_BUDGET_MS = 8_000;
20
+ const PARKING_POLL_MAX = 2;
21
+ const PARKING_POLL_DELAY_MS = 400;
22
+ const PARKING_POLL_WAIT_CAP_MS = 5_000;
23
+ export const HITL_DECISION_READBACK_BUDGET_MS = 4_000;
24
+ const IDEMPOTENCY_KEY_MAX = 255;
25
+ let timingOverride;
26
+ export function __setHitlDecisionSendTimingForTests(t) {
27
+ timingOverride = t;
28
+ }
29
+ export function hitlAskIdempotencyKey(askId, verdict) {
30
+ const key = `sema-ask:${askId}:${verdict}`;
31
+ return key.length <= IDEMPOTENCY_KEY_MAX ? key : key.slice(0, IDEMPOTENCY_KEY_MAX);
32
+ }
33
+ function isAbortShaped(e) {
34
+ const name = readThrownSlot(e, 'name');
35
+ return name === 'TimeoutError' || name === 'AbortError';
36
+ }
37
+ function transportClassOf(e, signal) {
38
+ if (signal?.aborted === true)
39
+ return 'aborted';
40
+ const status = typeof e === 'object' && e !== null ? readThrownStatusSlot(e) : undefined;
41
+ if (status === THROWN_SLOT_UNREADABLE)
42
+ return 'local_error';
43
+ if (typeof status === 'number')
44
+ return status === 501 ? 'verdict' : status >= 500 ? 'server_5xx' : 'verdict';
45
+ return isWireTransportError(e) || isAbortShaped(e) ? 'transport' : 'local_error';
46
+ }
47
+ function attemptSignal(attempt, caller, startedAt, budgetMs) {
48
+ if (attempt === 1)
49
+ return caller;
50
+ const deadline = AbortSignal.timeout(Math.max(1, budgetMs - (Date.now() - startedAt)));
51
+ if (caller === undefined)
52
+ return deadline;
53
+ if (typeof AbortSignal.any === 'function')
54
+ return AbortSignal.any([caller, deadline]);
55
+ const ac = new AbortController();
56
+ const abort = (s) => ac.abort(s.reason);
57
+ if (caller.aborted)
58
+ abort(caller);
59
+ else
60
+ caller.addEventListener('abort', () => abort(caller), { once: true });
61
+ deadline.addEventListener('abort', () => abort(deadline), { once: true });
62
+ return ac.signal;
63
+ }
64
+ function budgetSpent(tally, extraMs) {
65
+ return Date.now() - tally.startedAt + extraMs >= (timingOverride?.budgetMs ?? RETRY_BUDGET_MS);
66
+ }
67
+ function isAborted(signal) {
68
+ return signal?.aborted === true;
69
+ }
70
+ function sleepFor(ms, signal) {
71
+ return abortableSleep(ms, signal ?? new AbortController().signal);
72
+ }
73
+ async function withTransportRetry(send, signal, tally) {
74
+ const delays = timingOverride?.delays ?? RETRY_DELAYS_MS;
75
+ const budgetMs = timingOverride?.budgetMs ?? RETRY_BUDGET_MS;
76
+ const startedAt = tally.startedAt;
77
+ for (let attempt = 1;; attempt++) {
78
+ tally.attempts++;
79
+ try {
80
+ return await send(attemptSignal(attempt, signal, startedAt, budgetMs));
81
+ }
82
+ catch (e) {
83
+ const cls = transportClassOf(e, signal);
84
+ const delay = delays[attempt - 1] ?? 0;
85
+ const retriable = cls === 'transport' || cls === 'server_5xx';
86
+ const overBudget = Date.now() - startedAt + delay >= budgetMs;
87
+ if (!retriable || attempt > delays.length || overBudget)
88
+ throw e;
89
+ tally.retried = true;
90
+ await sleepFor(delay, signal);
91
+ if (signal?.aborted === true)
92
+ throw e;
93
+ }
94
+ }
95
+ }
96
+ function retryAfterMsOf(e) {
97
+ if (typeof e !== 'object' || e === null)
98
+ return undefined;
99
+ const ms = readErrorNumber(e, 'retryAfterMs');
100
+ if (ms !== undefined && Number.isFinite(ms) && ms >= 0)
101
+ return ms;
102
+ const sec = readErrorNumber(e, 'retryAfterSec');
103
+ return sec !== undefined && Number.isFinite(sec) && sec >= 0 ? sec * 1000 : undefined;
104
+ }
105
+ function actorLabelOf(e) {
106
+ const actor = readThrownSlot(e, 'actor', 'data');
107
+ if (typeof actor !== 'object' || actor === null)
108
+ return undefined;
109
+ const label = guardedRead(() => readOwnDataNonEmptyString(actor, 'label'));
110
+ if (label !== undefined)
111
+ return label;
112
+ const id = guardedRead(() => readOwnDataNonEmptyString(actor, 'id'));
113
+ return id !== undefined && id !== '_' ? id : undefined;
114
+ }
115
+ function verdictOf(word) {
116
+ return word === 'approve' || word === 'allow' || word === 'allow_session' ? 'approve' : word === 'deny' ? 'deny' : undefined;
117
+ }
118
+ function outcomeOfThrown(e, family, ours, signal, tally, noteCapable) {
119
+ const base = { family, attempts: tally.attempts };
120
+ const cls = transportClassOf(e, signal);
121
+ if (cls === 'aborted')
122
+ return { ...base, kind: 'unconfirmed', cause: 'aborted' };
123
+ if (cls === 'transport')
124
+ return { ...base, kind: 'unconfirmed', cause: 'transport' };
125
+ if (cls === 'local_error')
126
+ return { ...base, kind: 'unconfirmed', cause: 'local_error' };
127
+ const obj = e;
128
+ const status = readThrownStatusSlot(obj);
129
+ if (cls === 'server_5xx')
130
+ return { ...base, kind: 'unconfirmed', cause: 'server_error', status };
131
+ const code = effectiveWireErrorCode(e);
132
+ const at = { ...base, status, ...(code !== undefined ? { errorCode: code } : {}) };
133
+ if (family === 'ask') {
134
+ switch (code) {
135
+ case ASK_DECIDED_CODE: {
136
+ const first = verdictOf(readErrorString(obj, 'firstDecision'));
137
+ const decidedAtMs = readErrorNumber(obj, 'decidedAtMs');
138
+ const actorLabel = actorLabelOf(obj);
139
+ const note = readErrorString(obj, 'decisionNote');
140
+ return {
141
+ ...base,
142
+ kind: 'settled_elsewhere',
143
+ ...(first !== undefined ? { firstDecision: first } : {}),
144
+ ...(first !== undefined && ours !== undefined && first !== ours ? { contrary: true } : {}),
145
+ ...(decidedAtMs !== undefined ? { decidedAtMs } : {}),
146
+ ...(actorLabel !== undefined ? { actorLabel } : {}),
147
+ ...(noteCapable && note !== undefined ? { noteAudit: readDecisionNoteAudit({ decisionNote: note }) } : {}),
148
+ };
149
+ }
150
+ case ASK_IDEMPOTENCY_CONFLICT:
151
+ return { ...at, kind: 'not_applied', cause: 'idempotency_crossed', ...refusalOf(e) };
152
+ case ASK_GONE_PARKED: {
153
+ const sessionId = readErrorString(obj, 'sessionId');
154
+ return { ...at, kind: 'not_applied', cause: 'parked', ...(sessionId !== undefined ? { sessionId } : {}) };
155
+ }
156
+ case ASK_GONE: {
157
+ const gone = askGoneFromError(e);
158
+ return { ...at, kind: 'not_applied', cause: 'gone', ...(gone !== null ? { gone } : {}) };
159
+ }
160
+ case ASK_PARKING_RETRY: {
161
+ const retryAfterMs = retryAfterMsOf(e);
162
+ return { ...at, kind: 'not_applied', cause: 'parking', ...(retryAfterMs !== undefined ? { retryAfterMs } : {}) };
163
+ }
164
+ case ASK_DECISION_LANE_DISABLED:
165
+ return { ...at, kind: 'not_applied', cause: 'lane_disabled' };
166
+ case ASK_NOT_FOUND:
167
+ return { ...at, kind: 'not_applied', cause: 'gone' };
168
+ default:
169
+ break;
170
+ }
171
+ }
172
+ if (status === 404 || status === 410) {
173
+ return family !== 'ask' && tally.retried ? { ...base, kind: 'unconfirmed', cause: 'gone_after_retry', status } : { ...at, kind: 'not_applied', cause: 'gone' };
174
+ }
175
+ if (status === 501)
176
+ return { ...at, kind: 'not_applied', cause: 'lane_disabled' };
177
+ return { ...at, kind: 'not_applied', cause: 'refused', ...refusalOf(e) };
178
+ }
179
+ function refusalOf(e) {
180
+ const r = classifyDecisionRefusal(e);
181
+ return r !== undefined ? { refusal: r } : {};
182
+ }
183
+ async function sendApprovalFamily(respond, approvalId, answer, ctx) {
184
+ const payload = answerPayloadFor(answer, 'respond', { approvalDecisionNoteCapable: ctx.noteCapable });
185
+ if (payload === undefined)
186
+ return { family: 'approval', attempts: ctx.tally.attempts, kind: 'not_sent', cause: 'not_an_answer' };
187
+ const attribution = answer.kind === 'deny' ? decisionAttributionKeyOf(ctx.decidedBy, readApprovalDenySettledBy(answer)) : {};
188
+ const body = { ...payload, ...attribution };
189
+ const ladder = { lastCarried: false, dropped: false, renamed: false };
190
+ const ours = verdictOf(payload.decision);
191
+ const extras = bodyFacts(answer, payload, false);
192
+ let raw;
193
+ try {
194
+ raw = await deliverWithAttributionLadder(body, (b) => withTransportRetry((signal) => respond(approvalId, b, signal !== undefined ? { signal } : undefined), ctx.signal, ctx.tally), ctx.signal, ladder);
195
+ }
196
+ catch (e) {
197
+ return { ...outcomeOfThrown(e, 'approval', ours, ctx.signal, ctx.tally, ctx.noteCapable), ...extras };
198
+ }
199
+ const facts = { ...extras, ...(ladder.dropped ? { attributionDropped: true } : {}), ...(ladder.renamed ? { attributionRenamed: true } : {}) };
200
+ const base = { family: 'approval', attempts: ctx.tally.attempts, ...facts };
201
+ const parking = readToolApprovalRespondParking(raw);
202
+ if (parking !== undefined) {
203
+ const view = parking.kind === 'parking' && parking.view.approvalId === approvalId ? parking.view : undefined;
204
+ return { ...base, kind: 'not_applied', cause: 'parking', ...(view !== undefined ? { parking: view, ...(view.retryAfterMs !== undefined ? { retryAfterMs: view.retryAfterMs } : {}) } : {}) };
205
+ }
206
+ const parsed = readToolApprovalRespondAck(raw);
207
+ if (parsed !== undefined && isToolApprovalRespondReplayed(parsed)) {
208
+ const first = parsed.approvalId === approvalId ? verdictOf(parsed.decision) : undefined;
209
+ return {
210
+ ...base,
211
+ kind: 'settled_elsewhere',
212
+ ...(first !== undefined ? { firstDecision: first } : {}),
213
+ ...(first !== undefined && ours !== undefined && first !== ours ? { contrary: true } : {}),
214
+ };
215
+ }
216
+ if (ownTrue(raw, 'idempotent'))
217
+ return { ...base, kind: 'settled_elsewhere' };
218
+ const ack = parsed !== undefined && parsed.approvalId === approvalId ? parsed : undefined;
219
+ return { ...base, kind: 'applied', ...(ack !== undefined ? { ack } : {}), ...(ctx.noteCapable ? { noteAudit: readDecisionNoteAudit(raw) } : {}) };
220
+ }
221
+ function bodyFacts(answer, payload, rememberUnsupported) {
222
+ const reason = answer.kind === 'deny' && typeof answer.reason === 'string' ? answer.reason : undefined;
223
+ return {
224
+ ...(reason !== undefined && reason.trim() !== '' && payload.note === undefined ? { noteDropped: true } : {}),
225
+ ...(rememberUnsupported && answer.kind === 'allow' && answer.allowSession === true ? { rememberDropped: true } : {}),
226
+ };
227
+ }
228
+ function ownTrue(raw, key) {
229
+ return typeof raw === 'object' && raw !== null && guardedRead(() => readOwnDataValue(raw, key)) === true;
230
+ }
231
+ async function sendAskFamily(respondForFallback, decideAsk, req, ctx) {
232
+ const payload = answerPayloadFor(req.answer, 'respond', { approvalDecisionNoteCapable: ctx.noteCapable });
233
+ if (payload === undefined)
234
+ return { family: 'ask', attempts: ctx.tally.attempts, kind: 'not_sent', cause: 'not_an_answer' };
235
+ const verdict = verdictOf(payload.decision) ?? 'deny';
236
+ const attribution = req.answer.kind === 'deny' ? decisionAttributionKeyOf(ctx.decidedBy, readApprovalDenySettledBy(req.answer)) : {};
237
+ const body = {
238
+ decision: verdict,
239
+ idempotencyKey: hitlAskIdempotencyKey(req.askId, verdict),
240
+ ...(verdict === 'approve' && payload.updatedInput !== undefined ? { updatedInput: payload.updatedInput } : {}),
241
+ ...(payload.note !== undefined ? { note: payload.note } : {}),
242
+ ...attribution,
243
+ };
244
+ const extras = bodyFacts(req.answer, payload, true);
245
+ const ladder = { lastCarried: false, dropped: false, renamed: false };
246
+ let current = body;
247
+ const parkingDelay = timingOverride?.parkingDelayMs ?? PARKING_POLL_DELAY_MS;
248
+ for (let poll = 0;; poll++) {
249
+ try {
250
+ const raw = await deliverWithAttributionLadder(current, (b) => {
251
+ current = b;
252
+ return withTransportRetry((signal) => decideAsk(req.taskId, req.askId, b, signal !== undefined ? { signal } : undefined), ctx.signal, ctx.tally);
253
+ }, ctx.signal, ladder);
254
+ return {
255
+ family: 'ask',
256
+ attempts: ctx.tally.attempts,
257
+ ...extras,
258
+ ...(ladder.dropped ? { attributionDropped: true } : {}),
259
+ ...(ladder.renamed ? { attributionRenamed: true } : {}),
260
+ kind: 'applied',
261
+ ...(ctx.noteCapable ? { noteAudit: readDecisionNoteAudit(raw) } : {}),
262
+ };
263
+ }
264
+ catch (e) {
265
+ const code = effectiveWireErrorCode(e);
266
+ const wait = Math.max(parkingDelay * (poll + 1), retryAfterMsOf(e) ?? 0);
267
+ if (code === ASK_PARKING_RETRY && poll < PARKING_POLL_MAX && !isAborted(ctx.signal) && wait <= PARKING_POLL_WAIT_CAP_MS && !budgetSpent(ctx.tally, wait)) {
268
+ await sleepFor(wait, ctx.signal);
269
+ if (!isAborted(ctx.signal))
270
+ continue;
271
+ }
272
+ const out = outcomeOfThrown(e, 'ask', verdict, ctx.signal, ctx.tally, ctx.noteCapable);
273
+ if (out.kind === 'not_applied' && out.cause === 'lane_disabled' && code === ASK_DECISION_LANE_DISABLED && req.approvalId !== undefined) {
274
+ if (respondForFallback !== undefined && ctx.signal?.aborted !== true) {
275
+ const fb = await sendApprovalFamily(respondForFallback, req.approvalId, req.answer, ctx);
276
+ return { ...fb, family: 'ask', fallback: 'approval' };
277
+ }
278
+ }
279
+ return { ...out, ...extras };
280
+ }
281
+ }
282
+ }
283
+ function validElicitResponse(r) {
284
+ if (typeof r !== 'object' || r === null)
285
+ return undefined;
286
+ const action = guardedRead(() => readOwnDataValue(r, 'action'));
287
+ if (action !== 'accept' && action !== 'decline' && action !== 'cancel')
288
+ return undefined;
289
+ const content = guardedRead(() => readOwnDataValue(r, 'content'));
290
+ if (content === undefined)
291
+ return Object.freeze({ action });
292
+ if (action !== 'accept' || typeof content !== 'object' || content === null || Array.isArray(content))
293
+ return undefined;
294
+ const out = {};
295
+ try {
296
+ for (const k of Object.keys(content)) {
297
+ const v = readOwnDataValue(content, k);
298
+ if (typeof v === 'string' || typeof v === 'boolean' || (typeof v === 'number' && Number.isFinite(v)))
299
+ Object.defineProperty(out, k, { value: v, enumerable: true, writable: true, configurable: true });
300
+ else if (Array.isArray(v)) {
301
+ const copy = [];
302
+ for (let i = 0; i < v.length; i++) {
303
+ const x = readOwnDataValue(v, String(i));
304
+ if (typeof x !== 'string')
305
+ return undefined;
306
+ copy.push(x);
307
+ }
308
+ Object.defineProperty(out, k, { value: copy, enumerable: true, writable: true, configurable: true });
309
+ }
310
+ else
311
+ return undefined;
312
+ }
313
+ }
314
+ catch {
315
+ return undefined;
316
+ }
317
+ return Object.freeze({ action, content: out });
318
+ }
319
+ function verbOf(holder, name) {
320
+ if (typeof holder !== 'object' || holder === null)
321
+ return undefined;
322
+ try {
323
+ const fn = holder[name];
324
+ if (typeof fn !== 'function')
325
+ return undefined;
326
+ const bound = (...args) => {
327
+ try {
328
+ return Promise.resolve(fn.apply(holder, args));
329
+ }
330
+ catch (e) {
331
+ return Promise.reject(e);
332
+ }
333
+ };
334
+ return bound;
335
+ }
336
+ catch {
337
+ return undefined;
338
+ }
339
+ }
340
+ function signalOf(v) {
341
+ try {
342
+ return v instanceof AbortSignal ? v : undefined;
343
+ }
344
+ catch {
345
+ return undefined;
346
+ }
347
+ }
348
+ function holdOf(v) {
349
+ if (typeof v !== 'object' || v === null)
350
+ return undefined;
351
+ try {
352
+ const h = v;
353
+ return typeof h.sending === 'function' && typeof h.release === 'function' ? h : undefined;
354
+ }
355
+ catch {
356
+ return undefined;
357
+ }
358
+ }
359
+ function holdFits(handle, req) {
360
+ try {
361
+ const id = handle.identity;
362
+ if (typeof id.approvalId === 'string' && id.approvalId !== '')
363
+ return id.approvalId === req.approvalId;
364
+ if (typeof id.toolCallId !== 'string' || id.toolCallId === '' || id.toolCallId !== req.toolCallId)
365
+ return false;
366
+ return typeof id.taskId !== 'string' || id.taskId === '' || id.taskId === req.taskId;
367
+ }
368
+ catch {
369
+ return false;
370
+ }
371
+ }
372
+ function callHold(call) {
373
+ try {
374
+ call();
375
+ }
376
+ catch (e) {
377
+ hostLog('debug', `decisionSend.callHold: ${String(e).slice(0, 120)}`);
378
+ }
379
+ }
380
+ function hostField(holder, key) {
381
+ if (typeof holder !== 'object' || holder === null)
382
+ return undefined;
383
+ try {
384
+ return holder[key];
385
+ }
386
+ catch {
387
+ return undefined;
388
+ }
389
+ }
390
+ function snapshotAnswer(raw) {
391
+ const kind = guardedRead(() => readOwnDataValue(raw, 'kind'));
392
+ if (kind === 'allow') {
393
+ const allowSession = guardedRead(() => readOwnDataValue(raw, 'allowSession')) === true;
394
+ const edited = guardedRead(() => readOwnDataValue(raw, 'updatedInput'));
395
+ if (edited === undefined)
396
+ return Object.freeze({ kind: 'allow', allowSession });
397
+ let copy;
398
+ try {
399
+ const text = JSON.stringify(edited);
400
+ if (typeof text !== 'string')
401
+ return undefined;
402
+ copy = JSON.parse(text);
403
+ }
404
+ catch {
405
+ return undefined;
406
+ }
407
+ return Object.freeze({ kind: 'allow', allowSession, updatedInput: copy });
408
+ }
409
+ if (kind === 'deny') {
410
+ const reason = guardedRead(() => readOwnDataValue(raw, 'reason'));
411
+ const settledBy = readApprovalDenySettledBy(raw);
412
+ return Object.freeze({ kind: 'deny', ...(typeof reason === 'string' ? { reason } : {}), ...(settledBy !== undefined ? { settledBy } : {}) });
413
+ }
414
+ return typeof kind === 'string' ? Object.freeze({ kind }) : undefined;
415
+ }
416
+ function readRequest(req) {
417
+ if (typeof req !== 'object' || req === null)
418
+ return undefined;
419
+ const s = (k) => guardedRead(() => readOwnDataNonEmptyString(req, k));
420
+ const family = s('family');
421
+ if (family === 'elicitation') {
422
+ const elicitationId = s('elicitationId');
423
+ const response = validElicitResponse(guardedRead(() => readOwnDataValue(req, 'response')));
424
+ return elicitationId !== undefined && response !== undefined ? { family, elicitationId, taskId: s('taskId'), response } : undefined;
425
+ }
426
+ const rawAnswer = guardedRead(() => readOwnDataValue(req, 'answer'));
427
+ if (typeof rawAnswer !== 'object' || rawAnswer === null)
428
+ return undefined;
429
+ const answer = snapshotAnswer(rawAnswer);
430
+ if (answer === undefined)
431
+ return undefined;
432
+ if (family === 'approval') {
433
+ const approvalId = s('approvalId');
434
+ return approvalId !== undefined ? { family, approvalId, taskId: s('taskId'), toolCallId: s('toolCallId'), answer } : undefined;
435
+ }
436
+ if (family === 'ask') {
437
+ const taskId = s('taskId');
438
+ const askId = s('askId');
439
+ return taskId !== undefined && askId !== undefined ? { family, taskId, askId, approvalId: s('approvalId'), toolCallId: s('toolCallId'), answer } : undefined;
440
+ }
441
+ return undefined;
442
+ }
443
+ function latchKeysOf(r) {
444
+ if (r.family === 'elicitation')
445
+ return decisionLedgerKeysOf({ elicitationId: r.elicitationId });
446
+ if (r.family === 'ask')
447
+ return decisionLedgerKeysOf({ askId: r.askId, approvalId: r.approvalId });
448
+ return decisionLedgerKeysOf({ approvalId: r.approvalId });
449
+ }
450
+ function ledgerFamilyOf(family) {
451
+ return family === 'elicitation' ? 'elicitation' : 'tool_approval';
452
+ }
453
+ async function readBack(deps, gate) {
454
+ const port = hostField(deps, 'readPending');
455
+ if (typeof port !== 'function')
456
+ return undefined;
457
+ const settled = await settleWithinDeadline(HITL_DECISION_READBACK_BUDGET_MS, (signal) => Promise.resolve(port.call(deps, gate, { signal })));
458
+ if (settled.kind !== 'value')
459
+ return 'unknown';
460
+ return settled.value === 'pending' || settled.value === 'not_pending' ? settled.value : 'unknown';
461
+ }
462
+ export async function sendHitlDecision(request, deps) {
463
+ const familyRead = typeof request === 'object' && request !== null ? guardedRead(() => readOwnDataNonEmptyString(request, 'family')) : undefined;
464
+ const family = familyRead === 'ask' || familyRead === 'elicitation' ? familyRead : 'approval';
465
+ const notSent = (cause) => Object.freeze({ family, attempts: 0, kind: 'not_sent', cause });
466
+ let req;
467
+ try {
468
+ req = readRequest(request);
469
+ }
470
+ catch {
471
+ req = undefined;
472
+ }
473
+ if (req === undefined)
474
+ return notSent('invalid_request');
475
+ const signal = signalOf(hostField(deps, 'signal'));
476
+ const client = hostField(deps, 'client');
477
+ const toolApprovals = hostField(client, 'toolApprovals');
478
+ const respond = verbOf(toolApprovals, 'respond');
479
+ const decideAsk = verbOf(toolApprovals, 'decideAsk');
480
+ const elicit = verbOf(hostField(client, 'elicitations'), 'respond');
481
+ if ((req.family === 'approval' && respond === undefined) || (req.family === 'ask' && decideAsk === undefined) || (req.family === 'elicitation' && elicit === undefined))
482
+ return notSent('no_verb');
483
+ if (req.family !== 'elicitation' && req.answer.kind !== 'allow' && req.answer.kind !== 'deny')
484
+ return notSent('not_an_answer');
485
+ if (isAborted(signal))
486
+ return notSent('aborted');
487
+ let ownHold;
488
+ let hold;
489
+ if (req.family !== 'elicitation' && req.approvalId !== undefined) {
490
+ const given = holdOf(hostField(deps, 'hold'));
491
+ if (given !== undefined && hostHoldIsActive(given) && holdFits(given, req)) {
492
+ hold = given;
493
+ }
494
+ else {
495
+ const attempt = tryHoldApprovalNow({ approvalId: req.approvalId, ...(req.taskId !== undefined ? { taskId: req.taskId } : {}), ...(req.toolCallId !== undefined ? { toolCallId: req.toolCallId } : {}) }, signal);
496
+ if (attempt.kind === 'settled')
497
+ return Object.freeze({ family, attempts: 0, kind: 'settled_elsewhere' });
498
+ if (attempt.kind === 'busy')
499
+ return notSent('duplicate_in_flight');
500
+ ownHold = attempt.hold;
501
+ hold = ownHold;
502
+ }
503
+ callHold(() => hold?.sending());
504
+ }
505
+ const ledgerFamily = ledgerFamilyOf(req.family);
506
+ if (latchKeysOf(req).some((k) => decisionOpsWithKey(ledgerFamily, k).some((o) => o.phase === 'posting' && o.settledAt === null))) {
507
+ ownHold?.release();
508
+ return notSent('duplicate_in_flight');
509
+ }
510
+ const requestKeys = req.family === 'elicitation'
511
+ ? decisionLedgerKeysOf({ taskId: req.taskId, elicitationId: req.elicitationId })
512
+ : decisionLedgerKeysOf({ taskId: req.taskId, approvalId: req.approvalId, callId: req.toolCallId, askId: req.family === 'ask' ? req.askId : undefined });
513
+ const taken = hold !== undefined ? takeHostHoldLedgerOp(hold) : undefined;
514
+ let op;
515
+ if (taken !== undefined && taken.settledAt === null) {
516
+ op = taken;
517
+ op.phase = 'posting';
518
+ op.ownCallOpen = true;
519
+ addDecisionOpKeys(op, requestKeys);
520
+ }
521
+ else {
522
+ op = openDecisionOp(ledgerFamily, requestKeys, 'posting', true);
523
+ }
524
+ const decidedBy = hostField(deps, 'approvalDecidedBy');
525
+ const ctx = {
526
+ signal,
527
+ tally: { attempts: 0, retried: false, startedAt: Date.now() },
528
+ noteCapable: hostField(deps, 'approvalDecisionNoteCapable') === true,
529
+ decidedBy: typeof decidedBy === 'object' && decidedBy !== null ? decidedBy : undefined,
530
+ };
531
+ let out;
532
+ try {
533
+ if (req.family === 'approval') {
534
+ out = await sendApprovalFamily(respond, req.approvalId, req.answer, ctx);
535
+ }
536
+ else if (req.family === 'ask') {
537
+ out = await sendAskFamily(respond, decideAsk, { taskId: req.taskId, askId: req.askId, approvalId: req.approvalId, answer: req.answer }, ctx);
538
+ }
539
+ else {
540
+ const { elicitationId, response } = req;
541
+ try {
542
+ await withTransportRetry((sig) => elicit(elicitationId, response, sig !== undefined ? { signal: sig } : undefined), signal, ctx.tally);
543
+ out = { family: 'elicitation', attempts: ctx.tally.attempts, kind: 'applied' };
544
+ }
545
+ catch (e) {
546
+ out = outcomeOfThrown(e, 'elicitation', undefined, signal, ctx.tally, false);
547
+ }
548
+ }
549
+ const ambiguous = out.kind === 'unconfirmed' || (out.kind === 'not_applied' && (out.cause === 'parking' || out.cause === 'refused' || out.cause === 'idempotency_crossed'));
550
+ if (ambiguous && out.cause !== 'aborted') {
551
+ const gate = req.family === 'elicitation'
552
+ ? { family: req.family, elicitationId: req.elicitationId, ...(req.taskId !== undefined ? { taskId: req.taskId } : {}) }
553
+ : {
554
+ family: req.family,
555
+ ...(req.approvalId !== undefined ? { approvalId: req.approvalId } : {}),
556
+ ...(req.family === 'ask' ? { askId: req.askId } : {}),
557
+ ...(req.taskId !== undefined ? { taskId: req.taskId } : {}),
558
+ };
559
+ const pending = await readBack(deps, gate);
560
+ if (pending !== undefined) {
561
+ out = { ...out, pending };
562
+ if ((out.cause === 'refused' || out.cause === 'idempotency_crossed') && decisionRefusalClosesOut(out.refusal, pending))
563
+ out = { ...out, closesOut: true };
564
+ }
565
+ }
566
+ }
567
+ catch {
568
+ out = { family: req.family, attempts: ctx.tally.attempts, kind: 'unconfirmed', cause: 'local_error' };
569
+ }
570
+ const notApplied = out.kind !== 'applied' &&
571
+ (out.pending === 'pending' ||
572
+ (out.kind === 'not_applied' && (out.cause === 'lane_disabled' || out.cause === 'idempotency_crossed' || (out.cause === 'refused' && out.refusal?.standing === 'refused-pending'))));
573
+ if (notApplied)
574
+ op.notApplied = true;
575
+ closeDecisionOp(op, 'package');
576
+ if (ownHold !== undefined)
577
+ ownHold.release(out.kind === 'applied' || out.kind === 'settled_elsewhere' ? 'answered' : undefined);
578
+ return Object.freeze(out);
579
+ }
@@ -7,6 +7,7 @@ import { ownDataField, readOwnDataValue } from '../ownKey.js';
7
7
  import { PROTOCOL_ERROR_NAME, THROWN_SLOT_UNREADABLE, protocolErrorNameReading, readBlockedBy, readThrownSlot, thrownIsInstanceOf, wireFailureTextsOf } from '../wireFailureShape.js';
8
8
  import { APPROVAL_BINDING_MISMATCH, CHECKPOINT_INVALID_OUTCOME, RESUME_POLICY_FOLD_CODE } from '../engineErrorCodes.js';
9
9
  import { suspendedReopenOf } from './suspendedReopen.js';
10
+ import { closeDecisionOp, decisionLedgerKeysOf, openDecisionOp } from './decisionLedger.js';
10
11
  export const DEFAULT_DENY_REASON = 'The user rejected this tool use';
11
12
  export const MAX_DENY_REASON_CHARS = 4096;
12
13
  export function denyReasonForWire(reason, tag) {
@@ -172,6 +173,7 @@ class DecideSpan {
172
173
  owner;
173
174
  observer;
174
175
  identity;
176
+ ledgerOp;
175
177
  attempts = 0;
176
178
  ok = false;
177
179
  retryExhausted = false;
@@ -189,9 +191,11 @@ class DecideSpan {
189
191
  return mine === undefined || (mine.sessionId === identity.sessionId && mine.boundCallId === identity.boundCallId);
190
192
  }
191
193
  async run(identity, send) {
192
- if (this.observer !== undefined && this.identity === undefined) {
194
+ if (this.identity === undefined) {
193
195
  this.identity = identity;
194
- notifyDecideObserver(this.observer, Object.freeze({ ...this.identity, phase: 'start' }));
196
+ this.ledgerOp = openDecideLedgerOp(identity);
197
+ if (this.observer !== undefined)
198
+ notifyDecideObserver(this.observer, Object.freeze({ ...this.identity, phase: 'start' }));
195
199
  }
196
200
  this.inFlight++;
197
201
  const tally = { attempts: 0 };
@@ -224,11 +228,16 @@ class DecideSpan {
224
228
  if (this.ended)
225
229
  return;
226
230
  this.ended = true;
231
+ if (this.ledgerOp !== undefined)
232
+ closeDecisionOp(this.ledgerOp, 'package');
227
233
  if (this.observer === undefined || this.identity === undefined)
228
234
  return;
229
235
  notifyDecideObserver(this.observer, Object.freeze({ ...this.identity, phase: 'end', ok: this.ok, attempts: this.attempts, retryExhausted: this.retryExhausted }));
230
236
  }
231
237
  }
238
+ function openDecideLedgerOp(identity) {
239
+ return openDecisionOp('tool_approval', decisionLedgerKeysOf({ taskId: identity.taskId, callId: identity.boundCallId }), 'posting', true);
240
+ }
232
241
  function wireOptsOf(opts) {
233
242
  if (opts === undefined || opts.operation === undefined)
234
243
  return opts;
@@ -408,10 +417,10 @@ export class HitlBridge {
408
417
  }
409
418
  const observer = this.decideObserver;
410
419
  const tally = { attempts: 0 };
411
- if (observer === undefined)
412
- return this.decideRawAttempts(sessionId, decision, opts, tally);
413
420
  const identity = this.decideIdentityOf(sessionId, decision);
414
- notifyDecideObserver(observer, Object.freeze({ ...identity, phase: 'start' }));
421
+ const ledgerOp = openDecideLedgerOp(identity);
422
+ if (observer !== undefined)
423
+ notifyDecideObserver(observer, Object.freeze({ ...identity, phase: 'start' }));
415
424
  let ok = false;
416
425
  let retryExhausted = false;
417
426
  try {
@@ -424,7 +433,10 @@ export class HitlBridge {
424
433
  throw e;
425
434
  }
426
435
  finally {
427
- notifyDecideObserver(observer, Object.freeze({ ...identity, phase: 'end', ok, attempts: tally.attempts, retryExhausted }));
436
+ closeDecisionOp(ledgerOp, 'package');
437
+ if (observer !== undefined) {
438
+ notifyDecideObserver(observer, Object.freeze({ ...identity, phase: 'end', ok, attempts: tally.attempts, retryExhausted }));
439
+ }
428
440
  }
429
441
  }
430
442
  decideIdentityOf(sessionId, decision) {