@sema-agent/client-core 0.104.1 → 0.106.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 (175) hide show
  1. package/CHANGELOG.md +310 -0
  2. package/README.md +36 -14
  3. package/dist/adapt/arms.js +23 -6
  4. package/dist/adapter/activeRunSelfHeal.d.ts +14 -2
  5. package/dist/adapter/activeRunSelfHeal.js +90 -12
  6. package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -5
  7. package/dist/adapter/downstream/eventToSdkMessage.js +24 -23
  8. package/dist/adapter/downstream/terminalToSdkResult.js +2 -3
  9. package/dist/adapter/downstream/wiringManifestView.d.ts +7 -3
  10. package/dist/adapter/downstream/wiringManifestView.js +25 -1
  11. package/dist/adapter/runStream.js +8 -5
  12. package/dist/approvalReachWire.d.ts +1 -0
  13. package/dist/approvalReachWire.js +4 -1
  14. package/dist/approvalSeat.d.ts +21 -0
  15. package/dist/approvalSeat.js +73 -0
  16. package/dist/assistantInbox.d.ts +2 -0
  17. package/dist/assistantInbox.js +3 -0
  18. package/dist/checkpointRowVerdict.d.ts +23 -0
  19. package/dist/checkpointRowVerdict.js +89 -0
  20. package/dist/cloudConfigWireCaps.js +3 -0
  21. package/dist/compensations.js +6 -6
  22. package/dist/coreValuePorts.d.ts +14 -1
  23. package/dist/coreValuePorts.js +14 -0
  24. package/dist/decideFailureNote.js +3 -1
  25. package/dist/decideReceipt.d.ts +9 -0
  26. package/dist/decideReceipt.js +29 -1
  27. package/dist/declared/supportFloorDeclared.js +2 -2
  28. package/dist/deviceRebind.d.ts +7 -0
  29. package/dist/deviceRebind.js +39 -5
  30. package/dist/displayUntrusted.d.ts +4 -2
  31. package/dist/displayUntrusted.js +45 -22
  32. package/dist/engineAddress.d.ts +3 -0
  33. package/dist/engineAddress.js +25 -0
  34. package/dist/engineDirectory.js +9 -17
  35. package/dist/engineErrorCodes.d.ts +8 -0
  36. package/dist/engineErrorCodes.js +8 -0
  37. package/dist/engineGenerationPort.d.ts +11 -2
  38. package/dist/engineGenerationPort.js +63 -9
  39. package/dist/engineHealthFacts.d.ts +5 -0
  40. package/dist/engineHealthFacts.js +11 -0
  41. package/dist/engineNoticeCodes.d.ts +26 -3
  42. package/dist/engineNoticeCodes.js +52 -7
  43. package/dist/engineRowRef.js +1 -1
  44. package/dist/errorSupplementRoles.js +2 -0
  45. package/dist/eventLeg.d.ts +3 -3
  46. package/dist/eventLeg.js +25 -4
  47. package/dist/fleet/fleetProjection.js +26 -5
  48. package/dist/generated/engineFactTables.d.ts +3 -1
  49. package/dist/generated/engineFactTables.js +4 -1
  50. package/dist/generated/engineNoticeTables.js +19 -4
  51. package/dist/generated/sdkFactTables.d.ts +5 -0
  52. package/dist/generated/sdkFactTables.js +24 -0
  53. package/dist/generated/settingsSchemaFns.d.ts +11 -0
  54. package/dist/generated/settingsSchemaFns.js +2 -1
  55. package/dist/generated/settingsSchemaSafetyMergeSpec.js +1 -1
  56. package/dist/generated/settingsSchemaSettingsSources.d.ts +1 -1
  57. package/dist/generated/settingsSchemaSettingsSources.js +22 -2
  58. package/dist/headlessReconnectWire.js +31 -4
  59. package/dist/hitl/approvalLiveFace.d.ts +17 -0
  60. package/dist/hitl/approvalLiveFace.js +35 -0
  61. package/dist/hitl/approvalQueueRow.d.ts +21 -0
  62. package/dist/hitl/approvalQueueRow.js +69 -0
  63. package/dist/hitl/approvalsFeed.d.ts +5 -4
  64. package/dist/hitl/approvalsFeed.js +38 -6
  65. package/dist/hitl/askGateWire.js +26 -2
  66. package/dist/hitl/askParkRowRouting.d.ts +4 -3
  67. package/dist/hitl/askParkRowRouting.js +15 -3
  68. package/dist/hitl/automaticAnswerVerdict.js +13 -10
  69. package/dist/hitl/crashConverged.d.ts +5 -1
  70. package/dist/hitl/decisionConflict.js +2 -1
  71. package/dist/hitl/decisionRefusal.js +1 -1
  72. package/dist/hitl/decisionSend.d.ts +1 -0
  73. package/dist/hitl/decisionSend.js +14 -2
  74. package/dist/hitl/frameRouter.d.ts +4 -0
  75. package/dist/hitl/frameRouter.js +18 -5
  76. package/dist/hitl/gateIdentity.d.ts +1 -0
  77. package/dist/hitl/gateIdentity.js +3 -0
  78. package/dist/hitl/hitlBridge.d.ts +1 -1
  79. package/dist/hitl/hitlBridge.js +26 -5
  80. package/dist/hitl/livePendingAsk.d.ts +5 -0
  81. package/dist/hitl/livePendingAsk.js +6 -1
  82. package/dist/hitl/planReviewCard.d.ts +23 -0
  83. package/dist/hitl/planReviewCard.js +250 -0
  84. package/dist/hitl/planReviewWire.d.ts +39 -14
  85. package/dist/hitl/planReviewWire.js +450 -163
  86. package/dist/hitl/sessionAllowRoute.d.ts +2 -0
  87. package/dist/hitl/sessionAllowRoute.js +7 -0
  88. package/dist/hitl/sessionPolicyWire.d.ts +1 -7
  89. package/dist/hitl/sessionPolicyWire.js +3 -43
  90. package/dist/hitl/subagentSource.d.ts +1 -0
  91. package/dist/hitl/subagentSource.js +13 -0
  92. package/dist/hitl/toolApprovalWire.d.ts +3 -2
  93. package/dist/hitl/toolApprovalWire.js +79 -37
  94. package/dist/hostSettingsSource.d.ts +5 -2
  95. package/dist/hostSettingsSource.js +19 -10
  96. package/dist/hostTurnSettings.d.ts +5 -6
  97. package/dist/hostTurnSettings.js +106 -26
  98. package/dist/i18n/messages.en.d.ts +85 -12
  99. package/dist/i18n/messages.en.js +85 -12
  100. package/dist/i18n/messages.zh.js +88 -12
  101. package/dist/index.d.ts +35 -4
  102. package/dist/index.js +19 -2
  103. package/dist/liveInitToolFace.js +0 -1
  104. package/dist/liveModelCatalog.d.ts +15 -0
  105. package/dist/liveModelCatalog.js +40 -0
  106. package/dist/mcpTransportKindsCapability.d.ts +18 -0
  107. package/dist/mcpTransportKindsCapability.js +75 -0
  108. package/dist/mcpWireCaps.d.ts +4 -1
  109. package/dist/mcpWireCaps.js +25 -14
  110. package/dist/modelCapabilityProbe.js +66 -33
  111. package/dist/notifications.d.ts +9 -0
  112. package/dist/notifications.js +11 -2
  113. package/dist/ownKey.d.ts +4 -1
  114. package/dist/ownKey.js +8 -2
  115. package/dist/permissionModeWriteCapability.d.ts +13 -0
  116. package/dist/permissionModeWriteCapability.js +35 -0
  117. package/dist/permissionsAllowGrantCapability.d.ts +3 -0
  118. package/dist/permissionsAllowGrantCapability.js +26 -3
  119. package/dist/permissionsDeclaration.d.ts +25 -3
  120. package/dist/permissionsDeclaration.js +69 -41
  121. package/dist/planReviewCardFacts.d.ts +116 -0
  122. package/dist/planReviewCardFacts.js +469 -0
  123. package/dist/projectSeed.d.ts +39 -0
  124. package/dist/projectSeed.js +80 -0
  125. package/dist/promptDelivery.js +1 -1
  126. package/dist/reattachWindow.d.ts +8 -1
  127. package/dist/reattachWindow.js +44 -12
  128. package/dist/replayBody.d.ts +7 -6
  129. package/dist/replayBody.js +62 -7
  130. package/dist/request/taskRequest.js +3 -4
  131. package/dist/ruleCompileVerdict.d.ts +0 -2
  132. package/dist/ruleCompileVerdict.js +0 -31
  133. package/dist/ruleRefusalSentence.js +0 -3
  134. package/dist/runFollow.d.ts +30 -0
  135. package/dist/runFollow.js +117 -0
  136. package/dist/runLedgerSnapshot.d.ts +20 -0
  137. package/dist/runLedgerSnapshot.js +272 -0
  138. package/dist/runPermissionModeSwitch.d.ts +57 -0
  139. package/dist/runPermissionModeSwitch.js +135 -0
  140. package/dist/runReattachPolicy.d.ts +16 -5
  141. package/dist/runReattachPolicy.js +72 -24
  142. package/dist/runTerminal.js +30 -8
  143. package/dist/sandboxWire.d.ts +0 -3
  144. package/dist/sandboxWire.js +0 -9
  145. package/dist/seam.d.ts +17 -7
  146. package/dist/seam.js +13 -1
  147. package/dist/seatContract.d.ts +57 -7
  148. package/dist/seatContract.js +133 -3
  149. package/dist/serverSupportRange.d.ts +18 -0
  150. package/dist/serverSupportRange.js +25 -0
  151. package/dist/sessionEngine.d.ts +156 -0
  152. package/dist/sessionEngine.js +464 -0
  153. package/dist/sessionMap.d.ts +11 -1
  154. package/dist/sessionMap.js +35 -9
  155. package/dist/sessionMemoryErase.d.ts +1 -1
  156. package/dist/sessionMemoryErase.js +5 -0
  157. package/dist/sessionMemoryStatus.d.ts +27 -1
  158. package/dist/sessionMemoryStatus.js +117 -0
  159. package/dist/sessionMemorySync.d.ts +31 -0
  160. package/dist/sessionMemorySync.js +95 -0
  161. package/dist/sessionRowTag.d.ts +4 -0
  162. package/dist/sessionRowTag.js +18 -0
  163. package/dist/settingsLayerAdmission.d.ts +1 -0
  164. package/dist/settingsLayerAdmission.js +4 -1
  165. package/dist/sseIdleTriage.d.ts +3 -1
  166. package/dist/sseIdleTriage.js +19 -4
  167. package/dist/subagent/engineSubagentTail.js +2 -0
  168. package/dist/subagentContentStore.d.ts +3 -0
  169. package/dist/subagentContentStore.js +10 -1
  170. package/dist/toolResult.js +1 -1
  171. package/dist/turnFailurePresentation.d.ts +5 -2
  172. package/dist/turnFailurePresentation.js +12 -4
  173. package/dist/wireRefusalCopy.js +2 -6
  174. package/docs/INTEGRATION-CLIENTS.md +1507 -18
  175. package/package.json +5 -5
