@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
package/README.md CHANGED
@@ -64,6 +64,8 @@ console.log(result.success ? 'Done' : 'Failed');
64
64
 
65
65
  The package root is intentionally small. Use explicit subpaths for YAML,
66
66
  config editing, plugin registry helpers, and low-level dataflow utilities.
67
+ The SDK composes `@tagma/core` with `@tagma/runtime-bun`; import those packages
68
+ directly only when you need lower-level package boundaries.
67
69
 
68
70
  ## Features
69
71
 
@@ -75,8 +77,7 @@ config editing, plugin registry helpers, and low-level dataflow utilities.
75
77
  - **Middleware** -- enrich prompts before execution (e.g. inject static context)
76
78
  - **Completion checks** -- validate task output with `exit_code`, `file_exists`, or `output_check` plugins
77
79
  - **Plugin schemas** -- triggers/completions/middlewares can declare a `PluginSchema` so visual editors render typed forms for their config
78
- - **Lightweight task bindings** -- pass dynamic values with task-level `inputs` / `outputs` without declaring a typed contract
79
- - **Typed task ports** -- declare named, typed `ports.inputs` / `ports.outputs` when a task needs a strict, validated I/O contract
80
+ - **Unified task bindings** -- pass dynamic values with task-level `inputs` / `outputs`; add optional `type` metadata when a binding needs validation
80
81
 
81
82
  ## Pipeline YAML Reference
82
83
 
@@ -205,9 +206,8 @@ Each hook value can be a single command string or an array of commands.
205
206
  | `middlewares` | `MiddlewareConfig[]` | No | Inherited from track | Middleware override. Set `[]` to disable inherited middlewares |
206
207
  | `trigger` | `TriggerConfig` | No | — | Gate that must resolve before the task runs (see Triggers) |
207
208
  | `completion` | `CompletionConfig` | No | — | Post-execution check to validate task output (see Completions) |
208
- | `inputs` | `TaskInputBindings` | No | — | Lightweight parameter bindings for `{{inputs.<name>}}` |
209
- | `outputs` | `TaskOutputBindings` | No | — | Lightweight named outputs published after success |
210
- | `ports` | `TaskPorts` | No | — | Typed input/output ports — see Typed Ports below |
209
+ | `inputs` | `TaskInputBindings` | No | — | Task input bindings for `{{inputs.<name>}}`; optional `type` enables coercion/validation |
210
+ | `outputs` | `TaskOutputBindings` | No | — | Named outputs published after success; optional `type` enables coercion/validation |
211
211
 
212
212
  ### Permissions
213
213
 
@@ -227,15 +227,17 @@ Track-level `middlewares` apply to all tasks in the track. Setting task-level `m
227
227
 
228
228
  ---
229
229
 
230
- ### Lightweight Bindings
230
+ ### Unified Task Bindings
231
231
 
232
- Use task-level `inputs` / `outputs` for ordinary parameter passing. They are task-level only, do not inherit, and do not add prompt schema blocks or type coercion.
232
+ Use task-level `inputs` / `outputs` for parameter passing. They are task-level only, do not inherit, and can stay lightweight by omitting `type`. Add `type`, `required`, `enum`, and `description` when a binding should be strict and editor-visible.
233
233
 
234
234
  ```yaml
235
235
  - id: build
236
236
  command: bun run build
237
237
  outputs:
238
- bundlePath: { from: json.bundlePath }
238
+ bundlePath:
239
+ from: json.bundlePath
240
+ type: string
239
241
 
240
242
  - id: test
241
243
  depends_on: [build]
@@ -244,17 +246,18 @@ Use task-level `inputs` / `outputs` for ordinary parameter passing. They are tas
244
246
  bundlePath:
245
247
  from: t.build.outputs.bundlePath
246
248
  required: true
