@tagma/sdk 0.7.3 → 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.
Files changed (230) hide show
  1. package/README.md +85 -57
  2. package/dist/approval.d.ts +2 -12
  3. package/dist/approval.d.ts.map +1 -1
  4. package/dist/approval.js +1 -90
  5. package/dist/approval.js.map +1 -1
  6. package/dist/bootstrap.d.ts +1 -1
  7. package/dist/bootstrap.d.ts.map +1 -1
  8. package/dist/completions/file-exists.js +1 -1
  9. package/dist/completions/file-exists.js.map +1 -1
  10. package/dist/completions/output-check.d.ts.map +1 -1
  11. package/dist/completions/output-check.js +17 -4
  12. package/dist/completions/output-check.js.map +1 -1
  13. package/dist/config.d.ts +4 -4
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +2 -2
  16. package/dist/config.js.map +1 -1
  17. package/dist/dataflow.d.ts +3 -0
  18. package/dist/dataflow.d.ts.map +1 -0
  19. package/dist/dataflow.js +2 -0
  20. package/dist/dataflow.js.map +1 -0
  21. package/dist/drivers/opencode.d.ts.map +1 -1
  22. package/dist/drivers/opencode.js +23 -71
  23. package/dist/drivers/opencode.js.map +1 -1
  24. package/dist/engine.d.ts +5 -56
  25. package/dist/engine.d.ts.map +1 -1
  26. package/dist/engine.js +7 -297
  27. package/dist/engine.js.map +1 -1
  28. package/dist/index.d.ts +4 -6
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +2 -4
  31. package/dist/index.js.map +1 -1
  32. package/dist/logger.d.ts +2 -60
  33. package/dist/logger.d.ts.map +1 -1
  34. package/dist/logger.js +1 -153
  35. package/dist/logger.js.map +1 -1
  36. package/dist/middlewares/static-context.d.ts.map +1 -1
  37. package/dist/middlewares/static-context.js +1 -2
  38. package/dist/middlewares/static-context.js.map +1 -1
  39. package/dist/pipeline-runner.d.ts.map +1 -1
  40. package/dist/pipeline-runner.js +2 -2
  41. package/dist/pipeline-runner.js.map +1 -1
  42. package/dist/plugins.d.ts +2 -2
  43. package/dist/plugins.d.ts.map +1 -1
  44. package/dist/plugins.js +1 -1
  45. package/dist/plugins.js.map +1 -1
  46. package/dist/runner.d.ts +1 -35
  47. package/dist/runner.d.ts.map +1 -1
  48. package/dist/runner.js +1 -610
  49. package/dist/runner.js.map +1 -1
  50. package/dist/runtime/adapters/stdin-approval.d.ts +2 -0
  51. package/dist/runtime/adapters/stdin-approval.d.ts.map +1 -0
  52. package/dist/runtime/adapters/stdin-approval.js +2 -0
  53. package/dist/runtime/adapters/stdin-approval.js.map +1 -0
  54. package/dist/runtime/adapters/websocket-approval.d.ts +2 -0
  55. package/dist/runtime/adapters/websocket-approval.d.ts.map +1 -0
  56. package/dist/runtime/adapters/websocket-approval.js +2 -0
  57. package/dist/runtime/adapters/websocket-approval.js.map +1 -0
  58. package/dist/runtime/bun-process-runner.d.ts +2 -0
  59. package/dist/runtime/bun-process-runner.d.ts.map +1 -0
  60. package/dist/runtime/bun-process-runner.js +2 -0
  61. package/dist/runtime/bun-process-runner.js.map +1 -0
  62. package/dist/runtime.d.ts +2 -8
  63. package/dist/runtime.d.ts.map +1 -1
  64. package/dist/runtime.js +1 -7
  65. package/dist/runtime.js.map +1 -1
  66. package/dist/schema.d.ts.map +1 -1
  67. package/dist/schema.js +3 -4
  68. package/dist/schema.js.map +1 -1
  69. package/dist/tagma.d.ts +3 -4
  70. package/dist/tagma.d.ts.map +1 -1
  71. package/dist/tagma.js +2 -3
  72. package/dist/tagma.js.map +1 -1
  73. package/dist/triggers/file.d.ts.map +1 -1
  74. package/dist/triggers/file.js +74 -108
  75. package/dist/triggers/file.js.map +1 -1
  76. package/dist/triggers/manual.d.ts.map +1 -1
  77. package/dist/triggers/manual.js +1 -2
  78. package/dist/triggers/manual.js.map +1 -1
  79. package/dist/types.d.ts +1 -2
  80. package/dist/types.d.ts.map +1 -1
  81. package/dist/types.js +1 -12
  82. package/dist/types.js.map +1 -1
  83. package/dist/utils-api.d.ts +1 -1
  84. package/dist/utils-api.d.ts.map +1 -1
  85. package/dist/utils-api.js +1 -1
  86. package/dist/utils-api.js.map +1 -1
  87. package/dist/validate-raw.d.ts.map +1 -1
  88. package/dist/validate-raw.js +5 -12
  89. package/dist/validate-raw.js.map +1 -1
  90. package/package.json +20 -22
  91. package/dist/adapters/stdin-approval.d.ts +0 -6
  92. package/dist/adapters/stdin-approval.d.ts.map +0 -1
  93. package/dist/adapters/stdin-approval.js +0 -90
  94. package/dist/adapters/stdin-approval.js.map +0 -1
  95. package/dist/adapters/websocket-approval.d.ts +0 -28
  96. package/dist/adapters/websocket-approval.d.ts.map +0 -1
  97. package/dist/adapters/websocket-approval.js +0 -147
  98. package/dist/adapters/websocket-approval.js.map +0 -1
  99. package/dist/core/dataflow.d.ts +0 -23
  100. package/dist/core/dataflow.d.ts.map +0 -1
  101. package/dist/core/dataflow.js +0 -99
  102. package/dist/core/dataflow.js.map +0 -1
  103. package/dist/core/log-prune.d.ts +0 -16
  104. package/dist/core/log-prune.d.ts.map +0 -1
  105. package/dist/core/log-prune.js +0 -34
  106. package/dist/core/log-prune.js.map +0 -1
  107. package/dist/core/preflight.d.ts +0 -13
  108. package/dist/core/preflight.d.ts.map +0 -1
  109. package/dist/core/preflight.js +0 -61
  110. package/dist/core/preflight.js.map +0 -1
  111. package/dist/core/run-context.d.ts +0 -55
  112. package/dist/core/run-context.d.ts.map +0 -1
  113. package/dist/core/run-context.js +0 -158
  114. package/dist/core/run-context.js.map +0 -1
  115. package/dist/core/run-state.d.ts +0 -25
  116. package/dist/core/run-state.d.ts.map +0 -1
  117. package/dist/core/run-state.js +0 -93
  118. package/dist/core/run-state.js.map +0 -1
  119. package/dist/core/scheduler.d.ts +0 -13
  120. package/dist/core/scheduler.d.ts.map +0 -1
  121. package/dist/core/scheduler.js +0 -35
  122. package/dist/core/scheduler.js.map +0 -1
  123. package/dist/core/task-executor.d.ts +0 -13
  124. package/dist/core/task-executor.d.ts.map +0 -1
  125. package/dist/core/task-executor.js +0 -601
  126. package/dist/core/task-executor.js.map +0 -1
  127. package/dist/core/trigger-errors.d.ts +0 -9
  128. package/dist/core/trigger-errors.d.ts.map +0 -1
  129. package/dist/core/trigger-errors.js +0 -15
  130. package/dist/core/trigger-errors.js.map +0 -1
  131. package/dist/dag.d.ts +0 -45
  132. package/dist/dag.d.ts.map +0 -1
  133. package/dist/dag.js +0 -177
  134. package/dist/dag.js.map +0 -1
  135. package/dist/hooks.d.ts +0 -73
  136. package/dist/hooks.d.ts.map +0 -1
  137. package/dist/hooks.js +0 -106
  138. package/dist/hooks.js.map +0 -1
  139. package/dist/pipeline-definition.d.ts +0 -3
  140. package/dist/pipeline-definition.d.ts.map +0 -1
  141. package/dist/pipeline-definition.js +0 -4
  142. package/dist/pipeline-definition.js.map +0 -1
  143. package/dist/ports.d.ts +0 -196
  144. package/dist/ports.d.ts.map +0 -1
  145. package/dist/ports.js +0 -688
  146. package/dist/ports.js.map +0 -1
  147. package/dist/prompt-doc.d.ts +0 -70
  148. package/dist/prompt-doc.d.ts.map +0 -1
  149. package/dist/prompt-doc.js +0 -154
  150. package/dist/prompt-doc.js.map +0 -1
  151. package/dist/registry.d.ts +0 -67
  152. package/dist/registry.d.ts.map +0 -1
  153. package/dist/registry.js +0 -293
  154. package/dist/registry.js.map +0 -1
  155. package/dist/task-ref.d.ts +0 -55
  156. package/dist/task-ref.d.ts.map +0 -1
  157. package/dist/task-ref.js +0 -103
  158. package/dist/task-ref.js.map +0 -1
  159. package/dist/utils.d.ts +0 -13
  160. package/dist/utils.d.ts.map +0 -1
  161. package/dist/utils.js +0 -177
  162. package/dist/utils.js.map +0 -1
  163. package/src/adapters/stdin-approval.ts +0 -106
  164. package/src/adapters/websocket-approval.ts +0 -224
  165. package/src/approval.ts +0 -131
  166. package/src/bootstrap.ts +0 -55
  167. package/src/completions/exit-code.ts +0 -34
  168. package/src/completions/file-exists.ts +0 -66
  169. package/src/completions/output-check.test.ts +0 -50
  170. package/src/completions/output-check.ts +0 -92
  171. package/src/config-ops.test.ts +0 -70
  172. package/src/config-ops.ts +0 -328
  173. package/src/config.ts +0 -26
  174. package/src/core/dataflow.test.ts +0 -166
  175. package/src/core/dataflow.ts +0 -161
  176. package/src/core/log-prune.test.ts +0 -58
  177. package/src/core/log-prune.ts +0 -43
  178. package/src/core/preflight.test.ts +0 -49
  179. package/src/core/preflight.ts +0 -89
  180. package/src/core/run-context.test.ts +0 -256
  181. package/src/core/run-context.ts +0 -211
  182. package/src/core/run-state.test.ts +0 -98
  183. package/src/core/run-state.ts +0 -122
  184. package/src/core/scheduler.test.ts +0 -83
  185. package/src/core/scheduler.ts +0 -42
  186. package/src/core/task-executor.ts +0 -743
  187. package/src/core/trigger-errors.ts +0 -15
  188. package/src/dag.test.ts +0 -56
  189. package/src/dag.ts +0 -245
  190. package/src/drivers/opencode.ts +0 -410
  191. package/src/engine-ports-mixed.test.ts +0 -156
  192. package/src/engine-ports.test.ts +0 -166
  193. package/src/engine-task-type.test.ts +0 -56
  194. package/src/engine.ts +0 -458
  195. package/src/hooks.ts +0 -193
  196. package/src/index.ts +0 -33
  197. package/src/logger.ts +0 -182
  198. package/src/middlewares/static-context.ts +0 -49
  199. package/src/pipeline-definition.ts +0 -5
  200. package/src/pipeline-runner.test.ts +0 -91
  201. package/src/pipeline-runner.ts +0 -194
  202. package/src/plugin-registry.test.ts +0 -382
  203. package/src/plugins.ts +0 -21
  204. package/src/ports.test.ts +0 -678
  205. package/src/ports.ts +0 -925
  206. package/src/prompt-doc.test.ts +0 -174
  207. package/src/prompt-doc.ts +0 -169
  208. package/src/registry.ts +0 -353
  209. package/src/runner.test.ts +0 -142
  210. package/src/runner.ts +0 -666
  211. package/src/runtime.ts +0 -20
  212. package/src/schema-ports.test.ts +0 -172
  213. package/src/schema.test.ts +0 -213
  214. package/src/schema.ts +0 -379
  215. package/src/tagma.test.ts +0 -155
  216. package/src/tagma.ts +0 -62
  217. package/src/task-ref.test.ts +0 -401
  218. package/src/task-ref.ts +0 -121
  219. package/src/triggers/file.ts +0 -164
  220. package/src/triggers/manual.ts +0 -86
  221. package/src/types.ts +0 -18
  222. package/src/utils-api.ts +0 -8
  223. package/src/utils.test.ts +0 -28
  224. package/src/utils.ts +0 -203
  225. package/src/validate-raw-plugin-types.test.ts +0 -60
  226. package/src/validate-raw-ports.test.ts +0 -136
  227. package/src/validate-raw.ts +0 -852
  228. package/src/yaml-compiler.test.ts +0 -108
  229. package/src/yaml-compiler.ts +0 -110
  230. package/src/yaml.ts +0 -11
