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,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v3 orchestrator — pure decision layer.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors v0.2's `orchestrator.ts` pattern: a pure function maps the current
|
|
5
|
+
* run state + DAG to a list of action descriptors. The runtime (`runtime.ts`)
|
|
6
|
+
* owns every side effect — journal/STATE writes, ephemeral-worker dispatch via
|
|
7
|
+
* `runNode`, humanGate card posting — and the per-bot/per-CLI/global
|
|
8
|
+
* concurrency caps. Keeping the decision pure makes the critical-path
|
|
9
|
+
* semantics testable without spawning workers or touching the filesystem.
|
|
10
|
+
*
|
|
11
|
+
* MVP scope: static DAG, fail-fast. No loops / decisions / dynamic expand
|
|
12
|
+
* (those are deferred — design Q3/§7). Gate set is frozen at authoring time;
|
|
13
|
+
* the orchestrator never invents or skips a gate (design Q10).
|
|
14
|
+
*/
|
|
15
|
+
import { type V3Dag } from './dag.js';
|
|
16
|
+
import type { V3LoopRef, V3RunFailureReason } from './event-contract.js';
|
|
17
|
+
export type V3NodeStatus = 'pending' | 'gateWaiting' | 'running' | 'done' | 'skipped' | 'cancelled' | 'blocked' | 'superseded' | 'failed';
|
|
18
|
+
export interface V3NodeState {
|
|
19
|
+
status: V3NodeStatus;
|
|
20
|
+
/** True once an approved humanGate cleared this node — so after approval the
|
|
21
|
+
* next tick dispatches work instead of re-dispatching the gate. A rejected
|
|
22
|
+
* gate transitions the node straight to `failed` (set by the runtime), so
|
|
23
|
+
* this flag only ever records the approved case. */
|
|
24
|
+
gateCleared?: boolean;
|
|
25
|
+
/** Host-only: the frozen input approved by this gate. It must match the
|
|
26
|
+
* prepared sidecar before the runtime may publish hostEffectIntent. */
|
|
27
|
+
approvedHostInput?: {
|
|
28
|
+
attemptId: string;
|
|
29
|
+
approvalDigest: string;
|
|
30
|
+
inputHash: string;
|
|
31
|
+
};
|
|
32
|
+
/** The current live runtime instance of this DEFINITION node (`A#002`).
|
|
33
|
+
* Set on dispatch; cleared when a revisit supersedes it (the node then
|
|
34
|
+
* re-dispatches a fresh instance). Absent on the pre-instance-layer path
|
|
35
|
+
* (plain nodeId-keyed events) and loop body expansions. */
|
|
36
|
+
effectiveInstanceId?: string;
|
|
37
|
+
}
|
|
38
|
+
/** nodeId → state. A node absent from the map is treated as `pending`. */
|
|
39
|
+
export type V3RunState = Map<string, V3NodeState>;
|
|
40
|
+
/**
|
|
41
|
+
* Per-loop composite state, folded from the loop lifecycle events. A loop
|
|
42
|
+
* absent from the map has not started. The loop's coarse status (running /
|
|
43
|
+
* blocked / done) lives in the regular node-state map under the loop's id;
|
|
44
|
+
* this struct carries what that one enum can't: where the iteration cursor is
|
|
45
|
+
* and what the last decision said.
|
|
46
|
+
*/
|
|
47
|
+
export interface V3LoopState {
|
|
48
|
+
/** Current iteration, 1-based; 0 between loopStarted and the first
|
|
49
|
+
* loopIterationStarted. */
|
|
50
|
+
iteration: number;
|
|
51
|
+
/** True once the CURRENT iteration's decision event is recorded (reset by
|
|
52
|
+
* the next loopIterationStarted). */
|
|
53
|
+
decided: boolean;
|
|
54
|
+
/** The latest decision — drives what the orchestrator does next. */
|
|
55
|
+
lastDecision?: 'exit' | 'continue' | 'exhausted';
|
|
56
|
+
/** Extra iterations granted (each loopIterationGranted adds one); the
|
|
57
|
+
* effective budget is maxIterations + granted. */
|
|
58
|
+
granted: number;
|
|
59
|
+
/** An appended-but-unconsumed grant (cleared by the next
|
|
60
|
+
* loopIterationStarted) — the idempotency key for "already granted". */
|
|
61
|
+
pendingGrant: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** loopId → loop state. */
|
|
64
|
+
export type V3LoopRunState = Map<string, V3LoopState>;
|
|
65
|
+
export interface V3EdgeState {
|
|
66
|
+
active: boolean;
|
|
67
|
+
sourceAttemptId: string;
|
|
68
|
+
}
|
|
69
|
+
/** `${from}->${to}` → conditional edge state. */
|
|
70
|
+
export type V3EdgeRunState = Map<string, V3EdgeState>;
|
|
71
|
+
export interface V3OmittedInput {
|
|
72
|
+
from: string;
|
|
73
|
+
reason: 'edgeInactive' | 'sourceSkipped' | 'sourceCancelled' | 'earlyRelease';
|
|
74
|
+
}
|
|
75
|
+
export type V3Action =
|
|
76
|
+
/** Read one source result.json once and append edgeResolved. */
|
|
77
|
+
{
|
|
78
|
+
kind: 'resolveEdge';
|
|
79
|
+
from: string;
|
|
80
|
+
to: string;
|
|
81
|
+
}
|
|
82
|
+
/** Mark a node skipped because its triggerRule cannot be satisfied. */
|
|
83
|
+
| {
|
|
84
|
+
kind: 'skipNode';
|
|
85
|
+
nodeId: string;
|
|
86
|
+
detail?: string;
|
|
87
|
+
}
|
|
88
|
+
/** Abort an early-release loser whose remaining products are no longer used. */
|
|
89
|
+
| {
|
|
90
|
+
kind: 'cancelNode';
|
|
91
|
+
nodeId: string;
|
|
92
|
+
byNodeId: string;
|
|
93
|
+
detail?: string;
|
|
94
|
+
}
|
|
95
|
+
/** Post the humanGate approval card + persist a `waits/<id>.json` (Q10). */
|
|
96
|
+
| {
|
|
97
|
+
kind: 'dispatchGate';
|
|
98
|
+
nodeId: string;
|
|
99
|
+
instanceId?: string;
|
|
100
|
+
}
|
|
101
|
+
/** Spawn an ephemeral worker via `runNode` for this node's goal. `loop` is
|
|
102
|
+
* set for body-instance dispatches (the runtime synthesizes the instance
|
|
103
|
+
* node from the loop's body definition). `instanceId` is set when this is a
|
|
104
|
+
* cross-node-revisit RE-DISPATCH (`A#002`): the prior instance was
|
|
105
|
+
* superseded, so decideNext computes the next instance number deterministically
|
|
106
|
+
* from `state.instances` (constraint 4 — the action carries it, the runtime
|
|
107
|
+
* does not guess). Absent on a first dispatch / loop body (those keep the
|
|
108
|
+
* pre-instance-layer path until the runtime brick threads instances through). */
|
|
109
|
+
| {
|
|
110
|
+
kind: 'dispatchWork';
|
|
111
|
+
nodeId: string;
|
|
112
|
+
instanceId?: string;
|
|
113
|
+
loop?: V3LoopRef;
|
|
114
|
+
omitted?: V3OmittedInput[];
|
|
115
|
+
}
|
|
116
|
+
/** Outer deps of a loop are done → append loopStarted. */
|
|
117
|
+
| {
|
|
118
|
+
kind: 'startLoop';
|
|
119
|
+
loopId: string;
|
|
120
|
+
}
|
|
121
|
+
/** Begin iteration N (first, after a continue-decision, or after a grant). */
|
|
122
|
+
| {
|
|
123
|
+
kind: 'startLoopIteration';
|
|
124
|
+
loopId: string;
|
|
125
|
+
iteration: number;
|
|
126
|
+
}
|
|
127
|
+
/** Current iteration's body is fully done and undecided → the runtime reads
|
|
128
|
+
* the exit node's result.json, evaluates exit.when, appends the decision. */
|
|
129
|
+
| {
|
|
130
|
+
kind: 'evaluateLoopIteration';
|
|
131
|
+
loopId: string;
|
|
132
|
+
iteration: number;
|
|
133
|
+
}
|
|
134
|
+
/** Decision was 'exit' → seal the loop with a nodeSucceeded on the LOOP id
|
|
135
|
+
* carrying the output projection's manifest (downstream inputs/deps then
|
|
136
|
+
* treat the loop like any done node — zero special-casing). */
|
|
137
|
+
| {
|
|
138
|
+
kind: 'completeLoop';
|
|
139
|
+
loopId: string;
|
|
140
|
+
iteration: number;
|
|
141
|
+
}
|
|
142
|
+
/** Terminal: every node done; the run's product is the sink set. */
|
|
143
|
+
| {
|
|
144
|
+
kind: 'completeRunSucceeded';
|
|
145
|
+
}
|
|
146
|
+
/** Terminal (fail-fast): a node failed, so the run cannot proceed. */
|
|
147
|
+
| {
|
|
148
|
+
kind: 'completeRunFailed';
|
|
149
|
+
failedNodeId?: string;
|
|
150
|
+
reason?: V3RunFailureReason;
|
|
151
|
+
detail?: string;
|
|
152
|
+
}
|
|
153
|
+
/** Terminal-for-now: a node is blocked (contract failure, recoverable).
|
|
154
|
+
* Halts dispatch like failed, but the run can resume via a retry event. */
|
|
155
|
+
| {
|
|
156
|
+
kind: 'completeRunBlocked';
|
|
157
|
+
blockedNodeId: string;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Pure decision: given the current `state`, return every action that can be
|
|
161
|
+
* taken *now*. The runtime applies concurrency caps by acting on a prefix of
|
|
162
|
+
* the returned dispatch actions and re-invoking on the next tick — this
|
|
163
|
+
* function intentionally returns ALL ready dispatches (it does not throttle).
|
|
164
|
+
*
|
|
165
|
+
* Ordering follows topological order so callers see deps-ready nodes first.
|
|
166
|
+
* Fail-fast: the moment any node is `failed`, the only action is
|
|
167
|
+
* `completeRunFailed` (the runtime's attempt-quiescence barrier tears down and
|
|
168
|
+
* proves close for every in-flight peer before publishing the run terminal).
|
|
169
|
+
* When no dispatch is possible and nothing is pending, the run is complete.
|
|
170
|
+
*/
|
|
171
|
+
export declare function decideNext(dag: V3Dag, state: V3RunState, loops?: V3LoopRunState, edges?: V3EdgeRunState, instances?: V3RunState): V3Action[];
|
|
172
|
+
/** Sink nodes — no other node depends on them. Their products are the run's
|
|
173
|
+
* output. Pure helper for the runtime's success path. */
|
|
174
|
+
export declare function findSinks(dag: V3Dag): string[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure, host-neutral projection of terminal workflow artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Journal entries are audit data, not filesystem authority. This module
|
|
5
|
+
* derives the only allowed artifact paths from the validated attempt identity,
|
|
6
|
+
* revalidates the manifest, and returns hash-bound snapshots for consumers.
|
|
7
|
+
*/
|
|
8
|
+
import type { Manifest, ValidateManifest } from './artifact-contract.js';
|
|
9
|
+
import type { V3Dag } from './dag.js';
|
|
10
|
+
export interface PortableWorkflowFinalOutput {
|
|
11
|
+
nodeId: string;
|
|
12
|
+
instanceId?: string;
|
|
13
|
+
attemptId: string;
|
|
14
|
+
/** Canonical path to the revalidated sink manifest. */
|
|
15
|
+
manifestPath: string;
|
|
16
|
+
/** SHA-256 of the exact manifest bytes validated for this projection. */
|
|
17
|
+
manifestSha256: string;
|
|
18
|
+
/** A detached snapshot of the validated manifest. */
|
|
19
|
+
manifest: Manifest;
|
|
20
|
+
/** Canonical directory containing the sink's validated files. */
|
|
21
|
+
outputDir: string;
|
|
22
|
+
/** Canonical path when the sink manifest publishes `result.json`. */
|
|
23
|
+
resultPath?: string;
|
|
24
|
+
/** SHA-256 of the exact `result.json` bytes captured by this projection. */
|
|
25
|
+
resultSha256?: string;
|
|
26
|
+
/** Parsed snapshot of `result.json`; prefer this over reopening resultPath. */
|
|
27
|
+
resultJson?: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reconstruct final sink artifacts without trusting journal-supplied paths.
|
|
31
|
+
*
|
|
32
|
+
* This intentionally fails closed when a successful sink's artifact binding
|
|
33
|
+
* cannot be reproved. Callers receive parsed/hash-bound snapshots so a later
|
|
34
|
+
* filesystem replacement cannot silently change the delivered result.
|
|
35
|
+
*/
|
|
36
|
+
export declare function readPortableWorkflowFinalOutputs(dag: V3Dag, runDir: string, validateManifest: ValidateManifest): Promise<PortableWorkflowFinalOutput[]>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral adapter for the complete v3 Node runtime.
|
|
3
|
+
*
|
|
4
|
+
* The action realization remains single-sourced in shared-node-runtime.ts;
|
|
5
|
+
* this adapter only translates execution profiles and agent calls.
|
|
6
|
+
*/
|
|
7
|
+
import type { ValidateManifest } from './artifact-contract.js';
|
|
8
|
+
import { type V3Dag } from './dag.js';
|
|
9
|
+
import type { PortableWorkflowFinalOutput } from './portable-final-outputs.js';
|
|
10
|
+
import type { AgentExecutor, AttemptLeaseProvider, ExecutionContextSnapshot, ExecutionProfileSnapshot, GateResolver, HostExecutorRegistry, HostExecutorPolicy, ProviderReconciler } from './runtime-host-contract.js';
|
|
11
|
+
export interface PortableWorkflowPendingGate {
|
|
12
|
+
nodeId: string;
|
|
13
|
+
waitId: string;
|
|
14
|
+
prompt: string;
|
|
15
|
+
options: string[];
|
|
16
|
+
approveOptions: string[];
|
|
17
|
+
approvers: string[];
|
|
18
|
+
hostApproval?: {
|
|
19
|
+
attemptId: string;
|
|
20
|
+
approvalDigest: string;
|
|
21
|
+
inputHash: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export { readPortableWorkflowFinalOutputs, } from './portable-final-outputs.js';
|
|
25
|
+
export type { PortableWorkflowFinalOutput, } from './portable-final-outputs.js';
|
|
26
|
+
export type PortableWorkflowRunOutcome = {
|
|
27
|
+
reason: 'terminal';
|
|
28
|
+
runStatus: 'succeeded' | 'failed' | 'blocked' | 'cancelled';
|
|
29
|
+
failedNodeId?: string;
|
|
30
|
+
blockedNodeId?: string;
|
|
31
|
+
failureReason?: 'allSinksSkipped';
|
|
32
|
+
failureDetail?: string;
|
|
33
|
+
uncertainHostEffects?: Array<{
|
|
34
|
+
nodeId: string;
|
|
35
|
+
instanceId: string;
|
|
36
|
+
attemptId: string;
|
|
37
|
+
executor: string;
|
|
38
|
+
errorCode: string;
|
|
39
|
+
}>;
|
|
40
|
+
finalOutputs: PortableWorkflowFinalOutput[];
|
|
41
|
+
runDir: string;
|
|
42
|
+
} | {
|
|
43
|
+
reason: 'awaitingGate';
|
|
44
|
+
pendingWaits: PortableWorkflowPendingGate[];
|
|
45
|
+
runDir: string;
|
|
46
|
+
};
|
|
47
|
+
export interface PortableWorkflowRuntimeDeps {
|
|
48
|
+
executeAgent: AgentExecutor;
|
|
49
|
+
validateManifest: ValidateManifest;
|
|
50
|
+
resolveExecutionProfile: (selector: string | undefined) => ExecutionProfileSnapshot;
|
|
51
|
+
validateExecutionProfile?: (profile: ExecutionProfileSnapshot, selector: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Optional only for fresh process-local runs. The default provider cannot
|
|
54
|
+
* prove old attempts closed after recovery and therefore returns `unknown`.
|
|
55
|
+
* Desktop and other durable hosts must inject a durable provider.
|
|
56
|
+
*/
|
|
57
|
+
attemptLeaseProvider?: AttemptLeaseProvider;
|
|
58
|
+
hostExecutors?: HostExecutorRegistry;
|
|
59
|
+
hostReconcilers?: Map<string, ProviderReconciler>;
|
|
60
|
+
hostExecutorPolicy?: HostExecutorPolicy;
|
|
61
|
+
now?: () => number;
|
|
62
|
+
resolveGate?: GateResolver;
|
|
63
|
+
}
|
|
64
|
+
export interface PortableWorkflowRuntimeOptions {
|
|
65
|
+
/**
|
|
66
|
+
* Durable run root. Reusing the same baseDir + dag.runId formally resumes
|
|
67
|
+
* the existing journal and does not redispatch settled nodes.
|
|
68
|
+
*/
|
|
69
|
+
baseDir: string;
|
|
70
|
+
gateMode?: 'blocking' | 'suspend';
|
|
71
|
+
globalConcurrency?: number;
|
|
72
|
+
frozenExecutionProfiles?: ReadonlyMap<string, ExecutionProfileSnapshot>;
|
|
73
|
+
perProfileConcurrency?: number;
|
|
74
|
+
perExecutorConcurrency?: number;
|
|
75
|
+
cancelSignal?: AbortSignal;
|
|
76
|
+
authorizedArtifacts?: boolean;
|
|
77
|
+
executionContext?: ExecutionContextSnapshot;
|
|
78
|
+
/** @deprecated Use `executionContext`. */
|
|
79
|
+
resolvedWorkflowData?: ExecutionContextSnapshot;
|
|
80
|
+
hostResponseWaitMs?: number;
|
|
81
|
+
}
|
|
82
|
+
export declare function runPortableWorkflow(dag: V3Dag, deps: PortableWorkflowRuntimeDeps, options: PortableWorkflowRuntimeOptions): Promise<PortableWorkflowRunOutcome>;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral contracts for embedding the v3 workflow runtime.
|
|
3
|
+
*
|
|
4
|
+
* These types deliberately avoid daemon, Lark, worker-fence, and Botmux bot
|
|
5
|
+
* configuration details. A host may keep its own durable state and adapt it
|
|
6
|
+
* into this boundary.
|
|
7
|
+
*/
|
|
8
|
+
import type { V3GoalNode } from './dag.js';
|
|
9
|
+
export interface ExecutionContextSnapshot {
|
|
10
|
+
/** Immutable workflow parameters resolved when the run is authorized. */
|
|
11
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
12
|
+
/** Immutable host identity/context values resolved when the run is authorized. */
|
|
13
|
+
readonly context: Readonly<Record<string, string>>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One scheduler-owned execution lease. The id is durable while the signal is
|
|
17
|
+
* process-local; hosts should use the id for audit and the signal for teardown.
|
|
18
|
+
*/
|
|
19
|
+
export interface AttemptLease {
|
|
20
|
+
readonly attemptId: string;
|
|
21
|
+
readonly signal: AbortSignal;
|
|
22
|
+
/** Opaque provider token passed from the scheduler to the agent executor. */
|
|
23
|
+
readonly hostToken?: unknown;
|
|
24
|
+
}
|
|
25
|
+
export type AttemptLeaseCloseReason = 'pre_aborted' | 'setup_failed';
|
|
26
|
+
export interface AttemptLeaseBinding {
|
|
27
|
+
runId: string;
|
|
28
|
+
attemptId: string;
|
|
29
|
+
attemptDir: string;
|
|
30
|
+
}
|
|
31
|
+
export interface AttemptLeaseAcquisition {
|
|
32
|
+
hostToken?: unknown;
|
|
33
|
+
/** Preserve the legacy Botmux journal marker without imposing it on hosts. */
|
|
34
|
+
auditKind: 'workerFence' | 'attemptLease';
|
|
35
|
+
}
|
|
36
|
+
export type AttemptLeaseDrainResult =
|
|
37
|
+
/** Exact external ownership is proven closed; journal proof precedes finalization. */
|
|
38
|
+
{
|
|
39
|
+
status: 'closed';
|
|
40
|
+
finalizeAfterProof(): void;
|
|
41
|
+
}
|
|
42
|
+
/** `pending` is known live ownership; `unknown` means the host cannot prove either state. */
|
|
43
|
+
| {
|
|
44
|
+
status: 'pending' | 'unknown';
|
|
45
|
+
};
|
|
46
|
+
export interface AttemptLeaseProvider {
|
|
47
|
+
/**
|
|
48
|
+
* Durable/restartable hosts (including Desktop) must back this contract with
|
|
49
|
+
* host-owned execution state. Recovery may return `closed` only after proving
|
|
50
|
+
* the exact executor/terminal attempt stopped; `finalizeAfterProof` runs only
|
|
51
|
+
* after the workflow journal durably records that close.
|
|
52
|
+
*/
|
|
53
|
+
acquire(binding: AttemptLeaseBinding): AttemptLeaseAcquisition;
|
|
54
|
+
closeBeforeExecution(binding: AttemptLeaseBinding, acquisition: AttemptLeaseAcquisition, reason: AttemptLeaseCloseReason): void;
|
|
55
|
+
drainExternallyOwned(binding: AttemptLeaseBinding): AttemptLeaseDrainResult;
|
|
56
|
+
cleanupSettled(binding: AttemptLeaseBinding, acquisition?: AttemptLeaseAcquisition): void;
|
|
57
|
+
}
|
|
58
|
+
export interface ExecutionProfileSnapshot {
|
|
59
|
+
/** Stable profile selector frozen for this run. */
|
|
60
|
+
readonly profileId: string;
|
|
61
|
+
/** Concurrency/capability domain, e.g. a CLI or remote execution backend. */
|
|
62
|
+
readonly executorId: string;
|
|
63
|
+
readonly workingDirectory: string;
|
|
64
|
+
readonly model?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Host-owned, JSON-safe, non-secret adapter configuration. Persist stable
|
|
67
|
+
* credential references here; the executor resolves live secrets itself.
|
|
68
|
+
*/
|
|
69
|
+
readonly adapterData?: unknown;
|
|
70
|
+
}
|
|
71
|
+
export interface AgentSessionInfo {
|
|
72
|
+
sessionId: string;
|
|
73
|
+
webPort?: number;
|
|
74
|
+
token?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface AgentExecutionRequest {
|
|
77
|
+
runId: string;
|
|
78
|
+
attemptId: string;
|
|
79
|
+
attemptLease: AttemptLease;
|
|
80
|
+
node: V3GoalNode;
|
|
81
|
+
executionProfile: ExecutionProfileSnapshot;
|
|
82
|
+
runDir: string;
|
|
83
|
+
attemptDir: string;
|
|
84
|
+
inputsPath: string;
|
|
85
|
+
outputDir: string;
|
|
86
|
+
env: Record<string, string>;
|
|
87
|
+
timeoutMs: number;
|
|
88
|
+
onSessionReady?: (info: AgentSessionInfo & {
|
|
89
|
+
ptyLogPath?: string;
|
|
90
|
+
}) => void | Promise<void>;
|
|
91
|
+
stdoutPath?: string;
|
|
92
|
+
stderrPath?: string;
|
|
93
|
+
}
|
|
94
|
+
export interface AgentExecutionResult {
|
|
95
|
+
status: 'ok' | 'fail' | 'cancelled';
|
|
96
|
+
cancelReason?: unknown;
|
|
97
|
+
manifestPath: string;
|
|
98
|
+
sessionInfo?: AgentSessionInfo;
|
|
99
|
+
}
|
|
100
|
+
export type AgentExecutor = (request: AgentExecutionRequest) => Promise<AgentExecutionResult>;
|
|
101
|
+
export type ExecutorErrorClassification = {
|
|
102
|
+
errorCode: string;
|
|
103
|
+
errorClass: 'retryable' | 'fatal' | 'userFault' | 'manual';
|
|
104
|
+
errorMessage: string;
|
|
105
|
+
};
|
|
106
|
+
export interface HostSideEffectExecutor<Input = unknown, Output = unknown> {
|
|
107
|
+
readonly provider: string;
|
|
108
|
+
readonly idempotencyTtlMs: number;
|
|
109
|
+
canonicalInput(input: Input): unknown;
|
|
110
|
+
validateBeforeIntent?(input: Input, nowMs: number): {
|
|
111
|
+
ok: true;
|
|
112
|
+
} | {
|
|
113
|
+
ok: false;
|
|
114
|
+
errorCode: string;
|
|
115
|
+
message: string;
|
|
116
|
+
};
|
|
117
|
+
invoke(input: Input, idempotencyKey: string): Promise<{
|
|
118
|
+
output: Output;
|
|
119
|
+
externalRefs: Record<string, unknown>;
|
|
120
|
+
}>;
|
|
121
|
+
classifyError?(error: unknown): ExecutorErrorClassification | null;
|
|
122
|
+
}
|
|
123
|
+
export type RegisteredHostExecutor<Input = unknown, Output = unknown> = {
|
|
124
|
+
executor: HostSideEffectExecutor<Input, Output>;
|
|
125
|
+
parseInput(input: unknown): Input;
|
|
126
|
+
};
|
|
127
|
+
export type HostExecutorRegistry = Map<string, RegisteredHostExecutor>;
|
|
128
|
+
export type ReadOnlyLookupResult = {
|
|
129
|
+
found: true;
|
|
130
|
+
externalRefs: Record<string, unknown>;
|
|
131
|
+
evidence?: Record<string, unknown>;
|
|
132
|
+
} | {
|
|
133
|
+
found: false;
|
|
134
|
+
evidence?: Record<string, unknown>;
|
|
135
|
+
};
|
|
136
|
+
export type IdempotentSubmitResult = {
|
|
137
|
+
ok: true;
|
|
138
|
+
externalRefs: Record<string, unknown>;
|
|
139
|
+
evidence?: Record<string, unknown>;
|
|
140
|
+
} | {
|
|
141
|
+
ok: false;
|
|
142
|
+
errorCode: string;
|
|
143
|
+
errorClass: 'retryable' | 'fatal' | 'userFault' | 'manual';
|
|
144
|
+
errorMessage: string;
|
|
145
|
+
evidence?: Record<string, unknown>;
|
|
146
|
+
};
|
|
147
|
+
export interface ProviderReconciler {
|
|
148
|
+
readonly provider: string;
|
|
149
|
+
readonly requiresEffectInput?: boolean;
|
|
150
|
+
readOnlyLookup?(idempotencyKey: string, input: unknown): Promise<ReadOnlyLookupResult>;
|
|
151
|
+
idempotentSubmit?(idempotencyKey: string, input: unknown): Promise<IdempotentSubmitResult>;
|
|
152
|
+
canonicalInput?(input: unknown): unknown;
|
|
153
|
+
}
|
|
154
|
+
export interface HostExecutorPolicyRequest {
|
|
155
|
+
readonly nodeId: string;
|
|
156
|
+
readonly executor: string;
|
|
157
|
+
readonly input: unknown;
|
|
158
|
+
readonly executionContext?: ExecutionContextSnapshot;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Synchronous by design: authorization must finish before a prepared host
|
|
162
|
+
* effect can be committed or presented for approval.
|
|
163
|
+
*/
|
|
164
|
+
export type HostExecutorPolicy = (request: HostExecutorPolicyRequest) => void;
|
|
165
|
+
export interface GateResolutionRequest {
|
|
166
|
+
nodeId: string;
|
|
167
|
+
prompt: string;
|
|
168
|
+
waitId: string;
|
|
169
|
+
runDir: string;
|
|
170
|
+
hostApproval?: {
|
|
171
|
+
attemptId: string;
|
|
172
|
+
approvalDigest: string;
|
|
173
|
+
inputHash: string;
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
export interface GateResolution {
|
|
177
|
+
resolution: 'approved' | 'rejected';
|
|
178
|
+
by: string;
|
|
179
|
+
selected?: string;
|
|
180
|
+
}
|
|
181
|
+
export type GateResolver = (request: GateResolutionRequest) => Promise<GateResolution>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported daemon-independent v3 workflow surface.
|
|
3
|
+
*
|
|
4
|
+
* Keep this module limited to schema, pure scheduling/control policy, and
|
|
5
|
+
* host contracts. Node filesystem stores and Botmux/Lark adapters live behind
|
|
6
|
+
* separate modules.
|
|
7
|
+
*/
|
|
8
|
+
export { DEFAULT_HUMAN_GATE_OPTIONS, DEFAULT_REVISIT_BUDGET_PER_PAIR, DEFAULT_REVISIT_BUDGET_PER_RUN, isGoalNode, isHostNode, isLoopNode, loopInstanceId, topologicalOrder, validateDag, } from './dag.js';
|
|
9
|
+
export type { V3Dag, V3DependRef, V3EdgeWhen, V3GoalNode, V3HostNode, V3HumanGate, V3LoopExitWhen, V3LoopNode, V3Node, V3TriggerRule, } from './dag.js';
|
|
10
|
+
export { decideNext, findSinks, } from './orchestrator.js';
|
|
11
|
+
export type { V3Action, V3EdgeRunState, V3LoopRunState, V3NodeState, V3NodeStatus, V3RunState, } from './orchestrator.js';
|
|
12
|
+
export { matchLoopExitWhen, revisitBudgetStatus, } from './core-control.js';
|
|
13
|
+
export { canResolveGateWait, normalizeGateWaitInput, selectedResolution, } from './gate-policy.js';
|
|
14
|
+
export type { NormalizedGatePolicy } from './gate-policy.js';
|
|
15
|
+
export type { AttemptLease, ExecutionContextSnapshot, GateResolution, GateResolutionRequest, GateResolver, HostExecutorPolicy, HostExecutorPolicyRequest, } from './runtime-host-contract.js';
|
|
16
|
+
export type { StoredEvent, V3Event, V3LoopRef, } from './event-contract.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "botmux-workflow-core",
|
|
3
|
+
"version": "3.10.0",
|
|
4
|
+
"description": "Daemon-independent Workflow v3 schema, scheduler, control policy, and host contracts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/cjs/index.cjs",
|
|
8
|
+
"module": "./dist/esm/index.js",
|
|
9
|
+
"types": "./dist/types/packages/workflow-core/src/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/types/packages/workflow-core/src/index.d.ts",
|
|
13
|
+
"import": "./dist/esm/index.js",
|
|
14
|
+
"require": "./dist/cjs/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./schema": {
|
|
17
|
+
"types": "./dist/types/packages/workflow-core/src/schema.d.ts",
|
|
18
|
+
"import": "./dist/esm/schema.js",
|
|
19
|
+
"require": "./dist/cjs/schema.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./engine": {
|
|
22
|
+
"types": "./dist/types/packages/workflow-core/src/engine.d.ts",
|
|
23
|
+
"import": "./dist/esm/engine.js",
|
|
24
|
+
"require": "./dist/cjs/engine.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./control": {
|
|
27
|
+
"types": "./dist/types/packages/workflow-core/src/control.d.ts",
|
|
28
|
+
"import": "./dist/esm/control.js",
|
|
29
|
+
"require": "./dist/cjs/control.cjs"
|
|
30
|
+
},
|
|
31
|
+
"./gate-policy": {
|
|
32
|
+
"types": "./dist/types/packages/workflow-core/src/gate-policy.d.ts",
|
|
33
|
+
"import": "./dist/esm/gate-policy.js",
|
|
34
|
+
"require": "./dist/cjs/gate-policy.cjs"
|
|
35
|
+
},
|
|
36
|
+
"./host-contract": {
|
|
37
|
+
"types": "./dist/types/packages/workflow-core/src/host-contract.d.ts",
|
|
38
|
+
"import": "./dist/esm/host-contract.js",
|
|
39
|
+
"require": "./dist/cjs/host-contract.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./runtime": {
|
|
42
|
+
"types": "./dist/types/packages/workflow-core/src/runtime.d.ts",
|
|
43
|
+
"import": "./dist/esm/runtime.js",
|
|
44
|
+
"require": "./dist/cjs/runtime.cjs"
|
|
45
|
+
},
|
|
46
|
+
"./events": {
|
|
47
|
+
"types": "./dist/types/packages/workflow-core/src/events.d.ts",
|
|
48
|
+
"import": "./dist/esm/events.js",
|
|
49
|
+
"require": "./dist/cjs/events.cjs"
|
|
50
|
+
},
|
|
51
|
+
"./host-bindings": {
|
|
52
|
+
"types": "./dist/types/packages/workflow-core/src/host-bindings.d.ts",
|
|
53
|
+
"import": "./dist/esm/host-bindings.js",
|
|
54
|
+
"require": "./dist/cjs/host-bindings.cjs"
|
|
55
|
+
},
|
|
56
|
+
"./package.json": "./package.json"
|
|
57
|
+
},
|
|
58
|
+
"files": [
|
|
59
|
+
"dist/",
|
|
60
|
+
"README.md",
|
|
61
|
+
"LICENSE"
|
|
62
|
+
],
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "node ../../scripts/build-workflow-core-package.mjs",
|
|
65
|
+
"test": "pnpm run build && node ../../scripts/test-workflow-core-package.mjs",
|
|
66
|
+
"pack:local": "pnpm run build && node ../../scripts/pack-workflow-core-package.mjs",
|
|
67
|
+
"prepare": "pnpm run build",
|
|
68
|
+
"prepublishOnly": "pnpm run test"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"registry": "https://registry.npmjs.org/",
|
|
72
|
+
"access": "public"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=22"
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@types/node": "22.19.19",
|
|
79
|
+
"esbuild": "0.28.0",
|
|
80
|
+
"typescript": "5.9.3"
|
|
81
|
+
},
|
|
82
|
+
"license": "MIT"
|
|
83
|
+
}
|