249
+ type: string
247
250
  ```
248
251
 
249
- Input bindings support `value`, `from`, `default`, and `required`. `from` accepts `taskId.outputs.name`, `taskId.stdout`, `taskId.stderr`, `taskId.normalizedOutput`, `taskId.exitCode`, or `outputs.name` to name-match direct upstream outputs.
252
+ Input bindings support `value`, `from`, `default`, `required`, optional `type`, `enum`, and `description`. `from` accepts `taskId.outputs.name`, `taskId.stdout`, `taskId.stderr`, `taskId.normalizedOutput`, `taskId.exitCode`, or `outputs.name` to name-match direct upstream outputs.
250
253
 
251
- Output bindings support `value`, `from`, and `default`. `from` defaults to `json.<outputName>` and also accepts `stdout`, `stderr`, or `normalizedOutput`.
254
+ Output bindings support `value`, `from`, `default`, optional `type`, `enum`, and `description`. `from` defaults to `json.<outputName>` and also accepts `stdout`, `stderr`, or `normalizedOutput`.
252
255
 
253
- Use `ports` instead when downstream tasks need required typed values, the editor should present a stable I/O contract, or prompt tasks should receive `[Inputs]` / `[Output Format]` blocks.
256
+ Prompt tasks infer their structured input/output contract from neighboring command tasks that use typed `outputs` / `inputs`. The engine renders `[Inputs]` and `[Output Format]` blocks from that inferred contract.
254
257
 
255
258
  ---
256
259
 
257
- ### Typed Ports
260
+ ### Typed Binding Example
258
261
 
259
262
  Tasks can declare named, typed `inputs` / `outputs`. Inputs flow in from upstream task outputs; outputs are extracted from a task's stdout (or the AI driver's `normalizedOutput`) on success.
260
263
 
@@ -262,40 +265,50 @@ Tasks can declare named, typed `inputs` / `outputs`. Inputs flow in from upstrea
262
265
  - id: lookup-weather
263
266
  name: Lookup weather
264
267
  command: weather.sh --city "{{inputs.city}}"
265
- ports:
266
- inputs:
267
- - { name: city, type: string, required: true, description: Target city }
268
- outputs:
269
- - { name: temperature, type: number, description: Current temperature in Celsius }
270
- - { name: conditions, type: enum, enum: [sunny, cloudy, rain, snow] }
268
+ inputs:
269
+ city:
270
+ type: string
271
+ required: true
272
+ description: Target city
273
+ outputs:
274
+ temperature:
275
+ type: number
276
+ description: Current temperature in Celsius
277
+ conditions:
278
+ type: enum
279
+ enum: [sunny, cloudy, rain, snow]
271
280
 
272
281
  - id: write-report
273
282
  depends_on: [lookup-weather]
274
283
  prompt: 'Write a brief weather report for {{inputs.city}}.'
275
- ports:
276
- inputs:
277
- - { name: city, type: string, required: true }
278
- - { name: temperature, type: number, required: true }
279
- - { name: conditions, type: enum, enum: [sunny, cloudy, rain, snow] }
284
+ inputs:
285
+ city: { from: t.lookup-weather.outputs.city, type: string, required: true }
286
+ temperature: { from: t.lookup-weather.outputs.temperature, type: number, required: true }
287
+ conditions:
288
+ from: t.lookup-weather.outputs.conditions
289
+ type: enum
290
+ enum: [sunny, cloudy, rain, snow]
280
291
  ```
281
292
 
282
- #### `PortDef` fields
293
+ #### Binding fields
283
294
 
284
295
  | Field | Type | Required | Description |
285
296
  | ------------- | ----------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
286
- | `name` | `string` | Yes | Port name; also the substitution key (`{{inputs.<name>}}`) |
287
- | `type` | `'string' \| 'number' \| 'boolean' \| 'enum' \| 'json'` | Yes | Coercion type. Mismatched values block the task with a typed-error diagnostic |
297
+ | `value` | `unknown` | No | Literal value. For inputs, this wins over `from` |
298
+ | `from` | `string` | No | Inputs: upstream source such as `taskId.outputs.name`, `taskId.stdout`, or `outputs.name`. Outputs: `json.<key>`, `stdout`, `stderr`, or `normalizedOutput` |
299
+ | `default` | `unknown` | No | Fallback value when the source is missing |
300
+ | `required` | `boolean` | Inputs only | When `true`, missing upstream value and no `default` blocks the task |
301
+ | `type` | `'string' \| 'number' \| 'boolean' \| 'enum' \| 'json'` | No | Optional coercion type. Omit it for pass-through values |
288
302
  | `description` | `string` | No | Free-text description; rendered into the `[Inputs]` / `[Output Format]` blocks |
289
- | `required` | `boolean` | No | Inputs only. When `true`, missing upstream value (and no `default`) blocks the task |
290
- | `default` | `unknown` | No | Inputs only. Fallback value when no upstream produces the port |
291
303
  | `enum` | `string[]` | When `type: enum` | Allowed values |
