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.
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/cjs/control.cjs +80 -0
- package/dist/cjs/control.cjs.map +7 -0
- package/dist/cjs/engine.cjs +380 -0
- package/dist/cjs/engine.cjs.map +7 -0
- package/dist/cjs/events.cjs +19 -0
- package/dist/cjs/events.cjs.map +7 -0
- package/dist/cjs/gate-policy.cjs +55 -0
- package/dist/cjs/gate-policy.cjs.map +7 -0
- package/dist/cjs/host-bindings.cjs +285 -0
- package/dist/cjs/host-bindings.cjs.map +7 -0
- package/dist/cjs/host-contract.cjs +19 -0
- package/dist/cjs/host-contract.cjs.map +7 -0
- package/dist/cjs/index.cjs +1523 -0
- package/dist/cjs/index.cjs.map +7 -0
- package/dist/cjs/runtime.cjs +6393 -0
- package/dist/cjs/runtime.cjs.map +7 -0
- package/dist/cjs/schema.cjs +1190 -0
- package/dist/cjs/schema.cjs.map +7 -0
- package/dist/esm/control.js +52 -0
- package/dist/esm/control.js.map +7 -0
- package/dist/esm/engine.js +352 -0
- package/dist/esm/engine.js.map +7 -0
- package/dist/esm/events.js +1 -0
- package/dist/esm/events.js.map +7 -0
- package/dist/esm/gate-policy.js +26 -0
- package/dist/esm/gate-policy.js.map +7 -0
- package/dist/esm/host-bindings.js +252 -0
- package/dist/esm/host-bindings.js.map +7 -0
- package/dist/esm/host-contract.js +1 -0
- package/dist/esm/host-contract.js.map +7 -0
- package/dist/esm/index.js +1481 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/runtime.js +6426 -0
- package/dist/esm/runtime.js.map +7 -0
- package/dist/esm/schema.js +1140 -0
- package/dist/esm/schema.js.map +7 -0
- package/dist/types/packages/workflow-core/src/control.d.ts +1 -0
- package/dist/types/packages/workflow-core/src/engine.d.ts +1 -0
- package/dist/types/packages/workflow-core/src/events.d.ts +1 -0
- package/dist/types/packages/workflow-core/src/gate-policy.d.ts +1 -0
- package/dist/types/packages/workflow-core/src/host-bindings.d.ts +1 -0
- package/dist/types/packages/workflow-core/src/host-contract.d.ts +1 -0
- package/dist/types/packages/workflow-core/src/index.d.ts +1 -0
- package/dist/types/packages/workflow-core/src/runtime.d.ts +7 -0
- package/dist/types/packages/workflow-core/src/schema.d.ts +1 -0
- package/dist/types/src/workflows/v3/artifact-contract.d.ts +40 -0
- package/dist/types/src/workflows/v3/core-control.d.ts +15 -0
- package/dist/types/src/workflows/v3/dag.d.ts +341 -0
- package/dist/types/src/workflows/v3/event-contract.d.ts +272 -0
- package/dist/types/src/workflows/v3/gate-policy.d.ts +11 -0
- package/dist/types/src/workflows/v3/host-bindings.d.ts +39 -0
- package/dist/types/src/workflows/v3/in-process-attempt-lease.d.ts +9 -0
- package/dist/types/src/workflows/v3/orchestrator.d.ts +174 -0
- package/dist/types/src/workflows/v3/portable-final-outputs.d.ts +36 -0
- package/dist/types/src/workflows/v3/portable-runtime.d.ts +82 -0
- package/dist/types/src/workflows/v3/runtime-host-contract.d.ts +181 -0
- package/dist/types/src/workflows/v3/shared-runtime.d.ts +16 -0
- 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;
|