@tagma/sdk 0.7.1 → 0.7.4

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.
Files changed (108) hide show
  1. package/README.md +109 -48
  2. package/dist/adapters/stdin-approval.d.ts +1 -5
  3. package/dist/adapters/stdin-approval.d.ts.map +1 -1
  4. package/dist/adapters/stdin-approval.js +1 -89
  5. package/dist/adapters/stdin-approval.js.map +1 -1
  6. package/dist/adapters/websocket-approval.d.ts +1 -27
  7. package/dist/adapters/websocket-approval.d.ts.map +1 -1
  8. package/dist/adapters/websocket-approval.js +1 -146
  9. package/dist/adapters/websocket-approval.js.map +1 -1
  10. package/dist/approval.d.ts +2 -12
  11. package/dist/approval.d.ts.map +1 -1
  12. package/dist/approval.js +1 -90
  13. package/dist/approval.js.map +1 -1
  14. package/dist/bootstrap.d.ts +21 -1
  15. package/dist/bootstrap.d.ts.map +1 -1
  16. package/dist/bootstrap.js +21 -11
  17. package/dist/bootstrap.js.map +1 -1
  18. package/dist/core/run-context.d.ts +3 -0
  19. package/dist/core/run-context.d.ts.map +1 -1
  20. package/dist/core/run-context.js +2 -0
  21. package/dist/core/run-context.js.map +1 -1
  22. package/dist/core/task-executor.d.ts.map +1 -1
  23. package/dist/core/task-executor.js +24 -37
  24. package/dist/core/task-executor.js.map +1 -1
  25. package/dist/engine.d.ts +8 -53
  26. package/dist/engine.d.ts.map +1 -1
  27. package/dist/engine.js +7 -294
  28. package/dist/engine.js.map +1 -1
  29. package/dist/index.d.ts +5 -5
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +2 -3
  32. package/dist/index.js.map +1 -1
  33. package/dist/logger.d.ts +2 -60
  34. package/dist/logger.d.ts.map +1 -1
  35. package/dist/logger.js +1 -153
  36. package/dist/logger.js.map +1 -1
  37. package/dist/plugins.d.ts +3 -3
  38. package/dist/plugins.d.ts.map +1 -1
  39. package/dist/plugins.js +1 -1
  40. package/dist/plugins.js.map +1 -1
  41. package/dist/registry.d.ts +2 -60
  42. package/dist/registry.d.ts.map +1 -1
  43. package/dist/registry.js +1 -253
  44. package/dist/registry.js.map +1 -1
  45. package/dist/runner.d.ts +1 -35
  46. package/dist/runner.d.ts.map +1 -1
  47. package/dist/runner.js +1 -610
  48. package/dist/runner.js.map +1 -1
  49. package/dist/runtime/adapters/stdin-approval.d.ts +2 -0
  50. package/dist/runtime/adapters/stdin-approval.d.ts.map +1 -0
  51. package/dist/runtime/adapters/stdin-approval.js +2 -0
  52. package/dist/runtime/adapters/stdin-approval.js.map +1 -0
  53. package/dist/runtime/adapters/websocket-approval.d.ts +2 -0
  54. package/dist/runtime/adapters/websocket-approval.d.ts.map +1 -0
  55. package/dist/runtime/adapters/websocket-approval.js +2 -0
  56. package/dist/runtime/adapters/websocket-approval.js.map +1 -0
  57. package/dist/runtime/bun-process-runner.d.ts +2 -0
  58. package/dist/runtime/bun-process-runner.d.ts.map +1 -0
  59. package/dist/runtime/bun-process-runner.js +2 -0
  60. package/dist/runtime/bun-process-runner.js.map +1 -0
  61. package/dist/runtime.d.ts +3 -0
  62. package/dist/runtime.d.ts.map +1 -0
  63. package/dist/runtime.js +2 -0
  64. package/dist/runtime.js.map +1 -0
  65. package/dist/schema.d.ts.map +1 -1
  66. package/dist/schema.js +1 -7
  67. package/dist/schema.js.map +1 -1
  68. package/dist/tagma.d.ts +13 -4
  69. package/dist/tagma.d.ts.map +1 -1
  70. package/dist/tagma.js +7 -2
  71. package/dist/tagma.js.map +1 -1
  72. package/dist/triggers/file.d.ts.map +1 -1
  73. package/dist/triggers/file.js +74 -107
  74. package/dist/triggers/file.js.map +1 -1
  75. package/dist/validate-raw.d.ts.map +1 -1
  76. package/dist/validate-raw.js +1 -101
  77. package/dist/validate-raw.js.map +1 -1
  78. package/package.json +15 -4
  79. package/src/adapters/stdin-approval.ts +1 -106
  80. package/src/adapters/websocket-approval.ts +1 -224
  81. package/src/approval.ts +5 -127
  82. package/src/bootstrap.ts +24 -15
  83. package/src/core/run-context.test.ts +47 -0
  84. package/src/core/run-context.ts +4 -0
  85. package/src/core/task-executor.ts +28 -45
  86. package/src/engine-ports-mixed.test.ts +70 -44
  87. package/src/engine-ports.test.ts +77 -33
  88. package/src/engine.ts +21 -439
  89. package/src/index.ts +7 -4
  90. package/src/logger.ts +2 -182
  91. package/src/package-split.test.ts +15 -0
  92. package/src/pipeline-runner.test.ts +65 -12
  93. package/src/plugin-registry.test.ts +207 -4
  94. package/src/plugins.ts +6 -3
  95. package/src/registry.ts +7 -298
  96. package/src/runner.ts +1 -666
  97. package/src/runtime/adapters/stdin-approval.ts +1 -0
  98. package/src/runtime/adapters/websocket-approval.ts +1 -0
  99. package/src/runtime/bun-process-runner.ts +1 -0
  100. package/src/runtime-adapters.test.ts +10 -0
  101. package/src/runtime.ts +12 -0
  102. package/src/schema-ports.test.ts +23 -0
  103. package/src/schema.ts +1 -7
  104. package/src/tagma.test.ts +234 -1
  105. package/src/tagma.ts +24 -4
  106. package/src/triggers/file.test.ts +79 -0
  107. package/src/triggers/file.ts +85 -118
  108. package/src/validate-raw.ts +1 -117
