botmux-workflow-core 3.10.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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/cjs/control.cjs +80 -0
  4. package/dist/cjs/control.cjs.map +7 -0
  5. package/dist/cjs/engine.cjs +380 -0
  6. package/dist/cjs/engine.cjs.map +7 -0
  7. package/dist/cjs/events.cjs +19 -0
  8. package/dist/cjs/events.cjs.map +7 -0
  9. package/dist/cjs/gate-policy.cjs +55 -0
  10. package/dist/cjs/gate-policy.cjs.map +7 -0
  11. package/dist/cjs/host-bindings.cjs +285 -0
  12. package/dist/cjs/host-bindings.cjs.map +7 -0
  13. package/dist/cjs/host-contract.cjs +19 -0
  14. package/dist/cjs/host-contract.cjs.map +7 -0
  15. package/dist/cjs/index.cjs +1523 -0
  16. package/dist/cjs/index.cjs.map +7 -0
  17. package/dist/cjs/runtime.cjs +6393 -0
  18. package/dist/cjs/runtime.cjs.map +7 -0
  19. package/dist/cjs/schema.cjs +1190 -0
  20. package/dist/cjs/schema.cjs.map +7 -0
  21. package/dist/esm/control.js +52 -0
  22. package/dist/esm/control.js.map +7 -0
  23. package/dist/esm/engine.js +352 -0
  24. package/dist/esm/engine.js.map +7 -0
  25. package/dist/esm/events.js +1 -0
  26. package/dist/esm/events.js.map +7 -0
  27. package/dist/esm/gate-policy.js +26 -0
  28. package/dist/esm/gate-policy.js.map +7 -0
  29. package/dist/esm/host-bindings.js +252 -0
  30. package/dist/esm/host-bindings.js.map +7 -0
  31. package/dist/esm/host-contract.js +1 -0
  32. package/dist/esm/host-contract.js.map +7 -0
  33. package/dist/esm/index.js +1481 -0
  34. package/dist/esm/index.js.map +7 -0
  35. package/dist/esm/runtime.js +6426 -0
  36. package/dist/esm/runtime.js.map +7 -0
  37. package/dist/esm/schema.js +1140 -0
  38. package/dist/esm/schema.js.map +7 -0
  39. package/dist/types/packages/workflow-core/src/control.d.ts +1 -0
  40. package/dist/types/packages/workflow-core/src/engine.d.ts +1 -0
  41. package/dist/types/packages/workflow-core/src/events.d.ts +1 -0
  42. package/dist/types/packages/workflow-core/src/gate-policy.d.ts +1 -0
  43. package/dist/types/packages/workflow-core/src/host-bindings.d.ts +1 -0
  44. package/dist/types/packages/workflow-core/src/host-contract.d.ts +1 -0
  45. package/dist/types/packages/workflow-core/src/index.d.ts +1 -0
  46. package/dist/types/packages/workflow-core/src/runtime.d.ts +7 -0
  47. package/dist/types/packages/workflow-core/src/schema.d.ts +1 -0
  48. package/dist/types/src/workflows/v3/artifact-contract.d.ts +40 -0
  49. package/dist/types/src/workflows/v3/core-control.d.ts +15 -0
  50. package/dist/types/src/workflows/v3/dag.d.ts +341 -0
  51. package/dist/types/src/workflows/v3/event-contract.d.ts +272 -0
  52. package/dist/types/src/workflows/v3/gate-policy.d.ts +11 -0
  53. package/dist/types/src/workflows/v3/host-bindings.d.ts +39 -0
  54. package/dist/types/src/workflows/v3/in-process-attempt-lease.d.ts +9 -0
  55. package/dist/types/src/workflows/v3/orchestrator.d.ts +174 -0
  56. package/dist/types/src/workflows/v3/portable-final-outputs.d.ts +36 -0
  57. package/dist/types/src/workflows/v3/portable-runtime.d.ts +82 -0
  58. package/dist/types/src/workflows/v3/runtime-host-contract.d.ts +181 -0
  59. package/dist/types/src/workflows/v3/shared-runtime.d.ts +16 -0
  60. package/package.json +83 -0
