@tagma/sdk 0.4.12 → 0.4.13

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 (88) hide show
  1. package/README.md +572 -566
  2. package/dist/adapters/websocket-approval.d.ts.map +1 -1
  3. package/dist/adapters/websocket-approval.js +3 -1
  4. package/dist/adapters/websocket-approval.js.map +1 -1
  5. package/dist/approval.d.ts.map +1 -1
  6. package/dist/approval.js.map +1 -1
  7. package/dist/completions/exit-code.d.ts.map +1 -1
  8. package/dist/completions/exit-code.js.map +1 -1
  9. package/dist/completions/file-exists.d.ts.map +1 -1
  10. package/dist/completions/file-exists.js.map +1 -1
  11. package/dist/completions/output-check.js +2 -7
  12. package/dist/completions/output-check.js.map +1 -1
  13. package/dist/config-ops.d.ts.map +1 -1
  14. package/dist/config-ops.js +24 -26
  15. package/dist/config-ops.js.map +1 -1
  16. package/dist/dag.d.ts.map +1 -1
  17. package/dist/dag.js +1 -1
  18. package/dist/dag.js.map +1 -1
  19. package/dist/drivers/claude-code.d.ts.map +1 -1
  20. package/dist/drivers/claude-code.js +10 -5
  21. package/dist/drivers/claude-code.js.map +1 -1
  22. package/dist/engine.d.ts.map +1 -1
  23. package/dist/engine.js +54 -27
  24. package/dist/engine.js.map +1 -1
  25. package/dist/hooks.d.ts.map +1 -1
  26. package/dist/hooks.js +1 -3
  27. package/dist/hooks.js.map +1 -1
  28. package/dist/logger.d.ts.map +1 -1
  29. package/dist/logger.js +4 -2
  30. package/dist/logger.js.map +1 -1
  31. package/dist/pipeline-runner.d.ts.map +1 -1
  32. package/dist/pipeline-runner.js +10 -4
  33. package/dist/pipeline-runner.js.map +1 -1
  34. package/dist/registry.d.ts +11 -1
  35. package/dist/registry.d.ts.map +1 -1
  36. package/dist/registry.js +28 -3
  37. package/dist/registry.js.map +1 -1
  38. package/dist/runner.d.ts.map +1 -1
  39. package/dist/runner.js +18 -13
  40. package/dist/runner.js.map +1 -1
  41. package/dist/schema.d.ts.map +1 -1
  42. package/dist/schema.js +14 -14
  43. package/dist/schema.js.map +1 -1
  44. package/dist/schema.test.js +5 -1
  45. package/dist/schema.test.js.map +1 -1
  46. package/dist/sdk.d.ts +2 -2
  47. package/dist/sdk.d.ts.map +1 -1
  48. package/dist/sdk.js +1 -1
  49. package/dist/sdk.js.map +1 -1
  50. package/dist/triggers/file.d.ts.map +1 -1
  51. package/dist/triggers/file.js +11 -4
  52. package/dist/triggers/file.js.map +1 -1
  53. package/dist/triggers/manual.d.ts.map +1 -1
  54. package/dist/triggers/manual.js +2 -1
  55. package/dist/triggers/manual.js.map +1 -1
  56. package/dist/utils.d.ts.map +1 -1
  57. package/dist/utils.js +13 -6
  58. package/dist/utils.js.map +1 -1
  59. package/dist/validate-raw.d.ts.map +1 -1
  60. package/dist/validate-raw.js +40 -11
  61. package/dist/validate-raw.js.map +1 -1
  62. package/package.json +2 -2
  63. package/scripts/preinstall.js +1 -1
  64. package/src/adapters/stdin-approval.ts +106 -106
  65. package/src/adapters/websocket-approval.ts +224 -220
  66. package/src/approval.ts +131 -125
  67. package/src/bootstrap.ts +37 -37
  68. package/src/completions/exit-code.ts +34 -30
  69. package/src/completions/file-exists.ts +66 -60
  70. package/src/completions/output-check.ts +86 -86
  71. package/src/config-ops.ts +307 -322
  72. package/src/dag.ts +234 -228
  73. package/src/drivers/claude-code.ts +250 -240
  74. package/src/engine.ts +1098 -935
  75. package/src/hooks.ts +187 -179
  76. package/src/logger.ts +182 -178
  77. package/src/middlewares/static-context.ts +45 -45
  78. package/src/pipeline-runner.ts +156 -150
  79. package/src/registry.ts +51 -23
  80. package/src/runner.ts +395 -397
  81. package/src/schema.test.ts +5 -1
  82. package/src/schema.ts +338 -328
  83. package/src/sdk.ts +91 -81
  84. package/src/triggers/file.ts +33 -14
  85. package/src/triggers/manual.ts +86 -81
  86. package/src/types.ts +18 -18
  87. package/src/utils.ts +202 -191
  88. package/src/validate-raw.ts +442 -409
