@sema-agent/client-core 0.108.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 (179) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/README.md +77 -40
  3. package/dist/adapt/arms.js +41 -13
  4. package/dist/adapt/textStream.d.ts +2 -0
  5. package/dist/adapt/textStream.js +37 -16
  6. package/dist/adapter/activeRunSelfHeal.js +1 -1
  7. package/dist/adapter/downstream/eventToSdkMessage.js +11 -5
  8. package/dist/agentSession/engineAgentRegistryRead.js +2 -0
  9. package/dist/bashExitReading.d.ts +12 -0
  10. package/dist/bashExitReading.js +44 -0
  11. package/dist/checkpointRowVerdict.d.ts +1 -1
  12. package/dist/checkpointRowVerdict.js +4 -2
  13. package/dist/compensations.js +1 -1
  14. package/dist/declared/supportFloorDeclared.js +1 -1
  15. package/dist/displayUntrusted.js +132 -14
  16. package/dist/engineAgentAbsence.d.ts +2 -0
  17. package/dist/engineAgentAbsence.js +28 -0
  18. package/dist/engineErrorCodes.d.ts +7 -0
  19. package/dist/engineErrorCodes.js +7 -0
  20. package/dist/engineHealthFacts.d.ts +26 -0
  21. package/dist/engineHealthFacts.js +63 -4
  22. package/dist/engineInlineTaskStats.d.ts +4 -3
  23. package/dist/engineInlineTaskStats.js +18 -2
  24. package/dist/engineNoticeCodes.d.ts +138 -0
  25. package/dist/engineNoticeCodes.js +662 -55
  26. package/dist/engineRepoint.d.ts +140 -0
  27. package/dist/engineRepoint.js +285 -0
  28. package/dist/engineRowRef.js +2 -0
  29. package/dist/engineSessionMessages.d.ts +2 -1
  30. package/dist/engineSessionMessages.js +5 -0
  31. package/dist/errorSupplementRoles.js +5 -1
  32. package/dist/fleet/fleetProjection.js +3 -2
  33. package/dist/fleetTaskDesc.js +3 -3
  34. package/dist/generated/engineFactTables.d.ts +3 -0
  35. package/dist/generated/engineFactTables.js +18 -0
  36. package/dist/generated/engineNoticeTables.js +3 -0
  37. package/dist/generated/sdkFactTables.d.ts +1 -0
  38. package/dist/generated/sdkFactTables.js +4 -0
  39. package/dist/hitl/approvalAskers.d.ts +6 -0
  40. package/dist/hitl/approvalAskers.js +30 -0
  41. package/dist/hitl/approvalCardReasons.js +25 -4
  42. package/dist/hitl/approvalRequestFrameReading.d.ts +21 -0
  43. package/dist/hitl/approvalRequestFrameReading.js +90 -0
  44. package/dist/hitl/approvalResolution.d.ts +8 -1
  45. package/dist/hitl/approvalResolution.js +4 -0
  46. package/dist/hitl/approvalsFeed.js +1 -1
  47. package/dist/hitl/askGateWire.js +0 -2
  48. package/dist/hitl/askParkRowRouting.js +5 -2
  49. package/dist/hitl/automaticAnswerVerdict.d.ts +5 -2
  50. package/dist/hitl/automaticAnswerVerdict.js +18 -4
  51. package/dist/hitl/decisionConflict.d.ts +2 -1
  52. package/dist/hitl/decisionConflict.js +10 -4
  53. package/dist/hitl/decisionRefusal.js +1 -0
  54. package/dist/hitl/decisionSend.d.ts +3 -0
  55. package/dist/hitl/decisionSend.js +29 -11
  56. package/dist/hitl/editedApprovalAcceptance.d.ts +25 -0
  57. package/dist/hitl/editedApprovalAcceptance.js +26 -0
  58. package/dist/hitl/frameRouter.js +13 -19
  59. package/dist/hitl/gateLedger.d.ts +0 -4
  60. package/dist/hitl/gateLedger.js +0 -19
  61. package/dist/hitl/hitlBridge.d.ts +5 -0
  62. package/dist/hitl/hitlBridge.js +13 -9
  63. package/dist/hitl/hitlHostSurface.d.ts +1 -0
  64. package/dist/hitl/hitlHostSurface.js +7 -2
  65. package/dist/hitl/hostWithdrawal.d.ts +15 -0
  66. package/dist/hitl/hostWithdrawal.js +38 -0
  67. package/dist/hitl/livePendingAsk.js +17 -16
  68. package/dist/hitl/localAllowRule.js +2 -1
  69. package/dist/hitl/noAllowCause.d.ts +1 -1
  70. package/dist/hitl/noAllowCause.js +4 -2
  71. package/dist/hitl/parkExpiryJudgements.js +26 -1
  72. package/dist/hitl/parkOwnership.d.ts +2 -0
  73. package/dist/hitl/parkOwnership.js +48 -8
  74. package/dist/hitl/parkResolver.js +32 -16
  75. package/dist/hitl/parkedUnresumable.d.ts +13 -0
  76. package/dist/hitl/parkedUnresumable.js +80 -0
  77. package/dist/hitl/permissionRuleScope.d.ts +4 -0
  78. package/dist/hitl/permissionRuleScope.js +31 -0
  79. package/dist/hitl/persistedRulesWire.d.ts +2 -0
  80. package/dist/hitl/persistedRulesWire.js +13 -12
  81. package/dist/hitl/planReviewWire.js +14 -4
  82. package/dist/hitl/sessionAllowRoute.js +5 -4
  83. package/dist/hitl/sessionPolicyWire.js +6 -4
  84. package/dist/hitl/standingRows.d.ts +2 -5
  85. package/dist/hitl/standingRows.js +30 -15
  86. package/dist/hitl/toolApprovalWire.d.ts +42 -9
  87. package/dist/hitl/toolApprovalWire.js +267 -44
  88. package/dist/hooksWireCaps.js +35 -4
  89. package/dist/hostSettingsSource.js +2 -0
  90. package/dist/hostTurnSettings.d.ts +10 -4
  91. package/dist/hostTurnSettings.js +112 -5
  92. package/dist/i18n/messages.en.d.ts +251 -0
  93. package/dist/i18n/messages.en.js +254 -2
  94. package/dist/i18n/messages.zh.js +264 -2
  95. package/dist/index.d.ts +48 -12
  96. package/dist/index.js +29 -7
  97. package/dist/inheritEnvCapability.js +2 -0
  98. package/dist/inputAuthor.d.ts +16 -0
  99. package/dist/inputAuthor.js +44 -0
  100. package/dist/interruptRefusalNote.d.ts +2 -0
  101. package/dist/interruptRefusalNote.js +17 -0
  102. package/dist/liveModelCatalog.d.ts +2 -0
  103. package/dist/liveModelCatalog.js +21 -7
  104. package/dist/liveQuestionStore.d.ts +4 -1
  105. package/dist/liveQuestionStore.js +12 -3
  106. package/dist/localEngineJudge.d.ts +75 -0
  107. package/dist/localEngineJudge.js +242 -0
  108. package/dist/localEngineOccupancy.d.ts +88 -0
  109. package/dist/localEngineOccupancy.js +269 -0
  110. package/dist/localEngineSeatReading.d.ts +29 -0
  111. package/dist/localEngineSeatReading.js +294 -0
  112. package/dist/localEngineSpawn.d.ts +113 -0
  113. package/dist/localEngineSpawn.js +486 -0
  114. package/dist/localEngineStep.d.ts +286 -0
  115. package/dist/localEngineStep.js +1086 -0
  116. package/dist/localEngineStopLadder.d.ts +29 -0
  117. package/dist/localEngineStopLadder.js +139 -0
  118. package/dist/localEngineSwapCard.d.ts +43 -0
  119. package/dist/localEngineSwapCard.js +205 -0
  120. package/dist/localEngineTiming.d.ts +8 -0
  121. package/dist/localEngineTiming.js +8 -0
  122. package/dist/mcpEngineLeg.d.ts +3 -0
  123. package/dist/mcpEngineLeg.js +38 -1
  124. package/dist/mcpPanel.js +2 -0
  125. package/dist/mcpReconnect.d.ts +9 -1
  126. package/dist/mcpReconnect.js +132 -20
  127. package/dist/mcpWireCaps.js +2 -2
  128. package/dist/memoryEntriesWire.d.ts +1 -1
  129. package/dist/memoryEntriesWire.js +5 -0
  130. package/dist/modelCapabilityProbe.js +4 -3
  131. package/dist/modsFace.d.ts +8 -1
  132. package/dist/modsFace.js +27 -0
  133. package/dist/noticePresentation.d.ts +2 -0
  134. package/dist/noticePresentation.js +79 -1
  135. package/dist/notifications.d.ts +17 -6
  136. package/dist/notifications.js +67 -19
  137. package/dist/ownKey.d.ts +5 -0
  138. package/dist/ownKey.js +8 -1
  139. package/dist/peerFrames.d.ts +40 -2
  140. package/dist/peerFrames.js +158 -3
  141. package/dist/pluginHooksWire.d.ts +2 -1
  142. package/dist/pluginHooksWire.js +56 -1
  143. package/dist/replayBody.js +10 -2
  144. package/dist/request/taskRequest.d.ts +5 -0
  145. package/dist/request/taskRequest.js +138 -21
  146. package/dist/resumeRefusalCopy.d.ts +1 -3
  147. package/dist/resumeRefusalCopy.js +2 -1
  148. package/dist/retryStatus.d.ts +2 -2
  149. package/dist/retryStatus.js +2 -0
  150. package/dist/ruleReadings.js +1 -1
  151. package/dist/seam.d.ts +24 -2
  152. package/dist/seam.js +15 -2
  153. package/dist/seatContract.d.ts +125 -10
  154. package/dist/seatContract.js +373 -7
  155. package/dist/sessionBackground.js +4 -1
  156. package/dist/sessionBackgroundHandoff.js +2 -1
  157. package/dist/sessionEngine.d.ts +2 -0
  158. package/dist/sessionEngine.js +111 -0
  159. package/dist/sessionMemoryStatus.d.ts +2 -1
  160. package/dist/sessionSyncFileHistory.d.ts +4 -0
  161. package/dist/sessionSyncFileHistory.js +28 -0
  162. package/dist/sseIdleTriage.d.ts +13 -1
  163. package/dist/sseIdleTriage.js +24 -6
  164. package/dist/subagent/engineSubagentTail.js +6 -0
  165. package/dist/subagentContentStore.d.ts +12 -0
  166. package/dist/subagentContentStore.js +72 -11
  167. package/dist/terminalStatuses.d.ts +1 -0
  168. package/dist/terminalStatuses.js +4 -1
  169. package/dist/toolResult.js +18 -5
  170. package/dist/turnFailurePresentation.js +9 -2
  171. package/dist/typePins.d.ts +11 -0
  172. package/dist/usageAnalytics.d.ts +17 -0
  173. package/dist/usageAnalytics.js +44 -2
  174. package/dist/wireFailureShape.d.ts +22 -5
  175. package/dist/wireFailureShape.js +47 -26
  176. package/dist/wireRefusalCopy.d.ts +31 -1
  177. package/dist/wireRefusalCopy.js +167 -2
  178. package/docs/INTEGRATION-CLIENTS.md +3269 -21
  179. package/package.json +5 -5