@@ -0,0 +1,15 @@
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
+ });
@@ -1,25 +1,17 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { bootstrapBuiltins } from './bootstrap';
6
6
  import { PipelineRunner } from './pipeline-runner';
7
7
  import { PluginRegistry } from './registry';
8
- import type { PipelineConfig } from './types';
8
+ import type { PipelineConfig, TagmaRuntime, TaskResult } from './types';
9
9
 
10
10
  function makeDir(): string {
11
11
  return mkdtempSync(join(tmpdir(), 'tagma-pipeline-runner-'));
12
12
  }
13
13
 
14
14
  function bindingsPipeline(dir: string): PipelineConfig {
15
- const emit = join(dir, 'emit.js');
16
- writeFileSync(
17
- emit,
18
- 'process.stdout.write(JSON.stringify({ city: "Shanghai" }) + "\\n");\n',
19
- );
20
- const echo = join(dir, 'echo.js');
21
- writeFileSync(echo, 'process.stdout.write(process.argv[2] + "\\n");\n');
22
-
23
15
  return {
24
16
  name: 'runner-snapshot',
25
17
  tracks: [
@@ -30,14 +22,14 @@ function bindingsPipeline(dir: string): PipelineConfig {
30
22
  {
31
23
  id: 'up',
32
24
  name: 'up',
33
- command: `node "${emit}"`,
25
+ command: 'emit-city',
34
26
  outputs: { city: { type: 'string' } },
35
27
  },
36
28
  {
37
29
  id: 'down',
38
30
  name: 'down',
39
31
  depends_on: ['up'],
40
- command: `node "${echo}" "{{inputs.city}}"`,
32
+ command: 'echo-city "{{inputs.city}}"',
41
33
  inputs: { city: { from: 't.up.outputs.city', type: 'string', required: true } },
42
34
  },
43
35
  ],
@@ -46,11 +38,72 @@ function bindingsPipeline(dir: string): PipelineConfig {
46
38
  };
47
39
  }
48
40
 
41
+ function taskResult(stdout: string): TaskResult {
42
+ return {
43
+ exitCode: 0,
44
+ stdout,
45
+ stderr: '',
46
+ stdoutPath: null,
47
+ stderrPath: null,
48
+ stdoutBytes: stdout.length,
49
+ stderrBytes: 0,
50
+ durationMs: 1,
51
+ sessionId: null,
52
+ normalizedOutput: null,
53
+ failureKind: null,
54
+ };
55
+ }
56
+
57
+ function fakeRuntime(): TagmaRuntime {
58
+ return {
59
+ async runCommand(command) {
60
+ return command.startsWith('emit-city')
61
+ ? taskResult('{"city":"Shanghai"}\n')
62
+ : taskResult('Shanghai\n');
63
+ },
64
+ async runSpawn() {
65
+ throw new Error('runSpawn should not be called');
66
+ },
67
+ async ensureDir() {
68
+ /* no-op */
69
+ },
70
+ async fileExists() {
71
+ return false;
72
+ },
73
+ async *watch() {
74
+ /* no-op */
75
+ },
76
+ logStore: {
77
+ openRunLog({ runId }) {
78
+ return {
79
+ path: `mem://${runId}/pipeline.log`,
80
+ dir: `mem://${runId}`,
81
+ append() {
82
+ /* memory sink */
83
+ },
84
+ close() {
85
+ /* memory sink */
86
+ },
87
+ };
88
+ },
89
+ taskOutputPath({ runId, taskId, stream }) {
90
+ return `mem://${runId}/${taskId}.${stream}`;
91
+ },
92
+ logsDir() {
93
+ return 'mem://logs';
94
+ },
95
+ },
96
+ now: () => new Date('2026-04-26T00:00:00.000Z'),
97
+ sleep: () => Promise.resolve(),
98
+ };
99
+ }
100
+
49
101
  async function run(config: PipelineConfig, dir: string): Promise<PipelineRunner> {
50
102
  const registry = new PluginRegistry();
51
103
  bootstrapBuiltins(registry);
52
104
  const runner = new PipelineRunner(config, dir, {
53
105
  registry,
106
+ runtime: fakeRuntime(),
54
107
  skipPluginLoading: true,
55
108
  });
56
109
 
@@ -2,10 +2,11 @@ import { describe, expect, test } from 'bun:test';
2
2
  import { PluginRegistry } from './registry';
3
3
  import { bootstrapBuiltins } from './bootstrap';
4
4
  import { runPipeline } from './engine';
5
- import type { DriverPlugin, TriggerPlugin, PipelineConfig } from './types';
6
- import { mkdtempSync, rmSync } from 'node:fs';
5
+ import type { DriverPlugin, TriggerPlugin, PipelineConfig, TagmaRuntime, TaskResult } from './types';
6
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { join } from 'node:path';
9
+ import type { TagmaPlugin } from './types';
9
10
 
10
11
  function makeDriver(name: string, marker: string[]): DriverPlugin {
11
12
  return {
@@ -27,6 +28,64 @@ function makeTrigger(name: string, marker: string[]): TriggerPlugin {
27
28
  };
28
29
  }
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
+
30
89
  describe('PluginRegistry — instance isolation', () => {
31
90
  test('two registries do not share drivers registered under the same type', () => {
32
91
  const regA = new PluginRegistry();
@@ -100,6 +159,130 @@ describe('PluginRegistry — instance isolation', () => {
100
159
  });
101
160
  });
102
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
+
103
286
  describe('PluginRegistry — validation', () => {
104
287
  test('rejects unknown category', () => {
105
288
  const reg = new PluginRegistry();
@@ -153,6 +336,18 @@ describe('PluginRegistry — validation', () => {
153
336
  /bun add @tagma\/middleware-audit/,
154
337
  );
155
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
+ });
156
351
  });
157
352
 
158
353
  describe('runPipeline — options.registry isolation', () => {
@@ -187,8 +382,16 @@ describe('runPipeline — options.registry isolation', () => {
187
382
  const tmpB = mkdtempSync(join(tmpdir(), 'tagma-regB-'));
188
383
  try {
189
384
  const [resA, resB] = await Promise.all([
190
- runPipeline(config, tmpA, { registry: regA, skipPluginLoading: true }),
191
- runPipeline(config, tmpB, { registry: regB, skipPluginLoading: true }),
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
+ }),
192
395
  ]);
193
396
  expect(resA.success).toBe(true);
194
397
  expect(resB.success).toBe(true);
package/src/plugins.ts CHANGED
@@ -4,15 +4,18 @@ export {
4
4
  isValidPluginName,
5
5
  PLUGIN_NAME_RE,
6
6
  readPluginManifest,
7
- } from './registry';
8
- export type { RegisterResult } from './registry';
7
+ } from '@tagma/core';
8
+ export type { RegisteredCapability, RegisterResult } from '@tagma/core';
9
9
  export type {
10
+ CapabilityHandler,
10
11
  PluginCategory,
12
+ PluginCapabilities,
11
13
  PluginModule,
12
14
  PluginManifest,
15
+ PluginSetupContext,
16
+ TagmaPlugin,
13
17
  DriverPlugin,
14
18
  TriggerPlugin,
15
19
  CompletionPlugin,
16
20
  MiddlewarePlugin,
17
21
  } from './types';
18
-