# @codemation/core

## 0.15.0

### Minor Changes

- [#270](https://github.com/MadeRelevant/codemation/pull/270) [`60f23a3`](https://github.com/MadeRelevant/codemation/commit/60f23a37660cdda34e3d61acca8b2bf581a9db0e) Thanks [@cblokland90](https://github.com/cblokland90)! - Enrich deployment manifest with real trigger poll config and interval (B3c/[#310](https://github.com/MadeRelevant/codemation/issues/310)).

  `emitWorkflowManifest` now captures each polling trigger node's actual config object and poll interval into `ManifestTriggerConfig`. `ManifestTriggerConfig` gains an optional `pollIntervalMs` field. A dedicated `PollingTriggerConfig` interface carries `getTriggerPollConfig()` — only polling trigger nodes implement it; `NodeConfigBase` stays clean. `DefinedPollingTriggerConfig` implements `PollingTriggerConfig`, resolving `cfg.pollIntervalMs` (user instance override) falling back to the node-author default from `definePollingTrigger({ pollIntervalMs })`. The emitter narrows to `PollingTriggerConfig` via a type guard before calling the method. Bare trigger nodes that do not implement the interface continue to emit `config: {}` and no interval.

- [#254](https://github.com/MadeRelevant/codemation/pull/254) [`510e0d8`](https://github.com/MadeRelevant/codemation/commit/510e0d8a5f137a4fe10f43cbf46e40d05fea4977) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): code-parity guard — fail loudly when nodes resolve to MissingRuntime during run resume ([#271](https://github.com/MadeRelevant/codemation/issues/271))

  A woken runner must run the exact built code. Any node that resolves to MissingRuntime during resume or worker execution now causes a loud WorkflowParityMismatchError with the mismatched node ids and token ids, persisted as a failed run. Previously these nodes were silently skipped and the run completed as if nothing went wrong.

  New exports: `WorkflowParityMismatchError`, `MissingRuntimeParityGuard`, `MissingRuntimeNodeDetail`.

- [#263](https://github.com/MadeRelevant/codemation/pull/263) [`0dc8a9d`](https://github.com/MadeRelevant/codemation/commit/0dc8a9def376e4cd1821f3b92b0f887720e0c842) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): remove credentialHealthSeeds from WorkflowDeploymentManifest ([#304](https://github.com/MadeRelevant/codemation/issues/304))

  The manifest carries credential shapes only. Health is CP-side concern computed from the vault at query time.

- [#262](https://github.com/MadeRelevant/codemation/pull/262) [`3facadf`](https://github.com/MadeRelevant/codemation/commit/3facadf3bc09d21f33e698213a521fb64168d5dd) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): optional `createSessionFromAccessToken` on the OAuth credential type contract (E2 [#279](https://github.com/MadeRelevant/codemation/issues/279))

  OAuth credential types can now build an authenticated session from a bearer access token alone, with no client secret. This is the seam the scale-to-zero trigger-service uses: the control-plane is the sole credential store and the OAuth client secret never leaves it (epic [#261](https://github.com/MadeRelevant/codemation/issues/261) LD19) — the runner/trigger-service receives only a short-lived access token and constructs the vendor session from it.

  `CredentialType` gains an optional `createSessionFromAccessToken(args: { accessToken, grantedScopes, publicConfig })` method. It is additive and non-breaking — existing credential types are unaffected. `@codemation/core-nodes-gmail` implements it (the Google client accepts a bearer token directly). Microsoft Graph is intentionally not implemented yet: it builds its session through `ConfidentialClientApplication` + refresh-token exchange and genuinely requires the client secret, so it stays on the secret-bearing `createSession` path until a token-only path exists.

- [#269](https://github.com/MadeRelevant/codemation/pull/269) [`c08cf33`](https://github.com/MadeRelevant/codemation/commit/c08cf33ead44fec64d3d43b9294fccfdf6674156) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core-nodes-gmail): rewrite Gmail trigger to plain-node definePollingTrigger ([#306](https://github.com/MadeRelevant/codemation/issues/306))

  Converts OnNewGmailTriggerNode from a DI class with 6 injected deps to a definePollingTrigger definition (matching the msgraph pattern). Identity preserved byte-identical: @codemation/core-nodes-gmail::OnNewGmailTriggerNode.
  - Removes 8 DI registrations from GmailNodesRegistry (trigger services inlined)
  - Module-scope singletons preserve GmailConfiguredLabelService label cache across poll cycles
  - OnNewGmailTrigger legacy DSL config class still works; its type token points at the new runtime
  - Adds packageName? option to DefinePollingTriggerOptions in core to enable cross-package identity stamping

- [#282](https://github.com/MadeRelevant/codemation/pull/282) [`597fa8f`](https://github.com/MadeRelevant/codemation/commit/597fa8f697300d8d861a02c11e9819892f7947fa) Thanks [@cblokland90](https://github.com/cblokland90)! - Enrich `ManifestCanvas` nodes with `type`, `role`, `triggerKind`, `declaredOutputPorts`, and `declaredInputPorts` so the CP can render a faithful workflow diagram while the workspace pod is asleep. Bumps `DEPLOYMENT_MANIFEST_SCHEMA_VERSION` to 2; v1 manifests still deserialize gracefully (new fields are optional).

- [#268](https://github.com/MadeRelevant/codemation/pull/268) [`b15f8c6`](https://github.com/MadeRelevant/codemation/commit/b15f8c68125e3ec3082bab8d79414871f942e409) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): emitWorkflowManifest — pure build-time manifest emit function

- [#279](https://github.com/MadeRelevant/codemation/pull/279) [`ab5185a`](https://github.com/MadeRelevant/codemation/commit/ab5185a839118868eda1aab42b82f7a921037f37) Thanks [@cblokland90](https://github.com/cblokland90)! - Carry trigger item data through the trigger→runner dispatch ([#321](https://github.com/MadeRelevant/codemation/issues/321) JSON payload, [#317](https://github.com/MadeRelevant/codemation/issues/317) binaries via shared S3).

  `DispatchItemMeta` is reshaped from a per-attachment `{ id, mimeType?, filename? }` into a per-item shape `{ id, json?, binary? }`, where `binary` is a name-keyed map of `BinaryAttachment` refs (`DispatchBinaryRef`). The trigger service now sends each item's real `item.json` inline and one dispatch entry per item; `TriggerDispatchRegistrar` reconstructs `{ json, binary }` for the runner so downstream nodes receive the trigger's actual data instead of `undefined`.

  For binaries the metadata-first contract is kept: only the storage ref (`storageKey`/`storageDriver`/…) crosses the wire, never bytes/base64. The trigger runtime is wired symmetrically to the runner's shared `BinaryStorage` — `TriggerRuntimeContainerFactory` accepts a `BinaryStorage` and registers it at `CoreTokens.BinaryStorage` and into its `DefaultExecutionContextFactory`, and `InProcessTriggerInvoker` node-scopes `ctx.binary` so a trigger node's `ctx.binary.attach()` writes to the shared bucket. The `BINARY_STORAGE_KIND` / `BINARY_STORAGE_S3_*` keys are validated in the trigger-service `AppConfig` Zod schema (fail-fast at boot, the single `process.env` port), the same keys the host runner reads, so both sides share one bucket and the runner re-fetches bytes purely by `storageKey`. The composition root selects an in-memory store (default) or builds S3 via `S3TriggerBinaryStorageFactory` from the validated config; the factory never touches `process.env`.

- [#265](https://github.com/MadeRelevant/codemation/pull/265) [`fd188a2`](https://github.com/MadeRelevant/codemation/commit/fd188a2bac65c86dd62cf2f540034769c5bc0ac7) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core,host): TriggerInvoker port + InProcessTriggerInvoker slim impl (B2 PR1, [#274](https://github.com/MadeRelevant/codemation/issues/274))

  Adds a `TriggerInvoker` port to `@codemation/core` contracts (`invoke(trigger, config, lastRanAt) → Items`) and a `CoreTokens.TriggerInvoker` DI symbol.

  `@codemation/host` ships `InProcessTriggerInvoker`: a slim host implementation that runs one poll cycle for a given `TriggerNode` (no setInterval loop), captures items via the emit callback, persists the resulting state to `TriggerSetupStateRepository`, and returns the collected items. Registered lazily under `CoreTokens.TriggerInvoker` in `AppContainerFactory`.

  Open: `lastRanAt` → opaque node state reconstruction is PR2's responsibility.

### Patch Changes

- [#257](https://github.com/MadeRelevant/codemation/pull/257) [`a5457bb`](https://github.com/MadeRelevant/codemation/commit/a5457bb58edafec01e85cdcf6d30f9ca54521e27) Thanks [@cblokland90](https://github.com/cblokland90)! - Wire parity guard into HITL suspension resume path — resumeRun now fails loudly on missing-runtime nodes, preventing silent completions against mismatched code.

- [#253](https://github.com/MadeRelevant/codemation/pull/253) [`d5f49f7`](https://github.com/MadeRelevant/codemation/commit/d5f49f7f80deafc109dbc29f16ff0d571ee8591a) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): add WorkflowDeploymentManifest schema + serializer (CT1a)

- [#249](https://github.com/MadeRelevant/codemation/pull/249) [`364507a`](https://github.com/MadeRelevant/codemation/commit/364507a9fba45fd66cb208d5a41ee1f3bdc68311) Thanks [@cblokland90](https://github.com/cblokland90)! - Add transport-agnostic WorkflowDispatch contract (CT2, scale-to-zero [#263](https://github.com/MadeRelevant/codemation/issues/263))

- [#247](https://github.com/MadeRelevant/codemation/pull/247) [`bfdd759`](https://github.com/MadeRelevant/codemation/commit/bfdd7590b4903676b223c2f302b9bcd0f4a4583c) Thanks [@cblokland90](https://github.com/cblokland90)! - Remove all human-written comments from TypeScript source files and add `codemation/no-comments` ESLint rule to enforce self-describing code going forward.

- [#276](https://github.com/MadeRelevant/codemation/pull/276) [`0a681b3`](https://github.com/MadeRelevant/codemation/commit/0a681b357afd7b15ba3925788c732fd439d8e6b0) Thanks [@cblokland90](https://github.com/cblokland90)! - Add `schedulePollingTrigger` to core-nodes — a credential-less polling trigger that emits one `{ firedAt, tick }` item per cycle with a configurable `pollIntervalMs` (default 60 s). Its title is "Run on schedule" and it fires one tick item on every poll cycle. Fix missing `packageName` on `onNewMsGraphMailTrigger` so its `nodeTypeId` resolves correctly in `PersistedWorkflowTokenRegistry`. Add `nodeTypeId` field to `ManifestTriggerConfig` and populate it in `emitWorkflowManifest` so the CP scheduler can source the correct value to forward to the trigger-service `/poll` endpoint. Swap the legacy `CronTrigger` out of the builder agent-skills and examples in favour of `schedulePollingTrigger.create(...)` so the building agent is steered to the interval trigger.

- [#248](https://github.com/MadeRelevant/codemation/pull/248) [`f2aa0c4`](https://github.com/MadeRelevant/codemation/commit/f2aa0c42b2f9d2e9eb7bc4f3393f74342f7ef460) Thanks [@cblokland90](https://github.com/cblokland90)! - Extract `TriggerPollingPort` interface from `PollingTriggerHandle` as the explicit seam for the Trigger Service. `TriggerSetupContext.polling` stays typed as `PollingTriggerHandle` (no breaking change); `TriggerPollingPort` is the minimum contract the Trigger Service needs to implement.

- [#275](https://github.com/MadeRelevant/codemation/pull/275) [`cf2b146`](https://github.com/MadeRelevant/codemation/commit/cf2b146b452317e1ccaa1dfeaaa1a0e153181e30) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): populate webhook trigger config in deployment manifest

  `emitWorkflowManifest` now writes `{ endpointKey, methods }` into `ManifestTriggerConfig.config` for webhook-based trigger nodes, giving CP the data it needs to build the path→workspace index.

## 0.14.0

### Minor Changes

- [#225](https://github.com/MadeRelevant/codemation/pull/225) [`675260a`](https://github.com/MadeRelevant/codemation/commit/675260aa7a30c55a34b5a6107ad2222937f4a769) Thanks [@cblokland90](https://github.com/cblokland90)! - DSL `mergeForward(config)` — the config-level twin of `.thenMerge`, usable inside `.when([...])` branch arms to merge a node's output onto item.json instead of replacing it.

- [#234](https://github.com/MadeRelevant/codemation/pull/234) [`13f9f11`](https://github.com/MadeRelevant/codemation/commit/13f9f11454855f44cd0849ea7e8f0c60f4a28964) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): make `description` a first-class config option every authorable node accepts directly

  A node's plain-language `description` (the non-technical "what does this step do" line rendered in
  the node properties sidebar) is now a normal option passed inline alongside `id`, exactly like any
  other config field — no wrapper or clone helper:

  ```ts
  new Callback("Tag order as received", markReceived, { id: "mark", description: "Tags the order." });
  new If("High value?", (i) => i.json.total > 1000, { id: "gate", description: "Routes big orders." });
  new CronTrigger("Nightly", { schedule: "0 2 * * *" }, { id: "nightly", description: "Runs nightly." });
  ```

  - `NodeConfigBase` gains `readonly description?: string` (mirrors `icon`), and the
    `WorkflowSnapshotCodec` carries it into the persisted snapshot config the host / canvas mappers
    read — so the sidebar renders it. (`description` was added to the codec's non-serializable
    fallback whitelist for parity with `icon`.)
  - Every authorable built-in node threads `description` into its config: options-object nodes
    (`Callback`, `MapData`, `Assertion`, `HttpRequest`, `AIAgent`, `TestTrigger`) accept it in their
    options; bare-id nodes (`If`, `Split`, `Filter`, `Aggregate`, `Wait`, `Switch`, `SubWorkflow`,
    `Merge`, `NoOp`, `IsTestRun`, `CronTrigger`, `WebhookTrigger`, `ManualTrigger`) now take a final
    `string | { id?: string; description?: string }` argument — a bare `"id"` string still works
    (back-compat).
  - The `define*` factory helpers carry it too: `defineNode`, `defineBatchNode`,
    `definePollingTrigger` (and everything layered on `defineNode` — `defineRestNode`,
    `defineHumanApprovalNode`/`inboxApproval`, and the collection CRUD nodes) now accept
    `{ id?, description? }` in the trailing `create(config, name, idOrOptions)` slot, so a per-instance
    `description` survives into the persisted snapshot. A bare string id still works (back-compat).
  - The `workflow-dsl` skill teaches business-action node titles and a one-line `description` set
    directly in each node's options.

  This supersedes the rejected `present(node, { description })` clone-helper approach (PR [#232](https://github.com/MadeRelevant/codemation/issues/232)/[#233](https://github.com/MadeRelevant/codemation/issues/233)):
  `description` is a normal inline option, not a prototype clone.

- [#225](https://github.com/MadeRelevant/codemation/pull/225) [`675260a`](https://github.com/MadeRelevant/codemation/commit/675260aa7a30c55a34b5a6107ad2222937f4a769) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): DSL `.thenMerge(node)` + engine merge-json behavior

  Adds a clean carry-forward mechanism mirroring `keepBinaries`. A node appended with
  `.thenMerge(...)` has its output **merged onto `item.json`** (shallow, output-wins)
  instead of replacing it, so earlier fields survive a transform/OCR/extraction node —
  e.g. Gmail trigger metadata captured upstream survives `codemationDocumentScannerNode`
  (`{ markdown, fields }`) or an `AIAgent` extraction step.
  - Engine: `NodeOutputNormalizer.applyOutput` honors a new `mergeJson` behavior flag,
    shallow-merging node output onto the input json when both are plain objects (falls back
    to replace otherwise). Merge applies per fanned-out element.
  - `RunnableOutputBehaviorResolver` resolves `mergeJson` off the node config the same way
    it resolves `keepBinaries`.
  - DSL: `ChainCursor.thenMerge(config)` stamps the flag on the config and returns a cursor
    typed as the intersection `TCurrentJson & RunnableNodeOutputJson<TConfig>` so downstream
    steps see both the prior fields and the node's output.

- [#225](https://github.com/MadeRelevant/codemation/pull/225) [`675260a`](https://github.com/MadeRelevant/codemation/commit/675260aa7a30c55a34b5a6107ad2222937f4a769) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(dsl): boolean `.when` branches auto-merge when you continue the chain

  A boolean `.when(true, [...]).when(false, [...])` chain is no longer terminal. Continuing the
  trunk with `.then(...)` or `.humanApproval(...)` now connects ALL accumulated branch tails into
  the next node — the same fan-in merge the object form `.when({ true: [...], false: [...] })`
  already produced.

  ```ts
  workflow
    .start(...)
    .then(checkOrder.create(...))        // an If node
    .when(true, [enrich.create(...)])
    .when(false, [flag.create(...)])
    .then(notify.create(...))            // BOTH branch tails merge into `notify`
    .build();
  ```

  An empty arm (`.when(true, [])`) contributes the branch port off the If node, so it rejoins
  straight from the port on continuation — the old empty-arm "rejoin" hack is no longer needed.
  `.build()` on a branch chain (without continuation) still terminates as before, and the object
  form / `.route(...)` paths are unchanged.

  The continued cursor is typed `ChainCursor<TCurrentJson>` (the pre-branch item type): boolean
  arms carry no output guard, so the merged item type is underdetermined — use the object form when
  you need a precise merged type inline.

- [#222](https://github.com/MadeRelevant/codemation/pull/222) [`0881b46`](https://github.com/MadeRelevant/codemation/commit/0881b4676d2db29f36415d9b47709128d4c15e0e) Thanks [@cblokland90](https://github.com/cblokland90)! - feat: add writeFile capability to workspace file storage + writeWorkspaceFileNode

  Adds a `writeFile` method to `IWorkspaceFileStorage` (and all three adapters:
  `LocalFilesystemWorkspaceFileStorage`, `S3WorkspaceFileStorage`,
  `PresignedS3WorkspaceFileStorage`) that writes bytes into the workspace file pool
  under a new, randomly-generated fileId.

  Adds `IWorkspaceFileRegistrar` / `WorkspaceFileRegistrarToken` for the optional
  CP registry hook (post a WorkspaceFile row so the concierge can see the file).

  Adds `writeWorkspaceFileNode` (`workspace-files.write`) to
  `@codemation/core-nodes-workspace-files`: reads from a binary slot, calls
  `writeFile`, optionally calls the registrar, returns metadata with a `registered`
  flag.

  The `/internal/workspace-files/register` CP endpoint is NOT yet implemented —
  the node will set `registered: false` until it is added to the control plane.

## 0.13.2

### Patch Changes

- [#205](https://github.com/MadeRelevant/codemation/pull/205) [`3317947`](https://github.com/MadeRelevant/codemation/commit/33179472db68b2f4fc1a15961ac389df25d8cb6f) Thanks [@cblokland90](https://github.com/cblokland90)! - perf(ci): parallelize integration test suite with per-worker database isolation

  Adds graceful drain-before-stop to InlineDrivingScheduler so in-flight setImmediate
  activations complete before the DB connection is closed or a test transaction rolls back.

- [#210](https://github.com/MadeRelevant/codemation/pull/210) [`d4c7aca`](https://github.com/MadeRelevant/codemation/commit/d4c7aca5248352b82313efb16d5792cc24875005) Thanks [@cblokland90](https://github.com/cblokland90)! - AgentToolFactory.asTool() now accepts a per-binding `onRejected` ("halt" | "return"). Setting it marks that tool binding as human-in-the-loop and takes precedence over any `humanApprovalToolBehavior` default carried by the backing node, so two tools backed by the same node can reject differently without cloning the node config.

## 0.13.1

### Patch Changes

- [#206](https://github.com/MadeRelevant/codemation/pull/206) [`2fcb715`](https://github.com/MadeRelevant/codemation/commit/2fcb7153d9c732b2f846b8a8d1cc5626b4363fa6) Thanks [@cblokland90](https://github.com/cblokland90)! - perf(ci): parallelize integration test suite with per-worker database isolation

  Adds graceful drain-before-stop to InlineDrivingScheduler so in-flight setImmediate
  activations complete before the DB connection is closed or a test transaction rolls back.

## 0.13.0

### Minor Changes

- [#189](https://github.com/MadeRelevant/codemation/pull/189) [`c1b081f`](https://github.com/MadeRelevant/codemation/commit/c1b081ffc8b66b0c4593c94f1d57a1cdf5c41140) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(core): add `getBytes`, `getText`, and `getJson<T>` to the binary API

  `ExecutionBinaryService` (and `NodeBinaryAttachmentService`) now expose three read helpers:
  - `getBytes(attachment, maxBytes?)` — bounded read into `Uint8Array` (size-checked before allocation).
  - `getText(attachment, maxBytes?)` — bounded read + UTF-8 decode.
  - `getJson<T>(attachment, maxBytes?)` — bounded read + decode + `JSON.parse` with a clear `SyntaxError` on invalid JSON.

  The duplicate `readBinaryBody` helpers previously copied across `core-nodes` and `core-nodes-ocr` are removed; all callers now use `ctx.binary.getBytes()`. `openReadStream` remains for true streaming use cases.

- [#185](https://github.com/MadeRelevant/codemation/pull/185) [`be520d2`](https://github.com/MadeRelevant/codemation/commit/be520d2755144a3709ecc109019b84e2c502337e) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(workspace-files): read-only workspace file storage adapter (story 01)

  Adds `IWorkspaceFileStorage` port + `WorkspaceFileStorageToken` to `@codemation/core` and
  S3 + local-fs driver implementations to `@codemation/host`. The adapter reads the shared
  workspace file pool using the same key scheme (`<workspaceId>/files/<fileId>`) and env
  config shape (`BLOB_STORAGE_*`) as the control plane. Registered in `AppContainerFactory`
  when `WORKSPACE_ID` is set; nodes reach it via `ctx.resolve(WorkspaceFileStorageToken)`.
  Read-only — no put/delete surface is exposed.

## 0.12.0

### Minor Changes

- [#167](https://github.com/MadeRelevant/codemation/pull/167) [`3044474`](https://github.com/MadeRelevant/codemation/commit/3044474495525490735510ff74500b53761284b6) Thanks [@cblokland90](https://github.com/cblokland90)! - feat(hitl): Human-in-the-Loop — engine suspend/resume, inbox approval node + channels (local + control-plane), agent-as-tool, decision/timeout handling, inbox decision UX (toast + node status icons + "waiting for approval"), plus the consolidated dev/canvas/host fixes shipped alongside.

## 0.11.1

### Patch Changes

- [#154](https://github.com/MadeRelevant/codemation/pull/154) [`e0933eb`](https://github.com/MadeRelevant/codemation/commit/e0933ebc51806a9593f94758860c591b8346a7a5) Thanks [@cblokland90](https://github.com/cblokland90)! - fix: compile `./browser` and `./contracts` subpath exports (were shipping raw .ts, broke Turbopack consumers)

  The `./browser` and `./contracts` subpaths in `@codemation/core` were pointing at raw TypeScript source files (`./src/browser.ts`, `./src/contracts.ts`). Turbopack (used by Next.js) and other browser bundlers refuse to process raw `.ts` from published npm packages, causing builds to fail with "Unknown module type" errors.

  Both entry points are now compiled by tsdown and the exports map points at `./dist/browser.{js,cjs,d.ts}` and `./dist/contracts.{js,cjs,d.ts}`. A `development` condition retains the direct-source path for framework-author mode. API surface is unchanged.

## 0.11.0

### Minor Changes

- 8285ec0: Add a `statusLabel` field to `ConnectionInvocationRecord` / `ConnectionInvocationAppendArgs` so connection invocations can carry a short human-readable description of what they are doing (e.g. `"calling search_messages"`). The engine-side `NodeRunStateWriter` persists it; the canvas-side mirror picks it up via the standard patch projection.

  Wire per-MCP-tool-call lifecycle invocations through `AgentMcpIntegration`. `prepareMcpTools` now accepts an optional `appendMcpInvocation` callback (plus the agent activation / iteration / item / parent-invocation context). When the host-side `AgentMcpIntegrationImpl` wraps a tool's `execute`, it emits a `running` record with `statusLabel: "calling <toolName>"` and a matching `completed` or `failed` record; the existing telemetry span and 403 `NeedsReconsentEvent` paths are preserved. `@codemation/canvas-core` exposes a `CurrentStatusLabelSelector` and `WorkflowCanvasNodeData.currentStatusLabel`; `@codemation/canvas` renders the latest non-empty label as a sub-line under the node card. The two capabilities work together: MCP tool calls under an agent now stream the same invocation events the LLM and node-backed tool paths already emit, and the canvas surfaces the running label per-node.

- 8285ec0: Add optional `subjectName?: string` to `ConnectionInvocationRecord` and `ConnectionInvocationAppendArgs` — a stable identifier for the thing an invocation acts on that persists across status transitions. The MCP integration's `wrapToolExecutes` sets it to the tool name on every transition (running / completed / failed), so the inspector's tool-call timeline entries can render `"Tool call · <toolName>"` for MCP servers (which expose many tools through a single connection node) instead of an opaque `"Tool call"`.

  For node-backed agent tools, the parent connection node id already encodes the tool name — `subjectName` stays unset there and the inspector renders the existing `"Tool call"` title unchanged.

  `statusLabel` (the running-only sentence rendered on the canvas card sub-line) is unchanged; `subjectName` is the persistent structural sibling used by the inspector.

- 8285ec0: Export `McpServerDeclaration` and `McpServerTransport` from `@codemation/core` main entry point and contracts subpath.
- 7b50018: feat(core-nodes,msgraph,gmail): inspectorSummary on every built-in node

  Implements `inspectorSummary()` on all built-in node and trigger config classes so the workflow
  inspector panel introduced in #136 has content for every shipped node.
  - `@codemation/core`: extends `definePollingTrigger` to accept and plumb an `inspectorSummary`
    option, mirroring the existing `defineNode` / `defineBatchNode` pattern. Also extends
    `defineRestNode` (in `@codemation/core-nodes`) with the same option.
  - `@codemation/core-nodes`: `inspectorSummary()` on `HttpRequest`, `AIAgent`, `CronTrigger`,
    `ManualTrigger`, `SubWorkflow`, `Callback`, `If`, `Switch`, `Filter`, `Split`, `Merge`,
    `Wait`, `WebhookTrigger`, `TestTrigger`, `Aggregate`, `MapData`, `Assertion`.
  - `@codemation/core-nodes-msgraph`: `inspectorSummary` option on all 17 mail/drive/excel nodes
    plus the `onNewMsGraphMailTrigger` polling trigger.
  - `@codemation/core-nodes-gmail`: `inspectorSummary()` on `OnNewGmailTrigger`.
    Gmail action nodes (`SendGmailMessage`, `ReplyToGmailMessage`, `ModifyGmailLabels`) return
    `undefined` — all their config is per-item via `inputSchema`, nothing to surface at design time.
  - `@codemation/core`: `WorkflowSnapshotCodec.serializeConfig` now pre-serializes the result of
    `inspectorSummary()` into the snapshot JSON as `_inspectorSummary` so the browser-side mapper
    can surface the same rows without calling class methods.
  - `@codemation/next-host`: `PersistedWorkflowSnapshotMapper` now reads `_inspectorSummary` from
    the serialized config and includes it in the node DTO, maintaining parity with the live mapper.

- 8285ec0: Add LocalOAuthFlowExecutor for framework (OSS/standalone) mode. Reads clientId from the credential instance's publicConfig and clientSecret from its secret material; builds PKCE-protected consent URLs; exchanges auth codes and refresh tokens directly against the provider's token endpoint. Also patches OAuthFlowExecutor.refresh to accept typeId and instanceId alongside the material, since looking up the tokenUrl and app credentials requires the instance.
- 8285ec0: Remove the MCP credential bypass on AI agents. `AIAgent.mcpServers` is now a plain
  `ReadonlyArray<string>` of server ids — the inline `{ credential }` field is gone. Each
  declared server surfaces a standard credential slot on the agent node (key
  `mcp:<serverId>`, label and accepted types from the MCP catalog) and binds through the
  same `CredentialBinding` table as every other slot. At execute time the host resolves the
  binding via `getBinding({ workflowId, agentNodeId, slotKey: mcp:<serverId> })`, then opens
  the MCP pool with the resolved credential instance — no more reading the credential id
  out of the workflow config.

  Breaking — config shape change. Replace:

  ```ts
  mcpServers: {
    gmail: {
      credential: "<instanceId>";
    }
  }
  ```

  with:

  ```ts
  mcpServers: ["gmail"];
  ```

  Then bind the credential through the canvas credential dropdown before activating the
  workflow, the same way trigger credentials are bound. The `McpServerBindings` /
  `McpServerExplicitBinding` types are removed from `@codemation/core`;
  `AgentMcpIntegration.prepareMcpTools` now takes `{ workflowId, agentNodeId, serverIds }`.

- 8285ec0: Replace `McpServerDeclaration.credentialKind` / `credentialTypeId` / `oauthAppKey` with `acceptedCredentialTypes?: ReadonlyArray<string>`, matching the `CredentialRequirement.acceptedTypes` shape. Absent or empty array means no credential required. Gmail MCP declaration now uses `["oauth.google.gmail"]`, the same type as the Gmail trigger node.
- 8285ec0: Add `McpServerDeclaration` type and `McpServerCatalog` service (Story 7).
  - `@codemation/core` exports `McpServerDeclaration` and `McpServerTransport` from `packages/core/src/contracts/mcpTypes.ts`.
  - `CodemationPlugin` gains an optional `mcpServers?: ReadonlyArray<McpServerDeclaration>` field.
  - `CodemationConfig` gains an optional `mcpServers?: ReadonlyArray<McpServerDeclaration>` field (also threaded through `AppConfig` and `DefineCodemationAppOptions`).
  - `McpServerCatalog` in `packages/host/src/mcp/` merges declarations from three sources (`plugin`, `config`, `controlPlane`) with deterministic precedence and validation (id regex, stdio gate, credential requirements).
  - `CodemationPluginDiscovery.isPluginConfig` now recognises `mcpServers`-only plugins.
  - Plugin registrar and app container factory wire catalog merge on startup.

- 8285ec0: MCP servers are now first-class agent connection slots with credential pickers.

  `AgentConnectionNodeCollector.collect()` accepts an optional `mcpServerResolver` callback (Pattern A). When provided, each entry in `agentConfig.mcpServers` is resolved and emitted as a `"tools"` connection slot descriptor — identical pattern to tools. Each MCP slot gets a stable node id via `ConnectionNodeIdFactory.mcpConnectionNodeId()` and exposes `getCredentialRequirements()` from the resolved `McpServerDeclaration`.

  `AIAgentConnectionWorkflowExpander` accepts the resolver in its constructor; `AppContainerFactory` wires `McpServerCatalog.get` there. MCP credential pickers are now visible on the canvas alongside tool slots.

  Removes the `AIAgent.inspectorSummary()` band-aid that listed MCP server ids as plain text — those are now first-class connection nodes rendered on the canvas directly.

- 0082ab5: Adds an `inspectorSummary` hook on node configs (and `defineNode({ inspectorSummary })` for plugin-author nodes). Returns 2–6 short label/value pairs that describe what the node will do at design time — model + prompt for an agent, method + URL for an HTTP call, schedule + timezone for a cron, etc. Surfaced in the workflow editor's node-properties panel as a new "Configuration" section that renders before any run telemetry exists. Hidden when no rows are produced; node configs that don't implement the hook contribute nothing. Built-in nodes will fill these in across follow-up PRs.
- 8285ec0: Define `OAuthFlowExecutor` interface — the mode-agnostic contract for the OAuth dance (start → callback → token storage) and refresh. Implementations (local and managed) will register behind this single interface via DI.
- 8285ec0: Remove deprecated broker-era MCP fields: `NeedsReconsentEvent.oauthAppKey`, shorthand `McpServerBindings` string array form, and `AgentMcpIntegrationImpl.autoResolveCredential`. Explicit binding (`{ serverId: { credential: "<instanceId>" } }`) is now the only supported form — eliminating ambiguity when multiple credential instances of the same type exist.
- 8285ec0: feat(host/binary): S3BinaryStorage implementation + boot connectivity check (Sprint 15 Story 03)

  Adds `S3BinaryStorage` — a Scaleway-compatible S3 implementation of `BinaryStorage` using
  `@aws-sdk/client-s3` + `@aws-sdk/lib-storage` (multipart for large payloads). Key scheme:
  `<workspaceId>/<runId>/<binaryId>`.

  Runtime selection is controlled by `BINARY_STORAGE_KIND` env var (`"local"` default | `"s3"`).
  When `"s3"`, all `BINARY_STORAGE_S3_*` vars are required and validated at boot. A `HeadBucket`
  connectivity check fails loudly on startup if the bucket is unreachable.

  Extends `BinaryStorage` interface (core) with `deleteMany(keys)` and `listByPrefix(prefix)` for
  bulk-delete (1000-key S3 batching) and workspace-prefix enumeration (GDPR erasure). All existing
  implementations (`InMemoryBinaryStorage`, `LocalFilesystemBinaryStorage`, `UnavailableBinaryStorage`)
  updated with correct implementations.

- 8285ec0: feat(story-11): Wire MCP catalog into agent — explicit and shorthand binding, scope validation, pool integration, telemetry, and runtime 403 detection
  - `@codemation/core`: `AgentMcpIntegration` interface + token, `McpServerBindings` types, `NeedsReconsentEvent`, `AgentBindError`, `NoOpAgentMcpIntegration` fallback, `CodemationTelemetryAttributeNames.mcpServerId/mcpToolName`
  - `@codemation/core-nodes`: `AIAgentConfig` + `AIAgent` extended with `mcpServers` and `pinnedMcpTools`; `DeferredMetaToolStrategy.ownsToolName` covers MCP tools; `AIAgentNode` injects `AgentMcpIntegration` and strips AI SDK auto-execute from strategy tools
  - `@codemation/host`: `AgentMcpIntegrationImpl` — resolves bindings, validates scopes, opens pool, wraps tool execute with telemetry spans and 403/permission error detection

### Patch Changes

- 8285ec0: Fix workflow detail screen hydration mismatch caused by overlay siblings (tabs, run button, error banner, realtime badge) being rendered conditionally on controller state that diverges between SSR and a warm React Query client cache. Overlay siblings are now gated behind the same `hasMounted` flag as the canvas root.

  Render AIAgent MCP-server attachments in the canvas. `WorkflowDefinitionMapper` (the server-side mapper that feeds `/api/workflows/:id`) now passes an `McpServerResolver` backed by the host's `McpServerCatalog` to `AgentConnectionNodeCollector.collect`, so virtual connection nodes for declared `mcpServers` are emitted alongside the LLM and tool children. The MCP descriptor itself carries `icon: "lucide:plug"` and `lucide:plug` is added to the curated `WorkflowCanvasLucideIconRegistry` so MCP servers render with a distinct icon on the synchronous zero-HTTP path.

- 8285ec0: test: push @codemation/core coverage to ≥90% (Sprint 16 Story 01)
- 8285ec0: Stop leaking `node:crypto` and `node:module` into canvas's browser bundle. `NodeIterationIdFactory` and `ConnectionInvocationIdFactory` now use `globalThis.crypto.randomUUID()` instead of importing `randomUUID` from `node:crypto`. Canvas's `tsdown` build is configured with `platform: "neutral"` so the dist no longer ships `createRequire(import.meta.url)` from `node:module`. Fixes consumer Turbopack OOMs when the canvas dist is included in a Next.js client bundle.
- e4d3e1a: perf(core): yield event loop between node activations in InlineDrivingScheduler

  Switch `scheduleDrain` from `setTimeout(0)` to `setImmediate` and process one
  activation per drain call instead of draining the entire queue in a while loop.
  This ensures HTTP responses and WebSocket frames can flush to clients between
  node activations — previously synchronous SQLite writes during a 20-node run
  could block the proxy event loop for 3–4 s, making the canvas appear frozen
  until the run completed.

- 8285ec0: MCP credential slots now live on the MCP connection node, matching ChatModel and Tool
  connection nodes. Each declared `mcpServers` entry materializes an MCP connection node
  and the credential slot is attached to that node with slot key `"credential"` (label
  and accepted types derived from the MCP catalog declaration). The standard credential
  slot traversal picks them up via `AgentConnectionNodeCollector` — no special-case path.

  Removed the agent-owned `mcp:<serverId>` slot key. Removed the `mcpSlotKey(serverId)`
  helper from `@codemation/core` (and its re-export from the type-only `contracts`
  subpath). At runtime, `AgentMcpIntegration.prepareMcpTools` now resolves the binding at
  `(workflowId, ConnectionNodeIdFactory.mcpConnectionNodeId(agentNodeId, serverId), "credential")`.

  Gmail MCP `requiredScopes` trimmed to `["https://www.googleapis.com/auth/gmail.modify"]`
  — `gmail.modify` is a superset of `gmail.readonly` + `gmail.send` for messages, threads,
  drafts, and labels, so the previous list was redundant.

- e4d3e1a: perf(core): fail fast on unbound required credential slots before node execution

  `NodeExecutor` now checks all required (non-optional) credential slots via
  `ctx.getCredential` before entering the retry runner or calling the node's
  `execute`. This means a misconfigured credential surfaces as an immediate error
  without burning the retry budget or running any node setup work. The session is
  created and cached during the pre-flight, so the node itself pays no extra cost
  when it subsequently calls `getCredential`. Optional slots (`optional: true` in
  `getCredentialRequirements`) are skipped.

  Also adds a `shouldRetry` predicate to `InProcessRetryRunner.run` and uses it
  in `NodeExecutor` to skip all retry delays when the node throws a
  `CredentialUnboundError` (or an error whose `.cause` is one). Previously, nodes
  like `AIAgent` that check credentials inside `execute` rather than via
  `getCredentialRequirements` would burn their full retry budget (e.g. 3 × 2 s
  for AI agents) before surfacing the "slot not bound" error.

- 8285ec0: fix: validate edge output ports against declared node ports at load time

  Adds `WorkflowEdgePortValidator` to `@codemation/core`. The validator checks that every edge's `from.output` port is declared by the source node's `declaredOutputPorts`; nodes without declared ports are treated as unconstrained (legacy behaviour).

  The validator is wired into `WorkflowDefinitionExportsResolver` in `@codemation/host`, which is the common chokepoint for both the `CodemationConsumerConfigLoader` and `CodemationConsumerAppResolver` load paths. On violation, all errors are reported at once so an agent can self-correct in a single pass.

  `WorkflowElkPortInfoResolver` in `@codemation/canvas-core` is tightened to render _exactly_ the declared ports (plus the synthetic `error` port when applicable) when a node has `declaredOutputPorts`, preventing phantom handles from rogue edges on the canvas. Legacy nodes without declared ports continue to infer ports from edges as before.

  Root cause: an LLM agent created an `If` workflow node (declares `["true", "false"]`) with a rogue edge using `output: "main"`, which the canvas unioned into the port list, producing a phantom third handle.

- 8285ec0: Sprint 14 coverage: tests for WhenBuilder DSL helper, InMemoryWorkflowExecutionRepository retention paths, DevTrackedProcessTreeKiller edge cases, ConsumerCliTsconfigPreparation resolution, ListenPortConflictDescriber ss fallback, RedisRunEventBus publish/subscribe/teardown, CodemationChatModelFactory HMAC signing, registerCoreNodes smoke, single-react-component-per-file rule branches, and CodemationAgentSkillsCli error/help paths. No production code changes.
- 8285ec0: Remove the `development` export condition from `@codemation/canvas`, `@codemation/core`, and `@codemation/host` package.json exports. Module resolution now consistently uses the built `dist/` regardless of `NODE_ENV`.

  **Why:** the `development` condition is auto-applied by bundlers (Next.js dev mode, Vite dev, etc.) and was making every cross-repo monorepo consumer fall through to TypeScript source. For the framework's own `@codemation/next-host`, this was fine — turbo's `dev` already runs `tsdown --watch` on these packages so dist is always fresh in dev. For external consumers (notably the managed control plane), it caused multi-hundred-file recursive source compiles on every cold page load.

  **Impact:** zero behavior change for normal users (they consume published `dist/`). Framework monorepo devs editing canvas/core/host source still see live updates as long as `tsdown --watch` is running for the package — which is what `pnpm dev` (turbo) orchestrates by default. If you're running an app in isolation without the package's watch task, you now need to start it explicitly.

## 0.10.2

### Patch Changes

- [#133](https://github.com/MadeRelevant/codemation/pull/133) [`d283b48`](https://github.com/MadeRelevant/codemation/commit/d283b481f01a1a259d38d25c1482006eff963384) Thanks [@cblokland90](https://github.com/cblokland90)! - feat: deep-link from parent run to specific subworkflow execution

  Adds `childRunId` to `NodeExecutionSnapshot` so the UI can navigate directly to the
  child run when a `SubWorkflow` node is selected in the execution inspector, instead of
  only linking to the child workflow's editor. Fixes the gap from PR [#131](https://github.com/MadeRelevant/codemation/issues/131).
  - `@codemation/core` (patch): `NodeExecutionSnapshot` gains `childRunId?: RunId`;
    `ExecutionInstanceDto` gains `childRunId?: string`;
    `NodeExecutionStatePublisher` gains optional `setChildRunId` method;
    `NodeExecutionSnapshotFactory` propagates `previous.childRunId` through
    `completed`, `failed`, and `skipped` transitions.
  - `@codemation/host` (minor): `ExecutionInstance` table gains `child_run_id` column
    (nullable, backward-compatible); `PrismaWorkflowRunRepository` persists and reads
    `childRunId` on node-activation snapshots.
  - `@codemation/next-host` (minor): `NodeExecutionSnapshot` type gains `childRunId`;
    `WorkflowExecutionInspectorDetailBody` renders "Open subworkflow run" (with
    `?run=<childRunId>`) when a child run id is present, falling back to
    "Open subworkflow editor" for pre-existing snapshots.

## 0.10.1

### Patch Changes

- [#127](https://github.com/MadeRelevant/codemation/pull/127) [`1f10121`](https://github.com/MadeRelevant/codemation/commit/1f10121a093ef0612a33c873419b032709c9964d) Thanks [@cblokland90](https://github.com/cblokland90)! - Add regression test suite confirming `item.binary` slots survive SubWorkflow boundaries in both directions (parent→child and child→parent), including stream readback of bytes across run boundaries. Document the shared-BinaryStorage pattern required for tests that call `ctx.binary.attach`.

## 0.10.0

### Minor Changes

- [#119](https://github.com/MadeRelevant/codemation/pull/119) [`847deb4`](https://github.com/MadeRelevant/codemation/commit/847deb4c42801632bfb970cdb2625cd0755241cb) Thanks [@cblokland90](https://github.com/cblokland90)! - Reset source version line back to 0.x. Earlier releases prematurely jumped these packages to 1.x and 2.x via silent `major` changesets buried under unrelated work; the framework is still in beta. The npm versions 1.x and 2.0.0 are deprecated upstream — consume the 0.x line going forward.
  - `@codemation/core` 2.0.0 → 0.9.0 (continues from 0.8.1)
  - `@codemation/core-nodes` 1.1.0 → 0.5.0 (continues from 0.4.3)
  - `@codemation/host` 1.1.0 → 0.4.0 (continues from 0.3.1)

  `@codemation/agent-skills`, `create-codemation`, `@codemation/cli`, and `@codemation/core-nodes-msgraph` already track 0.x and are unaffected.

  `create-codemation` template dependency ranges updated from `1.x` to `0.x` to track the corrected line.

## 2.0.0

### Major Changes

- [#102](https://github.com/MadeRelevant/codemation/pull/102) [`6566d55`](https://github.com/MadeRelevant/codemation/commit/6566d55c829f6631357ac95052b0852e86092ac5) Thanks [@cblokland90](https://github.com/cblokland90)! - **Breaking change:** Default node ids in `WorkflowBuilder` now derive from a slug of the node's label (`config.name`) instead of a sequential counter (`${tokenName}:${seq}`).

  Previously, adding or reordering nodes changed their auto-assigned ids, silently orphaning credential bindings stored in the database (keyed by `workflowId + nodeId + slotKey`). The new scheme makes ids stable across reorders and inserts.

  **Migration required:** Any existing credential bindings keyed by the old `${tokenName}:${seq}` format will appear unbound after this change. Users must re-bind credentials manually in the workflow editor. To avoid disruption, add an explicit `id:` field to node configs before upgrading — explicit ids are unaffected by this change and take priority over the label slug.

  **Validation added:** `WorkflowBuilder.build()` now throws `WorkflowDefinitionError` if any node has an empty effective id (blank label + no explicit id) or if two nodes share the same effective id. Fix: provide a unique `id:` on the offending node configs.

### Minor Changes

- [#111](https://github.com/MadeRelevant/codemation/pull/111) [`a77505f`](https://github.com/MadeRelevant/codemation/commit/a77505f331d7d3892f3c1c8f19dc37952b4d96bd) Thanks [@cblokland90](https://github.com/cblokland90)! - Add `definePollingTrigger` helper for declarative polling trigger authoring.

  Plugin authors can now define polling triggers with a single `definePollingTrigger({...})` call instead of manually wiring `PollingTriggerRuntime` + `RunnableNodeConfig` + `@node` class pairs. The helper synthesises both the trigger config class and the runtime adapter, handles internal dedup-key bookkeeping, and exposes a `poll()` test seam for unit testing without spinning up the runtime.

- [#101](https://github.com/MadeRelevant/codemation/pull/101) [`2c0723f`](https://github.com/MadeRelevant/codemation/commit/2c0723fb1670e842c272939b5db73d4b95b25535) Thanks [@cblokland90](https://github.com/cblokland90)! - Add collections: declare typed Postgres/SQLite-backed data tables in the codemation config via `defineCollection({...})`. Schema sync runs at runtime startup behind an advisory lock (Postgres) or in-process mutex (SQLite).

  Workflow access:
  - `ctx.collections.<name>.crud(...)` from inside custom node code
  - Six new canvas nodes: `CollectionInsert`, `CollectionGet`, `CollectionFindOne`, `CollectionList`, `CollectionUpdate`, `CollectionDelete`

  Operator surfaces:
  - HTTP API at `/collections/*`
  - CLI: `codemation collections list|show|rows|get|insert|update|delete|sync`
  - UI at `/collections`

  Destructive schema changes (column drops, type changes) require `CODEMATION_COLLECTIONS_ALLOW_DESTRUCTIVE=1`.

  Out of scope (separate PRs):
  - Real leader election (advisory lock at boot is sufficient for sync; trigger double-firing during container swap is unaddressed)
  - Admin-role gating on the UI
  - Runtime user-defined schemas (Airtable-style)
  - Joins, aggregates, query DSL beyond indexed-field equality

- [#109](https://github.com/MadeRelevant/codemation/pull/109) [`fb9f7fe`](https://github.com/MadeRelevant/codemation/commit/fb9f7fed9bf5a3d6b0c5f78a30027be3ab7bcaca) Thanks [@cblokland90](https://github.com/cblokland90)! - OAuth2 plugin authors can now declare `authorizeUrl` / `tokenUrl` (with `{publicFieldKey}` template substitution) directly on a credential type's `auth` definition — no core change required to add a new provider. Migrated `@codemation/core-nodes-msgraph` to use this for Microsoft tenant-templated URLs (fixes "Unsupported OAuth2 provider id: microsoft" on connect).

  Removed dead `@codemation/core-nodes-gmail` devDep from `@codemation/host` and the matching `serverExternalPackages` entry from `@codemation/next-host` so plugin-author `pnpm dev` no longer rebuilds gmail when working on an unrelated plugin.

  Softened the credentials UI's "Not set in host env: …" message: it's now an informational tip with neutral styling (was destructive/error styling), since the field works perfectly fine when filled in manually.

- [#101](https://github.com/MadeRelevant/codemation/pull/101) [`2c0723f`](https://github.com/MadeRelevant/codemation/commit/2c0723fb1670e842c272939b5db73d4b95b25535) Thanks [@cblokland90](https://github.com/cblokland90)! - Add collections authoring API: `defineCollection()` for declaring typed data tables, `DefinedCollectionRegistry` for registration, and `CollectionStore` contract for runtime access. Includes column DSL (`c.text()`, `c.int()`, etc.) with validation for field/index names and reserved fields.

- [#108](https://github.com/MadeRelevant/codemation/pull/108) [`781c146`](https://github.com/MadeRelevant/codemation/commit/781c146eb9d8bb8bdbc1963ea2a4b9abe4b7bfbf) Thanks [@cblokland90](https://github.com/cblokland90)! - Extract generic polling-trigger machinery from gmail into core and expose it via setup context.

  **`@codemation/core`** — new polling-trigger API
  - New `PollingTriggerRuntime` class: owns the set-interval loop, overlap guard, and state persistence via `TriggerSetupStateRepository`. Plugin authors no longer need to implement these themselves.
  - New `PollingTriggerDedupWindow` class: merges processed-ID sets with a configurable cap (default 2000). Prevents unbounded memory growth across polling cycles.
  - New `PollingTriggerHandle` interface exposed on `TriggerSetupContext.polling`: pre-binds trigger id, emit, and registerCleanup so plugin code only supplies `intervalMs` and `runCycle`. The handle also carries a `.dedup` reference for message-level deduplication.
  - `EngineDeps.pollingTriggerLogger` optional field: hosts may wire a real logger; defaults to a no-op.
  - `PollingTriggerRuntime`, `PollingTriggerDedupWindow`, and `NoOpPollingTriggerLogger` are exported from the main `@codemation/core` barrel.
  - ESLint `allowedConstructorNames` extended to include `AbortController` (a global built-in, not a DI-managed class).

  **`@codemation/core-nodes-gmail`** — internal refactor, no external API change
  - `GmailPollingTriggerRuntime` deleted; loop/overlap-guard/persistence now come from the core runtime.
  - `GmailPollingService.poll` renamed to `runCycle`; repo injection and `persist()` method removed; dedup delegated to `PollingTriggerDedupWindow`.
  - `OnNewGmailTriggerNode.setup` now calls `ctx.polling.start(...)` instead of `gmailPollingTriggerRuntime.ensureStarted(...)`.
  - `GmailNodeTokens.RuntimeLogger` token removed (no longer needed).

- [#100](https://github.com/MadeRelevant/codemation/pull/100) [`11616ae`](https://github.com/MadeRelevant/codemation/commit/11616aefb91d4b96b7eb9af4b935eec055a8a7bb) Thanks [@cblokland90](https://github.com/cblokland90)! - Foundation for first-class **workflow testing**: a TestTrigger node, an IsTestRun branching node, an Assertion node, a `TestSuiteOrchestrator` service that fans one workflow run per yielded fixture item, host-side persistence (Prisma `TestSuiteRun` + `TestAssertion` tables, repositories, `TestRunnerService`), and a per-suite event tracker that records assertions and node coverage. HTTP routes and the canvas Tests tab (next-host) ship in follow-up slices.

  **What this slice adds**
  - **`@codemation/core` — additive contract changes**
    - `RunExecutionOptions.testContext?: { testSuiteRunId; testCaseIndex }` — set by the orchestrator on each test-case run; threaded through `ExecutionContext` so nodes can read it as `ctx.testContext`. Propagates to subworkflow runs via `ParentExecutionRef.testContext` + `EngineExecutionLimitsPolicy.mergeExecutionOptionsForNewRun`, so assertions emitted by subworkflows land under the correct parent test case.
    - `TriggerNodeConfig.triggerKind?: "live" | "test"` — `"test"` triggers are skipped by `TriggerRuntimeService` (live activation, webhooks, polling) and are only invoked by the orchestrator.
    - `NodeConfigBase.emitsAssertions?: true` — marker the host-side `TestAssertionPersister` (next slice) keys off when subscribing to `nodeCompleted`.
    - New `AssertionResult` type (`pass | fail | error`, plus `score`, `expected`, `actual`, `message`, `details`) — the stable shape every assertion node emits on `main`.
    - New `TestTriggerNodeConfig` + `TestTriggerSetupContext` — author callback signature returns `AsyncIterable<Item>` and exposes credential resolution + an `AbortSignal`.
    - New `RunEvent` kinds: `testSuiteStarted`, `testCaseStarted`, `testCaseCompleted`, `testSuiteFinished` (with terminal status `succeeded | failed | partial | cancelled | errored`).
    - New `TestSuiteOrchestrator` service in `orchestration/` — drives the iterator, applies a per-suite concurrency semaphore (default 4), dispatches one `engine.runWorkflow(...)` per item with `executionOptions.testContext` set, awaits terminal status, and publishes lifecycle events on the existing `RunEventBus`. No persistence, no HTTP — pure engine logic so tests can drive it via in-memory deps.
    - `TestSuiteRunIdFactory`, `AbortControllerFactory` — DI-friendly minters used by the orchestrator.
  - **`@codemation/core-nodes` — three new nodes**
    - **`TestTrigger`** / `TestTriggerNode`: drop on the canvas alongside live triggers. `setup` is a no-op; `execute` is a passthrough. The author's `generateItems` is consumed by the orchestrator.
    - **`IsTestRun`** / `IsTestRunNode`: per-item router with `true` / `false` ports. Routes to `true` iff `ctx.testContext` is set — lets workflows skip real side-effects in test runs (e.g. don't actually send the reply).
    - **`Assertion`** / `AssertionNode`: generic callback-style assertion node. Author returns `Promise<AssertionResult[]>` per item; the node emits one workflow `Item` per result. Sets `emitsAssertions: true` so the host persister can identify it.
    - Declarative shorthands (`StringEqualsAssertionNode`, `JudgeByAgentAssertionNode`) intentionally deferred — the generic callback node covers Phase 1 and the declarative variants compose on top.
  - **`@codemation/host` — persistence + orchestration + HTTP**
    - **Prisma schema**: new `TestSuiteRun` and `TestAssertion` tables in both Postgres and SQLite mirrors. Adds `Run.testSuiteRunId` (FK with `ON DELETE SET NULL`) and `Run.testCaseIndex` (indexed for join + ordering). Workflow definition itself is **not** FK'd — workflows live in code; `TestSuiteRun.triggerNodeName` is snapshotted at creation so historical viewing survives node renames/deletions.
    - **`TestSuiteRunRepository`** + **`TestAssertionRepository`** domain interfaces with Prisma + in-memory adapters.
    - **`TestRunnerService`** (host application layer) — single facade for "start a test suite": creates the persistence row, drives the orchestrator, awaits, finalizes counts + coverage. Subscribes to `RunEventBus.subscribeToWorkflow` only for the lifetime of one suite (no global subscriber, no shared mutable state across concurrent suites).
    - **`TestSuiteRunTracker`** + **`TestSuiteRunTrackerFactory`** — per-suite event accumulator. Two-stage event buffering tolerates inline runners that emit `nodeCompleted` synchronously inside `runWorkflow` (before the orchestrator publishes `testCaseStarted`); without it, fast/in-memory engines drop assertions silently.
    - **`AssertionResultGuard`** — type-guard the tracker uses to skip junk output if a misconfigured `emitsAssertions: true` node emits non-assertion items (defensive, not crash-on-bad-input).
    - **HTTP routes** (Hono, all behind the existing session-verifier middleware):
      - `POST /api/workflows/:workflowId/test-suite-runs` body `{ triggerNodeId, concurrency? }` → 201 with `{ testSuiteRunId, status, totalCases, passedCases, failedCases }`
      - `GET /api/workflows/:workflowId/test-suite-runs` → list summaries
      - `GET /api/test-suite-runs/:id` → detail (including `concurrency`, `nodeCoverage`, `errorMessage`)
      - `GET /api/test-suite-runs/:id/assertions` → all assertions across the suite's child runs
      - `GET /api/runs/:runId/assertions` → assertions for one child run
      - Paths exposed through `ApiPaths.workflowTestSuiteRuns/testSuiteRun/testSuiteRunAssertions/runAssertions` so the next-host React Query layer can call them by helper instead of string literals.
    - **DI bootstrap** in `AppContainerFactory`: registers all new singletons (factories, mappers, guard, repository selector, route handler + registrar) and wires Prisma vs in-memory `TestSuiteRunRepository` / `TestAssertionRepository` based on `appConfig.persistence.kind` (mirroring the existing `WorkflowRunRepository` selection). `TestSuiteOrchestrator` itself is registered via a tsyringe factory that injects `Engine` + the engine-side `RunEventBus` + a fresh `CredentialResolverFactory(CredentialSessionService)`.
    - **DTOs** in `application/contracts/TestingContracts.ts`: `StartTestSuiteRunRequest/Response`, `TestSuiteRunSummaryDto`, `TestSuiteRunDetailDto`, `TestAssertionDto`. Mappers (`TestSuiteRunSummaryMapper`, `TestAssertionMapper`) translate persistence records → wire shape.
    - **WebSocket / event narrowing** — `WorkflowWebsocketServer` and one integration test reader updated to type-narrow on the new test-suite event kinds (which carry `testSuiteRunId` rather than `runId`).

  **Tests**
  - `TestSuiteOrchestrator` unit suite (6 tests): per-item dispatch with `testContext`, partial-pass aggregation, lifecycle event emission, concurrency cap, `errored` status when `generateItems` throws, rejection of non-test triggers.
  - Node unit suite (6 tests): TestTrigger passthrough + `triggerKind === "test"`, IsTestRun routing on both branches, AssertionNode emitting one item per result, `emitsAssertions === true`.
  - `TestRunnerService` integration suite (2 tests): creates the persistence row, finalizes counts + coverage, persists 3 `TestAssertion` rows from a 2-case suite (one passing, one failing); rejects non-test triggers without leaving a phantom row.
  - **`@codemation/next-host` — Tests tab UI**
    - **Third canvas tab** ("Tests") next to Live workflow / Executions, mutually exclusive with both. Local React state for now (Phase 1) — promotion to the URL codec is a Phase 2 cleanup once the UX is settled.
    - **`TestsPanel`** — top-level container with a trigger picker (shadcn `Select` populated from workflow nodes whose `triggerKind === "test"`), a "Run tests" CTA wired through `useStartTestSuiteRunMutation`, a left list of past suite runs, and a right detail panel.
    - **`TestSuitePassRateChart`** — recharts line chart of pass rate over time across this workflow's suite runs. Carries an explicit `rolling-input` label so authors don't read trends as agent regressions when the underlying fixtures drift (Phase 2 ships snapshots).
    - **`TestSuiteRunsList`** + **`TestSuiteRunStatusBadge`** — list rows + colored status badges (`running` / `succeeded` / `partial` / `failed` / `cancelled` / `errored`).
    - **`TestSuiteRunDetailPanel`** — header with pass-rate + counts + concurrency + nodes-covered + (when set) an `errorMessage` callout; the body is a per-run grouped assertions list.
    - **`TestAssertionsList`** + **`TestAssertionRow`** — each assertion shows status badge, optional score, optional `expected`/`actual` JSON viewers side-by-side.
    - **React Query hooks** (`testSuiteHooks.ts`) cover all four GET endpoints plus the start mutation, with cache invalidation on `workflowTestSuiteRunsQueryKey` after a successful run.
    - **WorkflowNodeDto** + **mapper additions** (host + next-host's `PersistedWorkflowSnapshotMapper`) propagate `triggerKind` to the wire shape so the Tests panel can identify test triggers without server round-trips. Both mappers default omitted values to `"live"` to keep the wire DTO consistent.

  **Not in this slice (planned follow-ups)**
  - Test-input snapshots (Phase 2 — Phase 1 inputs are always live; UI carries a "rolling-input" label so charts aren't read as agent regressions).
  - Declarative assertion family (StringEquals, JsonPath, JudgeByAgent helpers — generic callback `Assertion` covers Phase 1).
  - Cancellation endpoint (`POST /api/test-suite-runs/:id/cancel`) — orchestrator already supports `AbortSignal` cancellation; the HTTP surface for it is deferred until the UI surfaces it.
  - Realtime updates on the Tests panel — currently the suite list refetches on mutation success; live `testSuite*` events arrive via the existing realtime bridge but the Tests panel doesn't subscribe yet.
  - URL codec entry for `pane=tests` so suite drilldowns are deep-linkable (currently in-memory React state).
  - Coverage heatmap overlay on the canvas itself.

  The contract additions are **strictly additive**; no existing API surface changed shape.

### Patch Changes

- [#110](https://github.com/MadeRelevant/codemation/pull/110) [`4902978`](https://github.com/MadeRelevant/codemation/commit/49029782243ece59ab6aa5bb46396db445cad47c) Thanks [@cblokland90](https://github.com/cblokland90)! - Add per-package `test:unit` scripts so Turbo can address each package individually for affected-only filtering. No runtime changes — dev-tooling only.

- [#100](https://github.com/MadeRelevant/codemation/pull/100) [`11616ae`](https://github.com/MadeRelevant/codemation/commit/11616aefb91d4b96b7eb9af4b935eec055a8a7bb) Thanks [@cblokland90](https://github.com/cblokland90)! - Major dev-server startup-time and bundle-size improvements, plus dev-CLI hardening.

  **Why this matters**

  Before this work, opening the workflow detail page on a 4-cpu / 8-GB WSL box would
  OOM-kill `next-server` mid-compile (~5 GB peak RSS). After: the page cold-compiles in
  **5.5 s** with peak **1.8 GB** and the dev server stays comfortably alive. The dev CLI
  also boots significantly faster and survives consumer-source errors without tearing
  the whole session down.

  **Hard numbers**
  - Workflow page Turbopack RSS peak: **5.0 GB → 1.8 GB** (-64%)
  - Workflow page cold compile time: **~14 s → ~5.5 s**
  - Lucide-react files in workflow page bundle: **1,713 → 74** (-95.7%)
  - Host package typecheck: **17.5 s / 4,093 files / 2.1 GB → 8.8 s / 2,806 files / 1.9 GB**
  - Host source tree: **-112,492 lines** of generated Prisma `.d.ts`
  - Host circular dep cycles: **92 → 21**
  - Core circular dep cycles: **53 → 50**

  **`@codemation/next-host`**
  - New `WorkflowCanvasLucideIconRegistry` — curated 18-icon set used by core node plugins.
    Replaces `lucide-react/dynamic` (which forced bundling of all 1,713 icons because it
    loads them by string at runtime). Workflows using `icon: "lucide:<unknown>"` now fall
    back to the `Boxes` icon and emit a one-time `console.warn`. **Plugin authors needing
    custom icons must ship SVG via `builtin:` / `si:` / URL tokens.**
  - New slim subpath exports on `@codemation/host`: **`@codemation/host/dto`**,
    **`@codemation/host/mapping`**, plus extensions to **`@codemation/host/client`**.
    All 65 deep `@codemation/host-src/*` imports replaced; `@codemation/host-src/*`
    tsconfig path removed. Prevents the UI from dragging the heavy host runtime graph
    through Turbopack on every UI route compile.
  - 42 lucide-react named imports rewritten to per-icon deep imports
    (`lucide-react/dist/esm/icons/<kebab>`).
  - Workflow detail page lazy-loads `WorkflowDetailScreenTestsView` and the
    Monaco-backed `WorkflowJsonEditorDialog`.
  - Removed `@codemation/core` and `@codemation/host` from `transpilePackages` and
    dropped the corresponding root-barrel tsconfig paths so Next loads them from
    compiled `dist/` instead of TypeScript source.
  - Dev: `EdgeSessionVerifier` resolves `/api/auth/session` via
    `x-forwarded-host` (the dev gateway) instead of `request.nextUrl.origin` (Next's
    loopback). Previously the auth-check fetch looped back into Next, forcing
    Turbopack to compile the catch-all `/api/[[...path]]` route on every page load.

  **`@codemation/host`**
  - Generated Prisma clients (`prisma-client`, `prisma-postgresql-client`,
    `prisma-sqlite-client`) moved out of `src/infrastructure/persistence/generated/`
    to `prisma-generated/` (sibling of `src/`). They're still typechecked and bundled
    by the host build, but no longer pollute the public source surface that downstream
    packages walk.
  - New **`@codemation/host/dto`**, **`@codemation/host/mapping`** subpath exports
    re-exposing only the contract DTO types and presentation factories the UI needs.
    The existing **`@codemation/host/client`** subpath gained `ApiPaths`,
    `BrowserLoggerFactory`, `logLevelPolicyFactory`, `InAppCallbackUrlPolicy`, and
    `Logger` so the UI no longer needs deep imports.

  **`@codemation/core`**
  - New **`@codemation/core/contracts`** subpath — re-exports only pure-type contracts
    (`assertionTypes`, `runTypes`, `workflowTypes`, etc.) using `export type *`. Type-only
    consumers can import from here to avoid dragging the workflow DSL runtime into their
    compile graph. Existing `@codemation/core` (root barrel) is unchanged for backwards
    compatibility.
  - Extracted `core/src/contracts/baseTypes.ts` (six fundamental id types) to break a
    long-standing `credentialTypes ↔ workflowTypes` cycle.

  **`@codemation/cli` — dev-CLI hardening**
  - **`DevHttpProbe`**: TCP-listener probe replaces the HTTP-response probe, so a slow
    Next dev cold compile no longer SIGTERMs the dev tree.
  - **Single-runtime swap** in `runQueuedRebuild`: stops the old in-process runtime
    before creating the new one, freeing ~1.5 GB during dev source-changes. Consumer
    errors are now non-fatal — the gateway returns 503 and the dev session stays up
    until the next save fixes the build.
  - **Workspace-plugin watch is now opt-in** via `CODEMATION_DEV_WATCH_PLUGINS=true`.
    By default `pnpm dev` no longer spawns `tsdown --watch` for each workspace plugin
    (saves ~500 MB baseline + the rebuild-loop pressure). Plugins still load from
    their existing `dist/` output; opt in only when actively editing a plugin's source.
  - **`DevSourceWatcher`**: 75 ms → 750 ms debounce so a single `tsdown` rebuild collapses
    into one runtime swap. Defense-in-depth ignore re-check at the event handler (chokidar
    doesn't always re-evaluate `ignored` for files created post-start). 20 s startup grace
    period to drop initial-build noise.
  - **Workspace plugin watch root** narrowed from `dist/` to the plugin's entry file —
    tsdown rewrites the entry once per real build, so one watch event per build instead of
    a dozen.
  - Removed `--conditions=development` from the Next-host's `NODE_OPTIONS`. Previously
    this resolved `@codemation/{core,host}` to TypeScript source; combined with
    `transpilePackages` it forced Turbopack to walk the full source tree on every
    UI route compile.

  **Architectural guard rails (no behavior change, prevent regressions)**
  - ESLint `no-restricted-imports` blocks `@codemation/host-src/*` and root
    `@codemation/host` from `next-host` UI; blocks `prisma-generated/*` outside host's
    persistence layer.
  - New **`dependency-cruiser`** config + `pnpm depcruise` script.
  - New **`knip`** config + `pnpm lint:knip` script.
  - New `tooling/scripts/check-circular-deps.mjs` + `pnpm lint:circular` wired into
    `pnpm lint` with frozen baselines (core: 50, host: 21, core-nodes: 73).
  - **`@next/bundle-analyzer`** wired up; `pnpm analyze` available for on-demand
    inspection (uses `next experimental-analyze` for Turbopack-mode introspection).
  - New `AGENTS.md` "Cross-package imports" section documenting the slim-subpath
    discipline and the rationale for it.

  The contract additions are strictly additive; no existing API surface changed shape.

- [#112](https://github.com/MadeRelevant/codemation/pull/112) [`6fc7d3f`](https://github.com/MadeRelevant/codemation/commit/6fc7d3fe95f8d88386c16971fffa8dd3faa7704f) Thanks [@cblokland90](https://github.com/cblokland90)! - Surface workflow-planning errors in the node inspector instead of swallowing them as `[codemation-http] unhandled route error`. When `NodeInstanceFactory` fails to instantiate a node (e.g. tsyringe `TypeInfo not known` for a constructor param), the offending `nodeId` is preserved via a new `NodeInstantiationError`, and `RunStartService` now persists a failed run with the error attached to that node — same shape execution errors already use, so the UI shows `name`/`message`/`stack` in the node "output" panel.

- [#100](https://github.com/MadeRelevant/codemation/pull/100) [`11616ae`](https://github.com/MadeRelevant/codemation/commit/11616aefb91d4b96b7eb9af4b935eec055a8a7bb) Thanks [@cblokland90](https://github.com/cblokland90)! - Workflow Testing UI polish and end-to-end correctness fixes.

  **`@codemation/next-host`** — Tests UI
  - Fix `Maximum update depth exceeded` on the Tests panel. The trends chart was receiving a
    fresh `[]` reference per render (`?? []` inline) which made recharts' internal effects loop;
    every `?? EMPTY_*` fallback the chart consumes is now a module-scoped stable reference.
  - Fix the same loop class on the canvas-play-dropdown → Tests path. The auto-start `useEffect`
    had `startMutation` (a react-query mutation result, unstable per render) in its deps array,
    which re-fired the mutation on every render. Now uses a ref keyed on `autoStartTriggerNodeId`
    with explicit reset when the prop clears.
  - Fix the canvas inspector showing `{ "json": {...} }` for historical / test-suite child runs.
    `WorkflowDetailPresenter.jsonValueToMainItems` was wrapping every array entry as
    `{ json: <entry> }`, but trigger outputs are persisted **already-Item-shaped**, producing
    `{json: {json: {...}}}`. Detects already-Item entries and passes them through.
  - Surface assertion-rollup-corrected status on the executions list. New `RunSummary.testCaseStatus`
    is preferred over engine `status` so a test-case run whose assertions failed shows as
    **failed** instead of "completed" (engine status is unchanged — only the UI display).
  - Tabs no longer overlap the test-cases detail panel — moved from absolute positioning to a flow
    header in the Tests view.
  - Filter strip above the case tree-table: All / Passing / Failing / Errored / In flight, with
    live counts. Empty buckets are disabled so users can't filter into a confusing empty state.
  - Collapse all / Expand all controls on the case tree-table; expansion state lifted from
    per-row `useState` to the table so broadcasts work. Auto-open-on-failure heuristic still fires
    per-row but only the first time each run id appears, so a row the user explicitly collapsed
    stays collapsed when realtime updates stream in.
  - Trend chart x-axis is now numeric `idx` with subsampled ticks (~5 evenly-spaced labels) and
    time-aware formatting (`HH:MM` when all runs share a day, `M/D HH:MM` across days).
  - Status icon expanded to cover the full case-status union (`succeeded` / `failed` / `errored` /
    `cancelled` / `running` / `queued`) with distinct icons and colors.

  **`@codemation/host`** — Testing framework correctness
  - Fix `TestSuiteRunTracker` race that left the last test case stuck on `testCaseStatus="running"`
    and the suite counters off by one. The bus dispatched events fire-and-forget; `finalize` ran
    before in-flight handlers wrote their `updateTestCaseStatus` calls. Tracker now serializes
    events through a `processingTail` chain and `finalize` awaits it before reading
    `listChildRuns`.
  - Initialize `Run.testCaseStatus` to `"running"` at row creation when `executionOptions.testContext`
    is present. Previously the tracker's `persistCaseStarted` raced the engine inserting the row
    and silently swallowed P2025 — the suite-detail page never showed a "running" transition.
  - `TestSuiteChildRunDto` exposes the new `testCaseStatus?: TestCaseRunStatus` field; mapper
    narrows the persistence string through a known-statuses guard.
  - `PrismaWorkflowRunRepository.listRuns` threads `testCaseStatus` into `RunSummary` so the
    executions list can render the corrected outcome.

  **`@codemation/core`**
  - `RunSummary` gains an optional `testCaseStatus?: TestCaseRunStatus`. Additive, non-breaking.

  **Dev experience**
  - `pnpm dev` (root) now runs `tsdown --watch` for `@codemation/host` alongside `test-dev` under
    `concurrently`, so host source edits rebuild `dist/` automatically. Without this, host changes
    were invisible to the running Next dev server (which deliberately resolves host from `dist/`
    to keep Turbopack memory bounded on 8 GB WSL boxes), forcing a manual
    `pnpm --filter @codemation/host build` after every host edit.

  **Documentation**
  - Top-level `docs/workflow-testing.md` and the `codemation-workflow-dsl` skill reference
    rewritten for the score-based assertion model (`score: 0..1` + `passThreshold?` + `errored?`),
    with examples for boolean assertions, continuous metrics, and judge-by-agent assertions.

  **Tests**
  - New HTTP-driven e2e suite (`packages/host/test/e2e/testSuiteRunHttpFlow.e2e.test.ts`) drives
    the full real-orchestrator + real-Prisma + real-engine lifecycle through `POST` →
    `GET /api/test-suite-runs/:id` → child runs → assertions, asserting the partial-suite
    outcome with assertion-rollup downgrade.
  - New unit tests cover the case-status filter engine, the historical-run double-wrap regression,
    and the chart prop-stability regression class.

## 1.0.1

### Patch Changes

- [`ed75183`](https://github.com/MadeRelevant/codemation/commit/ed75183f51ae71b06aa2e57ae4fc48ce9db2e4ce) - Establish "per Item per Call" identity end-to-end so the workflow run inspector reports, visualizes, and dashboards multi-item AI agents correctly.

  Previously, an orchestrator agent that processed N items emitted one flat list of LLM rounds and tool calls — the bottom execution tree, the right-panel agent timeline, cost dashboards, and the realtime event stream all collapsed iterations into one bucket, making sub-agent fan-outs (and parallel item processing in general) unreadable.

  **What changed**
  - **Engine** (`@codemation/core`): `NodeExecutor` mints a `NodeIterationId` per item inside per-item runnable activations and stamps it (with `itemIndex`) onto `NodeExecutionContext`. Connection invocations, telemetry spans (`gen_ai.chat.completion`, `agent.tool.call`), metric points (`codemation.cost.estimated`, `codemation.agent.turns`, `codemation.agent.tool_calls`), and run events all carry the per-item identity. New `ChildExecutionScopeFactory` re-roots `NodeExecutionContext` for sub-agents so credentials and iteration ids resolve correctly across the orchestrator → tool → sub-agent boundary.
  - **Sub-agent credentials** (`@codemation/core-nodes`): `NodeBackedToolRuntime.resolveNodeCtx` no longer re-wraps `args.ctx.nodeId` with `ConnectionNodeIdFactory.toolConnectionNodeId` — the caller already pre-wraps it. The previous double-nesting produced exponentially deep node ids (`AIAgentNode:2__conn__tool__conn__searchInMail__conn__tool__conn__searchInMail__conn__llm`) that didn't match user-bound credential slots. Sub-agent OpenAI / API-key slots resolve again.
  - **Realtime events**: new `connectionInvocationStarted` / `connectionInvocationCompleted` / `connectionInvocationFailed` events carry the full `ConnectionInvocationRecord` (incl. `iterationId`, `itemIndex`, `parentInvocationId`) and surgical reducers update the run cache without waiting for a coarse `runSaved` snapshot. Run-query polling dropped from 250 ms → 5 s now that WebSocket events drive most updates.
  - **Persistence** (`@codemation/host`): Prisma `ExecutionInstance` model gains `iteration_id`, `item_index`, `parent_invocation_id` columns + index (sqlite + postgres migrations); `PrismaWorkflowRunRepository` round-trips them on read/save and via `ExecutionInstanceDto`. Without this the cold reload of a finished run silently flattens the per-item tree because `runSaved` events stream through Prisma. Telemetry tables already carried these columns from Phase 4; both sides now agree.
  - **Iteration projection / cost queries** (`@codemation/host`): new `RunIterationProjectionFactory` projects `RunIterationRecord`s from connection invocations + iteration cost metrics and `GetIterationCostQueryHandler` serves per-iteration cost rollups for dashboards.
  - **Inspector view model** (`@codemation/next-host`): `NodeInspectorTelemetryPresenter` groups LLM and tool spans by `iterationId` into "Item N" accordion entries (single-item agents fall back to flat layout). New `FocusedInvocationModelFactory` powers item-level prev/next navigation when a specific invocation is selected — the breadcrumb shows "Item X of Y" and nav targets the first invocation of adjacent items. Tool spans now interleave chronologically with LLM rounds (request → tools → response) instead of LLM rounds first then orphan tools at the bottom.
  - **Bottom execution tree** (`@codemation/next-host`): new `ExecutionTreeItemGroupInjector` injects synthetic "Item N" parent rows between an agent and its connection invocations when the agent processed 2+ items. Single-item activations are left untouched; sub-agent invocations whose `parentInvocationId` already points at a tool-call row stay nested under the orchestrator's specific tool call.
  - **Sub-agent credential boundary**: `ChildExecutionScopeFactory.forSubAgent` ensures sub-agent `NodeExecutionContext` keeps the parent invocation id and span context intact so trace nesting and credential resolution agree on the connection-node id.
  - **Tests**: new unit + UI suites for each layer (sub-agent scope, item-group injector, focused invocation model, agent timeline per-item grouping, chronological ordering, Prisma iterationId round trip, item-aware properties panel, connection-invocation event publisher) and a runnable `apps/test-dev` sample (`agentSubAgentToolFanout`) that exercises the orchestrator → sub-agent fan-out across 2 items end-to-end.

## 1.0.0

### Major Changes

- [#93](https://github.com/MadeRelevant/codemation/pull/93) [`640e303`](https://github.com/MadeRelevant/codemation/commit/640e3032b1386568df725980a27761b6e230302c) Thanks [@cblokland90](https://github.com/cblokland90)! - Replace LangChain with the Vercel AI SDK for all AIAgent flows.

  Codemation no longer depends on `@langchain/core` or `@langchain/openai`. Chat model providers, the turn loop, structured output, and tool calls now run on top of the Vercel **AI SDK** (`ai`, `@ai-sdk/openai`, `@ai-sdk/provider`). Custom Codemation behaviors that LangChain did not cover — the **tool-args repair loop**, the **structured-output repair loop**, **connection-invocation tracking**, and our **telemetry / cost-tracking spans** — are preserved and built on top of the new primitives.

  ### Dependency changes
  - **Removed**: `@langchain/core`, `@langchain/openai` (from `@codemation/core-nodes`).
  - **Added**: `ai` `^6.0.168`, `@ai-sdk/openai` `^3.0.53`, `@ai-sdk/provider` `^3.0.8` (to `@codemation/core-nodes`). `@codemation/host` picks up `ai` + `@ai-sdk/provider` for its test harness only.

  ### Public API renames (`@codemation/core`)

  | Before                                               | After                                                                                                             |
  | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
  | `LangChainChatModelLike`                             | `ChatLanguageModel`                                                                                               |
  | `LangChainStructuredOutputModelLike`                 | _(removed — replaced by `StructuredOutputOptions` + `generateText({ experimental_output: Output.object(...) })`)_ |
  | `ChatModelFactory.create` → `LangChainChatModelLike` | `ChatModelFactory.create` → `ChatLanguageModel` (thin wrapper around an AI SDK `LanguageModelV2`)                 |

  `ChatLanguageModel` exposes the underlying AI SDK `LanguageModel` via `languageModel` plus `modelName`, `provider`, and optional `defaultCallOptions` (`maxOutputTokens`, `temperature`, `providerOptions`). `StructuredOutputOptions` mirrors `generateText({ output: Output.object(...) })` and carries an optional `schemaName` plus `strict` flag.

  ### Custom behavior preserved (not delegated to the AI SDK)
  - **Tool dispatch + tool-args repair**: tools are passed to `generateText` **without `execute`** so tool calls surface back to Codemation; `AgentToolExecutionCoordinator` still drives parallel execution, per-tool Zod-input validation, repair prompts, and retry accounting via `repairAttemptsByToolName`.
  - **Structured output repair**: `AgentStructuredOutputRunner` still runs the `OpenAiStrictJsonSchemaFactory` + `AgentStructuredOutputRepairPromptFactory` loop; AI SDK's `Output.object(...)` is used only for the **first** structured attempt when the provider supports it.
  - **Connection-invocation tracking**: `ConnectionInvocationIdFactory` + synthetic `LanguageModelConnectionNode` / tool connection node states (`queued` / `running` / `completed` / `failed`) are still emitted per turn and per tool call.
  - **Telemetry span names (intentional, short-term)**: LLM calls stay on `gen_ai.chat.completion`, tool calls on `agent.tool.call`, metrics on `codemation.ai.turns` / `codemation.ai.tool_calls` / `codemation.cost.estimated`. We disable AI SDK's built-in telemetry (`experimental_telemetry`) for this cut so host-side telemetry aggregations keep working unchanged. Migrating to AI SDK native span names is intentionally deferred.
  - **Engine-level retry control**: every `generateText` call uses `maxRetries: 0` so Codemation's own retry / repair policy is the single source of truth.

  ### New test utilities

  Tests that previously scripted `LangChainChatModelLike` now script AI SDK `LanguageModelV3` via `MockLanguageModelV3` from `ai/test`. `@codemation/core-nodes` and `@codemation/host` test files ship small adapters (`ScriptedResponseConverter`, `ScriptedDoGenerateFactory`, `TelemetryResponseConverter`) that translate Codemation's legacy `{ content, tool_calls, usage_metadata }` fixtures into `LanguageModelV3GenerateResult`.

  ### Migration notes for consumers
  - If you implemented a **custom `ChatModelFactory`**, return a `ChatLanguageModel` (wrap an AI SDK `LanguageModelV2`) instead of a LangChain-shaped chat model. The `name` / `modelName` / `provider` on your config still drive cost tracking.
  - If you imported the type `LangChainChatModelLike` (or `LangChainStructuredOutputModelLike`) from `@codemation/core`, switch to `ChatLanguageModel` (and drop structured-output-method imports — `generateText({ experimental_output })` covers it).
  - `OpenAIChatModelFactory` now builds an AI SDK OpenAI provider under the hood; behavior for end users (model presets, credential resolution, token accounting, structured output against strict mode) is unchanged.
  - Telemetry dashboards, trace views, and cost-tracking queries continue to work against the existing Codemation span / metric names.

### Patch Changes

- [#93](https://github.com/MadeRelevant/codemation/pull/93) [`640e303`](https://github.com/MadeRelevant/codemation/commit/640e3032b1386568df725980a27761b6e230302c) Thanks [@cblokland90](https://github.com/cblokland90)! - Fix `Unique constraint failed on the fields: (instance_id)` crash when rerunning a workflow that contains an AI agent.

  Reproduction: build `Manual trigger → AI agent → node → node`, click play on the agent, then click play on the next node (sometimes twice). The second run would fail at `PrismaWorkflowRunRepository.saveOnce` with a Postgres PK violation on the `ExecutionInstance` table.

  Root cause: `RunStartService.createRunCurrentState` was deep-copying the prior run's `connectionInvocations` verbatim into the new run's initial state. Each record kept its original globally-unique `invocationId`, which is the primary key in `ExecutionInstance`. `saveOnce`'s existing-row lookup is scoped to the current `runId`, so the collision against the prior run's rows was only detected by Postgres when the insert fired.

  Beyond the crash, the old behavior was also a data-model lie for compliance / OTEL: a `ConnectionInvocationRecord` represents a single auditable LLM / tool call and must belong to exactly one run. Copying it into another run made the same event appear to have happened twice.

  Fix (domain + defense-in-depth):
  - `@codemation/core` — `RunStartService.createRunCurrentState` now starts new runs with an empty invocation ledger. The prior run's invocations remain queryable on that run's persisted state (their true owner).
  - `@codemation/host` — `PrismaWorkflowRunRepository.buildExecutionInstances` skips any invocation whose `runId` differs from the run being saved, so a stray carry-over from any other code path self-heals instead of crashing the save.

  UI impact: none for the historical-run view (it reads invocations directly from the selected run). The client-side debugger overlay continues to surface the prior run's invocations locally during a rerun, and inspector telemetry already fetches against each invocation's original `runId`.

## 0.8.1

### Patch Changes

- [`7eaa288`](https://github.com/MadeRelevant/codemation/commit/7eaa288737f2d126218dac84fa4fde2a4113b7f3) Thanks [@cblokland90](https://github.com/cblokland90)! - Default DI container registrations to singletons so framework services that own long-lived resources (timers, subscriptions, sockets) have deterministic lifecycles. Previously `container.register(Class, { useClass: Class })` produced a new instance per resolution, which caused the `WorkflowRunRetentionPruneScheduler` `setInterval` timer to leak across HMR reloads and blocked `pnpm dev` from shutting down on Ctrl+C.

  Public registration DTOs still accept `useClass` as a shape hint, but the host applies every class-based registration as a singleton. Plugin authors using `plugin.register({ registerNode, registerClass })` and consumers using `containerRegistrations: [{ token, useClass }]` no longer need to reason about lifecycle. Redundant `@registry([{ useClass }])` decorators on Hono route registrars and domain event handlers have been removed.

  A new ESLint rule (`codemation/no-transient-container-register`) prevents reintroducing `.register(token, { useClass: Class })` and `@registry([{ useClass: Class }])` patterns across `packages/**` and `apps/**`.

## 0.8.0

### Minor Changes

- [`782e934`](https://github.com/MadeRelevant/codemation/commit/782e93469ea6eee701d976b8f1dc18649d045c79) Thanks [@cblokland90](https://github.com/cblokland90)! - Add catalog-backed cost tracking contracts and wire AI/OCR usage into telemetry so hosts can aggregate provider-native execution costs.

  Improve the telemetry dashboard and workflow detail experience with cost breakdowns, richer inspector data, workflow run cost totals, and credential rebinding fixes.

### Patch Changes

- [#85](https://github.com/MadeRelevant/codemation/pull/85) [`a250ab8`](https://github.com/MadeRelevant/codemation/commit/a250ab8b973429cdfe708526a205e2565b004868) Thanks [@cblokland90](https://github.com/cblokland90)! - Decouple telemetry retention from run deletion and move node-specific measurements onto metric points.
  - allow telemetry spans, artifacts, and metrics to outlive raw run state through explicit retention timestamps
  - narrow telemetry spans to canonical span fields and persist extensible node-specific measurements as metric points
  - update telemetry queries, docs, and regression coverage around real workflow execution plus agent/tool observability

- [#88](https://github.com/MadeRelevant/codemation/pull/88) [`052aba1`](https://github.com/MadeRelevant/codemation/commit/052aba17c9a4faf557bdfaa1a9644a1987ecc25e) Thanks [@cblokland90](https://github.com/cblokland90)! - Add a telemetry-backed node inspector slice for workflow detail and expose run-trace telemetry needed to power it.

- [`1a356af`](https://github.com/MadeRelevant/codemation/commit/1a356afae50bd3f982e92c3e9f931e3adbcd131f) - Repair malformed AI tool calls inside the agent loop instead of replaying the whole agent node, and surface clearer debugging details when recovery succeeds or is exhausted.
  - classify repairable validation failures separately from non-repairable tool errors and preserve stable invocation correlation for failed calls
  - persist structured validation details and expose them in next-host inspector fallbacks, timelines, and error views
  - add regression coverage for repaired tool calls, exhaustion behavior, and mixed parallel tool rounds

## 0.7.0

### Minor Changes

- [#81](https://github.com/MadeRelevant/codemation/pull/81) [`88844f7`](https://github.com/MadeRelevant/codemation/commit/88844f75a48fe051e4cb895c710408855de14da4) Thanks [@cblokland90](https://github.com/cblokland90)! - Add typed workflow authoring helpers for reusable node params and run-data reads.
  - export `Expr`, `Param`, and `ParamDeep` so helper-defined node params can accept literals or `itemExpr(...)`
  - export `nodeRef<TJson>()` plus generic `RunDataSnapshot` item accessors for typed `ctx.data` reads
  - keep helper-node runtime config resolved while expanding the public authoring surface for expression-style params

## 0.6.0

### Minor Changes

- [#71](https://github.com/MadeRelevant/codemation/pull/71) [`3044e73`](https://github.com/MadeRelevant/codemation/commit/3044e73fd3cfb33f8e2cbc579c10baf97ed94658) Thanks [@cblokland90](https://github.com/cblokland90)! - Add inline callable agent tools to the workflow DSL.

  This introduces `callableTool(...)` as a workflow-friendly helper for app-local agent tools, keeps
  `CallableToolFactory.callableTool(...)` as a compatible factory entry point, teaches `AIAgentNode`
  to execute callable tools with the same tracing and validation model as other tool kinds, and
  updates docs, skills, and the test-dev sample to show the new path.

- [#73](https://github.com/MadeRelevant/codemation/pull/73) [`418434a`](https://github.com/MadeRelevant/codemation/commit/418434a6a2ad88a6254a94cb70e6f14b886df348) Thanks [@cblokland90](https://github.com/cblokland90)! - Improve credential UX and add extensible advanced field presentation.
  - Run automatic credential health tests after create/save (including OAuth) and keep the dialog open when the test fails; auto-bind newly created credentials to empty workflow slots; auto-bind when picking an existing credential from the workflow slot dropdown while the slot is unbound.
  - Add `CredentialFieldSchema.visibility` (`default` | `advanced`) and optional `CredentialTypeDefinition.advancedSection` (advanced fields always render in a collapsible block; section labels default when omitted). Next host uses stable test ids and fixes collapsible chevron styling.
  - Credential dialog: title uses the credential type name (e.g. **Add …** / type display name on edit); hide the redundant type dropdown in edit mode.
  - Gmail OAuth: group Client ID with Client secret, move scope preset and custom scopes under an **OAuth scopes** advanced section (collapsed by default).
  - Documentation: `packages/core/docs/credential-ui-fields.md`, AGENTS.md, and credential development skill reference.

- [#76](https://github.com/MadeRelevant/codemation/pull/76) [`3774fd8`](https://github.com/MadeRelevant/codemation/commit/3774fd80bc357c7eb39957f6963c692f322c38eb) Thanks [@cblokland90](https://github.com/cblokland90)! - Preserve binaries for runnable node outputs and make workflow authoring APIs accept explicit output behavior options.

  This adds `keepBinaries` support across runnable execution paths, updates `MapData` and related workflow authoring helpers to use an options object for node ids and output behavior, and refreshes tests and docs around the new contract.

- [#75](https://github.com/MadeRelevant/codemation/pull/75) [`00bc135`](https://github.com/MadeRelevant/codemation/commit/00bc1351e2dd6222d5101dbff3602a76ead33ce1) Thanks [@cblokland90](https://github.com/cblokland90)! - Add structured-output schemas to AI agents and choose the safer OpenAI response mode per model snapshot.

  This exposes `outputSchema` on agent configs, teaches `AIAgentNode` to validate and repair structured outputs, and
  avoids opting older OpenAI snapshots into `json_schema` when only function calling is safe.

## Unreleased

### Minor Changes

- Add **`callableTool(...)`** (exported from authoring; same behavior as **`CallableToolFactory.callableTool(...)`**), **`CallableToolConfig`**, and **`CallableToolKindToken`** for inline Zod-typed agent tools (optional **`credentialRequirements`**, structural **`toolKind: "callable"`** for snapshots). Same runtime contract as other agent tools; no implicit merge of workflow **`item.json`** into tool input.

## 0.5.0

### Minor Changes

- [#60](https://github.com/MadeRelevant/codemation/pull/60) [`056c045`](https://github.com/MadeRelevant/codemation/commit/056c045d7813e7e6b749f0dc03bb43855ff7f58c) Thanks [@cblokland90](https://github.com/cblokland90)! - Harden the Gmail plugin so it imports reliably from the package root, returns an authenticated official Gmail session, and supports trigger/read/send/reply/label workflows with one OAuth credential.

  Add framework support for OAuth scope presets and custom per-credential scope replacement, and update the plugin starter/docs so future plugins scaffold the same publishable root-entrypoint conventions.

## 0.4.0

### Minor Changes

- [#54](https://github.com/MadeRelevant/codemation/pull/54) [`35b78bb`](https://github.com/MadeRelevant/codemation/commit/35b78bb4d8c7ee2998a8b8e51e5ffc3fd901e4c7) Thanks [@cblokland90](https://github.com/cblokland90)! - **Breaking change:** `defineNode(...)` now follows the per-item pipeline: implement **`execute(args, context)`** (optional **`inputSchema`**, **`mapInput`**, and **`TWireJson`** on the generated runnable config). Add **`defineBatchNode(...)`** with **`run(items, context)`** for plugin nodes that still require batch **`run`** semantics.

  Built-in nodes and workflow DSL (`split` / `filter` / `aggregate` on the fluent chain, Switch routing, execution normalization) align with the unified runnable model.

  Align documentation (site guides, repo **`AGENTS.md`**, **`strict-oop-di`** skill, **`packages/core/docs/item-node-execution.md`**) and the **plugin** starter **`AGENTS.md`** with **config** for static wiring (credentials, retry, presentation) vs **inputs** / wire JSON for per-item behavior.

- [#56](https://github.com/MadeRelevant/codemation/pull/56) [`eb97e53`](https://github.com/MadeRelevant/codemation/commit/eb97e5376f4f620099c32c14d7797ed3039bf7bb) Thanks [@cblokland90](https://github.com/cblokland90)! - Add fluent workflow authoring support for port routing and core nodes.
  - `workflow()` DSL: add `route(...)`, `merge(...)`, and `switch(...)` helpers so multi-port graphs can be expressed without manual `edges`.
  - `Callback`: allow returning `emitPorts(...)` and configuring declared output ports and error handling options.
  - Next host: fix execution inspector tree nesting by preferring `snapshot.parent.nodeId` when available (nested agent/tool invocations).

## 0.3.0

### Minor Changes

- [#52](https://github.com/MadeRelevant/codemation/pull/52) [`bb2b3b8`](https://github.com/MadeRelevant/codemation/commit/bb2b3b89069697c6aa36aac1de7124c5eea65c3e) Thanks [@cblokland90](https://github.com/cblokland90)! - **Breaking change:** `defineNode(...)` now follows the per-item pipeline: implement **`executeOne(args, context)`** (optional **`inputSchema`**, **`mapInput`**, and **`TWireJson`** on the generated runnable config). Add **`defineBatchNode(...)`** with **`run(items, context)`** for plugin nodes that still require legacy batch **`Node.execute`** semantics.

  Align documentation (site guides, repo **`AGENTS.md`**, **`strict-oop-di`** skill, **`packages/core/docs/item-node-execution.md`**) and the **plugin** starter **`AGENTS.md`** with **config** for static wiring (credentials, retry, presentation) vs **inputs** / wire JSON for per-item behavior.

## 0.2.3

### Patch Changes

- [#50](https://github.com/MadeRelevant/codemation/pull/50) [`d3a4321`](https://github.com/MadeRelevant/codemation/commit/d3a4321dc178df51dfd61cc6eb872ccca36bbcdb) Thanks [@cblokland90](https://github.com/cblokland90)! - Release automation: GitHub Actions now dispatches `publish-npm.yml` after versioning so npm OIDC trusted publishing continues to match the publish workflow file (no `workflow_call` from the Changesets workflow).

## 0.2.2

### Patch Changes

- [#47](https://github.com/MadeRelevant/codemation/pull/47) [`74dc571`](https://github.com/MadeRelevant/codemation/commit/74dc571afb592bd7c05297b25f9f1fb06a46815f) Thanks [@cblokland90](https://github.com/cblokland90)! - Item-node input mapping refinements, `RunQueuePlanner` multi-input merge routing, Split/Filter/Aggregate batch nodes, AIAgent `ItemNode` + optional `mapInput`/`inputSchema`, and documentation updates.

- [#47](https://github.com/MadeRelevant/codemation/pull/47) [`74dc571`](https://github.com/MadeRelevant/codemation/commit/74dc571afb592bd7c05297b25f9f1fb06a46815f) Thanks [@cblokland90](https://github.com/cblokland90)! - Add `TWireJson` to `RunnableNodeConfig`, typed `ItemInputMapper<TWire, TIn>` (bivariant for storage), `RunnableNodeWireJson` helper, and align `ChainCursor` / workflow DSL with upstream wire typing. Introduce `ItemInputMapperContext` so `mapInput` receives typed `ctx.data` (`RunDataSnapshot`) for reading any completed upstream node’s outputs.

## 0.2.1

### Patch Changes

- [#44](https://github.com/MadeRelevant/codemation/pull/44) [`4989e9c`](https://github.com/MadeRelevant/codemation/commit/4989e9c7d97513c05904d47d2f85794ba716a4d3) Thanks [@cblokland90](https://github.com/cblokland90)! - Add optional `icon` to `defineNode(...)` so plugin nodes can set `NodeConfigBase.icon` for the workflow canvas (Lucide, `builtin:`, `si:`, or image URLs).

## 0.2.0

### Minor Changes

- [#41](https://github.com/MadeRelevant/codemation/pull/41) [`a72444e`](https://github.com/MadeRelevant/codemation/commit/a72444e25c4e744a9a90e231a59c93f8d90346e5) Thanks [@cblokland90](https://github.com/cblokland90)! - Add `WorkflowTestKit` and related engine test harness exports on `@codemation/core/testing`, with create-codemation templates and agent skills updated to document plugin unit tests.

### Patch Changes

- [#41](https://github.com/MadeRelevant/codemation/pull/41) [`a72444e`](https://github.com/MadeRelevant/codemation/commit/a72444e25c4e744a9a90e231a59c93f8d90346e5) Thanks [@cblokland90](https://github.com/cblokland90)! - Normalize run persistence around work items, execution instances, and run slot projections, while aligning the HTTP/UI run detail flow to run-centric naming. This also fixes AI agent tool schema serialization, nested tool item propagation, and execution inspector/canvas status handling for inline scheduler workflows.

## 0.1.0

### Minor Changes

- [#39](https://github.com/MadeRelevant/codemation/pull/39) [`cbfe843`](https://github.com/MadeRelevant/codemation/commit/cbfe843ef2363e400a219f4d0bcd05b091ab83b4) Thanks [@cblokland90](https://github.com/cblokland90)! - Add `WorkflowTestKit` and related engine test harness exports on `@codemation/core/testing`, with create-codemation templates and agent skills updated to document plugin unit tests.

## 0.0.19

### Patch Changes

- [#26](https://github.com/MadeRelevant/codemation/pull/26) [`405c854`](https://github.com/MadeRelevant/codemation/commit/405c8541961f41dcba653f352691a821b0470ca0) Thanks [@cblokland90](https://github.com/cblokland90)! - Fix manual trigger reruns and current-state resume behavior.

  Current-state execution now treats empty upstream outputs like the live queue planner, so untaken branches stay dead on resume. Manual downstream runs can also synthesize trigger test items through core intent handling instead of relying on host-specific trigger logic.

## 0.0.18

### Patch Changes

- f0c6878: Introduce Changesets, a single CI status check for branch protection, and the Codemation pre-stable license across published packages.