package/src/sdk.ts CHANGED
@@ -1,82 +1,92 @@
1
- // ═══ tagma-sdk public API ═══
2
- //
3
- // This is the SDK entry point. Import from here, not from internal modules.
4
- // The CLI (src/index.ts in the CLI project) also imports from here.
5
-
6
- // ── Core engine ──
7
- export { runPipeline, TriggerBlockedError, TriggerTimeoutError } from './engine';
8
- export type { EngineResult, RunPipelineOptions, PipelineEvent } from './engine';
9
-
10
- // ── Pipeline runner (multi-pipeline lifecycle management) ──
11
- export { PipelineRunner } from './pipeline-runner';
12
- export type { PipelineRunnerStatus } from './pipeline-runner';
13
-
14
- // ── Raw config CRUD (visual editor / YAML sync) ──
15
- export {
16
- createEmptyPipeline,
17
- setPipelineField,
18
- upsertTrack,
19
- removeTrack,
20
- moveTrack,
21
- updateTrack,
22
- upsertTask,
23
- removeTask,
24
- moveTask,
25
- transferTask,
26
- } from './config-ops';
27
-
28
- // ── Raw config validation (real-time feedback) ──
29
- export { validateRaw } from './validate-raw';
30
- export type { ValidationError } from './validate-raw';
31
-
1
+ // ═══ tagma-sdk public API ═══
2
+ //
3
+ // This is the SDK entry point. Import from here, not from internal modules.
4
+ // The CLI (src/index.ts in the CLI project) also imports from here.
5
+
6
+ // ── Core engine ──
7
+ export { runPipeline, TriggerBlockedError, TriggerTimeoutError } from './engine';
8
+ export type { EngineResult, RunPipelineOptions, PipelineEvent } from './engine';
9
+
10
+ // ── Pipeline runner (multi-pipeline lifecycle management) ──
11
+ export { PipelineRunner } from './pipeline-runner';
12
+ export type { PipelineRunnerStatus } from './pipeline-runner';
13
+
14
+ // ── Raw config CRUD (visual editor / YAML sync) ──
15
+ export {
16
+ createEmptyPipeline,
17
+ setPipelineField,
18
+ upsertTrack,
19
+ removeTrack,
20
+ moveTrack,
21
+ updateTrack,
22
+ upsertTask,
23
+ removeTask,
24
+ moveTask,
25
+ transferTask,
26
+ } from './config-ops';
27
+
28
+ // ── Raw config validation (real-time feedback) ──
29
+ export { validateRaw } from './validate-raw';
30
+ export type { ValidationError } from './validate-raw';
31
+
32
32
  // ── Schema: parse / resolve / load / serialize / validate ──
