@tagma/sdk 0.3.0 → 0.3.2

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 CHANGED
@@ -326,6 +326,20 @@ Properties:
326
326
  - `runId` — engine-assigned run ID, available after the first `pipeline_start` event (`null` until then)
327
327
  - `status` — `'idle' | 'running' | 'done' | 'aborted'`
328
328
 
329
+ ### `TriggerBlockedError` / `TriggerTimeoutError`
330
+
331
+ Typed error classes for trigger plugin error classification. The engine uses `instanceof` checks on these to set the correct task status (`blocked` or `timeout`) instead of matching on error message substrings.
332
+
333
+ Built-in triggers (`manual`, `file`) throw these automatically. Third-party trigger plugins should throw `TriggerBlockedError` for user/policy rejections and `TriggerTimeoutError` for genuine wait timeouts. Plugins that throw plain `Error` still work — the engine falls back to string matching for backward compatibility, but typed errors are preferred to avoid misclassification from coincidental substrings.
334
+
335
+ ```ts
336
+ import { TriggerBlockedError, TriggerTimeoutError } from '@tagma/sdk';
337
+
338
+ // In a custom trigger plugin:
339
+ throw new TriggerBlockedError('Access denied by policy');
340
+ throw new TriggerTimeoutError('File did not appear within 30s');
341
+ ```
342
+
329
343
  ### `loadPlugins(names: string[]): Promise<void>`
330
344
 
331
345
  Dynamically loads and registers external plugin packages.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tagma/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
package/src/dag.ts CHANGED
@@ -84,7 +84,15 @@ export function buildDag(config: PipelineConfig): Dag {
84
84
  }
85
85
  }
86
86
  if (task.continue_from) {
87
- const resolved = resolveRef(task.continue_from, track.id);
87
+ let resolved: string;
88
+ try {
89
+ resolved = resolveRef(task.continue_from, track.id);
90
+ } catch {
91
+ throw new Error(
92
+ `Task "${qid}": continue_from "${task.continue_from}" — no such task found. ` +
93
+ `Use a fully-qualified reference (trackId.taskId) or ensure the target task exists.`
94
+ );
95
+ }
88
96
  if (!deps.includes(resolved)) {
89
97
  deps.push(resolved); // continue_from implies dependency
90
98
  }
@@ -241,11 +241,15 @@ function detectCycles(
241
241
  function dfs(id: string): void {
242
242
  if (inStack.has(id)) {
243
243
  const cycleStart = pathStack.indexOf(id);
244
- const cycleNodes = [...pathStack.slice(cycleStart), id];
245
- const key = [...cycleNodes].sort().join(',');
244
+ // Unique nodes in the cycle (without repeating the start node) for dedup.
245
+ // Previously the duplicate start node caused different sorted keys when
246
+ // the same cycle was discovered from different entry points.
247
+ const uniqueNodes = pathStack.slice(cycleStart);
248
+ const key = [...uniqueNodes].sort().join(',');
246
249
  if (!seenCycles.has(key)) {
247
250
  seenCycles.add(key);
248
- errors.push({ path: 'tracks', message: `Circular dependency detected: ${cycleNodes.join(' ')}` });
251
+ const display = [...uniqueNodes, id]; // include start for readable display
252
+ errors.push({ path: 'tracks', message: `Circular dependency detected: ${display.join(' → ')}` });
249
253
  }
250
254
  return;
251
255
  }