@tagma/sdk 0.5.0 → 0.5.2

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/README.md CHANGED
@@ -15,7 +15,7 @@
15
15
  > the npm registry website and cannot be removed — please ignore it and use
16
16
  > the command above.)_
17
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.
18
+ A local AI task orchestration SDK for [Bun](https://bun.sh). Define multi-track pipelines in YAML, run AI coding agents (OpenCode, Codex, Claude Code) and shell commands in parallel with dependency resolution, approval gates, and lifecycle hooks.
19
19
 
20
20
  ## Install
21
21
 
@@ -35,7 +35,7 @@ pipeline:
35
35
  tracks:
36
36
  - id: backend
37
37
  name: Backend
38
- driver: claude-code
38
+ driver: opencode
39
39
  permissions: { read: true, write: true, execute: false }
40
40
  tasks:
41
41
  - id: implement
@@ -65,7 +65,7 @@ console.log(result.success ? 'Done' : 'Failed');
65
65
  ## Features
66
66
 
67
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
68
+ - **Driver plugins** -- built-in `opencode` driver; install `@tagma/driver-codex` or `@tagma/driver-claude-code` for other agents
69
69
  - **Session handoff** -- `continue_from` passes context between tasks (session resume or text injection)
70
70
  - **Approval gates** -- trigger-based approval with stdin and WebSocket adapters
71
71
  - **Lifecycle hooks** -- `pipeline_start`, `task_start`, `task_success`, `task_failure`, `pipeline_complete`, `pipeline_error`
@@ -80,10 +80,11 @@ console.log(result.success ? 'Done' : 'Failed');
80
80
  ```yaml
81
81
  pipeline:
82
82
  name: my-pipeline
83
- driver: claude-code
83
+ driver: opencode
84
84
  timeout: 30m
85
85
  plugins:
86
86
  - '@tagma/driver-codex'
87
+ - '@tagma/driver-claude-code'
87
88
  hooks:
88
89
  pipeline_start: 'echo starting'
89
90
  task_start: 'echo task begin'
@@ -95,8 +96,8 @@ pipeline:
95
96
  - id: track-1
96
97
  name: Track One
97
98
  color: '#3b82f6'
98
- driver: claude-code
99
- model: claude-sonnet-4-6
99
+ driver: opencode
100
+ model: opencode/big-pickle
100
101
  agent_profile: senior
101
102
  cwd: ./services/backend
102
103
  permissions:
@@ -113,8 +114,8 @@ pipeline:
113
114
  name: Do something
114
115
  prompt: 'Your prompt here'
115
116
  timeout: 10m
116
- driver: claude-code
117
- model: claude-sonnet-4-6
117
+ driver: opencode
118
+ model: opencode/big-pickle
118
119
  agent_profile: senior
119
120
  cwd: ./src
120
121
  permissions:
@@ -143,7 +144,7 @@ pipeline:
143
144
  | Field | Type | Required | Description |
144
145
  | --------- | --------------- | -------- | ------------------------------------------------------------------------------------------ |
145
146
  | `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
+ | `driver` | `string` | No | Default driver for all tracks/tasks (inherited). Built-in: `opencode` |
147
148
  | `model` | `string` | No | Default model for all tracks/tasks (inherited). Exact model name, e.g. `claude-sonnet-4-6` |
148
149
  | `timeout` | `string` | No | Pipeline-level timeout. Format: `"30s"`, `"5m"`, `"2h"` |
149
150
  | `plugins` | `string[]` | No | External plugin packages to load, e.g. `["@tagma/driver-codex"]` |
@@ -280,7 +281,7 @@ Track-level `middlewares` apply to all tasks in the track. Setting task-level `m
280
281
 
281
282
  ### `bootstrapBuiltins()`
282
283
 
283
- Registers all built-in plugins (claude-code driver, file/manual triggers, completion checks, static-context middleware).
284
+ Registers all built-in plugins (opencode driver, file/manual triggers, completion checks, static-context middleware).
284
285
 
285
286
  ### `loadPipeline(yaml: string, workDir: string): Promise<PipelineConfig>`
286
287
 
@@ -294,24 +295,27 @@ Options:
294
295
 
295
296
  - `approvalGateway` -- custom `ApprovalGateway` instance (defaults to `InMemoryApprovalGateway`)
296
297
  - `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)
