diff --git a/PR_GRAPH_SUBPATH.md b/PR_GRAPH_SUBPATH.md new file mode 100644 index 00000000..34c0972c --- /dev/null +++ b/PR_GRAPH_SUBPATH.md @@ -0,0 +1,118 @@ +# PR Title + +feat(graph): expose plan tree via smithers-orchestrator/graph and add visual workflow builder sample + +# PR Body + +Closes #90 + +## Problem + +Smithers constructs an internal scheduling DAG via `buildPlanTree()` in `src/engine/scheduler.ts`, but this function and its types are not exported. External consumers who want to visualize or analyze workflow graphs are left with two incomplete options: + +1. The flat `tasks` array from `renderFrame()` — no edges, no dependency information. +2. The raw `xml` tree — requires reimplementing the XML-to-plan conversion that `buildPlanTree` already does. + +The actual graph that Smithers uses to schedule execution — the `PlanNode` tree — is the only representation that cleanly encodes task dependencies, parallel groups, and loop semantics. But it's internal. + +## What this PR adds + +### 1. `smithers-orchestrator/graph` subpath export + +A minimal new subpath that re-exports the existing runtime helper and types: + +```ts +export { buildPlanTree } from "../engine/scheduler"; +export type { PlanNode, RalphMeta } from "../engine/scheduler"; +``` + +This is zero new implementation code — just making existing internals available through a dedicated graph-focused entry point. + +**Files:** +- `src/graph/index.ts` — 2 lines +- `package.json` — 1 line (subpath export) +- `tsconfig.json` — 2 lines (path aliases) + +### 2. Documentation + +Adds `docs/runtime/graph.mdx` explaining: +- what `/graph` exports +- how to combine `renderFrame()` with `buildPlanTree()` +- how to derive `nodes[]` and `edges[]` for external graph tooling +- a complete example of building an n8n-style UI-friendly graph from the plan tree + +Updates `docs/runtime/render-frame.mdx` and `README.md` to point users at the new subpath. + +### 3. Visual workflow builder sample + +Adds `examples/graph-builder/` — a fully in-browser visual workflow editor that demonstrates what external tooling can build on top of `/graph`. + +The sample is a single self-contained HTML file with: +- node graph canvas with SVG edges, handles, and labeled connections +- support for agent, shell, approval, parallel, loop, and branch nodes +- drag-to-move node positioning +- zoom controls, fit view, horizontal/vertical orientation toggle +- minimap +- collapsible inspector panel with prompt, schema, and config editing +- generated Smithers TSX code preview +- runtime plan tree preview via `buildPlanTree` (inlined as pure browser JS) +- browser file picker for importing graph JSON or workflow TSX +- client-side TSX text parser for importing existing workflows +- graph JSON export for stable round-trip editing + +**No server, no API, no dependencies.** Open the HTML file in any browser. `buildPlanTree` and all its dependencies are inlined as pure functions. + +### 4. Test + +Adds `tests/graph-subpath.test.ts` verifying that `buildPlanTree` is importable and functional from `smithers-orchestrator/graph`. + +## Why this approach + +The issue discussion considered several options. This PR takes the most minimal path: + +- **Does not** export `scheduleTasks` or state-map internals +- **Does not** introduce a new stable graph node/edge schema +- **Does not** add server endpoints or CLI flags +- **Does not** add new Smithers core dependencies + +It exposes the existing XML-to-plan conversion behind a dedicated subpath, keeping the public surface small. The sample proves the API is sufficient for building rich external graph tooling without adding anything else to Smithers core. + +If external consumers prove out the need for a richer stable graph contract (e.g. explicit `nodes/edges` types), that can be added later on the same subpath without breaking what's here. + +## Validation + +### Automated + +```bash +bun test tests/graph-subpath.test.ts tests/scheduler-comprehensive.test.ts tests/worktree-plan.explicit.test.ts tests/nested-ralph-bug.test.ts +``` + +47 tests pass, 0 failures. Covers: +- subpath import resolution +- `buildPlanTree` export availability +- core plan tree behavior +- Ralph loop plan handling +- worktree / merge-queue plan handling +- nested Ralph edge cases + +### Manual + +- verified `smithers-orchestrator/graph` resolves correctly via `bun -e` +- verified the graph builder sample loads from `file://` with zero server +- verified TSX import of `examples/simple-workflow.tsx` and `examples/code-review-loop.tsx` via the in-browser text parser +- verified plan preview runs `buildPlanTree` entirely client-side +- verified generated Smithers TSX is syntactically valid + +## Files changed + +| File | Change | +|------|--------| +| `src/graph/index.ts` | New — 2-line re-export | +| `package.json` | Add `./graph` subpath export | +| `tsconfig.json` | Add path aliases for `smithers/graph` and `smithers-orchestrator/graph` | +| `docs/runtime/graph.mdx` | New — graph subpath documentation | +| `docs/runtime/render-frame.mdx` | Cross-reference to `/graph` | +| `README.md` | Brief graph tooling note | +| `tests/graph-subpath.test.ts` | New — subpath import test | +| `examples/graph-builder/index.html` | New — self-contained visual builder | +| `examples/graph-builder/README.md` | New — sample readme | diff --git a/README.md b/README.md index ee60637e..bbc181ad 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,17 @@ smithers list workflow.tsx smithers approve workflow.tsx --run-id abc123 --node-id review ``` +## Graph Tooling + +Smithers does not ship a visual workflow editor, but it exposes the XML-to-plan conversion the runtime uses internally: + +```ts +import { renderFrame } from "smithers-orchestrator"; +import { buildPlanTree } from "smithers-orchestrator/graph"; +``` + +That is enough to build external graph inspectors, React Flow canvases, and n8n-style workflow builders on top of Smithers without re-implementing its execution semantics. + ## Hot Module Replacement Edit your workflow files while a run is executing. Smithers watches your source tree and hot-reloads changes on save — prompts, config, agent settings, and component structure — without restarting the process or losing run state. diff --git a/docs/runtime/graph.mdx b/docs/runtime/graph.mdx new file mode 100644 index 00000000..f1457f58 --- /dev/null +++ b/docs/runtime/graph.mdx @@ -0,0 +1,128 @@ +--- +title: graph +description: Build workflow graph tooling on top of Smithers by converting rendered XML into the scheduler plan tree. +--- + +Smithers does not ship a visual workflow builder, but it does expose the same XML-to-plan conversion the runtime uses internally. That is enough to build external graph visualizers, inspectors, and node-based editors. + +## Import + +```ts +import { buildPlanTree } from "smithers-orchestrator/graph"; +import { renderFrame } from "smithers-orchestrator"; +``` + +## What `/graph` exports + +```ts +import { buildPlanTree } from "smithers-orchestrator/graph"; +import type { PlanNode, RalphMeta } from "smithers-orchestrator/graph"; +``` + +`buildPlanTree(xml)` converts the rendered `XmlNode` tree into the plan tree the engine schedules. + +## Basic usage + +```ts +import { renderFrame } from "smithers-orchestrator"; +import { buildPlanTree } from "smithers-orchestrator/graph"; +import workflow from "./workflow"; + +const snapshot = await renderFrame(workflow, { + runId: "preview", + iteration: 0, + input: {}, + outputs: {}, +}); + +const { plan, ralphs } = buildPlanTree(snapshot.xml); +console.log(plan, ralphs); +``` + +## PlanNode shape + +```ts +type PlanNode = + | { kind: "task"; nodeId: string } + | { kind: "sequence"; children: PlanNode[] } + | { kind: "parallel"; children: PlanNode[] } + | { + kind: "ralph"; + id: string; + children: PlanNode[]; + until: boolean; + maxIterations: number; + onMaxReached: "fail" | "return-last"; + } + | { kind: "group"; children: PlanNode[] }; +``` + +## Building a node graph UI + +A minimal graph builder can: + +1. call `renderFrame()` to get `snapshot.xml` +2. call `buildPlanTree(snapshot.xml)` +3. walk the returned `PlanNode` tree +4. emit your own UI-friendly `nodes[]` and `edges[]` + +For example: + +```ts +type UiNode = { id: string; label: string; kind: string }; +type UiEdge = { from: string; to: string; kind: string }; + +function planToUiGraph(plan: PlanNode | null) { + const nodes: UiNode[] = []; + const edges: UiEdge[] = []; + let nextId = 0; + + function visit(node: PlanNode, parentId?: string): string { + const id = node.kind === "task" ? node.nodeId : `${node.kind}:${nextId++}`; + nodes.push({ id, label: node.kind === "task" ? node.nodeId : node.kind, kind: node.kind }); + if (parentId) edges.push({ from: parentId, to: id, kind: "contains" }); + + if ("children" in node) { + let previousChildId: string | undefined; + for (const child of node.children) { + const childId = visit(child, id); + if (node.kind === "sequence" && previousChildId) { + edges.push({ from: previousChildId, to: childId, kind: "next" }); + } + previousChildId = childId; + } + if (node.kind === "ralph" && previousChildId) { + edges.push({ from: previousChildId, to: id, kind: "loop" }); + } + } + + return id; + } + + if (plan) visit(plan); + return { nodes, edges }; +} +``` + +This is enough to power React Flow, Dagre, Mermaid, n8n-style inspectors, or a custom workflow editor. + +## Why use `/graph` instead of re-parsing XML yourself? + +`buildPlanTree()` already captures Smithers scheduling semantics, including: + +- `smithers:sequence` → ordered children +- `smithers:parallel` / `smithers:merge-queue` → concurrent children +- `smithers:ralph` → loop node with iteration metadata +- `smithers:worktree` → grouping boundary + +Using `/graph` keeps third-party tooling aligned with the runtime instead of duplicating that mapping. + +## Scope + +The `/graph` subpath is intentionally minimal. It exposes the existing XML-to-plan conversion so external tools can build their own graph model and UI on top. + +## Related + +- [renderFrame](/runtime/render-frame) +- [Events](/runtime/events) +- [CLI overview](/cli/overview) diff --git a/docs/runtime/render-frame.mdx b/docs/runtime/render-frame.mdx index 1917bb4b..c65de012 100644 --- a/docs/runtime/render-frame.mdx +++ b/docs/runtime/render-frame.mdx @@ -207,6 +207,12 @@ const snap2 = await renderFrame(workflow, { }); ``` +### Building a Workflow Visualizer + +The XML tree and task list can be used to build visual representations of the workflow DAG. The `xml` field mirrors the JSX structure, while `tasks` provides the flattened execution order. + +If you want the same XML-to-plan conversion Smithers uses internally, import `buildPlanTree` from `smithers-orchestrator/graph` and pass `snapshot.xml` to it. + ### CLI ```bash @@ -217,6 +223,7 @@ Prints the `GraphSnapshot` as JSON to stdout. ## Related +- [graph](/runtime/graph) -- Convert rendered XML into the plan tree used for graph tooling. - [runWorkflow](/runtime/run-workflow) -- Execute the workflow. - [Events](/runtime/events) -- Monitor execution progress. - [Execution Model](/concepts/execution-model) -- The render-execute-persist loop. diff --git a/examples/graph-builder/README.md b/examples/graph-builder/README.md new file mode 100644 index 00000000..af1befdd --- /dev/null +++ b/examples/graph-builder/README.md @@ -0,0 +1,63 @@ +# Smithers Graph Builder + +A fully in-browser visual workflow editor for Smithers. No server required. + +One self-contained HTML file. Open it directly or host it anywhere. + +## What it does + +- lets you add and edit a richer set of node types + - agent prompt + - shell command + - approval gate + - parallel block + - bounded review loop + - branch +- visualizes the workflow as a node graph with + - labeled edges + - branch fan-out + - loop-back edges + - zoom controls + - horizontal / vertical reorientation + - a minimap +- edits prompts, output keys, schemas, commands, loop settings, and branch conditions inline +- exports both + - `workflow.graph.json` + - `workflow.tsx` +- supports loading local workflows from + - `workflow.graph.json` + - `workflow.tsx` + - a directory containing those files +- supports uploading graph JSON or a self-contained workflow TSX file manually +- validates generated code through Smithers before local save +- uses `buildPlanTree()` from `smithers-orchestrator/graph` to show the runtime plan tree + +## Stability model + +The builder is intentionally **graph-first**. + +- If a `workflow.graph.json` sidecar exists, it is treated as the source of truth. +- Generated `workflow.tsx` is derived from that graph model. +- Loading an existing standalone `workflow.tsx` works as a **best-effort import** using Smithers rendering. This is useful for drafting and visualization, but it is not guaranteed to round-trip perfectly. + +That split keeps editing stable and explicit while still letting the builder leverage Smithers to import existing workflows. + +## Run + +Open `index.html` in any browser. That's it. + +```bash +open examples/graph-builder/index.html +``` + +Or host it on any static file server, CDN, or paste it into a gist. + +## How it works + +Everything runs in the browser: + +- `buildPlanTree` from `smithers-orchestrator/graph` is inlined as a pure function +- TSX import uses a client-side text parser that extracts Smithers component structure +- graph editing, code generation, plan preview — all client-side JS +- no fetch calls, no API, no server, no dependencies + diff --git a/examples/graph-builder/index.html b/examples/graph-builder/index.html new file mode 100644 index 00000000..79ca52b2 --- /dev/null +++ b/examples/graph-builder/index.html @@ -0,0 +1,495 @@ + + +
+ + +