@@ -0,0 +1,1086 @@
1
+ import { engineIdentityOf, engineIdentityVerdict } from './engineIdentity.js';
2
+ import { localEngineIdle } from './localEngineOccupancy.js';
3
+ import { engineSwapCardOf, localEngineSwapAutoable, LOCAL_ENGINE_SWAP_CAUSES, } from './localEngineSwapCard.js';
4
+ import { LOCAL_ENGINE_STOP_LADDER, stopLadderStep } from './localEngineStopLadder.js';
5
+ import { LOCAL_ENGINE_TIMING } from './localEngineTiming.js';
6
+ import { ownSnapshotTyped, readOwnDataValue } from './ownKey.js';
7
+ import { slotReadOf } from './wireFailureShape.js';
8
+ export const LOCAL_ENGINE_FAIL_CODES = Object.freeze([
9
+ 'local_engine.behind',
10
+ 'local_engine.legacy_below_floor',
11
+ 'local_engine.unresponsive',
12
+ 'local_engine.swap_pending',
13
+ 'local_engine.drain_overdue',
14
+ 'local_engine.unanswered_unverifiable',
15
+ 'local_engine.lock_unavailable',
16
+ 'local_engine.spawn_failed',
17
+ 'local_engine.spawn_refused',
18
+ 'local_engine.stop_failed',
19
+ 'local_engine.crash_loop',
20
+ 'local_engine.high_water_unreadable',
21
+ 'local_engine.principal_unknown',
22
+ ]);
23
+ function isRecord(v) {
24
+ if (typeof v !== 'object' || v === null)
25
+ return false;
26
+ try {
27
+ return !Array.isArray(v);
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ function own(v, key) {
34
+ if (typeof v !== 'object' || v === null)
35
+ return undefined;
36
+ try {
37
+ return readOwnDataValue(v, key);
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
43
+ function isTime(v) {
44
+ return typeof v === 'number' && Number.isSafeInteger(v) && v >= 0;
45
+ }
46
+ function isPid(v) {
47
+ return typeof v === 'number' && Number.isSafeInteger(v) && v > 0;
48
+ }
49
+ function isOp(v) {
50
+ return typeof v === 'number' && Number.isSafeInteger(v) && v > 0;
51
+ }
52
+ function isCause(v) {
53
+ return typeof v === 'string' && LOCAL_ENGINE_SWAP_CAUSES.includes(v);
54
+ }
55
+ function deepFreeze(v) {
56
+ if (typeof v === 'object' && v !== null && !Object.isFrozen(v)) {
57
+ for (const k of Object.keys(v))
58
+ deepFreeze(v[k]);
59
+ Object.freeze(v);
60
+ }
61
+ return v;
62
+ }
63
+ const T = LOCAL_ENGINE_TIMING;
64
+ export const LOCAL_ENGINE_INITIAL_STATE = deepFreeze({
65
+ kind: 'unbound',
66
+ ctx: { causes: [], autoTried: [], opSeq: 0, lockRetry: 0, attached: null, demand: false, deaths: [], cooldown: 0 },
67
+ });
68
+ function isRef(t) {
69
+ return isRecord(own(t, 'identity'));
70
+ }
71
+ function idOf(t) {
72
+ if (isRef(t)) {
73
+ const id = engineIdentityOf(t.identity);
74
+ return id.pid !== undefined ? id : { ...id, pid: t.pid };
75
+ }
76
+ return engineIdentityOf(t);
77
+ }
78
+ function sameTarget(a, b) {
79
+ if (a === null || a === undefined || b === null || b === undefined)
80
+ return false;
81
+ return engineIdentityVerdict(idOf(a), idOf(b)) === 'same' && a.pid === b.pid;
82
+ }
83
+ function sameGeneration(a, b) {
84
+ if (a === null || a === undefined || b === null || b === undefined || !sameTarget(a, b))
85
+ return false;
86
+ const x = idOf(a);
87
+ const y = idOf(b);
88
+ return engineIdentityVerdict({ instanceId: x.instanceId, startedAt: x.startedAt }, { instanceId: y.instanceId, startedAt: y.startedAt }) === 'same';
89
+ }
90
+ function stoppable(t) {
91
+ if (t === null || t === undefined)
92
+ return false;
93
+ if (isRef(t))
94
+ return t.identity.pid === t.pid && t.identity.startedAt !== undefined;
95
+ return t.startedAt !== undefined;
96
+ }
97
+ function causeFp(causes, c) {
98
+ return causes.find((x) => x.cause === c)?.fingerprint;
99
+ }
100
+ function setCause(causes, c, fingerprint) {
101
+ const next = causes.filter((x) => x.cause !== c).concat([{ cause: c, fingerprint }]);
102
+ return LOCAL_ENGINE_SWAP_CAUSES.flatMap((k) => next.filter((x) => x.cause === k));
103
+ }
104
+ function dropCause(causes, c) {
105
+ return causes.filter((x) => x.cause !== c);
106
+ }
107
+ function tried(ctx, c, t) {
108
+ const id = idOf(t);
109
+ return ctx.autoTried.some((x) => x.cause === c && engineIdentityVerdict(x.target, id) === 'same');
110
+ }
111
+ function addTried(ctx, c, t) {
112
+ return tried(ctx, c, t) ? [...ctx.autoTried] : [...ctx.autoTried, { cause: c, target: idOf(t) }];
113
+ }
114
+ function idle(occ) {
115
+ if (occ === undefined || occ === null)
116
+ return false;
117
+ try {
118
+ return localEngineIdle(occ) === true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ function interactive(policy) {
125
+ return own(policy, 'lane') === 'interactive';
126
+ }
127
+ function headless(policy) {
128
+ return own(policy, 'lane') !== 'interactive';
129
+ }
130
+ function nextOp(ctx) {
131
+ const op = ctx.opSeq + 1;
132
+ return { op, ctx: { ...ctx, opSeq: op } };
133
+ }
134
+ function heldInstance(s) {
135
+ switch (s.kind) {
136
+ case 'attached':
137
+ return idOf(s.engine);
138
+ case 'pending':
139
+ return s.target === null ? undefined : idOf(s.target);
140
+ case 'draining-wait':
141
+ return idOf(s.engine);
142
+ case 'unanswered':
143
+ return s.startedAt === undefined ? undefined : { pid: s.pid, startedAt: s.startedAt };
144
+ default:
145
+ return undefined;
146
+ }
147
+ }
148
+ function holdsInstance(s) {
149
+ return s.kind === 'attached' || s.kind === 'pending' || s.kind === 'draining-wait' || s.kind === 'unanswered';
150
+ }
151
+ function attachedRef(s) {
152
+ if (s.kind === 'attached')
153
+ return s.engine;
154
+ if (s.kind === 'pending' && s.serving && s.target !== null && isRef(s.target))
155
+ return s.target;
156
+ return undefined;
157
+ }
158
+ function accountDeath(ctx, instance, clean, fromDraining, nowMs) {
159
+ const counted = deathCounted(ctx, clean, fromDraining);
160
+ const livedFull = ctx.attached !== null && instance !== undefined && engineIdentityVerdict(instance, ctx.attached.instance) === 'same' && nowMs - ctx.attached.at >= T.crashWindowMs;
161
+ const base = { ...ctx, causes: [], autoTried: [] };
162
+ if (counted)
163
+ return livedFull ? { ...base, deaths: [nowMs], cooldown: 0 } : { ...base, deaths: [...ctx.deaths, nowMs] };
164
+ return livedFull ? { ...base, deaths: [], cooldown: 0 } : base;
165
+ }
166
+ function deathOf(ctx, instance, clean, fromDraining, nowMs) {
167
+ const next = accountDeath(ctx, instance, clean, fromDraining, nowMs);
168
+ return { ctx: next, notice: { kind: 'notice', notice: { key: 'engineExited', counted: deathCounted(ctx, clean, fromDraining), crashLoopNext: next.deaths.length > T.crashMaxDeaths } } };
169
+ }
170
+ function deathCounted(ctx, clean, fromDraining) {
171
+ return ctx.demand && clean !== true && !fromDraining;
172
+ }
173
+ function fpOfVersion(engine, running, mine) {
174
+ const id = idOf(engine);
175
+ return JSON.stringify(['version', engine.pid, id.instanceId ?? null, id.startedAt ?? null, running, mine]);
176
+ }
177
+ function fpOfProcess(cause, p) {
178
+ return JSON.stringify([cause, p.pid, p.startedAt ?? null]);
179
+ }
180
+ const UNKNOWN_OCC = Object.freeze({
181
+ others: Object.freeze({ total: 0, midTurn: 0, known: false }),
182
+ inflight: 'unknown',
183
+ bgLive: 'unknown',
184
+ self: 'unknown',
185
+ bgIds: null,
186
+ });
187
+ function mintCard(hold, target, occ) {
188
+ const o = occ ?? hold.occupancy ?? UNKNOWN_OCC;
189
+ return engineSwapCardOf({
190
+ cause: hold.cause,
191
+ target: target ?? hold.target,
192
+ ...(hold.running !== undefined ? { running: hold.running } : {}),
193
+ ...(hold.own !== undefined ? { own: hold.own } : {}),
194
+ occupancy: o,
195
+ bgIds: own(o, 'bgIds') === undefined ? null : (o.bgIds ?? null),
196
+ });
197
+ }
198
+ const CARD_FAIL = Object.freeze({
199
+ version: 'local_engine.legacy_below_floor',
200
+ unresponsive: 'local_engine.unresponsive',
201
+ config: 'local_engine.swap_pending',
202
+ credential: 'local_engine.swap_pending',
203
+ 'model-default': 'local_engine.swap_pending',
204
+ user: 'local_engine.swap_pending',
205
+ });
206
+ const DOWN_FAIL = Object.freeze({
207
+ 'lock-unavailable': 'local_engine.lock_unavailable',
208
+ 'spawn-failed': 'local_engine.spawn_failed',
209
+ 'spawn-refused': 'local_engine.spawn_refused',
210
+ 'stop-failed': 'local_engine.stop_failed',
211
+ 'crash-loop': 'local_engine.crash_loop',
212
+ 'high-water-unreadable': 'local_engine.high_water_unreadable',
213
+ });
214
+ function showCard(p, hold, occ, fx, policy) {
215
+ if (headless(policy)) {
216
+ fx.push({ kind: 'fail', code: CARD_FAIL[hold.cause] });
217
+ p.card = 'hidden';
218
+ delete p.hold;
219
+ return;
220
+ }
221
+ const stored = { ...hold, occupancy: occ ?? hold.occupancy };
222
+ const card = mintCard(stored, p.target, occ);
223
+ if (card === undefined)
224
+ return;
225
+ fx.push({ kind: 'card', show: card });
226
+ p.card = 'shown';
227
+ p.hold = stored;
228
+ }
229
+ function hideCard(p, fx) {
230
+ fx.push({ kind: 'card', show: null });
231
+ p.card = 'hidden';
232
+ delete p.hold;
233
+ }
234
+ function pendingOf(target, serving, ctx, carry) {
235
+ const p = { kind: 'pending', target, serving, card: carry?.card ?? 'hidden', ctx };
236
+ if (carry?.relation !== undefined)
237
+ p.relation = carry.relation;
238
+ if (p.card === 'shown' && carry?.hold !== undefined)
239
+ p.hold = carry.hold;
240
+ else
241
+ p.card = 'hidden';
242
+ return p;
243
+ }
244
+ function refOf(v) {
245
+ const pid = own(v, 'pid');
246
+ const port = own(v, 'port');
247
+ const identity = own(v, 'identity');
248
+ if (!isPid(pid) || typeof port !== 'number' || !Number.isInteger(port) || port < 1 || port > 65535 || !isRecord(identity))
249
+ return undefined;
250
+ const version = own(v, 'version');
251
+ return { pid, port, identity: engineIdentityOf(identity), ...(typeof version === 'string' && version.length > 0 ? { version } : {}) };
252
+ }
253
+ function processOf(v) {
254
+ const pid = own(v, 'pid');
255
+ if (!isPid(pid))
256
+ return undefined;
257
+ const startedAt = own(v, 'startedAt');
258
+ return isTime(startedAt) && startedAt > 0 ? { pid, startedAt } : { pid };
259
+ }
260
+ function verdictOf(v) {
261
+ const kind = own(v, 'kind');
262
+ switch (kind) {
263
+ case 'none': {
264
+ const b = slotReadOf(v, 'blocked');
265
+ if (b.presence === 'absent' || (b.presence === 'present' && b.value === undefined))
266
+ return { kind };
267
+ return b.presence === 'present' && b.value === 'high-water-unreadable' ? { kind, blocked: b.value } : undefined;
268
+ }
269
+ case 'unanswered': {
270
+ const p = processOf(v);
271
+ const reason = own(v, 'reason');
272
+ if (p === undefined || (reason !== 'no-answer' && reason !== 'unverifiable'))
273
+ return undefined;
274
+ return { kind, ...p, reason };
275
+ }
276
+ case 'stranger': {
277
+ const mismatch = own(v, 'mismatch');
278
+ if (mismatch !== 'pid' && mismatch !== 'generation' && mismatch !== 'data-root')
279
+ return undefined;
280
+ const a = own(v, 'anchored');
281
+ const anchored = a === null || a === undefined ? null : processOf(a);
282
+ if (anchored === undefined)
283
+ return undefined;
284
+ return { kind, mismatch, anchored };
285
+ }
286
+ case 'draining': {
287
+ const engine = refOf(own(v, 'engine'));
288
+ const deadlineAt = own(v, 'deadlineAt');
289
+ if (engine === undefined || !isTime(deadlineAt))
290
+ return undefined;
291
+ return { kind, engine, deadlineAt };
292
+ }
293
+ case 'attach':
294
+ case 'version-unknown': {
295
+ const engine = refOf(own(v, 'engine'));
296
+ return engine === undefined ? undefined : { kind, engine };
297
+ }
298
+ case 'ahead': {
299
+ const engine = refOf(own(v, 'engine'));
300
+ const running = own(v, 'running');
301
+ const mine = own(v, 'own');
302
+ const servesWhileWaiting = own(v, 'servesWhileWaiting');
303
+ if (engine === undefined || typeof running !== 'string' || typeof mine !== 'string' || typeof servesWhileWaiting !== 'boolean')
304
+ return undefined;
305
+ return { kind, engine, running, own: mine, servesWhileWaiting };
306
+ }
307
+ case 'behind': {
308
+ const e = own(v, 'engine');
309
+ const engine = e === null ? null : refOf(e);
310
+ const running = own(v, 'running');
311
+ const mine = own(v, 'own');
312
+ const source = own(v, 'source');
313
+ if (engine === undefined || typeof running !== 'string' || typeof mine !== 'string' || (source !== 'running' && source !== 'high-water'))
314
+ return undefined;
315
+ return { kind, engine, running, own: mine, source };
316
+ }
317
+ default:
318
+ return undefined;
319
+ }
320
+ }
321
+ function verdictTarget(v) {
322
+ switch (v.kind) {
323
+ case 'draining':
324
+ case 'attach':
325
+ case 'version-unknown':
326
+ case 'ahead':
327
+ return v.engine;
328
+ case 'behind':
329
+ return v.engine ?? undefined;
330
+ case 'unanswered':
331
+ return v.reason === 'no-answer' && v.startedAt !== undefined ? { pid: v.pid, startedAt: v.startedAt } : undefined;
332
+ case 'stranger':
333
+ return v.mismatch === 'data-root' && v.anchored !== null ? v.anchored : undefined;
334
+ default:
335
+ return undefined;
336
+ }
337
+ }
338
+ function occOf(v) {
339
+ return isRecord(v) ? ownSnapshotTyped(v) : undefined;
340
+ }
341
+ function a8(v, fx, policy) {
342
+ fx.push({ kind: 'submits', open: false, sentence: 'behind' }, { kind: 'card', show: null }, { kind: 'banner', key: null });
343
+ if (headless(policy))
344
+ fx.push({ kind: 'fail', code: 'local_engine.behind' });
345
+ else
346
+ fx.push({ kind: 'hand-off' }, { kind: 'prompt-upgrade', running: v.running, own: v.own, source: v.source });
347
+ return { state: { kind: 'behind', running: v.running, own: v.own, source: v.source }, effects: fx };
348
+ }
349
+ function aRows(from, v, ctx0, occ, nowMs, policy, fx) {
350
+ let ctx = ctx0;
351
+ switch (v.kind) {
352
+ case 'none': {
353
+ if (v.blocked === 'high-water-unreadable') {
354
+ fx.push({ kind: 'submits', open: false }, { kind: 'notice', notice: { key: 'highWaterUnreadable' } });
355
+ return { state: { kind: 'down', reason: 'high-water-unreadable', since: nowMs, ctx }, effects: fx };
356
+ }
357
+ if (holdsInstance(from)) {
358
+ const d = deathOf(ctx, heldInstance(from), from.kind === 'draining-wait' ? true : 'unknown', from.kind === 'draining-wait', nowMs);
359
+ ctx = d.ctx;
360
+ fx.push(d.notice);
361
+ }
362
+ if (ctx.deaths.length > T.crashMaxDeaths) {
363
+ fx.push({ kind: 'submits', open: false }, { kind: 'notice', notice: { key: 'crashLoop', retryInMs: T.cooldownMs[ctx.cooldown] ?? null } });
364
+ return { state: { kind: 'down', reason: 'crash-loop', since: nowMs, ctx }, effects: fx };
365
+ }
366
+ const n = nextOp(ctx);
367
+ fx.push({ kind: 'acquire-lock', op: n.op }, { kind: 'submits', open: false });
368
+ return { state: { kind: 'locking', intent: { kind: 'spawn' }, held: false, entry: null, op: n.op, since: nowMs, plan: null, ctx: n.ctx }, effects: fx };
369
+ }
370
+ case 'stranger': {
371
+ if (v.mismatch === 'data-root' && v.anchored !== null) {
372
+ const target = v.anchored;
373
+ const p = pendingOf(target, false, { ...ctx, causes: setCause(ctx.causes, 'config', fpOfProcess('data-root', target)) });
374
+ fx.push({ kind: 'submits', open: false });
375
+ showCard(p, { cause: 'config', target, occupancy: occ ?? null }, occ, fx, policy);
376
+ return { state: p, effects: fx };
377
+ }
378
+ const n = nextOp(ctx);
379
+ fx.push({ kind: 'acquire-lock', op: n.op }, { kind: 'submits', open: false });
380
+ return { state: { kind: 'locking', intent: { kind: 'spawn' }, held: false, entry: null, op: n.op, since: nowMs, plan: null, ctx: n.ctx }, effects: fx };
381
+ }
382
+ case 'unanswered': {
383
+ if (from.kind === 'unanswered' && from.pid === v.pid && from.startedAt === v.startedAt && from.reason === v.reason) {
384
+ return { state: { ...from, ctx }, effects: fx };
385
+ }
386
+ fx.push({ kind: 'submits', open: false });
387
+ if (v.reason === 'unverifiable' && headless(policy))
388
+ fx.push({ kind: 'fail', code: 'local_engine.unanswered_unverifiable' });
389
+ const s = { kind: 'unanswered', pid: v.pid, reason: v.reason, since: nowMs, ctx };
390
+ if (v.startedAt !== undefined)
391
+ s.startedAt = v.startedAt;
392
+ return { state: s, effects: fx };
393
+ }
394
+ case 'draining': {
395
+ if (from.kind === 'draining-wait' && sameTarget(from.engine, v.engine) && from.deadlineAt === v.deadlineAt)
396
+ return { state: { ...from, ctx }, effects: fx };
397
+ if (!(from.kind === 'draining-wait' && sameTarget(from.engine, v.engine)))
398
+ fx.push({ kind: 'submits', open: false });
399
+ return { state: { kind: 'draining-wait', engine: v.engine, deadlineAt: v.deadlineAt, overdue: false, ctx }, effects: fx };
400
+ }
401
+ case 'attach':
402
+ case 'version-unknown': {
403
+ const causes = dropCause(ctx.causes, 'version');
404
+ const prev = attachedRef(from);
405
+ const sameInst = prev !== undefined && sameTarget(prev, v.engine);
406
+ if (from.kind === 'attached' && sameInst && from.relation === v.kind && causes.length === 0)
407
+ return { state: { ...from, ctx }, effects: fx };
408
+ if (from.kind === 'pending' && from.serving && sameInst && causes.length === from.ctx.causes.length && causes.every((c, i) => c.cause === from.ctx.causes[i]?.cause)) {
409
+ return { state: { ...from, ctx }, effects: fx };
410
+ }
411
+ ctx = { ...ctx, causes, lockRetry: 0 };
412
+ if (!sameInst) {
413
+ fx.push({ kind: 'attach', engine: v.engine });
414
+ if (ctx.attached === null || engineIdentityVerdict(ctx.attached.instance, idOf(v.engine)) !== 'same')
415
+ ctx = { ...ctx, attached: { instance: idOf(v.engine), at: nowMs } };
416
+ }
417
+ fx.push({ kind: 'submits', open: true }, { kind: 'card', show: null }, { kind: 'banner', key: causes.length > 0 ? 'pending' : null });
418
+ const prevRelation = from.kind === 'attached' ? from.relation : from.kind === 'pending' ? from.relation : undefined;
419
+ if (v.kind === 'version-unknown' && (!sameInst || prevRelation !== 'version-unknown'))
420
+ fx.push({ kind: 'notice', notice: { key: 'versionUnknown' } });
421
+ if (causes.length > 0)
422
+ return { state: pendingOf(v.engine, true, ctx, { relation: v.kind }), effects: fx };
423
+ const since = from.kind === 'attached' && sameInst ? from.since : nowMs;
424
+ return { state: { kind: 'attached', engine: v.engine, relation: v.kind, since, ctx }, effects: fx };
425
+ }
426
+ case 'ahead': {
427
+ const fp = fpOfVersion(v.engine, v.running, v.own);
428
+ const cardShownServing = from.kind === 'pending' && from.serving && from.card === 'shown';
429
+ const othersAutoable = ctx.causes.filter((c) => c.cause !== 'version').every((c) => localEngineSwapAutoable(c.cause, true, { credentialAuto: own(policy, 'credentialAuto') === true }));
430
+ if (v.servesWhileWaiting && stoppable(v.engine) && idle(occ) && !cardShownServing && !tried(ctx, 'version', v.engine) && othersAutoable && interactive(policy)) {
431
+ const n = nextOp({ ...ctx, causes: setCause(ctx.causes, 'version', fp), autoTried: addTried(ctx, 'version', v.engine) });
432
+ fx.push({ kind: 'acquire-lock', op: n.op }, { kind: 'submits', open: false });
433
+ return {
434
+ state: { kind: 'locking', intent: { kind: 'stop', mode: 'idle', serving: true }, held: false, entry: v.engine, op: n.op, since: nowMs, plan: null, ctx: n.ctx },
435
+ effects: fx,
436
+ };
437
+ }
438
+ if (from.kind === 'pending' && sameTarget(from.target, v.engine) && from.serving === v.servesWhileWaiting)
439
+ return { state: { ...from, ctx }, effects: fx };
440
+ ctx = { ...ctx, causes: setCause(ctx.causes, 'version', fp) };
441
+ if (v.servesWhileWaiting) {
442
+ ctx = { ...ctx, lockRetry: 0 };
443
+ if (ctx.attached === null || engineIdentityVerdict(ctx.attached.instance, idOf(v.engine)) !== 'same')
444
+ ctx = { ...ctx, attached: { instance: idOf(v.engine), at: nowMs } };
445
+ fx.push({ kind: 'attach', engine: v.engine }, { kind: 'submits', open: true }, { kind: 'banner', key: 'pending' });
446
+ const carry = from.kind === 'pending' ? { card: from.card, hold: from.hold } : {};
447
+ return { state: pendingOf(v.engine, true, ctx, carry), effects: fx };
448
+ }
449
+ const p = pendingOf(v.engine, false, ctx);
450
+ fx.push({ kind: 'submits', open: false, sentence: 'legacyBelowFloor' });
451
+ showCard(p, { cause: 'version', target: v.engine, running: v.running, own: v.own, occupancy: occ ?? null }, occ, fx, policy);
452
+ return { state: p, effects: fx };
453
+ }
454
+ case 'behind':
455
+ return a8(v, fx, policy);
456
+ }
457
+ }
458
+ function a0(from, v, ctx) {
459
+ if (from.kind === 'unbound')
460
+ return { state: { kind: 'unbound', ctx }, effects: [] };
461
+ const cleared = holdsInstance(from) && v.kind === 'none' ? { ...ctx, causes: [], autoTried: [] } : ctx;
462
+ return { state: { kind: 'unbound', ctx: cleared }, effects: [{ kind: 'submits', open: false }, { kind: 'card', show: null }, { kind: 'banner', key: null }] };
463
+ }
464
+ function onObserved(s, ev, policy) {
465
+ const { verdict: v, nowMs, demand } = ev;
466
+ const occ = ev.occupancy;
467
+ switch (s.kind) {
468
+ case 'unbound':
469
+ case 'attached':
470
+ case 'pending':
471
+ case 'draining-wait':
472
+ case 'unanswered': {
473
+ const ctx = { ...s.ctx, demand };
474
+ if (!demand)
475
+ return a0(s, v, ctx);
476
+ return aRows(s, v, ctx, occ, nowMs, policy, []);
477
+ }
478
+ case 'down': {
479
+ if (!demand)
480
+ return undefined;
481
+ const ctx = { ...s.ctx, demand };
482
+ if (v.kind === 'attach' || v.kind === 'version-unknown' || v.kind === 'behind')
483
+ return aRows(s, v, ctx, occ, nowMs, policy, []);
484
+ if (v.kind === 'ahead')
485
+ return aRows(s, v, { ...ctx, autoTried: addTried(ctx, 'version', v.engine) }, occ, nowMs, policy, []);
486
+ if (v.kind === 'none' && v.blocked === undefined && s.reason === 'lock-unavailable' && s.ctx.lockRetry === 0 && s.ctx.deaths.length <= T.crashMaxDeaths) {
487
+ const n = nextOp({ ...ctx, lockRetry: 1 });
488
+ return {
489
+ state: { kind: 'locking', intent: { kind: 'spawn' }, held: false, entry: null, op: n.op, since: nowMs, plan: null, ctx: n.ctx },
490
+ effects: [{ kind: 'acquire-lock', op: n.op }],
491
+ };
492
+ }
493
+ return undefined;
494
+ }
495
+ case 'locking': {
496
+ if (s.held)
497
+ return undefined;
498
+ if (!demand) {
499
+ const ctx = { ...s.ctx, demand };
500
+ return { state: { kind: 'unbound', ctx }, effects: [{ kind: 'cancel-lock', op: s.op }, { kind: 'submits', open: false }, { kind: 'card', show: null }, { kind: 'banner', key: null }] };
501
+ }
502
+ const ctx = { ...s.ctx, demand };
503
+ if (s.intent.kind === 'spawn') {
504
+ if (v.kind === 'attach' || v.kind === 'version-unknown' || v.kind === 'ahead' || v.kind === 'behind' || v.kind === 'draining') {
505
+ return aRows(s, v, ctx, occ, nowMs, policy, [{ kind: 'cancel-lock', op: s.op }]);
506
+ }
507
+ return undefined;
508
+ }
509
+ const t = verdictTarget(v);
510
+ const elsewhere = (v.kind === 'attach' || v.kind === 'version-unknown' || v.kind === 'ahead' || v.kind === 'draining') && !sameTarget(t, s.entry);
511
+ if (v.kind === 'behind' || elsewhere)
512
+ return aRows(s, v, ctx, occ, nowMs, policy, [{ kind: 'cancel-lock', op: s.op }]);
513
+ return undefined;
514
+ }
515
+ default:
516
+ return undefined;
517
+ }
518
+ }
519
+ function onRelooked(s, ev, policy) {
520
+ if (s.kind !== 'locking' || !s.held || s.op !== ev.op || s.plan !== null)
521
+ return undefined;
522
+ const { verdict: v, nowMs } = ev;
523
+ const occ = ev.occupancy;
524
+ const rel = [{ kind: 'release-lock', op: s.op }];
525
+ if (v.kind === 'behind')
526
+ return a8(v, rel, policy);
527
+ if (v.kind === 'draining')
528
+ return { state: { kind: 'draining-wait', engine: v.engine, deadlineAt: v.deadlineAt, overdue: false, ctx: s.ctx }, effects: rel };
529
+ const intent = s.intent;
530
+ const t = verdictTarget(v);
531
+ if (intent.kind === 'stop' && (v.kind === 'attach' || v.kind === 'version-unknown') && sameTarget(t, s.entry)) {
532
+ const causes = dropCause(s.ctx.causes, 'version');
533
+ if (causes.length === 0)
534
+ return aRows(s, v, { ...s.ctx, causes }, occ, nowMs, policy, rel);
535
+ }
536
+ const plan = (p, ctx) => ({ state: { ...s, plan: p, since: nowMs, ctx }, effects: [{ kind: 'prepare', plan: p, op: s.op }] });
537
+ if (intent.kind === 'spawn' && ((v.kind === 'none' && v.blocked === undefined) || (v.kind === 'stranger' && v.anchored === null)))
538
+ return plan({ kind: 'spawn' }, s.ctx);
539
+ if (intent.kind === 'stop' && v.kind === 'none' && v.blocked === undefined)
540
+ return plan({ kind: 'spawn' }, s.ctx);
541
+ if (intent.kind === 'stop' && t !== undefined && stoppable(t) && sameGeneration(t, s.entry)) {
542
+ const causes = v.kind === 'attach' || v.kind === 'version-unknown' ? dropCause(s.ctx.causes, 'version') : s.ctx.causes;
543
+ const ctx = { ...s.ctx, causes };
544
+ if (causes.length > 0 && (intent.mode === 'forced' || idle(occ)))
545
+ return plan({ kind: 'stop', target: t, mode: intent.mode }, ctx);
546
+ if (intent.mode === 'idle' && causes.length > 0) {
547
+ const serving = v.kind === 'attach' || v.kind === 'version-unknown' ? true : v.kind === 'ahead' ? v.servesWhileWaiting : false;
548
+ const fx = [...rel, { kind: 'banner', key: 'pending' }];
549
+ if (serving)
550
+ fx.push({ kind: 'submits', open: true });
551
+ return { state: pendingOf(t, serving, ctx), effects: fx };
552
+ }
553
+ }
554
+ return aRows(s, v, s.ctx, occ, nowMs, policy, rel);
555
+ }
556
+ function lockUnavailable(s, nowMs, fx) {
557
+ fx.push({ kind: 'notice', notice: { key: 'lockUnavailable' } });
558
+ if (s.intent.kind === 'stop') {
559
+ fx.push({ kind: 'banner', key: 'pending' });
560
+ if (s.intent.serving)
561
+ fx.push({ kind: 'submits', open: true });
562
+ return { state: pendingOf(s.entry, s.intent.serving, s.ctx), effects: fx };
563
+ }
564
+ return { state: { kind: 'down', reason: 'lock-unavailable', since: nowMs, ctx: s.ctx }, effects: fx };
565
+ }
566
+ function prepareFailed(s, plan, reason, nowMs) {
567
+ const fx = [{ kind: 'release-lock', op: s.op }, { kind: 'notice', notice: { key: 'prepareFailed', reason } }];
568
+ if (plan.kind === 'stop') {
569
+ const serving = s.intent.kind === 'stop' && s.intent.serving;
570
+ if (serving)
571
+ fx.push({ kind: 'submits', open: true });
572
+ return { state: pendingOf(plan.target, serving, s.ctx), effects: fx };
573
+ }
574
+ return { state: { kind: 'down', reason: 'spawn-refused', since: nowMs, ctx: s.ctx }, effects: fx };
575
+ }
576
+ function onTick(s, ev, policy) {
577
+ const { nowMs } = ev;
578
+ const occ = ev.occupancy;
579
+ switch (s.kind) {
580
+ case 'pending': {
581
+ if (!stoppable(s.target) || !idle(occ) || (s.serving && s.card === 'shown') || !interactive(policy) || s.ctx.causes.length === 0)
582
+ return undefined;
583
+ const target = s.target;
584
+ const credentialAuto = own(policy, 'credentialAuto') === true;
585
+ if (!s.ctx.causes.every((c) => localEngineSwapAutoable(c.cause, s.serving, { credentialAuto })))
586
+ return undefined;
587
+ const fresh = s.ctx.causes.filter((c) => !tried(s.ctx, c.cause, target));
588
+ if (fresh.length === 0)
589
+ return undefined;
590
+ let ctx = s.ctx;
591
+ for (const c of fresh)
592
+ ctx = { ...ctx, autoTried: addTried(ctx, c.cause, target) };
593
+ const n = nextOp(ctx);
594
+ return {
595
+ state: { kind: 'locking', intent: { kind: 'stop', mode: 'idle', serving: s.serving }, held: false, entry: target, op: n.op, since: nowMs, plan: null, ctx: n.ctx },
596
+ effects: [{ kind: 'acquire-lock', op: n.op }, { kind: 'submits', open: false }],
597
+ };
598
+ }
599
+ case 'draining-wait': {
600
+ if (s.overdue || nowMs < s.deadlineAt + LOCAL_ENGINE_STOP_LADDER.H)
601
+ return undefined;
602
+ const fx = headless(policy) ? [{ kind: 'fail', code: 'local_engine.drain_overdue' }] : [{ kind: 'notice', notice: { key: 'drainOverdue' } }];
603
+ return { state: { ...s, overdue: true }, effects: fx };
604
+ }
605
+ case 'unanswered': {
606
+ if (s.reason !== 'no-answer' || nowMs < s.since + T.unansweredWaitMs || causeFp(s.ctx.causes, 'unresponsive') !== undefined)
607
+ return undefined;
608
+ const target = s.startedAt !== undefined ? { pid: s.pid, startedAt: s.startedAt } : { pid: s.pid };
609
+ const p = pendingOf(target, false, { ...s.ctx, causes: setCause(s.ctx.causes, 'unresponsive', fpOfProcess('unresponsive', target)) });
610
+ const fx = [];
611
+ showCard(p, { cause: 'unresponsive', target, occupancy: occ ?? null }, occ, fx, policy);
612
+ return { state: p, effects: fx };
613
+ }
614
+ case 'down': {
615
+ if (s.reason !== 'crash-loop')
616
+ return undefined;
617
+ const wait = T.cooldownMs[s.ctx.cooldown];
618
+ if (wait === undefined || nowMs < s.since + wait)
619
+ return undefined;
620
+ return {
621
+ state: { kind: 'unbound', ctx: { ...s.ctx, cooldown: s.ctx.cooldown + 1, deaths: [] } },
622
+ effects: [{ kind: 'reread' }, { kind: 'notice', notice: { key: 'crashLoopRetry' } }],
623
+ };
624
+ }
625
+ case 'locking': {
626
+ if (!s.held) {
627
+ const budget = own(own(policy, 'budgets'), 'lockWaitMs');
628
+ if (!isTime(budget) || nowMs < s.since + budget)
629
+ return undefined;
630
+ return lockUnavailable(s, nowMs, [{ kind: 'cancel-lock', op: s.op }]);
631
+ }
632
+ if (nowMs < s.since + T.prepareTimeoutMs)
633
+ return undefined;
634
+ if (s.plan === null)
635
+ return { state: { kind: 'unbound', ctx: s.ctx }, effects: [{ kind: 'release-lock', op: s.op }, { kind: 'reread' }] };
636
+ return prepareFailed(s, s.plan, 'timeout', nowMs);
637
+ }
638
+ case 'stopping': {
639
+ const alive = ev.alive === 'alive' || ev.alive === 'gone' ? ev.alive : 'unknown';
640
+ if (alive === 'gone')
641
+ return toSpawning(s, nowMs);
642
+ const r = stopLadderStep(s.ladder, { nowMs, alive, health: ev.health === undefined ? null : ev.health });
643
+ if (r.action === 'gave-up') {
644
+ return { state: { kind: 'down', reason: 'stop-failed', since: nowMs, ctx: s.ctx }, effects: [{ kind: 'release-lock', op: s.op }, { kind: 'notice', notice: { key: 'stopFailed' } }] };
645
+ }
646
+ if (r.action === 'done')
647
+ return toSpawning(s, nowMs);
648
+ const fx = [];
649
+ if (r.action === 'term-2' || r.action === 'term')
650
+ fx.push(signalOf(s.target, 'term'));
651
+ else if (r.action === 'kill')
652
+ fx.push(signalOf(s.target, 'kill'));
653
+ return { state: { ...s, ladder: r.state }, effects: fx };
654
+ }
655
+ case 'spawning': {
656
+ const budget = own(own(policy, 'budgets'), 'healthWaitBudgetMs');
657
+ if (!isTime(budget) || nowMs < s.since + budget)
658
+ return undefined;
659
+ return {
660
+ state: { kind: 'down', reason: 'spawn-failed', since: nowMs, ctx: s.ctx },
661
+ effects: [{ kind: 'abandon-spawn', op: s.op }, { kind: 'release-lock', op: s.op }, { kind: 'notice', notice: { key: 'spawnFailed', timeout: true } }],
662
+ };
663
+ }
664
+ default:
665
+ return undefined;
666
+ }
667
+ }
668
+ function signalOf(t, sig) {
669
+ const startedAt = isRef(t) ? t.identity.startedAt : t.startedAt;
670
+ return startedAt !== undefined ? { kind: 'signal', pid: t.pid, startedAt, sig } : { kind: 'signal', pid: t.pid, sig };
671
+ }
672
+ function toSpawning(s, nowMs) {
673
+ return { state: { kind: 'spawning', forced: s.mode === 'forced', deferred: [], op: s.op, since: nowMs, ctx: s.ctx }, effects: [{ kind: 'spawn', op: s.op }] };
674
+ }
675
+ function onCause(s, ev, policy) {
676
+ const { cause: c, fingerprint: fp, nowMs } = ev;
677
+ const occ = ev.occupancy;
678
+ if (s.kind !== 'behind' && causeFp(s.ctx.causes, c) === fp)
679
+ return undefined;
680
+ switch (s.kind) {
681
+ case 'draining-wait':
682
+ return { state: { ...s, ctx: { ...s.ctx, causes: setCause(s.ctx.causes, c, fp) } }, effects: [{ kind: 'banner', key: 'pending' }] };
683
+ case 'attached':
684
+ case 'pending':
685
+ case 'unanswered': {
686
+ const target = s.kind === 'attached'
687
+ ? s.engine
688
+ : s.kind === 'pending'
689
+ ? s.target
690
+ : s.kind === 'unanswered' && s.reason === 'no-answer' && s.startedAt !== undefined
691
+ ? { pid: s.pid, startedAt: s.startedAt }
692
+ : null;
693
+ const display = s.kind === 'attached'
694
+ ? s.engine
695
+ : s.kind === 'pending'
696
+ ? (s.target ?? s.hold?.target)
697
+ : { pid: s.pid, ...(s.startedAt !== undefined ? { startedAt: s.startedAt } : {}) };
698
+ if (c === 'user' && stoppable(target) && idle(occ) && interactive(policy)) {
699
+ const serving = s.kind === 'attached' || (s.kind === 'pending' && s.serving);
700
+ const n = nextOp({ ...s.ctx, causes: setCause(s.ctx.causes, 'user', fp), autoTried: addTried(s.ctx, 'user', target) });
701
+ return {
702
+ state: { kind: 'locking', intent: { kind: 'stop', mode: 'idle', serving }, held: false, entry: target, op: n.op, since: nowMs, plan: null, ctx: n.ctx },
703
+ effects: [{ kind: 'acquire-lock', op: n.op }, { kind: 'submits', open: false }],
704
+ };
705
+ }
706
+ const serving = s.kind === 'attached' || (s.kind === 'pending' && s.serving);
707
+ const relation = s.kind === 'attached' ? s.relation : s.kind === 'pending' ? s.relation : undefined;
708
+ const carry = s.kind === 'pending' ? { relation, card: s.card, hold: s.hold } : { relation };
709
+ const p = pendingOf(target, serving, { ...s.ctx, causes: setCause(s.ctx.causes, c, fp) }, carry);
710
+ const fx = [];
711
+ if ((c === 'unresponsive' || c === 'user') && display !== undefined) {
712
+ const hold = {
713
+ cause: c,
714
+ target: display,
715
+ ...(s.kind === 'pending' && s.hold?.running !== undefined ? { running: s.hold.running } : {}),
716
+ ...(s.kind === 'pending' && s.hold?.own !== undefined ? { own: s.hold.own } : {}),
717
+ occupancy: occ ?? null,
718
+ };
719
+ showCard(p, hold, occ, fx, policy);
720
+ }
721
+ else if (c !== 'unresponsive' && c !== 'user')
722
+ fx.push({ kind: 'banner', key: 'pending' });
723
+ return { state: p, effects: fx };
724
+ }
725
+ case 'down':
726
+ if (c !== 'user')
727
+ return undefined;
728
+ return { state: { kind: 'unbound', ctx: { ...s.ctx, deaths: [], cooldown: 0, lockRetry: 0 } }, effects: [{ kind: 'reread' }] };
729
+ case 'locking':
730
+ case 'stopping':
731
+ return { state: { ...s, ctx: { ...s.ctx, causes: setCause(s.ctx.causes, c, fp) } }, effects: [] };
732
+ case 'spawning':
733
+ if (causeFp(s.deferred, c) === fp)
734
+ return undefined;
735
+ return { state: { ...s, deferred: setCause(s.deferred, c, fp) }, effects: [] };
736
+ default:
737
+ return undefined;
738
+ }
739
+ }
740
+ function onCauseResolved(s, ev) {
741
+ const { cause: c, fingerprint: fp, nowMs } = ev;
742
+ switch (s.kind) {
743
+ case 'pending': {
744
+ if (causeFp(s.ctx.causes, c) !== fp)
745
+ return undefined;
746
+ const causes = dropCause(s.ctx.causes, c);
747
+ const ctx = { ...s.ctx, causes };
748
+ if (causes.length === 0) {
749
+ const fx = [{ kind: 'card', show: null }, { kind: 'banner', key: null }];
750
+ if (s.serving && s.target !== null && isRef(s.target))
751
+ return { state: { kind: 'attached', engine: s.target, relation: s.relation ?? 'attach', since: nowMs, ctx }, effects: fx };
752
+ return { state: pendingOf(s.target, s.serving, ctx, { relation: s.relation }), effects: fx };
753
+ }
754
+ return { state: pendingOf(s.target, s.serving, ctx, { relation: s.relation, card: s.card, hold: s.hold }), effects: [{ kind: 'banner', key: 'pending' }] };
755
+ }
756
+ case 'locking':
757
+ case 'stopping':
758
+ if (causeFp(s.ctx.causes, c) !== fp)
759
+ return undefined;
760
+ return { state: { ...s, ctx: { ...s.ctx, causes: dropCause(s.ctx.causes, c) } }, effects: [] };
761
+ case 'spawning': {
762
+ const inCauses = causeFp(s.ctx.causes, c) === fp;
763
+ const inDeferred = causeFp(s.deferred, c) === fp;
764
+ if (!inCauses && !inDeferred)
765
+ return undefined;
766
+ return { state: { ...s, deferred: inDeferred ? dropCause(s.deferred, c) : s.deferred, ctx: inCauses ? { ...s.ctx, causes: dropCause(s.ctx.causes, c) } : s.ctx }, effects: [] };
767
+ }
768
+ default:
769
+ return undefined;
770
+ }
771
+ }
772
+ export function localEngineCurrentCard(s) {
773
+ try {
774
+ if (s.kind !== 'pending' || s.card !== 'shown' || s.hold === undefined)
775
+ return undefined;
776
+ return mintCard(s.hold, s.target, undefined);
777
+ }
778
+ catch {
779
+ return undefined;
780
+ }
781
+ }
782
+ function answerRowOf(s, ev, policy) {
783
+ if (s.kind !== 'pending' || s.card !== 'shown')
784
+ return null;
785
+ if (ev.choice === 'wait')
786
+ return { row: 'B5' };
787
+ const first = s.ctx.causes[0];
788
+ const hold = s.hold ?? (first !== undefined && s.target !== null ? { cause: first.cause, target: s.target, occupancy: null } : undefined);
789
+ const current = hold === undefined ? undefined : mintCard(hold, s.target, ev.occupancy);
790
+ const matches = current !== undefined && current.fingerprint === ev.fingerprint && interactive(policy);
791
+ if (!matches)
792
+ return { row: 'B4', current };
793
+ return { row: stoppable(s.target) ? 'B3' : 'B3p', current };
794
+ }
795
+ function onAnswer(s, ev, policy) {
796
+ const r = answerRowOf(s, ev, policy);
797
+ if (r === null || s.kind !== 'pending')
798
+ return undefined;
799
+ const p = pendingOf(s.target, s.serving, s.ctx, { relation: s.relation, card: s.card, hold: s.hold });
800
+ const fx = [];
801
+ if (r.row === 'B5') {
802
+ hideCard(p, fx);
803
+ fx.push({ kind: 'banner', key: 'pending' });
804
+ return { state: p, effects: fx };
805
+ }
806
+ if (r.row === 'B3' && stoppable(s.target)) {
807
+ const n = nextOp(s.ctx);
808
+ return {
809
+ state: { kind: 'locking', intent: { kind: 'stop', mode: 'forced', serving: s.serving }, held: false, entry: s.target, op: n.op, since: ev.nowMs, plan: null, ctx: n.ctx },
810
+ effects: [{ kind: 'card', show: null }, { kind: 'acquire-lock', op: n.op }, { kind: 'submits', open: false }],
811
+ };
812
+ }
813
+ if (r.row === 'B3p') {
814
+ hideCard(p, fx);
815
+ fx.push({ kind: 'notice', notice: { key: 'cannotVerifyEngine' } });
816
+ return { state: p, effects: fx };
817
+ }
818
+ const hold = s.hold ?? (r.current !== undefined ? { cause: r.current.cause, target: r.current.target, occupancy: ev.occupancy ?? null } : undefined);
819
+ if (hold === undefined) {
820
+ hideCard(p, fx);
821
+ return { state: p, effects: fx };
822
+ }
823
+ showCard(p, { ...hold, occupancy: ev.occupancy ?? hold.occupancy }, ev.occupancy, fx, policy);
824
+ return { state: p, effects: fx };
825
+ }
826
+ function onGone(s, ev) {
827
+ const { instance, clean, nowMs } = ev;
828
+ if (s.kind === 'stopping') {
829
+ if (engineIdentityVerdict(idOf(s.target), instance) !== 'same')
830
+ return undefined;
831
+ return toSpawning(s, nowMs);
832
+ }
833
+ if (s.kind !== 'attached' && s.kind !== 'pending' && s.kind !== 'draining-wait' && s.kind !== 'unanswered')
834
+ return undefined;
835
+ const mine = heldInstance(s);
836
+ if (mine === undefined || engineIdentityVerdict(mine, instance) !== 'same')
837
+ return undefined;
838
+ const d = deathOf(s.ctx, mine, clean, s.kind === 'draining-wait', nowMs);
839
+ return { state: { kind: 'unbound', ctx: d.ctx }, effects: [{ kind: 'reread' }, { kind: 'submits', open: false }, d.notice] };
840
+ }
841
+ function onOpEvent(s, ev, policy) {
842
+ const lstate = s.kind === 'locking' || s.kind === 'stopping' || s.kind === 'spawning' ? s : undefined;
843
+ const op = ev.op;
844
+ if (lstate === undefined || lstate.op !== op) {
845
+ if (ev.kind === 'lock' && ev.held)
846
+ return { state: s, effects: [{ kind: 'release-lock', op }] };
847
+ return undefined;
848
+ }
849
+ switch (ev.kind) {
850
+ case 'lock':
851
+ if (lstate.kind !== 'locking' || lstate.held)
852
+ return undefined;
853
+ if (ev.held)
854
+ return { state: { ...lstate, held: true, since: ev.nowMs }, effects: [{ kind: 'reread' }] };
855
+ return lockUnavailable(lstate, ev.nowMs, []);
856
+ case 'lock-lost': {
857
+ if (lstate.kind === 'locking' && !lstate.held)
858
+ return undefined;
859
+ const fx = [{ kind: 'reread' }, { kind: 'notice', notice: { key: 'lockLost' } }];
860
+ if (lstate.kind === 'spawning')
861
+ fx.push({ kind: 'abandon-spawn', op: lstate.op });
862
+ return { state: { kind: 'unbound', ctx: lstate.ctx }, effects: fx };
863
+ }
864
+ case 'relooked':
865
+ return onRelooked(s, ev, policy);
866
+ case 'prepared': {
867
+ if (lstate.kind !== 'locking' || !lstate.held || lstate.plan === null)
868
+ return undefined;
869
+ const plan = lstate.plan;
870
+ if (!ev.ok)
871
+ return prepareFailed(lstate, plan, typeof ev.reason === 'string' && ev.reason.length > 0 ? ev.reason : 'unknown', ev.nowMs);
872
+ if (plan.kind === 'spawn')
873
+ return { state: { kind: 'spawning', forced: false, deferred: [], op: lstate.op, since: ev.nowMs, ctx: lstate.ctx }, effects: [{ kind: 'spawn', op: lstate.op }] };
874
+ if (lstate.ctx.causes.length === 0 || (plan.mode === 'idle' && !idle(ev.occupancy))) {
875
+ return { state: { kind: 'unbound', ctx: lstate.ctx }, effects: [{ kind: 'release-lock', op: lstate.op }, { kind: 'reread' }] };
876
+ }
877
+ const t = plan.target;
878
+ const startedAt = isRef(t) ? t.identity.startedAt : t.startedAt;
879
+ const ladder0 = { mode: plan.mode, target: startedAt !== undefined ? { pid: t.pid, startedAt } : { pid: t.pid }, t0: ev.nowMs, sent: [] };
880
+ const r = stopLadderStep(ladder0, { nowMs: ev.nowMs, alive: 'alive', health: null });
881
+ return { state: { kind: 'stopping', target: t, mode: plan.mode, ladder: r.state, op: lstate.op, ctx: lstate.ctx }, effects: r.action === 'term' ? [signalOf(t, 'term')] : [] };
882
+ }
883
+ case 'spawned': {
884
+ if (lstate.kind !== 'spawning')
885
+ return undefined;
886
+ const v = ev.verdict;
887
+ let ctx = { ...lstate.ctx, causes: [...lstate.deferred], autoTried: [] };
888
+ if (v.kind === 'ahead')
889
+ ctx = { ...ctx, autoTried: addTried(ctx, 'version', v.engine) };
890
+ const out = aRows(s, v, ctx, undefined, ev.nowMs, policy, [{ kind: 'release-lock', op: lstate.op }]);
891
+ if (lstate.forced)
892
+ out.effects.push({ kind: 'notice', notice: { key: 'forcedLine' } });
893
+ return out;
894
+ }
895
+ case 'spawn-failed':
896
+ if (lstate.kind !== 'spawning')
897
+ return undefined;
898
+ return {
899
+ state: { kind: 'down', reason: 'spawn-failed', since: ev.nowMs, ctx: lstate.ctx },
900
+ effects: [{ kind: 'release-lock', op: lstate.op }, { kind: 'notice', notice: { key: 'spawnFailed', timeout: false } }],
901
+ };
902
+ default:
903
+ return undefined;
904
+ }
905
+ }
906
+ function eventOf(e) {
907
+ const kind = own(e, 'kind');
908
+ const nowMs = own(e, 'nowMs');
909
+ if (!isTime(nowMs))
910
+ return undefined;
911
+ const occRaw = own(e, 'occupancy');
912
+ const occupancy = occOf(occRaw);
913
+ const withOcc = occupancy !== undefined ? { occupancy } : {};
914
+ const op = own(e, 'op');
915
+ switch (kind) {
916
+ case 'observed': {
917
+ const verdict = verdictOf(own(e, 'verdict'));
918
+ if (verdict === undefined)
919
+ return undefined;
920
+ return { kind, verdict, demand: own(e, 'demand') === true, nowMs, ...withOcc };
921
+ }
922
+ case 'relooked': {
923
+ const verdict = verdictOf(own(e, 'verdict'));
924
+ if (verdict === undefined || !isOp(op))
925
+ return undefined;
926
+ return { kind, verdict, nowMs, op, ...withOcc };
927
+ }
928
+ case 'lock': {
929
+ const held = own(e, 'held');
930
+ if (typeof held !== 'boolean' || !isOp(op))
931
+ return undefined;
932
+ return { kind, held, op, nowMs };
933
+ }
934
+ case 'lock-lost':
935
+ return isOp(op) ? { kind, op, nowMs } : undefined;
936
+ case 'prepared': {
937
+ const ok = own(e, 'ok');
938
+ const reason = own(e, 'reason');
939
+ if (typeof ok !== 'boolean' || !isOp(op))
940
+ return undefined;
941
+ return { kind, ok, nowMs, op, ...withOcc, ...(typeof reason === 'string' ? { reason } : {}) };
942
+ }
943
+ case 'tick': {
944
+ const alive = own(e, 'alive');
945
+ const health = own(e, 'health');
946
+ return {
947
+ kind,
948
+ nowMs,
949
+ ...withOcc,
950
+ ...(alive === 'alive' || alive === 'gone' || alive === 'unknown' ? { alive } : {}),
951
+ ...(typeof health === 'object' ? { health: health } : {}),
952
+ };
953
+ }
954
+ case 'cause':
955
+ case 'cause-resolved': {
956
+ const cause = own(e, 'cause');
957
+ const fingerprint = own(e, 'fingerprint');
958
+ if (!isCause(cause) || typeof fingerprint !== 'string' || fingerprint.length === 0)
959
+ return undefined;
960
+ return kind === 'cause' ? { kind, cause, fingerprint, nowMs, ...withOcc } : { kind, cause, fingerprint, nowMs };
961
+ }
962
+ case 'answer': {
963
+ const choice = own(e, 'choice');
964
+ const fingerprint = own(e, 'fingerprint');
965
+ if ((choice !== 'force' && choice !== 'wait') || typeof fingerprint !== 'string')
966
+ return undefined;
967
+ return { kind, choice, fingerprint, nowMs, ...withOcc };
968
+ }
969
+ case 'spawned': {
970
+ const verdict = verdictOf(own(e, 'verdict'));
971
+ if (verdict === undefined || !isOp(op))
972
+ return undefined;
973
+ return { kind, verdict, op, nowMs };
974
+ }
975
+ case 'spawn-failed': {
976
+ const reason = own(e, 'reason');
977
+ if (!isOp(op))
978
+ return undefined;
979
+ return { kind, op, nowMs, ...(typeof reason === 'string' ? { reason } : {}) };
980
+ }
981
+ case 'gone': {
982
+ const inst = own(e, 'instance');
983
+ const clean = own(e, 'clean');
984
+ if (!isRecord(inst) || (clean !== true && clean !== false && clean !== 'unknown'))
985
+ return undefined;
986
+ return { kind, instance: engineIdentityOf(inst), clean, nowMs };
987
+ }
988
+ default:
989
+ return undefined;
990
+ }
991
+ }
992
+ const STATE_KINDS = Object.freeze(['unbound', 'attached', 'pending', 'draining-wait', 'unanswered', 'down', 'locking', 'stopping', 'spawning', 'behind']);
993
+ function stateOk(s) {
994
+ const kind = own(s, 'kind');
995
+ if (typeof kind !== 'string' || !STATE_KINDS.includes(kind))
996
+ return false;
997
+ if (kind === 'behind')
998
+ return true;
999
+ const ctx = own(s, 'ctx');
1000
+ return isRecord(ctx) && Array.isArray(own(ctx, 'causes')) && Array.isArray(own(ctx, 'autoTried')) && Array.isArray(own(ctx, 'deaths'));
1001
+ }
1002
+ export function localEngineStep(state, event, policy) {
1003
+ try {
1004
+ if (!stateOk(state))
1005
+ return Object.freeze({ state, effects: Object.freeze([]) });
1006
+ return stepSampled(ownSnapshotTyped(state), state, event, policy);
1007
+ }
1008
+ catch {
1009
+ return Object.freeze({ state, effects: Object.freeze([]) });
1010
+ }
1011
+ }
1012
+ function stepSampled(sampled, original, event, policy) {
1013
+ if (sampled === undefined || !stateOk(sampled))
1014
+ return Object.freeze({ state: original, effects: Object.freeze([]) });
1015
+ const state = sampled;
1016
+ const ev = eventOf(event);
1017
+ if (ev === undefined)
1018
+ return unchanged(state);
1019
+ if (state.kind === 'behind')
1020
+ return unchanged(state);
1021
+ let out;
1022
+ switch (ev.kind) {
1023
+ case 'observed':
1024
+ out = onObserved(state, ev, policy);
1025
+ break;
1026
+ case 'tick':
1027
+ out = onTick(state, ev, policy);
1028
+ break;
1029
+ case 'cause':
1030
+ out = onCause(state, ev, policy);
1031
+ break;
1032
+ case 'cause-resolved':
1033
+ out = onCauseResolved(state, ev);
1034
+ break;
1035
+ case 'answer':
1036
+ out = onAnswer(state, ev, policy);
1037
+ break;
1038
+ case 'gone':
1039
+ out = onGone(state, ev);
1040
+ break;
1041
+ default:
1042
+ out = onOpEvent(state, ev, policy);
1043
+ }
1044
+ if (out === undefined)
1045
+ return unchanged(state);
1046
+ if (headless(policy) && out.state.kind === 'down' && state.kind !== 'down')
1047
+ out.effects.push({ kind: 'fail', code: DOWN_FAIL[out.state.reason] });
1048
+ return resultOf(out.state, out.effects) ?? unchanged(state);
1049
+ }
1050
+ function resultOf(state, effects) {
1051
+ const st = ownSnapshotTyped(state);
1052
+ if (st === undefined)
1053
+ return undefined;
1054
+ const fx = [];
1055
+ for (const e of effects) {
1056
+ const c = ownSnapshotTyped(e);
1057
+ if (c === undefined)
1058
+ return undefined;
1059
+ fx.push(c);
1060
+ }
1061
+ return Object.freeze({ state: st, effects: Object.freeze(fx) });
1062
+ }
1063
+ function unchanged(state) {
1064
+ return resultOf(state, []) ?? Object.freeze({ state, effects: Object.freeze([]) });
1065
+ }
1066
+ export function engineSwapAnswerOutcomeOf(before, event, policy) {
1067
+ try {
1068
+ const b = stateOk(before) ? ownSnapshotTyped(before) : undefined;
1069
+ if (b === undefined || !stateOk(b))
1070
+ return 'nothing-pending';
1071
+ const ev = eventOf(event);
1072
+ if (ev === undefined || ev.kind !== 'answer')
1073
+ return 'nothing-pending';
1074
+ const r = answerRowOf(b, ev, policy);
1075
+ if (r === null)
1076
+ return 'nothing-pending';
1077
+ if (r.row === 'B4')
1078
+ return 'stale-card';
1079
+ if (r.row === 'B3p')
1080
+ return 'nothing-pending';
1081
+ return 'started';
1082
+ }
1083
+ catch {
1084
+ return 'nothing-pending';
1085
+ }
1086
+ }