@demlik/tea 0.15.0 → 0.17.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.
- package/README.md +30 -7
- package/dist/agent/index.d.ts +49 -37
- package/dist/agent/index.js +12 -8
- package/dist/chunk-4LPY67E4.js +18 -0
- package/dist/chunk-4LPY67E4.js.map +1 -0
- package/dist/chunk-4NTCPTNQ.js +64 -0
- package/dist/chunk-4NTCPTNQ.js.map +1 -0
- package/dist/{chunk-3B5X7SBB.js → chunk-4PXNZ5UY.js} +11 -9
- package/dist/chunk-4PXNZ5UY.js.map +1 -0
- package/dist/{chunk-AYS5N6WY.js → chunk-6XBEYZME.js} +62 -172
- package/dist/chunk-6XBEYZME.js.map +1 -0
- package/dist/chunk-C4U5D5HP.js +380 -0
- package/dist/chunk-C4U5D5HP.js.map +1 -0
- package/dist/{chunk-QPEVCJ5Q.js → chunk-DFUEAA6L.js} +13 -6
- package/dist/chunk-DFUEAA6L.js.map +1 -0
- package/dist/{chunk-22POWGFA.js → chunk-GWWCCSDH.js} +46 -20
- package/dist/chunk-GWWCCSDH.js.map +1 -0
- package/dist/chunk-K4ETBNKS.js +733 -0
- package/dist/chunk-K4ETBNKS.js.map +1 -0
- package/dist/chunk-KWNCM6XM.js +539 -0
- package/dist/chunk-KWNCM6XM.js.map +1 -0
- package/dist/{chunk-V46HSVCK.js → chunk-LYVOGOBC.js} +3 -3
- package/dist/{chunk-V46HSVCK.js.map → chunk-LYVOGOBC.js.map} +1 -1
- package/dist/{chunk-VTQI2NHP.js → chunk-OPH6Y4UT.js} +9 -8
- package/dist/chunk-OPH6Y4UT.js.map +1 -0
- package/dist/{chunk-Q5CQTBFA.js → chunk-OYR7ENKU.js} +20 -30
- package/dist/chunk-OYR7ENKU.js.map +1 -0
- package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
- package/dist/chunk-QNGYQMZB.js.map +1 -0
- package/dist/chunk-QWIPH4YH.js +98 -0
- package/dist/chunk-QWIPH4YH.js.map +1 -0
- package/dist/{chunk-TCLBCERH.js → chunk-XBZQT2TD.js} +238 -212
- package/dist/chunk-XBZQT2TD.js.map +1 -0
- package/dist/{core-DnzqWy6u.d.ts → core-rDbOaJTN.d.ts} +236 -140
- package/dist/do/index.d.ts +37 -33
- package/dist/do/index.js +21 -14
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-CWzA-o2B.d.ts → durable-effects-CNaw97Be.d.ts} +1 -1
- package/dist/effect/index.d.ts +151 -0
- package/dist/effect/index.js +98 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/extension/index.d.ts +6 -7
- package/dist/extension/index.js +3 -2
- package/dist/extension/index.js.map +1 -1
- package/dist/flow/index.d.ts +68 -59
- package/dist/flow/index.js +32 -27
- package/dist/flow/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +6 -7
- package/dist/idempotency/index.js +4 -2
- package/dist/{index-KXqTRJyb.d.ts → index-BOrsbdAf.d.ts} +19 -96
- package/dist/{index-DrIRb_Ch.d.ts → index-B_BxplWi.d.ts} +1 -1
- package/dist/index-CWmsXNdX.d.ts +207 -0
- package/dist/{index-C2DwkjlP.d.ts → index-Cberb5Rr.d.ts} +57 -45
- package/dist/{index-CSyMYDAB.d.ts → index-CcrKLtse.d.ts} +1 -1
- package/dist/{index-CuNO9m8i.d.ts → index-Cx0F2M34.d.ts} +14 -16
- package/dist/{index-HSYgG2Yt.d.ts → index-DaX_P35Q.d.ts} +247 -211
- package/dist/{index-CA_7I5g9.d.ts → index-q1PoukOZ.d.ts} +137 -246
- package/dist/index.d.ts +121 -385
- package/dist/index.js +2 -1
- package/dist/jev/index.d.ts +133 -97
- package/dist/jev/index.js +205 -161
- package/dist/jev/index.js.map +1 -1
- package/dist/{machine-DGlPWCWI.d.ts → machine-BcfCFMYF.d.ts} +171 -288
- package/dist/machine-viz/index.d.ts +5 -5
- package/dist/machine-viz/index.js +8 -4
- package/dist/machine-viz/index.js.map +1 -1
- package/dist/mem/index.d.ts +3 -4
- package/dist/mem/index.js +2 -1
- package/dist/mem/index.js.map +1 -1
- package/dist/node/index.d.ts +119 -78
- package/dist/node/index.js +52 -38
- package/dist/node/index.js.map +1 -1
- package/dist/paginate/index.d.ts +27 -22
- package/dist/paginate/index.js +6 -4
- package/dist/parity/index.d.ts +4 -5
- package/dist/parity/index.js +3 -2
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/index.d.ts +2 -2
- package/dist/pbt/index.js +2 -1
- package/dist/pbt/index.js.map +1 -1
- package/dist/persistence/index.d.ts +10 -12
- package/dist/persistence/index.js +5 -3
- package/dist/promise/index.d.ts +186 -0
- package/dist/promise/index.js +5 -0
- package/dist/promise/index.js.map +1 -0
- package/dist/{protocol-D8VbH-VH.d.ts → protocol-ClRZBhHQ.d.ts} +7 -8
- package/dist/react/index.d.ts +36 -17
- package/dist/react/index.js +27 -4
- package/dist/react/index.js.map +1 -1
- package/dist/resilience/index.d.ts +63 -456
- package/dist/resilience/index.js +37 -483
- package/dist/resilience/index.js.map +1 -1
- package/dist/{runtime-types-CMF13BBh.d.ts → runtime-types-B62Ucnxr.d.ts} +132 -389
- package/dist/testing/index.d.ts +33 -113
- package/dist/testing/index.js +39 -181
- package/dist/testing/index.js.map +1 -1
- package/dist/timing/index.d.ts +25 -22
- package/dist/timing/index.js +4 -3
- package/dist/timing/index.js.map +1 -1
- package/dist/{types-BseNPIhr.d.ts → types-DPm5YCDQ.d.ts} +2 -2
- package/dist/work-queue/index.d.ts +5 -6
- package/package.json +20 -7
- package/dist/chunk-22POWGFA.js.map +0 -1
- package/dist/chunk-3B5X7SBB.js.map +0 -1
- package/dist/chunk-66PY5WYF.js +0 -1816
- package/dist/chunk-66PY5WYF.js.map +0 -1
- package/dist/chunk-AYS5N6WY.js.map +0 -1
- package/dist/chunk-L3TND7LA.js +0 -97
- package/dist/chunk-L3TND7LA.js.map +0 -1
- package/dist/chunk-MYXFNWAJ.js.map +0 -1
- package/dist/chunk-Q5CQTBFA.js.map +0 -1
- package/dist/chunk-QPEVCJ5Q.js.map +0 -1
- package/dist/chunk-TCLBCERH.js.map +0 -1
- package/dist/chunk-VTQI2NHP.js.map +0 -1
- package/dist/index-ZCMhnVKN.d.ts +0 -272
package/README.md
CHANGED
|
@@ -8,21 +8,26 @@ A TEA / Elm-Architecture TypeScript library for durable, replayable state machin
|
|
|
8
8
|
pnpm add @demlik/tea
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The one runtime dependency is `@standard-schema/spec`, a types-only package
|
|
12
|
+
installed for you. `Cmd.define` takes any Standard Schema, so bring the schema
|
|
13
|
+
library you already use (zod, or Effect Schema through
|
|
14
|
+
`Schema.toStandardSchemaV1`). The host and
|
|
12
15
|
testing adapters ride on optional peers you add only for the surface you use:
|
|
13
16
|
`react` / `react-dom` (the `./react` hooks), `ws` (Node WebSocket subs),
|
|
14
|
-
`fast-check` (the `./pbt` property-testing helpers),
|
|
15
|
-
utilities).
|
|
17
|
+
`fast-check` (the `./pbt` property-testing helpers), `vitest` (the testing
|
|
18
|
+
utilities), and `effect` (the `./effect` engine).
|
|
16
19
|
|
|
17
20
|
## Quickstart
|
|
18
21
|
|
|
19
|
-
A machine is plain data: an `init
|
|
20
|
-
when it has effects
|
|
21
|
-
`Msg`; `getState` reads the current
|
|
22
|
+
A machine is plain data: an `init` and an `update` map keyed by `Msg` type.
|
|
23
|
+
`run` drives it, and when it has effects you hand `run` the `interpret` map
|
|
24
|
+
that performs them; `dispatch` folds a `Msg`; `getState` reads the current
|
|
25
|
+
Model. You name the Model and the `Msg` union
|
|
22
26
|
once, under `types`; everything else is inferred from the machine itself.
|
|
23
27
|
|
|
24
28
|
```ts
|
|
25
|
-
import { defineMachine
|
|
29
|
+
import { defineMachine } from "@demlik/tea";
|
|
30
|
+
import { run } from "@demlik/tea/promise";
|
|
26
31
|
|
|
27
32
|
type State = { readonly count: number };
|
|
28
33
|
type Msg = { readonly type: "increment" } | { readonly type: "reset" };
|
|
@@ -41,6 +46,24 @@ await runtime.dispatch({ type: "increment" });
|
|
|
41
46
|
runtime.getState(); // { count: 1 }
|
|
42
47
|
```
|
|
43
48
|
|
|
49
|
+
## Two engines, one machine
|
|
50
|
+
|
|
51
|
+
The core is `@demlik/tea`, and each engine has its own entry point. A machine
|
|
52
|
+
file imports only the core, so it runs unchanged on either engine.
|
|
53
|
+
|
|
54
|
+
- `@demlik/tea` — the core: `defineMachine`, `Cmd`, `replay` and the types. It
|
|
55
|
+
imports no engine.
|
|
56
|
+
- `@demlik/tea/promise` — the Promise engine. Handlers return Promises.
|
|
57
|
+
- `@demlik/tea/effect` — the Effect engine (`experimental`, Effect v4). Handlers
|
|
58
|
+
return Effects, services come from your Layers, and closing the scope
|
|
59
|
+
interrupts whatever is in flight.
|
|
60
|
+
|
|
61
|
+
[Run a machine on the Promise engine](https://github.com/kamp-us/demlik/blob/main/docs/how-to/run-on-the-promise-engine.md)
|
|
62
|
+
and [on the Effect engine](https://github.com/kamp-us/demlik/blob/main/docs/how-to/run-on-the-effect-engine.md)
|
|
63
|
+
build on the same machine file. Coming from 0.15?
|
|
64
|
+
[The migration guide](https://github.com/kamp-us/demlik/blob/main/docs/how-to/migrate-from-0-15.md)
|
|
65
|
+
shows every removed or reshaped API, before and after.
|
|
66
|
+
|
|
44
67
|
## Documentation
|
|
45
68
|
|
|
46
69
|
The four Diátaxis quadrants live in [`docs/`](https://github.com/kamp-us/demlik/blob/main/docs/README.md):
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
4
|
-
import { a as
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { M as Machine, o as Interpret, d as Subscribe, C as Cmd } from '../core-rDbOaJTN.js';
|
|
2
|
+
import { b as DeadlinesSub } from '../index-DaX_P35Q.js';
|
|
3
|
+
export { a as DeadlineSub, c as deadlineSub, e as deadlinesSub, s as subscribeDeadline } from '../index-DaX_P35Q.js';
|
|
4
|
+
import { T as ToolCall, a as ToolOutcome, C as Conversation, A as AnyToolDef, P as PlainModel, b as AgentTurn, S as StreamingModel, c as AgentState, d as ToolResult, e as ToolFailureOf, f as ToolsCtx, g as AgentEvent, h as TurnChunk, i as AgentMachineMsg, W as WiredToolMsg, j as AgentCmd, k as ToolCmd, l as AgentConfigCore, m as CompactionPolicy, n as AgentKnob } from '../machine-BcfCFMYF.js';
|
|
5
|
+
export { o as AgentBootMsg, p as AgentCancelMsg, q as AgentCompactErrMsg, r as AgentCompactOkMsg, s as AgentCompactRunCmd, t as AgentCompactionConfig, u as AgentConfig, v as AgentFailure, w as AgentLlmErrMsg, x as AgentLlmOkMsg, y as AgentLlmRunCmd, z as AgentSnapshotConfig, B as AgentStatus, D as AgentTerminalFailure, E as AgentTimerMsg, F as AgentToMachine, G as Awaiting, H as COMPACTION_PURPOSE, I as CompactInterpret, J as CompactionOutputs, K as CompactionPurpose, L as CompactionSummary, M as Llm, N as LlmCall, O as LlmErr, Q as LlmFailMsg, R as LlmOk, U as LlmRunCmd, V as LlmSucceedMsg, X as MessageLoader, Y as ModelFactory, Z as ModelPort, _ as ModelStream, $ as PLAIN_MODEL_MISROUTE_REASON, a0 as ReservedToolName, a1 as Schema, a2 as SnapshotInterpret, a3 as TOOL_RETRY_EXHAUSTED_TAG, a4 as TOOL_TIMEOUT_TAG, a5 as TaggedFailure, a6 as ToolConstructors, a7 as ToolDef, a8 as ToolError, a9 as ToolFail, aa as ToolFailure, ab as ToolHandler, ac as ToolInput, ad as ToolMsg, ae as ToolOk, af as ToolRecord, ag as ToolRejectedCmd, ah as ToolRejection, ai as ToolResilience, aj as ToolResilienceError, ak as ToolRetryExhausted, al as ToolRouter, am as ToolSettlement, an as ToolThrown, ao as ToolTimedOut, ap as WiredToolCmd, aq as agentBootMsg, ar as agentCancelMsg, as as agentEvents, at as agentTurnSchema, au as compactionSummarySchema, av as fanOutInterpret, aw as isAgentTurn, ax as isCompactionSummary, ay as isReservedToolName, az as isStreamingModel, aA as liftAgent, aB as mergeInterpret, aC as plainModel, aD as status, aE as tool, aF as toolErrorReason, aG as toolRouter } from '../machine-BcfCFMYF.js';
|
|
6
|
+
import { C as CtxArg, S as Store } from '../runtime-types-B62Ucnxr.js';
|
|
7
|
+
import { E as EndedRun } from '../index-BOrsbdAf.js';
|
|
8
|
+
export { M as MonitoredRunCmd, R as RunFailure } from '../index-BOrsbdAf.js';
|
|
7
9
|
import { RetryPolicy } from '../retry-backoff/index.js';
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
import '
|
|
11
|
-
import '../
|
|
12
|
-
import '../
|
|
13
|
-
import '../index-
|
|
14
|
-
import '../index-
|
|
15
|
-
import 'better-result';
|
|
16
|
-
import '../index-ZCMhnVKN.js';
|
|
17
|
-
import '../types-BseNPIhr.js';
|
|
10
|
+
import '@standard-schema/spec';
|
|
11
|
+
import '../types-DPm5YCDQ.js';
|
|
12
|
+
import '../index-q1PoukOZ.js';
|
|
13
|
+
import '../protocol-ClRZBhHQ.js';
|
|
14
|
+
import '../index-Cx0F2M34.js';
|
|
15
|
+
import '../index-CcrKLtse.js';
|
|
16
|
+
import '../index-CWmsXNdX.js';
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* @demlik/tea/agent — `defineAgent`, the convenience layer over `createAgent`.
|
|
@@ -88,8 +87,8 @@ type DefinedAgentState<T extends AnyToolDef> = AgentState<string, LidPurpose, Li
|
|
|
88
87
|
type DefinedAgentResolvedState<T extends AnyToolDef> = Omit<DefinedAgentState<T>, "run"> & {
|
|
89
88
|
readonly run: EndedRun<string>;
|
|
90
89
|
};
|
|
91
|
-
/** The ctx the tools'
|
|
92
|
-
type DefinedAgentCtx<T extends AnyToolDef> =
|
|
90
|
+
/** The ctx the tools' handlers read, intersected — what `run` asks for. */
|
|
91
|
+
type DefinedAgentCtx<T extends AnyToolDef> = ToolsCtx<T>;
|
|
93
92
|
/** The Msg union a defined agent's machine folds. */
|
|
94
93
|
type DefinedAgentMsg<T extends AnyToolDef> = AgentMachineMsg<LidPurpose, LidOutputs, ToolResult<T>> | WiredToolMsg<T>;
|
|
95
94
|
/**
|
|
@@ -112,8 +111,19 @@ type DefinedAgentCmd<T extends AnyToolDef> = AgentCmd<LidPurpose, ToolCmd<T>, fa
|
|
|
112
111
|
* router's `tool_rejected`, and the agent-owned brain call.
|
|
113
112
|
*/
|
|
114
113
|
type DefinedAgentInterpret<T extends AnyToolDef> = Interpret<DefinedAgentMsg<T>, DefinedAgentCmd<T>, DefinedAgentCtx<T>>;
|
|
115
|
-
/** The wired machine `defineAgent` builds per `input
|
|
116
|
-
type DefinedAgentMachine<T extends AnyToolDef> = Machine<DefinedAgentState<T>, DefinedAgentMsg<T>, DefinedAgentCmd<T>,
|
|
114
|
+
/** The wired machine `defineAgent` builds per `input`. */
|
|
115
|
+
type DefinedAgentMachine<T extends AnyToolDef> = Machine<DefinedAgentState<T>, DefinedAgentMsg<T>, DefinedAgentCmd<T>, DeadlinesSub, DefinedAgentCtx<T>>;
|
|
116
|
+
/**
|
|
117
|
+
* The machine `defineAgent` builds per `input` beside the handlers it runs
|
|
118
|
+
* under — the interpret table and the `deadline` runner (a machine carries
|
|
119
|
+
* none — #278, #279). It is a `Wired`; feed it to the raw `run`:
|
|
120
|
+
* `run(wired.machine, { ...wired, ctx })`.
|
|
121
|
+
*/
|
|
122
|
+
type DefinedAgentWired<T extends AnyToolDef> = {
|
|
123
|
+
readonly machine: DefinedAgentMachine<T>;
|
|
124
|
+
readonly interpret: DefinedAgentInterpret<T>;
|
|
125
|
+
readonly subscribe: Subscribe<DefinedAgentMsg<T>, DeadlinesSub, DefinedAgentCtx<T>>;
|
|
126
|
+
};
|
|
117
127
|
/**
|
|
118
128
|
* The brain a defined agent runs, in either of its two shapes:
|
|
119
129
|
*
|
|
@@ -327,12 +337,10 @@ type DefinedAgentEvent<T extends AnyToolDef> = AgentEvent<ToolResult<T>>;
|
|
|
327
337
|
/**
|
|
328
338
|
* Host wiring for one `run`: the store, the ctx the tools need, a runId, a clock.
|
|
329
339
|
*
|
|
330
|
-
* `ctx` is
|
|
331
|
-
*
|
|
332
|
-
* is accepted here in place of the object and gets the same acquire-at-boot /
|
|
333
|
-
* release-at-terminal lifetime.
|
|
340
|
+
* `ctx` is the plain object the tools' handlers read (ADR 0020). A host with a
|
|
341
|
+
* resource to release wraps `run` in its own `try` / `finally`.
|
|
334
342
|
*/
|
|
335
|
-
type DefinedAgentRunOptions<T extends AnyToolDef> =
|
|
343
|
+
type DefinedAgentRunOptions<T extends AnyToolDef> = CtxArg<DefinedAgentCtx<T>> & {
|
|
336
344
|
readonly store?: Store<DefinedAgentState<T>>;
|
|
337
345
|
/** The run's identity. Omit → a fresh UUID. */
|
|
338
346
|
readonly runId?: string;
|
|
@@ -407,9 +415,10 @@ type DefinedAgentRunOptions<T extends AnyToolDef> = ScopedCtxArg<DefinedAgentCtx
|
|
|
407
415
|
* `defineAgent` built.
|
|
408
416
|
*
|
|
409
417
|
* The wrapper's signature is the cell's, so calling `next(cmd, ctx, dispatch)`
|
|
410
|
-
* is how the wrapped work happens
|
|
411
|
-
*
|
|
412
|
-
* fold, and therefore a replay,
|
|
418
|
+
* is how the wrapped work happens. A tool cell returns its outcome and the
|
|
419
|
+
* engine mints `<name>_ok` / `<name>_err` from it (ADR 0021), so returning
|
|
420
|
+
* what `next` returned is what keeps the fold, and therefore a replay,
|
|
421
|
+
* identical to the unwrapped run's.
|
|
413
422
|
*/
|
|
414
423
|
type InterpretOverlay<T extends AnyToolDef> = CellWrappers<DefinedAgentInterpret<T>>;
|
|
415
424
|
/**
|
|
@@ -444,8 +453,11 @@ interface DefinedAgent<T extends AnyToolDef> {
|
|
|
444
453
|
* reads without a guard against an `idle` arm this promise cannot produce.
|
|
445
454
|
*/
|
|
446
455
|
readonly run: (input: string, ...opts: [Record<never, never>] extends [DefinedAgentCtx<T>] ? [opts?: DefinedAgentRunOptions<T>] : [opts: DefinedAgentRunOptions<T>]) => Promise<DefinedAgentResolvedState<T>>;
|
|
447
|
-
/**
|
|
448
|
-
|
|
456
|
+
/**
|
|
457
|
+
* The machine `run` drives for `input` and the interpret table it runs under
|
|
458
|
+
* — the door down to the raw kernel.
|
|
459
|
+
*/
|
|
460
|
+
readonly machine: (input: string) => DefinedAgentWired<T>;
|
|
449
461
|
/**
|
|
450
462
|
* The ramp between the lid and `createAgent`: wrap ONE interpret cell of the
|
|
451
463
|
* machine this agent builds and get back a NEW defined agent that runs the
|
|
@@ -616,8 +628,8 @@ declare function transcript<R>(seed?: TranscriptSeed<R>): Transcript<R>;
|
|
|
616
628
|
* (via the llm-call message loader), the schemas, and the model. `createAgent(config)` returns the uniform handle contract every
|
|
617
629
|
* composition exposes (`init` / verbs returning `readonly [State, Cmd[]]` /
|
|
618
630
|
* `subs`) AND a ready-to-`run` `defineMachine` (`toMachine`) — THE one wired
|
|
619
|
-
* path. (`
|
|
620
|
-
*
|
|
631
|
+
* path. (`brainInterpret` and `brain` are the pieces a consumer wiring the
|
|
632
|
+
* verbs by hand reaches for.)
|
|
621
633
|
*
|
|
622
634
|
* ## The composition (three siblings wired into ONE machine)
|
|
623
635
|
*
|
|
@@ -628,7 +640,8 @@ declare function transcript<R>(seed?: TranscriptSeed<R>): Transcript<R>;
|
|
|
628
640
|
* delegates `start` / `advance` / `progress` / `onDeadline` / `boot` to it.
|
|
629
641
|
* - `../llm-call` — every brain call. One
|
|
630
642
|
* `LlmCall` per turn, structured-output parsed, retry composed in. The agent
|
|
631
|
-
* delegates the resilient slice + verbs and
|
|
643
|
+
* delegates the resilient slice + verbs, and owns the one Promise handler
|
|
644
|
+
* that invokes the model (`./model`).
|
|
632
645
|
* - `../fan-out` — the tool calls a turn produced. Tools dispatch SERIALLY by
|
|
633
646
|
* default (concurrency 1); fan-out generalizes that to a bounded number
|
|
634
647
|
* LAUNCHED per transition, `config.toolConcurrency`. Above `1` a router's
|
|
@@ -700,8 +713,8 @@ declare function transcript<R>(seed?: TranscriptSeed<R>): Transcript<R>;
|
|
|
700
713
|
* });
|
|
701
714
|
* // The consumer supplies the per-tool (+ snapshot) interpret; the agent owns
|
|
702
715
|
* // the brain interpret and merges them in `toMachine`.
|
|
703
|
-
* const
|
|
704
|
-
* const runtime = run(machine, { ctx, store });
|
|
716
|
+
* const wired = agent.toMachine({ toolInterpret });
|
|
717
|
+
* const runtime = run(wired.machine, { ...wired, ctx, store });
|
|
705
718
|
*/
|
|
706
719
|
|
|
707
720
|
/**
|
|
@@ -716,7 +729,6 @@ declare function transcript<R>(seed?: TranscriptSeed<R>): Transcript<R>;
|
|
|
716
729
|
* pass obliges you to supply the matching handler at `toMachine`, and each one
|
|
717
730
|
* you omit forbids it. `rng` is the determinism seam for the retry backoff —
|
|
718
731
|
* pass a fixed `() => 0` to pin it in tests; omit for `Math.random`.
|
|
719
|
-
* `unsafeDetachedHandlers` is the hand-wiring escape hatch.
|
|
720
732
|
*
|
|
721
733
|
* The four overloads exist because the two obligations are derived
|
|
722
734
|
* from the `config` VALUE, never inferred. `{ snapshotEvery: number }` REQUIRES
|
|
@@ -745,4 +757,4 @@ declare function createAgent<Stage, P extends string, O extends Record<P, AgentT
|
|
|
745
757
|
readonly compaction?: never;
|
|
746
758
|
}): AgentKnob<Stage, P, O, R, TC, false, false>;
|
|
747
759
|
|
|
748
|
-
export { AgentCmd, AgentConfigCore, AgentEvent, AgentKnob, AgentMachineMsg, type AgentMessage, type AgentPrompt, AgentState, AgentTurn, AnyToolDef, CompactionPolicy, Conversation,
|
|
760
|
+
export { AgentCmd, AgentConfigCore, AgentEvent, AgentKnob, AgentMachineMsg, type AgentMessage, type AgentPrompt, AgentState, AgentTurn, AnyToolDef, CompactionPolicy, Conversation, DeadlinesSub, type DefineAgentCompaction, type DefineAgentConfig, type DefinedAgent, type DefinedAgentCmd, type DefinedAgentCtx, type DefinedAgentEvent, type DefinedAgentInterpret, type DefinedAgentMachine, type DefinedAgentModel, type DefinedAgentMsg, type DefinedAgentOverlay, type DefinedAgentResolvedState, type DefinedAgentRunOptions, type DefinedAgentState, type DefinedAgentWired, EndedRun, type InterpretOverlay, type LidPurpose, PlainModel, StreamingModel, ToolCall, ToolCmd, type ToolErrorContext, ToolFailureOf, ToolOutcome, ToolResult, ToolsCtx, type Transcript, type TranscriptOutcome, type TranscriptSeed, type TranscriptSnapshot, type TranscriptToolResult, TurnChunk, WiredToolMsg, createAgent, defineAgent, renderPrompt, transcript };
|
package/dist/agent/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
export { COMPACTION_PURPOSE, PLAIN_MODEL_MISROUTE_REASON, TOOL_RETRY_EXHAUSTED_TAG, TOOL_TIMEOUT_TAG, agentBootMsg, agentCancelMsg, agentEvents, agentTurnSchema, compactionSummarySchema, createAgent, defineAgent, fanOutInterpret, isAgentTurn, isCompactionSummary, isReservedToolName, isStreamingModel, liftAgent, mergeInterpret, plainModel, renderPrompt, status, tool, toolErrorReason, toolRouter, transcript } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
3
|
-
import '../chunk-
|
|
1
|
+
export { COMPACTION_PURPOSE, PLAIN_MODEL_MISROUTE_REASON, TOOL_RETRY_EXHAUSTED_TAG, TOOL_TIMEOUT_TAG, agentBootMsg, agentCancelMsg, agentEvents, agentTurnSchema, compactionSummarySchema, createAgent, defineAgent, fanOutInterpret, isAgentTurn, isCompactionSummary, isReservedToolName, isStreamingModel, liftAgent, mergeInterpret, plainModel, renderPrompt, status, tool, toolErrorReason, toolRouter, transcript } from '../chunk-XBZQT2TD.js';
|
|
2
|
+
import '../chunk-G5E74IJB.js';
|
|
3
|
+
import '../chunk-4PXNZ5UY.js';
|
|
4
|
+
import '../chunk-4NTCPTNQ.js';
|
|
5
|
+
import '../chunk-QNGYQMZB.js';
|
|
4
6
|
import '../chunk-TXN2KFHF.js';
|
|
5
|
-
import '../chunk-
|
|
6
|
-
import '../chunk-AYS5N6WY.js';
|
|
7
|
+
import '../chunk-6XBEYZME.js';
|
|
7
8
|
import '../chunk-VBDU4JL6.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import '../chunk-
|
|
9
|
+
import '../chunk-4LPY67E4.js';
|
|
10
|
+
export { deadlineSub, deadlinesSub, subscribeDeadline } from '../chunk-GWWCCSDH.js';
|
|
11
|
+
import '../chunk-QWIPH4YH.js';
|
|
12
|
+
import '../chunk-KWNCM6XM.js';
|
|
13
|
+
import '../chunk-C4U5D5HP.js';
|
|
14
|
+
import '../chunk-K4ETBNKS.js';
|
|
11
15
|
//# sourceMappingURL=index.js.map
|
|
12
16
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/internal/schema.ts
|
|
2
|
+
var VENDOR = "@demlik/tea";
|
|
3
|
+
function schema(validate) {
|
|
4
|
+
return { "~standard": { version: 1, vendor: VENDOR, validate } };
|
|
5
|
+
}
|
|
6
|
+
function unchecked() {
|
|
7
|
+
return schema((value) => ({ value }));
|
|
8
|
+
}
|
|
9
|
+
var undefinedOnly = schema(
|
|
10
|
+
(value) => value === void 0 ? { value: void 0 } : { issues: [{ message: "expected no value" }] }
|
|
11
|
+
);
|
|
12
|
+
var rejectAll = schema(() => ({
|
|
13
|
+
issues: [{ message: "this Cmd never settles Ok" }]
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
export { rejectAll, unchecked, undefinedOnly };
|
|
17
|
+
//# sourceMappingURL=chunk-4LPY67E4.js.map
|
|
18
|
+
//# sourceMappingURL=chunk-4LPY67E4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/schema.ts"],"names":[],"mappings":";AAaA,IAAM,MAAA,GAAS,aAAA;AAEf,SAAS,OACP,QAAA,EACwB;AACxB,EAAA,OAAO,EAAE,aAAa,EAAE,OAAA,EAAS,GAAG,MAAA,EAAQ,MAAA,EAAQ,UAAS,EAAE;AACjE;AAGO,SAAS,SAAA,GAAuC;AACrD,EAAA,OAAO,MAAA,CAAO,CAAC,KAAA,MAAW,EAAE,OAAkB,CAAE,CAAA;AAClD;AAGO,IAAM,aAAA,GAAwD,MAAA;AAAA,EACnE,CAAC,KAAA,KACC,KAAA,KAAU,MAAA,GACN,EAAE,KAAA,EAAO,MAAA,EAAU,GACnB,EAAE,QAAQ,CAAC,EAAE,OAAA,EAAS,mBAAA,EAAqB,CAAA;AACnD;AAGO,IAAM,SAAA,GAA4C,OAAO,OAAO;AAAA,EACrE,MAAA,EAAQ,CAAC,EAAE,OAAA,EAAS,6BAA6B;AACnD,CAAA,CAAE","file":"chunk-4LPY67E4.js","sourcesContent":["/**\n * The three Standard Schemas tea's own `Cmd.define`s need, written by hand so\n * the published package depends on no schema library (ADR 0021 §5).\n *\n * A battery's Cmd input is data it built itself and its `ok` is mostly a type\n * annotation, so what these check is deliberately thin: `unchecked<T>()` names\n * a type and checks nothing, `undefinedOnly` is a handler that settles with no\n * value, and `rejectAll` is a Cmd that never settles `Ok`. A consumer's own\n * `Cmd.define` brings a real schema — zod, Effect Schema — instead.\n */\n\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\nconst VENDOR = \"@demlik/tea\";\n\nfunction schema<T>(\n validate: (value: unknown) => StandardSchemaV1.Result<T>,\n): StandardSchemaV1<T, T> {\n return { \"~standard\": { version: 1, vendor: VENDOR, validate } };\n}\n\n/** Accept every value as `T`. The type is the whole contract. */\nexport function unchecked<T>(): StandardSchemaV1<T, T> {\n return schema((value) => ({ value: value as T }));\n}\n\n/** Accept `undefined` only — the `ok` of a handler that settles with no value. */\nexport const undefinedOnly: StandardSchemaV1<undefined, undefined> = schema(\n (value) =>\n value === undefined\n ? { value: undefined }\n : { issues: [{ message: \"expected no value\" }] },\n);\n\n/** Accept nothing — the `ok` of a Cmd whose handler only ever fails. */\nexport const rejectAll: StandardSchemaV1<never, never> = schema(() => ({\n issues: [{ message: \"this Cmd never settles Ok\" }],\n}));\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { undefinedOnly, unchecked } from './chunk-4LPY67E4.js';
|
|
2
|
+
import { Cmd } from './chunk-K4ETBNKS.js';
|
|
3
|
+
|
|
4
|
+
// src/internal/persistence/snapshot/index.ts
|
|
5
|
+
var DEFAULT_SNAPSHOT_KEY = "@@snapshot";
|
|
6
|
+
function snapshotWriteDef() {
|
|
7
|
+
return Cmd.define("snapshot_write", {
|
|
8
|
+
input: unchecked(),
|
|
9
|
+
ok: undefinedOnly,
|
|
10
|
+
err: ["snapshot_write_failed"]
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function snapshotLoadDef() {
|
|
14
|
+
return Cmd.define("snapshot_load", {
|
|
15
|
+
input: unchecked(),
|
|
16
|
+
ok: unchecked(),
|
|
17
|
+
err: ["snapshot_load_failed"]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function createSnapshot(config) {
|
|
21
|
+
const every = config.every >= 1 ? config.every : 1;
|
|
22
|
+
const key = config.key ?? DEFAULT_SNAPSHOT_KEY;
|
|
23
|
+
const snapshotWrite = snapshotWriteDef();
|
|
24
|
+
const snapshotLoad = snapshotLoadDef();
|
|
25
|
+
function emitWrite(state, payload, at) {
|
|
26
|
+
const seq = state.seq + 1;
|
|
27
|
+
const next = { ...state, sinceLast: 0, seq };
|
|
28
|
+
return [next, [snapshotWrite({ key, seq, at, payload })]];
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
write: snapshotWrite,
|
|
32
|
+
load: snapshotLoad,
|
|
33
|
+
init() {
|
|
34
|
+
return { sinceLast: 0, seq: 0, lastSavedSeq: null, lastSavedAt: null };
|
|
35
|
+
},
|
|
36
|
+
record(state, payload, at) {
|
|
37
|
+
const sinceLast = state.sinceLast + 1;
|
|
38
|
+
if (sinceLast < every) {
|
|
39
|
+
return [{ ...state, sinceLast }, []];
|
|
40
|
+
}
|
|
41
|
+
return emitWrite(state, payload, at);
|
|
42
|
+
},
|
|
43
|
+
force(state, payload, at) {
|
|
44
|
+
return emitWrite(state, payload, at);
|
|
45
|
+
},
|
|
46
|
+
confirm(state, msg) {
|
|
47
|
+
const { seq, at } = msg.cmd;
|
|
48
|
+
if (state.lastSavedSeq !== null && seq <= state.lastSavedSeq) {
|
|
49
|
+
return [state, Cmd.none];
|
|
50
|
+
}
|
|
51
|
+
return [{ ...state, lastSavedSeq: seq, lastSavedAt: at }, Cmd.none];
|
|
52
|
+
},
|
|
53
|
+
boot(state) {
|
|
54
|
+
return [{ ...state, sinceLast: 0 }, Cmd.none];
|
|
55
|
+
},
|
|
56
|
+
requestLoad(state) {
|
|
57
|
+
return [state, [snapshotLoad({ key })]];
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { createSnapshot, snapshotLoadDef, snapshotWriteDef };
|
|
63
|
+
//# sourceMappingURL=chunk-4NTCPTNQ.js.map
|
|
64
|
+
//# sourceMappingURL=chunk-4NTCPTNQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/persistence/snapshot/index.ts"],"names":[],"mappings":"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","file":"chunk-4NTCPTNQ.js","sourcesContent":["/**\n * internal/persistence/snapshot — periodic state checkpoint to a host store\n * (R2/KV-shaped). Internal since #49 — not published on any subpath.\n *\n * The problem it solves: a long-running machine (a multi-stage run, a paginated\n * crawl, a saga) lives in TEA's in-memory `State` and is persisted as ONE blob\n * by the substrate's `Store<S>` on every transition. That blob is fine for\n * crash recovery of the *runtime* — but it is NOT a coarse, named checkpoint a\n * human (or a separate recovery job) can point a restart at. `snapshot` adds\n * the \"every N units of progress, write a durable checkpoint to R2/KV\"\n * discipline on top, so a crashed long run restarts from the last checkpoint\n * instead of from zero (the field-guide pain row).\n *\n * Shape: `createSnapshot(config)` hands back plain functions and two\n * `Cmd.define`d Cmds (`write`, `load`) you call from your own `update` (ADR\n * 0022). The state it owns is a Model slice (durable + replayable, the two\n * non-negotiables); the verbs are PURE decisions; the I/O (the actual `put` /\n * `get` against your store) lives only in the two handlers you write (ADR 0021).\n *\n * It is a LEAF module: it imports only the core substrate (`../index`, for\n * `Cmd`) and no sibling subpath. (It does NOT reuse\n * `@demlik/tea/retry-backoff` — that is an even-leafier utility that depends on\n * nothing at all, not even the core; the kinship here is \"leaf with injected\n * time\", not a shared dependency.)\n * Nothing here reads the clock or the RNG on its own behalf — time enters as an\n * `at`/`now` parameter at the verb boundary and the store I/O is realized only\n * when the runtime performs the emitted `Cmd`. That keeps every verb\n * deterministic (invariant 2) and the checkpoint write a `Cmd` the runtime\n * interprets (invariant 3).\n *\n * The store is the handler's business, never this module's — so it never\n * imports `@cloudflare/workers-types` and can be checkpointed to anything (R2,\n * KV, a DO sub-key, an in-memory map in tests).\n *\n * Typical wiring:\n *\n * const snap = createSnapshot<RunState>({ every: 25, key: \"run/checkpoint\" });\n *\n * // machine state:\n * type State = { run: RunState; snapshot: SnapshotState };\n * cmds: [snap.write, snap.load],\n * init: (loaded) => loaded ? [loaded, []] : [{ run: initRun(), snapshot: snap.init() }, []],\n *\n * // in `update`, on each progress Msg, fold the snapshot slice:\n * progress: (s, msg) => {\n * const run = advance(s.run, msg);\n * const [snapshot, cmds] = snap.record(s.snapshot, run, msg.at);\n * return [{ ...s, run, snapshot }, cmds];\n * },\n * // the write completed → durable ack folds back through update:\n * snapshot_write_ok: (s, msg) => {\n * const [snapshot, cmds] = snap.confirm(s.snapshot, msg);\n * return [{ ...s, snapshot }, cmds];\n * },\n * snapshot_write_err: (s) => [s, []], // errors are data — decide here\n *\n * // RECOVERY — the half that makes a checkpoint a restart point, not just a\n * // backup. A `boot` Msg the host dispatches once after `run(...)` asks the\n * // store for the durable checkpoint; the answer folds the recovered payload\n * // back into the run slice:\n * boot: (s) => snap.requestLoad(s), // emits snapshot_load Cmd\n * snapshot_load_ok: (s, msg) => {\n * if (msg.value === null) return [s, []]; // no checkpoint → keep fresh init\n * const [snapshot, cmds] = snap.boot(s.snapshot);\n * return [{ ...s, run: msg.value, snapshot }, cmds];\n * },\n * snapshot_load_err: (s) => [s, []], // errors are data — decide here\n *\n * // and where the machine runs, the two handlers you write:\n * run(machine, {\n * interpret: {\n * snapshot_write: async (cmd, { ok, err }) => {\n * try { await r2.put(cmd.key, cmd.payload); return ok(undefined); }\n * catch (cause) { return err({ _tag: \"snapshot_write_failed\", cause }); }\n * },\n * snapshot_load: async (cmd, { ok, err }) => {\n * try { return ok(await r2.get(cmd.key)); }\n * catch (cause) { return err({ _tag: \"snapshot_load_failed\", cause }); }\n * },\n * },\n * });\n */\n\nimport {\n Cmd,\n type CmdOf,\n type SettledErr,\n type SettledOk,\n type TaggedError,\n} from \"../../../index\";\nimport { unchecked, undefinedOnly } from \"../../schema\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Config — the knob.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot knob.\n *\n * `every` is the cadence: a checkpoint is written once `every` units of\n * progress have accumulated since the last one (see `record`). It is the\n * `snapshotEvery` field `monitored-run` threads through to this module.\n *\n * `key` is the store key the checkpoint is written under. A single machine\n * keeps a single rolling checkpoint, so the key is fixed at config time rather\n * than passed per write — the latest checkpoint overwrites the previous one\n * (R2/KV are last-write-wins on a key). Defaults to `\"@@snapshot\"`, mirroring\n * `doStore`'s `@@state` default-key convention.\n */\nexport interface SnapshotConfig {\n /**\n * Write a checkpoint once this many progress units have accumulated since the\n * last write. `every: 1` checkpoints on every `record`; a value `<= 0` is\n * treated as `1` (every progress unit triggers a write) — a non-positive\n * cadence has no other sensible meaning, and clamping keeps `record` from\n * never checkpointing on a misconfigured `0`.\n */\n readonly every: number;\n /** Store key the rolling checkpoint is written under. Defaults to `\"@@snapshot\"`. */\n readonly key?: string;\n}\n\nconst DEFAULT_SNAPSHOT_KEY = \"@@snapshot\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Slice — the Model field this knob owns.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot bookkeeping slice. Plain data — JSON-serializable so it rides\n * inside the consumer's `Store<S>` blob and survives DO eviction / reload (the\n * \"durable\" non-negotiable). It carries NO payload: the thing being\n * checkpointed lives elsewhere in the machine's state; this slice only tracks\n * *when* to checkpoint and *what has been acknowledged durable*.\n *\n * - `sinceLast` — progress units accumulated since the last checkpoint\n * DECISION (reset to 0 the moment `record` decides to write, not when the\n * write lands). The cadence counter.\n * - `seq` — monotonic checkpoint sequence. Bumped each time a write is\n * emitted, so the write Cmd and its later acknowledgement can be matched\n * even if several are in flight. The \"version\" of the latest emitted\n * checkpoint.\n * - `lastSavedSeq` — the highest `seq` whose write the host has confirmed\n * durable (via `confirm`). `null` until the first confirmed write. The gap\n * `seq - lastSavedSeq` is the \"writes emitted but not yet acknowledged\"\n * depth — observable for backpressure / health.\n * - `lastSavedAt` — the `at` of the most recent confirmed write (epoch ms),\n * `null` until the first. Carried for observability, never compared inside\n * a verb (time is injected, never read here).\n */\nexport interface SnapshotState {\n readonly sinceLast: number;\n readonly seq: number;\n readonly lastSavedSeq: number | null;\n readonly lastSavedAt: number | null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cmds + Msgs — the write effect, the read effect, and their settled Msgs.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The checkpoint-write Cmd a `record` / `force` decision emits. Plain data\n * (invariant 1 — Cmds are data, not closures): it carries the `payload` to\n * write, the `key` to write it under, the `seq` it represents, and the `at` it\n * was decided.\n *\n * `payload` is `V` — the consumer's serializable checkpoint value (the run\n * state, the cursor, whatever the machine wants restartable). The verb takes it\n * as an argument so the snapshot slice never has to hold a copy of the\n * machine's domain state.\n *\n * `Cmd.define`d (ADR 0014, 0021), minted per `V` by a factory the way\n * `workflowActivityDef<A>()` is, since the payload type is the knob's own\n * parameter. The handler you write puts the payload in your store and returns\n * `ok(undefined)`, or `err({ _tag: \"snapshot_write_failed\", … })`; the engine\n * mints `snapshot_write_ok` / `snapshot_write_err`.\n */\nexport function snapshotWriteDef<V>() {\n return Cmd.define(\"snapshot_write\", {\n input: unchecked<{\n readonly key: string;\n readonly seq: number;\n readonly at: number;\n readonly payload: V;\n }>(),\n ok: undefinedOnly,\n err: [\"snapshot_write_failed\"],\n });\n}\nexport type SnapshotWriteCmd<V> = CmdOf<ReturnType<typeof snapshotWriteDef<V>>>;\n\n/**\n * The checkpoint-READ Cmd a `requestLoad` decision emits — the recovery half of\n * the module. Plain data (invariant 1): it carries only the `key` to read. The\n * handler you write reads the store and returns `ok(payload)` — `null` when\n * the store has no checkpoint under `key` — or\n * `err({ _tag: \"snapshot_load_failed\", … })`.\n *\n * Distinct from `snapshot_write`: a write carries a payload OUT to the store; a\n * load asks the store to hand a payload BACK. The consumer dispatches the\n * triggering Msg once on resume (a `boot` Msg, never `init`'s rehydrate branch —\n * invariant 2), folds the recovered payload into its run slice, then continues.\n */\nexport function snapshotLoadDef<V>() {\n return Cmd.define(\"snapshot_load\", {\n input: unchecked<{ readonly key: string }>(),\n ok: unchecked<V | null>(),\n err: [\"snapshot_load_failed\"],\n });\n}\nexport type SnapshotLoadCmd<V = unknown> = CmdOf<\n ReturnType<typeof snapshotLoadDef<V>>\n>;\n\n/**\n * The Msg the engine mints when a write lands. Fold it through\n * `confirm(state, msg)` to advance `lastSavedSeq` / `lastSavedAt`. It carries\n * the write Cmd, so `msg.cmd.seq` matches the emitting decision and a late\n * acknowledgement of an older write can never regress a newer confirmed one.\n */\nexport type SnapshotSavedMsg<V = unknown> = SettledOk<\n \"snapshot_write\",\n SnapshotWriteCmd<V>,\n undefined\n>;\n\n/**\n * The Msg the engine mints when a write fails. Errors are data (never\n * swallowed): the consumer's reducer decides whether a failed checkpoint is\n * fatal, retryable (compose `@demlik/tea/retry-backoff`), or merely logged.\n * `msg.cmd.seq` identifies which checkpoint failed.\n */\nexport type SnapshotFailedMsg<V = unknown> = SettledErr<\n \"snapshot_write\",\n SnapshotWriteCmd<V>,\n TaggedError<\"snapshot_write_failed\">\n>;\n\n/**\n * The Msg the engine mints when a read resolves. `value` is the recovered\n * checkpoint, or `null` when the store has no checkpoint under `key` (fresh\n * boot — never checkpointed, or a wiped store). `null` is NOT an error — \"no\n * checkpoint yet\" is a normal first-run state, distinct from \"the read failed\"\n * (`snapshot_load_err`).\n */\nexport type SnapshotLoadedMsg<V> = SettledOk<\n \"snapshot_load\",\n SnapshotLoadCmd<V>,\n V | null\n>;\n\n/**\n * The Msg the engine mints when a read fails. Errors are data: a failed\n * recovery read is a visible signal the consumer's reducer decides on\n * (fatal-abort, retry, or boot-fresh-and-log). Kept apart from a `null` load\n * so \"the store has no checkpoint\" can never masquerade as \"the read failed\".\n */\nexport type SnapshotLoadFailedMsg<V = unknown> = SettledErr<\n \"snapshot_load\",\n SnapshotLoadCmd<V>,\n TaggedError<\"snapshot_load_failed\">\n>;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The combinator.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The bundle `createSnapshot` returns: plain functions plus the two Cmd defs to\n * list in `cmds`. There are no subs: a checkpoint is driven by progress (a\n * `record` call from the consumer's reducer), not by a timer.\n */\nexport interface SnapshotKnob<V> {\n /** The `snapshot_write` Cmd def — list it in the machine's `cmds`. */\n readonly write: ReturnType<typeof snapshotWriteDef<V>>;\n /** The `snapshot_load` Cmd def — list it in the machine's `cmds`. */\n readonly load: ReturnType<typeof snapshotLoadDef<V>>;\n /** The initial slice — zero progress, no checkpoint emitted or confirmed yet. */\n init(): SnapshotState;\n /**\n * Account one unit of progress. PURE. Increments `sinceLast`; once the\n * accumulated count reaches the configured cadence, DECIDES to checkpoint:\n * resets `sinceLast` to 0, bumps `seq`, and emits a single `snapshot_write`\n * Cmd carrying `payload` + `key` + the new `seq` + `at`. Below the cadence it\n * returns the bumped slice and NO Cmd.\n *\n * `payload` is the checkpoint value to persist (the consumer's domain state);\n * `at` is the caller-stamped time the progress occurred (epoch ms) — injected,\n * never read from the clock here.\n */\n record(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly SnapshotWriteCmd<V>[]];\n /**\n * Force a checkpoint regardless of the cadence counter. PURE. Always bumps\n * `seq`, resets `sinceLast`, and emits the write Cmd. Use on a phase boundary\n * the cadence would otherwise miss — e.g. \"the run just finished, checkpoint\n * the terminal state now\" — so the last durable checkpoint is the final state,\n * not whatever the modulo happened to land on.\n */\n force(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly SnapshotWriteCmd<V>[]];\n /**\n * Record a confirmed durable write. PURE. Advances `lastSavedSeq` /\n * `lastSavedAt` to the acknowledged write's `seq` / `at`, but only forward:\n * an out-of-order acknowledgement of an OLDER `seq` (a slow write landing\n * after a newer one) is ignored, so the confirmed watermark is monotonic.\n * Fold the `snapshot_write_ok` Msg here.\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — an ack emits nothing) for uniformity with every sibling\n * knob, so it drops straight into a `snapshot_write_ok` cell.\n */\n confirm(\n state: SnapshotState,\n msg: SnapshotSavedMsg<V>,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Resume after a reload. PURE. Resets `sinceLast` to 0 because any\n * un-checkpointed progress accumulated before the crash is moot — recovery\n * resumes from the last DURABLE checkpoint, so the cadence counter starts\n * fresh from there. `seq` / `lastSavedSeq` / `lastSavedAt` are preserved\n * (they describe what is durable, which a reload does not change). Call from\n * a `boot` Msg the host dispatches once after `run(...)`, never from `init`'s\n * rehydrate branch (invariant 2).\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — a resume emits nothing) so it drops straight into a\n * `snapshot_load_ok` cell.\n */\n boot(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Ask the store for the durable checkpoint. PURE. Emits a single\n * `snapshot_load` Cmd carrying the configured `key`; returns the slice\n * UNCHANGED (a read decides nothing about the cadence bookkeeping — only the\n * later `snapshot_load_ok` fold, via `boot`, touches the slice). This is the\n * entry point of the recovery loop: the consumer dispatches a `boot` Msg once\n * after `run(...)` and folds `requestLoad` there.\n */\n requestLoad(\n state: SnapshotState,\n ): readonly [SnapshotState, readonly SnapshotLoadCmd<V>[]];\n}\n\n/**\n * Create a snapshot knob from a `SnapshotConfig`. The returned bundle is spliced\n * into a consumer machine: `init()` seeds the slice, `record` / `force` are\n * folded in reducer cells, `confirm` / `boot` handle the acknowledgement +\n * resume Msgs, and `write` / `load` are the Cmd defs whose handlers you write.\n */\nexport function createSnapshot<V>(config: SnapshotConfig): SnapshotKnob<V> {\n // Clamp the cadence to >= 1 at construction so `record`'s threshold check is\n // branchless and a misconfigured `0` / negative `every` checkpoints on every\n // progress unit instead of never.\n const every = config.every >= 1 ? config.every : 1;\n const key = config.key ?? DEFAULT_SNAPSHOT_KEY;\n const snapshotWrite = snapshotWriteDef<V>();\n const snapshotLoad = snapshotLoadDef<V>();\n\n /**\n * Emit the write Cmd for a checkpoint decision. Shared by `record`'s\n * cadence-hit branch and `force`. Bumps `seq`, resets `sinceLast`, returns the\n * new slice + the single `snapshot_write` Cmd. The input slice is never\n * mutated (invariant 1).\n */\n function emitWrite(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly SnapshotWriteCmd<V>[]] {\n const seq = state.seq + 1;\n const next: SnapshotState = { ...state, sinceLast: 0, seq };\n return [next, [snapshotWrite({ key, seq, at, payload })]];\n }\n\n return {\n write: snapshotWrite,\n load: snapshotLoad,\n\n init(): SnapshotState {\n return { sinceLast: 0, seq: 0, lastSavedSeq: null, lastSavedAt: null };\n },\n\n record(state, payload, at) {\n const sinceLast = state.sinceLast + 1;\n // Below the cadence: accumulate, emit nothing.\n if (sinceLast < every) {\n return [{ ...state, sinceLast }, []];\n }\n // Cadence reached: checkpoint. `emitWrite` resets the counter + bumps seq.\n return emitWrite(state, payload, at);\n },\n\n force(state, payload, at) {\n return emitWrite(state, payload, at);\n },\n\n confirm(state, msg) {\n const { seq, at } = msg.cmd;\n // Forward-only watermark: ignore a stale ack of an older seq so a slow\n // write landing after a newer one can't regress `lastSavedSeq`.\n if (state.lastSavedSeq !== null && seq <= state.lastSavedSeq) {\n return [state, Cmd.none];\n }\n return [{ ...state, lastSavedSeq: seq, lastSavedAt: at }, Cmd.none];\n },\n\n boot(state) {\n return [{ ...state, sinceLast: 0 }, Cmd.none];\n },\n\n requestLoad(state) {\n // A read decides nothing about the cadence bookkeeping — return the slice\n // unchanged and emit the single read Cmd. The recovered payload folds back\n // through `snapshot_load_ok` (the consumer's reducer), not here.\n return [state, [snapshotLoad({ key })]];\n },\n };\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createSnapshot } from './chunk-4NTCPTNQ.js';
|
|
1
2
|
import { queueAdapter } from './chunk-TXN2KFHF.js';
|
|
2
|
-
import {
|
|
3
|
-
import { deadlineSub } from './chunk-22POWGFA.js';
|
|
3
|
+
import { deadlineSub, nextTimer } from './chunk-GWWCCSDH.js';
|
|
4
4
|
|
|
5
5
|
// src/internal/flow/monitored-run/index.ts
|
|
6
6
|
function safetyTimerId(runId, progressSeq) {
|
|
@@ -155,7 +155,7 @@ function createMonitoredRun(config = {}) {
|
|
|
155
155
|
};
|
|
156
156
|
return [reseeded, []];
|
|
157
157
|
}
|
|
158
|
-
function
|
|
158
|
+
function deadlines(s) {
|
|
159
159
|
if (config.deadlineMs === void 0) return [];
|
|
160
160
|
if (s.phase !== "running" && s.phase !== "stale") return [];
|
|
161
161
|
return [
|
|
@@ -165,8 +165,8 @@ function createMonitoredRun(config = {}) {
|
|
|
165
165
|
)
|
|
166
166
|
];
|
|
167
167
|
}
|
|
168
|
-
function
|
|
169
|
-
return
|
|
168
|
+
function timer(s) {
|
|
169
|
+
return nextTimer(deadlines(s), s.lastProgressAt);
|
|
170
170
|
}
|
|
171
171
|
function confirmSnapshot(s, msg) {
|
|
172
172
|
if (snap === null) return s;
|
|
@@ -174,6 +174,8 @@ function createMonitoredRun(config = {}) {
|
|
|
174
174
|
return { ...s, snapshot: confirmed };
|
|
175
175
|
}
|
|
176
176
|
return {
|
|
177
|
+
/** The `snapshot_write` Cmd def — list it in `cmds` when checkpointing. */
|
|
178
|
+
checkpoint: activeSnap.write,
|
|
177
179
|
init,
|
|
178
180
|
start,
|
|
179
181
|
progress,
|
|
@@ -183,8 +185,8 @@ function createMonitoredRun(config = {}) {
|
|
|
183
185
|
onDeadline,
|
|
184
186
|
boot,
|
|
185
187
|
confirmSnapshot,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
deadlines,
|
|
189
|
+
timer
|
|
188
190
|
};
|
|
189
191
|
}
|
|
190
192
|
function liftRun(state, [slice, cmds]) {
|
|
@@ -192,5 +194,5 @@ function liftRun(state, [slice, cmds]) {
|
|
|
192
194
|
}
|
|
193
195
|
|
|
194
196
|
export { createMonitoredRun, liftRun };
|
|
195
|
-
//# sourceMappingURL=chunk-
|
|
196
|
-
//# sourceMappingURL=chunk-
|
|
197
|
+
//# sourceMappingURL=chunk-4PXNZ5UY.js.map
|
|
198
|
+
//# sourceMappingURL=chunk-4PXNZ5UY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/flow/monitored-run/index.ts"],"names":[],"mappings":"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","file":"chunk-4PXNZ5UY.js","sourcesContent":["/**\n * internal/flow/monitored-run — a long-running operation that is BOTH staged and\n * watched: an ordered stage pipeline whose POSITION survives eviction, wrapped\n * by a no-progress safety deadline and (optionally) a periodic durable\n * checkpoint.\n *\n * This is the merge of two field-guide concerns the audit-agent machine\n * hand-wired together (`seed/audit-agent-machine`): the OUTER pipeline position\n * (`RunStage` — planning → testing → summarizing → reporting, replayed on cold\n * wake by re-emitting the one outstanding effect) and the no-progress safety\n * alarm (`subs.ts`, keyed on `progressSeq` so every progress event retires the\n * old alarm and arms a fresh one). `monitored-run` lifts both into one config-\n * driven knob of plain functions (ADR 0022): `init()`, the verbs (`start` /\n * `advance` / `progress` / `onDeadline` / `boot` / `confirmSnapshot`),\n * `deadlines(state)` / `timer(state)`, and the checkpoint Cmd def\n * (`checkpoint`).\n *\n * - It is **staged, optionally**. Omit `stages` and the run is single-shot:\n * `start` enters `running`, `advance` finishes it to `done`. Provide\n * `stages` and it becomes an ordered pipeline — each `advance` retires the\n * current stage and claims the next, finishing to `done` only when the last\n * stage is retired. The pipeline POSITION lives in the Model (a\n * `work-queue` of stage items), so an eviction mid-pipeline resumes at the\n * same stage via `boot` — the audit machine's \"pipeline position survives\n * eviction\" property, generalized.\n * - It is **watched**. While `running`, a no-progress deadline is armed at\n * `lastProgressAt + deadlineMs` and keyed on `progressSeq`. Every\n * `progress` / `advance` bumps the seq, so the substrate retires the old\n * alarm and arms a new one — a self-rearming watchdog. If the alarm fires\n * (`onDeadline`) the run goes `failed` with a deadline reason. Omit\n * `deadlineMs` → no watchdog (the `stale` phase is never reachable).\n * - It **checkpoints, optionally**. Provide `snapshotEvery` and every\n * progress unit is accounted to a `../snapshot` slice; once the cadence is\n * reached a `snapshot_write` Cmd is emitted (its handler is yours), so a\n * crashed run restarts from the last durable checkpoint rather than from\n * zero. Omit it → no checkpointing.\n *\n * ## Why the run lifecycle is INLINE (run-tracker not extracted)\n *\n * The phase union (`idle` / `running` / `stale` / `done` / `failed` / `cancelled`, a\n * discriminated union carrying only each phase's own data) and its bookkeeping\n * (`runId`, `startedAt`, `lastProgressAt`, `progressSeq`) live directly on this\n * slice rather than behind a `run-tracker` sub-knob.\n * Extraction earns its keep when two independent axes vary; here the lifecycle\n * has exactly one anchoring axis (this run), so a separate module would be a\n * premature seam. The two genuinely independent axes — the safety DEADLINE and\n * the periodic SNAPSHOT — ARE extracted (`../deadline`, `../snapshot`), and the\n * stage list is a `../work-queue` slice (`QueueItem<Stage>[]`) whose every\n * status flip is DELEGATED to the blessed `../work-queue/ops` (`enqueueOp` /\n * `claimNextOp` / `markDoneOp`) — never re-rolled here. The lifecycle is the\n * glue between them.\n *\n * ## The two non-negotiables (canon)\n *\n * - **Durable** — the slice is plain data (the stage queue is a\n * JSON-serializable `QueueItem<Stage>[]`, the snapshot slice is plain\n * numbers), so it survives DO eviction / reload.\n * - **Replayable** — every transition is a pure verb returning new state +\n * Cmds; nothing here reads the clock or RNG. Time arrives as an `at` / `atMs`\n * parameter. The knob ships no I/O.\n *\n * ## Typical wiring\n *\n * const run = createMonitoredRun<MyStage, MyState>({\n * stages: [\"plan\", \"test\", \"report\"],\n * deadlineMs: 10 * 60_000,\n * snapshotEvery: 25,\n * });\n *\n * cmds: [run.checkpoint],\n * init: (loaded) => loaded ? [loaded, []] : [{ run: run.init() }, []],\n * update: {\n * begin: (s, m) => lift(s, run.start(s.run, m.runId, m.at)),\n * advance: (s, m) => lift(s, run.advance(s.run, checkpoint(s), m.result, m.at)),\n * progress: (s, m) => lift(s, run.progress(s.run, checkpoint(s), m.at)),\n * deadline_exceeded: (s, m) => lift(s, run.onDeadline(s.run, m)),\n * boot: (s, m) => lift(s, run.boot(s.run, m.at)),\n * snapshot_write_ok: (s, m) => [{ ...s, run: run.confirmSnapshot(s.run, m) }, []],\n * snapshot_write_err: (s) => [s, []],\n * },\n * subs: [{ type: \"timer\", deps: (s) => run.timer(s.run) }],\n *\n * // and where it runs — the checkpoint write is the handler you write (the\n * // engine's `run` imported under another name, since `run` is the knob):\n * runMachine(machine, {\n * interpret: {\n * snapshot_write: async (cmd, { ok, err }) => {\n * try { await r2.put(cmd.key, cmd.payload); return ok(undefined); }\n * catch (cause) { return err({ _tag: \"snapshot_write_failed\", cause }); }\n * },\n * },\n * });\n */\n\nimport type { Cmd, TimerDeps } from \"../../../index\";\nimport {\n createSnapshot,\n type SnapshotSavedMsg,\n type SnapshotState,\n type SnapshotWriteCmd,\n} from \"../../persistence/snapshot\";\nimport {\n type DeadlineExceeded,\n type DeadlineSub,\n deadlineSub,\n nextTimer,\n} from \"../../resilience/deadline\";\nimport type { QueueItem } from \"../../work-queue\";\nimport { queueAdapter } from \"../../work-queue/adapter\";\n\n// ===========================================================================\n// Config — the knob. Every field optional; omit a field → omit its gate.\n// ===========================================================================\n\n/**\n * The monitored-run knob. EVERY field is optional:\n * - omit `stages` → single-shot run (no pipeline; one `advance` finishes).\n * - omit `deadlineMs` → no no-progress watchdog (the run is never auto-failed).\n * - omit `snapshotEvery`→ no periodic checkpoint (no `snapshot_write` Cmds).\n *\n * An empty config `{}` is a valid single-shot, unwatched, uncheckpointed run.\n *\n * `Stage` is the consumer's stage identifier — any JSON-serializable value\n * (a string id, a `{ kind, ... }` record). It rides through the stage queue\n * unchanged so the consumer's reducer can branch on the current stage to emit\n * that stage's outstanding effect (the audit machine's `outstandingEffect`).\n */\nexport interface MonitoredRunConfig<Stage> {\n /**\n * The ordered stages of the pipeline. Position survives eviction (lives in\n * the Model's stage queue). Omit for a single-shot run. An empty array is\n * treated as single-shot too — there is nothing to pipeline through.\n */\n readonly stages?: readonly Stage[];\n /**\n * No-progress watchdog budget, in ms. The safety deadline is armed at\n * `lastProgressAt + deadlineMs` and re-armed on every progress event. Omit\n * for no watchdog.\n */\n readonly deadlineMs?: number;\n /**\n * Checkpoint cadence — write a durable snapshot once this many progress\n * units have accumulated. Threaded straight into `../snapshot`'s `every`.\n * Omit for no checkpointing.\n */\n readonly snapshotEvery?: number;\n /**\n * Store key the rolling checkpoint is written under. Forwarded to\n * `../snapshot`'s `key`; defaults to that module's `@@snapshot`. Ignored\n * when `snapshotEvery` is omitted.\n */\n readonly snapshotKey?: string;\n}\n\n// ===========================================================================\n// Slice — the Model field this knob owns. Run lifecycle INLINE + composed bricks.\n// ===========================================================================\n\n/**\n * Why a run terminated as `failed`. Discriminated so each reason carries only\n * its own data (pattern 11):\n * - `deadline` — the no-progress watchdog fired (`onDeadline`).\n * - `stage` — the consumer reported a stage failure via `advance(..., fail)`.\n *\n * `stale` is a SEPARATE phase (not a fail reason): a run is `stale` only by an\n * explicit consumer signal that progress has wedged WITHOUT the deadline having\n * fired — a soft, recoverable observation. The deadline firing is a hard\n * terminal (`failed { reason: \"deadline\" }`).\n *\n * The `stage` variant carries the FAILING STAGE VALUE (the `Stage` itself), not\n * a positional index: done stages leave the queue (`markDoneOp` filters them),\n * so a residual index drifts as the pipeline advances. The stage value is the\n * stable, meaningful identifier — `undefined` for a single-shot run (no stage).\n */\nexport type RunFailure<Stage> =\n | { readonly reason: \"deadline\"; readonly at: number }\n | {\n readonly reason: \"stage\";\n readonly stage: Stage | undefined;\n readonly error: unknown;\n };\n\n/**\n * The run bookkeeping every phase carries in common, split out so each arm of\n * the `MonitoredRunState` discriminated union names only the data it ADDS\n * (`runId` on started phases, `failure` on `failed`) — the same discipline the\n * sibling `PaginatorState` uses for its shared `seen` / `pages` counters. Plain\n * data end to end so the whole slice survives DO eviction / reload:\n *\n * - `stepStates` — the stage pipeline as a `work-queue` of stage items.\n * Empty for a single-shot run. The ONE `running` item is\n * the current stage; `done` items are retired stages\n * (they leave the queue via `markDoneOp`, so the queue\n * shrinks as the pipeline advances and is empty at\n * `done`). POSITION survives eviction because this list\n * is durable.\n * - `startedAt` — `at` of `start`. Observability; never compared in a verb.\n * - `lastProgressAt` — `at` of the most recent progress event; the watchdog\n * base. Re-armed by `progress` / `advance`.\n * - `progressSeq` — monotonic progress counter; keys the safety alarm Sub\n * id so every bump retires the old alarm and arms a new\n * one (the audit subs' `safety:{progressSeq}` discipline).\n * - `snapshot` — the `../snapshot` slice (cadence counter + durable\n * watermark). Always present (uniform shape); the gate\n * is simply never driven when `snapshotEvery` is omitted.\n */\ninterface RunCore<Stage> {\n readonly stepStates: readonly QueueItem<Stage>[];\n readonly startedAt: number;\n readonly lastProgressAt: number;\n readonly progressSeq: number;\n readonly snapshot: SnapshotState;\n}\n\n/**\n * The slice — a discriminated union on `phase` so each phase carries ONLY its\n * own data and impossible combinations are unrepresentable (pattern 11), the\n * same idiom the sibling `CircuitState` / `PaginatorState` / `RunFailure` follow:\n *\n * - `idle` — created but never started. Carries no `runId` (identity is\n * host-minted at `start`) and no `failure`: \"not started\" is its\n * OWN phase, not a `running` run wearing an empty-string `runId`\n * sentinel. `start` is the only transition out, so no verb has to\n * guard \"has this actually started?\" — `phase === \"idle\"` says it.\n * - `running` — the run is live. Carries the host-minted `runId` and the\n * `RunCore` pipeline position + watchdog bookkeeping.\n * - `stale` — a soft \"no progress observed\" mark the consumer can set and\n * clear; still resumable (a `progress` clears it back to\n * `running`). Distinct from a fired deadline, which is terminal.\n * - `done` — every stage retired (or the single-shot `advance` landed).\n * - `failed` — terminal failure; the typed `failure` reason rides ONLY this\n * arm, so it is present iff `phase === \"failed\"` BY CONSTRUCTION —\n * there is no nullable side field to keep in sync.\n * - `cancelled` — the consumer stopped the run from outside (`cancel`). Its own\n * phase rather than a `RunFailure` reason, on the same argument\n * that keeps `stale` a phase: a caller who asked for the stop did\n * not suffer a failure, and every \"did this fail?\" reader would\n * otherwise have to except one reason. Terminal, so it re-enters\n * no verb and arms no watchdog.\n *\n * `runId` is the host-minted run identity, stamped at `start` (the machine never\n * mints — see the audit seed's \"run identity is host-minted\"). It rides every\n * started phase, but not `idle`. `cancelled` is the one phase reachable from BOTH\n * sides of that line, so its `runId` is `string | null`: `null` says the run was\n * cancelled before it ever started and there was no identity to mint, which is a\n * different fact from a cancelled live run and stays a different value.\n */\nexport type MonitoredRunState<Stage> =\n | (RunCore<Stage> & { readonly phase: \"idle\" })\n | (RunCore<Stage> & { readonly phase: \"running\"; readonly runId: string })\n | (RunCore<Stage> & { readonly phase: \"stale\"; readonly runId: string })\n | (RunCore<Stage> & { readonly phase: \"done\"; readonly runId: string })\n | (RunCore<Stage> & {\n readonly phase: \"failed\";\n readonly runId: string;\n readonly failure: RunFailure<Stage>;\n })\n | (RunCore<Stage> & {\n readonly phase: \"cancelled\";\n readonly runId: string | null;\n readonly at: number;\n });\n\n/**\n * The live (still-timed) phases — `running` or `stale`. The verbs narrow to this\n * before touching the watchdog bookkeeping (`runId` / `progressSeq`), so a\n * settled (`done` / `failed` / `cancelled`) or never-started (`idle`) slice can never be bumped\n * — the narrow replaces the old `runId === \"\"` born-live guard.\n */\ntype LiveRun<Stage> = Extract<\n MonitoredRunState<Stage>,\n { readonly phase: \"running\" | \"stale\" }\n>;\n\n/**\n * The ENDED phases — a run that finished (`done`) or was stopped from outside\n * (`cancelled`). Every arm here was started or explicitly stopped, so `idle` is\n * out and `runId` is present on the whole union: a consumer holding one of these\n * reads the run identity without a `phase` guard the value can never fail.\n *\n * It is the resolved half of a drive: a promise that resolves only on an ended\n * run should say so in its type rather than resolve `MonitoredRunState` and\n * leave every reader to re-prove the `idle` arm impossible (#155). `cancelled`\n * keeps its `string | null` `runId` unchanged — a run cancelled before it ever\n * started genuinely has no identity, and that stays a different value here.\n */\nexport type EndedRun<Stage> = Extract<\n MonitoredRunState<Stage>,\n { readonly phase: \"done\" | \"cancelled\" }\n>;\n\n/**\n * The outcome a consumer reports to `advance`: the current stage either\n * succeeded (retire it, claim the next) or failed (terminate the run). Plain\n * data — the consumer parses its stage result at its own boundary and hands a\n * settled outcome here.\n */\nexport type StageResult =\n | { readonly kind: \"ok\" }\n | { readonly kind: \"fail\"; readonly error: unknown };\n\n// ===========================================================================\n// Cmd + Msg — re-exported from the composed bricks so consumers wire one import.\n// ===========================================================================\n\n/** The checkpoint-write Cmd, generic over the consumer's checkpoint value `V`. */\nexport type MonitoredRunCmd<V> = SnapshotWriteCmd<V>;\n\n/** The deadline Msg the safety alarm dispatches when the watchdog fires. */\nexport type MonitoredRunTimerMsg = DeadlineExceeded;\n\n// ===========================================================================\n// Deadline-id family. The safety alarm is keyed on `progressSeq` so a progress\n// event retires the old alarm and arms a new one (single-shot, re-armed by id\n// bump — the audit subs discipline).\n// ===========================================================================\n\nfunction safetyTimerId(runId: string, progressSeq: number): string {\n return `monitored:safety:${runId}:${progressSeq}`;\n}\n\n// ===========================================================================\n// The knob factory.\n// ===========================================================================\n\n/**\n * Build a monitored-run knob from `config`. `Stage` is the consumer's stage\n * identifier type; `V` is the consumer's checkpoint value type (what\n * `snapshotEvery` checkpoints). Returns the uniform L2 knob contract.\n *\n * No `rng` parameter: unlike `resilient-call`, nothing here jitters — the only\n * external inputs are time (an `at` / `atMs` parameter) and the consumer's\n * stage outcomes.\n */\nexport function createMonitoredRun<Stage, V = unknown>(\n config: MonitoredRunConfig<Stage> = {},\n) {\n const stages = config.stages ?? [];\n const isPipeline = stages.length > 0;\n\n // The stage-queue seam, bound once. Every stage status flip delegates to the\n // `QueueAdapter` verbs (`wq.enqueue` / `wq.claim` / `wq.markDone`), so the\n // `QueueItem<Stage>` shape stays insulated behind them.\n const wq = queueAdapter<Stage>();\n\n // The snapshot sub-knob is built only when `snapshotEvery` is configured;\n // absence is `null` so the progress gate short-circuits to \"no checkpoint\".\n // When present, the slice still carries a default SnapshotState so the shape\n // is uniform across configs — the gate simply never records into it.\n const snap =\n config.snapshotEvery !== undefined\n ? createSnapshot<V>({\n every: config.snapshotEvery,\n ...(config.snapshotKey !== undefined\n ? { key: config.snapshotKey }\n : {}),\n })\n : null;\n\n // The snapshot knob to actually drive: `snap` when configured, else a single\n // inert default (`every: 1`) bound ONCE here. Its `record` is never invoked\n // (the progress gate short-circuits when `snap` is null), so the cadence value\n // is immaterial — one shared stateless knob of pure functions stands in for\n // the disabled-checkpoint case across init/boot/handlers.\n const activeSnap = snap ?? createSnapshot<V>({ every: 1 });\n\n /** The starting slice for a never-started run: the `idle` phase, no `runId`. */\n function init(): MonitoredRunState<Stage> {\n return {\n phase: \"idle\",\n stepStates: [],\n startedAt: 0,\n lastProgressAt: 0,\n progressSeq: 0,\n // A default snapshot slice even without the brick → uniform shape. When\n // `snap` is null the inert `activeSnap` default supplies the slice; its\n // `record` is never called, so the cadence value is immaterial.\n snapshot: activeSnap.init(),\n };\n }\n\n /**\n * Seed the stage queue from the configured stages: every stage enqueued\n * `pending`, then the first claimed `running`. `at` stamps the claim. PURE —\n * the work-queue ops are pure array transforms; ids are positional (`stage:i`)\n * so they are deterministic and replayable (no `crypto`, no `Math.random`).\n */\n function seedStages(at: number): readonly QueueItem<Stage>[] {\n let queue: readonly QueueItem<Stage>[] = [];\n stages.forEach((stage, i) => {\n queue = wq.enqueue(queue, stage, at, `stage:${i}`).next;\n });\n // Claim the first stage as the running position. A non-empty pipeline always\n // has a pending head here, so the claim succeeds.\n const claimed = wq.claim(queue, at);\n return claimed ? claimed.next : queue;\n }\n\n /** The current (running) stage item, or `undefined` for a single-shot run. */\n function currentStage(\n s: MonitoredRunState<Stage>,\n ): QueueItem<Stage> | undefined {\n return s.stepStates.find((i) => i.status === \"running\");\n }\n\n // === Verb: start =========================================================\n\n /**\n * Start (or restart) the run for `runId` at time `at`. Seeds the stage queue\n * (if a pipeline) and enters `running` with the watchdog clock primed. Stamps\n * `runId` from the host — the machine never mints (invariant: identity is\n * host-minted, parsed at the boundary). PURE.\n */\n function start(\n s: MonitoredRunState<Stage>,\n runId: string,\n at: number,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n // Build the `running` arm explicitly rather than spreading `...s`: a restart\n // from `failed` must NOT carry the old `failure` field forward (the `running`\n // arm has none), and a start from `idle` has no `runId` to preserve. Only the\n // snapshot slice threads through.\n const next: MonitoredRunState<Stage> = {\n phase: \"running\",\n runId,\n stepStates: isPipeline ? seedStages(at) : [],\n startedAt: at,\n lastProgressAt: at,\n progressSeq: 0,\n snapshot: s.snapshot,\n };\n return [next, []];\n }\n\n /**\n * Bump the watchdog clock: set `lastProgressAt`/`progressSeq` so the safety\n * alarm's id changes and the engine re-arms a fresh deadline. Shared by\n * `progress` and `advance`. PURE.\n */\n function bumpProgress(s: LiveRun<Stage>, at: number): LiveRun<Stage> {\n return { ...s, lastProgressAt: at, progressSeq: s.progressSeq + 1 };\n }\n\n /**\n * Account one progress unit to the snapshot slice (when the brick is\n * configured), returning the new slice + any checkpoint Cmd. `payload` is the\n * consumer's checkpoint value to persist. PURE — `snap.record` is pure, the\n * write is realized only when the runtime performs the emitted Cmd.\n */\n function recordSnapshot(\n s: MonitoredRunState<Stage>,\n payload: V,\n at: number,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n if (snap === null) return [s, []];\n const [snapshot, cmds] = snap.record(s.snapshot, payload, at);\n // `snap.record` only ever emits `SnapshotWriteCmd<V>` (its sole Cmd), but\n // its signature widens to `readonly Cmd[]`. Narrow back to this knob's\n // precise Cmd union so a consumer's host Cmd type can stay exact.\n return [{ ...s, snapshot }, cmds as readonly MonitoredRunCmd<V>[]];\n }\n\n // === Verb: progress ======================================================\n\n /**\n * Record forward progress: bump the watchdog (re-arming the safety alarm),\n * clear any `stale` mark back to `running`, and account a snapshot unit\n * (emitting a checkpoint Cmd on a cadence hit). A no-op unless the run is live\n * (`running` / `stale`) — a late progress event on a settled (`done` /\n * `failed`) or never-started (`idle`) run is ignored. PURE.\n */\n function progress(\n s: MonitoredRunState<Stage>,\n payload: V,\n at: number,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n if (s.phase !== \"running\" && s.phase !== \"stale\") return [s, []];\n const bumped: MonitoredRunState<Stage> = {\n ...bumpProgress(s, at),\n phase: \"running\",\n };\n return recordSnapshot(bumped, payload, at);\n }\n\n // === Verb: markStale =====================================================\n\n /**\n * Mark the run `stale` — a soft \"no progress observed\" signal the consumer\n * raises WITHOUT the deadline having fired (e.g. an upstream heartbeat gap).\n * Recoverable: the next `progress` flips it back to `running`. A no-op unless\n * the run is currently `running` (a settled run cannot go stale). Does NOT\n * touch `progressSeq` — going stale is not progress, so the armed deadline\n * keeps counting toward the hard terminal. PURE.\n */\n function markStale(\n s: MonitoredRunState<Stage>,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n if (s.phase !== \"running\") return [s, []];\n return [{ ...s, phase: \"stale\" }, []];\n }\n\n // === Verb: cancel ========================================================\n\n /**\n * Stop the run from outside and settle it `cancelled` at `at`. The durable half\n * of an `AbortSignal`: the outcome is a phase in the slice, so a reload reads a\n * run that ended rather than one to resume.\n *\n * Legal from `idle` too, not just the live phases — a signal already aborted\n * when the run is asked for has to end it BEFORE `start`, and leaving it `idle`\n * would let the next boot start the very run the caller stopped. That is the\n * one arm where `runId` is `null`, built explicitly rather than spread so no\n * stale `failure` or absent `runId` rides along.\n *\n * A no-op on a settled run (`done` / `failed` / `cancelled`): a terminal outcome\n * is already the answer, and an abort landing after it must not overwrite it.\n * That also makes a second abort idempotent. Emits no Cmd — cancelling issues no\n * effect, and the in-flight ones it cannot recall are the consumer's to drain.\n * PURE.\n */\n function cancel(\n s: MonitoredRunState<Stage>,\n at: number,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n if (s.phase !== \"idle\" && s.phase !== \"running\" && s.phase !== \"stale\") {\n return [s, []];\n }\n const next: MonitoredRunState<Stage> = {\n phase: \"cancelled\",\n runId: s.phase === \"idle\" ? null : s.runId,\n at,\n stepStates: s.stepStates,\n startedAt: s.startedAt,\n lastProgressAt: s.lastProgressAt,\n progressSeq: s.progressSeq,\n snapshot: s.snapshot,\n };\n return [next, []];\n }\n\n // === Verb: advance =======================================================\n\n /**\n * Advance the pipeline on a stage outcome:\n *\n * - `result.kind === \"fail\"` → terminate `failed { reason: \"stage\" }`,\n * carrying the failing stage index + error. No further stages run.\n * - `result.kind === \"ok\"`:\n * - single-shot run → finish to `done` (and force a final checkpoint\n * when checkpointing, so the last durable snapshot is the terminal\n * state).\n * - pipeline → retire the current stage (`markDoneOp`) and claim the\n * next (`claimNextOp`). If none remain, finish to `done`. The new\n * position survives eviction.\n *\n * An advance is itself a progress event, so it bumps the watchdog (re-arming\n * the alarm at the new stage) and accounts a snapshot unit. A no-op on a\n * settled (`done` / `failed` / `cancelled`) or never-started (`idle`) run. PURE — `at` is the\n * only clock, ids are positional.\n */\n function advance(\n s: MonitoredRunState<Stage>,\n payload: V,\n result: StageResult,\n at: number,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n if (s.phase !== \"running\" && s.phase !== \"stale\") return [s, []];\n\n // Stage failure → hard terminal. Carry the failing stage VALUE (stable),\n // `undefined` for a single-shot run with no stage.\n if (result.kind === \"fail\") {\n const failed: MonitoredRunState<Stage> = {\n ...s,\n phase: \"failed\",\n failure: {\n reason: \"stage\",\n stage: currentStage(s)?.input,\n error: result.error,\n },\n };\n return [failed, []];\n }\n\n // Success: bump the watchdog first (an advance is progress), then move the\n // pipeline position.\n const bumped = bumpProgress(s, at);\n\n // Single-shot: one advance finishes the run.\n if (!isPipeline) {\n const done: MonitoredRunState<Stage> = { ...bumped, phase: \"done\" };\n // Force a terminal checkpoint so the last durable snapshot is `done`,\n // not whatever the cadence happened to land on.\n return finishWithSnapshot(done, payload, at);\n }\n\n // Pipeline: retire the running stage, claim the next.\n const current = currentStage(s);\n const afterDone =\n current !== undefined\n ? wq.markDone(s.stepStates, current.id).next\n : s.stepStates;\n const claimed = wq.claim(afterDone, at);\n\n if (claimed === null) {\n // No more stages — the pipeline is complete.\n const done: MonitoredRunState<Stage> = {\n ...bumped,\n phase: \"done\",\n stepStates: afterDone,\n };\n return finishWithSnapshot(done, payload, at);\n }\n\n // Advance to the next stage — POSITION now points at the claimed item.\n const moved: MonitoredRunState<Stage> = {\n ...bumped,\n stepStates: claimed.next,\n };\n return recordSnapshot(moved, payload, at);\n }\n\n /**\n * Finish helper: force a terminal checkpoint when checkpointing is enabled\n * (so the durable snapshot is the terminal state), else just settle. Shared\n * by single-shot finish and pipeline completion. PURE.\n */\n function finishWithSnapshot(\n s: MonitoredRunState<Stage>,\n payload: V,\n at: number,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n if (snap === null) return [s, []];\n const [snapshot, cmds] = snap.force(s.snapshot, payload, at);\n // Same narrowing as `recordSnapshot`: `snap.force` emits only the write Cmd.\n return [{ ...s, snapshot }, cmds as readonly MonitoredRunCmd<V>[]];\n }\n\n // === Verb: onDeadline ====================================================\n\n /**\n * The safety alarm fired. Two outcomes by deadline-id match:\n *\n * - the alarm matches the CURRENT `progressSeq` (no progress since it was\n * armed) → the run is wedged → terminate `failed { reason: \"deadline\" }`.\n * - the alarm matches an OLDER seq → a stale fire racing a progress event\n * that already re-armed the alarm → no-op (the engine retired this id;\n * tolerate a fire still in flight defensively).\n *\n * A no-op on a settled run (its alarm was reconciled away; tolerate a stale\n * fire) and on a NEVER-STARTED (`idle`) run (`subs` armed nothing for it, so\n * any alarm reaching it is a rogue fire that must not un-start it). PURE —\n * `msg.atMs` stamps the failure.\n */\n function onDeadline(\n s: MonitoredRunState<Stage>,\n msg: MonitoredRunTimerMsg,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n // Only a live (`running` / `stale`) run is watched. A settled (`done` /\n // `failed`) run's alarm was reconciled away, and a never-started (`idle`) run\n // — which has no host-minted `runId` and armed nothing — must not be\n // un-started by a born-live/rogue fire. The phase narrow IS the guard the old\n // `runId === \"\"` sentinel stood in for.\n if (s.phase !== \"running\" && s.phase !== \"stale\") return [s, []];\n // Only the alarm armed against the current progressSeq is live. A bumped\n // seq means progress re-armed it; an older fire is stale.\n if (msg.id !== safetyTimerId(s.runId, s.progressSeq)) return [s, []];\n const failed: MonitoredRunState<Stage> = {\n ...s,\n phase: \"failed\",\n failure: { reason: \"deadline\", at: msg.atMs },\n };\n return [failed, []];\n }\n\n // === Verb: boot ==========================================================\n\n /**\n * Resume after a reload (cold wake). Re-seeds the watchdog clock to `at` so\n * the safety alarm re-arms from NOW (the pre-crash `lastProgressAt` would\n * immediately fire a deadline that already elapsed during downtime — a cold\n * wake is not a no-progress wedge). Bumps `progressSeq` so the alarm gets a\n * fresh id, and resets the snapshot cadence counter (un-checkpointed\n * pre-crash progress is moot — recovery resumes from the last durable\n * checkpoint). The pipeline POSITION (`stepStates`) is preserved untouched —\n * that is the whole point of staging: resume at the same stage.\n *\n * A no-op on a settled (`done` / `failed` / `cancelled`) or never-started (`idle`) run. Emits\n * NO Cmd — re-emitting the current stage's outstanding effect is the CONSUMER's\n * job (it knows the per-stage Cmd), the audit machine's `outstandingEffect(stage)`\n * pattern. PURE.\n */\n function boot(\n s: MonitoredRunState<Stage>,\n at: number,\n ): readonly [MonitoredRunState<Stage>, readonly MonitoredRunCmd<V>[]] {\n if (s.phase !== \"running\" && s.phase !== \"stale\") return [s, []];\n // `snap.boot` returns the reducer-cell shape `[SnapshotState, Cmd[]]`\n // (uniform across every sibling knob); a boot emits no Cmd, so take only the\n // slice. Assigning the tuple itself would put an ARRAY where a SnapshotState\n // belongs — a type error AND a non-round-trippable slice.\n const [bootedSnapshot] = activeSnap.boot(s.snapshot);\n const reseeded: MonitoredRunState<Stage> = {\n ...s,\n phase: \"running\",\n lastProgressAt: at,\n progressSeq: s.progressSeq + 1,\n snapshot: bootedSnapshot,\n // Any stage left `running` mid-flight stays the running position; the\n // queue is already correct (a single in-flight stage). resetRunningOp is\n // NOT applied — unlike a work pool, a monitored run has exactly one\n // in-flight stage and resuming it (not re-queuing it) is the correct\n // cold-wake behavior.\n };\n return [reseeded, []];\n }\n\n // === Timers ==============================================================\n\n /**\n * The no-progress safety deadline, listed only while the run is live\n * (`running` / `stale`) AND a `deadlineMs` is configured. Its id is keyed on\n * `progressSeq`, so every progress event retires the old alarm and arms a\n * fresh one at the new `lastProgressAt + deadlineMs` — a self-rearming\n * watchdog with no manual `clearTimeout`. A settled run lists none.\n *\n * A NEVER-STARTED slice (`init()`) is its OWN `idle` phase, not a `running` run\n * with an empty `runId`. The `phase !== \"running\" && phase !== \"stale\"` narrow\n * therefore excludes it structurally — no separate `runId === \"\"` sentinel gate\n * is needed. Were `idle` armed, it would fire a deadline at `0 + deadlineMs`\n * (already in the past) and auto-fail a run that never began — the \"born live\"\n * defect. `start` is the only transition out of `idle`, so the watchdog exists\n * exactly once a real run is in flight.\n */\n function deadlines(s: MonitoredRunState<Stage>): readonly DeadlineSub[] {\n if (config.deadlineMs === undefined) return [];\n if (s.phase !== \"running\" && s.phase !== \"stale\") return [];\n return [\n deadlineSub(\n safetyTimerId(s.runId, s.progressSeq),\n s.lastProgressAt + config.deadlineMs,\n ),\n ];\n }\n\n /**\n * The built-in `timer` Sub's deps for the watchdog: `deadlineMs` counted from\n * `lastProgressAt`, the instant the current alarm was armed. Its fire is a\n * `deadline_exceeded` Msg; route it to `onDeadline`. Declare\n * `{ type: \"timer\", deps: (s) => run.timer(s.run) }`. PURE.\n */\n function timer(\n s: MonitoredRunState<Stage>,\n ): TimerDeps<MonitoredRunTimerMsg> | null {\n return nextTimer(deadlines(s), s.lastProgressAt);\n }\n\n /**\n * Fold a confirmed checkpoint write into the slice — advances the snapshot\n * watermark (forward-only). A no-op pass-through when checkpointing is\n * disabled. PURE. Fold the `snapshot_write_ok` Msg here.\n */\n function confirmSnapshot(\n s: MonitoredRunState<Stage>,\n msg: SnapshotSavedMsg<V>,\n ): MonitoredRunState<Stage> {\n if (snap === null) return s;\n // `snap.confirm` returns the reducer-cell shape `[SnapshotState, Cmd[]]`\n // (an ack emits nothing). Take only the slice — folding the whole tuple into\n // `snapshot` would store an ARRAY where a SnapshotState belongs (a type error\n // and a non-round-trippable slice).\n const [confirmed] = snap.confirm(s.snapshot, msg);\n return { ...s, snapshot: confirmed };\n }\n\n return {\n /** The `snapshot_write` Cmd def — list it in `cmds` when checkpointing. */\n checkpoint: activeSnap.write,\n init,\n start,\n progress,\n markStale,\n cancel,\n advance,\n onDeadline,\n boot,\n confirmSnapshot,\n deadlines,\n timer,\n };\n}\n\n/**\n * Lift a knob result `[slice, cmds]` into a host `[State, cmds]` where the\n * slice lives at `state.run`. Convenience for the common single-slice host;\n * hosts with a differently-named field spread by hand. PURE — a thin record\n * rebuild, no clock / RNG.\n */\nexport function liftRun<\n S extends { run: MonitoredRunState<Stage> },\n Stage,\n C extends Cmd,\n>(\n state: S,\n [slice, cmds]: readonly [MonitoredRunState<Stage>, readonly C[]],\n): readonly [S, readonly C[]] {\n return [{ ...state, run: slice }, cmds];\n}\n\nexport type { SnapshotSavedMsg, SnapshotWriteCmd };\n"]}
|