292
- | `from` | `string` | No | Inputs only. Explicit upstream binding — bare `portName` (match by name) or `taskId.portName` (fully qualified). Unset = match by name across all direct upstreams; ambiguous matches block |
293
304
 
294
305
  #### Substitution and AI prompt blocks
295
306
 
296
307
  - `{{inputs.<name>}}` is expanded verbatim in `command` and `prompt` strings before execution. Quote your placeholders in command lines (`--city "{{inputs.city}}"`) — the engine does not shell-escape.
297
- - AI tasks additionally get two prepended `PromptContextBlock`s: `[Output Format]` (instructs the model to emit a final-line JSON object matching the declared outputs) and `[Inputs]` (renders the resolved inputs as `name: value # description`). Tasks without ports get no extra blocks.
298
- - Output extraction strategy: prefer `normalizedOutput` (AI tasks), fall back to stdout (command tasks). Find the last non-empty line that parses as a JSON object, then read each declared output key. Failures append a diagnostic to stderr; the port is absent from `outputs` and downstream tasks see it as missing.
308
+ - AI tasks get `[Output Format]` and `[Inputs]` blocks when typed bindings can be inferred from neighboring command tasks.
309
+ - Output extraction strategy: prefer `normalizedOutput` (AI tasks), fall back to stdout (command tasks). Find the last non-empty line that parses as a JSON object, then read each declared output key. Failures append a diagnostic to stderr; the binding is absent from `outputs` and downstream tasks see it as missing.
310
+
311
+ YAML `ports` has been replaced by typed `inputs` / `outputs`. `validateRaw` reports any `ports` field as a migration error.
299
312
 
300
313
  ---
301
314
 
@@ -363,9 +376,10 @@ Tasks can declare named, typed `inputs` / `outputs`. Inputs flow in from upstrea
363
376
  ### Root: `@tagma/sdk`
364
377
 
365
378
  - `createTagma(options?)`
379
+ - `bunRuntime()`
366
380
  - `definePipeline(pipeline)`
367
381
  - `PluginRegistry`
368
- - stable pipeline/result/event types
382
+ - stable pipeline/result/event/plugin/runtime types, including `TagmaPlugin` and `TagmaRuntime`
369
383
  - trigger error classes
370
384
 
371
385
  ### YAML: `@tagma/sdk/yaml`
@@ -391,10 +405,22 @@ Tasks can declare named, typed `inputs` / `outputs`. Inputs flow in from upstrea
391
405
  - raw validation helpers
392
406
  - task reference helpers
393
407
 
394
- ### Ports: `@tagma/sdk/ports`
408
+ ### Dataflow helpers: `@tagma/sdk/ports`
395
409
 
396
- - current dataflow helpers for placeholder substitution, binding resolution,
397
- output extraction, and prompt-port inference
410
+ - placeholder substitution, binding resolution, output extraction, and internal prompt-contract inference
411
+ - the subpath name is historical; YAML `ports` is rejected by validation
412
+
413
+ ### Runtime approval adapters
414
+
415
+ - `@tagma/sdk/runtime/adapters/stdin-approval`
416
+ - `@tagma/sdk/runtime/adapters/websocket-approval`
417
+ - the older `@tagma/sdk/adapters/*` subpaths remain thin compatibility re-exports
418
+
419
+ ### Split packages
420
+
421
+ - `@tagma/core` -- runtime-independent orchestration, registry, approval, logging, event/result types, and the `TagmaRuntime` interface
422
+ - `@tagma/runtime-bun` -- Bun process execution, file watching, log storage, `bunRuntime()`, and runtime approval adapters
423
+ - `@tagma/sdk` -- convenience package that composes core + Bun runtime + built-in plugins
398
424
 
399
425
  ### `bootstrapBuiltins(registry)`
400
426
 