@@ -0,0 +1,341 @@
1
+ /**
2
+ * v3 DAG definition — schema, loader, validator, topological order.
3
+ *
4
+ * The v3 runtime (LLM-driven workflow) loads a hand-written `dag.json`,
5
+ * validates it, and walks it in topological order with deps gating. This
6
+ * module is the *schema half* of the engine: pure data + validation. Node.js
7
+ * filesystem loading lives in `dag-loader.ts`.
8
+ *
9
+ * Deliberately standalone from v0.2's `definition.ts` — v3 nodes are a much
10
+ * smaller surface (goal / host, no loop / decision / fanout) and coupling the
11
+ * two schemas would drag v0.2's complexity into the new engine. See
12
+ * `docs/design/2026-06-01-v3-mvp-engine-split.md` §3 for the authored shape.
13
+ */
14
+ /**
15
+ * `goal` — an LLM node driven by the `botmux-goal` skill (single goal, one
16
+ * ephemeral worker). `host` — a deterministic side-effect node (feishu-send
17
+ * / base write / schedule) that does NOT route through an LLM. MVP runs
18
+ * `goal` nodes end to end; `host` is reserved in the schema so the runtime
19
+ * can grow into it without a breaking change (it is rejected at validate
20
+ * time until the executor lands — see `validateDag`).
21
+ * `loop` — a composite node wrapping a bounded sub-pipeline (structured rework:
22
+ * `code -> test` until the test's structured result passes). The outer DAG
23
+ * stays acyclic — rework NEVER appears as a back-edge; it only exists inside
24
+ * an explicit loop body. See docs/design/2026-06-06-v3-structured-loop-design.md.
25
+ */
26
+ export type V3NodeType = 'goal' | 'host' | 'loop';
27
+ export declare const NODE_KINDS: readonly V3NodeType[];
28
+ /** First host slice: every registered executor is side-effecting and must be
29
+ * approved against its frozen runtime input. Keep this list in lockstep with
30
+ * the shared host-executor registry. */
31
+ export declare const V3_HOST_EXECUTORS: readonly ["feishu-send", "feishu-reply", "botmux-schedule"];
32
+ export type V3HostExecutorName = typeof V3_HOST_EXECUTORS[number];
33
+ /** Default per-node wall-clock budget when a node omits `timeoutSec`.
34
+ * Generous on purpose: completion is detected by the manifest watcher
35
+ * (seconds after the agent finishes), so the timeout only fires for hung
36
+ * nodes — a long default costs nothing on the happy path. The architect is
37
+ * prompted to set per-node `timeoutSec` explicitly for long tasks. */
38
+ export declare const DEFAULT_NODE_TIMEOUT_SEC = 1800;
39
+ /** Hard ceiling for per-node `timeoutSec` (4h) — rejects runaway budgets the
40
+ * architect might hallucinate while still allowing genuinely long tasks. */
41
+ export declare const MAX_NODE_TIMEOUT_SEC = 14400;
42
+ /** A humanGate frozen at authoring time — the runtime never lets a node
43
+ * add / skip a gate at runtime (design Q10). */
44
+ export interface V3HumanGate {
45
+ /** Approval-card body shown to the human reviewer. */
46
+ prompt: string;
47
+ /** Button option keys shown on the approval card. */
48
+ options?: string[];
49
+ /** Selecting any of these options maps to `resolution:'approved'`. */
50
+ approveOptions?: string[];
51
+ /** Empty = any operator allowed by the outer daemon permission gate. */
52
+ approvers?: string[];
53
+ }
54
+ export declare const DEFAULT_HUMAN_GATE_OPTIONS: readonly string[];
55
+ export declare const MAX_HUMAN_GATE_OPTIONS = 8;
56
+ export declare const MAX_HUMAN_GATE_OPTION_LENGTH = 32;
57
+ /**
58
+ * Declares that this node consumes an upstream node's products. MVP pulls the
59
+ * upstream node's *whole* manifest (all files) into this node's `inputs.json`;
60
+ * a per-file selector is deferred (design §2.3). Invariant: `from` MUST also
61
+ * appear in the node's `depends` — you can only read outputs of a node you
62
+ * wait for.
63
+ */
64
+ export interface V3InputRef {
65
+ /** Upstream nodeId whose manifest files become this node's inputs. */
66
+ from: string;
67
+ /** P3 per-file selector: pull ONE named product instead of the whole
68
+ * manifest. Exactly one of `name` (manifest logical name) / `path`
69
+ * (manifest relative path) when present. A selector that matches nothing
70
+ * at dispatch time is surfaced to the agent via `GoalInputs.omitted`
71
+ * (reason 'selectorMiss') — absence reads as a contract gap, not silence. */
72
+ select?: {
73
+ name?: string;
74
+ path?: string;
75
+ };
76
+ }
77
+ /**
78
+ * A normalized incoming edge (edge-activation design 2026-06-06 §1.1).
79
+ * Authored as either a plain string (`"build"`) or an object
80
+ * (`{ "from": "review", "when": {...} }`); validateDag normalizes both to this
81
+ * shape. No `when` = unconditional (source `done` ⇒ active). With `when`,
82
+ * the edge's activation is decided ONCE by the runtime reading the source's
83
+ * `result.json` and journaled as `edgeResolved` — never re-read afterwards.
84
+ *
85
+ * `from` values are deduped per node: P0 supports at most ONE edge per
86
+ * (from, to) pair, so `(from, to)` is a stable idempotency key for
87
+ * `edgeResolved`. Express OR over outcomes inside the source's structured
88
+ * result instead of authoring parallel conditional edges.
89
+ */
90
+ export interface V3DependRef {
91
+ from: string;
92
+ /** Predicate over the SOURCE node's structured result — same shape and
93
+ * validation as a loop exit predicate (`result.<key>` + exactly one
94
+ * comparison operator, declared + required + type-compatible). */
95
+ when?: V3EdgeWhen;
96
+ }
97
+ /** Edge predicates reuse the loop-exit predicate shape verbatim. */
98
+ export type V3EdgeWhen = V3LoopExitWhen;
99
+ /**
100
+ * Join semantics over a node's incoming edges (design §1.2). Evaluated ONCE,
101
+ * only after every incoming edge has settled (source done/skipped and any
102
+ * predicate journaled) — no early release, no loser cancellation in P0.
103
+ */
104
+ export type V3TriggerRule = 'all_success' | 'one_success' | {
105
+ quorum: number;
106
+ };
107
+ /**
108
+ * Per-node capability override (P2, edge-activation follow-up). Merged onto
109
+ * the bot's frozen `BotSnapshot` at dispatch time:
110
+ * - `model` picks a different model for THIS node (cost control: cheap
111
+ * models for research nodes, strong models for code nodes);
112
+ * - `systemPromptAppend` adds node-specific instructions to the goal file.
113
+ * Permission is deliberately not overridable: every workflow worker requires
114
+ * CLI bypass permission, and bots configured to disable it are rejected.
115
+ * `toolsSubset` is deferred — it needs a per-CLI capability matrix across the
116
+ * daemon init/worker/adapter chain (P2b).
117
+ */
118
+ export interface V3CapabilityOverride {
119
+ model?: string;
120
+ systemPromptAppend?: string;
121
+ }
122
+ export declare const MAX_OVERRIDE_MODEL_LENGTH = 64;
123
+ export declare const MAX_OVERRIDE_SYSTEM_PROMPT_APPEND = 8000;
124
+ /**
125
+ * Opt-in structured-output contract — a deliberately TINY subset of
126
+ * JSON-Schema (flat object, primitive-typed properties, optional required
127
+ * list). Hand-validated (no deps, repo style); anything outside the subset
128
+ * is rejected at validateDag time so the architect can never author a schema
129
+ * the runtime's validator cannot execute.
130
+ *
131
+ * NOT supported (first slice): nested schemas, array item types, patterns.
132
+ * `type:'array'|'object'` properties validate the TOP-LEVEL type only.
133
+ * `enum` is supported on STRING properties only (edge-activation design §1.3)
134
+ * — it is the decision-vocabulary anchor for edge predicates: validateDag
135
+ * cross-checks `equals`/`notEquals` operands against the source field's enum,
136
+ * so a typo'd decision value fails at validate time, not at runtime.
137
+ */
138
+ export interface V3ResultSchema {
139
+ type: 'object';
140
+ properties: Record<string, {
141
+ type: V3ResultFieldType;
142
+ enum?: string[];
143
+ }>;
144
+ required?: string[];
145
+ }
146
+ export type V3ResultFieldType = 'string' | 'number' | 'boolean' | 'array' | 'object';
147
+ /** Caps on the resultSchema subset (anti-runaway: a giant schema bloats the
148
+ * goal prompt and the validator). Checked at validateDag time. */
149
+ export declare const RESULT_SCHEMA_MAX_PROPERTIES = 32;
150
+ export declare const RESULT_SCHEMA_MAX_BYTES = 4096;
151
+ /** Caps on a string property's `enum` (anti prompt-bloat; counted inside the
152
+ * 4KB schema budget like everything else). */
153
+ export declare const RESULT_ENUM_MAX_VALUES = 16;
154
+ export declare const RESULT_ENUM_MAX_VALUE_LENGTH = 64;
155
+ /** Backstop ceiling for `maxIterations` — like the timeout cap, it rejects a
156
+ * runaway budget the architect might hallucinate; a human can still grant
157
+ * extra iterations one at a time once the loop blocks. */
158
+ export declare const MAX_LOOP_ITERATIONS = 20;
159
+ /** Cross-node revisit budgets (anti-infinite-loop). Two tiers:
160
+ * - PER-PAIR (source→target): how many times one node may revisit one ancestor
161
+ * before the run blocks — default 1 (a node sends each ancestor back once;
162
+ * expected multi-round rework belongs in a structured loop, not ad-hoc
163
+ * revisit). Pinpoints which edge is ping-ponging.
164
+ * - PER-RUN: total revisits across the whole run — a generous backstop so many
165
+ * distinct pairs (or many nodes revisiting) can't run away.
166
+ * Exhaustion blocks the run; a human grants +1 (revisitBudgetGranted). */
167
+ export declare const DEFAULT_REVISIT_BUDGET_PER_PAIR = 1;
168
+ export declare const DEFAULT_REVISIT_BUDGET_PER_RUN = 8;
169
+ /**
170
+ * Exit predicate over the exit node's structured result. Deliberately tiny:
171
+ * `path` is fixed to `result.<key>` (the resultSchema subset is flat, so there
172
+ * is nothing deeper to address) and exactly ONE comparison operator must be
173
+ * set. validateDag cross-checks the key against the exit node's resultSchema
174
+ * (declared AND required, operator type-compatible), so "field missing at
175
+ * runtime" is a validate-time impossibility, not a runtime branch.
176
+ *
177
+ * No `continue.when` counterpart — when the predicate does not match, the loop
178
+ * implicitly continues (until maxIterations). Two independent predicates
179
+ * would create undefined both-match / neither-match states.
180
+ */
181
+ export interface V3LoopExitWhen {
182
+ /** `result.<key>` — a key of the exit node's resultSchema. */
183
+ path: string;
184
+ equals?: string | number | boolean;
185
+ notEquals?: string | number | boolean;
186
+ gt?: number;
187
+ gte?: number;
188
+ lt?: number;
189
+ lte?: number;
190
+ }
191
+ export interface V3LoopExit {
192
+ /** Body nodeId whose structured result decides the loop's exit. */
193
+ node: string;
194
+ when: V3LoopExitWhen;
195
+ }
196
+ /** Which body node's final-iteration manifest is the loop's outward product
197
+ * (what downstream `inputs: [{from: <loopId>}]` reads). Defaults to the
198
+ * exit node, but a repair loop usually exports the WORKER's product (`code`),
199
+ * not the gate's (`test`). */
200
+ export interface V3LoopOutput {
201
+ from: string;
202
+ }
203
+ export interface V3Node {
204
+ /** Unique within the DAG; also used as a runDir path segment, so it is
205
+ * constrained to `[A-Za-z0-9._-]`. */
206
+ id: string;
207
+ type: V3NodeType;
208
+ /** Required + non-empty for `goal` nodes; the single-sentence objective. */
209
+ goal?: string;
210
+ /** Which bot/CLI runs this node. MVP dogfoods a single CLI, but the field
211
+ * is per-node so a mixed-backend DAG is a non-breaking extension. */
212
+ bot?: string;
213
+ /** Normalized incoming edges. Authored as `string | {from, when?}`;
214
+ * validateDag normalizes to `V3DependRef[]` (edge-activation design §1.1).
215
+ * Unconditional edges gate on source `done`; `when` edges additionally
216
+ * gate on the journaled `edgeResolved` verdict. */
217
+ depends: V3DependRef[];
218
+ /** Join semantics over incoming edges; defaults to 'all_success' (exactly
219
+ * today's behavior). Only meaningful on nodes with ≥1 incoming edge. */
220
+ triggerRule?: V3TriggerRule;
221
+ /** Per-node capability override (restrict/redirect only — see
222
+ * V3CapabilityOverride). Goal nodes (incl. loop body nodes) only; a loop
223
+ * composite never spawns a worker, so it rejects this field. */
224
+ override?: V3CapabilityOverride;
225
+ /** Upstream products to thread in as inputs (every `from` ⊆ `depends`). */
226
+ inputs: V3InputRef[];
227
+ /** Wall-clock budget in seconds; falls back to DEFAULT_NODE_TIMEOUT_SEC. */
228
+ timeoutSec?: number;
229
+ /** Optional human approval gate, evaluated *before* the node's work runs. */
230
+ humanGate?: V3HumanGate | null;
231
+ /** Opt-in structured-output contract: when set, the node must write a
232
+ * `result.json` (listed in its manifest files) matching this schema; a
233
+ * violation blocks (not fails) the node. Absent → zero behavior change. */
234
+ resultSchema?: V3ResultSchema;
235
+ /** Definition-level revisit exits (cross-node回溯). When this node's
236
+ * `result.json` returns `{ "status": "revisit", "revisitTo": "<A>" }`, the
237
+ * runtime may revisit ancestor node `<A>` — but ONLY if `<A>` is listed
238
+ * here. Default (absent / empty) = the node cannot revisit anything.
239
+ * validateDag enforces every entry is an ANCESTOR (transitive `depends`),
240
+ * so a revisit can never create a forward jump or a cycle in the run. */
241
+ revisitTo?: string[];
242
+ /** Deterministic executor invoked by the host runtime (never an LLM). */
243
+ executor?: V3HostExecutorName;
244
+ /** Frozen before the runtime gate; supports typed host bindings. */
245
+ input?: unknown;
246
+ /** Hard iteration bound; the loop blocks (recoverable, human can grant +1)
247
+ * when it is exhausted without the exit predicate matching. */
248
+ maxIterations?: number;
249
+ /** The per-iteration sub-pipeline. Goal nodes only — no nesting, no
250
+ * humanGate inside a body (both first-cut restrictions). */
251
+ body?: {
252
+ nodes: V3Node[];
253
+ };
254
+ /** Structured exit condition; not matching ⇒ implicit continue. */
255
+ exit?: V3LoopExit;
256
+ /** Previous-iteration products threaded into the NEXT iteration's inputs.
257
+ * Entries are `<bodyId>.result` | `<bodyId>.files` | `<bodyId>.manifest`. */
258
+ feedback?: string[];
259
+ /** Outward product projection (defaults to exit.node). */
260
+ output?: V3LoopOutput;
261
+ /** Only supported value (and the default): 'blocked'. */
262
+ onExhausted?: 'blocked';
263
+ /** Only supported value (and the default): 'fresh' — every iteration's every
264
+ * body node runs a fresh ephemeral worker. `resumeWithinLoop` is deferred. */
265
+ sessionPolicy?: 'fresh';
266
+ }
267
+ /** A `V3Node` narrowed to a goal node — `goal` is guaranteed present. This is
268
+ * what crosses into `runNode` (the pool only ever runs goal nodes in MVP). */
269
+ export interface V3GoalNode extends V3Node {
270
+ type: 'goal';
271
+ goal: string;
272
+ }
273
+ /** Narrowing guard: a validated goal node always has a non-empty `goal`. */
274
+ export declare function isGoalNode(node: V3Node): node is V3GoalNode;
275
+ export interface V3HostNode extends V3Node {
276
+ type: 'host';
277
+ executor: V3HostExecutorName;
278
+ input: unknown;
279
+ humanGate: V3HumanGate;
280
+ }
281
+ export declare function isHostNode(node: V3Node): node is V3HostNode;
282
+ /** A `V3Node` narrowed to a loop node — validateDag guarantees every loop
283
+ * field is present and normalized (output defaulted to exit.node, feedback
284
+ * defaulted to `[]`). */
285
+ export interface V3LoopNode extends V3Node {
286
+ type: 'loop';
287
+ maxIterations: number;
288
+ body: {
289
+ nodes: V3Node[];
290
+ };
291
+ exit: V3LoopExit;
292
+ feedback: string[];
293
+ output: V3LoopOutput;
294
+ }
295
+ /** Narrowing guard for validated loop nodes. */
296
+ export declare function isLoopNode(node: V3Node): node is V3LoopNode;
297
+ /**
298
+ * The expanded id a body node instance runs under in iteration N:
299
+ * `repairLoop.i001.code`. Path-safe by construction (loopId/bodyId are
300
+ * SEGMENT_RE, `.` is in the charset) and free of the `:` the blocked-card
301
+ * nonce uses as a separator. OPAQUE — never parse this string back; journal
302
+ * events carry a structured `loop: {loopId, iteration, bodyNodeId}` instead.
303
+ */
304
+ export declare function loopInstanceId(loopId: string, iteration: number, bodyNodeId: string): string;
305
+ export interface V3Dag {
306
+ /** Stable id for this run; used as the runDir name, so path-segment safe. */
307
+ runId: string;
308
+ nodes: V3Node[];
309
+ }
310
+ /** Thrown by `validateDag` / `loadDag` with every problem found, not just the
311
+ * first — authoring a DAG by hand is iterative, so surface the full list. */
312
+ export declare class DagValidationError extends Error {
313
+ readonly problems: string[];
314
+ constructor(problems: string[]);
315
+ }
316
+ /** Node ids and runId double as filesystem path segments under the runDir. */
317
+ export declare const V3_DAG_SEGMENT_RE: RegExp;
318
+ /**
319
+ * Validate an untrusted parsed value into a `V3Dag`. Pure — throws
320
+ * `DagValidationError` with the full problem list on any violation, otherwise
321
+ * returns a normalized dag (defaults filled, `humanGate: undefined` → `null`).
322
+ *
323
+ * Checks: runId shape; non-empty unique path-safe node ids; known `type`;
324
+ * `goal` non-empty for goal nodes; host executor/input/gate policy;
325
+ * `depends` reference existing nodes, no self-dep, no dup `from` (P0: one
326
+ * edge per (from,to)); edge predicates validated against the SOURCE's
327
+ * resultSchema (goal-with-schema sources only); `triggerRule` shape/bounds;
328
+ * `inputs.from` reference existing nodes AND appear in `depends`; acyclic
329
+ * (delegated to `topologicalOrder`, conditional edges included).
330
+ */
331
+ export declare function validateDag(raw: unknown): V3Dag;
332
+ /**
333
+ * Deterministic topological order via Kahn's algorithm. Ties (nodes with the
334
+ * same remaining in-degree available at once) are broken by ascending id so
335
+ * the schedule is stable across runs — important for reproducible journals.
336
+ * Throws if the graph contains a cycle (lists the offending nodes).
337
+ *
338
+ * Assumes `depends` already reference existing nodes; `validateDag` enforces
339
+ * that before calling here.
340
+ */
341
+ export declare function topologicalOrder(dag: V3Dag): string[];
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Durable workflow event contracts shared by the scheduler and host runtimes.
3
+ *
4
+ * Keep this module data-only. Journal persistence, Botmux sessions, workers,
5
+ * and provider adapters consume these shapes but must never leak into them.
6
+ */
7
+ export type GoalAsk = {
8
+ question: string;
9
+ options: string[];
10
+ freeText?: false;
11
+ } | {
12
+ question: string;
13
+ freeText: true;
14
+ options?: never;
15
+ };
16
+ export type GoalAnswer = {
17
+ selected: string;
18
+ by: string;
19
+ } | {
20
+ text: string;
21
+ by: string;
22
+ };
23
+ export type V3ErrorClass = 'workerError' | 'manifestInvalid' | 'resultInvalid' | 'timeout' | 'gateRejected' | 'cancelled';
24
+ export type V3RunFailureReason = 'allSinksSkipped';
25
+ export interface V3UncertainHostEffect {
26
+ nodeId: string;
27
+ instanceId: string;
28
+ attemptId: string;
29
+ executor: string;
30
+ errorCode: string;
31
+ }
32
+ export interface V3LoopRef {
33
+ loopId: string;
34
+ iteration: number;
35
+ bodyNodeId: string;
36
+ }
37
+ export type V3Event = {
38
+ type: 'runStarted';
39
+ runId: string;
40
+ } | {
41
+ type: 'nodeDispatched';
42
+ nodeId: string;
43
+ instanceId?: string;
44
+ attemptId: string;
45
+ loop?: V3LoopRef;
46
+ } | {
47
+ type: 'hostInputPrepared';
48
+ nodeId: string;
49
+ instanceId: string;
50
+ attemptId: string;
51
+ executor: string;
52
+ provider: string;
53
+ inputRef: {
54
+ path: string;
55
+ sha256: string;
56
+ bytes: number;
57
+ };
58
+ inputHash: string;
59
+ idempotencyKey: string;
60
+ idempotencyTtlMs: number;
61
+ approvalDigest: string;
62
+ } | {
63
+ type: 'hostEffectIntent';
64
+ nodeId: string;
65
+ instanceId: string;
66
+ attemptId: string;
67
+ executor: string;
68
+ provider: string;
69
+ inputRef: {
70
+ path: string;
71
+ sha256: string;
72
+ bytes: number;
73
+ };
74
+ inputHash: string;
75
+ idempotencyKey: string;
76
+ idempotencyTtlMs: number;
77
+ approvalDigest: string;
78
+ } | {
79
+ type: 'hostEffectUncertain';
80
+ nodeId: string;
81
+ instanceId: string;
82
+ attemptId: string;
83
+ executor: string;
84
+ reason: 'ttlExpired' | 'inputUnrecoverable' | 'outputUnrecoverable' | 'definitionMismatch' | 'unknownProvider' | 'inputHashMismatch' | 'providerUncertain';
85
+ errorCode: string;
86
+ } | {
87
+ type: 'hostEffectRetryDeferred';
88
+ nodeId: string;
89
+ instanceId: string;
90
+ attemptId: string;
91
+ retryCount: number;
92
+ nextRetryAt: number;
93
+ errorCode: string;
94
+ } | {
95
+ type: 'nodeWorkerFenceArmed';
96
+ nodeId: string;
97
+ instanceId?: string;
98
+ attemptId: string;
99
+ } | {
100
+ type: 'nodeSessionReady';
101
+ nodeId: string;
102
+ instanceId?: string;
103
+ attemptId: string;
104
+ sessionInfo: {
105
+ sessionId: string;
106
+ webPort?: number;
107
+ };
108
+ ptyLogPath?: string;
109
+ } | {
110
+ type: 'nodeSucceeded';
111
+ nodeId: string;
112
+ instanceId?: string;
113
+ attemptId: string;
114
+ manifestPath: string;
115
+ } | {
116
+ type: 'nodeFailed';
117
+ nodeId: string;
118
+ instanceId?: string;
119
+ attemptId: string;
120
+ errorClass: V3ErrorClass;
121
+ errorCode?: string;
122
+ message?: string;
123
+ } | {
124
+ type: 'nodeBlocked';
125
+ nodeId: string;
126
+ instanceId?: string;
127
+ attemptId: string;
128
+ errorClass: V3ErrorClass;
129
+ errorCode?: string;
130
+ message?: string;
131
+ ask?: GoalAsk;
132
+ revisitTo?: string;
133
+ } | {
134
+ type: 'nodeRetryRequested';
135
+ nodeId: string;
136
+ instanceId?: string;
137
+ previousAttemptId: string;
138
+ nextAttemptId: string;
139
+ reason: 'blockedRetry';
140
+ previousErrorClass?: V3ErrorClass;
141
+ previousErrorCode?: string;
142
+ resetGate?: boolean;
143
+ answer?: {
144
+ path: string;
145
+ preview: string;
146
+ by: string;
147
+ };
148
+ } | {
149
+ type: 'gateDispatched';
150
+ nodeId: string;
151
+ instanceId?: string;
152
+ waitId: string;
153
+ hostApproval?: {
154
+ attemptId: string;
155
+ approvalDigest: string;
156
+ inputHash: string;
157
+ };
158
+ } | {
159
+ type: 'gateResolved';
160
+ nodeId: string;
161
+ instanceId?: string;
162
+ waitId: string;
163
+ resolution: 'approved' | 'rejected';
164
+ by: string;
165
+ selected?: string;
166
+ hostApproval?: {
167
+ attemptId: string;
168
+ approvalDigest: string;
169
+ inputHash: string;
170
+ };
171
+ } | {
172
+ type: 'edgeResolved';
173
+ from: string;
174
+ to: string;
175
+ fromInstanceId?: string;
176
+ toInstanceId?: string;
177
+ sourceAttemptId: string;
178
+ active: boolean;
179
+ detail?: string;
180
+ } | {
181
+ type: 'nodeSkipped';
182
+ nodeId: string;
183
+ reason: 'triggerRuleUnsatisfied';
184
+ detail?: string;
185
+ } | ({
186
+ type: 'nodeCancelled';
187
+ nodeId: string;
188
+ instanceId?: string;
189
+ attemptId?: string;
190
+ detail?: string;
191
+ } & ({
192
+ reason: 'earlyReleaseLoser';
193
+ byNodeId: string;
194
+ } | {
195
+ reason: 'runCancelled';
196
+ cancelRequestId: string;
197
+ })) | {
198
+ type: 'nodeAttemptDrained';
199
+ nodeId: string;
200
+ instanceId?: string;
201
+ attemptId: string;
202
+ reason: 'terminalPeer' | 'obsoleteAttempt' | 'orphanRecovery' | 'runCancellation';
203
+ } | {
204
+ type: 'nodeRevisitRequested';
205
+ nodeId: string;
206
+ instanceId: string;
207
+ attemptId: string;
208
+ toNodeId: string;
209
+ reason?: string;
210
+ reasonPath?: string;
211
+ sourceManifestPath?: string;
212
+ targetPreviousManifestPath?: string;
213
+ } | {
214
+ type: 'nodeInstanceSuperseded';
215
+ nodeId: string;
216
+ instanceId: string;
217
+ byNodeId: string;
218
+ reason: 'refresh';
219
+ } | {
220
+ type: 'revisitBudgetGranted';
221
+ sourceNodeId?: string;
222
+ toNodeId?: string;
223
+ by: string;
224
+ reason?: string;
225
+ } | {
226
+ type: 'loopStarted';
227
+ loopId: string;
228
+ } | {
229
+ type: 'loopIterationStarted';
230
+ loopId: string;
231
+ iteration: number;
232
+ } | {
233
+ type: 'loopIterationDecision';
234
+ loopId: string;
235
+ iteration: number;
236
+ decision: 'exit' | 'continue' | 'exhausted';
237
+ detail?: string;
238
+ } | {
239
+ type: 'loopIterationGranted';
240
+ loopId: string;
241
+ fromIteration: number;
242
+ by?: string;
243
+ } | {
244
+ type: 'runCancelRequested';
245
+ cancelRequestId: string;
246
+ by: string;
247
+ reason?: string;
248
+ } | {
249
+ type: 'runCancelled';
250
+ cancelRequestId: string;
251
+ by: string;
252
+ uncertainHostEffects?: V3UncertainHostEffect[];
253
+ } | {
254
+ type: 'runSucceeded';
255
+ } | {
256
+ type: 'runFailed';
257
+ failedNodeId?: string;
258
+ reason?: V3RunFailureReason;
259
+ detail?: string;
260
+ } | {
261
+ type: 'runBlocked';
262
+ blockedNodeId: string;
263
+ };
264
+ export type StoredEvent = V3Event & {
265
+ ts: number;
266
+ };
267
+ export interface JournalMutation {
268
+ readonly events: readonly StoredEvent[];
269
+ append(event: V3Event, options?: {
270
+ durable?: boolean;
271
+ }): StoredEvent;
272
+ }
@@ -0,0 +1,11 @@
1
+ import { type V3HumanGate } from './dag.js';
2
+ export interface NormalizedGatePolicy {
3
+ prompt: string;
4
+ options: string[];
5
+ approveOptions: string[];
6
+ approvers: string[];
7
+ }
8
+ /** Normalize authored gate defaults without requiring a persistence adapter. */
9
+ export declare function normalizeGateWaitInput(gate: V3HumanGate): NormalizedGatePolicy;
10
+ export declare function selectedResolution(wait: Pick<NormalizedGatePolicy, 'options' | 'approveOptions'>, selected: string): 'approved' | 'rejected' | undefined;
11
+ export declare function canResolveGateWait(wait: Pick<NormalizedGatePolicy, 'approvers'>, by: string | undefined): boolean;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Typed bindings for v3 host-executor inputs.
3
+ *
4
+ * Host nodes cannot receive the goal worker's file-oriented `inputs.json` and
5
+ * ask an LLM to interpret it: the host must materialize one exact provider
6
+ * payload before approval. This module resolves the deliberately small
7
+ * binding language without doing any filesystem or provider I/O.
8
+ */
9
+ export type V3HostBindingRef = {
10
+ kind: 'params';
11
+ path: string[];
12
+ } | {
13
+ kind: 'context';
14
+ path: string[];
15
+ } | {
16
+ kind: 'result';
17
+ nodeId: string;
18
+ path: string[];
19
+ };
20
+ export interface V3HostBindingContext {
21
+ params: Readonly<Record<string, unknown>>;
22
+ context: Readonly<Record<string, string>>;
23
+ loadResult(nodeId: string): Promise<unknown>;
24
+ }
25
+ export declare class V3HostBindingError extends Error {
26
+ constructor(message: string);
27
+ }
28
+ export declare function composeV3HostGatePrompt(authoredPrompt: string, preview: string): string;
29
+ export declare function splitV3HostGatePrompt(prompt: string): {
30
+ authoredPrompt: string;
31
+ preview?: string;
32
+ };
33
+ export declare function parseV3HostBindingRef(ref: string): V3HostBindingRef;
34
+ /** Collect every ref and reject malformed `${...}` / `$ref` shapes early. */
35
+ export declare function collectV3HostBindingRefs(template: unknown): V3HostBindingRef[];
36
+ /** Resolve one host template into a plain JSON value. */
37
+ export declare function resolveV3HostInputTemplate(template: unknown, ctx: V3HostBindingContext): Promise<unknown>;
38
+ /** A bounded, redacted preview safe to append to a gate prompt. */
39
+ export declare function renderV3HostInputPreview(executor: string, input: unknown, inputHash: string): string;
@@ -0,0 +1,9 @@
1
+ import type { AttemptLeaseProvider } from './runtime-host-contract.js';
2
+ /**
3
+ * Lease provider for fresh, process-local executions.
4
+ *
5
+ * It can prove only closures observed by this exact provider instance. An
6
+ * attempt absent from the local ledger may belong to another live process, so
7
+ * restart recovery stays fail-closed instead of inventing a close proof.
8
+ */
9
+ export declare function createInProcessAttemptLeaseProvider(): AttemptLeaseProvider;