@tagma/sdk 0.7.4 → 0.7.6
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 +60 -53
- package/dist/completions/file-exists.js +1 -1
- package/dist/completions/file-exists.js.map +1 -1
- package/dist/completions/output-check.d.ts.map +1 -1
- package/dist/completions/output-check.js +17 -4
- package/dist/completions/output-check.js.map +1 -1
- package/dist/config.d.ts +4 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/dataflow.d.ts +3 -0
- package/dist/dataflow.d.ts.map +1 -0
- package/dist/dataflow.js +2 -0
- package/dist/dataflow.js.map +1 -0
- package/dist/drivers/opencode.d.ts.map +1 -1
- package/dist/drivers/opencode.js +23 -71
- package/dist/drivers/opencode.js.map +1 -1
- package/dist/middlewares/static-context.d.ts.map +1 -1
- package/dist/middlewares/static-context.js +1 -2
- package/dist/middlewares/static-context.js.map +1 -1
- package/dist/pipeline-runner.d.ts.map +1 -1
- package/dist/pipeline-runner.js +2 -2
- package/dist/pipeline-runner.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +3 -4
- package/dist/schema.js.map +1 -1
- package/dist/triggers/file.d.ts.map +1 -1
- package/dist/triggers/file.js +1 -2
- package/dist/triggers/file.js.map +1 -1
- package/dist/triggers/manual.d.ts.map +1 -1
- package/dist/triggers/manual.js +1 -2
- package/dist/triggers/manual.js.map +1 -1
- package/dist/types.d.ts +1 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -12
- package/dist/types.js.map +1 -1
- package/dist/utils-api.d.ts +1 -1
- package/dist/utils-api.d.ts.map +1 -1
- package/dist/utils-api.js +1 -1
- package/dist/utils-api.js.map +1 -1
- package/dist/validate-raw.d.ts +4 -4
- package/dist/validate-raw.d.ts.map +1 -1
- package/dist/validate-raw.js +45 -52
- package/dist/validate-raw.js.map +1 -1
- package/package.json +11 -24
- package/dist/adapters/stdin-approval.d.ts +0 -2
- package/dist/adapters/stdin-approval.d.ts.map +0 -1
- package/dist/adapters/stdin-approval.js +0 -2
- package/dist/adapters/stdin-approval.js.map +0 -1
- package/dist/adapters/websocket-approval.d.ts +0 -2
- package/dist/adapters/websocket-approval.d.ts.map +0 -1
- package/dist/adapters/websocket-approval.js +0 -2
- package/dist/adapters/websocket-approval.js.map +0 -1
- package/dist/core/dataflow.d.ts +0 -23
- package/dist/core/dataflow.d.ts.map +0 -1
- package/dist/core/dataflow.js +0 -99
- package/dist/core/dataflow.js.map +0 -1
- package/dist/core/log-prune.d.ts +0 -16
- package/dist/core/log-prune.d.ts.map +0 -1
- package/dist/core/log-prune.js +0 -34
- package/dist/core/log-prune.js.map +0 -1
- package/dist/core/preflight.d.ts +0 -13
- package/dist/core/preflight.d.ts.map +0 -1
- package/dist/core/preflight.js +0 -61
- package/dist/core/preflight.js.map +0 -1
- package/dist/core/run-context.d.ts +0 -55
- package/dist/core/run-context.d.ts.map +0 -1
- package/dist/core/run-context.js +0 -158
- package/dist/core/run-context.js.map +0 -1
- package/dist/core/run-state.d.ts +0 -25
- package/dist/core/run-state.d.ts.map +0 -1
- package/dist/core/run-state.js +0 -93
- package/dist/core/run-state.js.map +0 -1
- package/dist/core/scheduler.d.ts +0 -13
- package/dist/core/scheduler.d.ts.map +0 -1
- package/dist/core/scheduler.js +0 -35
- package/dist/core/scheduler.js.map +0 -1
- package/dist/core/task-executor.d.ts +0 -13
- package/dist/core/task-executor.d.ts.map +0 -1
- package/dist/core/task-executor.js +0 -610
- package/dist/core/task-executor.js.map +0 -1
- package/dist/core/trigger-errors.d.ts +0 -9
- package/dist/core/trigger-errors.d.ts.map +0 -1
- package/dist/core/trigger-errors.js +0 -15
- package/dist/core/trigger-errors.js.map +0 -1
- package/dist/dag.d.ts +0 -45
- package/dist/dag.d.ts.map +0 -1
- package/dist/dag.js +0 -177
- package/dist/dag.js.map +0 -1
- package/dist/hooks.d.ts +0 -73
- package/dist/hooks.d.ts.map +0 -1
- package/dist/hooks.js +0 -106
- package/dist/hooks.js.map +0 -1
- package/dist/pipeline-definition.d.ts +0 -3
- package/dist/pipeline-definition.d.ts.map +0 -1
- package/dist/pipeline-definition.js +0 -4
- package/dist/pipeline-definition.js.map +0 -1
- package/dist/ports.d.ts +0 -196
- package/dist/ports.d.ts.map +0 -1
- package/dist/ports.js +0 -688
- package/dist/ports.js.map +0 -1
- package/dist/prompt-doc.d.ts +0 -70
- package/dist/prompt-doc.d.ts.map +0 -1
- package/dist/prompt-doc.js +0 -154
- package/dist/prompt-doc.js.map +0 -1
- package/dist/registry.d.ts +0 -3
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -2
- package/dist/registry.js.map +0 -1
- package/dist/task-ref.d.ts +0 -55
- package/dist/task-ref.d.ts.map +0 -1
- package/dist/task-ref.js +0 -103
- package/dist/task-ref.js.map +0 -1
- package/dist/utils.d.ts +0 -13
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -177
- package/dist/utils.js.map +0 -1
- package/src/adapters/stdin-approval.ts +0 -1
- package/src/adapters/websocket-approval.ts +0 -1
- package/src/approval.ts +0 -9
- package/src/bootstrap.ts +0 -55
- package/src/completions/exit-code.ts +0 -34
- package/src/completions/file-exists.ts +0 -66
- package/src/completions/output-check.test.ts +0 -50
- package/src/completions/output-check.ts +0 -92
- package/src/config-ops.test.ts +0 -70
- package/src/config-ops.ts +0 -328
- package/src/config.ts +0 -26
- package/src/core/dataflow.test.ts +0 -166
- package/src/core/dataflow.ts +0 -161
- package/src/core/log-prune.test.ts +0 -58
- package/src/core/log-prune.ts +0 -43
- package/src/core/preflight.test.ts +0 -49
- package/src/core/preflight.ts +0 -89
- package/src/core/run-context.test.ts +0 -291
- package/src/core/run-context.ts +0 -211
- package/src/core/run-state.test.ts +0 -98
- package/src/core/run-state.ts +0 -122
- package/src/core/scheduler.test.ts +0 -83
- package/src/core/scheduler.ts +0 -42
- package/src/core/task-executor.ts +0 -752
- package/src/core/trigger-errors.ts +0 -15
- package/src/dag.test.ts +0 -56
- package/src/dag.ts +0 -245
- package/src/drivers/opencode.ts +0 -410
- package/src/engine-ports-mixed.test.ts +0 -182
- package/src/engine-ports.test.ts +0 -210
- package/src/engine-task-type.test.ts +0 -56
- package/src/engine.ts +0 -32
- package/src/hooks.ts +0 -193
- package/src/index.ts +0 -31
- package/src/logger.ts +0 -2
- package/src/middlewares/static-context.ts +0 -49
- package/src/package-split.test.ts +0 -15
- package/src/pipeline-definition.ts +0 -5
- package/src/pipeline-runner.test.ts +0 -144
- package/src/pipeline-runner.ts +0 -194
- package/src/plugin-registry.test.ts +0 -448
- package/src/plugins.ts +0 -21
- package/src/ports.test.ts +0 -678
- package/src/ports.ts +0 -925
- package/src/prompt-doc.test.ts +0 -174
- package/src/prompt-doc.ts +0 -169
- package/src/registry.ts +0 -7
- package/src/runner.test.ts +0 -142
- package/src/runner.ts +0 -1
- package/src/runtime/adapters/stdin-approval.ts +0 -1
- package/src/runtime/adapters/websocket-approval.ts +0 -1
- package/src/runtime/bun-process-runner.ts +0 -1
- package/src/runtime-adapters.test.ts +0 -10
- package/src/runtime.ts +0 -12
- package/src/schema-ports.test.ts +0 -172
- package/src/schema.test.ts +0 -213
- package/src/schema.ts +0 -379
- package/src/tagma.test.ts +0 -317
- package/src/tagma.ts +0 -67
- package/src/task-ref.test.ts +0 -401
- package/src/task-ref.ts +0 -121
- package/src/triggers/file.test.ts +0 -79
- package/src/triggers/file.ts +0 -131
- package/src/triggers/manual.ts +0 -86
- package/src/types.ts +0 -18
- package/src/utils-api.ts +0 -8
- package/src/utils.test.ts +0 -28
- package/src/utils.ts +0 -203
- package/src/validate-raw-plugin-types.test.ts +0 -60
- package/src/validate-raw-ports.test.ts +0 -136
- package/src/validate-raw.ts +0 -852
- package/src/yaml-compiler.test.ts +0 -108
- package/src/yaml-compiler.ts +0 -110
- package/src/yaml.ts +0 -11
package/src/engine-ports.test.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { bootstrapBuiltins } from './bootstrap';
|
|
6
|
-
import { runPipeline, type RunEventPayload } from './engine';
|
|
7
|
-
import { PluginRegistry } from './registry';
|
|
8
|
-
import type { PipelineConfig, TaskConfig, TagmaRuntime, TaskResult, TaskStatus } from './types';
|
|
9
|
-
|
|
10
|
-
const PERMS = { read: true, write: false, execute: false };
|
|
11
|
-
|
|
12
|
-
function freshRegistry(): PluginRegistry {
|
|
13
|
-
const reg = new PluginRegistry();
|
|
14
|
-
bootstrapBuiltins(reg);
|
|
15
|
-
return reg;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeDir(): string {
|
|
19
|
-
return mkdtempSync(join(tmpdir(), 'tagma-bindings-'));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function task(overrides: Partial<TaskConfig> & { id: string }): TaskConfig {
|
|
23
|
-
return {
|
|
24
|
-
name: overrides.id,
|
|
25
|
-
permissions: PERMS,
|
|
26
|
-
driver: 'opencode',
|
|
27
|
-
...overrides,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function pipeline(tasks: TaskConfig[]): PipelineConfig {
|
|
32
|
-
return {
|
|
33
|
-
name: 'bindings-test',
|
|
34
|
-
tracks: [
|
|
35
|
-
{
|
|
36
|
-
id: 't',
|
|
37
|
-
name: 'T',
|
|
38
|
-
driver: 'opencode',
|
|
39
|
-
permissions: PERMS,
|
|
40
|
-
on_failure: 'skip_downstream',
|
|
41
|
-
tasks,
|
|
42
|
-
},
|
|
43
|
-
],
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function taskResult(stdout: string): TaskResult {
|
|
48
|
-
return {
|
|
49
|
-
exitCode: 0,
|
|
50
|
-
stdout,
|
|
51
|
-
stderr: '',
|
|
52
|
-
stdoutPath: null,
|
|
53
|
-
stderrPath: null,
|
|
54
|
-
stdoutBytes: stdout.length,
|
|
55
|
-
stderrBytes: 0,
|
|
56
|
-
durationMs: 1,
|
|
57
|
-
sessionId: null,
|
|
58
|
-
normalizedOutput: null,
|
|
59
|
-
failureKind: null,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function fakeRuntime(commandStdout: Record<string, string>): TagmaRuntime {
|
|
64
|
-
return {
|
|
65
|
-
async runCommand(command) {
|
|
66
|
-
for (const [prefix, stdout] of Object.entries(commandStdout)) {
|
|
67
|
-
if (command.startsWith(prefix)) return taskResult(stdout);
|
|
68
|
-
}
|
|
69
|
-
return taskResult('Shanghai|42\n');
|
|
70
|
-
},
|
|
71
|
-
async runSpawn() {
|
|
72
|
-
throw new Error('runSpawn should not be called');
|
|
73
|
-
},
|
|
74
|
-
async ensureDir() {
|
|
75
|
-
/* no-op */
|
|
76
|
-
},
|
|
77
|
-
async fileExists() {
|
|
78
|
-
return false;
|
|
79
|
-
},
|
|
80
|
-
async *watch() {
|
|
81
|
-
/* no-op */
|
|
82
|
-
},
|
|
83
|
-
logStore: {
|
|
84
|
-
openRunLog({ runId }) {
|
|
85
|
-
return {
|
|
86
|
-
path: `mem://${runId}/pipeline.log`,
|
|
87
|
-
dir: `mem://${runId}`,
|
|
88
|
-
append() {
|
|
89
|
-
/* memory sink */
|
|
90
|
-
},
|
|
91
|
-
close() {
|
|
92
|
-
/* memory sink */
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
},
|
|
96
|
-
taskOutputPath({ runId, taskId, stream }) {
|
|
97
|
-
return `mem://${runId}/${taskId}.${stream}`;
|
|
98
|
-
},
|
|
99
|
-
logsDir() {
|
|
100
|
-
return 'mem://logs';
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
now: () => new Date('2026-04-26T00:00:00.000Z'),
|
|
104
|
-
sleep: () => Promise.resolve(),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function run(config: PipelineConfig, workDir: string, runtime: TagmaRuntime) {
|
|
109
|
-
const events: RunEventPayload[] = [];
|
|
110
|
-
const result = await runPipeline(config, workDir, {
|
|
111
|
-
registry: freshRegistry(),
|
|
112
|
-
runtime,
|
|
113
|
-
skipPluginLoading: true,
|
|
114
|
-
onEvent: (e) => events.push(e),
|
|
115
|
-
});
|
|
116
|
-
return { events, success: result.success };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function finalUpdateFor(events: RunEventPayload[], qid: string): RunEventPayload | undefined {
|
|
120
|
-
let last: RunEventPayload | undefined;
|
|
121
|
-
for (const ev of events) {
|
|
122
|
-
if (ev.type === 'task_update' && ev.taskId === qid) last = ev;
|
|
123
|
-
}
|
|
124
|
-
return last;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function finalStatusFrom(events: RunEventPayload[], qid: string): TaskStatus | undefined {
|
|
128
|
-
const last = finalUpdateFor(events, qid);
|
|
129
|
-
return last && last.type === 'task_update' ? last.status : undefined;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
describe('engine — unified inputs and outputs', () => {
|
|
133
|
-
test('typed outputs feed typed inputs and command placeholders', async () => {
|
|
134
|
-
const dir = makeDir();
|
|
135
|
-
try {
|
|
136
|
-
const runtime = fakeRuntime({ 'emit-valid': '{"id":"42","city":"Shanghai"}\n' });
|
|
137
|
-
const config = pipeline([
|
|
138
|
-
task({
|
|
139
|
-
id: 'up',
|
|
140
|
-
command: 'emit-valid',
|
|
141
|
-
outputs: { id: { type: 'number' }, city: { type: 'string' } },
|
|
142
|
-
}),
|
|
143
|
-
task({
|
|
144
|
-
id: 'down',
|
|
145
|
-
depends_on: ['up'],
|
|
146
|
-
command: 'echo-down "{{inputs.city}}" "{{inputs.id}}"',
|
|
147
|
-
inputs: {
|
|
148
|
-
city: { from: 't.up.outputs.city', type: 'string', required: true },
|
|
149
|
-
id: { from: 't.up.outputs.id', type: 'number', required: true },
|
|
150
|
-
},
|
|
151
|
-
}),
|
|
152
|
-
]);
|
|
153
|
-
|
|
154
|
-
const { events, success } = await run(config, dir, runtime);
|
|
155
|
-
expect(success).toBe(true);
|
|
156
|
-
expect(finalUpdateFor(events, 't.up')?.outputs).toEqual({ id: 42, city: 'Shanghai' });
|
|
157
|
-
expect(finalUpdateFor(events, 't.down')?.inputs).toEqual({ city: 'Shanghai', id: 42 });
|
|
158
|
-
expect(finalUpdateFor(events, 't.down')?.stdout).toContain('Shanghai|42');
|
|
159
|
-
} finally {
|
|
160
|
-
rmSync(dir, { recursive: true, force: true });
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('missing required unified input blocks without spawning downstream', async () => {
|
|
165
|
-
const dir = makeDir();
|
|
166
|
-
try {
|
|
167
|
-
const runtime = fakeRuntime({ 'emit-missing': '{"other":"x"}\n' });
|
|
168
|
-
const config = pipeline([
|
|
169
|
-
task({ id: 'up', command: 'emit-missing', outputs: { city: { type: 'string' } } }),
|
|
170
|
-
task({
|
|
171
|
-
id: 'down',
|
|
172
|
-
depends_on: ['up'],
|
|
173
|
-
command: 'echo-down "{{inputs.city}}"',
|
|
174
|
-
inputs: { city: { from: 't.up.outputs.city', type: 'string', required: true } },
|
|
175
|
-
}),
|
|
176
|
-
]);
|
|
177
|
-
|
|
178
|
-
const { events, success } = await run(config, dir, runtime);
|
|
179
|
-
expect(success).toBe(false);
|
|
180
|
-
expect(finalStatusFrom(events, 't.up')).toBe('success');
|
|
181
|
-
expect(finalStatusFrom(events, 't.down')).toBe('blocked');
|
|
182
|
-
} finally {
|
|
183
|
-
rmSync(dir, { recursive: true, force: true });
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test('typed output coercion diagnostics leave missing downstream input', async () => {
|
|
188
|
-
const dir = makeDir();
|
|
189
|
-
try {
|
|
190
|
-
const runtime = fakeRuntime({ 'emit-bad': '{"id":"not-a-number"}\n' });
|
|
191
|
-
const config = pipeline([
|
|
192
|
-
task({ id: 'up', command: 'emit-bad', outputs: { id: { type: 'number' } } }),
|
|
193
|
-
task({
|
|
194
|
-
id: 'down',
|
|
195
|
-
depends_on: ['up'],
|
|
196
|
-
command: 'echo-down "{{inputs.id}}"',
|
|
197
|
-
inputs: { id: { from: 't.up.outputs.id', type: 'number', required: true } },
|
|
198
|
-
}),
|
|
199
|
-
]);
|
|
200
|
-
|
|
201
|
-
const { events, success } = await run(config, dir, runtime);
|
|
202
|
-
expect(success).toBe(false);
|
|
203
|
-
expect(finalStatusFrom(events, 't.up')).toBe('success');
|
|
204
|
-
expect(finalUpdateFor(events, 't.up')?.stderr).toContain('expected number');
|
|
205
|
-
expect(finalStatusFrom(events, 't.down')).toBe('blocked');
|
|
206
|
-
} finally {
|
|
207
|
-
rmSync(dir, { recursive: true, force: true });
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { runPipeline, type RunEventPayload } from './engine';
|
|
6
|
-
import { PluginRegistry } from './registry';
|
|
7
|
-
import type { PipelineConfig } from './types';
|
|
8
|
-
|
|
9
|
-
function makeDir(): string {
|
|
10
|
-
return mkdtempSync(join(tmpdir(), 'tagma-task-type-'));
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
describe('engine task type detection', () => {
|
|
14
|
-
test('empty command is still a command task and does not require a driver', async () => {
|
|
15
|
-
const dir = makeDir();
|
|
16
|
-
const previousShell = process.env.PIPELINE_SHELL;
|
|
17
|
-
process.env.PIPELINE_SHELL = 'cmd';
|
|
18
|
-
try {
|
|
19
|
-
const events: RunEventPayload[] = [];
|
|
20
|
-
const config: PipelineConfig = {
|
|
21
|
-
name: 'empty-command',
|
|
22
|
-
tracks: [
|
|
23
|
-
{
|
|
24
|
-
id: 't',
|
|
25
|
-
name: 'T',
|
|
26
|
-
tasks: [{ id: 'cmd', name: 'cmd', command: '' }],
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const result = await runPipeline(config, dir, {
|
|
32
|
-
registry: new PluginRegistry(),
|
|
33
|
-
skipPluginLoading: true,
|
|
34
|
-
onEvent: (event) => events.push(event),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
expect(result.success).toBe(true);
|
|
38
|
-
expect(events.some((event) => event.type === 'run_start')).toBe(true);
|
|
39
|
-
const final = events.findLast(
|
|
40
|
-
(event) => event.type === 'task_update' && event.taskId === 't.cmd',
|
|
41
|
-
);
|
|
42
|
-
expect(final?.type).toBe('task_update');
|
|
43
|
-
if (final?.type === 'task_update') {
|
|
44
|
-
expect(final.status).toBe('success');
|
|
45
|
-
expect(final.resolvedDriver).toBeNull();
|
|
46
|
-
}
|
|
47
|
-
} finally {
|
|
48
|
-
if (previousShell === undefined) {
|
|
49
|
-
delete process.env.PIPELINE_SHELL;
|
|
50
|
-
} else {
|
|
51
|
-
process.env.PIPELINE_SHELL = previousShell;
|
|
52
|
-
}
|
|
53
|
-
rmSync(dir, { recursive: true, force: true });
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
});
|
package/src/engine.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
runPipeline as runCorePipeline,
|
|
3
|
-
TriggerBlockedError,
|
|
4
|
-
TriggerTimeoutError,
|
|
5
|
-
type EngineResult,
|
|
6
|
-
type RunEventPayload,
|
|
7
|
-
type RunPipelineOptions as CoreRunPipelineOptions,
|
|
8
|
-
} from '@tagma/core';
|
|
9
|
-
import { bunRuntime } from '@tagma/runtime-bun';
|
|
10
|
-
import type { PipelineConfig, TagmaRuntime } from './types';
|
|
11
|
-
|
|
12
|
-
export { TriggerBlockedError, TriggerTimeoutError };
|
|
13
|
-
export type { EngineResult, RunEventPayload };
|
|
14
|
-
|
|
15
|
-
export interface RunPipelineOptions extends Omit<CoreRunPipelineOptions, 'runtime'> {
|
|
16
|
-
/**
|
|
17
|
-
* Runtime implementation for command and driver process execution.
|
|
18
|
-
* Defaults to the SDK's Bun runtime.
|
|
19
|
-
*/
|
|
20
|
-
readonly runtime?: TagmaRuntime;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function runPipeline(
|
|
24
|
-
config: PipelineConfig,
|
|
25
|
-
workDir: string,
|
|
26
|
-
options: RunPipelineOptions,
|
|
27
|
-
): Promise<EngineResult> {
|
|
28
|
-
return runCorePipeline(config, workDir, {
|
|
29
|
-
...options,
|
|
30
|
-
runtime: options.runtime ?? bunRuntime(),
|
|
31
|
-
});
|
|
32
|
-
}
|
package/src/hooks.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import type { HooksConfig, HookCommand, AbortReason } from './types';
|
|
2
|
-
import { shellArgs } from './utils';
|
|
3
|
-
|
|
4
|
-
type HookEvent =
|
|
5
|
-
| 'pipeline_start'
|
|
6
|
-
| 'task_start'
|
|
7
|
-
| 'task_success'
|
|
8
|
-
| 'task_failure'
|
|
9
|
-
| 'pipeline_complete'
|
|
10
|
-
| 'pipeline_error';
|
|
11
|
-
|
|
12
|
-
const GATE_HOOKS: ReadonlySet<HookEvent> = new Set(['pipeline_start', 'task_start']);
|
|
13
|
-
|
|
14
|
-
export interface HookResult {
|
|
15
|
-
readonly allowed: boolean; // for gate hooks: true = proceed, false = block
|
|
16
|
-
readonly exitCode: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function normalizeCommands(cmd: HookCommand | undefined): readonly string[] {
|
|
20
|
-
if (!cmd) return [];
|
|
21
|
-
if (typeof cmd === 'string') return [cmd];
|
|
22
|
-
return cmd;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DEFAULT_HOOK_TIMEOUT_MS = 30_000;
|
|
26
|
-
|
|
27
|
-
async function runSingleHook(
|
|
28
|
-
command: string,
|
|
29
|
-
context: unknown,
|
|
30
|
-
cwd?: string,
|
|
31
|
-
signal?: AbortSignal,
|
|
32
|
-
timeoutMs: number = DEFAULT_HOOK_TIMEOUT_MS,
|
|
33
|
-
): Promise<number> {
|
|
34
|
-
const jsonInput = JSON.stringify(context, null, 2);
|
|
35
|
-
|
|
36
|
-
const controller = new AbortController();
|
|
37
|
-
const timer = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : null;
|
|
38
|
-
|
|
39
|
-
// Wire pipeline abort signal into hook process
|
|
40
|
-
const onAbort = () => controller.abort();
|
|
41
|
-
if (signal) {
|
|
42
|
-
if (signal.aborted) {
|
|
43
|
-
controller.abort();
|
|
44
|
-
} else {
|
|
45
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
const proc = Bun.spawn(shellArgs(command) as string[], {
|
|
51
|
-
stdin: 'pipe',
|
|
52
|
-
stdout: 'pipe',
|
|
53
|
-
stderr: 'pipe',
|
|
54
|
-
signal: controller.signal,
|
|
55
|
-
...(cwd ? { cwd } : {}),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (proc.stdin) {
|
|
59
|
-
try {
|
|
60
|
-
proc.stdin.write(jsonInput);
|
|
61
|
-
proc.stdin.end();
|
|
62
|
-
} catch {
|
|
63
|
-
// Process may exit before reading stdin (e.g. `exit 1`), ignore EPIPE
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Consume stdout and stderr concurrently with waiting for exit.
|
|
68
|
-
// Sequential reads after proc.exited risk a pipe-buffer deadlock when
|
|
69
|
-
// hook output exceeds the ~64 KB kernel buffer.
|
|
70
|
-
const [exitCode, stdout, stderr] = await Promise.all([
|
|
71
|
-
proc.exited,
|
|
72
|
-
new Response(proc.stdout).text(),
|
|
73
|
-
new Response(proc.stderr).text(),
|
|
74
|
-
]);
|
|
75
|
-
|
|
76
|
-
if (stdout.trim()) {
|
|
77
|
-
console.warn(`[hook: ${command}] stdout: ${stdout.trim()}`);
|
|
78
|
-
}
|
|
79
|
-
if (stderr.trim()) {
|
|
80
|
-
console.error(`[hook: ${command}] stderr: ${stderr.trim()}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return exitCode;
|
|
84
|
-
} catch (err) {
|
|
85
|
-
console.error(
|
|
86
|
-
`[hook: ${command}] spawn error: ${err instanceof Error ? err.message : String(err)}`,
|
|
87
|
-
);
|
|
88
|
-
return -1;
|
|
89
|
-
} finally {
|
|
90
|
-
if (timer) clearTimeout(timer);
|
|
91
|
-
if (signal) signal.removeEventListener('abort', onAbort);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function executeHook(
|
|
96
|
-
hooks: HooksConfig | undefined,
|
|
97
|
-
event: HookEvent,
|
|
98
|
-
context: unknown,
|
|
99
|
-
workDir?: string,
|
|
100
|
-
signal?: AbortSignal,
|
|
101
|
-
): Promise<HookResult> {
|
|
102
|
-
if (!hooks) return { allowed: true, exitCode: 0 };
|
|
103
|
-
|
|
104
|
-
const commands = normalizeCommands(hooks[event]);
|
|
105
|
-
if (commands.length === 0) return { allowed: true, exitCode: 0 };
|
|
106
|
-
|
|
107
|
-
const isGate = GATE_HOOKS.has(event);
|
|
108
|
-
|
|
109
|
-
for (const cmd of commands) {
|
|
110
|
-
const exitCode = await runSingleHook(cmd, context, workDir, signal);
|
|
111
|
-
|
|
112
|
-
if (isGate && exitCode === 1) {
|
|
113
|
-
// Only exit code 1 has gate semantics (block execution)
|
|
114
|
-
return { allowed: false, exitCode };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (exitCode !== 0) {
|
|
118
|
-
// Non-zero but not 1: hook itself had an error, log but don't block
|
|
119
|
-
console.warn(`[hook: ${event}] "${cmd}" exited with code ${exitCode}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return { allowed: true, exitCode: 0 };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ═══ Context Builders ═══
|
|
127
|
-
|
|
128
|
-
export interface PipelineInfo {
|
|
129
|
-
readonly name: string;
|
|
130
|
-
readonly run_id: string;
|
|
131
|
-
readonly started_at: string;
|
|
132
|
-
readonly finished_at?: string;
|
|
133
|
-
readonly duration_ms?: number;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export interface TrackInfo {
|
|
137
|
-
readonly id: string;
|
|
138
|
-
readonly name: string;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface TaskInfo {
|
|
142
|
-
readonly id: string;
|
|
143
|
-
readonly name: string;
|
|
144
|
-
readonly type: 'ai' | 'command';
|
|
145
|
-
readonly status: string;
|
|
146
|
-
readonly exit_code: number | null;
|
|
147
|
-
readonly duration_ms: number | null;
|
|
148
|
-
readonly stderr_path: string | null;
|
|
149
|
-
readonly session_id: string | null;
|
|
150
|
-
readonly started_at: string | null;
|
|
151
|
-
readonly finished_at: string | null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function buildPipelineStartContext(pipeline: PipelineInfo) {
|
|
155
|
-
return { event: 'pipeline_start', pipeline };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export function buildTaskContext(
|
|
159
|
-
event: 'task_start' | 'task_success' | 'task_failure',
|
|
160
|
-
pipeline: PipelineInfo,
|
|
161
|
-
track: TrackInfo,
|
|
162
|
-
task: TaskInfo,
|
|
163
|
-
) {
|
|
164
|
-
return { event, pipeline, track, task };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function buildPipelineCompleteContext(
|
|
168
|
-
pipeline: PipelineInfo & { finished_at: string; duration_ms: number },
|
|
169
|
-
summary: {
|
|
170
|
-
total: number;
|
|
171
|
-
success: number;
|
|
172
|
-
failed: number;
|
|
173
|
-
skipped: number;
|
|
174
|
-
timeout: number;
|
|
175
|
-
blocked: number;
|
|
176
|
-
},
|
|
177
|
-
) {
|
|
178
|
-
return { event: 'pipeline_complete', pipeline, summary };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function buildPipelineErrorContext(
|
|
182
|
-
pipeline: PipelineInfo,
|
|
183
|
-
error: string,
|
|
184
|
-
eventType?: string,
|
|
185
|
-
abortReason?: AbortReason,
|
|
186
|
-
) {
|
|
187
|
-
return {
|
|
188
|
-
event: eventType ?? 'pipeline_error',
|
|
189
|
-
pipeline,
|
|
190
|
-
error,
|
|
191
|
-
...(abortReason !== undefined ? { abort_reason: abortReason } : {}),
|
|
192
|
-
};
|
|
193
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export { createTagma } from './tagma';
|
|
2
|
-
export type { CreateTagmaOptions, Tagma, TagmaRunOptions } from './tagma';
|
|
3
|
-
export { bunRuntime } from '@tagma/runtime-bun';
|
|
4
|
-
export type { TagmaRuntime, RunOptions as RuntimeRunOptions } from '@tagma/core';
|
|
5
|
-
export { definePipeline, PluginRegistry, TriggerBlockedError, TriggerTimeoutError } from '@tagma/core';
|
|
6
|
-
export type { EngineResult, RunEventPayload } from '@tagma/core';
|
|
7
|
-
export { RUN_PROTOCOL_VERSION, TASK_LOG_CAP } from './types';
|
|
8
|
-
export type {
|
|
9
|
-
PipelineConfig,
|
|
10
|
-
RawPipelineConfig,
|
|
11
|
-
RawTrackConfig,
|
|
12
|
-
RawTaskConfig,
|
|
13
|
-
TrackConfig,
|
|
14
|
-
TaskConfig,
|
|
15
|
-
RunSnapshotPayload,
|
|
16
|
-
WireRunEvent,
|
|
17
|
-
RunTaskState,
|
|
18
|
-
TaskLogLine,
|
|
19
|
-
ApprovalRequestInfo,
|
|
20
|
-
TaskStatus,
|
|
21
|
-
ApprovalRequest,
|
|
22
|
-
PluginCategory,
|
|
23
|
-
PluginCapabilities,
|
|
24
|
-
PluginSetupContext,
|
|
25
|
-
TagmaPlugin,
|
|
26
|
-
DriverPlugin,
|
|
27
|
-
TriggerPlugin,
|
|
28
|
-
CompletionPlugin,
|
|
29
|
-
MiddlewarePlugin,
|
|
30
|
-
RunEventPayload as PipelineRunEventPayload,
|
|
31
|
-
} from '@tagma/types';
|
package/src/logger.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { basename } from 'path';
|
|
2
|
-
import type { MiddlewarePlugin, MiddlewareContext, PromptDocument } from '../types';
|
|
3
|
-
import { validatePath } from '../utils';
|
|
4
|
-
import { appendContext } from '../prompt-doc';
|
|
5
|
-
|
|
6
|
-
export const StaticContextMiddleware: MiddlewarePlugin = {
|
|
7
|
-
name: 'static_context',
|
|
8
|
-
schema: {
|
|
9
|
-
description: 'Prepend a reference file to the prompt as static context.',
|
|
10
|
-
fields: {
|
|
11
|
-
file: {
|
|
12
|
-
type: 'path',
|
|
13
|
-
required: true,
|
|
14
|
-
description: 'Path to the reference file (relative to workDir or absolute).',
|
|
15
|
-
placeholder: 'docs/spec.md',
|
|
16
|
-
},
|
|
17
|
-
label: {
|
|
18
|
-
type: 'string',
|
|
19
|
-
description: 'Header shown before the content. Defaults to "Reference: <basename>".',
|
|
20
|
-
placeholder: 'Reference: spec.md',
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
|
|
25
|
-
async enhanceDoc(
|
|
26
|
-
doc: PromptDocument,
|
|
27
|
-
config: Record<string, unknown>,
|
|
28
|
-
ctx: MiddlewareContext,
|
|
29
|
-
): Promise<PromptDocument> {
|
|
30
|
-
const filePath = config.file as string;
|
|
31
|
-
if (!filePath) throw new Error('static_context middleware: "file" is required');
|
|
32
|
-
|
|
33
|
-
const safePath = validatePath(filePath, ctx.workDir);
|
|
34
|
-
const file = Bun.file(safePath);
|
|
35
|
-
|
|
36
|
-
if (!(await file.exists())) {
|
|
37
|
-
console.warn(`static_context: file ${filePath} not found, skipping`);
|
|
38
|
-
return doc;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const content = await file.text();
|
|
42
|
-
const label = (config.label as string) ?? `Reference: ${basename(filePath)}`;
|
|
43
|
-
|
|
44
|
-
// Append a labeled context block; the engine's serializer joins blocks
|
|
45
|
-
// with blank lines and places the task last. No [Task] header here —
|
|
46
|
-
// that framing is the driver's concern (e.g. opencode's agent_profile).
|
|
47
|
-
return appendContext(doc, { label, content });
|
|
48
|
-
},
|
|
49
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { PluginRegistry, InMemoryApprovalGateway } from '@tagma/core';
|
|
3
|
-
import { bunRuntime } from '@tagma/runtime-bun';
|
|
4
|
-
import { createTagma } from './index';
|
|
5
|
-
|
|
6
|
-
describe('Phase 6 package split', () => {
|
|
7
|
-
test('sdk composes the core registry and bun runtime packages', () => {
|
|
8
|
-
const runtime = bunRuntime();
|
|
9
|
-
const tagma = createTagma({ runtime, builtins: false });
|
|
10
|
-
|
|
11
|
-
expect(tagma.registry).toBeInstanceOf(PluginRegistry);
|
|
12
|
-
expect(typeof runtime.runCommand).toBe('function');
|
|
13
|
-
expect(new InMemoryApprovalGateway().pending()).toEqual([]);
|
|
14
|
-
});
|
|
15
|
-
});
|