@tagma/sdk 0.4.8 → 0.4.9
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/LICENSE +21 -21
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +12 -38
- package/dist/engine.js.map +1 -1
- package/dist/hooks.d.ts +0 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js.map +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +0 -5
- package/dist/runner.js.map +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +0 -2
- package/dist/schema.js.map +1 -1
- package/dist/templates.d.ts +20 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +93 -0
- package/dist/templates.js.map +1 -0
- package/package.json +1 -1
- package/src/dag.ts +17 -17
- package/src/engine.ts +12 -39
- package/src/hooks.ts +0 -1
- package/src/registry.ts +214 -214
- package/src/runner.ts +0 -5
- package/src/schema.test.ts +97 -97
- package/src/schema.ts +0 -2
- package/src/sdk.ts +2 -2
- package/src/utils.ts +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// ═══ Template Discovery (F1) ═══
|
|
2
|
+
//
|
|
3
|
+
// Public helpers so editors / UIs can enumerate installed `@tagma/template-*`
|
|
4
|
+
// packages in a workspace and read each template's declarative metadata
|
|
5
|
+
// (name, description, params) without actually expanding the template.
|
|
6
|
+
//
|
|
7
|
+
// The legacy private `loadTemplate` in schema.ts uses Bun-specific APIs
|
|
8
|
+
// (Bun.file, require.resolve). These helpers are Node-compatible because
|
|
9
|
+
// the editor server runs on Node, not Bun.
|
|
10
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import yaml from 'js-yaml';
|
|
13
|
+
/**
|
|
14
|
+
* Scan the workspace's `node_modules/@tagma/*` for packages whose name starts
|
|
15
|
+
* with `template-` and load each one's manifest. Packages without a valid
|
|
16
|
+
* `template.yaml` (or that fail to parse) are silently skipped.
|
|
17
|
+
*
|
|
18
|
+
* Returns an empty array when `workDir` doesn't exist or has no such packages.
|
|
19
|
+
*/
|
|
20
|
+
export function discoverTemplates(workDir) {
|
|
21
|
+
const out = [];
|
|
22
|
+
const scopeDir = join(workDir, 'node_modules', '@tagma');
|
|
23
|
+
if (!existsSync(scopeDir))
|
|
24
|
+
return out;
|
|
25
|
+
let entries = [];
|
|
26
|
+
try {
|
|
27
|
+
entries = readdirSync(scopeDir);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (!entry.startsWith('template-'))
|
|
34
|
+
continue;
|
|
35
|
+
const pkgDir = join(scopeDir, entry);
|
|
36
|
+
try {
|
|
37
|
+
const st = statSync(pkgDir);
|
|
38
|
+
if (!st.isDirectory())
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const ref = `@tagma/${entry}`;
|
|
45
|
+
const manifest = loadTemplateManifestFromDir(pkgDir, ref);
|
|
46
|
+
if (manifest)
|
|
47
|
+
out.push(manifest);
|
|
48
|
+
}
|
|
49
|
+
// Sort alphabetically for deterministic UI rendering.
|
|
50
|
+
out.sort((a, b) => a.ref.localeCompare(b.ref));
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Load a single template's manifest by its ref (e.g. `@tagma/template-review`)
|
|
55
|
+
* from the given workspace's `node_modules`. Returns `null` if the package
|
|
56
|
+
* isn't installed or its manifest can't be parsed.
|
|
57
|
+
*/
|
|
58
|
+
export function loadTemplateManifest(ref, workDir) {
|
|
59
|
+
// Only @tagma/template-* refs are supported (matches SDK validateTemplateRef).
|
|
60
|
+
const stripped = ref.replace(/@v\d+$/, '');
|
|
61
|
+
if (!stripped.startsWith('@tagma/template-'))
|
|
62
|
+
return null;
|
|
63
|
+
const pkgDir = join(workDir, 'node_modules', stripped);
|
|
64
|
+
if (!existsSync(pkgDir))
|
|
65
|
+
return null;
|
|
66
|
+
return loadTemplateManifestFromDir(pkgDir, stripped);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve a template manifest from an absolute package directory. Tries
|
|
70
|
+
* `template.yaml` first (the documented convention), then a `template` export
|
|
71
|
+
* from `package.json`'s `main`. Returns `null` on any failure so discovery
|
|
72
|
+
* stays robust against malformed packages.
|
|
73
|
+
*/
|
|
74
|
+
function loadTemplateManifestFromDir(pkgDir, ref) {
|
|
75
|
+
const yamlPath = join(pkgDir, 'template.yaml');
|
|
76
|
+
if (existsSync(yamlPath)) {
|
|
77
|
+
try {
|
|
78
|
+
const content = readFileSync(yamlPath, 'utf-8');
|
|
79
|
+
const doc = yaml.load(content);
|
|
80
|
+
const tpl = (doc && typeof doc === 'object' && 'template' in doc
|
|
81
|
+
? doc.template
|
|
82
|
+
: doc);
|
|
83
|
+
if (tpl && typeof tpl === 'object' && tpl.name && Array.isArray(tpl.tasks)) {
|
|
84
|
+
return { ...tpl, ref };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/dag.ts
CHANGED
|
@@ -178,22 +178,22 @@ export interface RawDag {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
/**
|
|
181
|
-
* Build a lightweight DAG from a raw (unresolved) pipeline config.
|
|
182
|
-
* Unlike buildDag, this function:
|
|
183
|
-
* - Does not require a workDir or resolved PipelineConfig
|
|
184
|
-
* - Is lenient: missing or ambiguous refs are silently skipped
|
|
185
|
-
*
|
|
186
|
-
* Intended for the visual editor to render the flow graph before a pipeline is run.
|
|
187
|
-
*/
|
|
188
|
-
export function buildRawDag(config: RawPipelineConfig): RawDag {
|
|
181
|
+
* Build a lightweight DAG from a raw (unresolved) pipeline config.
|
|
182
|
+
* Unlike buildDag, this function:
|
|
183
|
+
* - Does not require a workDir or resolved PipelineConfig
|
|
184
|
+
* - Is lenient: missing or ambiguous refs are silently skipped
|
|
185
|
+
*
|
|
186
|
+
* Intended for the visual editor to render the flow graph before a pipeline is run.
|
|
187
|
+
*/
|
|
188
|
+
export function buildRawDag(config: RawPipelineConfig): RawDag {
|
|
189
189
|
const nodes = new Map<string, RawDagNode>();
|
|
190
190
|
const bareToQualified = new Map<string, string>();
|
|
191
191
|
|
|
192
|
-
// 1. Register all concrete tasks
|
|
193
|
-
for (const track of config.tracks) {
|
|
194
|
-
for (const task of track.tasks) {
|
|
195
|
-
const qid = `${track.id}.${task.id}`;
|
|
196
|
-
if (nodes.has(qid)) continue; // skip duplicates silently
|
|
192
|
+
// 1. Register all concrete tasks
|
|
193
|
+
for (const track of config.tracks) {
|
|
194
|
+
for (const task of track.tasks) {
|
|
195
|
+
const qid = `${track.id}.${task.id}`;
|
|
196
|
+
if (nodes.has(qid)) continue; // skip duplicates silently
|
|
197
197
|
|
|
198
198
|
if (bareToQualified.has(task.id)) {
|
|
199
199
|
bareToQualified.set(task.id, '__ambiguous__');
|
|
@@ -216,10 +216,10 @@ export function buildRawDag(config: RawPipelineConfig): RawDag {
|
|
|
216
216
|
|
|
217
217
|
const edges: { from: string; to: string }[] = [];
|
|
218
218
|
|
|
219
|
-
for (const track of config.tracks) {
|
|
220
|
-
for (const task of track.tasks) {
|
|
221
|
-
const qid = `${track.id}.${task.id}`;
|
|
222
|
-
const deps: string[] = [];
|
|
219
|
+
for (const track of config.tracks) {
|
|
220
|
+
for (const task of track.tasks) {
|
|
221
|
+
const qid = `${track.id}.${task.id}`;
|
|
222
|
+
const deps: string[] = [];
|
|
223
223
|
|
|
224
224
|
for (const ref of task.depends_on ?? []) {
|
|
225
225
|
const resolved = tryResolve(ref, track.id);
|
package/src/engine.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resolve
|
|
2
|
-
import {
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import { readdir, rm } from 'fs/promises';
|
|
3
3
|
import type {
|
|
4
4
|
PipelineConfig, TaskConfig, TrackConfig, TaskState, TaskStatus,
|
|
5
5
|
TaskResult, DriverPlugin, TriggerPlugin, CompletionPlugin,
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
import { buildDag, type Dag, type DagNode } from './dag';
|
|
10
10
|
import { getHandler, hasHandler, loadPlugins } from './registry';
|
|
11
11
|
import { runSpawn, runCommand } from './runner';
|
|
12
|
-
import { parseDuration, nowISO, generateRunId
|
|
12
|
+
import { parseDuration, nowISO, generateRunId } from './utils';
|
|
13
13
|
import {
|
|
14
14
|
executeHook,
|
|
15
15
|
buildPipelineStartContext, buildTaskContext,
|
|
@@ -79,7 +79,7 @@ function preflight(config: PipelineConfig, dag: Dag): void {
|
|
|
79
79
|
const upstream = dag.nodes.get(upstreamId);
|
|
80
80
|
if (upstream) {
|
|
81
81
|
// A handoff is possible via session resume (already ruled out above),
|
|
82
|
-
//
|
|
82
|
+
// OR in-memory text injection through normalizedMap
|
|
83
83
|
// (when the upstream driver implements parseResult and returns normalizedOutput).
|
|
84
84
|
const upstreamDriverName = upstream.task.driver ?? upstream.track.driver
|
|
85
85
|
?? config.driver ?? 'claude-code';
|
|
@@ -88,12 +88,12 @@ function preflight(config: PipelineConfig, dag: Dag): void {
|
|
|
88
88
|
: null;
|
|
89
89
|
const canNormalize = typeof upstreamDriver?.parseResult === 'function';
|
|
90
90
|
|
|
91
|
-
if (!
|
|
91
|
+
if (!canNormalize) {
|
|
92
92
|
errors.push(
|
|
93
93
|
`Task "${node.taskId}" uses continue_from: "${task.continue_from}", ` +
|
|
94
|
-
`but upstream task "${upstreamId}"
|
|
94
|
+
`but upstream task "${upstreamId}" its driver ` +
|
|
95
95
|
`does not implement parseResult for text-injection handoff. ` +
|
|
96
|
-
`
|
|
96
|
+
`Use a driver with parseResult, or remove continue_from.`
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -297,7 +297,6 @@ export async function runPipeline(
|
|
|
297
297
|
options.onEvent?.({ type: 'pipeline_start', runId, states: statesSnapshot });
|
|
298
298
|
|
|
299
299
|
const sessionMap = new Map<string, string>();
|
|
300
|
-
const outputMap = new Map<string, string>();
|
|
301
300
|
const normalizedMap = new Map<string, string>();
|
|
302
301
|
|
|
303
302
|
// Pipeline timeout
|
|
@@ -407,7 +406,6 @@ export async function runPipeline(
|
|
|
407
406
|
status: state.status,
|
|
408
407
|
exit_code: state.result?.exitCode ?? null,
|
|
409
408
|
duration_ms: state.result?.durationMs ?? null,
|
|
410
|
-
output_path: state.result?.outputPath ?? null,
|
|
411
409
|
stderr_path: state.result?.stderrPath ?? null,
|
|
412
410
|
session_id: state.result?.sessionId ?? null,
|
|
413
411
|
started_at: state.startedAt,
|
|
@@ -585,7 +583,7 @@ export async function runPipeline(
|
|
|
585
583
|
log.debug(`[task:${taskId}]`,
|
|
586
584
|
`middleware chain: ${mws.map(m => m.type).join(' → ')}`);
|
|
587
585
|
const mwCtx: MiddlewareContext = {
|
|
588
|
-
task, track,
|
|
586
|
+
task, track, workDir: task.cwd ?? workDir,
|
|
589
587
|
};
|
|
590
588
|
for (const mwConfig of mws) {
|
|
591
589
|
const before = prompt.length;
|
|
@@ -611,7 +609,7 @@ export async function runPipeline(
|
|
|
611
609
|
|
|
612
610
|
// H1: hand the driver a continue_from that has already been
|
|
613
611
|
// qualified by dag.ts. Without this, drivers like codex/opencode/
|
|
614
|
-
// claude-code
|
|
612
|
+
// claude-code look up maps directly with
|
|
615
613
|
// the user's raw (possibly bare) string, which races whenever two
|
|
616
614
|
// tracks share a task name. dag.ts has the only authoritative
|
|
617
615
|
// resolver, so we use its precomputed answer here.
|
|
@@ -621,7 +619,7 @@ export async function runPipeline(
|
|
|
621
619
|
continue_from: node.resolvedContinueFrom ?? task.continue_from,
|
|
622
620
|
};
|
|
623
621
|
const driverCtx: DriverContext = {
|
|
624
|
-
sessionMap,
|
|
622
|
+
sessionMap, normalizedMap, workDir: task.cwd ?? workDir,
|
|
625
623
|
};
|
|
626
624
|
const spec = await driver.buildCommand(enrichedTask, track, driverCtx);
|
|
627
625
|
log.debug(`[task:${taskId}]`, `driver=${driverName}`);
|
|
@@ -635,27 +633,6 @@ export async function runPipeline(
|
|
|
635
633
|
result = await runSpawn(spec, driver, runOpts);
|
|
636
634
|
}
|
|
637
635
|
|
|
638
|
-
// 5. Write output file with RAW stdout (preserves driver output format).
|
|
639
|
-
// Done BEFORE the completion check so a `file_exists` completion pointing
|
|
640
|
-
// at `task.output` observes the AI-generated artefact. Writes happen
|
|
641
|
-
// regardless of exit code so failed/timed-out tasks still leave a
|
|
642
|
-
// debuggable artefact on disk.
|
|
643
|
-
if (task.output) {
|
|
644
|
-
// validatePath enforces no .. traversal and no absolute paths escaping workDir.
|
|
645
|
-
const outPath = validatePath(task.output, workDir);
|
|
646
|
-
await mkdir(dirname(outPath), { recursive: true });
|
|
647
|
-
await Bun.write(outPath, result.stdout);
|
|
648
|
-
result = { ...result, outputPath: outPath };
|
|
649
|
-
// H1: only write the fully-qualified taskId. The previous "also store
|
|
650
|
-
// bare id when not yet present" trick produced non-deterministic
|
|
651
|
-
// continue_from lookups when two tracks shared a task name —
|
|
652
|
-
// whichever finished first won the bare key. dag.ts now resolves
|
|
653
|
-
// continue_from to a qualified id (DagNode.resolvedContinueFrom),
|
|
654
|
-
// and the enrichedTask handed to drivers carries that qualified
|
|
655
|
-
// version, so bare keys are no longer needed.
|
|
656
|
-
outputMap.set(taskId, outPath);
|
|
657
|
-
}
|
|
658
|
-
|
|
659
636
|
// 6. Determine terminal status (without emitting yet — result must be complete first)
|
|
660
637
|
// H2: branch on failureKind so spawn errors no longer masquerade as
|
|
661
638
|
// timeouts. Old runners that don't set failureKind still work — we
|
|
@@ -697,7 +674,6 @@ export async function runPipeline(
|
|
|
697
674
|
? result.normalizedOutput.slice(0, MAX_NORMALIZED_BYTES) +
|
|
698
675
|
`\n[…clipped at ${MAX_NORMALIZED_BYTES} bytes]`
|
|
699
676
|
: result.normalizedOutput;
|
|
700
|
-
// H1: qualified-only key (see comment near outputMap above).
|
|
701
677
|
normalizedMap.set(taskId, clipped);
|
|
702
678
|
}
|
|
703
679
|
|
|
@@ -708,7 +684,7 @@ export async function runPipeline(
|
|
|
708
684
|
}
|
|
709
685
|
|
|
710
686
|
if (result.sessionId) {
|
|
711
|
-
// H1: qualified-only key
|
|
687
|
+
// H1: qualified-only key.
|
|
712
688
|
sessionMap.set(taskId, result.sessionId);
|
|
713
689
|
}
|
|
714
690
|
|
|
@@ -736,9 +712,6 @@ export async function runPipeline(
|
|
|
736
712
|
if (result.sessionId) {
|
|
737
713
|
log.debug(`[task:${taskId}]`, `sessionId: ${result.sessionId}`);
|
|
738
714
|
}
|
|
739
|
-
if (result.outputPath) {
|
|
740
|
-
log.debug(`[task:${taskId}]`, `wrote output: ${result.outputPath}`);
|
|
741
|
-
}
|
|
742
715
|
if (result.stderrPath) {
|
|
743
716
|
log.debug(`[task:${taskId}]`, `wrote stderr: ${result.stderrPath}`);
|
|
744
717
|
}
|
|
@@ -760,7 +733,7 @@ export async function runPipeline(
|
|
|
760
733
|
exitCode: -1,
|
|
761
734
|
stdout: '',
|
|
762
735
|
stderr: errMsg,
|
|
763
|
-
|
|
736
|
+
stderrPath: null, durationMs: 0,
|
|
764
737
|
sessionId: null, normalizedOutput: null,
|
|
765
738
|
// H2: Engine-level pre-execution errors (driver throw, middleware
|
|
766
739
|
// throw, getHandler 404) classify as spawn_error — the process never
|
package/src/hooks.ts
CHANGED
|
@@ -141,7 +141,6 @@ export interface TaskInfo {
|
|
|
141
141
|
readonly status: string;
|
|
142
142
|
readonly exit_code: number | null;
|
|
143
143
|
readonly duration_ms: number | null;
|
|
144
|
-
readonly output_path: string | null;
|
|
145
144
|
readonly stderr_path: string | null;
|
|
146
145
|
readonly session_id: string | null;
|
|
147
146
|
readonly started_at: string | null;
|