33
- export { parseYaml, resolveConfig, loadPipeline, serializePipeline, deresolvePipeline, validateConfig } from './schema';
34
-
35
- // ── DAG ──
36
- export { buildDag, buildRawDag } from './dag';
37
- export type { DagNode, Dag, RawDagNode, RawDag } from './dag';
38
-
39
- // ── Plugin registry ──
40
- export { bootstrapBuiltins } from './bootstrap';
41
- export {
42
- loadPlugins,
43
- registerPlugin,
44
- unregisterPlugin,
45
- getHandler,
46
- hasHandler,
47
- listRegistered,
48
- isValidPluginName,
49
- PLUGIN_NAME_RE,
50
- readPluginManifest,
51
- } from './registry';
52
- export type { RegisterResult } from './registry';
53
-
54
- // ── Approval gateway ──
55
- export { InMemoryApprovalGateway } from './approval';
56
- export type {
57
- ApprovalGateway,
58
- ApprovalRequest,
59
- ApprovalDecision,
60
- ApprovalOutcome,
61
- ApprovalEvent,
62
- ApprovalListener,
63
- } from './approval';
64
-
65
- // ── Approval adapters ──
66
- export { attachStdinApprovalAdapter } from './adapters/stdin-approval';
67
- export type { StdinApprovalAdapter } from './adapters/stdin-approval';
68
- export { attachWebSocketApprovalAdapter } from './adapters/websocket-approval';
69
- export type { WebSocketApprovalAdapter, WebSocketApprovalAdapterOptions } from './adapters/websocket-approval';
70
-
71
- // ── Logger ──
72
- export { Logger, tailLines, clip } from './logger';
73
- export type { LogRecord, LogLevel, LogListener } from './logger';
74
-
75
- // ── Hook context types (useful for frontend display) ──
76
- export type { HookResult, PipelineInfo, TrackInfo, TaskInfo } from './hooks';
77
-
78
- // ── Utils (public subset) ──
79
- export { parseDuration, validatePath, generateRunId, nowISO, truncateForName } from './utils';
80
-
81
- // ── All types from @tagma/types + runtime constants ──
82
- export * from './types';
33
+ export {
34
+ parseYaml,
35
+ resolveConfig,
36
+ loadPipeline,
37
+ serializePipeline,
38
+ deresolvePipeline,
39
+ validateConfig,
40
+ } from './schema';
41
+
42
+ // ── DAG ──
43
+ export { buildDag, buildRawDag } from './dag';
44
+ export type { DagNode, Dag, RawDagNode, RawDag } from './dag';
45
+
46
+ // ── Plugin registry ──
47
+ export { bootstrapBuiltins } from './bootstrap';
48
+ export {
49
+ loadPlugins,
50
+ registerPlugin,
51
+ unregisterPlugin,
52
+ getHandler,
53
+ hasHandler,
54
+ listRegistered,
55
+ isValidPluginName,
56
+ PLUGIN_NAME_RE,
57
+ readPluginManifest,
58
+ } from './registry';
59
+ export type { RegisterResult } from './registry';
60
+
61
+ // ── Approval gateway ──
62
+ export { InMemoryApprovalGateway } from './approval';
63
+ export type {
64
+ ApprovalGateway,
65
+ ApprovalRequest,
66
+ ApprovalDecision,
67
+ ApprovalOutcome,
68
+ ApprovalEvent,
69
+ ApprovalListener,
70
+ } from './approval';
71
+
72
+ // ── Approval adapters ──
73
+ export { attachStdinApprovalAdapter } from './adapters/stdin-approval';
74
+ export type { StdinApprovalAdapter } from './adapters/stdin-approval';
75
+ export { attachWebSocketApprovalAdapter } from './adapters/websocket-approval';
76
+ export type {
77
+ WebSocketApprovalAdapter,
78
+ WebSocketApprovalAdapterOptions,
79
+ } from './adapters/websocket-approval';
80
+
81
+ // ── Logger ──
82
+ export { Logger, tailLines, clip } from './logger';
83
+ export type { LogRecord, LogLevel, LogListener } from './logger';
84
+
85
+ // ── Hook context types (useful for frontend display) ──
86
+ export type { HookResult, PipelineInfo, TrackInfo, TaskInfo } from './hooks';
87
+
88
+ // ── Utils (public subset) ──
89
+ export { parseDuration, validatePath, generateRunId, nowISO, truncateForName } from './utils';
90
+
91
+ // ── All types from @tagma/types + runtime constants ──
92
+ export * from './types';
@@ -57,7 +57,9 @@ export const FileTrigger: TriggerPlugin = {
57
57
  const dir = dirname(safePath);
58
58
  try {
59
59
  await mkdir(dir, { recursive: true });
60
- } catch { /* best effort — dir may already exist */ }
60
+ } catch {
61
+ /* best effort — dir may already exist */
62
+ }
61
63
 