@@ -0,0 +1,272 @@
1
+ import { createStreamDrainingLegs } from './streamDrainingLegs.js';
2
+ import { abortableSleep, settleWithinDeadline } from './abortableSleep.js';
3
+ import { readOwnDataValue } from './ownKey.js';
4
+ const RUN_LEDGER_SNAPSHOT_BUDGET_MS = 8_000;
5
+ const UNNUMBERED_FRAME_CAP = 64;
6
+ const PARK_TRAILER_MAX = 4;
7
+ function frameSeq(ev) {
8
+ try {
9
+ const id = ev?.id;
10
+ if (typeof id !== 'string' || id.length === 0)
11
+ return undefined;
12
+ const n = Number(id);
13
+ return Number.isSafeInteger(n) && n >= 0 ? n : undefined;
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ function isParkMarker(ev) {
20
+ try {
21
+ const t = ev?.type;
22
+ return t === 'suspended' || t === 'needs_review';
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ function isOwnToolStart(ev, callId) {
29
+ if (callId === undefined || callId.length === 0)
30
+ return false;
31
+ try {
32
+ const e = ev;
33
+ return e !== null && typeof e === 'object' && e.type === 'tool_start' && e.toolCallId === callId;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ function errorNameOf(e) {
40
+ try {
41
+ const name = e?.name;
42
+ return typeof name === 'string' ? name : 'Error';
43
+ }
44
+ catch {
45
+ return 'Error';
46
+ }
47
+ }
48
+ function budgetMsOf(deps) {
49
+ try {
50
+ const raw = readOwnDataValue(deps, 'budgetMs');
51
+ return typeof raw === 'number' && Number.isFinite(raw) ? Math.max(0, raw) : RUN_LEDGER_SNAPSHOT_BUDGET_MS;
52
+ }
53
+ catch {
54
+ return RUN_LEDGER_SNAPSHOT_BUDGET_MS;
55
+ }
56
+ }
57
+ export async function readRunLedgerSnapshot(taskId, callId, deps, signal) {
58
+ const log = (msg) => {
59
+ try {
60
+ deps.log?.(msg);
61
+ }
62
+ catch {
63
+ }
64
+ };
65
+ if (typeof taskId !== 'string' || taskId.length === 0)
66
+ return null;
67
+ const ac = new AbortController();
68
+ const onOuterAbort = () => ac.abort();
69
+ let outerWatched = false;
70
+ try {
71
+ if (signal?.aborted === true)
72
+ return null;
73
+ signal?.addEventListener('abort', onOuterAbort, { once: true });
74
+ outerWatched = signal !== undefined;
75
+ }
76
+ catch (e) {
77
+ log(`[ledgerSnapshot] signal-unreadable task=${taskId} name=${errorNameOf(e)}`);
78
+ return null;
79
+ }
80
+ const budget = budgetMsOf(deps);
81
+ let onExpire;
82
+ const expired = new Promise((resolve) => {
83
+ onExpire = () => resolve(null);
84
+ if (ac.signal.aborted)
85
+ resolve(null);
86
+ else
87
+ ac.signal.addEventListener('abort', onExpire, { once: true });
88
+ });
89
+ const parkedNow = async () => {
90
+ try {
91
+ return (await deps.isParked(taskId, ac.signal)) === true;
92
+ }
93
+ catch (e) {
94
+ log(`[ledgerSnapshot] parked-read-threw task=${taskId} name=${errorNameOf(e)}`);
95
+ return false;
96
+ }
97
+ };
98
+ const waitAbortable = (ms) => abortableSleep(Math.max(0, ms), ac.signal).then(() => !ac.signal.aborted);
99
+ const readOnce = async (drainLegs) => {
100
+ let max;
101
+ let folded;
102
+ let numbered = 0;
103
+ let lastParkAt = -1;
104
+ let openToolStart;
105
+ let unnumbered = 0;
106
+ let preambleLen = 0;
107
+ let unnumberedRun = 0;
108
+ let complete = false;
109
+ let cappedOut = false;
110
+ let disordered = false;
111
+ const passAc = new AbortController();
112
+ const onOuter = () => passAc.abort();
113
+ if (ac.signal.aborted)
114
+ passAc.abort();
115
+ else
116
+ ac.signal.addEventListener('abort', onOuter, { once: true });
117
+ try {
118
+ if (deps.fold !== undefined)
119
+ folded = deps.fold.init();
120
+ for await (const ev of deps.frames(taskId, passAc.signal)) {
121
+ if (ac.signal.aborted)
122
+ break;
123
+ if (drainLegs.see(ev))
124
+ continue;
125
+ const seq = frameSeq(ev);
126
+ if (seq === undefined && deps.fold?.substantive?.(ev) === true) {
127
+ disordered = true;
128
+ break;
129
+ }
130
+ if (seq === undefined) {
131
+ unnumbered += 1;
132
+ if (unnumbered > UNNUMBERED_FRAME_CAP + 3 * preambleLen) {
133
+ cappedOut = true;
134
+ break;
135
+ }
136
+ if (max === undefined) {
137
+ preambleLen += 1;
138
+ continue;
139
+ }
140
+ unnumberedRun += 1;
141
+ if (preambleLen > 0 && unnumberedRun >= 2 * preambleLen) {
142
+ complete = true;
143
+ break;
144
+ }
145
+ continue;
146
+ }
147
+ unnumberedRun = 0;
148
+ if (deps.fold?.strictOrder === true && max !== undefined && seq <= max) {
149
+ disordered = true;
150
+ break;
151
+ }
152
+ numbered += 1;
153
+ if (max === undefined || seq > max)
154
+ max = seq;
155
+ if (isParkMarker(ev))
156
+ lastParkAt = numbered;
157
+ if (isOwnToolStart(ev, callId))
158
+ openToolStart = ev;
159
+ if (deps.fold !== undefined)
160
+ folded = deps.fold.step(folded, ev);
161
+ }
162
+ if (!complete && !cappedOut && !disordered && !ac.signal.aborted) {
163
+ const drained = drainLegs.legEnded('returned');
164
+ if (drained !== undefined)
165
+ return { kind: 'drained', decision: drained };
166
+ complete = true;
167
+ }
168
+ }
169
+ catch (e) {
170
+ drainLegs.legEnded('threw');
171
+ const name = errorNameOf(e);
172
+ if (!ac.signal.aborted && name === 'StreamEndedWithoutTerminalError')
173
+ complete = true;
174
+ else
175
+ log(`[ledgerSnapshot] read-ended-unproven task=${taskId} name=${name}`);
176
+ }
177
+ finally {
178
+ passAc.abort();
179
+ ac.signal.removeEventListener('abort', onOuter);
180
+ }
181
+ if (!complete || disordered || max === undefined || ac.signal.aborted) {
182
+ if (disordered)
183
+ log(`[ledgerSnapshot] read-unordered task=${taskId}`);
184
+ return { kind: 'read', result: null };
185
+ }
186
+ const nearParkTail = lastParkAt > 0 && numbered - lastParkAt <= PARK_TRAILER_MAX;
187
+ return {
188
+ kind: 'read',
189
+ result: { max, nearParkTail, ...(openToolStart !== undefined ? { openToolStart } : {}), ...(deps.fold !== undefined ? { folded: folded } : {}) },
190
+ };
191
+ };
192
+ const readPass = async () => {
193
+ const drainLegs = createStreamDrainingLegs();
194
+ for (;;) {
195
+ const once = await readOnce(drainLegs);
196
+ if (once.kind === 'read')
197
+ return once.result;
198
+ if (!('reconnectAfterMs' in once.decision)) {
199
+ log(`[ledgerSnapshot] drained-give-up task=${taskId}`);
200
+ return null;
201
+ }
202
+ log(`[ledgerSnapshot] drained-reread task=${taskId} ms=${String(once.decision.reconnectAfterMs)}`);
203
+ if (!(await waitAbortable(once.decision.reconnectAfterMs)))
204
+ return null;
205
+ }
206
+ };
207
+ const body = async () => {
208
+ try {
209
+ if (!(await parkedNow())) {
210
+ log(`[ledgerSnapshot] not-parked-before task=${taskId}`);
211
+ return null;
212
+ }
213
+ const [first, second] = await Promise.all([readPass(), readPass()]);
214
+ if (first === null) {
215
+ log(`[ledgerSnapshot] no-proof task=${taskId}`);
216
+ return null;
217
+ }
218
+ if (!first.nearParkTail) {
219
+ log(`[ledgerSnapshot] tail-not-at-park task=${taskId}`);
220
+ return null;
221
+ }
222
+ if (second === null || second.max !== first.max || !second.nearParkTail) {
223
+ log(`[ledgerSnapshot] passes-disagree-on-end task=${taskId}`);
224
+ return null;
225
+ }
226
+ const agree = deps.fold?.agree;
227
+ if (agree !== undefined && !(first.folded !== undefined && second.folded !== undefined && agree(first.folded, second.folded) === true)) {
228
+ log(`[ledgerSnapshot] passes-disagree-on-fold task=${taskId}`);
229
+ return null;
230
+ }
231
+ const chosen = second.openToolStart !== undefined ? second : first;
232
+ if (!(await parkedNow())) {
233
+ log(`[ledgerSnapshot] not-parked-after task=${taskId}`);
234
+ return null;
235
+ }
236
+ if (ac.signal.aborted)
237
+ return null;
238
+ return {
239
+ highWater: String(chosen.max),
240
+ ...(chosen.openToolStart !== undefined ? { openToolStart: chosen.openToolStart } : {}),
241
+ ...(deps.fold !== undefined && chosen.folded !== undefined ? { folded: chosen.folded } : {}),
242
+ };
243
+ }
244
+ catch (e) {
245
+ log(`[ledgerSnapshot] internal-error task=${taskId} name=${errorNameOf(e)}`);
246
+ return null;
247
+ }
248
+ };
249
+ try {
250
+ const bounded = settleWithinDeadline(budget, (deadline) => {
251
+ if (deadline.aborted)
252
+ ac.abort();
253
+ else
254
+ deadline.addEventListener('abort', () => ac.abort(), { once: true });
255
+ return body();
256
+ }).then((settled) => (settled.kind === 'value' ? settled.value : null));
257
+ return await Promise.race([bounded, expired]);
258
+ }
259
+ finally {
260
+ if (outerWatched) {
261
+ try {
262
+ signal?.removeEventListener('abort', onOuterAbort);
263
+ }
264
+ catch (e) {
265
+ log(`[ledgerSnapshot] signal-unlisten-threw task=${taskId} name=${errorNameOf(e)}`);
266
+ }
267
+ }
268
+ if (onExpire !== undefined)
269
+ ac.signal.removeEventListener('abort', onExpire);
270
+ ac.abort();
271
+ }
272
+ }
@@ -0,0 +1,57 @@
1
+ import type { UiLanguage } from './env/uiLanguage.js';
2
+ import { type PermissionModeReceiptView } from './adapter/downstream/wiringManifestView.js';
3
+ import { SDK_PERMISSION_MODE_ADMISSION_FOLDS, SDK_PERMISSION_MODE_PERSIST_MISSES } from './generated/sdkFactTables.js';
4
+ import { type PermissionModeWriteReading } from './permissionModeWriteCapability.js';
5
+ import type { PermissionMode } from './permissionWireCaps.js';
6
+ export type { PermissionModeReceiptView };
7
+ type AdmissionFold = (typeof SDK_PERMISSION_MODE_ADMISSION_FOLDS)[number];
8
+ type PersistMiss = (typeof SDK_PERMISSION_MODE_PERSIST_MISSES)[number];
9
+ export interface PermissionModeAdmissionView {
10
+ readonly requested: string;
11
+ readonly admitted: string;
12
+ readonly foldedBy: AdmissionFold;
13
+ }
14
+ export type RunPermissionModeSwitchOutcome = {
15
+ readonly kind: 'switched';
16
+ readonly taskId: string;
17
+ readonly sessionId: string;
18
+ readonly permissionMode: PermissionModeReceiptView;
19
+ readonly admission?: PermissionModeAdmissionView;
20
+ readonly persisted: true;
21
+ } | {
22
+ readonly kind: 'switched';
23
+ readonly taskId: string;
24
+ readonly sessionId: string;
25
+ readonly permissionMode: PermissionModeReceiptView;
26
+ readonly admission?: PermissionModeAdmissionView;
27
+ readonly persisted: false;
28
+ readonly persistMiss: PersistMiss;
29
+ } | {
30
+ readonly kind: 'not-running';
31
+ } | {
32
+ readonly kind: 'not-found';
33
+ } | {
34
+ readonly kind: 'refused';
35
+ readonly status: number;
36
+ readonly errorCode?: string;
37
+ } | {
38
+ readonly kind: 'unconfirmed';
39
+ readonly why: 'transport' | 'server-error' | 'unreadable-answer';
40
+ readonly status?: number;
41
+ } | {
42
+ readonly kind: 'unavailable';
43
+ };
44
+ export interface RunPermissionModeSwitchRuns {
45
+ setPermissionMode(taskId: string, mode: PermissionMode, opts?: {
46
+ session?: string;
47
+ signal?: AbortSignal;
48
+ }): Promise<object>;
49
+ }
50
+ export interface RunPermissionModeSwitchOptions {
51
+ readonly permissionModeWrite?: PermissionModeWriteReading | undefined;
52
+ readonly sessionId?: string | undefined;
53
+ readonly signal?: AbortSignal | undefined;
54
+ }
55
+ export declare const PERMISSION_MODE_SWITCH_RESULT_KEYS: readonly ["taskId", "sessionId", "status", "permissionMode", "admission", "persisted", "persistMiss"];
56
+ export declare function setRunPermissionMode(runs: RunPermissionModeSwitchRuns | null | undefined, taskId: string, mode: PermissionMode, opts?: RunPermissionModeSwitchOptions): Promise<RunPermissionModeSwitchOutcome>;
57
+ export declare function runPermissionModeSwitchLine(outcome: RunPermissionModeSwitchOutcome, lang?: UiLanguage): string;
@@ -0,0 +1,135 @@
1
+ import { atMostOnceVerdictOf } from './atMostOnce.js';
2
+ import { readPermissionModeReceiptSegment } from './adapter/downstream/wiringManifestView.js';
3
+ import { displayUntrusted } from './displayUntrusted.js';
4
+ import { SDK_PERMISSION_MODE_ADMISSION_FOLDS, SDK_PERMISSION_MODE_PERSIST_MISSES } from './generated/sdkFactTables.js';
5
+ import { msg, renderMessage } from './i18n/translate.js';
6
+ import { ownDataField } from './ownKey.js';
7
+ import { permissionModeWriteAvailable } from './permissionModeWriteCapability.js';
8
+ import { isDisplayableWireCode } from './wireFailureShape.js';
9
+ export const PERMISSION_MODE_SWITCH_RESULT_KEYS = Object.freeze(['taskId', 'sessionId', 'status', 'permissionMode', 'admission', 'persisted', 'persistMiss']);
10
+ const NOT_RUNNING_CODE = 'steering.not_running';
11
+ const PRE_SWITCH_CORRUPT_CODE = 'corrupt';
12
+ function guardedSlot(o, key) {
13
+ if (o === null || (typeof o !== 'object' && typeof o !== 'function'))
14
+ return undefined;
15
+ try {
16
+ return o[key];
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ const unavailable = () => Object.freeze({ kind: 'unavailable' });
23
+ const unreadableAnswer = () => Object.freeze({ kind: 'unconfirmed', why: 'unreadable-answer' });
24
+ function admissionOf(raw) {
25
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
26
+ return undefined;
27
+ const requested = ownDataField(raw, 'requested');
28
+ const admitted = ownDataField(raw, 'admitted');
29
+ const foldedBy = ownDataField(raw, 'foldedBy');
30
+ if (typeof requested !== 'string' || requested === '' || typeof admitted !== 'string' || admitted === '')
31
+ return undefined;
32
+ const fold = SDK_PERMISSION_MODE_ADMISSION_FOLDS.find((w) => w === foldedBy);
33
+ return fold === undefined ? undefined : Object.freeze({ requested, admitted, foldedBy: fold });
34
+ }
35
+ function switchOutcomeOf(taskId, sent, body) {
36
+ try {
37
+ if (body === null || typeof body !== 'object' || Array.isArray(body))
38
+ return unreadableAnswer();
39
+ const keys = PERMISSION_MODE_SWITCH_RESULT_KEYS;
40
+ for (const k of Reflect.ownKeys(body))
41
+ if (typeof k !== 'string' || !keys.includes(k))
42
+ return unreadableAnswer();
43
+ if (ownDataField(body, 'taskId') !== taskId)
44
+ return unreadableAnswer();
45
+ const sessionId = ownDataField(body, 'sessionId');
46
+ if (typeof sessionId !== 'string' || sessionId === '' || ownDataField(body, 'status') !== 'running')
47
+ return unreadableAnswer();
48
+ const permissionMode = readPermissionModeReceiptSegment(body, 'permissionMode');
49
+ if (permissionMode === undefined)
50
+ return unreadableAnswer();
51
+ const admissionRaw = ownDataField(body, 'admission');
52
+ const admission = admissionRaw === undefined ? undefined : admissionOf(admissionRaw);
53
+ if (admissionRaw !== undefined && admission === undefined)
54
+ return unreadableAnswer();
55
+ const asked = admission !== undefined ? admission.requested : permissionMode.requested;
56
+ if (asked !== undefined && asked !== sent)
57
+ return unreadableAnswer();
58
+ if (admission !== undefined && permissionMode.requested !== undefined && permissionMode.requested !== admission.admitted)
59
+ return unreadableAnswer();
60
+ const persisted = ownDataField(body, 'persisted');
61
+ const persistMiss = ownDataField(body, 'persistMiss');
62
+ const base = { kind: 'switched', taskId, sessionId, permissionMode, ...(admission !== undefined ? { admission } : {}) };
63
+ if (persisted === true && persistMiss === undefined)
64
+ return Object.freeze({ ...base, persisted: true });
65
+ const miss = persisted === false ? SDK_PERMISSION_MODE_PERSIST_MISSES.find((w) => w === persistMiss) : undefined;
66
+ return miss === undefined ? unreadableAnswer() : Object.freeze({ ...base, persisted: false, persistMiss: miss });
67
+ }
68
+ catch {
69
+ return unreadableAnswer();
70
+ }
71
+ }
72
+ function failureOutcomeOf(e) {
73
+ const v = atMostOnceVerdictOf(e);
74
+ if (v.status === 501)
75
+ return unavailable();
76
+ if (v.delivery === 'rejected' && v.status !== undefined) {
77
+ if (v.status === 404)
78
+ return Object.freeze({ kind: 'not-found' });
79
+ if (v.status === 409 && v.code === NOT_RUNNING_CODE)
80
+ return Object.freeze({ kind: 'not-running' });
81
+ return Object.freeze({ kind: 'refused', status: v.status, ...(v.code !== undefined ? { errorCode: v.code } : {}) });
82
+ }
83
+ if (v.status === 500 && v.code === PRE_SWITCH_CORRUPT_CODE)
84
+ return Object.freeze({ kind: 'refused', status: 500, errorCode: v.code });
85
+ return v.status !== undefined
86
+ ? Object.freeze({ kind: 'unconfirmed', why: 'server-error', status: v.status })
87
+ : Object.freeze({ kind: 'unconfirmed', why: 'transport' });
88
+ }
89
+ export async function setRunPermissionMode(runs, taskId, mode, opts = {}) {
90
+ if (!permissionModeWriteAvailable(guardedSlot(opts, 'permissionModeWrite')))
91
+ return unavailable();
92
+ const verb = guardedSlot(runs, 'setPermissionMode');
93
+ if (typeof verb !== 'function')
94
+ return unavailable();
95
+ const sessionId = guardedSlot(opts, 'sessionId');
96
+ const signal = guardedSlot(opts, 'signal');
97
+ const callOpts = {
98
+ ...(typeof sessionId === 'string' && sessionId.length > 0 ? { session: sessionId } : {}),
99
+ ...(signal !== undefined && signal !== null ? { signal: signal } : {}),
100
+ };
101
+ let body;
102
+ try {
103
+ body = await verb.call(runs, taskId, mode, callOpts);
104
+ }
105
+ catch (e) {
106
+ return failureOutcomeOf(e);
107
+ }
108
+ return switchOutcomeOf(taskId, mode, body);
109
+ }
110
+ export function runPermissionModeSwitchLine(outcome, lang) {
111
+ switch (outcome.kind) {
112
+ case 'switched': {
113
+ const mode = displayUntrusted(outcome.permissionMode.effective, { max: 64 });
114
+ const reason = outcome.admission !== undefined
115
+ ? msg(outcome.admission.foldedBy === 'entry_floor' ? 'session.permissionModeSwitch.reason.entryFloor' : 'session.permissionModeSwitch.reason.disableAutoMode')
116
+ : outcome.permissionMode.foldedBy !== undefined
117
+ ? msg('session.permissionModeSwitch.reason.engine')
118
+ : undefined;
119
+ const line = renderMessage(reason === undefined ? msg('session.permissionModeSwitch.applied', { mode }) : msg('session.permissionModeSwitch.folded', { mode, reason }), lang);
120
+ return outcome.persisted ? line : renderMessage(msg('session.permissionModeSwitch.notPersisted', { line }), lang);
121
+ }
122
+ case 'not-running':
123
+ return renderMessage(msg('session.permissionModeSwitch.notRunning'), lang);
124
+ case 'not-found':
125
+ return renderMessage(msg('session.permissionModeSwitch.notFound'), lang);
126
+ case 'refused':
127
+ return outcome.errorCode !== undefined && isDisplayableWireCode(outcome.errorCode)
128
+ ? renderMessage(msg('session.permissionModeSwitch.refusedWithCode', { status: String(outcome.status), code: outcome.errorCode }), lang)
129
+ : renderMessage(msg('session.permissionModeSwitch.refused', { status: String(outcome.status) }), lang);
130
+ case 'unconfirmed':
131
+ return renderMessage(msg('session.permissionModeSwitch.unconfirmed'), lang);
132
+ case 'unavailable':
133
+ return renderMessage(msg('session.permissionModeSwitch.unavailable'), lang);
134
+ }
135
+ }
@@ -1,31 +1,42 @@
1
+ import { type RunNotFollowed } from './runFollow.js';
1
2
  export declare const REATTACH_MAX_TRIES = 5;
3
+ export declare const STREAM_CAP_CREDIBLE_AFTER_MS = 300000;
2
4
  export interface ReattachBudget {
3
5
  readonly tries: number;
4
6
  readonly drainingStreak: number;
5
7
  readonly drainingTotal: number;
6
8
  }
7
9
  export declare const REATTACH_BUDGET_FRESH: ReattachBudget;
10
+ export declare const REATTACH_BUDGET_NONE: ReattachBudget;
8
11
  export type ReattachEvent = {
9
12
  readonly cause: 'connection_lost';
10
- readonly hasRunId: boolean;
13
+ readonly runId?: string;
11
14
  readonly error?: object;
12
15
  } | {
13
16
  readonly cause: 'stream_cap';
14
- readonly hasRunId: boolean;
17
+ readonly runId?: string;
18
+ readonly legElapsedMs?: number;
15
19
  } | {
16
20
  readonly cause: 'stream_draining';
17
- readonly hasRunId: boolean;
21
+ readonly runId?: string;
18
22
  readonly frame: object;
19
23
  readonly madeProgress?: boolean;
24
+ } | {
25
+ readonly cause: 'stream_draining';
26
+ readonly runId?: string;
27
+ readonly ladder: 'exhausted';
20
28
  };
21
- export type ReattachGiveUp = 'no_run_id' | 'budget_exhausted' | 'bad_input';
29
+ export type ReattachDrainingEvent = Extract<ReattachEvent, {
30
+ readonly cause: 'stream_draining';
31
+ }>;
22
32
  export type ReattachDecision = {
23
33
  readonly action: 'reattach';
24
34
  readonly afterMs: number;
35
+ readonly premature?: true;
25
36
  readonly budget: ReattachBudget;
26
37
  } | {
27
38
  readonly action: 'give_up';
28
- readonly reason: ReattachGiveUp;
29
39
  readonly budget: ReattachBudget;
40
+ readonly notFollowed: RunNotFollowed<'reattach_exhausted' | 'no_run_id' | 'bad_input'>;
30
41
  };
31
42
  export declare function runReattachPolicy(budget: ReattachBudget, event: ReattachEvent): ReattachDecision;
@@ -1,46 +1,94 @@
1
1
  import { STREAM_DRAINING_RECONNECT_MAX, drainingRetryDelayMs, isPreStreamDrainingReject } from './wireErrorTriage.js';
2
2
  import { drainingLegStep } from './streamDrainingLegs.js';
3
+ import { mintRunNotFollowed } from './runFollow.js';
3
4
  export const REATTACH_MAX_TRIES = 5;
5
+ export const STREAM_CAP_CREDIBLE_AFTER_MS = 300_000;
6
+ const connectionLostBackoffMs = (n) => 1000 * n;
4
7
  const DRAINING_TOTAL_MAX = REATTACH_MAX_TRIES * STREAM_DRAINING_RECONNECT_MAX;
5
8
  export const REATTACH_BUDGET_FRESH = Object.freeze({ tries: 0, drainingStreak: 0, drainingTotal: 0 });
9
+ export const REATTACH_BUDGET_NONE = Object.freeze({ tries: REATTACH_MAX_TRIES, drainingStreak: 0, drainingTotal: DRAINING_TOTAL_MAX });
6
10
  const isCount = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
7
- const giveUp = (reason, budget) => Object.freeze({ action: 'give_up', reason, budget });
8
- const reattach = (afterMs, tries, drainingStreak, drainingTotal) => Object.freeze({ action: 'reattach', afterMs, budget: Object.freeze({ tries, drainingStreak, drainingTotal }) });
11
+ const giveUp = (notFollowed, budget) => Object.freeze({ action: 'give_up', budget, notFollowed });
12
+ const reattach = (afterMs, tries, drainingStreak, drainingTotal, premature) => Object.freeze({ action: 'reattach', afterMs, ...(premature === true ? { premature } : {}), budget: Object.freeze({ tries, drainingStreak, drainingTotal }) });
9
13
  export function runReattachPolicy(budget, event) {
14
+ let handle;
10
15
  try {
11
- if (typeof budget !== 'object' || budget === null || !isCount(budget.tries) || !isCount(budget.drainingStreak) || !isCount(budget.drainingTotal))
12
- return giveUp('bad_input', REATTACH_BUDGET_FRESH);
13
- if (typeof event !== 'object' || event === null || typeof event.hasRunId !== 'boolean')
14
- return giveUp('bad_input', budget);
15
- if (!event.hasRunId)
16
- return giveUp('no_run_id', budget);
16
+ const eventOk = typeof event === 'object' && event !== null;
17
+ const runId = eventOk ? event.runId : undefined;
18
+ handle = typeof runId === 'string' && runId !== '' ? runId : undefined;
19
+ const budgetOk = typeof budget === 'object' && budget !== null && isCount(budget.tries) && isCount(budget.drainingStreak) && isCount(budget.drainingTotal);
20
+ const keep = budgetOk ? budget : REATTACH_BUDGET_FRESH;
21
+ if (!eventOk)
22
+ return giveUp(mintRunNotFollowed('bad_input'), keep);
23
+ const bad = () => giveUp(mintRunNotFollowed('bad_input', handle), keep);
24
+ if (!budgetOk)
25
+ return bad();
26
+ const cause = event.cause;
27
+ if (cause !== 'connection_lost' && cause !== 'stream_cap' && cause !== 'stream_draining')
28
+ return bad();
29
+ let draining;
30
+ if (cause === 'stream_draining') {
31
+ const ladder = event.ladder;
32
+ const frame = event.frame;
33
+ if ((ladder === undefined) === (frame === undefined))
34
+ return bad();
35
+ if (ladder !== undefined) {
36
+ if (ladder !== 'exhausted')
37
+ return bad();
38
+ draining = { ladder: true };
39
+ }
40
+ else {
41
+ const madeProgress = event.madeProgress;
42
+ const streak = madeProgress === true ? 0 : budget.drainingStreak;
43
+ const st = drainingLegStep(streak, frame);
44
+ if ('giveUp' in st.decision && st.decision.giveUp === 'not_draining_end')
45
+ return bad();
46
+ draining = { step: st };
47
+ }
48
+ }
49
+ let premature = false;
50
+ if (cause === 'stream_cap') {
51
+ const legElapsedMs = event.legElapsedMs;
52
+ if (legElapsedMs !== undefined) {
53
+ if (typeof legElapsedMs !== 'number' || !Number.isFinite(legElapsedMs) || legElapsedMs < 0)
54
+ return bad();
55
+ premature = legElapsedMs < STREAM_CAP_CREDIBLE_AFTER_MS;
56
+ }
57
+ }
58
+ if (runId === undefined)
59
+ return giveUp(mintRunNotFollowed('no_run_id'), keep);
60
+ if (handle === undefined)
61
+ return bad();
62
+ const exhausted = (brk) => giveUp(mintRunNotFollowed('reattach_exhausted', handle, brk), budget);
17
63
  const { tries, drainingStreak, drainingTotal } = budget;
18
- if (event.cause === 'connection_lost') {
64
+ if (cause === 'connection_lost') {
19
65
  if (tries >= REATTACH_MAX_TRIES)
20
- return giveUp('budget_exhausted', budget);
66
+ return exhausted(cause);
67
+ const error = event.error;
21
68
  const next = tries + 1;
22
- const afterMs = isPreStreamDrainingReject(event.error) ? drainingRetryDelayMs(event.error, next - 1) : 1000 * next;
69
+ const afterMs = isPreStreamDrainingReject(error) ? drainingRetryDelayMs(error, next - 1) : connectionLostBackoffMs(next);
23
70
  return reattach(afterMs, next, 0, drainingTotal);
24
71
  }
25
- if (event.cause === 'stream_cap') {
72
+ if (cause === 'stream_cap') {
26
73
  if (tries >= REATTACH_MAX_TRIES)
27
- return giveUp('budget_exhausted', budget);
74
+ return exhausted(cause);
75
+ if (premature)
76
+ return reattach(connectionLostBackoffMs(tries + 1), tries + 1, 0, drainingTotal, true);
28
77
  return reattach(0, tries + 1, 0, drainingTotal);
29
78
  }
30
- if (event.cause === 'stream_draining') {
31
- const streak = event.madeProgress === true ? 0 : drainingStreak;
32
- const { decision, streak: nextStreak } = drainingLegStep(streak, event.frame);
33
- if ('reconnectAfterMs' in decision && drainingTotal < DRAINING_TOTAL_MAX)
34
- return reattach(decision.reconnectAfterMs, tries, nextStreak, drainingTotal + 1);
35
- if ('giveUp' in decision && decision.giveUp === 'not_draining_end')
36
- return giveUp('bad_input', budget);
79
+ if (draining === undefined || 'ladder' in draining) {
37
80
  if (tries >= REATTACH_MAX_TRIES)
38
- return giveUp('budget_exhausted', budget);
39
- return reattach(0, tries + 1, nextStreak, drainingTotal);
81
+ return exhausted(cause);
82
+ return reattach(0, tries + 1, drainingStreak, drainingTotal);
40
83
  }
41
- return giveUp('bad_input', budget);
84
+ const { decision, streak: nextStreak } = draining.step;
85
+ if ('reconnectAfterMs' in decision && drainingTotal < DRAINING_TOTAL_MAX)
86
+ return reattach(decision.reconnectAfterMs, tries, nextStreak, drainingTotal + 1);
87
+ if (tries >= REATTACH_MAX_TRIES)
88
+ return exhausted(cause);
89
+ return reattach(0, tries + 1, nextStreak, drainingTotal);
42
90
  }
43
91
  catch {
44
- return giveUp('bad_input', REATTACH_BUDGET_FRESH);
92
+ return giveUp(mintRunNotFollowed('bad_input', handle), REATTACH_BUDGET_FRESH);
45
93
  }
46
94
  }