@@ -404,6 +430,17 @@ Registers all built-in plugins (opencode driver, file/manual triggers, completio
404
430
 
405
431
  Parses YAML, resolves inheritance, and validates the configuration.
406
432
 
433
+ ### `createTagma(options?)`
434
+
435
+ Creates an isolated SDK instance with its own plugin registry.
436
+
437
+ Options:
438
+
439
+ - `registry` -- use an existing `PluginRegistry` instance
440
+ - `builtins` -- register built-in plugins into the instance registry; defaults to `true`
441
+ - `plugins` -- register package-level `TagmaPlugin` capability objects into the instance registry
442
+ - `runtime` -- override process execution, file watching, file existence checks, log storage, time, and sleep; defaults to `bunRuntime()`
443
+
407
444
  ### `createTagma().run(config, options): Promise<EngineResult>`
408
445
 
409
446
  Executes the pipeline. Returns `{ success, runId, logPath, summary, states }`.
@@ -414,7 +451,7 @@ Options:
414
451
  - `signal` -- `AbortSignal` to cancel the run externally
415
452
  - `onEvent` -- callback for real-time `RunEventPayload` updates. Every payload carries `runId`. The editor server stamps a per-run `seq` on top of this payload before broadcasting over SSE (producing a `WireRunEvent`); the SDK itself does not stamp `seq`. Event variants:
416
453
  - `run_start` — pipeline approved and all tasks transitioned to `waiting`; includes `tasks: RunTaskState[]` (wire-shape snapshot of every task). Fires only when the `pipeline_start` hook allows the run — blocked pipelines emit no wire events at all.
417
- - `task_update` — a task's status or result changed; flat fields (`status`, `startedAt?`, `finishedAt?`, `durationMs?`, `exitCode?`, `stdout?`, `stderr?`, `stdoutPath?`, `stderrPath?`, `stdoutBytes?`, `stderrBytes?`, `sessionId?`, `normalizedOutput?`, `outputs?`, `inputs?`, `resolvedDriver?`, `resolvedModel?`, `resolvedPermissions?`) so clients can fold partial updates with `??` semantics. `inputs` carries the resolved port input map (set once just before the task transitions to `running`); `outputs` carries the extracted port output map after a successful terminal transition. `startedAt` is populated before the `running` transition; `finishedAt` and result fields are populated before any terminal-status transition. Terminal-state locking in the engine guarantees at most one terminal event per task.
454
+ - `task_update` — a task's status or result changed; flat fields (`status`, `startedAt?`, `finishedAt?`, `durationMs?`, `exitCode?`, `stdout?`, `stderr?`, `stdoutPath?`, `stderrPath?`, `stdoutBytes?`, `stderrBytes?`, `sessionId?`, `normalizedOutput?`, `outputs?`, `inputs?`, `resolvedDriver?`, `resolvedModel?`, `resolvedPermissions?`) so clients can fold partial updates with `??` semantics. `inputs` carries the resolved task input map (set once just before the task transitions to `running`); `outputs` carries the extracted binding output map after a successful terminal transition. `startedAt` is populated before the `running` transition; `finishedAt` and result fields are populated before any terminal-status transition. Terminal-state locking in the engine guarantees at most one terminal event per task.
418
455
  - `task_log` — a structured log line was written to `pipeline.log`. Mirrors every `Logger` call (info/warn/error/debug/section/quiet) and carries `{ taskId: string | null, level, timestamp, text }`. `taskId` is non-null for lines tagged with a `[task:<id>]` prefix (or passed explicitly to `section`/`quiet`) and `null` for pipeline-wide messages such as the configuration dump and DAG topology. Use this to stream the full run process into UIs without tailing the log file.
419
456
  - `run_end` — pipeline finished; includes `success: boolean` and `abortReason: 'timeout' | 'stop_all' | 'external' | null`. `null` means the run completed on its own steam (success may still be `false` if tasks failed).
420
457
  - `run_error` — reserved for fatal engine errors surfaced over the wire.
@@ -423,7 +460,7 @@ Options:
423
460
  - `maxLogRuns` -- number of per-run log directories to keep under `<workDir>/.tagma/logs/` (default: 20)
424
461
  - `skipPluginLoading` -- skip the engine's built-in `loadPlugins(config.plugins)` call. Set this when the host has already pre-loaded plugins from a custom resolution path (e.g. the editor loading from the user's workspace `node_modules`) so the engine doesn't re-resolve them via Node's default cwd-based import.
425
462
 