62
64
  // Pass `cwd: dir` so chokidar resolves paths relative to the watched
63
65
  // directory. The 'add'/'change' events will then carry paths relative
@@ -74,7 +76,9 @@ export const FileTrigger: TriggerPlugin = {
74
76
  const cleanup = () => {
75
77
  if (settled) return;
76
78
  settled = true;
77
- watcher.close().catch(() => { /* ignore */ });
79
+ watcher.close().catch(() => {
80
+ /* ignore */
81
+ });
78
82
  if (timer) clearTimeout(timer);
79
83
  ctx.signal.removeEventListener('abort', onAbort);
80
84
  };
@@ -106,7 +110,11 @@ export const FileTrigger: TriggerPlugin = {
106
110
  watcher.on('error', (err: unknown) => {
107
111
  if (settled) return;
108
112
  cleanup();
109
- reject(new Error(`file trigger watch error: ${err instanceof Error ? err.message : String(err)}`));
113
+ reject(
114
+ new Error(
115
+ `file trigger watch error: ${err instanceof Error ? err.message : String(err)}`,
116
+ ),
117
+ );
110
118
  });
111
119
 
112
120
  // After the watcher finishes its initial scan, check if the file already exists.
@@ -114,24 +122,35 @@ export const FileTrigger: TriggerPlugin = {
114
122
  // and watcher startup, so we neither miss events nor double-resolve.
115
123
  watcher.on('ready', () => {
116
124
  if (settled) return;
117
- Bun.file(safePath).exists().then((exists) => {
118
- if (settled) return;
119
- if (exists) {
125
+ Bun.file(safePath)
126
+ .exists()
127
+ .then((exists) => {
128
+ if (settled) return;
129
+ if (exists) {
130
+ cleanup();
131
+ resolve_p({ path: safePath });
132
+ }
133
+ })
134
+ .catch((err: unknown) => {
135
+ if (settled) return;
120
136
  cleanup();
121
- resolve_p({ path: safePath });
122
- }
123
- }).catch((err: unknown) => {
124
- if (settled) return;
125
- cleanup();
126
- reject(new Error(`file trigger existence check failed: ${err instanceof Error ? err.message : String(err)}`));
127
- });
137
+ reject(
138
+ new Error(
139
+ `file trigger existence check failed: ${err instanceof Error ? err.message : String(err)}`,
140
+ ),
141
+ );
142
+ });
128
143
  });
129
144
 
130
145
  if (timeoutMs > 0) {
131
146
  timer = setTimeout(() => {
132
147
  if (settled) return;
133
148
  cleanup();
134
- reject(new TriggerTimeoutError(`file trigger timeout: ${filePath} did not appear within ${config.timeout}`));
149
+ reject(
150
+ new TriggerTimeoutError(
151
+ `file trigger timeout: ${filePath} did not appear within ${config.timeout}`,
152
+ ),
153
+ );
135
154
  }, timeoutMs);
136
155
  }
137
156
 
@@ -1,81 +1,86 @@
1
- import type { TriggerPlugin, TriggerContext } from '../types';
2
- import { parseDuration } from '../utils';
3
- import { TriggerBlockedError, TriggerTimeoutError } from '../engine';
4
-
5
- export const ManualTrigger: TriggerPlugin = {
6
- name: 'manual',
7
- schema: {
8
- description: 'Pause the task until a user approves via the approval gateway.',
9
- fields: {
10
- message: {
11
- type: 'string',
12
- description: 'Prompt shown to the approver. Defaults to a generic message if empty.',
13
- placeholder: 'Confirm deployment to production?',
14
- },
15
- timeout: {
16
- type: 'duration',
17
- description: 'Maximum wait time (e.g. 10m). Omit or 0 to wait indefinitely.',
18
- placeholder: '10m',
19
- },
20
- },
21
- },
22
-
23
- async watch(config: Record<string, unknown>, ctx: TriggerContext): Promise<unknown> {
24
- const message =
25
- (config.message as string | undefined) ?? `Manual confirmation required for task "${ctx.taskId}"`;
26
- const timeoutMs = config.timeout ? parseDuration(config.timeout as string) : 0;
27
- const metadata =
28
- config.metadata && typeof config.metadata === 'object'
29
- ? (config.metadata as Record<string, unknown>)
30
- : undefined;
31
-
32
- const decisionPromise = ctx.approvalGateway.request({
33
- taskId: ctx.taskId,
34
- trackId: ctx.trackId,
35
- message,
36
- timeoutMs,
37
- metadata,
38
- });
39
-
40
- // Wire AbortSignal → try to resolve this specific request as aborted.
41
- // We can't directly cancel via the gateway (no id yet at .request() call site),
42
- // so instead we race against an abort promise and let engine status logic
43
- // fall back to pipelineAborted skipped. abortAll() on gateway still runs
44
- // from engine shutdown path to clean up any truly-pending entries.
45
- const onAbort = () => {};
46
- const abortPromise = new Promise<never>((_, reject) => {
47
- if (ctx.signal.aborted) {
48
- reject(new Error('Pipeline aborted'));
49
- return;
50
- }
51
- const handler = () => reject(new Error('Pipeline aborted'));
52
- // Store reference so we can remove it after the race settles.
53
- (onAbort as { handler?: () => void }).handler = handler;
54
- ctx.signal.addEventListener('abort', handler, { once: true });
55
- });
56
-
57
- let decision: Awaited<typeof decisionPromise>;
58
- try {
59
- decision = await Promise.race([decisionPromise, abortPromise]);
60
- } finally {
61
- // Clean up the abort listener to prevent leaking on normal completion.
62
- const handler = (onAbort as { handler?: () => void }).handler;
63
- if (handler) ctx.signal.removeEventListener('abort', handler);
64
- }
65
-
66
- switch (decision.outcome) {
67
- case 'approved':
68
- return { confirmed: true, approvalId: decision.approvalId, actor: decision.actor };
69
- case 'rejected':
70
- // A7: Use typed error for proper classification in the engine.
71
- throw new TriggerBlockedError(
72
- `Manual trigger rejected by ${decision.actor ?? 'user'}` +
73
- (decision.reason ? `: ${decision.reason}` : ''),
74
- );
75
- case 'timeout':
76
- throw new TriggerTimeoutError(`Manual trigger timeout: ${decision.reason ?? 'no decision made'}`);
77
- case 'aborted':
78
- throw new TriggerBlockedError(`Manual trigger aborted: ${decision.reason ?? 'pipeline aborted'}`);
79
- }
80
- },
81
- };
1
+ import type { TriggerPlugin, TriggerContext } from '../types';
2
+ import { parseDuration } from '../utils';
3
+ import { TriggerBlockedError, TriggerTimeoutError } from '../engine';
4
+
5
+ export const ManualTrigger: TriggerPlugin = {
6
+ name: 'manual',
7
+ schema: {
8
+ description: 'Pause the task until a user approves via the approval gateway.',
9
+ fields: {
10
+ message: {
11
+ type: 'string',
12
+ description: 'Prompt shown to the approver. Defaults to a generic message if empty.',
13
+ placeholder: 'Confirm deployment to production?',
14
+ },
15
+ timeout: {
16
+ type: 'duration',
17
+ description: 'Maximum wait time (e.g. 10m). Omit or 0 to wait indefinitely.',
18
+ placeholder: '10m',
19
+ },
20
+ },
21
+ },
22
+
23
+ async watch(config: Record<string, unknown>, ctx: TriggerContext): Promise<unknown> {
24
+ const message =
25
+ (config.message as string | undefined) ??
26
+ `Manual confirmation required for task "${ctx.taskId}"`;
27
+ const timeoutMs = config.timeout ? parseDuration(config.timeout as string) : 0;
28
+ const metadata =
29
+ config.metadata && typeof config.metadata === 'object'
30
+ ? (config.metadata as Record<string, unknown>)
31
+ : undefined;
32
+
33
+ const decisionPromise = ctx.approvalGateway.request({
34
+ taskId: ctx.taskId,
35
+ trackId: ctx.trackId,
36
+ message,
37
+ timeoutMs,
38
+ metadata,
39
+ });
40
+
41
+ // Wire AbortSignal try to resolve this specific request as aborted.
42
+ // We can't directly cancel via the gateway (no id yet at .request() call site),
43
+ // so instead we race against an abort promise and let engine status logic
44
+ // fall back to pipelineAborted skipped. abortAll() on gateway still runs
45
+ // from engine shutdown path to clean up any truly-pending entries.
46
+ const onAbort = () => {};
47
+ const abortPromise = new Promise<never>((_, reject) => {
48
+ if (ctx.signal.aborted) {
49
+ reject(new Error('Pipeline aborted'));
50
+ return;
51
+ }
52
+ const handler = () => reject(new Error('Pipeline aborted'));
53
+ // Store reference so we can remove it after the race settles.
54
+ (onAbort as { handler?: () => void }).handler = handler;
55
+ ctx.signal.addEventListener('abort', handler, { once: true });
56
+ });
57
+
58
+ let decision: Awaited<typeof decisionPromise>;
59
+ try {
60
+ decision = await Promise.race([decisionPromise, abortPromise]);
61
+ } finally {
62
+ // Clean up the abort listener to prevent leaking on normal completion.
63
+ const handler = (onAbort as { handler?: () => void }).handler;
64
+ if (handler) ctx.signal.removeEventListener('abort', handler);
65
+ }
66
+
67
+ switch (decision.outcome) {
68
+ case 'approved':
69
+ return { confirmed: true, approvalId: decision.approvalId, actor: decision.actor };
70
+ case 'rejected':
71
+ // A7: Use typed error for proper classification in the engine.
72
+ throw new TriggerBlockedError(
73
+ `Manual trigger rejected by ${decision.actor ?? 'user'}` +
74
+ (decision.reason ? `: ${decision.reason}` : ''),
75
+ );
76
+ case 'timeout':
77
+ throw new TriggerTimeoutError(
78
+ `Manual trigger timeout: ${decision.reason ?? 'no decision made'}`,
79
+ );
80
+ case 'aborted':
81
+ throw new TriggerBlockedError(
82
+ `Manual trigger aborted: ${decision.reason ?? 'pipeline aborted'}`,
83
+ );
84
+ }
85
+ },
86
+ };
package/src/types.ts CHANGED
@@ -1,18 +1,18 @@
1
- // ═══ Engine-facing type surface ═══
2
- //
3
- // All type definitions live in the shared `@tagma/types` workspace package
4
- // so that plugins under plugins/* can depend on the same types without
5
- // reaching into the engine's internals. This file re-exports everything
6
- // and adds runtime-only values (constants) that plugins don't need.
7
-
8
- export * from '@tagma/types';
9
-
10
- import type { Permissions } from '@tagma/types';
11
-
12
- // ═══ Runtime Constants ═══
13
-
14
- export const DEFAULT_PERMISSIONS: Permissions = {
15
- read: true,
16
- write: false,
17
- execute: false,
18
- };
1
+ // ═══ Engine-facing type surface ═══
2
+ //
3
+ // All type definitions live in the shared `@tagma/types` workspace package
4
+ // so that plugins under plugins/* can depend on the same types without
5
+ // reaching into the engine's internals. This file re-exports everything
6
+ // and adds runtime-only values (constants) that plugins don't need.
7
+
8
+ export * from '@tagma/types';
9
+
10
+ import type { Permissions } from '@tagma/types';
11
+
12
+ // ═══ Runtime Constants ═══
13
+
14
+ export const DEFAULT_PERMISSIONS: Permissions = {
15
+ read: true,
16
+ write: false,
17
+ execute: false,
18
+ };