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,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
+ }