426
- > **stdout / stderr persistence.** The engine streams every task's stdout and stderr to disk under `<workDir>/.tagma/logs/<runId>/<taskId>.stdout` and `.stderr`. The `TaskResult.stdout` / `stderr` strings are bounded tails (8 MB / 4 MB by default) — long outputs are truncated from the head with a marker, and consumers that need the full bytes should read `TaskResult.stdoutPath` / `stderrPath`. Use `TaskResult.stdoutBytes` / `stderrBytes` to display "32 MB (truncated)" without re-stat'ing the file.
463
+ > **stdout / stderr persistence.** With the default Bun runtime, the engine streams every task's stdout and stderr to disk under `<workDir>/.tagma/logs/<runId>/<taskId>.stdout` and `.stderr`. Custom runtimes can relocate those artifacts by implementing `runtime.logStore.taskOutputPath()`. The `TaskResult.stdout` / `stderr` strings are bounded tails (8 MB / 4 MB by default) — long outputs are truncated from the head with a marker, and consumers that need the full bytes should read `TaskResult.stdoutPath` / `stderrPath`. Use `TaskResult.stdoutBytes` / `stderrBytes` to display "32 MB (truncated)" without re-stat'ing the file.
427
464
 
428
465
  ### `PipelineRunner`
429
466
 
@@ -460,7 +497,7 @@ Properties:
460
497
 
461
498
  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.
462
499
 
463
- 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.
500
+ Built-in triggers (`manual`, `file`) throw these automatically. Trigger plugins should throw `TriggerBlockedError` for user/policy rejections and `TriggerTimeoutError` for genuine wait timeouts.
464
501
 
465
502
  ```ts
466
503
  import { TriggerBlockedError, TriggerTimeoutError } from '@tagma/sdk';
@@ -470,13 +507,30 @@ throw new TriggerBlockedError('Access denied by policy');
470
507
  throw new TriggerTimeoutError('File did not appear within 30s');
471
508
  ```
472
509
 
473
- ### `PluginRegistry.loadPlugins(names): Promise<void>`
510
+ ### `PluginRegistry.loadPlugins(names, resolveFrom?): Promise<void>`
511
+
512
+ Dynamically loads and registers external plugin packages. Each package must default-export a `TagmaPlugin` with capability maps.
513
+
514
+ ```ts
515
+ export default {
516
+ name: '@tagma/trigger-http',
517
+ capabilities: {
518
+ triggers: {
519
+ http: HttpTrigger,
520
+ },
521
+ },
522
+ } satisfies TagmaPlugin;
523
+ ```
524
+
525
+ Pass `resolveFrom` when plugins are installed in a workspace-local `node_modules`.
526
+
527
+ ### `PluginRegistry.registerTagmaPlugin(plugin): RegisteredCapability[]`
474
528
 
475
- Dynamically loads and registers external plugin packages.
529
+ Registers every supported capability from a `TagmaPlugin` default export.
476
530
 
477
531
  ### `PluginRegistry.registerPlugin(category, type, handler): void`
478
532
 
479
- Registers a plugin handler manually. Idempotent duplicate registrations are silently ignored.
533
+ Registers one capability handler manually. Prefer `registerTagmaPlugin()` for package-level plugins.
480
534
 
481
535
  Plugin handlers (`TriggerPlugin`, `CompletionPlugin`, `MiddlewarePlugin`) may optionally expose a declarative `schema: PluginSchema` field so visual editors can render a typed form for the plugin's config instead of a raw key/value editor:
482
536
 
@@ -494,6 +548,8 @@ export const HttpTrigger: TriggerPlugin = {
494
548
  },
495
549
  },
496
550
  async watch(config, ctx) {
551
+ // Trigger plugins should use ctx.runtime for file IO, watching, and
552
+ // timing so they can run under non-Bun test/runtime implementations.
497
553
  /* ... */
498
554
  },
499
555
  };
@@ -612,9 +668,9 @@ Validates a resolved pipeline config without executing it. Checks DAG structure
612
668
 
613
669
  Use `validateRaw` for editing raw configs in a UI; use `validateConfig` after `resolveConfig` for a final pre-run check.
614
670
 
615
- ### Bindings And Ports API
671
+ ### Bindings API
616
672
 
617
- Pure helpers backing lightweight bindings and `task.ports`. Safe to use in editors, simulators, and custom drivers — no I/O, no side effects.
673
+ Pure helpers backing typed task bindings and internal prompt-contract inference. Safe to use in editors, simulators, and custom drivers — no I/O, no side effects.
618
674
 
619
675
  | Function | Description |
