@tagma/sdk 0.5.2 → 0.6.1
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 +573 -573
- package/dist/drivers/opencode.d.ts.map +1 -1
- package/dist/drivers/opencode.js +47 -17
- package/dist/drivers/opencode.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 +92 -92
- package/src/config-ops.ts +307 -307
- package/src/drivers/opencode.ts +68 -29
- package/src/engine.ts +1220 -1220
- package/src/hooks.ts +193 -193
- package/src/logger.ts +182 -182
- package/src/middlewares/static-context.ts +49 -49
- package/src/pipeline-runner.ts +173 -173
- package/src/registry.ts +267 -267
- package/src/runner.ts +460 -460
- package/src/schema.test.ts +101 -101
- package/src/schema.ts +338 -338
- package/src/sdk.ts +118 -118
- 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 -412
- package/dist/drivers/claude-code.d.ts +0 -3
- package/dist/drivers/claude-code.d.ts.map +0 -1
- package/dist/drivers/claude-code.js +0 -225
- package/dist/drivers/claude-code.js.map +0 -1
- package/dist/templates.d.ts +0 -20
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js +0 -93
- package/dist/templates.js.map +0 -1
package/src/pipeline-runner.ts
CHANGED
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
// ═══ PipelineRunner ═══
|
|
2
|
-
//
|
|
3
|
-
// Wraps runPipeline in a lifecycle object suited for multi-pipeline
|
|
4
|
-
// management in sidecar / Tauri IPC scenarios. Each instance controls
|
|
5
|
-
// one pipeline run.
|
|
6
|
-
//
|
|
7
|
-
// The runner forwards wire-shape `RunEventPayload` values to its
|
|
8
|
-
// subscribers — identical to what the editor server broadcasts over SSE —
|
|
9
|
-
// so sidecar hosts don't need to know anything about the engine's
|
|
10
|
-
// internal TaskState.
|
|
11
|
-
//
|
|
12
|
-
// Typical sidecar usage:
|
|
13
|
-
//
|
|
14
|
-
// const runners = new Map<string, PipelineRunner>();
|
|
15
|
-
//
|
|
16
|
-
// const runner = new PipelineRunner(config, workDir);
|
|
17
|
-
// runner.subscribe(event => ipcEmit('run_event', event));
|
|
18
|
-
// runner.start();
|
|
19
|
-
// runners.set(runner.instanceId, runner);
|
|
20
|
-
//
|
|
21
|
-
// // Later, from IPC:
|
|
22
|
-
// runners.get(id)?.abort();
|
|
23
|
-
|
|
24
|
-
import { runPipeline } from './engine';
|
|
25
|
-
import type { EngineResult, RunPipelineOptions } from './engine';
|
|
26
|
-
import type { PipelineConfig, RunEventPayload, RunTaskState } from './types';
|
|
27
|
-
import { generateRunId } from './utils';
|
|
28
|
-
|
|
29
|
-
export type { EngineResult };
|
|
30
|
-
|
|
31
|
-
export type PipelineRunnerStatus = 'idle' | 'running' | 'done' | 'aborted';
|
|
32
|
-
|
|
33
|
-
export class PipelineRunner {
|
|
34
|
-
/**
|
|
35
|
-
* Stable ID assigned before start() — safe to use as a Map key before
|
|
36
|
-
* the engine-assigned runId becomes available.
|
|
37
|
-
*/
|
|
38
|
-
readonly instanceId: string;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The runId generated by the engine. Set when the first `run_start`
|
|
42
|
-
* event arrives on the forwarded event stream. null until then.
|
|
43
|
-
*/
|
|
44
|
-
private _runId: string | null = null;
|
|
45
|
-
private _status: PipelineRunnerStatus = 'idle';
|
|
46
|
-
private _result: Promise<EngineResult> | null = null;
|
|
47
|
-
private _abortController = new AbortController();
|
|
48
|
-
private _handlers = new Set<(event: RunEventPayload) => void>();
|
|
49
|
-
/**
|
|
50
|
-
* Wire-shape task mirror, kept in sync with `run_start` / `task_update`
|
|
51
|
-
* events. Exposed through `getTasks()`. Hosts see the same wire
|
|
52
|
-
* projection the editor client sees, so there is exactly one task-state
|
|
53
|
-
* vocabulary across IPC boundaries.
|
|
54
|
-
*/
|
|
55
|
-
private _tasks = new Map<string, RunTaskState>();
|
|
56
|
-
|
|
57
|
-
constructor(
|
|
58
|
-
private readonly config: PipelineConfig,
|
|
59
|
-
private readonly workDir: string,
|
|
60
|
-
private readonly opts: Omit<RunPipelineOptions, 'signal' | 'onEvent'> = {},
|
|
61
|
-
) {
|
|
62
|
-
this.instanceId = generateRunId();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
get runId(): string | null {
|
|
66
|
-
return this._runId;
|
|
67
|
-
}
|
|
68
|
-
get status(): PipelineRunnerStatus {
|
|
69
|
-
return this._status;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Start the pipeline. Calling start() more than once returns the same Promise.
|
|
74
|
-
*/
|
|
75
|
-
start(): Promise<EngineResult> {
|
|
76
|
-
if (this._result) return this._result;
|
|
77
|
-
|
|
78
|
-
// Guard: if abort() was called before start(), the signal is already
|
|
79
|
-
// aborted. Create a fresh controller so the pipeline doesn't terminate
|
|
80
|
-
// immediately. If users truly want pre-abort semantics, they call
|
|
81
|
-
// abort() after start().
|
|
82
|
-
if (this._abortController.signal.aborted) {
|
|
83
|
-
this._abortController = new AbortController();
|
|
84
|
-
this._status = 'idle';
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
this._status = 'running';
|
|
88
|
-
this._result = runPipeline(this.config, this.workDir, {
|
|
89
|
-
...this.opts,
|
|
90
|
-
signal: this._abortController.signal,
|
|
91
|
-
onEvent: (event) => {
|
|
92
|
-
this._applyEvent(event);
|
|
93
|
-
for (const h of this._handlers) h(event);
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
.then((result) => {
|
|
97
|
-
if (this._status === 'running') this._status = 'done';
|
|
98
|
-
return result;
|
|
99
|
-
})
|
|
100
|
-
.catch((err) => {
|
|
101
|
-
this._status = 'aborted';
|
|
102
|
-
throw err;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
return this._result;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private _applyEvent(event: RunEventPayload): void {
|
|
109
|
-
switch (event.type) {
|
|
110
|
-
case 'run_start':
|
|
111
|
-
this._runId = event.runId;
|
|
112
|
-
this._tasks.clear();
|
|
113
|
-
for (const t of event.tasks) this._tasks.set(t.taskId, { ...t });
|
|
114
|
-
return;
|
|
115
|
-
case 'task_update': {
|
|
116
|
-
const prev = this._tasks.get(event.taskId);
|
|
117
|
-
if (!prev) return;
|
|
118
|
-
const pick = <T>(incoming: T | undefined, previous: T): T =>
|
|
119
|
-
incoming !== undefined ? incoming : previous;
|
|
120
|
-
this._tasks.set(event.taskId, {
|
|
121
|
-
...prev,
|
|
122
|
-
status: event.status,
|
|
123
|
-
startedAt: pick(event.startedAt, prev.startedAt),
|
|
124
|
-
finishedAt: pick(event.finishedAt, prev.finishedAt),
|
|
125
|
-
durationMs: pick(event.durationMs, prev.durationMs),
|
|
126
|
-
exitCode: pick(event.exitCode, prev.exitCode),
|
|
127
|
-
stdout: pick(event.stdout, prev.stdout),
|
|
128
|
-
stderr: pick(event.stderr, prev.stderr),
|
|
129
|
-
stderrPath: pick(event.stderrPath, prev.stderrPath),
|
|
130
|
-
sessionId: pick(event.sessionId, prev.sessionId),
|
|
131
|
-
normalizedOutput: pick(event.normalizedOutput, prev.normalizedOutput),
|
|
132
|
-
resolvedDriver: pick(event.resolvedDriver, prev.resolvedDriver),
|
|
133
|
-
resolvedModel: pick(event.resolvedModel, prev.resolvedModel),
|
|
134
|
-
resolvedPermissions: pick(event.resolvedPermissions, prev.resolvedPermissions),
|
|
135
|
-
});
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
case 'run_end':
|
|
139
|
-
this._status = this._abortController.signal.aborted ? 'aborted' : 'done';
|
|
140
|
-
return;
|
|
141
|
-
default:
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Cancel the running pipeline. Safe to call multiple times or before start().
|
|
148
|
-
*/
|
|
149
|
-
abort(reason?: string): void {
|
|
150
|
-
this._status = 'aborted';
|
|
151
|
-
this._abortController.abort(reason);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Live snapshot of wire-shape task states. Populated from the first
|
|
156
|
-
* `run_start` event onward. Returns an empty map before the run starts.
|
|
157
|
-
*/
|
|
158
|
-
getTasks(): ReadonlyMap<string, RunTaskState> {
|
|
159
|
-
const copy = new Map<string, RunTaskState>();
|
|
160
|
-
for (const [id, t] of this._tasks) copy.set(id, { ...t });
|
|
161
|
-
return copy;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Subscribe to run events. Returns an unsubscribe function. Events are
|
|
166
|
-
* emitted synchronously in the engine's event loop, so keep handlers
|
|
167
|
-
* non-blocking (e.g. queue to IPC, do not await inside).
|
|
168
|
-
*/
|
|
169
|
-
subscribe(handler: (event: RunEventPayload) => void): () => void {
|
|
170
|
-
this._handlers.add(handler);
|
|
171
|
-
return () => this._handlers.delete(handler);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
1
|
+
// ═══ PipelineRunner ═══
|
|
2
|
+
//
|
|
3
|
+
// Wraps runPipeline in a lifecycle object suited for multi-pipeline
|
|
4
|
+
// management in sidecar / Tauri IPC scenarios. Each instance controls
|
|
5
|
+
// one pipeline run.
|
|
6
|
+
//
|
|
7
|
+
// The runner forwards wire-shape `RunEventPayload` values to its
|
|
8
|
+
// subscribers — identical to what the editor server broadcasts over SSE —
|
|
9
|
+
// so sidecar hosts don't need to know anything about the engine's
|
|
10
|
+
// internal TaskState.
|
|
11
|
+
//
|
|
12
|
+
// Typical sidecar usage:
|
|
13
|
+
//
|
|
14
|
+
// const runners = new Map<string, PipelineRunner>();
|
|
15
|
+
//
|
|
16
|
+
// const runner = new PipelineRunner(config, workDir);
|
|
17
|
+
// runner.subscribe(event => ipcEmit('run_event', event));
|
|
18
|
+
// runner.start();
|
|
19
|
+
// runners.set(runner.instanceId, runner);
|
|
20
|
+
//
|
|
21
|
+
// // Later, from IPC:
|
|
22
|
+
// runners.get(id)?.abort();
|
|
23
|
+
|
|
24
|
+
import { runPipeline } from './engine';
|
|
25
|
+
import type { EngineResult, RunPipelineOptions } from './engine';
|
|
26
|
+
import type { PipelineConfig, RunEventPayload, RunTaskState } from './types';
|
|
27
|
+
import { generateRunId } from './utils';
|
|
28
|
+
|
|
29
|
+
export type { EngineResult };
|
|
30
|
+
|
|
31
|
+
export type PipelineRunnerStatus = 'idle' | 'running' | 'done' | 'aborted';
|
|
32
|
+
|
|
33
|
+
export class PipelineRunner {
|
|
34
|
+
/**
|
|
35
|
+
* Stable ID assigned before start() — safe to use as a Map key before
|
|
36
|
+
* the engine-assigned runId becomes available.
|
|
37
|
+
*/
|
|
38
|
+
readonly instanceId: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The runId generated by the engine. Set when the first `run_start`
|
|
42
|
+
* event arrives on the forwarded event stream. null until then.
|
|
43
|
+
*/
|
|
44
|
+
private _runId: string | null = null;
|
|
45
|
+
private _status: PipelineRunnerStatus = 'idle';
|
|
46
|
+
private _result: Promise<EngineResult> | null = null;
|
|
47
|
+
private _abortController = new AbortController();
|
|
48
|
+
private _handlers = new Set<(event: RunEventPayload) => void>();
|
|
49
|
+
/**
|
|
50
|
+
* Wire-shape task mirror, kept in sync with `run_start` / `task_update`
|
|
51
|
+
* events. Exposed through `getTasks()`. Hosts see the same wire
|
|
52
|
+
* projection the editor client sees, so there is exactly one task-state
|
|
53
|
+
* vocabulary across IPC boundaries.
|
|
54
|
+
*/
|
|
55
|
+
private _tasks = new Map<string, RunTaskState>();
|
|
56
|
+
|
|
57
|
+
constructor(
|
|
58
|
+
private readonly config: PipelineConfig,
|
|
59
|
+
private readonly workDir: string,
|
|
60
|
+
private readonly opts: Omit<RunPipelineOptions, 'signal' | 'onEvent'> = {},
|
|
61
|
+
) {
|
|
62
|
+
this.instanceId = generateRunId();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get runId(): string | null {
|
|
66
|
+
return this._runId;
|
|
67
|
+
}
|
|
68
|
+
get status(): PipelineRunnerStatus {
|
|
69
|
+
return this._status;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Start the pipeline. Calling start() more than once returns the same Promise.
|
|
74
|
+
*/
|
|
75
|
+
start(): Promise<EngineResult> {
|
|
76
|
+
if (this._result) return this._result;
|
|
77
|
+
|
|
78
|
+
// Guard: if abort() was called before start(), the signal is already
|
|
79
|
+
// aborted. Create a fresh controller so the pipeline doesn't terminate
|
|
80
|
+
// immediately. If users truly want pre-abort semantics, they call
|
|
81
|
+
// abort() after start().
|
|
82
|
+
if (this._abortController.signal.aborted) {
|
|
83
|
+
this._abortController = new AbortController();
|
|
84
|
+
this._status = 'idle';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
this._status = 'running';
|
|
88
|
+
this._result = runPipeline(this.config, this.workDir, {
|
|
89
|
+
...this.opts,
|
|
90
|
+
signal: this._abortController.signal,
|
|
91
|
+
onEvent: (event) => {
|
|
92
|
+
this._applyEvent(event);
|
|
93
|
+
for (const h of this._handlers) h(event);
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
.then((result) => {
|
|
97
|
+
if (this._status === 'running') this._status = 'done';
|
|
98
|
+
return result;
|
|
99
|
+
})
|
|
100
|
+
.catch((err) => {
|
|
101
|
+
this._status = 'aborted';
|
|
102
|
+
throw err;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return this._result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private _applyEvent(event: RunEventPayload): void {
|
|
109
|
+
switch (event.type) {
|
|
110
|
+
case 'run_start':
|
|
111
|
+
this._runId = event.runId;
|
|
112
|
+
this._tasks.clear();
|
|
113
|
+
for (const t of event.tasks) this._tasks.set(t.taskId, { ...t });
|
|
114
|
+
return;
|
|
115
|
+
case 'task_update': {
|
|
116
|
+
const prev = this._tasks.get(event.taskId);
|
|
117
|
+
if (!prev) return;
|
|
118
|
+
const pick = <T>(incoming: T | undefined, previous: T): T =>
|
|
119
|
+
incoming !== undefined ? incoming : previous;
|
|
120
|
+
this._tasks.set(event.taskId, {
|
|
121
|
+
...prev,
|
|
122
|
+
status: event.status,
|
|
123
|
+
startedAt: pick(event.startedAt, prev.startedAt),
|
|
124
|
+
finishedAt: pick(event.finishedAt, prev.finishedAt),
|
|
125
|
+
durationMs: pick(event.durationMs, prev.durationMs),
|
|
126
|
+
exitCode: pick(event.exitCode, prev.exitCode),
|
|
127
|
+
stdout: pick(event.stdout, prev.stdout),
|
|
128
|
+
stderr: pick(event.stderr, prev.stderr),
|
|
129
|
+
stderrPath: pick(event.stderrPath, prev.stderrPath),
|
|
130
|
+
sessionId: pick(event.sessionId, prev.sessionId),
|
|
131
|
+
normalizedOutput: pick(event.normalizedOutput, prev.normalizedOutput),
|
|
132
|
+
resolvedDriver: pick(event.resolvedDriver, prev.resolvedDriver),
|
|
133
|
+
resolvedModel: pick(event.resolvedModel, prev.resolvedModel),
|
|
134
|
+
resolvedPermissions: pick(event.resolvedPermissions, prev.resolvedPermissions),
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
case 'run_end':
|
|
139
|
+
this._status = this._abortController.signal.aborted ? 'aborted' : 'done';
|
|
140
|
+
return;
|
|
141
|
+
default:
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Cancel the running pipeline. Safe to call multiple times or before start().
|
|
148
|
+
*/
|
|
149
|
+
abort(reason?: string): void {
|
|
150
|
+
this._status = 'aborted';
|
|
151
|
+
this._abortController.abort(reason);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Live snapshot of wire-shape task states. Populated from the first
|
|
156
|
+
* `run_start` event onward. Returns an empty map before the run starts.
|
|
157
|
+
*/
|
|
158
|
+
getTasks(): ReadonlyMap<string, RunTaskState> {
|
|
159
|
+
const copy = new Map<string, RunTaskState>();
|
|
160
|
+
for (const [id, t] of this._tasks) copy.set(id, { ...t });
|
|
161
|
+
return copy;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Subscribe to run events. Returns an unsubscribe function. Events are
|
|
166
|
+
* emitted synchronously in the engine's event loop, so keep handlers
|
|
167
|
+
* non-blocking (e.g. queue to IPC, do not await inside).
|
|
168
|
+
*/
|
|
169
|
+
subscribe(handler: (event: RunEventPayload) => void): () => void {
|
|
170
|
+
this._handlers.add(handler);
|
|
171
|
+
return () => this._handlers.delete(handler);
|
|
172
|
+
}
|
|
173
|
+
}
|