@cleocode/playbooks 2026.4.93 → 2026.4.95
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/dist/approval.d.ts +113 -0
- package/dist/approval.js +244 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +34 -0
- package/dist/parser.d.ts +60 -0
- package/dist/parser.js +509 -0
- package/dist/policy.d.ts +55 -0
- package/dist/policy.js +85 -0
- package/dist/runtime.d.ts +206 -0
- package/dist/runtime.js +601 -0
- package/dist/schema.d.ts +374 -0
- package/dist/schema.js +34 -0
- package/dist/state.d.ts +96 -0
- package/dist/state.js +322 -0
- package/package.json +3 -3
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playbook runtime — deterministic state machine executor for `.cantbook` flows.
|
|
3
|
+
*
|
|
4
|
+
* This module is the executable heart of CLEO's T910 "Orchestration Coherence v4"
|
|
5
|
+
* pipeline. It walks a validated {@link PlaybookDefinition} one node at a time,
|
|
6
|
+
* merging node outputs into a shared `context`, enforcing per-node iteration
|
|
7
|
+
* caps, and pausing for HITL approval gates via the signed resume-token
|
|
8
|
+
* protocol (see `approval.ts`).
|
|
9
|
+
*
|
|
10
|
+
* Design constraints (non-negotiable):
|
|
11
|
+
*
|
|
12
|
+
* 1. Pure dependency injection — the runtime never imports or instantiates
|
|
13
|
+
* subprocess code. Callers pass an {@link AgentDispatcher} for `agentic`
|
|
14
|
+
* nodes and an optional {@link DeterministicRunner} for `deterministic`
|
|
15
|
+
* nodes. Tests can therefore exercise every branch without mocking any
|
|
16
|
+
* `@cleocode/*` module.
|
|
17
|
+
* 2. Deterministic ordering — a topological traversal is computed up front
|
|
18
|
+
* from the {@link PlaybookDefinition.edges} graph (with `depends[]`
|
|
19
|
+
* treated as reverse edges, exactly as the parser's cycle check does).
|
|
20
|
+
* Execution order is stable across runs for the same definition.
|
|
21
|
+
* 3. Fail-closed policy — unknown node kinds, missing successors, unresolved
|
|
22
|
+
* `inject_into` targets, or dispatcher errors all terminate the run with
|
|
23
|
+
* a typed `terminalStatus`. The runtime never silently swallows failures.
|
|
24
|
+
* 4. HITL gates persist — when an `approval` node executes, the run is
|
|
25
|
+
* marked `paused` in `playbook_runs`, a {@link PlaybookApproval} row is
|
|
26
|
+
* written with the HMAC-signed resume token, and the returned
|
|
27
|
+
* {@link ExecutePlaybookResult.approvalToken} is what the human reviewer
|
|
28
|
+
* must present via `resumePlaybook` to continue.
|
|
29
|
+
*
|
|
30
|
+
* @task T930 — Playbook Runtime State Machine
|
|
31
|
+
*/
|
|
32
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
33
|
+
import type { PlaybookDefinition } from '@cleocode/contracts';
|
|
34
|
+
/**
|
|
35
|
+
* Input payload handed to {@link AgentDispatcher.dispatch} on every
|
|
36
|
+
* `agentic` node execution. All fields are read-only from the dispatcher's
|
|
37
|
+
* perspective — the runtime owns the lifecycle.
|
|
38
|
+
*/
|
|
39
|
+
export interface AgentDispatchInput {
|
|
40
|
+
/** Playbook run identifier (FK into `playbook_runs.run_id`). */
|
|
41
|
+
runId: string;
|
|
42
|
+
/** Node identifier within the run graph. */
|
|
43
|
+
nodeId: string;
|
|
44
|
+
/** Agent identity resolved from `node.agent` (falls back to `node.skill`). */
|
|
45
|
+
agentId: string;
|
|
46
|
+
/** Task identifier lifted from `context.taskId` if present, otherwise `runId`. */
|
|
47
|
+
taskId: string;
|
|
48
|
+
/** Snapshot of the accumulated bindings at dispatch time. */
|
|
49
|
+
context: Record<string, unknown>;
|
|
50
|
+
/** 1-based iteration counter for this specific node (retry-aware). */
|
|
51
|
+
iteration: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Terminal output returned by {@link AgentDispatcher.dispatch}. The runtime
|
|
55
|
+
* merges {@link output} into the run context on `status === 'success'` and
|
|
56
|
+
* triggers iteration-cap / escalation logic on `'failure'`.
|
|
57
|
+
*/
|
|
58
|
+
export interface AgentDispatchResult {
|
|
59
|
+
status: 'success' | 'failure';
|
|
60
|
+
/** Key-value pairs merged into the run context on success. */
|
|
61
|
+
output: Record<string, unknown>;
|
|
62
|
+
/** Human-readable failure reason. Persisted to `playbook_runs.error_context`. */
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Injected contract for spawning subagents. Implementations MUST NOT depend
|
|
67
|
+
* on any `@cleocode/*` module — the runtime passes all state through
|
|
68
|
+
* {@link AgentDispatchInput} so tests can provide deterministic stubs.
|
|
69
|
+
*/
|
|
70
|
+
export interface AgentDispatcher {
|
|
71
|
+
/** Execute a single `agentic` node; return a success/failure envelope. */
|
|
72
|
+
dispatch(input: AgentDispatchInput): Promise<AgentDispatchResult>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Input payload handed to {@link DeterministicRunner.run} on every
|
|
76
|
+
* `deterministic` node execution.
|
|
77
|
+
*/
|
|
78
|
+
export interface DeterministicRunInput {
|
|
79
|
+
runId: string;
|
|
80
|
+
nodeId: string;
|
|
81
|
+
command: string;
|
|
82
|
+
args: readonly string[];
|
|
83
|
+
cwd?: string;
|
|
84
|
+
env?: Readonly<Record<string, string>>;
|
|
85
|
+
/** Timeout in milliseconds; `undefined` means the runner picks a default. */
|
|
86
|
+
timeout_ms?: number;
|
|
87
|
+
context: Record<string, unknown>;
|
|
88
|
+
iteration: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Terminal output returned by {@link DeterministicRunner.run}. Shape mirrors
|
|
92
|
+
* {@link AgentDispatchResult} for runtime uniformity.
|
|
93
|
+
*/
|
|
94
|
+
export interface DeterministicRunResult {
|
|
95
|
+
status: 'success' | 'failure';
|
|
96
|
+
output: Record<string, unknown>;
|
|
97
|
+
error?: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Injected contract for running `deterministic` nodes (CLI tools, validators,
|
|
101
|
+
* decide scripts). If not supplied, the runtime delegates to
|
|
102
|
+
* {@link AgentDispatcher.dispatch} with `agentId = "deterministic:<command>"`
|
|
103
|
+
* so a single stub can cover both node kinds during unit testing.
|
|
104
|
+
*/
|
|
105
|
+
export interface DeterministicRunner {
|
|
106
|
+
run(input: DeterministicRunInput): Promise<DeterministicRunResult>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Options accepted by {@link executePlaybook}.
|
|
110
|
+
*/
|
|
111
|
+
export interface ExecutePlaybookOptions {
|
|
112
|
+
/** Open `node:sqlite` handle with the T889 migration applied. */
|
|
113
|
+
db: DatabaseSync;
|
|
114
|
+
/** Validated playbook definition (output of {@link parsePlaybook}). */
|
|
115
|
+
playbook: PlaybookDefinition;
|
|
116
|
+
/** SHA-256 hex of the playbook source (output of {@link parsePlaybook}). */
|
|
117
|
+
playbookHash: string;
|
|
118
|
+
/** Starting bindings seeded into the run context (e.g. `{ taskId }`). */
|
|
119
|
+
initialContext: Record<string, unknown>;
|
|
120
|
+
/** Required dispatcher for `agentic` nodes. */
|
|
121
|
+
dispatcher: AgentDispatcher;
|
|
122
|
+
/** Optional runner for `deterministic` nodes. Defaults to `dispatcher`. */
|
|
123
|
+
deterministicRunner?: DeterministicRunner;
|
|
124
|
+
/** Override secret for HMAC resume-token signing. */
|
|
125
|
+
approvalSecret?: string;
|
|
126
|
+
/** Fallback per-node iteration cap when `on_failure.max_iterations` is unset. */
|
|
127
|
+
maxIterationsDefault?: number;
|
|
128
|
+
/** Epic id persisted to `playbook_runs.epic_id` for dashboard filtering. */
|
|
129
|
+
epicId?: string;
|
|
130
|
+
/** Session id persisted to `playbook_runs.session_id`. */
|
|
131
|
+
sessionId?: string;
|
|
132
|
+
/** Injectable clock for deterministic tests (defaults to `() => new Date()`). */
|
|
133
|
+
now?: () => Date;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Options accepted by {@link resumePlaybook}. The runtime validates that the
|
|
137
|
+
* supplied approval token resolves to an `approved` {@link PlaybookApproval}
|
|
138
|
+
* row before continuing execution.
|
|
139
|
+
*/
|
|
140
|
+
export interface ResumePlaybookOptions {
|
|
141
|
+
db: DatabaseSync;
|
|
142
|
+
playbook: PlaybookDefinition;
|
|
143
|
+
/** The token previously returned in {@link ExecutePlaybookResult.approvalToken}. */
|
|
144
|
+
approvalToken: string;
|
|
145
|
+
dispatcher: AgentDispatcher;
|
|
146
|
+
deterministicRunner?: DeterministicRunner;
|
|
147
|
+
approvalSecret?: string;
|
|
148
|
+
maxIterationsDefault?: number;
|
|
149
|
+
now?: () => Date;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Terminal status values reported by the runtime.
|
|
153
|
+
*/
|
|
154
|
+
export type PlaybookTerminalStatus = 'completed' | 'failed' | 'pending_approval' | 'exceeded_iteration_cap';
|
|
155
|
+
/**
|
|
156
|
+
* Final envelope returned by both {@link executePlaybook} and
|
|
157
|
+
* {@link resumePlaybook}.
|
|
158
|
+
*/
|
|
159
|
+
export interface ExecutePlaybookResult {
|
|
160
|
+
runId: string;
|
|
161
|
+
terminalStatus: PlaybookTerminalStatus;
|
|
162
|
+
/** Fully-merged bindings at the point the runtime stopped. */
|
|
163
|
+
finalContext: Record<string, unknown>;
|
|
164
|
+
/** Set when `terminalStatus === 'pending_approval'`. */
|
|
165
|
+
approvalToken?: string;
|
|
166
|
+
/** Set when the run stopped because a specific node failed. */
|
|
167
|
+
failedNodeId?: string;
|
|
168
|
+
/** Set when the run stopped because a node hit its iteration cap. */
|
|
169
|
+
exceededNodeId?: string;
|
|
170
|
+
/** Human-readable error reason mirrored from the last failing node. */
|
|
171
|
+
errorContext?: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Error code stamped onto errors thrown by the runtime for invalid inputs.
|
|
175
|
+
* Exported for parity with the rest of the `@cleocode/playbooks` error codes.
|
|
176
|
+
*/
|
|
177
|
+
export declare const E_PLAYBOOK_RUNTIME_INVALID: "E_PLAYBOOK_RUNTIME_INVALID";
|
|
178
|
+
/**
|
|
179
|
+
* Error code thrown when {@link resumePlaybook} is called with a token that
|
|
180
|
+
* does not resolve to an `approved` gate.
|
|
181
|
+
*/
|
|
182
|
+
export declare const E_PLAYBOOK_RESUME_BLOCKED: "E_PLAYBOOK_RESUME_BLOCKED";
|
|
183
|
+
/**
|
|
184
|
+
* Execute a playbook from its entry node until a terminal state is reached
|
|
185
|
+
* (`completed`, `failed`, `exceeded_iteration_cap`, or `pending_approval`).
|
|
186
|
+
*
|
|
187
|
+
* Every execution is persisted to `playbook_runs` so that crashes or HITL
|
|
188
|
+
* pauses can resume via {@link resumePlaybook}. Returned
|
|
189
|
+
* {@link ExecutePlaybookResult.finalContext} is a fully-merged snapshot at
|
|
190
|
+
* the moment the runtime stopped.
|
|
191
|
+
*
|
|
192
|
+
* @param options - Runtime configuration, including the injected dispatcher.
|
|
193
|
+
* @returns Terminal envelope describing where the run stopped.
|
|
194
|
+
*/
|
|
195
|
+
export declare function executePlaybook(options: ExecutePlaybookOptions): Promise<ExecutePlaybookResult>;
|
|
196
|
+
/**
|
|
197
|
+
* Resume a paused playbook run using a HITL approval token. The runtime
|
|
198
|
+
* validates that the token maps to an `approved` {@link PlaybookApproval}
|
|
199
|
+
* row and that the associated run is in `paused` state, then continues from
|
|
200
|
+
* the approval node's single successor.
|
|
201
|
+
*
|
|
202
|
+
* @throws Error stamped with {@link E_PLAYBOOK_RESUME_BLOCKED} if the token
|
|
203
|
+
* is unknown, the gate is still `pending`, the gate was `rejected`, the
|
|
204
|
+
* run is not `paused`, or the approval node has no successor.
|
|
205
|
+
*/
|
|
206
|
+
export declare function resumePlaybook(options: ResumePlaybookOptions): Promise<ExecutePlaybookResult>;
|