620
676
  | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -623,7 +679,7 @@ Pure helpers backing lightweight bindings and `task.ports`. Safe to use in edito
623
679
  | `resolveTaskBindingInputs(task, upstreamData, dependsOn)` | Resolve lightweight task-level `inputs` from literal values, upstream outputs, stdout/stderr, normalized output, defaults, and required flags |
624
680
  | `resolveTaskInputs(task, upstreamOutputs, dependsOn)` | Gather the input values a task will consume from its direct upstreams. Applies `from` bindings, defaults, and type coercion. Returns `{ kind: 'ready', inputs, missingOptional }` or `{ kind: 'blocked', missingRequired, ambiguous, typeErrors, reason }` |
625
681
  | `extractTaskBindingOutputs(outputs, stdout, stderr, normalizedOutput)` | Publish lightweight task-level `outputs` from final-line JSON, stdout/stderr, normalized output, literal values, or defaults |
626
- | `extractTaskOutputs(ports, stdout, normalizedOutput)` | Pull declared output values from a terminated task's output. Strategy: prefer `normalizedOutput`; find the last non-empty line that parses as a JSON object; coerce each declared key. Returns `{ outputs, diagnostic }` |
682
+ | `extractTaskOutputs(ports, stdout, normalizedOutput)` | Internal helper for inferred prompt contracts. Strategy: prefer `normalizedOutput`; find the last non-empty line that parses as a JSON object; coerce each declared key. Returns `{ outputs, diagnostic }` |
627
683
  | `prependContext(doc, block)` | Same shape as `appendContext` but prepends; the engine uses this to place `[Output Format]` and `[Inputs]` blocks before middleware-added context |
628
684
  | `renderInputsBlock(inputsDecl, values)` | Build the `[Inputs]` `PromptContextBlock` rendered into AI prompts (`name: value # description` lines). Returns `null` when no inputs to render |
629
685
  | `renderOutputSchemaBlock(outputsDecl)` | Build the `[Output Format]` `PromptContextBlock` instructing the model to emit a final-line JSON object matching the declared outputs. Returns `null` when no outputs declared |
@@ -634,7 +690,7 @@ Custom drivers that wrap the prompt in their own envelope can read `DriverContex
634
690
 
635
691
  Validates a raw pipeline config without resolving inheritance or executing anything. Returns a flat list of `{ path, message, severity? }` objects — empty array means valid. `severity` is `'error'` (default, fatal) or `'warning'` (soft hint; non-blocking).
636
692
 
637
- Checks: required fields, `prompt`/`command` exclusivity, duplicate task IDs within a track, `depends_on`/`continue_from` reference integrity (including ambiguous bare refs that exist in multiple tracks — use `trackId.taskId` to disambiguate), circular dependency detection, port shape (name format, valid `type`, duplicate names, `enum` requires non-empty `enum` array, `required`/`from` ignored on outputs), `{{inputs.<name>}}` references resolving to a declared input port, and `permissions` shape (must be an object with boolean `read`/`write`/`execute`). Tolerant of half-built configs — non-array `tracks` or `tasks` produce a structured error instead of throwing.
693
+ Checks: required fields, `prompt`/`command` exclusivity, duplicate task IDs within a track, `depends_on`/`continue_from` reference integrity (including ambiguous bare refs that exist in multiple tracks — use `trackId.taskId` to disambiguate), circular dependency detection, binding shape (name format, valid `type`, duplicate names, `enum` requires non-empty `enum` array), `ports` migration errors, `{{inputs.<name>}}` references resolving to a declared or inferred input binding, and `permissions` shape (must be an object with boolean `read`/`write`/`execute`). Tolerant of half-built configs — non-array `tracks` or `tasks` produce a structured error instead of throwing.
638
694
 
639
695
  Plugin-type checks are opt-in via `knownTypes`: when provided, references to trigger/completion/middleware/driver types that are neither built-in nor in the supplied set produce a **warning** (`severity: 'warning'`) so editors can light up uninstalled plugins without blocking save / run. Omit `knownTypes` for offline / pre-load validation — no plugin warnings are emitted in that case.
640
696
 
@@ -694,7 +750,9 @@ Use `buildDag` instead when you have a fully resolved `PipelineConfig` and need
694
750
  Dual-channel logger — console + file. Creates a per-run log file at `<workDir>/.tagma/logs/<runId>/pipeline.log`.
695
751
 
696
752
  ```ts