298
+ - `onEvent` -- callback for real-time `RunEventPayload` updates. Every payload carries `runId`. The editor server stamps a per-run `seq` on top of this payload before broadcasting over SSE (producing a `WireRunEvent`); the SDK itself does not stamp `seq`. Event variants:
299
+ - `run_start` — pipeline approved and all tasks transitioned to `waiting`; includes `tasks: RunTaskState[]` (wire-shape snapshot of every task). Fires only when the `pipeline_start` hook allows the run — blocked pipelines emit no wire events at all.
300
+ - `task_update` — a task's status or result changed; flat fields (`status`, `startedAt?`, `finishedAt?`, `durationMs?`, `exitCode?`, `stdout?`, `stderr?`, `stderrPath?`, `sessionId?`, `normalizedOutput?`, `resolvedDriver?`, `resolvedModel?`, `resolvedPermissions?`) so clients can fold partial updates with `??` semantics. `startedAt` is populated before the `running` transition; `finishedAt` and result fields are populated before any terminal-status transition. Terminal-state locking in the engine guarantees at most one terminal event per task.
300
301
  - `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
+ - `run_end` — pipeline finished; includes `success: boolean` and `abortReason: 'timeout' | 'stop_all' | 'external' | null`. `null` means the run completed on its own steam (success may still be `false` if tasks failed).
303
+ - `run_error` — reserved for fatal engine errors surfaced over the wire.
304
+ - `approval_request` / `approval_resolved` — bridged from the approval gateway so hosts see approvals on the same channel as task updates, without separately subscribing to the gateway.
302
305
  - `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
306
  - `maxLogRuns` -- number of per-run log directories to keep under `<workDir>/.tagma/logs/` (default: 20)
307
+ - `skipPluginLoading` -- skip the engine's built-in `loadPlugins(config.plugins)` call. Set this when the host has already pre-loaded plugins from a custom resolution path (e.g. the editor loading from the user's workspace `node_modules`) so the engine doesn't re-resolve them via Node's default cwd-based import.
304
308
 
305
309
  ### `PipelineRunner`
306
310
 
307
311
  Higher-level wrapper for managing multiple concurrent pipeline runs — designed for sidecar / Tauri IPC scenarios where the frontend controls pipeline lifecycle by ID.
308
312
 
309
313
  ```ts
310
- const runner = new PipelineRunner(config, workDir);
314
+ const runner = new PipelineRunner(config, workDir, options?); // options: Omit<RunPipelineOptions, 'signal' | 'onEvent'>
311
315
 
312
- // Subscribe before start — handler is called for every PipelineEvent
316
+ // Subscribe before start — handler is called for every RunEventPayload
313
317
  const unsubscribe = runner.subscribe((event) => {
314
- tauriEmit('pipeline_event', { id: runner.instanceId, event });
318
+ tauriEmit('run_event', { id: runner.instanceId, event });
315
319
  });
316
320
 
317
321
  runner.start(); // returns Promise<EngineResult>, idempotent
@@ -319,16 +323,16 @@ runner.start(); // returns Promise<EngineResult>, idempotent
319
323
  // Cancel from IPC
320
324
  runner.abort();
321
325
 
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
326
+ // Live wire-shape task mirror, maintained from run_start + task_update events.
327
+ // Empty map before the first run_start; safe to read at any time.
328
+ const tasks = runner.getTasks(); // ReadonlyMap<taskId, RunTaskState>
325
329
  ```
326
330
 
327
331
  Properties:
328
332
 
329
333
  - `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'`
334
+ - `runId` — engine-assigned run ID, available after the first `run_start` event (`null` until then)
335
+ - `status` — `'idle' | 'running' | 'done' | 'aborted'` (see `PipelineRunnerStatus`)
332
336
 
333
337
  ### `TriggerBlockedError` / `TriggerTimeoutError`
334
338
 
@@ -561,8 +565,8 @@ Truncates `text` to at most `maxBytes` UTF-8 bytes (default 16 KB), appending a
561
565
  | Package | Description |
562
566
  | ------------------------------------------------------------------------------ | -------------------------- |