package/src/schema.ts DELETED
@@ -1,379 +0,0 @@
1
- import yaml from 'js-yaml';
2
- import { relative } from 'path';
3
- import type {
4
- PipelineConfig,
5
- RawPipelineConfig,
6
- RawTrackConfig,
7
- RawTaskConfig,
8
- TrackConfig,
9
- TaskConfig,
10
- Permissions,
11
- CompletionConfig,
12
- } from './types';
13
- import { truncateForName, validatePath } from './utils';
14
- import { DEFAULT_PERMISSIONS } from './types';
15
- import { buildDag } from './dag';
16
-
17
- // ═══ YAML Parsing ═══
18
-
19
- export function parseYaml(content: string): RawPipelineConfig {
20
- const doc = yaml.load(content) as { pipeline?: unknown };
21
- if (!doc?.pipeline) {
22
- throw new Error('YAML must contain a top-level "pipeline" key');
23
- }
24
- if (typeof doc.pipeline !== 'object' || Array.isArray(doc.pipeline)) {
25
- throw new Error('pipeline must be an object');
26
- }
27
- const p = doc.pipeline as RawPipelineConfig;
28
- if (!p.name) throw new Error('pipeline.name is required');
29
- if (!Array.isArray(p.tracks)) throw new Error('pipeline.tracks must be an array');
30
- if (p.tracks.length === 0) throw new Error('pipeline.tracks must be non-empty');
31
-
32
- // D14: Detect duplicate track IDs before per-track validation so the error
33
- // message is clear ("Duplicate track id") rather than a confusing DAG error
34
- // ("Duplicate task ID: track.task_x") that only surfaces at runPipeline time.
35
- const seenTrackIds = new Set<string>();
36
- for (const track of p.tracks) {
37
- if (track.id) {
38
- if (seenTrackIds.has(track.id)) {
39
- throw new Error(`Duplicate track id "${track.id}": each track must have a unique id.`);
40
- }
41
- seenTrackIds.add(track.id);
42
- }
43
- }
44
-
45
- for (const track of p.tracks) {
46
- validateRawTrack(track);
47
- }
48
- return p;
49
- }
50
-
51
- // D8: IDs must start with a letter or underscore and contain only
52
- // alphanumerics, underscores, and hyphens. Dots are forbidden because
53
- // the engine uses "trackId.taskId" as the qualified separator — a dot in
54
- // either part creates an ambiguous qualified ID and breaks resolveRef.
55
- const ID_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
56
-
57
- function assertValidId(id: string, label: string): void {
58
- if (!ID_RE.test(id)) {
59
- throw new Error(
60
- `${label}: id "${id}" is invalid. IDs must match /^[A-Za-z_][A-Za-z0-9_-]*$/ ` +
61
- `(letters, digits, underscores, hyphens; no dots or spaces; must start with letter/underscore).`,
62
- );
63
- }
64
- }
65
-
66
- function validateRawTrack(track: RawTrackConfig): void {
67
- if (!track || typeof track !== 'object' || Array.isArray(track)) {
68
- throw new Error('track must be an object');
69
- }
70
- if (!track.id) throw new Error('track.id is required');
71
- assertValidId(track.id, `track "${track.id}"`);
72
- if (!track.name) throw new Error(`track "${track.id}": name is required`);
73
- if (!Array.isArray(track.tasks)) {
74
- throw new Error(`track "${track.id}": tasks must be an array`);
75
- }
76
- if (track.tasks.length === 0) {
77
- throw new Error(`track "${track.id}": tasks must be non-empty`);
78
- }
79
- for (const task of track.tasks) {
80
- validateRawTask(task, track.id);
81
- }
82
- }
83
-
84
- function validateRawTask(task: RawTaskConfig, trackId: string): void {
85
- if (!task || typeof task !== 'object' || Array.isArray(task)) {
86
- throw new Error(`track "${trackId}": task must be an object`);
87
- }
88
- if (!task.id) throw new Error(`track "${trackId}": task.id is required`);
89
- assertValidId(task.id, `task "${task.id}" in track "${trackId}"`);
90
-
91
- const hasPromptKey = typeof task.prompt === 'string';
92
- const hasCommandKey = typeof task.command === 'string';
93
- if (!hasPromptKey && !hasCommandKey) {
94
- throw new Error(`task "${task.id}": must have either "prompt" or "command"`);
95
- }
96
- if (hasPromptKey && hasCommandKey) {
97
- throw new Error(`task "${task.id}": cannot have both "prompt" and "command"`);
98
- }
99
- // Empty-content tasks (e.g. `prompt: ''`) are allowed at parse time and
100
- // flagged as hard validation errors by validate-raw.ts.
101
- }
102
-
103
- // ═══ Config Inheritance Resolution ═══
104
-
105
- export function resolveConfig(raw: RawPipelineConfig, workDir: string): PipelineConfig {
106
- // Build qualified ID set for resolving bare continue_from references
107
- const allQualifiedIds = new Set<string>();
108
- for (const t of raw.tracks) {
109
- if (!t.id) continue;
110
- for (const tk of t.tasks ?? []) {
111
- if (tk.id) allQualifiedIds.add(`${t.id}.${tk.id}`);
112
- }
113
- }
114
-
115
- function qualifyContinueFrom(ref: string, trackId: string): string {
116
- // Already qualified
117
- if (allQualifiedIds.has(ref)) return ref;
118
- // Same-track shorthand
119
- const sameTrack = `${trackId}.${ref}`;
120
- if (allQualifiedIds.has(sameTrack)) return sameTrack;
121
- // Cross-track bare lookup — must be unambiguous
122
- let match: string | null = null;
123
- for (const qid of allQualifiedIds) {
124
- if (qid.endsWith(`.${ref}`)) {
125
- if (match !== null) return ref; // ambiguous — leave as-is
126
- match = qid;
127
- }
128
- }
129
- return match ?? ref; // not found — leave as-is (validated elsewhere)
130
- }
131
-
132
- const tracks: TrackConfig[] = raw.tracks.map((rawTrack) => {
133
- const trackDriver = rawTrack.driver ?? raw.driver;
134
- // validatePath enforces no .. traversal and no absolute paths escaping workDir.
135
- const trackCwd = rawTrack.cwd ? validatePath(rawTrack.cwd, workDir) : workDir;
136
-
137
- const tasks: TaskConfig[] = rawTrack.tasks.map((rawTask) => {
138
- const name =
139
- rawTask.name ??
140
- (rawTask.prompt ? truncateForName(rawTask.prompt) : (rawTask.command ?? rawTask.id));
141
-
142
- return {
143
- id: rawTask.id,
144
- name,
145
- prompt: rawTask.prompt,
146
- command: rawTask.command,
147
- depends_on: rawTask.depends_on,
148
- trigger: rawTask.trigger,
149
- continue_from: rawTask.continue_from
150
- ? qualifyContinueFrom(rawTask.continue_from, rawTrack.id)
151
- : undefined,
152
- // Inheritance: Task > Track > Pipeline
153
- model: rawTask.model ?? rawTrack.model ?? raw.model,
154
- reasoning_effort:
155
- rawTask.reasoning_effort ?? rawTrack.reasoning_effort ?? raw.reasoning_effort,
156
- permissions: rawTask.permissions ?? rawTrack.permissions ?? raw.permissions ?? DEFAULT_PERMISSIONS,
157
- driver: rawTask.driver ?? trackDriver ?? 'opencode',
158
- timeout: rawTask.timeout,
159
- // Middleware: Task-level overrides Track (including [] to disable)
160
- middlewares: rawTask.middlewares !== undefined ? rawTask.middlewares : rawTrack.middlewares,
161
- completion: rawTask.completion,
162
- agent_profile: rawTask.agent_profile ?? rawTrack.agent_profile,
163
- cwd: rawTask.cwd ? validatePath(rawTask.cwd, workDir) : trackCwd,
164
- // Unified bindings have no inheritance; they describe
165
- // per-task data flow, not cross-task defaults.
166
- inputs: rawTask.inputs,
167
- outputs: rawTask.outputs,
168
- };
169
- });
170
-
171
- return {
172
- id: rawTrack.id,
173
- name: rawTrack.name,
174
- color: rawTrack.color,
175
- agent_profile: rawTrack.agent_profile,
176
- model: rawTrack.model ?? raw.model,
177
- reasoning_effort: rawTrack.reasoning_effort ?? raw.reasoning_effort,
178
- permissions: rawTrack.permissions ?? raw.permissions ?? DEFAULT_PERMISSIONS,
179
- driver: trackDriver ?? 'opencode',
180
- cwd: trackCwd,
181
- middlewares: rawTrack.middlewares,
182
- on_failure: rawTrack.on_failure ?? 'skip_downstream',
183
- tasks,
184
- };
185
- });
186
-
187
- return {
188
- name: raw.name,
189
- driver: raw.driver,
190
- model: raw.model,
191
- reasoning_effort: raw.reasoning_effort,
192
- permissions: raw.permissions,
193
- timeout: raw.timeout,
194
- plugins: raw.plugins,
195
- hooks: raw.hooks,
196
- tracks,
197
- };
198
- }
199
-
200
- // Field-by-field permissions comparison — avoids relying on JSON.stringify key order.
201
- function permissionsEqual(a: Permissions | undefined, b: Permissions | undefined): boolean {
202
- if (a === b) return true;
203
- if (!a || !b) return false;
204
- return a.read === b.read && a.write === b.write && a.execute === b.execute;
205
- }
206
-
207
- function isDefaultExitCodeCompletion(completion: CompletionConfig | undefined): boolean {
208
- if (!completion || completion.type !== 'exit_code') return false;
209
- const {
210
- type: _type,
211
- expect,
212
- ...rest
213
- } = completion as CompletionConfig & {
214
- expect?: unknown;
215
- };
216
- if (Object.keys(rest).length > 0) return false;
217
- return expect === undefined || expect === 0;
218
- }
219
-
220
- function stripDefaultTaskCompletion<T extends { completion?: CompletionConfig }>(task: T): T {
221
- if (!isDefaultExitCodeCompletion(task.completion)) return task;
222
- const { completion: _completion, ...rest } = task;
223
- return rest as T;
224
- }
225
-
226
- // `continue_from` is a prompt-only field — it tells AI drivers with
227
- // session-resume capability to thread off an upstream prompt task's context.
228
- // A command task runs as a plain shell subprocess and has no session to
229
- // resume, so any `continue_from` on a command task is dead weight. Drop it
230
- // at serialization time so YAML on disk never carries the stale field after
231
- // a user toggles task mode from prompt → command. The tagma-yaml agent's
232
- // system prompt (apps/editor/server/opencode-seed.ts) documents this
233
- // stripping — keep them in sync.
234
- function stripPromptOnlyFieldsFromCommandTask<
235
- T extends { command?: string; continue_from?: string },
236
- >(task: T): T {
237
- if (task.command === undefined || task.continue_from === undefined) return task;
238
- const { continue_from: _cf, ...rest } = task;
239
- return rest as T;
240
- }
241
-
242
- function stripForSerialization<T extends PipelineConfig | RawPipelineConfig>(
243
- config: T,
244
- ): T {
245
- return {
246
- ...config,
247
- tracks: config.tracks.map((track) => ({
248
- ...track,
249
- tasks: track.tasks.map((task) =>
250
- stripPromptOnlyFieldsFromCommandTask(stripDefaultTaskCompletion(task)),
251
- ),
252
- })),
253
- } as T;
254
- }
255
-
256
- // ═══ YAML Serialization ═══
257
-
258
- /**
259
- * Serialize a pipeline config back to YAML string.
260
- * Wraps the config under the top-level `pipeline` key as expected by parseYaml.
261
- */
262
- export function serializePipeline(config: PipelineConfig | RawPipelineConfig): string {
263
- return yaml.dump(
264
- { pipeline: stripForSerialization(config) },
265
- { lineWidth: 120, indent: 2 },
266
- );
267
- }
268
-
269
- /**
270
- * Convert a resolved PipelineConfig back to a RawPipelineConfig for serialization.
271
- * Strips injected defaults and converts absolute cwd paths back to relative so the
272
- * resulting YAML is portable across machines.
273
- *
274
- * Use this when you need to save a config that was previously loaded via
275
- * loadPipeline(). For a pure load→edit→save cycle on raw YAML, prefer
276
- * parseYaml() → edit RawPipelineConfig → serializePipeline().
277
- */
278
- export function deresolvePipeline(config: PipelineConfig, workDir: string): RawPipelineConfig {
279
- const tracks: RawTrackConfig[] = config.tracks.map((track) => {
280
- const trackCwdRel =
281
- track.cwd && track.cwd !== workDir ? relative(workDir, track.cwd) : undefined;
282
- const effectiveTrackDriver = track.driver ?? config.driver ?? 'opencode';
283
- const effectiveTrackModel = track.model ?? config.model;
284
- const effectiveTrackReasoning = track.reasoning_effort ?? config.reasoning_effort;
285
-
286
- const tasks: RawTaskConfig[] = track.tasks.map((task) => {
287
- const taskCwdRel =
288
- task.cwd && task.cwd !== track.cwd ? relative(workDir, task.cwd) : undefined;
289
-
290
- return {
291
- id: task.id,
292
- ...(task.name ? { name: task.name } : {}),
293
- ...(task.prompt !== undefined ? { prompt: task.prompt } : {}),
294
- ...(task.command !== undefined ? { command: task.command } : {}),
295
- ...(task.depends_on?.length ? { depends_on: task.depends_on } : {}),
296
- ...(task.trigger ? { trigger: task.trigger } : {}),
297
- ...(task.continue_from ? { continue_from: task.continue_from } : {}),
298
- ...(taskCwdRel ? { cwd: taskCwdRel } : {}),
299
- ...(task.model && task.model !== effectiveTrackModel ? { model: task.model } : {}),
300
- ...(task.reasoning_effort && task.reasoning_effort !== effectiveTrackReasoning
301
- ? { reasoning_effort: task.reasoning_effort }
302
- : {}),
303
- ...(task.driver && task.driver !== effectiveTrackDriver ? { driver: task.driver } : {}),
304
- ...(task.timeout ? { timeout: task.timeout } : {}),
305
- ...(task.middlewares !== undefined ? { middlewares: task.middlewares } : {}),
306
- ...(task.completion && !isDefaultExitCodeCompletion(task.completion)
307
- ? { completion: task.completion }
308
- : {}),
309
- ...(task.agent_profile ? { agent_profile: task.agent_profile } : {}),
310
- ...(task.permissions && !permissionsEqual(task.permissions, track.permissions)
311
- ? { permissions: task.permissions }
312
- : {}),
313
- ...(task.inputs && Object.keys(task.inputs).length > 0 ? { inputs: task.inputs } : {}),
314
- ...(task.outputs && Object.keys(task.outputs).length > 0 ? { outputs: task.outputs } : {}),
315
- };
316
- });
317
-
318
- return {
319
- id: track.id,
320
- name: track.name,
321
- ...(track.color ? { color: track.color } : {}),
322
- ...(track.agent_profile ? { agent_profile: track.agent_profile } : {}),
323
- ...(track.model && track.model !== config.model ? { model: track.model } : {}),
324
- ...(track.reasoning_effort && track.reasoning_effort !== config.reasoning_effort
325
- ? { reasoning_effort: track.reasoning_effort }
326
- : {}),
327
- ...(track.driver && track.driver !== (config.driver ?? 'opencode')
328
- ? { driver: track.driver }
329
- : {}),
330
- ...(trackCwdRel ? { cwd: trackCwdRel } : {}),
331
- ...(track.middlewares?.length ? { middlewares: track.middlewares } : {}),
332
- ...(track.on_failure && track.on_failure !== 'skip_downstream'
333
- ? { on_failure: track.on_failure }
334
- : {}),
335
- ...(track.permissions && !permissionsEqual(track.permissions, config.permissions ?? DEFAULT_PERMISSIONS)
336
- ? { permissions: track.permissions }
337
- : {}),
338
- tasks,
339
- };
340
- });
341
-
342
- return {
343
- name: config.name,
344
- ...(config.driver ? { driver: config.driver } : {}),
345
- ...(config.model ? { model: config.model } : {}),
346
- ...(config.reasoning_effort ? { reasoning_effort: config.reasoning_effort } : {}),
347
- ...(config.permissions && !permissionsEqual(config.permissions, DEFAULT_PERMISSIONS)
348
- ? { permissions: config.permissions }
349
- : {}),
350
- ...(config.timeout ? { timeout: config.timeout } : {}),
351
- ...(config.plugins?.length ? { plugins: config.plugins } : {}),
352
- ...(config.hooks ? { hooks: config.hooks } : {}),
353
- tracks,
354
- };
355
- }
356
-
357
- // ═══ Offline Validation ═══
358
-
359
- /**
360
- * Validate a pipeline config without executing it.
361
- * Only checks structural/DAG correctness — does not check plugin registration.
362
- * Returns an array of error messages (empty = valid).
363
- */
364
- export function validateConfig(config: PipelineConfig): string[] {
365
- const errors: string[] = [];
366
- try {
367
- buildDag(config);
368
- } catch (err) {
369
- errors.push(err instanceof Error ? err.message : String(err));
370
- }
371
- return errors;
372
- }
373
-
374
- // ═══ Full Parse Pipeline ═══
375
-
376
- export async function loadPipeline(yamlContent: string, workDir: string): Promise<PipelineConfig> {
377
- const raw = parseYaml(yamlContent);
378
- return resolveConfig(raw, workDir);
379
- }
package/src/tagma.test.ts DELETED
@@ -1,155 +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 { createTagma } from './tagma';
6
- import type { DriverPlugin, TagmaPlugin, TaskResult } from './types';
7
- import type { TagmaRuntime } from './runtime';
8
-
9
- function makeDir(prefix: string): string {
10
- return mkdtempSync(join(tmpdir(), prefix));
11
- }
12
-
13
- function makeDriver(name: string, marker: string[]): DriverPlugin {
14
- return {
15
- name,
16
- capabilities: { sessionResume: false, systemPrompt: false, outputFormat: false },
17
- async buildCommand() {
18
- marker.push(name);
19
- return { args: ['echo', name] };
20
- },
21
- };
22
- }
23
-
24
- describe('createTagma', () => {
25
- test('runs command tasks through the configured runtime', async () => {
26
- const calls: string[] = [];
27
- const taskResult: TaskResult = {
28
- exitCode: 0,
29
- stdout: 'runtime-ok',
30
- stderr: '',
31
- stdoutPath: null,
32
- stderrPath: null,
33
- stdoutBytes: 10,
34
- stderrBytes: 0,
35
- durationMs: 1,
36
- sessionId: null,
37
- normalizedOutput: null,
38
- failureKind: null,
39
- };
40
- const runtime: TagmaRuntime = {
41
- async runCommand(command, cwd) {
42
- calls.push(`${cwd}:${command}`);
43
- return taskResult;
44
- },
45
- async runSpawn() {
46
- throw new Error('runSpawn should not be called for command tasks');
47
- },
48
- };
49
- const tagma = createTagma({ builtins: false, runtime });
50
- const dir = makeDir('tagma-runtime-run-');
51
- try {
52
- const result = await tagma.run(
53
- {
54
- name: 'runtime-run',
55
- tracks: [
56
- {
57
- id: 't',
58
- name: 'T',
59
- tasks: [{ id: 'cmd', name: 'cmd', command: 'fake-only-command' }],
60
- },
61
- ],
62
- },
63
- {
64
- cwd: dir,
65
- skipPluginLoading: true,
66
- },
67
- );
68
-
69
- expect(result.success).toBe(true);
70
- expect(calls).toEqual([`${dir}:fake-only-command`]);
71
- expect(result.states.get('t.cmd')?.result?.stdout).toBe('runtime-ok');
72
- } finally {
73
- rmSync(dir, { recursive: true, force: true });
74
- }
75
- });
76
-
77
- test('registers capability plugins passed to options', () => {
78
- const seen: string[] = [];
79
- const driver = makeDriver('driver-plugin', seen);
80
- const plugin: TagmaPlugin = {
81
- name: 'tagma-plugin-local',
82
- capabilities: {
83
- drivers: {
84
- mock: driver,
85
- },
86
- },
87
- };
88
-
89
- const tagma = createTagma({ builtins: false, plugins: [plugin] });
90
-
91
- expect(tagma.registry.getHandler<DriverPlugin>('drivers', 'mock')).toBe(driver);
92
- expect(seen).toEqual([]);
93
- });
94
-
95
- test('instances own isolated plugin registries', () => {
96
- const seenA: string[] = [];
97
- const seenB: string[] = [];
98
- const tagmaA = createTagma({ builtins: false });
99
- const tagmaB = createTagma({ builtins: false });
100
-
101
- tagmaA.registry.registerPlugin('drivers', 'mock', makeDriver('driver-a', seenA));
102
- tagmaB.registry.registerPlugin('drivers', 'mock', makeDriver('driver-b', seenB));
103
-
104
- expect(tagmaA.registry.getHandler<DriverPlugin>('drivers', 'mock').name).toBe('driver-a');
105
- expect(tagmaB.registry.getHandler<DriverPlugin>('drivers', 'mock').name).toBe('driver-b');
106
- expect(seenA).toEqual([]);
107
- expect(seenB).toEqual([]);
108
- });
109
-
110
- test('run uses only the instance registry', async () => {
111
- const tagma = createTagma({ builtins: false });
112
- const dir = makeDir('tagma-instance-run-');
113
- try {
114
- await expect(
115
- tagma.run(
116
- {
117
- name: 'instance-run',
118
- tracks: [
119
- {
120
- id: 't',
121
- name: 'T',
122
- tasks: [{ id: 'prompt', name: 'prompt', prompt: 'hello' }],
123
- },
124
- ],
125
- },
126
- {
127
- cwd: dir,
128
- skipPluginLoading: true,
129
- },
130
- ),
131
- ).rejects.toThrow(/driver "opencode" not registered/);
132
- } finally {
133
- rmSync(dir, { recursive: true, force: true });
134
- }
135
- });
136
-
137
- test('validate returns structural pipeline errors without running tasks', () => {
138
- const tagma = createTagma({ builtins: false });
139
-
140
- expect(
141
- tagma.validate({
142
- name: 'invalid',
143
- tracks: [
144
- {
145
- id: 't',
146
- name: 'T',
147
- tasks: [
148
- { id: 'a', name: 'A', command: 'echo a', depends_on: ['missing'] },
149
- ],
150
- },
151
- ],
152
- }),
153
- ).toEqual(['Task reference "missing" not found']);
154
- });
155
- });
package/src/tagma.ts DELETED
@@ -1,62 +0,0 @@
1
- import { runPipeline, type EngineResult, type RunPipelineOptions } from './engine';
2
- import { bootstrapBuiltins } from './bootstrap';
3
- import { PluginRegistry } from './registry';
4
- import { validateConfig } from './schema';
5
- import { bunRuntime, type TagmaRuntime } from './runtime';
6
- import type { PipelineConfig, TagmaPlugin } from './types';
7
-
8
- export interface CreateTagmaOptions {
9
- /**
10
- * Registry used by this SDK instance. Omit to create an isolated registry.
11
- */
12
- readonly registry?: PluginRegistry;
13
- /**
14
- * Register built-in drivers/triggers/completions/middlewares into the
15
- * instance registry. Defaults to true.
16
- */
17
- readonly builtins?: boolean;
18
- /**
19
- * Package-level capability plugins to register into this SDK instance.
20
- */
21
- readonly plugins?: readonly TagmaPlugin[];
22
- /**
23
- * Runtime implementation used for command and driver process execution.
24
- * Defaults to the SDK's Bun runtime.
25
- */
26
- readonly runtime?: TagmaRuntime;
27
- }
28
-
29
- export interface TagmaRunOptions extends Omit<RunPipelineOptions, 'registry'> {
30
- readonly cwd: string;
31
- }
32
-
33
- export interface Tagma {
34
- readonly registry: PluginRegistry;
35
- run(config: PipelineConfig, options: TagmaRunOptions): Promise<EngineResult>;
36
- validate(config: PipelineConfig): readonly string[];
37
- }
38
-
39
- export function createTagma(options: CreateTagmaOptions = {}): Tagma {
40
- const registry = options.registry ?? new PluginRegistry();
41
- const runtime = options.runtime ?? bunRuntime();
42
- if (options.builtins !== false) {
43
- bootstrapBuiltins(registry);
44
- }
45
- for (const plugin of options.plugins ?? []) {
46
- registry.registerTagmaPlugin(plugin);
47
- }
48
-
49
- return {
50
- registry,
51
- run(config, { cwd, ...runOptions }) {
52
- return runPipeline(config, cwd, {
53
- ...runOptions,
54
- registry,
55
- runtime,
56
- });
57
- },
58
- validate(config) {
59
- return validateConfig(config);
60
- },
61
- };
62
- }