697
- const logger = new Logger(workDir, runId);
753
+ import { bunRuntime } from '@tagma/sdk';
754
+
755
+ const logger = new Logger(workDir, runId, bunRuntime().logStore);
698
756
  logger.info('[track]', 'message'); // console + file
699
757
  logger.warn('[track]', 'message'); // console + file
700
758
  logger.error('[track]', 'message'); // console + file
@@ -706,13 +764,14 @@ logger.dir; // run artifact directory
706
764
  logger.close(); // close the persistent file handle (called automatically when Tagma.run completes)
707
765
  ```
708
766
 
709
- Pass an optional third argument to stream every appended line out as a
767
+ Pass an optional fourth argument to stream every appended line out as a
710
768
  structured `LogRecord`; the engine uses this to emit `task_log` events:
711
769
 
712
770
  ```ts
771
+ import { bunRuntime } from '@tagma/sdk';
713
772
  import { Logger, type LogRecord } from '@tagma/sdk/logger';
714
773
 
715
- const logger = new Logger(workDir, runId, (record: LogRecord) => {
774
+ const logger = new Logger(workDir, runId, bunRuntime().logStore, (record: LogRecord) => {
716
775
  // record = { level, taskId, timestamp, text }
717
776
  // level = 'info' | 'warn' | 'error' | 'debug' | 'section' | 'quiet'
718
777
  // taskId is extracted from a '[task:<id>]' prefix, or null for untagged lines
@@ -751,6 +810,8 @@ Truncates `text` to at most `maxBytes` UTF-8 bytes (default 16 KB), appending a
751
810
  | Package | Description |
752
811
  | ---------------------------------------------------------------------------------------- | --------------------------------------------- |
753
812
  | [@tagma/types](https://www.npmjs.com/package/@tagma/types) | Shared TypeScript types |
813
+ | [@tagma/core](https://www.npmjs.com/package/@tagma/core) | Runtime-independent orchestration core |
814
+ | [@tagma/runtime-bun](https://www.npmjs.com/package/@tagma/runtime-bun) | Bun runtime implementation |
754
815
  | [@tagma/driver-codex](https://www.npmjs.com/package/@tagma/driver-codex) | Codex CLI driver plugin |
755
816
  | [@tagma/driver-claude-code](https://www.npmjs.com/package/@tagma/driver-claude-code) | Claude Code CLI driver plugin |
756
817
  | [@tagma/middleware-lightrag](https://www.npmjs.com/package/@tagma/middleware-lightrag) | LightRAG knowledge-graph retrieval middleware |
@@ -1,6 +1,2 @@
1
- import type { ApprovalGateway } from '../approval';
2
- export interface StdinApprovalAdapter {
3
- readonly detach: () => void;
4
- }
5
- export declare function attachStdinApprovalAdapter(gateway: ApprovalGateway): StdinApprovalAdapter;
1
+ export * from '../runtime/adapters/stdin-approval';
6
2
  //# sourceMappingURL=stdin-approval.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdin-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,aAAa,CAAC;AAQpE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,eAAe,GAAG,oBAAoB,CA4FzF"}
1
+ {"version":3,"file":"stdin-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"AAAA,cAAc,oCAAoC,CAAC"}
@@ -1,90 +1,2 @@
1
- import * as readline from 'readline';
2
- export function attachStdinApprovalAdapter(gateway) {
3
- const queue = [];
4
- let processing = false;
5
- let rl = null;
6
- function ensureReadline() {
7
- if (!rl) {
8
- rl = readline.createInterface({ input: process.stdin, terminal: false });
9
- }
10
- return rl;
11
- }
12
- function readOneLine() {
13
- return new Promise((resolvePromise) => {
14
- const reader = ensureReadline();
15
- const handler = (line) => {
16
- reader.off('line', handler);
17
- resolvePromise(line);
18
- };
19
- reader.on('line', handler);
20
- });
21
- }
22
- async function processNext() {
23
- if (processing)
24
- return;
25
- processing = true;
26
- try {
27
- while (queue.length > 0) {
28
- const req = queue.shift();
29
- // If the request was already resolved by another path while queued, skip it.
30
- if (!gateway.pending().some((p) => p.id === req.id))
31
- continue;
32
- process.stdout.write(`\n[APPROVAL REQUIRED] ${req.message}\n` +
33
- ` id: ${req.id}\n` +
34
- ` task: ${req.taskId}${req.trackId ? ` (track: ${req.trackId})` : ''}\n` +
35
- ` approve / reject > `);
36
- const input = (await readOneLine()).trim().toLowerCase();
37
- const approveAliases = new Set(['approve', 'yes', 'y', 'ok', 'true', '1']);
38
- const rejectAliases = new Set(['reject', 'no', 'n', 'deny', 'false', '0']);
39
- if (approveAliases.has(input)) {
40
- gateway.resolve(req.id, { outcome: 'approved', actor: 'cli' });
41
- }
42
- else if (rejectAliases.has(input)) {
43
- gateway.resolve(req.id, {
44
- outcome: 'rejected',
45
- actor: 'cli',
46
- reason: 'user rejected via CLI',
47
- });
48
- }
49
- else {
50
- process.stdout.write(` unrecognized input "${input}" — treating as rejection\n`);
51
- gateway.resolve(req.id, {
52
- outcome: 'rejected',
53
- actor: 'cli',
54
- reason: `unrecognized CLI input: ${input}`,
55
- });
56
- }
57
- }
58
- }
59
- finally {
60
- processing = false;
61
- }
62
- }
63
- const unsubscribe = gateway.subscribe((event) => {
64
- switch (event.type) {
65
- case 'requested':
66
- queue.push(event.request);
67
- void processNext();
68
- return;
69
- case 'resolved':
70
- case 'expired':
71
- case 'aborted': {
72
- // Drop from queue if it's still waiting its turn.
73
- const idx = queue.findIndex((r) => r.id === event.request.id);
74
- if (idx >= 0)
75
- queue.splice(idx, 1);
76
- return;
77
- }
78
- }
79
- });
80
- return {
81
- detach: () => {
82
- unsubscribe();
83
- if (rl) {
84
- rl.close();
85
- rl = null;
86
- }
87
- },
88
- };
89
- }
1
+ export * from '../runtime/adapters/stdin-approval';
90
2
  //# sourceMappingURL=stdin-approval.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stdin-approval.js","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stdin-approval.js","sourceRoot":"","sources":["../../src/adapters/stdin-approval.ts"],"names":[],"mappings":"AAAA,cAAc,oCAAoC,CAAC"}
@@ -1,28 +1,2 @@
1
- import type { ApprovalGateway } from '../approval';
2
- export interface WebSocketApprovalAdapterOptions {
3
- port?: number;
4
- hostname?: string;
5
- /**
6
- * M11: shared secret required from the client during the WebSocket
7
- * upgrade. The token can be supplied either as the `?token=` query
8
- * parameter or in the `x-tagma-token` request header. When set, any
9
- * upgrade request that fails the check is rejected with HTTP 401 and
10
- * never reaches the WebSocket layer (so a misconfigured client cannot
11
- * exhaust rate-limit slots either). Leave undefined for backward
12
- * compatibility with localhost-only deployments.
13
- */
14
- token?: string;
15
- /**
16
- * M11: opt-out of origin checking. Defaults to false, meaning Origin
17
- * headers are restricted to loopback hosts (localhost / 127.0.0.1 / ::1).
18
- * Requests without an Origin header are still allowed so non-browser local
19
- * clients can connect. Set true only for trusted reverse-proxy setups.
20
- */
21
- allowAnyOrigin?: boolean;
22
- }
23
- export interface WebSocketApprovalAdapter {
24
- readonly port: number;
25
- readonly detach: () => void;
26
- }
27
- export declare function attachWebSocketApprovalAdapter(gateway: ApprovalGateway, options?: WebSocketApprovalAdapterOptions): WebSocketApprovalAdapter;
1
+ export * from '../runtime/adapters/websocket-approval';
28
2
  //# sourceMappingURL=websocket-approval.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/websocket-approval.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,aAAa,CAAC;AAoBlE,MAAM,WAAW,+BAA+B;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;CAC7B;AAQD,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,+BAAoC,GAC5C,wBAAwB,CAmJ1B"}
1
+ {"version":3,"file":"websocket-approval.d.ts","sourceRoot":"","sources":["../../src/adapters/websocket-approval.ts"],"names":[],"mappings":"AAAA,cAAc,wCAAwC,CAAC"}