563
567
  | [@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 |
568
+ | [@tagma/driver-codex](https://www.npmjs.com/package/@tagma/driver-codex) | Codex CLI driver plugin |
569
+ | [@tagma/driver-claude-code](https://www.npmjs.com/package/@tagma/driver-claude-code) | Claude Code CLI driver plugin |
566
570
 
567
571
  ## License
568
572
 
package/dist/bootstrap.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { registerPlugin } from './registry';
2
2
  // Built-in Drivers
3
- // Only claude-code is built in. Other drivers (codex, opencode) ship as
4
- // workspace plugins under plugins/ and must be declared in pipeline.yaml
3
+ // Only opencode is built in. Other drivers (codex, claude-code) ship as
4
+ // workspace plugins under packages/ and must be declared in pipeline.yaml
5
5
  // via the `plugins` field, e.g.:
6
- // plugins: ["@tagma/driver-codex", "@tagma/driver-opencode"]
7
- import { ClaudeCodeDriver } from './drivers/claude-code';
6
+ // plugins: ["@tagma/driver-codex", "@tagma/driver-claude-code"]
7
+ import { OpenCodeDriver } from './drivers/opencode';
8
8
  // Built-in Triggers
9
9
  import { FileTrigger } from './triggers/file';
10
10
  import { ManualTrigger } from './triggers/manual';
@@ -16,7 +16,7 @@ import { OutputCheckCompletion } from './completions/output-check';
16
16
  import { StaticContextMiddleware } from './middlewares/static-context';
17
17
  export function bootstrapBuiltins() {
18
18
  // Drivers
19
- registerPlugin('drivers', 'claude-code', ClaudeCodeDriver);
19
+ registerPlugin('drivers', 'opencode', OpenCodeDriver);
20
20
  // Triggers
21
21
  registerPlugin('triggers', 'file', FileTrigger);
22
22
  registerPlugin('triggers', 'manual', ManualTrigger);
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,mBAAmB;AACnB,wEAAwE;AACxE,yEAAyE;AACzE,iCAAiC;AACjC,+DAA+D;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,oBAAoB;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,uBAAuB;AACvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,sBAAsB;AACtB,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,MAAM,UAAU,iBAAiB;IAC/B,UAAU;IACV,cAAc,CAAC,SAAS,EAAE,aAAa,EAAE,gBAAgB,CAAC,CAAC;IAE3D,WAAW;IACX,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAChD,cAAc,CAAC,UAAU,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAEpD,cAAc;IACd,cAAc,CAAC,aAAa,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAC;IAC/D,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,oBAAoB,CAAC,CAAC;IACnE,cAAc,CAAC,aAAa,EAAE,cAAc,EAAE,qBAAqB,CAAC,CAAC;IAErE,cAAc;IACd,cAAc,CAAC,aAAa,EAAE,gBAAgB,EAAE,uBAAuB,CAAC,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,mBAAmB;AACnB,wEAAwE;AACxE,0EAA0E;AAC1E,iCAAiC;AACjC,kEAAkE;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,oBAAoB;AACpB,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,uBAAuB;AACvB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,sBAAsB;AACtB,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,MAAM,UAAU,iBAAiB;IAC/B,UAAU;IACV,cAAc,CAAC,SAAS,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;IAEtD,WAAW;IACX,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAChD,cAAc,CAAC,UAAU,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;IAEpD,cAAc;IACd,cAAc,CAAC,aAAa,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAC;IAC/D,cAAc,CAAC,aAAa,EAAE,aAAa,EAAE,oBAAoB,CAAC,CAAC;IACnE,cAAc,CAAC,aAAa,EAAE,cAAc,EAAE,qBAAqB,CAAC,CAAC;IAErE,cAAc;IACd,cAAc,CAAC,aAAa,EAAE,gBAAgB,EAAE,uBAAuB,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { DriverPlugin } from '../types';
2
+ export declare const OpenCodeDriver: DriverPlugin;
3
+ //# sourceMappingURL=opencode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.d.ts","sourceRoot":"","sources":["../../src/drivers/opencode.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAOb,MAAM,UAAU,CAAC;AAyLlB,eAAO,MAAM,cAAc,EAAE,YAiL5B,CAAC"}
@@ -0,0 +1,316 @@
1
+ const DEFAULT_MODEL = 'opencode/big-pickle';
2
+ // NOTE on Windows multi-line prompts: `opencode` resolves to `opencode.cmd`,
3
+ // an npm-generated batch wrapper. cmd.exe silently truncates argv elements
4
+ // at the first newline, so a multi-line prompt reaches the model as only
5
+ // its first line. The SDK's runner auto-unwraps npm .cmd shims into direct
6
+ // `node <js-entry>` invocations so newlines survive, and this driver can
7
+ // keep using the bare `opencode` name on every platform.
8
+ // tagma uses a provider-neutral reasoning_effort vocabulary (low|medium|high)
9
+ // but opencode's `--variant` is provider-specific (e.g. high|max|minimal).
10
+ // Map the tagma values to the closest opencode variant:
11
+ // low → minimal (least thinking)
12
+ // medium → <no flag, provider default>
13
+ // high → high (most thinking)
14
+ // Unknown values pass through unchanged so users who target a specific
15
+ // opencode variant (e.g. "max") still work.
16
+ const EFFORT_TO_VARIANT = {
17
+ low: 'minimal',
18
+ medium: null,
19
+ high: 'high',
20
+ };
21
+ let opencodeReady;
22
+ let cachedDefaultModel;
23
+ async function runCapture(args) {
24
+ try {
25
+ const proc = Bun.spawn(args, { stdout: 'pipe', stderr: 'pipe' });
26
+ const [stdout, stderr, code] = await Promise.all([
27
+ new Response(proc.stdout).text(),
28
+ new Response(proc.stderr).text(),
29
+ proc.exited,
30
+ ]);
31
+ return { code, stdout, stderr };
32
+ }
33
+ catch {
34
+ return { code: -1, stdout: '', stderr: '' };
35
+ }
36
+ }
37
+ async function ensureOpencodeInstalled() {
38
+ if (opencodeReady !== undefined)
39
+ return opencodeReady;
40
+ // Probe existing install first — users who already have it get no delay.
41
+ const probe = await runCapture(['opencode', '--version']);
42
+ if (probe.code === 0) {
43
+ opencodeReady = true;
44
+ return true;
45
+ }
46
+ console.error('[driver:opencode] opencode CLI not found — installing via `bun install -g opencode-ai`... (this may take up to a minute)');
47
+ // Use inherit here so the user sees bun's own progress during the one-time
48
+ // install; runCapture would swallow it.
49
+ const install = Bun.spawn(['bun', 'install', '-g', 'opencode-ai'], {
50
+ stdout: 'inherit',
51
+ stderr: 'inherit',
52
+ });
53
+ const installCode = await install.exited;
54
+ if (installCode !== 0) {
55
+ console.error('[driver:opencode] install failed — opencode run will likely fail below.');
56
+ opencodeReady = false;
57
+ return false;
58
+ }
59
+ // Bun installs globals under `~/.bun/bin` (or `%USERPROFILE%\.bun\bin`),
60
+ // which isn't on this process's cached PATH unless the user already has
61
+ // bun set up. Ask bun for the directory and prepend it so bare `opencode`
62
+ // resolves in this process without requiring a shell reload.
63
+ const bin = await runCapture(['bun', 'pm', 'bin', '-g']);
64
+ if (bin.code === 0) {
65
+ const dir = bin.stdout.trim();
66
+ const sep = process.platform === 'win32' ? ';' : ':';
67
+ const current = process.env.PATH ?? '';
68
+ if (dir && !current.split(sep).includes(dir)) {
69
+ process.env.PATH = `${dir}${sep}${current}`;
70
+ }
71
+ }
72
+ const verify = await runCapture(['opencode', '--version']);
73
+ opencodeReady = verify.code === 0;
74
+ if (!opencodeReady) {
75
+ console.error('[driver:opencode] `opencode` still not resolvable after install — check that bun global bin is on PATH.');
76
+ }
77
+ return opencodeReady;
78
+ }
79
+ // `opencode models --verbose` emits "<provider>/<id>\n{...json...}\n" pairs.
80
+ // Walk balanced braces rather than split on newlines so we survive any
81
+ // whitespace oddities in the JSON payload.
82
+ function parseVerboseModels(stdout) {
83
+ const out = [];
84
+ let depth = 0;
85
+ let start = -1;
86
+ for (let i = 0; i < stdout.length; i++) {
87
+ const c = stdout[i];
88
+ if (c === '{') {
89
+ if (depth === 0)
90
+ start = i;
91
+ depth++;
92
+ }
93
+ else if (c === '}') {
94
+ depth--;
95
+ if (depth === 0 && start !== -1) {
96
+ try {
97
+ out.push(JSON.parse(stdout.slice(start, i + 1)));
98
+ }
99
+ catch {
100
+ /* skip malformed block */
101
+ }
102
+ start = -1;
103
+ }
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ function pickFreeModel(models) {
109
+ const fullId = (m) => `${m.providerID ?? 'opencode'}/${m.id ?? ''}`;
110
+ const eligible = models.filter((m) => {
111
+ if (!m.id || m.id === 'big-pickle')
112
+ return false;
113
+ if (m.status && m.status !== 'active')
114
+ return false;
115
+ const cost = m.cost;
116
+ if (!cost || cost.input !== 0 || cost.output !== 0)
117
+ return false;
118
+ const ctx = m.limit?.context;
119
+ if (typeof ctx !== 'number' || ctx <= 128000)
120
+ return false;
121
+ return true;
122
+ });
123
+ // Prefer models explicitly labelled "-free" by the provider — those are
124
+ // a stronger stability signal than "cost happens to be 0 right now".
125
+ const preferred = eligible.filter((m) => m.id?.endsWith('-free'));
126
+ const pool = preferred.length > 0 ? preferred : eligible;
127
+ if (pool.length === 0)
128
+ return null;
129
+ // Deterministic pick: sort by full id so upstream model-list reordering
130
+ // doesn't flip our choice between runs.
131
+ pool.sort((a, b) => fullId(a).localeCompare(fullId(b)));
132
+ return fullId(pool[0]);
133
+ }
134
+ async function resolveDefaultModel() {
135
+ if (cachedDefaultModel !== undefined)
136
+ return cachedDefaultModel;
137
+ const ready = await ensureOpencodeInstalled();
138
+ if (!ready) {
139
+ cachedDefaultModel = DEFAULT_MODEL;
140
+ return cachedDefaultModel;
141
+ }
142
+ console.error('[driver:opencode] resolving free opencode model...');
143
+ const { code, stdout } = await runCapture(['opencode', 'models', '--verbose']);
144
+ if (code !== 0) {
145
+ cachedDefaultModel = DEFAULT_MODEL;
146
+ return cachedDefaultModel;
147
+ }
148
+ const picked = pickFreeModel(parseVerboseModels(stdout));
149
+ cachedDefaultModel = picked ?? DEFAULT_MODEL;
150
+ console.error(`[driver:opencode] default model: ${cachedDefaultModel}`);
151
+ return cachedDefaultModel;
152
+ }
153
+ export const OpenCodeDriver = {
154
+ name: 'opencode',
155
+ capabilities: {
156
+ sessionResume: true, // supports --session
157
+ systemPrompt: false, // no --system-prompt flag; prepend to prompt instead
158
+ outputFormat: true, // supports --format json
159
+ },
160
+ resolveModel() {
161
+ return DEFAULT_MODEL;
162
+ },
163
+ async buildCommand(task, track, ctx) {
164
+ const explicitModel = task.model ?? track.model;
165
+ // Always make sure the opencode CLI is usable before we spawn it — even
166
+ // when the user pinned a model. If missing, ensureOpencodeInstalled
167
+ // auto-installs it via `bun install -g opencode-ai`.
168
+ if (explicitModel)
169
+ await ensureOpencodeInstalled();
170
+ // Otherwise resolveDefaultModel both ensures the CLI and picks a free
171
+ // model from `opencode models --verbose` (cached per-process).
172
+ const model = explicitModel ?? (await resolveDefaultModel());
173
+ // Resolve reasoning_effort → opencode --variant. SDK schema layer already
174
+ // resolved task → track → pipeline inheritance, so we only need to read
175
+ // task.reasoning_effort here.
176
+ const rawEffort = task.reasoning_effort ?? track.reasoning_effort;
177
+ const variant = rawEffort
178
+ ? rawEffort in EFFORT_TO_VARIANT
179
+ ? EFFORT_TO_VARIANT[rawEffort]
180
+ : rawEffort
181
+ : null;
182
+ let prompt = task.prompt;
183
+ // agent_profile has no dedicated flag; prepend to prompt
184
+ const profile = task.agent_profile ?? track.agent_profile;
185
+ if (profile) {
186
+ prompt = `[Role]\n${profile}\n\n[Task]\n${prompt}`;
187
+ }
188
+ // continue_from: prefer session resume, fall back to text injection
189
+ let sessionId = null;
190
+ if (task.continue_from) {
191
+ sessionId = ctx.sessionMap.get(task.continue_from) ?? null;
192
+ if (!sessionId) {
193
+ // no session — degrade to text context passthrough
194
+ let prev = null;
195
+ if (ctx.normalizedMap.has(task.continue_from)) {
196
+ prev = ctx.normalizedMap.get(task.continue_from);
197
+ }
198
+ if (prev !== null) {
199
+ prompt = `[Previous Output]\n${prev}\n\n[Current Task]\n${prompt}`;
200
+ }
201
+ }
202
+ }
203
+ // opencode run does not support stdin (no `-` placeholder like codex exec).
204
+ // Prompt is always a positional argument. Flags must be declared before `--`;
205
+ // the prompt follows after so that leading `--flag` content cannot be
206
+ // misread by opencode's argument parser (flag-injection mitigation).
207
+ // Shell-level injection is already prevented by Bun.spawn's direct argv array.
208
+ // Windows cmd.exe argv truncation on the `.cmd` wrapper is handled by the
209
+ // SDK runner's shim unwrapping — see note at the top of this file.
210
+ const args = [
211
+ 'opencode',
212
+ 'run',
213
+ '--model',
214
+ model,
215
+ '--format',
216
+ 'json', // JSON output for parseResult
217
+ ];
218
+ // `--variant` must precede `--` like every other flag. opencode rejects
219
+ // unknown variant names with a clear error, so we don't pre-validate.
220
+ if (variant) {
221
+ args.push('--variant', variant);
222
+ }
223
+ // session resume (must appear before --)
224
+ if (sessionId) {
225
+ args.push('--session', sessionId);
226
+ }
227
+ // `--` (POSIX end-of-options) isolates prompt from flag parsing
228
+ args.push('--', prompt);
229
+ return { args, cwd: task.cwd ?? ctx.workDir };
230
+ },
231
+ parseResult(stdout) {
232
+ // opencode --format json emits NDJSON — one JSON object per line
233
+ // (step_start / text / step_finish / …). The previous single
234
+ // `JSON.parse(stdout)` always threw on this shape and fell through to
235
+ // the catch, returning sessionId:null and losing session resume.
236
+ // Walk line-by-line, pick up the first sessionID we see, concatenate
237
+ // any text-type parts into normalizedOutput, and bail early on error
238
+ // payloads.
239
+ const lines = stdout.split(/\r?\n/);
240
+ let sessionId;
241
+ const textParts = [];
242
+ let sawAnyJson = false;
243
+ let errorReason = null;
244
+ for (const raw of lines) {
245
+ const line = raw.trim();
246
+ if (!line)
247
+ continue;
248
+ let json;
249
+ try {
250
+ json = JSON.parse(line);
251
+ }
252
+ catch {
253
+ continue; // tolerate interleaved non-JSON noise
254
+ }
255
+ sawAnyJson = true;
256
+ // M12: opencode sometimes emits {type:"error", error:{...}} with
257
+ // exit 0 for transient API failures. Force-fail so downstream
258
+ // skip_downstream / stop_all kicks in.
259
+ if (json.type === 'error') {
260
+ const err = json.error;
261
+ const msg = typeof err === 'object' && err !== null && typeof err.message === 'string'
262
+ ? err.message
263
+ : typeof err === 'string'
264
+ ? err
265
+ : null;
266
+ errorReason = msg
267
+ ? `opencode reported error: ${msg}`
268
+ : 'opencode emitted an error JSON payload';
269
+ // D21: stop at the first error. Continuing meant subsequent text
270
+ // lines got accumulated into `textParts` only to be discarded by
271
+ // the error-return below, and a later `{type:"error"}` would
272
+ // silently overwrite the original cause — operators then debugged
273
+ // a downstream symptom while the root-cause line scrolled past.
274
+ break;
275
+ }
276
+ // Session id — opencode uses `sessionID` (camelCase with capital D).
277
+ // Keep `session_id` / `sessionId` as fallbacks for forward/backward
278
+ // compatibility with other shapes.
279
+ if (!sessionId) {
280
+ const sid = json.sessionID ??
281
+ json.session_id ??
282
+ json.sessionId ??
283
+ null;
284
+ if (typeof sid === 'string' && sid.length > 0)
285
+ sessionId = sid;
286
+ }
287
+ // Extract human-readable text from text-type parts.
288
+ if (json.type === 'text') {
289
+ const part = json.part;
290
+ if (part && typeof part.text === 'string') {
291
+ textParts.push(part.text);
292
+ }
293
+ }
294
+ else if (typeof json.result === 'string') {
295
+ textParts.push(json.result);
296
+ }
297
+ else if (typeof json.content === 'string') {
298
+ textParts.push(json.content);
299
+ }
300
+ }
301
+ if (errorReason) {
302
+ return { forceFailure: true, forceFailureReason: errorReason };
303
+ }
304
+ // If nothing parsed as JSON, treat stdout as plain text.
305
+ const normalizedOutput = !sawAnyJson
306
+ ? stdout
307
+ : textParts.length > 0
308
+ ? textParts.join('\n')
309
+ : stdout;
310
+ return {
311
+ sessionId,
312
+ normalizedOutput,
313
+ };
314
+ },
315
+ };
316
+ //# sourceMappingURL=opencode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode.js","sourceRoot":"","sources":["../../src/drivers/opencode.ts"],"names":[],"mappings":"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"}
package/dist/engine.js CHANGED
@@ -31,7 +31,7 @@ function preflight(config, dag) {
31
31
  for (const [, node] of dag.nodes) {
32
32
  const task = node.task;
33
33
  const track = node.track;
34
- const driverName = task.driver ?? track.driver ?? config.driver ?? 'claude-code';
34
+ const driverName = task.driver ?? track.driver ?? config.driver ?? 'opencode';
35
35
  // Pure command tasks don't use a driver — skip driver registration check.
36
36
  const isCommandOnly = task.command && !task.prompt;
37
37
  if (!isCommandOnly && !hasHandler('drivers', driverName)) {
@@ -62,7 +62,7 @@ function preflight(config, dag) {
62
62
  // A handoff is possible via session resume (already ruled out above),
63
63
  // OR in-memory text injection through normalizedMap
64
64
  // (when the upstream driver implements parseResult and returns normalizedOutput).
65
- const upstreamDriverName = upstream.task.driver ?? upstream.track.driver ?? config.driver ?? 'claude-code';
65
+ const upstreamDriverName = upstream.task.driver ?? upstream.track.driver ?? config.driver ?? 'opencode';
66
66
  const upstreamDriver = hasHandler('drivers', upstreamDriverName)
67
67
  ? getHandler('drivers', upstreamDriverName)
68
68
  : null;
@@ -153,7 +153,7 @@ export async function runPipeline(config, workDir, options = {}) {
153
153
  // File-only: dump the resolved pipeline shape + DAG topology for post-mortem.
154
154
  log.section('Pipeline configuration');
155
155
  log.quiet(`name: ${config.name}`);
156
- log.quiet(`driver: ${config.driver ?? '(default: claude-code)'}`);
156
+ log.quiet(`driver: ${config.driver ?? '(default: opencode)'}`);
157
157
  log.quiet(`timeout: ${config.timeout ?? '(none)'}`);
158
158
  log.quiet(`tracks: ${config.tracks.length}`);
159
159
  log.quiet(`tasks (total): ${dag.nodes.size}`);
@@ -515,7 +515,7 @@ export async function runPipeline(config, workDir, options = {}) {
515
515
  setTaskStatus(taskId, 'running');
516
516
  log.info(`[task:${taskId}]`, task.command ? `running: ${task.command}` : `running (driver task)`);
517
517
  // File-only: resolved config for this task
518
- const resolvedDriver = task.driver ?? track.driver ?? config.driver ?? 'claude-code';
518
+ const resolvedDriver = task.driver ?? track.driver ?? config.driver ?? 'opencode';
519
519
  const resolvedModel = task.model ?? track.model ?? config.model ?? '(default)';
520
520
  const resolvedPerms = task.permissions ?? track.permissions ?? '(default)';
521
521
  const resolvedCwd = task.cwd ?? track.cwd ?? workDir;
@@ -537,7 +537,7 @@ export async function runPipeline(config, workDir, options = {}) {
537
537
  }
538
538
  else {
539
539
  // AI task: apply middleware chain against a structured PromptDocument.
540
- const driverName = task.driver ?? track.driver ?? config.driver ?? 'claude-code';
540
+ const driverName = task.driver ?? track.driver ?? config.driver ?? 'opencode';
541
541
  const driver = getHandler('drivers', driverName);
542
542
  const originalLen = task.prompt.length;
543
543
  let doc = promptDocumentFromString(task.prompt);