@tagma/sdk 0.7.4 → 0.7.5
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.map +1 -1
- package/dist/validate-raw.js +5 -12
- 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
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { PluginRegistry } from './registry';
|
|
3
|
-
import { bootstrapBuiltins } from './bootstrap';
|
|
4
|
-
import { runPipeline } from './engine';
|
|
5
|
-
import type { DriverPlugin, TriggerPlugin, PipelineConfig, TagmaRuntime, TaskResult } from './types';
|
|
6
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
|
-
import { tmpdir } from 'node:os';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
import type { TagmaPlugin } from './types';
|
|
10
|
-
|
|
11
|
-
function makeDriver(name: string, marker: string[]): DriverPlugin {
|
|
12
|
-
return {
|
|
13
|
-
name,
|
|
14
|
-
capabilities: { sessionResume: false, systemPrompt: false, outputFormat: false },
|
|
15
|
-
async buildCommand() {
|
|
16
|
-
marker.push(`buildCommand:${name}`);
|
|
17
|
-
return { args: ['echo', 'noop'] };
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function makeTrigger(name: string, marker: string[]): TriggerPlugin {
|
|
23
|
-
return {
|
|
24
|
-
name,
|
|
25
|
-
async watch() {
|
|
26
|
-
marker.push(`watch:${name}`);
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function taskResult(stdout = 'ok\n'): TaskResult {
|
|
32
|
-
return {
|
|
33
|
-
exitCode: 0,
|
|
34
|
-
stdout,
|
|
35
|
-
stderr: '',
|
|
36
|
-
stdoutPath: null,
|
|
37
|
-
stderrPath: null,
|
|
38
|
-
stdoutBytes: stdout.length,
|
|
39
|
-
stderrBytes: 0,
|
|
40
|
-
durationMs: 1,
|
|
41
|
-
sessionId: null,
|
|
42
|
-
normalizedOutput: null,
|
|
43
|
-
failureKind: null,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function fakeRuntime(): TagmaRuntime {
|
|
48
|
-
return {
|
|
49
|
-
async runCommand() {
|
|
50
|
-
return taskResult();
|
|
51
|
-
},
|
|
52
|
-
async runSpawn() {
|
|
53
|
-
return taskResult();
|
|
54
|
-
},
|
|
55
|
-
async ensureDir() {
|
|
56
|
-
/* no-op */
|
|
57
|
-
},
|
|
58
|
-
async fileExists() {
|
|
59
|
-
return false;
|
|
60
|
-
},
|
|
61
|
-
async *watch() {
|
|
62
|
-
/* no-op */
|
|
63
|
-
},
|
|
64
|
-
logStore: {
|
|
65
|
-
openRunLog({ runId }) {
|
|
66
|
-
return {
|
|
67
|
-
path: `mem://${runId}/pipeline.log`,
|
|
68
|
-
dir: `mem://${runId}`,
|
|
69
|
-
append() {
|
|
70
|
-
/* memory sink */
|
|
71
|
-
},
|
|
72
|
-
close() {
|
|
73
|
-
/* memory sink */
|
|
74
|
-
},
|
|
75
|
-
};
|
|
76
|
-
},
|
|
77
|
-
taskOutputPath({ runId, taskId, stream }) {
|
|
78
|
-
return `mem://${runId}/${taskId}.${stream}`;
|
|
79
|
-
},
|
|
80
|
-
logsDir() {
|
|
81
|
-
return 'mem://logs';
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
now: () => new Date('2026-04-26T00:00:00.000Z'),
|
|
85
|
-
sleep: () => Promise.resolve(),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
describe('PluginRegistry — instance isolation', () => {
|
|
90
|
-
test('two registries do not share drivers registered under the same type', () => {
|
|
91
|
-
const regA = new PluginRegistry();
|
|
92
|
-
const regB = new PluginRegistry();
|
|
93
|
-
const markerA: string[] = [];
|
|
94
|
-
const markerB: string[] = [];
|
|
95
|
-
|
|
96
|
-
regA.registerPlugin('drivers', 'mock', makeDriver('mockA', markerA));
|
|
97
|
-
regB.registerPlugin('drivers', 'mock', makeDriver('mockB', markerB));
|
|
98
|
-
|
|
99
|
-
expect(regA.getHandler<DriverPlugin>('drivers', 'mock').name).toBe('mockA');
|
|
100
|
-
expect(regB.getHandler<DriverPlugin>('drivers', 'mock').name).toBe('mockB');
|
|
101
|
-
|
|
102
|
-
expect(regA.hasHandler('drivers', 'mock')).toBe(true);
|
|
103
|
-
expect(regB.hasHandler('drivers', 'mock')).toBe(true);
|
|
104
|
-
expect(regA.hasHandler('triggers', 'mock')).toBe(false);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('unregistering in one registry does not affect the other', () => {
|
|
108
|
-
const regA = new PluginRegistry();
|
|
109
|
-
const regB = new PluginRegistry();
|
|
110
|
-
regA.registerPlugin('drivers', 'mock', makeDriver('mockA', []));
|
|
111
|
-
regB.registerPlugin('drivers', 'mock', makeDriver('mockB', []));
|
|
112
|
-
|
|
113
|
-
expect(regA.unregisterPlugin('drivers', 'mock')).toBe(true);
|
|
114
|
-
expect(regA.hasHandler('drivers', 'mock')).toBe(false);
|
|
115
|
-
expect(regB.hasHandler('drivers', 'mock')).toBe(true);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('listRegistered is scoped per instance', () => {
|
|
119
|
-
const regA = new PluginRegistry();
|
|
120
|
-
const regB = new PluginRegistry();
|
|
121
|
-
regA.registerPlugin('triggers', 'a-only', makeTrigger('a-only', []));
|
|
122
|
-
regB.registerPlugin('triggers', 'b-only', makeTrigger('b-only', []));
|
|
123
|
-
|
|
124
|
-
expect(regA.listRegistered('triggers')).toEqual(['a-only']);
|
|
125
|
-
expect(regB.listRegistered('triggers')).toEqual(['b-only']);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test('registering the same instance twice returns unchanged', () => {
|
|
129
|
-
const reg = new PluginRegistry();
|
|
130
|
-
const driver = makeDriver('same', []);
|
|
131
|
-
expect(reg.registerPlugin('drivers', 'mock', driver)).toBe('registered');
|
|
132
|
-
expect(reg.registerPlugin('drivers', 'mock', driver)).toBe('unchanged');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('replacing with a different handler returns replaced', () => {
|
|
136
|
-
const reg = new PluginRegistry();
|
|
137
|
-
expect(reg.registerPlugin('drivers', 'mock', makeDriver('one', []))).toBe('registered');
|
|
138
|
-
expect(reg.registerPlugin('drivers', 'mock', makeDriver('two', []))).toBe('replaced');
|
|
139
|
-
expect(reg.getHandler<DriverPlugin>('drivers', 'mock').name).toBe('two');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test('bootstrapBuiltins(target) populates a specific instance', () => {
|
|
143
|
-
const fresh = new PluginRegistry();
|
|
144
|
-
expect(fresh.hasHandler('drivers', 'opencode')).toBe(false);
|
|
145
|
-
|
|
146
|
-
bootstrapBuiltins(fresh);
|
|
147
|
-
|
|
148
|
-
expect(fresh.hasHandler('drivers', 'opencode')).toBe(true);
|
|
149
|
-
expect(fresh.hasHandler('triggers', 'file')).toBe(true);
|
|
150
|
-
expect(fresh.hasHandler('triggers', 'manual')).toBe(true);
|
|
151
|
-
expect(fresh.hasHandler('completions', 'exit_code')).toBe(true);
|
|
152
|
-
expect(fresh.hasHandler('middlewares', 'static_context')).toBe(true);
|
|
153
|
-
|
|
154
|
-
// Default registry's state is independent of `fresh` — if the default
|
|
155
|
-
// happens to have opencode (because another test bootstrapped it), that
|
|
156
|
-
// is fine; the guarantee is that `fresh.unregister` does not leak.
|
|
157
|
-
fresh.unregisterPlugin('drivers', 'opencode');
|
|
158
|
-
expect(fresh.hasHandler('drivers', 'opencode')).toBe(false);
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('PluginRegistry — capability plugins', () => {
|
|
163
|
-
test('registerTagmaPlugin registers multiple capabilities from one package', () => {
|
|
164
|
-
const reg = new PluginRegistry();
|
|
165
|
-
const driver = makeDriver('cap-driver', []);
|
|
166
|
-
const trigger = makeTrigger('cap-trigger', []);
|
|
167
|
-
const plugin: TagmaPlugin = {
|
|
168
|
-
name: 'tagma-plugin-multi',
|
|
169
|
-
capabilities: {
|
|
170
|
-
drivers: { cap_driver: driver },
|
|
171
|
-
triggers: { cap_trigger: trigger },
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
expect(reg.registerTagmaPlugin(plugin)).toEqual([
|
|
176
|
-
{ category: 'drivers', type: 'cap_driver', result: 'registered' },
|
|
177
|
-
{ category: 'triggers', type: 'cap_trigger', result: 'registered' },
|
|
178
|
-
]);
|
|
179
|
-
expect(reg.getHandler<DriverPlugin>('drivers', 'cap_driver')).toBe(driver);
|
|
180
|
-
expect(reg.getHandler<TriggerPlugin>('triggers', 'cap_trigger')).toBe(trigger);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test('registerTagmaPlugin keeps replacement warnings from the registry path', () => {
|
|
184
|
-
const reg = new PluginRegistry();
|
|
185
|
-
const originalWarn = console.warn;
|
|
186
|
-
const warnings: string[] = [];
|
|
187
|
-
console.warn = (message?: unknown) => {
|
|
188
|
-
warnings.push(String(message));
|
|
189
|
-
};
|
|
190
|
-
try {
|
|
191
|
-
reg.registerPlugin('drivers', 'mock', makeDriver('first', []));
|
|
192
|
-
const result = reg.registerTagmaPlugin({
|
|
193
|
-
name: 'tagma-plugin-replacement',
|
|
194
|
-
capabilities: {
|
|
195
|
-
drivers: { mock: makeDriver('second', []) },
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
expect(result).toEqual([{ category: 'drivers', type: 'mock', result: 'replaced' }]);
|
|
200
|
-
expect(warnings).toContain(
|
|
201
|
-
'[tagma-sdk] registerPlugin: replaced existing drivers/mock - check for duplicate plugin packages claiming the same type.',
|
|
202
|
-
);
|
|
203
|
-
} finally {
|
|
204
|
-
console.warn = originalWarn;
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('loadPlugins accepts capability plugin default exports', async () => {
|
|
209
|
-
const dir = mkdtempSync(join(tmpdir(), 'tagma-capability-plugin-'));
|
|
210
|
-
const pluginDir = join(dir, 'node_modules', 'tagma-plugin-capability');
|
|
211
|
-
mkdirSync(pluginDir, { recursive: true });
|
|
212
|
-
writeFileSync(
|
|
213
|
-
join(pluginDir, 'package.json'),
|
|
214
|
-
JSON.stringify({ name: 'tagma-plugin-capability', version: '1.0.0', type: 'module', main: './index.js' }),
|
|
215
|
-
'utf-8',
|
|
216
|
-
);
|
|
217
|
-
writeFileSync(
|
|
218
|
-
join(pluginDir, 'index.js'),
|
|
219
|
-
[
|
|
220
|
-
'const driver = {',
|
|
221
|
-
" name: 'cap-driver',",
|
|
222
|
-
' capabilities: { sessionResume: false, systemPrompt: false, outputFormat: false },',
|
|
223
|
-
" async buildCommand() { return { args: ['echo', 'cap'] }; },",
|
|
224
|
-
'};',
|
|
225
|
-
'const trigger = {',
|
|
226
|
-
" name: 'cap-trigger',",
|
|
227
|
-
' async watch() {}',
|
|
228
|
-
'};',
|
|
229
|
-
'export default {',
|
|
230
|
-
" name: 'tagma-plugin-capability',",
|
|
231
|
-
' capabilities: {',
|
|
232
|
-
' drivers: { cap_driver: driver },',
|
|
233
|
-
' triggers: { cap_trigger: trigger },',
|
|
234
|
-
' },',
|
|
235
|
-
'};',
|
|
236
|
-
'',
|
|
237
|
-
].join('\n'),
|
|
238
|
-
'utf-8',
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
const reg = new PluginRegistry();
|
|
243
|
-
await reg.loadPlugins(['tagma-plugin-capability'], dir);
|
|
244
|
-
expect(reg.hasHandler('drivers', 'cap_driver')).toBe(true);
|
|
245
|
-
expect(reg.hasHandler('triggers', 'cap_trigger')).toBe(true);
|
|
246
|
-
} finally {
|
|
247
|
-
rmSync(dir, { recursive: true, force: true });
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
test('loadPlugins rejects legacy plugin module exports', async () => {
|
|
252
|
-
const dir = mkdtempSync(join(tmpdir(), 'tagma-legacy-plugin-'));
|
|
253
|
-
const pluginDir = join(dir, 'node_modules', 'tagma-plugin-legacy');
|
|
254
|
-
mkdirSync(pluginDir, { recursive: true });
|
|
255
|
-
writeFileSync(
|
|
256
|
-
join(pluginDir, 'package.json'),
|
|
257
|
-
JSON.stringify({ name: 'tagma-plugin-legacy', version: '1.0.0', type: 'module', main: './index.js' }),
|
|
258
|
-
'utf-8',
|
|
259
|
-
);
|
|
260
|
-
writeFileSync(
|
|
261
|
-
join(pluginDir, 'index.js'),
|
|
262
|
-
[
|
|
263
|
-
"export const pluginCategory = 'drivers';",
|
|
264
|
-
"export const pluginType = 'legacy';",
|
|
265
|
-
'export default {',
|
|
266
|
-
" name: 'legacy',",
|
|
267
|
-
' capabilities: { sessionResume: false, systemPrompt: false, outputFormat: false },',
|
|
268
|
-
" async buildCommand() { return { args: ['echo', 'legacy'] }; },",
|
|
269
|
-
'};',
|
|
270
|
-
'',
|
|
271
|
-
].join('\n'),
|
|
272
|
-
'utf-8',
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const reg = new PluginRegistry();
|
|
277
|
-
await expect(reg.loadPlugins(['tagma-plugin-legacy'], dir)).rejects.toThrow(
|
|
278
|
-
/must default-export a TagmaPlugin/,
|
|
279
|
-
);
|
|
280
|
-
} finally {
|
|
281
|
-
rmSync(dir, { recursive: true, force: true });
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe('PluginRegistry — validation', () => {
|
|
287
|
-
test('rejects unknown category', () => {
|
|
288
|
-
const reg = new PluginRegistry();
|
|
289
|
-
expect(() =>
|
|
290
|
-
reg.registerPlugin(
|
|
291
|
-
'nope' as 'drivers',
|
|
292
|
-
'x',
|
|
293
|
-
makeDriver('x', []),
|
|
294
|
-
),
|
|
295
|
-
).toThrow(/Unknown plugin category/);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('rejects driver missing buildCommand', () => {
|
|
299
|
-
const reg = new PluginRegistry();
|
|
300
|
-
expect(() =>
|
|
301
|
-
reg.registerPlugin(
|
|
302
|
-
'drivers',
|
|
303
|
-
'broken',
|
|
304
|
-
// deliberately bad: no buildCommand
|
|
305
|
-
{ name: 'broken', capabilities: { sessionResume: false, systemPrompt: false, outputFormat: false } } as unknown as DriverPlugin,
|
|
306
|
-
),
|
|
307
|
-
).toThrow(/must export buildCommand/);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test('rejects handler with missing name', () => {
|
|
311
|
-
const reg = new PluginRegistry();
|
|
312
|
-
expect(() =>
|
|
313
|
-
reg.registerPlugin(
|
|
314
|
-
'drivers',
|
|
315
|
-
'x',
|
|
316
|
-
// deliberately bad: no name
|
|
317
|
-
{ capabilities: { sessionResume: false, systemPrompt: false, outputFormat: false }, buildCommand: async () => ({ args: [] }) } as unknown as DriverPlugin,
|
|
318
|
-
),
|
|
319
|
-
).toThrow(/non-empty "name"/);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test('rejects plugin type identifiers that are not YAML-safe ids', () => {
|
|
323
|
-
const reg = new PluginRegistry();
|
|
324
|
-
expect(() =>
|
|
325
|
-
reg.registerPlugin(
|
|
326
|
-
'drivers',
|
|
327
|
-
'../evil',
|
|
328
|
-
makeDriver('evil', []),
|
|
329
|
-
),
|
|
330
|
-
).toThrow(/Plugin type .* must match/);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
test('middleware install hint uses singular middleware package name', () => {
|
|
334
|
-
const reg = new PluginRegistry();
|
|
335
|
-
expect(() => reg.getHandler('middlewares', 'audit')).toThrow(
|
|
336
|
-
/bun add @tagma\/middleware-audit/,
|
|
337
|
-
);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
test('rejects middleware without enhanceDoc', () => {
|
|
341
|
-
const reg = new PluginRegistry();
|
|
342
|
-
expect(() =>
|
|
343
|
-
reg.registerPlugin('middlewares', 'old', {
|
|
344
|
-
name: 'old',
|
|
345
|
-
async enhance(prompt: string) {
|
|
346
|
-
return prompt;
|
|
347
|
-
},
|
|
348
|
-
} as never),
|
|
349
|
-
).toThrow(/must export enhanceDoc/);
|
|
350
|
-
});
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
describe('runPipeline — options.registry isolation', () => {
|
|
354
|
-
test('concurrent runs with different registries see their own drivers', async () => {
|
|
355
|
-
const regA = new PluginRegistry();
|
|
356
|
-
const regB = new PluginRegistry();
|
|
357
|
-
const seenA: string[] = [];
|
|
358
|
-
const seenB: string[] = [];
|
|
359
|
-
|
|
360
|
-
bootstrapBuiltins(regA);
|
|
361
|
-
bootstrapBuiltins(regB);
|
|
362
|
-
|
|
363
|
-
regA.registerPlugin('drivers', 'mock', makeDriver('mockA', seenA));
|
|
364
|
-
regB.registerPlugin('drivers', 'mock', makeDriver('mockB', seenB));
|
|
365
|
-
|
|
366
|
-
// Command-only pipeline exercises the preflight path (which uses the
|
|
367
|
-
// registry) plus the run-loop path without requiring a real driver
|
|
368
|
-
// invocation. We verify isolation by asserting that preflight with a
|
|
369
|
-
// registry missing `mock` rejects, while the matching registry accepts.
|
|
370
|
-
const config: PipelineConfig = {
|
|
371
|
-
name: 'isolation-test',
|
|
372
|
-
tracks: [
|
|
373
|
-
{
|
|
374
|
-
id: 't',
|
|
375
|
-
name: 'T',
|
|
376
|
-
tasks: [{ id: 'only', name: 'only', command: 'echo hi' }],
|
|
377
|
-
},
|
|
378
|
-
],
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
const tmpA = mkdtempSync(join(tmpdir(), 'tagma-regA-'));
|
|
382
|
-
const tmpB = mkdtempSync(join(tmpdir(), 'tagma-regB-'));
|
|
383
|
-
try {
|
|
384
|
-
const [resA, resB] = await Promise.all([
|
|
385
|
-
runPipeline(config, tmpA, {
|
|
386
|
-
registry: regA,
|
|
387
|
-
runtime: fakeRuntime(),
|
|
388
|
-
skipPluginLoading: true,
|
|
389
|
-
}),
|
|
390
|
-
runPipeline(config, tmpB, {
|
|
391
|
-
registry: regB,
|
|
392
|
-
runtime: fakeRuntime(),
|
|
393
|
-
skipPluginLoading: true,
|
|
394
|
-
}),
|
|
395
|
-
]);
|
|
396
|
-
expect(resA.success).toBe(true);
|
|
397
|
-
expect(resB.success).toBe(true);
|
|
398
|
-
expect(resA.runId).not.toBe(resB.runId);
|
|
399
|
-
} finally {
|
|
400
|
-
rmSync(tmpA, { recursive: true, force: true });
|
|
401
|
-
rmSync(tmpB, { recursive: true, force: true });
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
test('preflight fails when referenced driver is missing from the passed registry', async () => {
|
|
406
|
-
const regNoOpencode = new PluginRegistry();
|
|
407
|
-
// Deliberately do NOT bootstrap builtins — opencode is not registered.
|
|
408
|
-
const config: PipelineConfig = {
|
|
409
|
-
name: 'preflight-miss',
|
|
410
|
-
tracks: [
|
|
411
|
-
{
|
|
412
|
-
id: 't',
|
|
413
|
-
name: 'T',
|
|
414
|
-
tasks: [{ id: 'x', name: 'x', prompt: 'hello' }],
|
|
415
|
-
},
|
|
416
|
-
],
|
|
417
|
-
};
|
|
418
|
-
const tmp = mkdtempSync(join(tmpdir(), 'tagma-miss-'));
|
|
419
|
-
try {
|
|
420
|
-
await expect(
|
|
421
|
-
runPipeline(config, tmp, { registry: regNoOpencode, skipPluginLoading: true }),
|
|
422
|
-
).rejects.toThrow(/driver "opencode" not registered/);
|
|
423
|
-
} finally {
|
|
424
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
|
|
428
|
-
test('runPipeline rejects missing explicit registry', async () => {
|
|
429
|
-
const config: PipelineConfig = {
|
|
430
|
-
name: 'missing-registry',
|
|
431
|
-
tracks: [
|
|
432
|
-
{
|
|
433
|
-
id: 't',
|
|
434
|
-
name: 'T',
|
|
435
|
-
tasks: [{ id: 'only', name: 'only', command: 'echo hi' }],
|
|
436
|
-
},
|
|
437
|
-
],
|
|
438
|
-
};
|
|
439
|
-
const tmp = mkdtempSync(join(tmpdir(), 'tagma-default-'));
|
|
440
|
-
try {
|
|
441
|
-
await expect(
|
|
442
|
-
runPipeline(config, tmp, { skipPluginLoading: true } as never),
|
|
443
|
-
).rejects.toThrow(/requires options\.registry/);
|
|
444
|
-
} finally {
|
|
445
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
});
|
package/src/plugins.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export { bootstrapBuiltins } from './bootstrap';
|
|
2
|
-
export {
|
|
3
|
-
PluginRegistry,
|
|
4
|
-
isValidPluginName,
|
|
5
|
-
PLUGIN_NAME_RE,
|
|
6
|
-
readPluginManifest,
|
|
7
|
-
} from '@tagma/core';
|
|
8
|
-
export type { RegisteredCapability, RegisterResult } from '@tagma/core';
|
|
9
|
-
export type {
|
|
10
|
-
CapabilityHandler,
|
|
11
|
-
PluginCategory,
|
|
12
|
-
PluginCapabilities,
|
|
13
|
-
PluginModule,
|
|
14
|
-
PluginManifest,
|
|
15
|
-
PluginSetupContext,
|
|
16
|
-
TagmaPlugin,
|
|
17
|
-
DriverPlugin,
|
|
18
|
-
TriggerPlugin,
|
|
19
|
-
CompletionPlugin,
|
|
20
|
-
MiddlewarePlugin,
|
|
21
|
-
} from './types';
|