@tagma/sdk 0.4.14 → 0.4.16
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 -21
- package/README.md +569 -569
- package/dist/dag.d.ts.map +1 -1
- package/dist/dag.js +58 -69
- package/dist/dag.js.map +1 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +63 -37
- package/dist/engine.js.map +1 -1
- package/dist/middlewares/static-context.d.ts.map +1 -1
- package/dist/middlewares/static-context.js +7 -3
- package/dist/middlewares/static-context.js.map +1 -1
- package/dist/prompt-doc.d.ts +36 -0
- package/dist/prompt-doc.d.ts.map +1 -0
- package/dist/prompt-doc.js +44 -0
- package/dist/prompt-doc.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +11 -0
- package/dist/registry.js.map +1 -1
- package/dist/sdk.d.ts +3 -0
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +4 -0
- package/dist/sdk.js.map +1 -1
- package/dist/task-ref.d.ts +55 -0
- package/dist/task-ref.d.ts.map +1 -0
- package/dist/task-ref.js +101 -0
- package/dist/task-ref.js.map +1 -0
- package/dist/task-ref.test.d.ts +2 -0
- package/dist/task-ref.test.d.ts.map +1 -0
- package/dist/task-ref.test.js +364 -0
- package/dist/task-ref.test.js.map +1 -0
- package/dist/templates.d.ts +20 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +93 -0
- package/dist/templates.js.map +1 -0
- package/dist/validate-raw.d.ts.map +1 -1
- package/dist/validate-raw.js +27 -53
- package/dist/validate-raw.js.map +1 -1
- package/package.json +2 -2
- package/scripts/preinstall.js +31 -31
- package/src/adapters/stdin-approval.ts +106 -106
- package/src/adapters/websocket-approval.ts +224 -224
- package/src/approval.ts +131 -131
- package/src/bootstrap.ts +37 -37
- package/src/completions/exit-code.ts +34 -34
- package/src/completions/file-exists.ts +66 -66
- package/src/completions/output-check.ts +86 -86
- package/src/config-ops.ts +307 -307
- package/src/dag.ts +61 -67
- package/src/drivers/claude-code.ts +250 -250
- package/src/engine.ts +1137 -1098
- package/src/hooks.ts +187 -187
- package/src/logger.ts +182 -182
- package/src/middlewares/static-context.ts +49 -45
- package/src/pipeline-runner.ts +156 -156
- package/src/prompt-doc.ts +49 -0
- package/src/registry.ts +255 -242
- package/src/runner.ts +395 -395
- package/src/schema.test.ts +101 -101
- package/src/schema.ts +338 -338
- package/src/sdk.ts +111 -92
- package/src/task-ref.test.ts +401 -0
- package/src/task-ref.ts +120 -0
- package/src/triggers/file.ts +164 -164
- package/src/triggers/manual.ts +86 -86
- package/src/types.ts +18 -18
- package/src/utils.ts +203 -203
- package/src/validate-raw.ts +412 -442
package/README.md
CHANGED
|
@@ -1,569 +1,569 @@
|
|
|
1
|
-
# @tagma/sdk
|
|
2
|
-
|
|
3
|
-
> ## ⚠️ Bun-only runtime
|
|
4
|
-
>
|
|
5
|
-
> ```bash
|
|
6
|
-
> bun add @tagma/sdk
|
|
7
|
-
> ```
|
|
8
|
-
>
|
|
9
|
-
> This package is published as pre-built JavaScript (`dist/`), but the runtime
|
|
10
|
-
> uses Bun-only APIs (`Bun.spawn`, `Bun.file`, `Bun.serve`). It will install
|
|
11
|
-
> under `npm` / `yarn` / `pnpm` without error, but **crash at runtime on Node**
|
|
12
|
-
> the first time a pipeline spawns a task. Get Bun at <https://bun.sh>.
|
|
13
|
-
>
|
|
14
|
-
> _(The `npm i @tagma/sdk` line in the npmjs.com sidebar is auto-generated by
|
|
15
|
-
> the npm registry website and cannot be removed — please ignore it and use
|
|
16
|
-
> the command above.)_
|
|
17
|
-
|
|
18
|
-
A local AI task orchestration SDK for [Bun](https://bun.sh). Define multi-track pipelines in YAML, run AI coding agents (Claude Code, Codex, OpenCode) and shell commands in parallel with dependency resolution, approval gates, and lifecycle hooks.
|
|
19
|
-
|
|
20
|
-
## Install
|
|
21
|
-
|
|
22
|
-
**Requires Bun ≥ 1.3.** Node/npm are not supported.
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
bun add @tagma/sdk
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Quick Start
|
|
29
|
-
|
|
30
|
-
**1. Define a pipeline** (`pipeline.yaml`)
|
|
31
|
-
|
|
32
|
-
```yaml
|
|
33
|
-
pipeline:
|
|
34
|
-
name: build-and-test
|
|
35
|
-
tracks:
|
|
36
|
-
- id: backend
|
|
37
|
-
name: Backend
|
|
38
|
-
driver: claude-code
|
|
39
|
-
permissions: { read: true, write: true, execute: false }
|
|
40
|
-
tasks:
|
|
41
|
-
- id: implement
|
|
42
|
-
name: Implement feature
|
|
43
|
-
prompt: 'Add a /health endpoint to src/server.ts'
|
|
44
|
-
- id: test
|
|
45
|
-
name: Run tests
|
|
46
|
-
command: 'bun test'
|
|
47
|
-
depends_on: [implement]
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**2. Run it programmatically**
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
import { bootstrapBuiltins, loadPipeline, runPipeline, InMemoryApprovalGateway } from '@tagma/sdk';
|
|
54
|
-
|
|
55
|
-
// Register built-in drivers, triggers, completions
|
|
56
|
-
bootstrapBuiltins();
|
|
57
|
-
|
|
58
|
-
const yaml = await Bun.file('pipeline.yaml').text();
|
|
59
|
-
const config = await loadPipeline(yaml, process.cwd());
|
|
60
|
-
|
|
61
|
-
const result = await runPipeline(config, process.cwd());
|
|
62
|
-
console.log(result.success ? 'Done' : 'Failed');
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Features
|
|
66
|
-
|
|
67
|
-
- **Multi-track DAG execution** -- tasks run in parallel across tracks, respecting `depends_on` ordering
|
|
68
|
-
- **Driver plugins** -- built-in `claude-code` driver; install `@tagma/driver-codex` or `@tagma/driver-opencode` for other agents
|
|
69
|
-
- **Session handoff** -- `continue_from` passes context between tasks (session resume or text injection)
|
|
70
|
-
- **Approval gates** -- trigger-based approval with stdin and WebSocket adapters
|
|
71
|
-
- **Lifecycle hooks** -- `pipeline_start`, `task_start`, `task_success`, `task_failure`, `pipeline_complete`, `pipeline_error`
|
|
72
|
-
- **Middleware** -- enrich prompts before execution (e.g. inject static context)
|
|
73
|
-
- **Completion checks** -- validate task output with `exit_code`, `file_exists`, or `output_check` plugins
|
|
74
|
-
- **Plugin schemas** -- triggers/completions/middlewares can declare a `PluginSchema` so visual editors render typed forms for their config
|
|
75
|
-
|
|
76
|
-
## Pipeline YAML Reference
|
|
77
|
-
|
|
78
|
-
### Full Structure
|
|
79
|
-
|
|
80
|
-
```yaml
|
|
81
|
-
pipeline:
|
|
82
|
-
name: my-pipeline
|
|
83
|
-
driver: claude-code
|
|
84
|
-
timeout: 30m
|
|
85
|
-
plugins:
|
|
86
|
-
- '@tagma/driver-codex'
|
|
87
|
-
hooks:
|
|
88
|
-
pipeline_start: 'echo starting'
|
|
89
|
-
task_start: 'echo task begin'
|
|
90
|
-
task_success: 'echo task ok'
|
|
91
|
-
task_failure: 'notify-slack.sh'
|
|
92
|
-
pipeline_complete: 'echo done'
|
|
93
|
-
pipeline_error: 'alert.sh'
|
|
94
|
-
tracks:
|
|
95
|
-
- id: track-1
|
|
96
|
-
name: Track One
|
|
97
|
-
color: '#3b82f6'
|
|
98
|
-
driver: claude-code
|
|
99
|
-
model: claude-sonnet-4-6
|
|
100
|
-
agent_profile: senior
|
|
101
|
-
cwd: ./services/backend
|
|
102
|
-
permissions:
|
|
103
|
-
read: true
|
|
104
|
-
write: true
|
|
105
|
-
execute: false
|
|
106
|
-
on_failure: skip_downstream
|
|
107
|
-
middlewares:
|
|
108
|
-
- type: static_context
|
|
109
|
-
file: ./context.md
|
|
110
|
-
label: Architecture Guide
|
|
111
|
-
tasks:
|
|
112
|
-
- id: task-a
|
|
113
|
-
name: Do something
|
|
114
|
-
prompt: 'Your prompt here'
|
|
115
|
-
timeout: 10m
|
|
116
|
-
driver: claude-code
|
|
117
|
-
model: claude-sonnet-4-6
|
|
118
|
-
agent_profile: senior
|
|
119
|
-
cwd: ./src
|
|
120
|
-
permissions:
|
|
121
|
-
read: true
|
|
122
|
-
write: true
|
|
123
|
-
execute: false
|
|
124
|
-
middlewares:
|
|
125
|
-
- type: static_context
|
|
126
|
-
file: ./ref.md
|
|
127
|
-
trigger:
|
|
128
|
-
type: manual
|
|
129
|
-
message: 'Approve before running'
|
|
130
|
-
timeout: 5m
|
|
131
|
-
completion:
|
|
132
|
-
type: exit_code
|
|
133
|
-
expect: 0
|
|
134
|
-
- id: task-b
|
|
135
|
-
name: Follow up
|
|
136
|
-
prompt: 'Continue the work'
|
|
137
|
-
continue_from: task-a
|
|
138
|
-
depends_on: [task-a]
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Pipeline Fields
|
|
142
|
-
|
|
143
|
-
| Field | Type | Required | Description |
|
|
144
|
-
| --------- | --------------- | -------- | ------------------------------------------------------------------------------------------ |
|
|
145
|
-
| `name` | `string` | Yes | Pipeline name, used in logs and run IDs |
|
|
146
|
-
| `driver` | `string` | No | Default driver for all tracks/tasks (inherited). Built-in: `claude-code` |
|
|
147
|
-
| `model` | `string` | No | Default model for all tracks/tasks (inherited). Exact model name, e.g. `claude-sonnet-4-6` |
|
|
148
|
-
| `timeout` | `string` | No | Pipeline-level timeout. Format: `"30s"`, `"5m"`, `"2h"` |
|
|
149
|
-
| `plugins` | `string[]` | No | External plugin packages to load, e.g. `["@tagma/driver-codex"]` |
|
|
150
|
-
| `hooks` | `HooksConfig` | No | Shell commands to run at lifecycle events (see Hooks below) |
|
|
151
|
-
| `tracks` | `TrackConfig[]` | Yes | List of parallel execution tracks |
|
|
152
|
-
|
|
153
|
-
### Hooks Fields
|
|
154
|
-
|
|
155
|
-
Each hook value can be a single command string or an array of commands.
|
|
156
|
-
|
|
157
|
-
| Field | Type | Description |
|
|
158
|
-
| ------------------- | -------------------- | -------------------------------------------- |
|
|
159
|
-
| `pipeline_start` | `string \| string[]` | Runs when the pipeline begins |
|
|
160
|
-
| `task_start` | `string \| string[]` | Runs before each task starts |
|
|
161
|
-
| `task_success` | `string \| string[]` | Runs after a task succeeds |
|
|
162
|
-
| `task_failure` | `string \| string[]` | Runs after a task fails |
|
|
163
|
-
| `pipeline_complete` | `string \| string[]` | Runs when the pipeline finishes successfully |
|
|
164
|
-
| `pipeline_error` | `string \| string[]` | Runs when the pipeline finishes with errors |
|
|
165
|
-
|
|
166
|
-
### Track Fields
|
|
167
|
-
|
|
168
|
-
| Field | Type | Required | Default | Description |
|
|
169
|
-
| --------------- | -------------------- | -------- | ----------------------- | -------------------------------------------------------------------- |
|
|
170
|
-
| `id` | `string` | Yes | — | Unique track identifier |
|
|
171
|
-
| `name` | `string` | Yes | — | Display name |
|
|
172
|
-
| `color` | `string` | No | — | Color hint for UI rendering (e.g. `"#3b82f6"`) |
|
|
173
|
-
| `driver` | `string` | No | Inherited from pipeline | Driver for all tasks in this track |
|
|
174
|
-
| `model` | `string` | No | Inherited from pipeline | Exact model name passed to the driver CLI (e.g. `claude-sonnet-4-6`) |
|
|
175
|
-
| `agent_profile` | `string` | No | — | Named agent configuration profile |
|
|
176
|
-
| `cwd` | `string` | No | Pipeline workDir | Working directory for tasks in this track (relative path) |
|
|
177
|
-
| `permissions` | `Permissions` | No | Inherited from pipeline | Default permissions for tasks (see Permissions) |
|
|
178
|
-
| `on_failure` | `OnFailure` | No | `skip_downstream` | Failure strategy: `skip_downstream`, `stop_all`, `ignore` |
|
|
179
|
-
| `middlewares` | `MiddlewareConfig[]` | No | — | Middlewares applied to all tasks (task-level overrides track-level) |
|
|
180
|
-
| `tasks` | `TaskConfig[]` | Yes | — | Ordered list of tasks in this track |
|
|
181
|
-
|
|
182
|
-
### Task Fields
|
|
183
|
-
|
|
184
|
-
| Field | Type | Required | Default | Description |
|
|
185
|
-
| --------------- | -------------------- | -------- | -------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
186
|
-
| `id` | `string` | Yes | — | Unique task identifier (unique within the pipeline) |
|
|
187
|
-
| `name` | `string` | No | — | Display name |
|
|
188
|
-
| `prompt` | `string` | No\* | — | AI prompt to send to the driver. \*Mutually exclusive with `command` |
|
|
189
|
-
| `command` | `string` | No\* | — | Shell command to execute directly. \*Mutually exclusive with `prompt` |
|
|
190
|
-
| `depends_on` | `string[]` | No | — | Task IDs that must complete before this task runs. Cross-track refs use `trackId.taskId` |
|
|
191
|
-
| `continue_from` | `string` | No | — | Task ID whose output/session to continue from (session handoff). Cross-track refs use `trackId.taskId` |
|
|
192
|
-
| `driver` | `string` | No | Inherited from track | Driver override for this task |
|
|
193
|
-
| `model` | `string` | No | Inherited from track | Model name override for this task |
|
|
194
|
-
| `agent_profile` | `string` | No | Inherited from track | Agent profile override |
|
|
195
|
-
| `cwd` | `string` | No | Inherited from track | Working directory override (relative path) |
|
|
196
|
-
| `timeout` | `string` | No | — | Task-level timeout. Format: `"30s"`, `"5m"`, `"2h"` |
|
|
197
|
-
| `permissions` | `Permissions` | No | Inherited from track | Permission override (see Permissions) |
|
|
198
|
-
| `middlewares` | `MiddlewareConfig[]` | No | Inherited from track | Middleware override. Set `[]` to disable inherited middlewares |
|
|
199
|
-
| `trigger` | `TriggerConfig` | No | — | Gate that must resolve before the task runs (see Triggers) |
|
|
200
|
-
| `completion` | `CompletionConfig` | No | — | Post-execution check to validate task output (see Completions) |
|
|
201
|
-
|
|
202
|
-
### Permissions
|
|
203
|
-
|
|
204
|
-
| Field | Type | Default | Description |
|
|
205
|
-
| --------- | --------- | ------- | ----------------------------------- |
|
|
206
|
-
| `read` | `boolean` | — | Allow the agent to read files |
|
|
207
|
-
| `write` | `boolean` | — | Allow the agent to write files |
|
|
208
|
-
| `execute` | `boolean` | — | Allow the agent to execute commands |
|
|
209
|
-
|
|
210
|
-
### Inheritance
|
|
211
|
-
|
|
212
|
-
Fields are inherited top-down: **pipeline → track → task**. A value set at a lower level overrides the inherited value.
|
|
213
|
-
|
|
214
|
-
Inherited fields: `driver`, `model`, `permissions`, `cwd`, `middlewares`.
|
|
215
|
-
|
|
216
|
-
Track-level `middlewares` apply to all tasks in the track. Setting task-level `middlewares` **replaces** (not appends) the track-level list. Use `middlewares: []` to disable all inherited middlewares for a task.
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
### Built-in Triggers
|
|
221
|
-
|
|
222
|
-
#### `manual` — Human approval gate
|
|
223
|
-
|
|
224
|
-
| Field | Type | Required | Default | Description |
|
|
225
|
-
| ---------- | ---------- | -------- | ------------------------------------------------------ | ------------------------------------------------- |
|
|
226
|
-
| `type` | `"manual"` | Yes | — | Trigger type |
|
|
227
|
-
| `message` | `string` | No | `"Manual confirmation required for task \"{taskId}\""` | Message shown to the approver |
|
|
228
|
-
| `timeout` | `string` | No | — | How long to wait for a decision before timing out |
|
|
229
|
-
| `metadata` | `object` | No | — | Arbitrary metadata passed to the approval gateway |
|
|
230
|
-
|
|
231
|
-
#### `file` — File watcher gate
|
|
232
|
-
|
|
233
|
-
| Field | Type | Required | Default | Description |
|
|
234
|
-
| --------- | -------- | -------- | ------- | ---------------------------------------------- |
|
|
235
|
-
| `type` | `"file"` | Yes | — | Trigger type |
|
|
236
|
-
| `path` | `string` | Yes | — | File path to watch (relative to workDir) |
|
|
237
|
-
| `timeout` | `string` | No | — | How long to wait for the file to appear/change |
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
### Built-in Completions
|
|
242
|
-
|
|
243
|
-
#### `exit_code` — Exit code check
|
|
244
|
-
|
|
245
|
-
| Field | Type | Required | Default | Description |
|
|
246
|
-
| -------- | -------------------- | -------- | ------- | ------------------------------------------------------------------ |
|
|
247
|
-
| `type` | `"exit_code"` | Yes | — | Completion type |
|
|
248
|
-
| `expect` | `number \| number[]` | No | `0` | Expected exit code(s). Pass an array for multiple acceptable codes |
|
|
249
|
-
|
|
250
|
-
#### `file_exists` — File existence check
|
|
251
|
-
|
|
252
|
-
| Field | Type | Required | Default | Description |
|
|
253
|
-
| ---------- | -------------------------- | -------- | ------- | ----------------------------------------------------- |
|
|
254
|
-
| `type` | `"file_exists"` | Yes | — | Completion type |
|
|
255
|
-
| `path` | `string` | Yes | — | File or directory path to check (relative to workDir) |
|
|
256
|
-
| `kind` | `"file" \| "dir" \| "any"` | No | `"any"` | Entity type constraint |
|
|
257
|
-
| `min_size` | `number` | No | — | Minimum file size in bytes (files only) |
|
|
258
|
-
|
|
259
|
-
#### `output_check` — Command-based output validation
|
|
260
|
-
|
|
261
|
-
| Field | Type | Required | Default | Description |
|
|
262
|
-
| --------- | ---------------- | -------- | ------- | ----------------------------------------------------------------------- |
|
|
263
|
-
| `type` | `"output_check"` | Yes | — | Completion type |
|
|
264
|
-
| `check` | `string` | Yes | — | Shell command to run. Task stdout is piped to its stdin; exits 0 = pass |
|
|
265
|
-
| `timeout` | `string` | No | `"30s"` | Max time to wait for the check command |
|
|
266
|
-
|
|
267
|
-
---
|
|
268
|
-
|
|
269
|
-
### Built-in Middlewares
|
|
270
|
-
|
|
271
|
-
#### `static_context` — Prepend file content to prompt
|
|
272
|
-
|
|
273
|
-
| Field | Type | Required | Default | Description |
|
|
274
|
-
| ------- | ------------------ | -------- | ------------------------- | ---------------------------------------------- |
|
|
275
|
-
| `type` | `"static_context"` | Yes | — | Middleware type |
|
|
276
|
-
| `file` | `string` | Yes | — | Path to the context file (relative to workDir) |
|
|
277
|
-
| `label` | `string` | No | `"Reference: {filename}"` | Label for the injected context section |
|
|
278
|
-
|
|
279
|
-
## API
|
|
280
|
-
|
|
281
|
-
### `bootstrapBuiltins()`
|
|
282
|
-
|
|
283
|
-
Registers all built-in plugins (claude-code driver, file/manual triggers, completion checks, static-context middleware).
|
|
284
|
-
|
|
285
|
-
### `loadPipeline(yaml: string, workDir: string): Promise<PipelineConfig>`
|
|
286
|
-
|
|
287
|
-
Parses YAML, resolves inheritance, and validates the configuration.
|
|
288
|
-
|
|
289
|
-
### `runPipeline(config, workDir, options?): Promise<EngineResult>`
|
|
290
|
-
|
|
291
|
-
Executes the pipeline. Returns `{ success, runId, logPath, summary, states }`.
|
|
292
|
-
|
|
293
|
-
Options:
|
|
294
|
-
|
|
295
|
-
- `approvalGateway` -- custom `ApprovalGateway` instance (defaults to `InMemoryApprovalGateway`)
|
|
296
|
-
- `signal` -- `AbortSignal` to cancel the run externally
|
|
297
|
-
- `onEvent` -- callback for real-time `PipelineEvent` updates:
|
|
298
|
-
- `pipeline_start` — pipeline began; includes `states: ReadonlyMap<taskId, TaskState>` (initial snapshot of all tasks at `waiting`)
|
|
299
|
-
- `task_status_change` — a task changed status; includes `state: TaskState` (complete snapshot at the time of change: `startedAt` is populated before the `running` event; `result` and `finishedAt` are populated before any terminal-status event)
|
|
300
|
-
- `task_log` — a structured log line was written to `pipeline.log`. Mirrors every `Logger` call (info/warn/error/debug/section/quiet) and carries `{ taskId: string | null, level, timestamp, text }`. `taskId` is non-null for lines tagged with a `[task:<id>]` prefix (or passed explicitly to `section`/`quiet`) and `null` for pipeline-wide messages such as the configuration dump and DAG topology. Use this to stream the full run process into UIs without tailing the log file.
|
|
301
|
-
- `pipeline_end` — pipeline finished; includes `success: boolean`
|
|
302
|
-
- `runId` -- caller-supplied run ID. When provided the engine uses this instead of generating its own, keeping the caller and the SDK log directories aligned on the same ID
|
|
303
|
-
- `maxLogRuns` -- number of per-run log directories to keep under `<workDir>/.tagma/logs/` (default: 20)
|
|
304
|
-
|
|
305
|
-
### `PipelineRunner`
|
|
306
|
-
|
|
307
|
-
Higher-level wrapper for managing multiple concurrent pipeline runs — designed for sidecar / Tauri IPC scenarios where the frontend controls pipeline lifecycle by ID.
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
const runner = new PipelineRunner(config, workDir);
|
|
311
|
-
|
|
312
|
-
// Subscribe before start — handler is called for every PipelineEvent
|
|
313
|
-
const unsubscribe = runner.subscribe((event) => {
|
|
314
|
-
tauriEmit('pipeline_event', { id: runner.instanceId, event });
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
runner.start(); // returns Promise<EngineResult>, idempotent
|
|
318
|
-
|
|
319
|
-
// Cancel from IPC
|
|
320
|
-
runner.abort();
|
|
321
|
-
|
|
322
|
-
// Available from the first pipeline_start event onward (not just after completion)
|
|
323
|
-
// Returns null only if the pipeline has never started
|
|
324
|
-
const states = runner.getStates(); // ReadonlyMap<taskId, TaskState> | null
|
|
325
|
-
```
|
|
326
|
-
|
|
327
|
-
Properties:
|
|
328
|
-
|
|
329
|
-
- `instanceId` — stable ID assigned at construction, safe to use as a Map key before `start()`
|
|
330
|
-
- `runId` — engine-assigned run ID, available after the first `pipeline_start` event (`null` until then)
|
|
331
|
-
- `status` — `'idle' | 'running' | 'done' | 'aborted'`
|
|
332
|
-
|
|
333
|
-
### `TriggerBlockedError` / `TriggerTimeoutError`
|
|
334
|
-
|
|
335
|
-
Typed error classes for trigger plugin error classification. The engine uses `instanceof` checks on these to set the correct task status (`blocked` or `timeout`) instead of matching on error message substrings.
|
|
336
|
-
|
|
337
|
-
Built-in triggers (`manual`, `file`) throw these automatically. Third-party trigger plugins should throw `TriggerBlockedError` for user/policy rejections and `TriggerTimeoutError` for genuine wait timeouts. Plugins that throw plain `Error` still work — the engine falls back to string matching for backward compatibility, but typed errors are preferred to avoid misclassification from coincidental substrings.
|
|
338
|
-
|
|
339
|
-
```ts
|
|
340
|
-
import { TriggerBlockedError, TriggerTimeoutError } from '@tagma/sdk';
|
|
341
|
-
|
|
342
|
-
// In a custom trigger plugin:
|
|
343
|
-
throw new TriggerBlockedError('Access denied by policy');
|
|
344
|
-
throw new TriggerTimeoutError('File did not appear within 30s');
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### `loadPlugins(names: string[]): Promise<void>`
|
|
348
|
-
|
|
349
|
-
Dynamically loads and registers external plugin packages.
|
|
350
|
-
|
|
351
|
-
### `registerPlugin(category, type, handler): void`
|
|
352
|
-
|
|
353
|
-
Registers a plugin handler manually. Idempotent — duplicate registrations are silently ignored.
|
|
354
|
-
|
|
355
|
-
Plugin handlers (`TriggerPlugin`, `CompletionPlugin`, `MiddlewarePlugin`) may optionally expose a declarative `schema: PluginSchema` field so visual editors can render a typed form for the plugin's config instead of a raw key/value editor:
|
|
356
|
-
|
|
357
|
-
```ts
|
|
358
|
-
import type { TriggerPlugin } from '@tagma/types';
|
|
359
|
-
|
|
360
|
-
export const HttpTrigger: TriggerPlugin = {
|
|
361
|
-
name: 'http',
|
|
362
|
-
schema: {
|
|
363
|
-
description: 'Wait for an HTTP endpoint to return 2xx before the task runs.',
|
|
364
|
-
fields: {
|
|
365
|
-
url: { type: 'string', required: true, placeholder: 'https://...' },
|
|
366
|
-
method: { type: 'enum', enum: ['GET', 'POST'], default: 'GET' },
|
|
367
|
-
timeout: { type: 'duration', description: 'Give up after this long.' },
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
async watch(config, ctx) {
|
|
371
|
-
/* ... */
|
|
372
|
-
},
|
|
373
|
-
};
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
The schema is purely descriptive — plugins still perform their own runtime validation. Supported field types: `string`, `number`, `boolean`, `enum`, `path`, `duration`, `number-or-list`. Each field can declare `required`, `default`, `description`, `enum`, `min`/`max`, `placeholder`. Built-in plugins (`file`/`manual` triggers; `exit_code`/`file_exists`/`output_check` completions; `static_context` middleware) all ship with schemas so editors can generate forms out of the box.
|
|
377
|
-
|
|
378
|
-
### `getHandler(category, type): PluginType`
|
|
379
|
-
|
|
380
|
-
Retrieves a registered plugin handler. Throws if the plugin is not registered.
|
|
381
|
-
|
|
382
|
-
### `hasHandler(category, type): boolean`
|
|
383
|
-
|
|
384
|
-
Returns `true` if a handler is registered for the given category and type.
|
|
385
|
-
|
|
386
|
-
### `listRegistered(category): string[]`
|
|
387
|
-
|
|
388
|
-
Lists all registered handler type names for a plugin category (`'drivers'`, `'triggers'`, `'completions'`, `'middlewares'`).
|
|
389
|
-
|
|
390
|
-
### `resolveConfig(raw: RawPipelineConfig, workDir: string): PipelineConfig`
|
|
391
|
-
|
|
392
|
-
Resolves a raw pipeline config into a fully resolved `PipelineConfig` — applies inheritance (pipeline → track → task) for driver, model, permissions, and cwd. Validates and resolves all file paths against `workDir`.
|
|
393
|
-
|
|
394
|
-
Use `loadPipeline` for the common parse-and-resolve flow. Use `resolveConfig` directly when you need to manipulate the raw config between parsing and resolution.
|
|
395
|
-
|
|
396
|
-
### `attachStdinApprovalAdapter(gateway): StdinApprovalAdapter`
|
|
397
|
-
|
|
398
|
-
Attaches an interactive stdin-based approval handler.
|
|
399
|
-
|
|
400
|
-
### `attachWebSocketApprovalAdapter(gateway, options?): WebSocketApprovalAdapter`
|
|
401
|
-
|
|
402
|
-
Starts a WebSocket server for remote approval decisions.
|
|
403
|
-
|
|
404
|
-
### Config CRUD (`config-ops`)
|
|
405
|
-
|
|
406
|
-
Pure, immutable helper functions for building and editing `RawPipelineConfig` in a visual editor. No runtime dependencies — safe to use in renderer processes.
|
|
407
|
-
|
|
408
|
-
```ts
|
|
409
|
-
import {
|
|
410
|
-
createEmptyPipeline,
|
|
411
|
-
setPipelineField,
|
|
412
|
-
upsertTrack,
|
|
413
|
-
removeTrack,
|
|
414
|
-
moveTrack,
|
|
415
|
-
updateTrack,
|
|
416
|
-
upsertTask,
|
|
417
|
-
removeTask,
|
|
418
|
-
moveTask,
|
|
419
|
-
transferTask,
|
|
420
|
-
serializePipeline,
|
|
421
|
-
} from '@tagma/sdk';
|
|
422
|
-
|
|
423
|
-
// Build a config programmatically
|
|
424
|
-
let config = createEmptyPipeline('my-pipeline');
|
|
425
|
-
config = upsertTrack(config, { id: 'backend', name: 'Backend', tasks: [] });
|
|
426
|
-
config = upsertTask(config, 'backend', { id: 'implement', prompt: 'Add /health endpoint' });
|
|
427
|
-
|
|
428
|
-
// Sync back to YAML
|
|
429
|
-
const yaml = serializePipeline(config);
|
|
430
|
-
```
|
|
431
|
-
|
|
432
|
-
| Function | Description |
|
|
433
|
-
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
434
|
-
| `createEmptyPipeline(name)` | Create a minimal pipeline config |
|
|
435
|
-
| `setPipelineField(config, fields)` | Update top-level pipeline fields |
|
|
436
|
-
| `upsertTrack(config, track)` | Insert or replace a track by id |
|
|
437
|
-
| `removeTrack(config, trackId)` | Remove a track |
|
|
438
|
-
| `moveTrack(config, trackId, toIndex)` | Reorder a track |
|
|
439
|
-
| `updateTrack(config, trackId, fields)` | Patch track fields (not tasks) |
|
|
440
|
-
| `upsertTask(config, trackId, task)` | Insert or replace a task |
|
|
441
|
-
| `removeTask(config, trackId, taskId, cleanRefs?)` | Remove a task; pass `cleanRefs: true` to also strip dangling `depends_on` / `continue_from` references. Only refs that resolve to the deleted task are removed — same-named tasks in other tracks are unaffected |
|
|
442
|
-
| `moveTask(config, trackId, taskId, toIndex)` | Reorder a task within its track |
|
|
443
|
-
| `transferTask(config, fromTrackId, taskId, toTrackId, qualifyRefs?)` | Move a task across tracks. When `qualifyRefs` is `true` (default), bare `depends_on` / `continue_from` references to the moved task are converted to fully-qualified form (`toTrackId.taskId`) so same-track resolution stays correct |
|
|
444
|
-
|
|
445
|
-
### `parseYaml(content: string): RawPipelineConfig`
|
|
446
|
-
|
|
447
|
-
Parses a YAML string and returns the raw (unresolved) pipeline config. Use this when you need to edit and re-save YAML without losing relative paths or user-authored formatting — pass the result to `serializePipeline()` rather than going through `loadPipeline()`.
|
|
448
|
-
|
|
449
|
-
### `deresolvePipeline(config: PipelineConfig, workDir: string): RawPipelineConfig`
|
|
450
|
-
|
|
451
|
-
Converts a resolved `PipelineConfig` back to a `RawPipelineConfig` suitable for serialization. Strips injected defaults and converts absolute `cwd` paths back to relative so the output YAML is portable across machines.
|
|
452
|
-
|
|
453
|
-
Use this when you have a programmatically modified resolved config and need to save it back to YAML:
|
|
454
|
-
|
|
455
|
-
```ts
|
|
456
|
-
// Correct: load → modify resolved config → deresolve → save
|
|
457
|
-
const config = await loadPipeline(yaml, workDir);
|
|
458
|
-
const modified = { ...config, name: 'renamed' };
|
|
459
|
-
const savedYaml = serializePipeline(deresolvePipeline(modified, workDir));
|
|
460
|
-
|
|
461
|
-
// Also correct: work entirely in raw space (preferred for visual editors)
|
|
462
|
-
const raw = parseYaml(yaml);
|
|
463
|
-
const updatedRaw = setPipelineField(raw, { name: 'renamed' });
|
|
464
|
-
const savedYaml = serializePipeline(updatedRaw);
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
### `validateConfig(config: PipelineConfig): string[]`
|
|
468
|
-
|
|
469
|
-
Validates a resolved pipeline config without executing it. Checks DAG structure (cycles, missing dependencies). Returns an array of error message strings — empty means valid.
|
|
470
|
-
|
|
471
|
-
Use `validateRaw` for editing raw configs in a UI; use `validateConfig` after `resolveConfig` for a final pre-run check.
|
|
472
|
-
|
|
473
|
-
### `validateRaw(config: RawPipelineConfig): ValidationError[]`
|
|
474
|
-
|
|
475
|
-
Validates a raw pipeline config without resolving inheritance or executing anything. Returns a flat list of `{ path, message }` objects — empty array means valid.
|
|
476
|
-
|
|
477
|
-
Checks: required fields, `prompt`/`command` exclusivity, duplicate task IDs within a track, `depends_on`/`continue_from` reference integrity (including ambiguous bare refs that exist in multiple tracks — use `trackId.taskId` to disambiguate), circular dependency detection.
|
|
478
|
-
|
|
479
|
-
Does **not** check plugin registration (plugins may not be loaded at edit time).
|
|
480
|
-
|
|
481
|
-
```ts
|
|
482
|
-
const errors = validateRaw(draftConfig);
|
|
483
|
-
if (errors.length > 0) {
|
|
484
|
-
errors.forEach((e) => highlightNode(e.path, e.message));
|
|
485
|
-
}
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### `buildRawDag(config: RawPipelineConfig): RawDag`
|
|
489
|
-
|
|
490
|
-
Extracts the topology of a raw (unresolved) pipeline config as a graph — no `workDir` or plugin registration required. Intended for the visual editor to render the flow graph during editing.
|
|
491
|
-
|
|
492
|
-
Returns `{ nodes: ReadonlyMap<taskId, RawDagNode>, edges: { from, to }[] }` where each edge represents a dependency (from must complete before to). Unresolvable refs are silently skipped.
|
|
493
|
-
|
|
494
|
-
```ts
|
|
495
|
-
const { nodes, edges } = buildRawDag(draftConfig);
|
|
496
|
-
// nodes — keyed by "trackId.taskId"
|
|
497
|
-
// edges — [{ from: "track.taskA", to: "track.taskB" }, ...]
|
|
498
|
-
```
|
|
499
|
-
|
|
500
|
-
Use `buildDag` instead when you have a fully resolved `PipelineConfig` and need topological sort order.
|
|
501
|
-
|
|
502
|
-
### `Logger`
|
|
503
|
-
|
|
504
|
-
Dual-channel logger — console + file. Creates a per-run log file at `<workDir>/.tagma/logs/<runId>/pipeline.log`.
|
|
505
|
-
|
|
506
|
-
```ts
|
|
507
|
-
const logger = new Logger(workDir, runId);
|
|
508
|
-
logger.info('[track]', 'message'); // console + file
|
|
509
|
-
logger.warn('[track]', 'message'); // console + file
|
|
510
|
-
logger.error('[track]', 'message'); // console + file
|
|
511
|
-
logger.debug('[track]', 'message'); // file only
|
|
512
|
-
logger.section('Title'); // file only — visual separator
|
|
513
|
-
logger.quiet(bulkText); // file only — bulk payload
|
|
514
|
-
logger.path; // log file path
|
|
515
|
-
logger.dir; // run artifact directory
|
|
516
|
-
logger.close(); // close the persistent file handle (called automatically by runPipeline at run completion)
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
Pass an optional third argument to stream every appended line out as a
|
|
520
|
-
structured `LogRecord` — `runPipeline` uses this to emit `task_log` events:
|
|
521
|
-
|
|
522
|
-
```ts
|
|
523
|
-
import { Logger, type LogRecord } from '@tagma/sdk';
|
|
524
|
-
|
|
525
|
-
const logger = new Logger(workDir, runId, (record: LogRecord) => {
|
|
526
|
-
// record = { level, taskId, timestamp, text }
|
|
527
|
-
// level = 'info' | 'warn' | 'error' | 'debug' | 'section' | 'quiet'
|
|
528
|
-
// taskId is extracted from a '[task:<id>]' prefix, or null for untagged lines
|
|
529
|
-
forwardToUI(record);
|
|
530
|
-
});
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
`section` and `quiet` carry no prefix, so pass an explicit `taskId` when the
|
|
534
|
-
line logically belongs to a task — the extractor cannot infer one otherwise:
|
|
535
|
-
|
|
536
|
-
```ts
|
|
537
|
-
logger.section(`Task ${taskId}`, taskId);
|
|
538
|
-
logger.quiet(`--- stdout (${taskId}) ---\n${body}\n--- end stdout ---`, taskId);
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
### `tailLines(text: string, n: number): string`
|
|
542
|
-
|
|
543
|
-
Returns the last `n` non-empty lines of `text`, joined with newlines.
|
|
544
|
-
|
|
545
|
-
### `clip(text: string, maxBytes?: number): string`
|
|
546
|
-
|
|
547
|
-
Truncates `text` to at most `maxBytes` UTF-8 bytes (default 16 KB), appending a `…[truncated N bytes]` marker when truncation occurs. Multi-byte characters (CJK, emoji) are counted correctly.
|
|
548
|
-
|
|
549
|
-
### Utilities
|
|
550
|
-
|
|
551
|
-
| Function | Description |
|
|
552
|
-
| ------------------------------------- | --------------------------------------------------------- |
|
|
553
|
-
| `parseDuration(input)` | Parses `"30s"`, `"5m"`, `"2h"` → milliseconds |
|
|
554
|
-
| `validatePath(filePath, projectRoot)` | Resolves path, throws if it escapes project root |
|
|
555
|
-
| `generateRunId()` | Generates a unique run ID (`run_<ts>_<seq>_<rand>`) |
|
|
556
|
-
| `nowISO()` | Returns `new Date().toISOString()` |
|
|
557
|
-
| `truncateForName(text, maxLen?)` | Truncates first line to `maxLen` (default 40) for display |
|
|
558
|
-
|
|
559
|
-
## Related Packages
|
|
560
|
-
|
|
561
|
-
| Package | Description |
|
|
562
|
-
| ------------------------------------------------------------------------------ | -------------------------- |
|
|
563
|
-
| [@tagma/types](https://www.npmjs.com/package/@tagma/types) | Shared TypeScript types |
|
|
564
|
-
| [@tagma/driver-codex](https://www.npmjs.com/package/@tagma/driver-codex) | Codex CLI driver plugin |
|
|
565
|
-
| [@tagma/driver-opencode](https://www.npmjs.com/package/@tagma/driver-opencode) | OpenCode CLI driver plugin |
|
|
566
|
-
|
|
567
|
-
## License
|
|
568
|
-
|
|
569
|
-
MIT
|
|
1
|
+
# @tagma/sdk
|
|
2
|
+
|
|
3
|
+
> ## ⚠️ Bun-only runtime
|
|
4
|
+
>
|
|
5
|
+
> ```bash
|
|
6
|
+
> bun add @tagma/sdk
|
|
7
|
+
> ```
|
|
8
|
+
>
|
|
9
|
+
> This package is published as pre-built JavaScript (`dist/`), but the runtime
|
|
10
|
+
> uses Bun-only APIs (`Bun.spawn`, `Bun.file`, `Bun.serve`). It will install
|
|
11
|
+
> under `npm` / `yarn` / `pnpm` without error, but **crash at runtime on Node**
|
|
12
|
+
> the first time a pipeline spawns a task. Get Bun at <https://bun.sh>.
|
|
13
|
+
>
|
|
14
|
+
> _(The `npm i @tagma/sdk` line in the npmjs.com sidebar is auto-generated by
|
|
15
|
+
> the npm registry website and cannot be removed — please ignore it and use
|
|
16
|
+
> the command above.)_
|
|
17
|
+
|
|
18
|
+
A local AI task orchestration SDK for [Bun](https://bun.sh). Define multi-track pipelines in YAML, run AI coding agents (Claude Code, Codex, OpenCode) and shell commands in parallel with dependency resolution, approval gates, and lifecycle hooks.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
**Requires Bun ≥ 1.3.** Node/npm are not supported.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
bun add @tagma/sdk
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
**1. Define a pipeline** (`pipeline.yaml`)
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
pipeline:
|
|
34
|
+
name: build-and-test
|
|
35
|
+
tracks:
|
|
36
|
+
- id: backend
|
|
37
|
+
name: Backend
|
|
38
|
+
driver: claude-code
|
|
39
|
+
permissions: { read: true, write: true, execute: false }
|
|
40
|
+
tasks:
|
|
41
|
+
- id: implement
|
|
42
|
+
name: Implement feature
|
|
43
|
+
prompt: 'Add a /health endpoint to src/server.ts'
|
|
44
|
+
- id: test
|
|
45
|
+
name: Run tests
|
|
46
|
+
command: 'bun test'
|
|
47
|
+
depends_on: [implement]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**2. Run it programmatically**
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { bootstrapBuiltins, loadPipeline, runPipeline, InMemoryApprovalGateway } from '@tagma/sdk';
|
|
54
|
+
|
|
55
|
+
// Register built-in drivers, triggers, completions
|
|
56
|
+
bootstrapBuiltins();
|
|
57
|
+
|
|
58
|
+
const yaml = await Bun.file('pipeline.yaml').text();
|
|
59
|
+
const config = await loadPipeline(yaml, process.cwd());
|
|
60
|
+
|
|
61
|
+
const result = await runPipeline(config, process.cwd());
|
|
62
|
+
console.log(result.success ? 'Done' : 'Failed');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Features
|
|
66
|
+
|
|
67
|
+
- **Multi-track DAG execution** -- tasks run in parallel across tracks, respecting `depends_on` ordering
|
|
68
|
+
- **Driver plugins** -- built-in `claude-code` driver; install `@tagma/driver-codex` or `@tagma/driver-opencode` for other agents
|
|
69
|
+
- **Session handoff** -- `continue_from` passes context between tasks (session resume or text injection)
|
|
70
|
+
- **Approval gates** -- trigger-based approval with stdin and WebSocket adapters
|
|
71
|
+
- **Lifecycle hooks** -- `pipeline_start`, `task_start`, `task_success`, `task_failure`, `pipeline_complete`, `pipeline_error`
|
|
72
|
+
- **Middleware** -- enrich prompts before execution (e.g. inject static context)
|
|
73
|
+
- **Completion checks** -- validate task output with `exit_code`, `file_exists`, or `output_check` plugins
|
|
74
|
+
- **Plugin schemas** -- triggers/completions/middlewares can declare a `PluginSchema` so visual editors render typed forms for their config
|
|
75
|
+
|
|
76
|
+
## Pipeline YAML Reference
|
|
77
|
+
|
|
78
|
+
### Full Structure
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
pipeline:
|
|
82
|
+
name: my-pipeline
|
|
83
|
+
driver: claude-code
|
|
84
|
+
timeout: 30m
|
|
85
|
+
plugins:
|
|
86
|
+
- '@tagma/driver-codex'
|
|
87
|
+
hooks:
|
|
88
|
+
pipeline_start: 'echo starting'
|
|
89
|
+
task_start: 'echo task begin'
|
|
90
|
+
task_success: 'echo task ok'
|
|
91
|
+
task_failure: 'notify-slack.sh'
|
|
92
|
+
pipeline_complete: 'echo done'
|
|
93
|
+
pipeline_error: 'alert.sh'
|
|
94
|
+
tracks:
|
|
95
|
+
- id: track-1
|
|
96
|
+
name: Track One
|
|
97
|
+
color: '#3b82f6'
|
|
98
|
+
driver: claude-code
|
|
99
|
+
model: claude-sonnet-4-6
|
|
100
|
+
agent_profile: senior
|
|
101
|
+
cwd: ./services/backend
|
|
102
|
+
permissions:
|
|
103
|
+
read: true
|
|
104
|
+
write: true
|
|
105
|
+
execute: false
|
|
106
|
+
on_failure: skip_downstream
|
|
107
|
+
middlewares:
|
|
108
|
+
- type: static_context
|
|
109
|
+
file: ./context.md
|
|
110
|
+
label: Architecture Guide
|
|
111
|
+
tasks:
|
|
112
|
+
- id: task-a
|
|
113
|
+
name: Do something
|
|
114
|
+
prompt: 'Your prompt here'
|
|
115
|
+
timeout: 10m
|
|
116
|
+
driver: claude-code
|
|
117
|
+
model: claude-sonnet-4-6
|
|
118
|
+
agent_profile: senior
|
|
119
|
+
cwd: ./src
|
|
120
|
+
permissions:
|
|
121
|
+
read: true
|
|
122
|
+
write: true
|
|
123
|
+
execute: false
|
|
124
|
+
middlewares:
|
|
125
|
+
- type: static_context
|
|
126
|
+
file: ./ref.md
|
|
127
|
+
trigger:
|
|
128
|
+
type: manual
|
|
129
|
+
message: 'Approve before running'
|
|
130
|
+
timeout: 5m
|
|
131
|
+
completion:
|
|
132
|
+
type: exit_code
|
|
133
|
+
expect: 0
|
|
134
|
+
- id: task-b
|
|
135
|
+
name: Follow up
|
|
136
|
+
prompt: 'Continue the work'
|
|
137
|
+
continue_from: task-a
|
|
138
|
+
depends_on: [task-a]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Pipeline Fields
|
|
142
|
+
|
|
143
|
+
| Field | Type | Required | Description |
|
|
144
|
+
| --------- | --------------- | -------- | ------------------------------------------------------------------------------------------ |
|
|
145
|
+
| `name` | `string` | Yes | Pipeline name, used in logs and run IDs |
|
|
146
|
+
| `driver` | `string` | No | Default driver for all tracks/tasks (inherited). Built-in: `claude-code` |
|
|
147
|
+
| `model` | `string` | No | Default model for all tracks/tasks (inherited). Exact model name, e.g. `claude-sonnet-4-6` |
|
|
148
|
+
| `timeout` | `string` | No | Pipeline-level timeout. Format: `"30s"`, `"5m"`, `"2h"` |
|
|
149
|
+
| `plugins` | `string[]` | No | External plugin packages to load, e.g. `["@tagma/driver-codex"]` |
|
|
150
|
+
| `hooks` | `HooksConfig` | No | Shell commands to run at lifecycle events (see Hooks below) |
|
|
151
|
+
| `tracks` | `TrackConfig[]` | Yes | List of parallel execution tracks |
|
|
152
|
+
|
|
153
|
+
### Hooks Fields
|
|
154
|
+
|
|
155
|
+
Each hook value can be a single command string or an array of commands.
|
|
156
|
+
|
|
157
|
+
| Field | Type | Description |
|
|
158
|
+
| ------------------- | -------------------- | -------------------------------------------- |
|
|
159
|
+
| `pipeline_start` | `string \| string[]` | Runs when the pipeline begins |
|
|
160
|
+
| `task_start` | `string \| string[]` | Runs before each task starts |
|
|
161
|
+
| `task_success` | `string \| string[]` | Runs after a task succeeds |
|
|
162
|
+
| `task_failure` | `string \| string[]` | Runs after a task fails |
|
|
163
|
+
| `pipeline_complete` | `string \| string[]` | Runs when the pipeline finishes successfully |
|
|
164
|
+
| `pipeline_error` | `string \| string[]` | Runs when the pipeline finishes with errors |
|
|
165
|
+
|
|
166
|
+
### Track Fields
|
|
167
|
+
|
|
168
|
+
| Field | Type | Required | Default | Description |
|
|
169
|
+
| --------------- | -------------------- | -------- | ----------------------- | -------------------------------------------------------------------- |
|
|
170
|
+
| `id` | `string` | Yes | — | Unique track identifier |
|
|
171
|
+
| `name` | `string` | Yes | — | Display name |
|
|
172
|
+
| `color` | `string` | No | — | Color hint for UI rendering (e.g. `"#3b82f6"`) |
|
|
173
|
+
| `driver` | `string` | No | Inherited from pipeline | Driver for all tasks in this track |
|
|
174
|
+
| `model` | `string` | No | Inherited from pipeline | Exact model name passed to the driver CLI (e.g. `claude-sonnet-4-6`) |
|
|
175
|
+
| `agent_profile` | `string` | No | — | Named agent configuration profile |
|
|
176
|
+
| `cwd` | `string` | No | Pipeline workDir | Working directory for tasks in this track (relative path) |
|
|
177
|
+
| `permissions` | `Permissions` | No | Inherited from pipeline | Default permissions for tasks (see Permissions) |
|
|
178
|
+
| `on_failure` | `OnFailure` | No | `skip_downstream` | Failure strategy: `skip_downstream`, `stop_all`, `ignore` |
|
|
179
|
+
| `middlewares` | `MiddlewareConfig[]` | No | — | Middlewares applied to all tasks (task-level overrides track-level) |
|
|
180
|
+
| `tasks` | `TaskConfig[]` | Yes | — | Ordered list of tasks in this track |
|
|
181
|
+
|
|
182
|
+
### Task Fields
|
|
183
|
+
|
|
184
|
+
| Field | Type | Required | Default | Description |
|
|
185
|
+
| --------------- | -------------------- | -------- | -------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
186
|
+
| `id` | `string` | Yes | — | Unique task identifier (unique within the pipeline) |
|
|
187
|
+
| `name` | `string` | No | — | Display name |
|
|
188
|
+
| `prompt` | `string` | No\* | — | AI prompt to send to the driver. \*Mutually exclusive with `command` |
|
|
189
|
+
| `command` | `string` | No\* | — | Shell command to execute directly. \*Mutually exclusive with `prompt` |
|
|
190
|
+
| `depends_on` | `string[]` | No | — | Task IDs that must complete before this task runs. Cross-track refs use `trackId.taskId` |
|
|
191
|
+
| `continue_from` | `string` | No | — | Task ID whose output/session to continue from (session handoff). Cross-track refs use `trackId.taskId` |
|
|
192
|
+
| `driver` | `string` | No | Inherited from track | Driver override for this task |
|
|
193
|
+
| `model` | `string` | No | Inherited from track | Model name override for this task |
|
|
194
|
+
| `agent_profile` | `string` | No | Inherited from track | Agent profile override |
|
|
195
|
+
| `cwd` | `string` | No | Inherited from track | Working directory override (relative path) |
|
|
196
|
+
| `timeout` | `string` | No | — | Task-level timeout. Format: `"30s"`, `"5m"`, `"2h"` |
|
|
197
|
+
| `permissions` | `Permissions` | No | Inherited from track | Permission override (see Permissions) |
|
|
198
|
+
| `middlewares` | `MiddlewareConfig[]` | No | Inherited from track | Middleware override. Set `[]` to disable inherited middlewares |
|
|
199
|
+
| `trigger` | `TriggerConfig` | No | — | Gate that must resolve before the task runs (see Triggers) |
|
|
200
|
+
| `completion` | `CompletionConfig` | No | — | Post-execution check to validate task output (see Completions) |
|
|
201
|
+
|
|
202
|
+
### Permissions
|
|
203
|
+
|
|
204
|
+
| Field | Type | Default | Description |
|
|
205
|
+
| --------- | --------- | ------- | ----------------------------------- |
|
|
206
|
+
| `read` | `boolean` | — | Allow the agent to read files |
|
|
207
|
+
| `write` | `boolean` | — | Allow the agent to write files |
|
|
208
|
+
| `execute` | `boolean` | — | Allow the agent to execute commands |
|
|
209
|
+
|
|
210
|
+
### Inheritance
|
|
211
|
+
|
|
212
|
+
Fields are inherited top-down: **pipeline → track → task**. A value set at a lower level overrides the inherited value.
|
|
213
|
+
|
|
214
|
+
Inherited fields: `driver`, `model`, `permissions`, `cwd`, `middlewares`.
|
|
215
|
+
|
|
216
|
+
Track-level `middlewares` apply to all tasks in the track. Setting task-level `middlewares` **replaces** (not appends) the track-level list. Use `middlewares: []` to disable all inherited middlewares for a task.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### Built-in Triggers
|
|
221
|
+
|
|
222
|
+
#### `manual` — Human approval gate
|
|
223
|
+
|
|
224
|
+
| Field | Type | Required | Default | Description |
|
|
225
|
+
| ---------- | ---------- | -------- | ------------------------------------------------------ | ------------------------------------------------- |
|
|
226
|
+
| `type` | `"manual"` | Yes | — | Trigger type |
|
|
227
|
+
| `message` | `string` | No | `"Manual confirmation required for task \"{taskId}\""` | Message shown to the approver |
|
|
228
|
+
| `timeout` | `string` | No | — | How long to wait for a decision before timing out |
|
|
229
|
+
| `metadata` | `object` | No | — | Arbitrary metadata passed to the approval gateway |
|
|
230
|
+
|
|
231
|
+
#### `file` — File watcher gate
|
|
232
|
+
|
|
233
|
+
| Field | Type | Required | Default | Description |
|
|
234
|
+
| --------- | -------- | -------- | ------- | ---------------------------------------------- |
|
|
235
|
+
| `type` | `"file"` | Yes | — | Trigger type |
|
|
236
|
+
| `path` | `string` | Yes | — | File path to watch (relative to workDir) |
|
|
237
|
+
| `timeout` | `string` | No | — | How long to wait for the file to appear/change |
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
### Built-in Completions
|
|
242
|
+
|
|
243
|
+
#### `exit_code` — Exit code check
|
|
244
|
+
|
|
245
|
+
| Field | Type | Required | Default | Description |
|
|
246
|
+
| -------- | -------------------- | -------- | ------- | ------------------------------------------------------------------ |
|
|
247
|
+
| `type` | `"exit_code"` | Yes | — | Completion type |
|
|
248
|
+
| `expect` | `number \| number[]` | No | `0` | Expected exit code(s). Pass an array for multiple acceptable codes |
|
|
249
|
+
|
|
250
|
+
#### `file_exists` — File existence check
|
|
251
|
+
|
|
252
|
+
| Field | Type | Required | Default | Description |
|
|
253
|
+
| ---------- | -------------------------- | -------- | ------- | ----------------------------------------------------- |
|
|
254
|
+
| `type` | `"file_exists"` | Yes | — | Completion type |
|
|
255
|
+
| `path` | `string` | Yes | — | File or directory path to check (relative to workDir) |
|
|
256
|
+
| `kind` | `"file" \| "dir" \| "any"` | No | `"any"` | Entity type constraint |
|
|
257
|
+
| `min_size` | `number` | No | — | Minimum file size in bytes (files only) |
|
|
258
|
+
|
|
259
|
+
#### `output_check` — Command-based output validation
|
|
260
|
+
|
|
261
|
+
| Field | Type | Required | Default | Description |
|
|
262
|
+
| --------- | ---------------- | -------- | ------- | ----------------------------------------------------------------------- |
|
|
263
|
+
| `type` | `"output_check"` | Yes | — | Completion type |
|
|
264
|
+
| `check` | `string` | Yes | — | Shell command to run. Task stdout is piped to its stdin; exits 0 = pass |
|
|
265
|
+
| `timeout` | `string` | No | `"30s"` | Max time to wait for the check command |
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### Built-in Middlewares
|
|
270
|
+
|
|
271
|
+
#### `static_context` — Prepend file content to prompt
|
|
272
|
+
|
|
273
|
+
| Field | Type | Required | Default | Description |
|
|
274
|
+
| ------- | ------------------ | -------- | ------------------------- | ---------------------------------------------- |
|
|
275
|
+
| `type` | `"static_context"` | Yes | — | Middleware type |
|
|
276
|
+
| `file` | `string` | Yes | — | Path to the context file (relative to workDir) |
|
|
277
|
+
| `label` | `string` | No | `"Reference: {filename}"` | Label for the injected context section |
|
|
278
|
+
|
|
279
|
+
## API
|
|
280
|
+
|
|
281
|
+
### `bootstrapBuiltins()`
|
|
282
|
+
|
|
283
|
+
Registers all built-in plugins (claude-code driver, file/manual triggers, completion checks, static-context middleware).
|
|
284
|
+
|
|
285
|
+
### `loadPipeline(yaml: string, workDir: string): Promise<PipelineConfig>`
|
|
286
|
+
|
|
287
|
+
Parses YAML, resolves inheritance, and validates the configuration.
|
|
288
|
+
|
|
289
|
+
### `runPipeline(config, workDir, options?): Promise<EngineResult>`
|
|
290
|
+
|
|
291
|
+
Executes the pipeline. Returns `{ success, runId, logPath, summary, states }`.
|
|
292
|
+
|
|
293
|
+
Options:
|
|
294
|
+
|
|
295
|
+
- `approvalGateway` -- custom `ApprovalGateway` instance (defaults to `InMemoryApprovalGateway`)
|
|
296
|
+
- `signal` -- `AbortSignal` to cancel the run externally
|
|
297
|
+
- `onEvent` -- callback for real-time `PipelineEvent` updates:
|
|
298
|
+
- `pipeline_start` — pipeline began; includes `states: ReadonlyMap<taskId, TaskState>` (initial snapshot of all tasks at `waiting`)
|
|
299
|
+
- `task_status_change` — a task changed status; includes `state: TaskState` (complete snapshot at the time of change: `startedAt` is populated before the `running` event; `result` and `finishedAt` are populated before any terminal-status event)
|
|
300
|
+
- `task_log` — a structured log line was written to `pipeline.log`. Mirrors every `Logger` call (info/warn/error/debug/section/quiet) and carries `{ taskId: string | null, level, timestamp, text }`. `taskId` is non-null for lines tagged with a `[task:<id>]` prefix (or passed explicitly to `section`/`quiet`) and `null` for pipeline-wide messages such as the configuration dump and DAG topology. Use this to stream the full run process into UIs without tailing the log file.
|
|
301
|
+
- `pipeline_end` — pipeline finished; includes `success: boolean`
|
|
302
|
+
- `runId` -- caller-supplied run ID. When provided the engine uses this instead of generating its own, keeping the caller and the SDK log directories aligned on the same ID
|
|
303
|
+
- `maxLogRuns` -- number of per-run log directories to keep under `<workDir>/.tagma/logs/` (default: 20)
|
|
304
|
+
|
|
305
|
+
### `PipelineRunner`
|
|
306
|
+
|
|
307
|
+
Higher-level wrapper for managing multiple concurrent pipeline runs — designed for sidecar / Tauri IPC scenarios where the frontend controls pipeline lifecycle by ID.
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
const runner = new PipelineRunner(config, workDir);
|
|
311
|
+
|
|
312
|
+
// Subscribe before start — handler is called for every PipelineEvent
|
|
313
|
+
const unsubscribe = runner.subscribe((event) => {
|
|
314
|
+
tauriEmit('pipeline_event', { id: runner.instanceId, event });
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
runner.start(); // returns Promise<EngineResult>, idempotent
|
|
318
|
+
|
|
319
|
+
// Cancel from IPC
|
|
320
|
+
runner.abort();
|
|
321
|
+
|
|
322
|
+
// Available from the first pipeline_start event onward (not just after completion)
|
|
323
|
+
// Returns null only if the pipeline has never started
|
|
324
|
+
const states = runner.getStates(); // ReadonlyMap<taskId, TaskState> | null
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Properties:
|
|
328
|
+
|
|
329
|
+
- `instanceId` — stable ID assigned at construction, safe to use as a Map key before `start()`
|
|
330
|
+
- `runId` — engine-assigned run ID, available after the first `pipeline_start` event (`null` until then)
|
|
331
|
+
- `status` — `'idle' | 'running' | 'done' | 'aborted'`
|
|
332
|
+
|
|
333
|
+
### `TriggerBlockedError` / `TriggerTimeoutError`
|
|
334
|
+
|
|
335
|
+
Typed error classes for trigger plugin error classification. The engine uses `instanceof` checks on these to set the correct task status (`blocked` or `timeout`) instead of matching on error message substrings.
|
|
336
|
+
|
|
337
|
+
Built-in triggers (`manual`, `file`) throw these automatically. Third-party trigger plugins should throw `TriggerBlockedError` for user/policy rejections and `TriggerTimeoutError` for genuine wait timeouts. Plugins that throw plain `Error` still work — the engine falls back to string matching for backward compatibility, but typed errors are preferred to avoid misclassification from coincidental substrings.
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
import { TriggerBlockedError, TriggerTimeoutError } from '@tagma/sdk';
|
|
341
|
+
|
|
342
|
+
// In a custom trigger plugin:
|
|
343
|
+
throw new TriggerBlockedError('Access denied by policy');
|
|
344
|
+
throw new TriggerTimeoutError('File did not appear within 30s');
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### `loadPlugins(names: string[]): Promise<void>`
|
|
348
|
+
|
|
349
|
+
Dynamically loads and registers external plugin packages.
|
|
350
|
+
|
|
351
|
+
### `registerPlugin(category, type, handler): void`
|
|
352
|
+
|
|
353
|
+
Registers a plugin handler manually. Idempotent — duplicate registrations are silently ignored.
|
|
354
|
+
|
|
355
|
+
Plugin handlers (`TriggerPlugin`, `CompletionPlugin`, `MiddlewarePlugin`) may optionally expose a declarative `schema: PluginSchema` field so visual editors can render a typed form for the plugin's config instead of a raw key/value editor:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import type { TriggerPlugin } from '@tagma/types';
|
|
359
|
+
|
|
360
|
+
export const HttpTrigger: TriggerPlugin = {
|
|
361
|
+
name: 'http',
|
|
362
|
+
schema: {
|
|
363
|
+
description: 'Wait for an HTTP endpoint to return 2xx before the task runs.',
|
|
364
|
+
fields: {
|
|
365
|
+
url: { type: 'string', required: true, placeholder: 'https://...' },
|
|
366
|
+
method: { type: 'enum', enum: ['GET', 'POST'], default: 'GET' },
|
|
367
|
+
timeout: { type: 'duration', description: 'Give up after this long.' },
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
async watch(config, ctx) {
|
|
371
|
+
/* ... */
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
The schema is purely descriptive — plugins still perform their own runtime validation. Supported field types: `string`, `number`, `boolean`, `enum`, `path`, `duration`, `number-or-list`. Each field can declare `required`, `default`, `description`, `enum`, `min`/`max`, `placeholder`. Built-in plugins (`file`/`manual` triggers; `exit_code`/`file_exists`/`output_check` completions; `static_context` middleware) all ship with schemas so editors can generate forms out of the box.
|
|
377
|
+
|
|
378
|
+
### `getHandler(category, type): PluginType`
|
|
379
|
+
|
|
380
|
+
Retrieves a registered plugin handler. Throws if the plugin is not registered.
|
|
381
|
+
|
|
382
|
+
### `hasHandler(category, type): boolean`
|
|
383
|
+
|
|
384
|
+
Returns `true` if a handler is registered for the given category and type.
|
|
385
|
+
|
|
386
|
+
### `listRegistered(category): string[]`
|
|
387
|
+
|
|
388
|
+
Lists all registered handler type names for a plugin category (`'drivers'`, `'triggers'`, `'completions'`, `'middlewares'`).
|
|
389
|
+
|
|
390
|
+
### `resolveConfig(raw: RawPipelineConfig, workDir: string): PipelineConfig`
|
|
391
|
+
|
|
392
|
+
Resolves a raw pipeline config into a fully resolved `PipelineConfig` — applies inheritance (pipeline → track → task) for driver, model, permissions, and cwd. Validates and resolves all file paths against `workDir`.
|
|
393
|
+
|
|
394
|
+
Use `loadPipeline` for the common parse-and-resolve flow. Use `resolveConfig` directly when you need to manipulate the raw config between parsing and resolution.
|
|
395
|
+
|
|
396
|
+
### `attachStdinApprovalAdapter(gateway): StdinApprovalAdapter`
|
|
397
|
+
|
|
398
|
+
Attaches an interactive stdin-based approval handler.
|
|
399
|
+
|
|
400
|
+
### `attachWebSocketApprovalAdapter(gateway, options?): WebSocketApprovalAdapter`
|
|
401
|
+
|
|
402
|
+
Starts a WebSocket server for remote approval decisions.
|
|
403
|
+
|
|
404
|
+
### Config CRUD (`config-ops`)
|
|
405
|
+
|
|
406
|
+
Pure, immutable helper functions for building and editing `RawPipelineConfig` in a visual editor. No runtime dependencies — safe to use in renderer processes.
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import {
|
|
410
|
+
createEmptyPipeline,
|
|
411
|
+
setPipelineField,
|
|
412
|
+
upsertTrack,
|
|
413
|
+
removeTrack,
|
|
414
|
+
moveTrack,
|
|
415
|
+
updateTrack,
|
|
416
|
+
upsertTask,
|
|
417
|
+
removeTask,
|
|
418
|
+
moveTask,
|
|
419
|
+
transferTask,
|
|
420
|
+
serializePipeline,
|
|
421
|
+
} from '@tagma/sdk';
|
|
422
|
+
|
|
423
|
+
// Build a config programmatically
|
|
424
|
+
let config = createEmptyPipeline('my-pipeline');
|
|
425
|
+
config = upsertTrack(config, { id: 'backend', name: 'Backend', tasks: [] });
|
|
426
|
+
config = upsertTask(config, 'backend', { id: 'implement', prompt: 'Add /health endpoint' });
|
|
427
|
+
|
|
428
|
+
// Sync back to YAML
|
|
429
|
+
const yaml = serializePipeline(config);
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
| Function | Description |
|
|
433
|
+
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
434
|
+
| `createEmptyPipeline(name)` | Create a minimal pipeline config |
|
|
435
|
+
| `setPipelineField(config, fields)` | Update top-level pipeline fields |
|
|
436
|
+
| `upsertTrack(config, track)` | Insert or replace a track by id |
|
|
437
|
+
| `removeTrack(config, trackId)` | Remove a track |
|
|
438
|
+
| `moveTrack(config, trackId, toIndex)` | Reorder a track |
|
|
439
|
+
| `updateTrack(config, trackId, fields)` | Patch track fields (not tasks) |
|
|
440
|
+
| `upsertTask(config, trackId, task)` | Insert or replace a task |
|
|
441
|
+
| `removeTask(config, trackId, taskId, cleanRefs?)` | Remove a task; pass `cleanRefs: true` to also strip dangling `depends_on` / `continue_from` references. Only refs that resolve to the deleted task are removed — same-named tasks in other tracks are unaffected |
|
|
442
|
+
| `moveTask(config, trackId, taskId, toIndex)` | Reorder a task within its track |
|
|
443
|
+
| `transferTask(config, fromTrackId, taskId, toTrackId, qualifyRefs?)` | Move a task across tracks. When `qualifyRefs` is `true` (default), bare `depends_on` / `continue_from` references to the moved task are converted to fully-qualified form (`toTrackId.taskId`) so same-track resolution stays correct |
|
|
444
|
+
|
|
445
|
+
### `parseYaml(content: string): RawPipelineConfig`
|
|
446
|
+
|
|
447
|
+
Parses a YAML string and returns the raw (unresolved) pipeline config. Use this when you need to edit and re-save YAML without losing relative paths or user-authored formatting — pass the result to `serializePipeline()` rather than going through `loadPipeline()`.
|
|
448
|
+
|
|
449
|
+
### `deresolvePipeline(config: PipelineConfig, workDir: string): RawPipelineConfig`
|
|
450
|
+
|
|
451
|
+
Converts a resolved `PipelineConfig` back to a `RawPipelineConfig` suitable for serialization. Strips injected defaults and converts absolute `cwd` paths back to relative so the output YAML is portable across machines.
|
|
452
|
+
|
|
453
|
+
Use this when you have a programmatically modified resolved config and need to save it back to YAML:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
// Correct: load → modify resolved config → deresolve → save
|
|
457
|
+
const config = await loadPipeline(yaml, workDir);
|
|
458
|
+
const modified = { ...config, name: 'renamed' };
|
|
459
|
+
const savedYaml = serializePipeline(deresolvePipeline(modified, workDir));
|
|
460
|
+
|
|
461
|
+
// Also correct: work entirely in raw space (preferred for visual editors)
|
|
462
|
+
const raw = parseYaml(yaml);
|
|
463
|
+
const updatedRaw = setPipelineField(raw, { name: 'renamed' });
|
|
464
|
+
const savedYaml = serializePipeline(updatedRaw);
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### `validateConfig(config: PipelineConfig): string[]`
|
|
468
|
+
|
|
469
|
+
Validates a resolved pipeline config without executing it. Checks DAG structure (cycles, missing dependencies). Returns an array of error message strings — empty means valid.
|
|
470
|
+
|
|
471
|
+
Use `validateRaw` for editing raw configs in a UI; use `validateConfig` after `resolveConfig` for a final pre-run check.
|
|
472
|
+
|
|
473
|
+
### `validateRaw(config: RawPipelineConfig): ValidationError[]`
|
|
474
|
+
|
|
475
|
+
Validates a raw pipeline config without resolving inheritance or executing anything. Returns a flat list of `{ path, message }` objects — empty array means valid.
|
|
476
|
+
|
|
477
|
+
Checks: required fields, `prompt`/`command` exclusivity, duplicate task IDs within a track, `depends_on`/`continue_from` reference integrity (including ambiguous bare refs that exist in multiple tracks — use `trackId.taskId` to disambiguate), circular dependency detection.
|
|
478
|
+
|
|
479
|
+
Does **not** check plugin registration (plugins may not be loaded at edit time).
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
const errors = validateRaw(draftConfig);
|
|
483
|
+
if (errors.length > 0) {
|
|
484
|
+
errors.forEach((e) => highlightNode(e.path, e.message));
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### `buildRawDag(config: RawPipelineConfig): RawDag`
|
|
489
|
+
|
|
490
|
+
Extracts the topology of a raw (unresolved) pipeline config as a graph — no `workDir` or plugin registration required. Intended for the visual editor to render the flow graph during editing.
|
|
491
|
+
|
|
492
|
+
Returns `{ nodes: ReadonlyMap<taskId, RawDagNode>, edges: { from, to }[] }` where each edge represents a dependency (from must complete before to). Unresolvable refs are silently skipped.
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
const { nodes, edges } = buildRawDag(draftConfig);
|
|
496
|
+
// nodes — keyed by "trackId.taskId"
|
|
497
|
+
// edges — [{ from: "track.taskA", to: "track.taskB" }, ...]
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Use `buildDag` instead when you have a fully resolved `PipelineConfig` and need topological sort order.
|
|
501
|
+
|
|
502
|
+
### `Logger`
|
|
503
|
+
|
|
504
|
+
Dual-channel logger — console + file. Creates a per-run log file at `<workDir>/.tagma/logs/<runId>/pipeline.log`.
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
const logger = new Logger(workDir, runId);
|
|
508
|
+
logger.info('[track]', 'message'); // console + file
|
|
509
|
+
logger.warn('[track]', 'message'); // console + file
|
|
510
|
+
logger.error('[track]', 'message'); // console + file
|
|
511
|
+
logger.debug('[track]', 'message'); // file only
|
|
512
|
+
logger.section('Title'); // file only — visual separator
|
|
513
|
+
logger.quiet(bulkText); // file only — bulk payload
|
|
514
|
+
logger.path; // log file path
|
|
515
|
+
logger.dir; // run artifact directory
|
|
516
|
+
logger.close(); // close the persistent file handle (called automatically by runPipeline at run completion)
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Pass an optional third argument to stream every appended line out as a
|
|
520
|
+
structured `LogRecord` — `runPipeline` uses this to emit `task_log` events:
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
import { Logger, type LogRecord } from '@tagma/sdk';
|
|
524
|
+
|
|
525
|
+
const logger = new Logger(workDir, runId, (record: LogRecord) => {
|
|
526
|
+
// record = { level, taskId, timestamp, text }
|
|
527
|
+
// level = 'info' | 'warn' | 'error' | 'debug' | 'section' | 'quiet'
|
|
528
|
+
// taskId is extracted from a '[task:<id>]' prefix, or null for untagged lines
|
|
529
|
+
forwardToUI(record);
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
`section` and `quiet` carry no prefix, so pass an explicit `taskId` when the
|
|
534
|
+
line logically belongs to a task — the extractor cannot infer one otherwise:
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
logger.section(`Task ${taskId}`, taskId);
|
|
538
|
+
logger.quiet(`--- stdout (${taskId}) ---\n${body}\n--- end stdout ---`, taskId);
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### `tailLines(text: string, n: number): string`
|
|
542
|
+
|
|
543
|
+
Returns the last `n` non-empty lines of `text`, joined with newlines.
|
|
544
|
+
|
|
545
|
+
### `clip(text: string, maxBytes?: number): string`
|
|
546
|
+
|
|
547
|
+
Truncates `text` to at most `maxBytes` UTF-8 bytes (default 16 KB), appending a `…[truncated N bytes]` marker when truncation occurs. Multi-byte characters (CJK, emoji) are counted correctly.
|
|
548
|
+
|
|
549
|
+
### Utilities
|
|
550
|
+
|
|
551
|
+
| Function | Description |
|
|
552
|
+
| ------------------------------------- | --------------------------------------------------------- |
|
|
553
|
+
| `parseDuration(input)` | Parses `"30s"`, `"5m"`, `"2h"` → milliseconds |
|
|
554
|
+
| `validatePath(filePath, projectRoot)` | Resolves path, throws if it escapes project root |
|
|
555
|
+
| `generateRunId()` | Generates a unique run ID (`run_<ts>_<seq>_<rand>`) |
|
|
556
|
+
| `nowISO()` | Returns `new Date().toISOString()` |
|
|
557
|
+
| `truncateForName(text, maxLen?)` | Truncates first line to `maxLen` (default 40) for display |
|
|
558
|
+
|
|
559
|
+
## Related Packages
|
|
560
|
+
|
|
561
|
+
| Package | Description |
|
|
562
|
+
| ------------------------------------------------------------------------------ | -------------------------- |
|
|
563
|
+
| [@tagma/types](https://www.npmjs.com/package/@tagma/types) | Shared TypeScript types |
|
|
564
|
+
| [@tagma/driver-codex](https://www.npmjs.com/package/@tagma/driver-codex) | Codex CLI driver plugin |
|
|
565
|
+
| [@tagma/driver-opencode](https://www.npmjs.com/package/@tagma/driver-opencode) | OpenCode CLI driver plugin |
|
|
566
|
+
|
|
567
|
+
## License
|
|
568
|
+
|
|
569
|
+
MIT
|