Repository navigation
feat(flows): tinyflows Phases 3–8 — editable canvas, authoring/templates, workflow-builder agent, engine 0.5.0, Skills rename - #4570
Conversation
Move NODE_KIND_META / DEFAULT_NODE_META / COLOR_CLASSES / handleOffsets out of FlowNodeComponent into a dependency-free lib/flows/nodeKindMeta.ts so the editable canvas's node palette can pull the same emoji/accent from one source of truth. Also exports NODE_KINDS (ordered kind list) and a nodeKindMeta() lookup helper. FlowNodeComponent now imports from it — render output is unchanged. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Add two pure helpers to graphAdapter for the editable canvas: - isValidFlowConnection(connection, nodes, edges): reuses the canvas's derived input/output ports to accept only valid connections; rejects missing endpoints, self-loops, unknown nodes/handles, and duplicate edges. Shared by <ReactFlow isValidConnection> (live drag feedback) and onConnect (the commit). - createFlowNode(kind, position, id, name?): builds a fresh xyflow node with a single default main input/output handle, empty config/ports — round-trips cleanly through xyflowToWorkflowGraph. Unit tests cover both plus a palette-built graph round-trip. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…client
Wire the three Phase 1-2 backend controllers into the flows RPC client,
following the existing callCoreRpc + unwrapCliEnvelope pattern:
- updateFlow(id, {name?, graph?, requireApproval?}) — the canvas Save
path (flows_update; server re-validates before persisting).
- validateFlow(graph) — pure, cheap flows_validate for inline validation.
- listFlowConnections() — secret-free FlowConnection[] for the node-config
credential picker.
Adds FlowValidation / FlowConnection / FlowUpdate wire types.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Add an `editable` prop to FlowCanvas (default false — the read-only viewer at /flows/:id is untouched). When editable, FlowCanvas delegates to the new EditableFlowCanvas, which lifts nodes/edges into controlled state and wires: - drag/move — nodesDraggable; onNodesChange persists positions. - connect — port-aware onConnect via isValidFlowConnection; rejects invalid connections (self-loop, unknown handle, duplicate) and reports them through onInvalidConnection; isValidConnection paints live drag feedback. - delete — Backspace/Delete plus an explicit "Delete selected" toolbar button; deleting a node also drops its incident edges. - add — NodePalette lists all 12 node kinds (shared emoji/accent) and inserts by click (cascade position) or drag-drop (under the cursor). - save — a "Save" button serializes the live canvas to a WorkflowGraph via xyflowToWorkflowGraph and hands it to onSave (the flows_update call + dirty-guard land in 3d). FlowCanvasPage drops the now-removed `readonly` prop (default is read-only). New i18n keys flows.palette.* / flows.editor.* added to en + all 13 locales. Behavior tests drive the palette/save click paths. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
… 3b) New flows.nodeConfig.* namespace for the node config side-panel: drawer chrome, shared field affordances (expression badge, key/value map, raw-JSON editor), the credential picker, and per-kind form labels (trigger, http_request, agent, tool_call, condition, switch, transform, code). Real translations added to all 13 non-English locales (ar, bn, de, es, fr, hi, id, it, ko, pl, pt, ru, zh-CN); i18n:check key-parity passes and the additions introduce zero new i18n:english:check flags. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…tch (Phase 3b) Right-hand drawer that opens on single-node select in the editable canvas: - Shared, controlled field primitives (nodeConfigFields): text/textarea/ select, an =-expression field with a monospace input + "Expression" affordance, a key→value map (headers / transform set), a JSON editor that holds invalid intermediate buffers, and a secret-free credential picker fed by flows_list_connections (shows display/kind only). - Per-kind forms (nodeConfigForms) for high-traffic kinds — trigger (schedule/webhook/app_event), http_request (method/url/headers/body + http-only credential), agent (prompt/model + credential), tool_call (slug/args + credential), condition (field), switch (expression/field), transform (set map), code (language toggle + source). Field keys mirror what vendor/tinyflows reads at runtime. - NodeConfigDrawer shell: kind badge + inline name edit, the per-kind form, a raw-JSON escape hatch for kinds without a form (plus an opt-in Edit-as-JSON toggle for every kind), Escape/close semantics. Every edit is a controlled onChange(nodeId, patch) — no local config mirror that drifts. Co-located Vitest covers http_request, transform, and trigger forms, the kind-filtered credential picker, and the drawer (name/form edits, raw-JSON toggle, raw default for form-less kinds, close). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Wire the NodeConfigDrawer into EditableFlowCanvas: onSelectionChange opens the drawer for an unambiguous single-node selection (any edge or 0/2+ nodes closes it), edits apply to live node state via a controlled updateNode(nodeId, patch), and closing deselects so re-clicking the same node reopens. Credential refs are loaded once via flows_list_connections, guarded so a non-Tauri/RPC failure just yields the picker's empty state. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Add the pieces the editable Workflow Canvas needs to surface flows_validate
results inline, ahead of wiring them into the canvas:
- lib/flows/flowValidation.ts: erroredNodeIds() maps graph-level validation
error strings back to the node ids they name (whole-token match, hyphen-safe)
so the canvas can ring the offending node(s). Unit-tested.
- useFlowValidation.ts: debounced + on-demand validation of the live draft
against openhuman.flows_validate; RPC failures fail open (advisory only).
- FlowValidationBanner.tsx: inline errors (coral) / warnings (amber) surface,
plus a distinct save-failure row.
- flowCanvasStyles.css: .flow-node-error ring for a node named by a hard error.
- i18n: flows.editor.{validate,validating,discard,unsaved,saving,saveBlocked,
errorsTitle,warningsTitle,saveFailedTitle,leave*} across all 14 locales.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…e 3c/3d)
Turn the editable Workflow Canvas into a real builder host:
Validation UX (3c):
- EditableFlowCanvas runs useFlowValidation on the live draft (debounced +
a manual "Validate" button), renders FlowValidationBanner (errors block Save,
warnings don't), and rings error-named nodes via displayNodes className.
- Save is gated on hard errors (button disabled + belt-and-braces guard).
Draft/dirty state (3d):
- Dirty is derived by comparing the serialized live graph to a saved baseline;
reported up via onDirtyChange. A "Discard changes" button resets to baseline;
an "Unsaved changes" badge shows while dirty.
- Save (no autosave — a live flow would fire schedules) awaits onSave and only
advances the baseline on success; a failure surfaces inline and stays dirty.
- FlowCanvasPage now hosts the editable canvas: onSave -> flows_update(id,
{ graph }); an unsaved-changes guard warns on tab close (beforeunload) and
intercepts the Back button with a confirm dialog. (HashRouter has no data
router, so full useBlocker route interception isn't available.)
Tests: invalid graph blocks Save + rings the node + shows the banner; warnings
allow Save; dirty gates Save/Discard, Discard resets, Save clears dirty; a Save
failure surfaces inline; page persists via flows_update and prompts before
leaving while dirty.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…Progress Consume the core's FlowRunProgress feed on the frontend (closes Phase 1's deferred follow-up). New useFlowRunProgress(runId) hook subscribes to the socket `flow:run_progress`/`flow_run_progress` aliases and yields a node_id -> live status map, filtered to the watched run and torn down on unmount / run change (mirrors useTinyplaceStream). EditableFlowCanvas composes the status into displayNodes so nodes ring running (pulsing)/success/error — n8n's signature interaction. The 2s useFlowRunPoller stays as the durable fallback; the socket just makes it live. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…pector Add a Run button to FlowCanvasPage that starts the persisted flow and hands its thread_id to the canvas as activeRunId, so the live overlay is reachable (runs the saved version, not the dirty draft — matching the explicit-save model). Reflect the same live per-node status in FlowRunInspectorDrawer by colouring each step's dot from useFlowRunProgress (ocean/sage/coral), keeping the poller's durable step list as the source of truth. Adds flows.editor.run / running / runFailed i18n keys with full translations across all locales. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Add an "Open in canvas" action to the chat WorkflowProposalCard so a user can review/edit an agent-proposed WorkflowGraph on the editable canvas BEFORE the single persistence gate. Critical invariant: opening a draft never persists or enables a flow. The proposed graph rides in router `location.state` (ephemeral — dropped on reload, exactly what an unsaved draft should be) to a new `/flows/draft` route hosting the editable canvas. No flows_create / flows_update fires on open; the canvas's own Save button stays the one persistence gate (for a draft it calls flows_create, then replaces into the new flow's `/flows/:id` canvas). Drafts aren't runnable until saved. - lib/flows/canvasDraft.ts: shared route constant + draft-state type/guard - WorkflowProposalCard: "Open in canvas" button navigates with the draft; proposal left intact in the thread (not dismissed) - FlowCanvasPage: FlowEditor generalized to saved-or-draft; new FlowCanvasDraftPage reads the draft from router state (empty state when absent); AppRoutes wires `/flows/draft` before `/flows/:id` - i18n: chat.flowProposal.openInCanvas + flows.canvas.draftMissing across all 14 locales - Tests: open-in-canvas navigates without persisting; draft renders without fetch, saves via flows_create (never flows_update), empty state Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…data) Add six structurally-valid tinyflows templates under app/src/lib/flows/ templates/ as static JSON with a typed index (no dynamic imports), plus a validity test asserting each graph has exactly one trigger, unique node ids, and edges that reference existing nodes. Display strings are i18n'd via flows.templates.* / flows.chooser.* keys added to en.ts and all 13 locales. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Replace FlowsPage's /chat TODO with a chooser modal (NewWorkflowModal): start from scratch (creates a blank flow with a single manual trigger via flows_create, then opens the canvas), from a template (FlowTemplateGallery over the bundled templates), or describe it (interim Chat hand-off, TODO phase-5). The empty state also surfaces the template gallery inline. Shared create-and-open logic lives in the useCreateFlow hook; the blank starter graph in lib/flows/newFlow.ts. Adds behavior tests for all three chooser paths, the blank-graph shape, and the empty-state gallery, and updates the FlowsPage tests for the new chooser behavior. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Add `openhuman.flows_import(graph, {format})`: parse a native tinyflows
WorkflowGraph or an n8n workflow export, migrate + validate it host-side,
and return the normalized graph plus non-fatal warnings WITHOUT persisting
(the user Saves via the existing flows_create gate).
- New `flows/n8n_import.rs`: maps the overlapping n8n vocabulary (if→condition,
switch→switch, merge→merge, splitOut→split_out, httpRequest→http_request,
code/function→code, scheduleTrigger/cron→trigger(schedule), webhook→
trigger(webhook), manualTrigger→trigger(manual)). Unmapped node types land as
annotated placeholder `transform` nodes (original type+params preserved), never
a failed import. `={{ $json.path }}` expressions translated to `=`-jq where
trivial, else kept raw + warned. Connections/positions preserved; triggers
reconciled to exactly one (synthesize manual / demote extras) so validate passes.
- `FlowImport { graph, warnings }` type; auto-detect native vs n8n by shape.
- Wired via schemas.rs + all.rs registry (no dispatch.rs). Unit tests for the
n8n mapping (IF→condition, unmapped→placeholder, expression translation, etc.).
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…ort UI - Export: a per-row "Export" action downloads a flow's WorkflowGraph as `<name>.flow.json` (pure client-side serialize; new lib/flows/exportFlow.ts). - Import: a header "Import" button opens a file picker → flowsApi.importFlow → runs the JSON through `openhuman.flows_import` (host migrate + validate + best-effort n8n mapping) and opens the normalized graph on the editable canvas as an UNSAVED draft via the existing /flows/draft route. Nothing persists until the user Saves. Auto-detects native vs n8n. - Import warnings ride in FlowCanvasDraftState.importWarnings and surface as dismissible toasts over the draft canvas so n8n approximations aren't lost. - i18n keys added to en.ts + all 13 locales (real translations). - Vitest: exportFlow (slug/serialize/download), flowsApi.importFlow, FlowListRow export action, FlowsPage import/export flows. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Exercises `openhuman.flows_import` end-to-end: a native tinyflows graph imports warning-free; an auto-detected n8n export maps IF→condition and an unmapped Airtable node to an annotated placeholder with a warning; and neither call persists a Flow (flows_list stays empty). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…run) Adds the deliberately narrow agent tool belt for authoring tinyflows graphs in chat (src/openhuman/flows/builder_tools.rs), registered in tools/ops.rs: - revise_workflow — validate a revised draft -> proposal (never persists; same contract as propose_workflow, reuses build_summary) - list_flows / get_flow / get_flow_run / list_flow_connections — read-only (PermissionLevel::None), delegate to flows::ops - search_tool_catalog — grounds tool_call slugs in the real curated Composio catalog (agent_ready_toolkits + catalog_for_toolkit) - dry_run_workflow — runs a DRAFT against tinyflows MOCK capabilities (no real side effects), autonomy-tier gated (read-only refuses), output labeled sandbox; enables the tinyflows `mock` feature 16 unit tests: each tool's happy path, read-only tools return None, revise never persists, dry_run tier-gated + sandbox-labeled. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Adds the workflow_builder built-in agent (Worker tier, burst hint) that
turns a natural-language automation request into a validated tinyflows
WorkflowGraph PROPOSAL — it never persists or enables a flow.
- agent_registry/agents/workflow_builder/{agent.toml,prompt.rs,prompt.md,mod.rs}
ToolScope::Named is EXACTLY the Phase 5b propose-or-read belt (no shell,
no file writes, no channel sends, no flows_create/update/set_enabled);
overridable by user TOML via the existing definition_loader path.
- prompt.md teaches the 12 node kinds, =/jq expression semantics, port/edge
rules, which trigger kinds fire live (manual/schedule/app_event) vs warn,
per-node on_error/retry/requires_approval, and the propose-never-persist
invariant.
- Registered in agents/mod.rs + loader.rs BUILTINS; reachable by delegation
as `build_workflow` from the orchestrator's subagents allowlist.
- Registry tests: narrow scope + Worker leaf + no persistence tools +
orchestrator delegation edge; prompt teaches the invariant; id present in
the builtin_definitions expected-ids test.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
… routing Add a "Describe a workflow…" prompt bar to the Flows page (hero on the empty state, compact otherwise). Submitting spawns a workflow-builder turn in a dedicated thread via a new useWorkflowBuilderChat hook and renders the returned proposal inline with the existing WorkflowProposalCard. Turns route to the workflow_builder specialist via the orchestrator's build_workflow delegation (there is no direct entry-agent selection in the runtime), so the propose_workflow/revise_workflow result is produced inside the delegated worker. ChatRuntimeProvider now parses that proposal from onSubagentToolResult too (not just onToolResult), keyed on the parent thread, so the card surfaces regardless of which agent emitted it. Still validate-only: Save & enable stays the sole persistence gate. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Dock a workflow-builder copilot on the editable canvas (/flows/:id and
drafts). Each turn injects the current draft graph as context and calls
revise_workflow; the returned proposal enters a diff-preview overlay —
added nodes ringed sage, removed nodes ghosted — that the user Accepts
(applies to the local Phase 3d draft) or Rejects (reverts). A revise loop
("add a Slack notification on failure", "weekdays only").
EditableFlowCanvas gains onGraphChange + added/removed id sets + saveDisabled;
FlowEditor holds the draft/preview state and re-seeds the canvas per accept/
reject. Nothing persists — Save stays the only gate and is disabled while a
proposal is under review.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
A failed run's inspector now surfaces a "Fix with agent" action that hands the run context (flow id, run id, error, failing node ids) up through FlowRunsDrawer to the Flows page, which navigates to the flow's canvas with a copilot repair seed in location.state. The copilot opens preloaded and auto-sends a repair turn (get_flow_run + diagnose + propose). Propose-only: no persistence. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…etention/pruning Add openhuman.flows_duplicate(id): creates an independent copy of a saved flow's graph under a new id, name suffixed " (copy)", enabled=false so it is NOT schedule/trigger-bound and can never immediately fire. require_approval carries over; run history resets. Add per-flow run retention: MAX_FLOW_RUNS_PER_FLOW cap pruned on every new-run insert (atomic in the same connection) and via a manual openhuman.flows_prune_runs sweep. Prune deletes only terminal runs (completed/failed/cancelled) outside the newest-N window; a running or pending_approval run is never pruned, so a parked run can't be removed out from under a flows_resume. Wired both controllers via schemas.rs + the flows registry (all.rs picks them up through the aggregation fns). Verbose [flows] logging throughout. Tests: duplicate produces a disabled, unbound copy with a new id and identical graph; pruning keeps the N newest and spares pending_approval/running; insert auto-prunes past the cap; store round-trips. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…ut pairing
Add the n8n-signature table⟷JSON toggle to the run inspector's per-step
output, plus a paired_item-driven "Source" affordance that reveals the input
item a given output item derived from.
- lib/flows/runItems.ts: forgiving normalizer turning a step's opaque `output`
into `FlowRunItem[]` ({ json, binary, pairedIndex }); union-column derivation
and cell/JSON formatters. Never throws on messy real-world payloads.
- components/flows/RunItemDataBrowser.tsx: extracted data browser. Table view
(one row per item, union columns, truncating cells, horizontal scroll), JSON
view (pretty-printed payloads), binary placeholder chips (name/MIME, never
inlined), and per-row source-input reveal when paired_item resolves.
- FlowRunInspectorDrawer: StepRow now renders the data browser inside the
Output disclosure; passes the upstream step's items as pairing input.
- i18n: 10 new flowRuns.inspector.* keys across en + all 13 locales.
- Tests: normalizer unit tests, data-browser component tests (table rows/union
columns, JSON toggle, binary chip, pairing present/absent), updated drawer
test for the new table default.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Add app/test/e2e/specs/flows.spec.ts driving the Workflows flow end-to-end through real UI clicks: open /flows, create from scratch via the New workflow chooser, run on the editable canvas, then open the run inspector and assert a terminal (Completed) status with at least one step. Uses existing data-testids on the flows components and the shared element/reset/nav helpers; no component changes needed. Trigger-only graph runs deterministically in the local tinyflows engine under the mock backend. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
… (Phase 7A) Bump vendored tinyflows to 0.4.0 (adds the WorkflowResolver capability + the sub_workflow-by-id engine path) and wire the host side: - flows::ops::load_flow_graph — sync id → saved-flow WorkflowGraph lookup. - OpenHumanWorkflowResolver (caps.rs) implements tinyflows::caps::WorkflowResolver over the flows store; unknown id → capability error naming the id. Wired into build_capabilities so a sub_workflow node carrying `workflow_id` loads and runs the referenced saved flow's graph. Verbose [flows] logging throughout. - Cargo.toml dep requirement 0.3 → 0.4 so the [patch] path override still applies. Host tests: resolver loads a saved flow's graph by id; unknown id errors. Gates: GGML_NATIVE=OFF cargo check green; cargo test --lib openhuman::flows:: (142) and the resolver tests green. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…ion, cancellation token, agent sub-ports) Advance the vendor/tinyflows submodule gitlink to the Phase 7B engine commit and bump the dep requirement (0.4 -> 0.5). Host cancel path (task-abort + run_registry) left as-is; the engine now additionally exposes a cooperative CancellationToken (run_cancellable / resume_cancellable) available for future belt-and-suspenders wiring. No host source changes required — the new engine API is additive and the existing run/resume entry points are unchanged. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
The `src/openhuman/workflows/` domain is the Skills discovery/parse/
install/run backend (its README literally starts with "# Skills"). Rename
it to `skills::` to free the "Workflows" name for tinyflows.
- git mv src/openhuman/workflows -> src/openhuman/skills
- src/openhuman/mod.rs: `pub mod workflows;` -> `pub mod skills;`
- all_workflows_{registered_controllers,controller_schemas}
-> all_skills_* (+ call sites in src/core/all.rs)
- module path crate::openhuman::workflows:: -> crate::openhuman::skills::
- RPC methods renamed via `namespace: "workflows"` -> `"skills"`:
workflows_{create,list,update,describe,install_from_url,read_resource,
uninstall,run,cancel,read_run_log,recent_runs} -> skills_*
- handler fns handle_workflows_* -> handle_skills_*, dispatcher
workflows_schemas -> skills_schemas, namespace_description arm,
doc comments
- On-disk `.openhuman/workflows/` dirs, `Workflow`/`WorkflowSummary`
types, and the `workflows` wire output field are UNCHANGED (no data
migration). skill_registry/skill_runtime/flows/workflow_run untouched.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Follow-up to the workflows->skills domain rename: update JSON-RPC E2E
callers (openhuman.workflows_{create,list,describe,uninstall} ->
skills_*) and openhuman_core::openhuman::workflows:: module paths in
json_rpc_e2e.rs, agent_round26_raw_coverage_e2e.rs, and
x402_twit_sh_live.rs. The `workflows` list-result data field is left
unchanged (wire data, not an RPC method).
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
The backend `workflows::` domain was renamed to `skills::`, renaming its
JSON-RPC namespace prefix workflows_* -> skills_*. Repoint every frontend
call site (workflows_{list,read_resource,create,update,install_from_url,
describe}) plus the corresponding Vitest mock assertions and doc comments.
The skill_runtime_* and skill_registry_uninstall calls are unchanged — those
are separate, still-registered backend domains, not part of the rename.
Wire-field keys left intact: skills_list still returns its array under the
`workflows` JSON key, and create/update still return `workflow`.
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Pre-push hook's format:check surfaced formatting drift the per-file agent checks missed (long-chain/match reflow in Rust, prettier normalization across flows UI + i18n locale files). Formatting-only; no logic change. Includes a benign transitive Cargo.lock re-resolution (windows-sys 0.60→0.61). tinyflows pin unchanged (0.5.0). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (16)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (13)
📝 WalkthroughWalkthroughThis PR renames the workflows subsystem to skills across Rust and frontend APIs, and adds editable flow canvas, template/gallery creation, import/export, copilot, live run repair, and the ChangesWorkflows→Skills Rename
Workflow Canvas and Builder Feature
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant WorkflowCopilotPanel
participant useWorkflowBuilderChat
participant ChatRuntimeProvider
participant EditableFlowCanvas
User->>WorkflowCopilotPanel: type revise instruction, Send
WorkflowCopilotPanel->>useWorkflowBuilderChat: send(buildRevisePrompt)
useWorkflowBuilderChat->>ChatRuntimeProvider: chatSend to dedicated thread
ChatRuntimeProvider-->>useWorkflowBuilderChat: WorkflowProposal (revise_workflow)
useWorkflowBuilderChat-->>WorkflowCopilotPanel: proposal
WorkflowCopilotPanel->>WorkflowCopilotPanel: diffGraphs(current, proposal)
User->>WorkflowCopilotPanel: Accept
WorkflowCopilotPanel->>EditableFlowCanvas: onAccept(proposal)
sequenceDiagram
participant User
participant FlowRunInspectorDrawer
participant useFlowRunProgress
participant FlowsPage
participant FlowCanvasPage
User->>FlowRunInspectorDrawer: open failed run
FlowRunInspectorDrawer->>useFlowRunProgress: live node statuses
User->>FlowRunInspectorDrawer: click "Fix with agent"
FlowRunInspectorDrawer->>FlowsPage: onFixWithAgent(FlowRepairRequest)
FlowsPage->>FlowCanvasPage: navigate with location.state.copilotRepair
FlowCanvasPage->>FlowCanvasPage: seed WorkflowCopilotPanel repair turn
Possibly related issues
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 627f33e422
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| setDraftGraph(proposal.graph as WorkflowGraph); | ||
| setPreview(null); | ||
| setCanvasVersion(v => v + 1); |
There was a problem hiding this comment.
Preserve copilot proposals as unsaved edits
When a user accepts a copilot proposal and makes no additional manual edits, this remounts the canvas with the proposed graph as the new initial state. EditableFlowCanvas seeds its dirty baseline from those initial nodes/edges, so the accepted proposal is immediately considered clean: the Save button stays disabled and the navigation guard stands down, causing the accepted changes to be lost on back/reload instead of being persistable through the required Save gate.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 74c94ea — EditableFlowCanvas now accepts an initialDirty prop that force-seeds the dirty flag at mount (cleared on a real Save/Discard). FlowCanvasPage computes it by diffing the canvas's current graph against a ref of the last-persisted graph (only advanced by a successful Save), so an accepted-but-unpersisted proposal stays flagged dirty across the canvasVersion remount — this generalizes to the reject/preview remounts too, not just accept. Added a regression test in EditableFlowCanvas.validation.test.tsx.
| ports: [], | ||
| inputPorts: [DEFAULT_PORT], | ||
| outputPorts: [DEFAULT_PORT], |
There was a problem hiding this comment.
Seed branch nodes with runtime branch ports
When a user adds a condition node from the palette, it is created with no declared ports and only a main output handle, but conditions route via true/false ports in the flow contract/templates and the config drawer has no way to edit node ports. This makes newly-authored conditional flows impossible to wire correctly from the canvas: authors can only draw a main edge, which does not correspond to the runtime branch outputs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 74c94ea — createFlowNode now seeds condition nodes with declared true/false output ports at creation time (other kinds unaffected; switch's case ports stay config/edge-derived since they only materialize once wired). Added a regression test in graphAdapter.test.ts.
| if let Some(code) = cfg.remove(src) { | ||
| cfg.entry("code".to_string()).or_insert(code); |
There was a problem hiding this comment.
Import n8n code into the source field
For n8n code/function nodes, this stores the imported body under config.code, while the rest of the flow surface and execution contract use config.source for code nodes. Imported code nodes therefore open in the canvas with an empty Source field (and execute without the imported body unless the user manually rewrites it), so code-heavy n8n imports silently lose their executable content.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0d92f23 — map_code now writes the translated n8n jsCode/functionCode/pythonCode body under source (the key the code node actually reads, per vendor/tinyflows/src/nodes/integration/code.rs) instead of code. Updated the existing unit test to assert the correct key.
…ig.code The code/function node reads its body from config.source (vendor/tinyflows/src/nodes/integration/code.rs), but map_code() was writing the translated jsCode/functionCode/pythonCode body under a `code` key instead — so imported code nodes opened in the canvas with an empty Source field and silently lost their executable content until the author manually rewrote it (addresses @chatgpt-codex-connector on src/openhuman/flows/n8n_import.rs:271). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
A palette-added condition node had no declared ports and only the default main output handle, but tinyflows' condition node always routes runtime output through true/false (vendor/tinyflows/src/nodes/control_flow/condition.rs) and the node config drawer has no port editor — so a freshly-authored conditional flow was impossible to wire correctly: only a single main edge could ever be drawn, which doesn't correspond to the runtime branch outputs. createFlowNode now seeds condition nodes with declared true/false ports (other kinds are unaffected — e.g. switch's case ports stay config/edge -derived, since they only materialize once wired, per effectiveOutputPorts' existing doc comment) (addresses @chatgpt-codex-connector on app/src/lib/flows/graphAdapter.ts:254). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
Accepting a copilot proposal (with no further manual edit) bumps FlowCanvasPage's canvasVersion to remount EditableFlowCanvas with the proposed graph as its new nodes/edges. That component seeds its dirty baseline from whatever it's mounted with, so the accepted-but-unpersisted proposal instantly read as clean: Save stayed disabled and the unsaved -changes nav guard stood down, silently losing the accepted change on back/reload instead of gating it behind the required Save. EditableFlowCanvas now accepts an initialDirty prop that force-seeds the dirty flag at mount (cleared on a real Save/Discard); FlowCanvasPage computes it by diffing the canvas's current graph against a ref of the last-persisted graph (only advanced by handleSave), so it survives any number of accept/reject/preview remounts, not just the accept case (addresses @chatgpt-codex-connector on app/src/pages/FlowCanvasPage.tsx:178). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/openhuman/skills/schemas/handlers.rs (1)
32-66: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winStale
[workflows]log prefixes survived the handler rename.
handle_skills_list(lines 36, 56),handle_skills_cancel(line 182), andhandle_skills_update(lines 264, 277) still emit"[workflows][rpc] ..."tracing labels, while every other handler in this file (create,read_resource,install_from_url,uninstall,describe,recent_runs,read_run_log) was consistently updated to"[skills][rpc] ...". This will make log-based debugging/searching inconsistent for the renamed RPC surface.🏷️ Proposed fix for stale log prefixes
- tracing::debug!(include_skills, "[workflows][rpc] list automations"); + tracing::debug!(include_skills, "[skills][rpc] list automations"); @@ - "[workflows][rpc] list result" + "[skills][rpc] list result" @@ - tracing::info!(run_id = %payload.run_id, cancelled, "[workflows][rpc] cancel"); + tracing::info!(run_id = %payload.run_id, cancelled, "[skills][rpc] cancel"); @@ - "[workflows][rpc] update" + "[skills][rpc] update" @@ - tracing::debug!(error = %err, "[workflows][rpc] update: rejected"); + tracing::debug!(error = %err, "[skills][rpc] update: rejected");Also applies to: 174-188, 255-282
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/skills/schemas/handlers.rs` around lines 32 - 66, The tracing labels in the renamed skills RPC handlers still use the old “[workflows][rpc]” prefix, making logs inconsistent with the rest of the file. Update the debug calls in handle_skills_list, handle_skills_cancel, and handle_skills_update to use the “[skills][rpc]” prefix everywhere those messages are emitted, matching the other handlers in this module. Keep the existing log structure and fields unchanged aside from the prefix.
🧹 Nitpick comments (7)
app/src/lib/i18n/zh-CN.ts (1)
3673-3674: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winInconsistent term for "agent" within the new template strings.
These new template descriptions use 代理 for "agent" (
'代理编写简短摘要并发布到频道。','传入的 Webhook 由代理分流,然后通知你。','询问代理'/'一个简单的手动触发器,将任务交给代理。'), while the rest of this same PR'sflows.*namespace consistently uses 智能体 for "agent" (e.g.flows.nodeKind.agent:'智能体', and the newly addedflows.nodeConfig.agent.promptLabel:'给智能体的指令…'). Recommend aligning these three template strings to 智能体 for consistency.🌐 Proposed fix
- 'flows.templates.daily-digest.description': '按计划,代理编写简短摘要并发布到频道。', + 'flows.templates.daily-digest.description': '按计划,智能体编写简短摘要并发布到频道。', 'flows.templates.scheduled-scrape.name': '定时抓取到记忆', 'flows.templates.scheduled-scrape.description': '按计划获取来源,重塑结果,并存储到记忆中。', - 'flows.templates.webhook-triage.name': 'Webhook 分流与通知', - 'flows.templates.webhook-triage.description': '传入的 Webhook 由代理分流,然后通知你。', + 'flows.templates.webhook-triage.description': '传入的 Webhook 由智能体分流,然后通知你。', ... - 'flows.templates.ask-agent.name': '询问代理', - 'flows.templates.ask-agent.description': '一个简单的手动触发器,将任务交给代理。', + 'flows.templates.ask-agent.name': '询问智能体', + 'flows.templates.ask-agent.description': '一个简单的手动触发器,将任务交给智能体。',Also applies to: 3677-3678, 3683-3684
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/lib/i18n/zh-CN.ts` around lines 3673 - 3674, The new flows.template strings use 代理 for “agent”, but the rest of the flows namespace in this PR uses 智能体, so align these template descriptions for consistency. Update the affected translation entries in zh-CN.ts for the daily-digest, webhook relay, and ask-agent templates to use 智能体 wording, following the existing terminology used by flows.nodeKind.agent and flows.nodeConfig.agent.promptLabel.src/openhuman/skills/schemas/wire_types.rs (1)
251-253: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueStruct name still carries old
Workflowsprefix.Field type was renamed to
skills::run_log::ScannedRun, but the struct itself is stillWorkflowsRecentRunsResulteven though the corresponding schema is nowskills_recent_runs. Consider renaming for consistency with the rest of the renamed surface (e.g.SkillsRecentRunsResult), unless this is intentionally kept for wire/serialization compatibility.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/openhuman/skills/schemas/wire_types.rs` around lines 251 - 253, The result struct still uses the old Workflows prefix even though the schema and field type were renamed, so update the name in the wire_types module for consistency. Rename WorkflowsRecentRunsResult to match the new skills_recent_runs surface (for example, SkillsRecentRunsResult) and check any references to this struct so the renamed symbol is used consistently unless the old name is required for serialization compatibility.app/src/components/flows/RunItemDataBrowser.tsx (1)
263-265: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
scope="row"is invalid on<td>.The
scopeattribute only applies to<th>elements; on a<td>it's silently ignored, so no row-header semantics are actually conveyed to assistive tech.♿ Proposed fix
- <td className="px-1.5 py-1 font-mono text-content-faint" scope="row"> + <th className="px-1.5 py-1 font-mono text-content-faint font-normal text-left" scope="row"> {index + 1} - </td> + </th>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/flows/RunItemDataBrowser.tsx` around lines 263 - 265, The row index cell in RunItemDataBrowser is using scope on a td, which is invalid and does not provide row-header semantics. Update the table markup in the cell that renders index + 1 so that scope is removed from the td, and if row-header behavior is intended, move the semantics to a th element in that same row rendering instead.app/src/components/flows/NewWorkflowModal.tsx (1)
71-92: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider logging view-transition actions too.
startFromScratch/startFromTemplatelog viadebug, butopenGallery/backToChooser(and theonDescribehand-off) don't. As per coding guidelines, "On new or changed flows, add verbose debug logging with stable prefixes, correlation fields... incomplete changes are those lacking logging." Minor gap for tracing the chooser flow end-to-end.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/flows/NewWorkflowModal.tsx` around lines 71 - 92, Add missing verbose debug logging for the chooser flow transitions in NewWorkflowModal so the flow can be traced end-to-end. Mirror the existing logging style used by startFromScratch and startFromTemplate by adding stable-prefix debug entries in openGallery, backToChooser, and the onDescribe hand-off path. Include correlation-friendly fields such as the selected template id or target view where applicable, and keep the messages consistent with the existing log() usage in NewWorkflowModal.Source: Path instructions
app/src/components/flows/WorkflowCopilotPanel.tsx (1)
70-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate submit/keyboard-handling logic vs
WorkflowPromptBar.tsx.The
submit/onKeyDown(Enter-to-submit, Shift+Enter-for-newline, disabled-while-sending) logic here is nearly identical toWorkflowPromptBar.tsx(lines 32-48). Consider extracting a small shared hook (e.g.usePromptSubmit(onSubmit, disabled)) to avoid drift between the two authoring surfaces.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/flows/WorkflowCopilotPanel.tsx` around lines 70 - 96, The submit and Enter-key handling in WorkflowCopilotPanel duplicates the same logic used in WorkflowPromptBar, so refactor this shared behavior into a reusable hook or helper such as usePromptSubmit. Move the trimmed submit, sending guard, and Enter/Shift+Enter keyboard handling into the shared abstraction, then have WorkflowCopilotPanel consume it instead of keeping its own copy, so both authoring surfaces stay aligned.app/src/components/flows/canvas/EditableFlowCanvas.tsx (1)
159-225: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winFull graph re-serialization (JSON.stringify) runs on every drag frame.
currentGraph/currentKey(Line 159-163) recompute on everynodes/edgeschange, including the continuous position updates xyflow emits while a user drags a node. Each recompute re-serializes the entire graph, and drives theonGraphChangeeffect (Line 168-171) to also fire on every drag tick — which, per the Phase 5c comment, can be forwarded to the copilot host on every pixel of movement. For larger graphs this could produce a noticeable performance hit and needless downstream work during interactive dragging.Consider gating the expensive recompute/notification on drag-end (e.g., skip while any node has xyflow's
dragging: true, or debounce viauseDeferredValue) rather than on every intermediate position tick.♻️ Sketch: skip recompute while a node is actively being dragged
+ const isDragging = nodes.some(n => n.dragging); const currentGraph = useMemo( - () => xyflowToWorkflowGraph(nodes, edges, meta), - [nodes, edges, meta] + () => xyflowToWorkflowGraph(nodes, edges, meta), + // eslint-disable-next-line react-hooks/exhaustive-deps + [isDragging ? null : nodes, edges, meta] );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/flows/canvas/EditableFlowCanvas.tsx` around lines 159 - 225, The graph serialization in EditableFlowCanvas is being recomputed and broadcast on every drag tick, which causes unnecessary JSON.stringify work and repeated onGraphChange calls. Update the currentGraph/currentKey flow to skip or defer recomputation while xyflow nodes are actively dragging (for example by checking each node’s dragging state or debouncing the update), and keep the live notification tied to the settled graph state in the existing useEffect/onGraphChange path.app/src/components/flows/canvas/FlowCanvas.tsx (1)
104-119: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winHoist the default
metafallback to a stable reference.
meta ?? { schema_version: 1, name: '' }allocates a new object every render whenmetais omitted, defeatingEditableFlowCanvas'suseMemodependency onmeta(Lines 159-175 inEditableFlowCanvas.tsx) and forcing repeat graph serialization on every parent re-render even with no actual edits.♻️ Proposed fix
+const DEFAULT_META: WorkflowGraphMeta = { schema_version: 1, name: '' }; + function FlowCanvas({ nodes, edges, editable = false, meta, ... }: FlowCanvasProps) { if (editable) { return ( <EditableFlowCanvas nodes={nodes} edges={edges} - meta={meta ?? { schema_version: 1, name: '' }} + meta={meta ?? DEFAULT_META}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/src/components/flows/canvas/FlowCanvas.tsx` around lines 104 - 119, The `meta ?? { schema_version: 1, name: '' }` fallback in `FlowCanvas` creates a new object on every render, causing `EditableFlowCanvas` to see a changing `meta` prop and rerun its `useMemo` work unnecessarily. Hoist that default into a stable reference in `FlowCanvas` (or a shared constant) and pass the same object instance whenever `meta` is absent, so `EditableFlowCanvas` can rely on referential equality and avoid repeated graph serialization.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/src/components/chat/WorkflowProposalCard.test.tsx`:
- Around line 11-22: The Vitest mock factories in WorkflowProposalCard.test.tsx
close over mockUpdateFlow, mockDispatch, and mockNavigate before those handles
are initialized because vi.mock is hoisted. Move all mock handles referenced by
the vi.mock factories into a vi.hoisted(...) block, then keep the existing
createFlow, updateFlow, useAppDispatch, and useNavigate mocks wired to those
hoisted handles.
In `@app/src/components/chat/WorkflowProposalCard.tsx`:
- Line 56: The `openInCanvas` debug log in `WorkflowProposalCard` is leaking
user-provided workflow text via `proposal.name`. Keep the stable prefix and
`threadId`, but replace the name with non-sensitive shape data (for example, a
boolean, length, or count-based summary) in the `log(...)` call. Update the
logging in this flow to follow the “no secrets or full PII” guideline while
preserving correlation and debugging usefulness.
In `@app/src/components/flows/canvas/useFlowValidation.ts`:
- Around line 66-85: The validation flow in useFlowValidation’s run callback can
apply an out-of-order result, so add a monotonic request token/sequence guard
around validateFlow(graphRef.current) and only call setValidation for the latest
in-flight request. Update the run/validateNow path to compare the active token
before logging or setting state, and keep the verbose debug log in place with a
stable prefix plus the new correlation field for each validation attempt.
In `@app/src/hooks/useFlowRunProgress.ts`:
- Around line 102-115: The effect in useFlowRunProgress is resetting statuses
synchronously with setStatuses({}) at the top, which violates the repo’s
react-hooks/set-state-in-effect pattern. Move the “clear previous run state when
runId changes” logic out of the useEffect in useFlowRunProgress and into
render-phase reset handling, using a ref to detect when runId changes and
trigger the reset during render instead. Keep the subscription/unsubscription
logic in the effect tied to runId and handleProgress, but remove the
unconditional state update from the effect body.
In `@app/src/hooks/useWorkflowBuilderChat.ts`:
- Around line 105-143: The send failure cleanup in useWorkflowBuilderChat leaves
a newly created thread active because the catch block only clears state when the
original threadId exists. Update the send flow so the actual targetThreadId
created in the success path is available to the catch block, and use it to clear
runtime/inference state after failures from createNewThread, addMessageLocal, or
chatSend. Make the cleanup in send consistent with the existing
beginInferenceTurn, markThreadInferenceActive, clearRuntimeForThread, and
clearThreadInferenceActive calls so a freshly created builder thread is always
reset on error.
In `@src/openhuman/flows/mod.rs`:
- Line 10: The flow workflow-builder tools should not be exposed as a separate
domain module; move the `builder_tools` implementation under the flow domain’s
`tools.rs` (or a submodule it re-exports) and remove the standalone `pub mod
builder_tools` from `mod.rs`. Update the flow tool wiring so any builder-related
types/functions remain accessible through the existing `tools` module and are
re-exported via `src/openhuman/tools/mod.rs` in line with the domain-owned tool
pattern.
In `@src/openhuman/flows/n8n_import.rs`:
- Around line 403-414: The synthetic trigger node in the n8n import flow uses a
fixed id of "trigger", which can collide with an existing imported node id and
break validation. Update the trigger insertion logic in the n8n_import flow to
generate a unique, collision-free id before calling nodes.insert, reusing the
existing import/reconciliation context in this area so the synthetic Trigger
node remains distinct from all imported nodes.
- Around line 337-375: The json_path_to_jq helper is dropping quotes when
converting bracketed JSON keys, which breaks jq for keys with spaces or
punctuation. Update json_path_to_jq to detect non-bare identifiers and emit
quoted field access instead of plain dot notation, while keeping bare
identifiers unchanged; adjust the bracket-key handling in json_path_to_jq so
cases like ["first name"] stay valid for downstream jq parsing.
---
Outside diff comments:
In `@src/openhuman/skills/schemas/handlers.rs`:
- Around line 32-66: The tracing labels in the renamed skills RPC handlers still
use the old “[workflows][rpc]” prefix, making logs inconsistent with the rest of
the file. Update the debug calls in handle_skills_list, handle_skills_cancel,
and handle_skills_update to use the “[skills][rpc]” prefix everywhere those
messages are emitted, matching the other handlers in this module. Keep the
existing log structure and fields unchanged aside from the prefix.
---
Nitpick comments:
In `@app/src/components/flows/canvas/EditableFlowCanvas.tsx`:
- Around line 159-225: The graph serialization in EditableFlowCanvas is being
recomputed and broadcast on every drag tick, which causes unnecessary
JSON.stringify work and repeated onGraphChange calls. Update the
currentGraph/currentKey flow to skip or defer recomputation while xyflow nodes
are actively dragging (for example by checking each node’s dragging state or
debouncing the update), and keep the live notification tied to the settled graph
state in the existing useEffect/onGraphChange path.
In `@app/src/components/flows/canvas/FlowCanvas.tsx`:
- Around line 104-119: The `meta ?? { schema_version: 1, name: '' }` fallback in
`FlowCanvas` creates a new object on every render, causing `EditableFlowCanvas`
to see a changing `meta` prop and rerun its `useMemo` work unnecessarily. Hoist
that default into a stable reference in `FlowCanvas` (or a shared constant) and
pass the same object instance whenever `meta` is absent, so `EditableFlowCanvas`
can rely on referential equality and avoid repeated graph serialization.
In `@app/src/components/flows/NewWorkflowModal.tsx`:
- Around line 71-92: Add missing verbose debug logging for the chooser flow
transitions in NewWorkflowModal so the flow can be traced end-to-end. Mirror the
existing logging style used by startFromScratch and startFromTemplate by adding
stable-prefix debug entries in openGallery, backToChooser, and the onDescribe
hand-off path. Include correlation-friendly fields such as the selected template
id or target view where applicable, and keep the messages consistent with the
existing log() usage in NewWorkflowModal.
In `@app/src/components/flows/RunItemDataBrowser.tsx`:
- Around line 263-265: The row index cell in RunItemDataBrowser is using scope
on a td, which is invalid and does not provide row-header semantics. Update the
table markup in the cell that renders index + 1 so that scope is removed from
the td, and if row-header behavior is intended, move the semantics to a th
element in that same row rendering instead.
In `@app/src/components/flows/WorkflowCopilotPanel.tsx`:
- Around line 70-96: The submit and Enter-key handling in WorkflowCopilotPanel
duplicates the same logic used in WorkflowPromptBar, so refactor this shared
behavior into a reusable hook or helper such as usePromptSubmit. Move the
trimmed submit, sending guard, and Enter/Shift+Enter keyboard handling into the
shared abstraction, then have WorkflowCopilotPanel consume it instead of keeping
its own copy, so both authoring surfaces stay aligned.
In `@app/src/lib/i18n/zh-CN.ts`:
- Around line 3673-3674: The new flows.template strings use 代理 for “agent”, but
the rest of the flows namespace in this PR uses 智能体, so align these template
descriptions for consistency. Update the affected translation entries in
zh-CN.ts for the daily-digest, webhook relay, and ask-agent templates to use 智能体
wording, following the existing terminology used by flows.nodeKind.agent and
flows.nodeConfig.agent.promptLabel.
In `@src/openhuman/skills/schemas/wire_types.rs`:
- Around line 251-253: The result struct still uses the old Workflows prefix
even though the schema and field type were renamed, so update the name in the
wire_types module for consistency. Rename WorkflowsRecentRunsResult to match the
new skills_recent_runs surface (for example, SkillsRecentRunsResult) and check
any references to this struct so the renamed symbol is used consistently unless
the old name is required for serialization compatibility.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9179b5d7-c05c-4b0f-848d-e19a9c28bb2c
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockapp/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (191)
Cargo.tomlapp/src/AppRoutes.auth.test.tsxapp/src/AppRoutes.tsxapp/src/components/chat/WorkflowProposalCard.test.tsxapp/src/components/chat/WorkflowProposalCard.tsxapp/src/components/flows/FlowListRow.test.tsxapp/src/components/flows/FlowListRow.tsxapp/src/components/flows/FlowRunInspectorDrawer.tsxapp/src/components/flows/FlowRunsDrawer.tsxapp/src/components/flows/FlowTemplateGallery.tsxapp/src/components/flows/NewWorkflowModal.test.tsxapp/src/components/flows/NewWorkflowModal.tsxapp/src/components/flows/RunItemDataBrowser.tsxapp/src/components/flows/WorkflowCopilotPanel.test.tsxapp/src/components/flows/WorkflowCopilotPanel.tsxapp/src/components/flows/WorkflowPromptBar.test.tsxapp/src/components/flows/WorkflowPromptBar.tsxapp/src/components/flows/__tests__/FlowRunInspectorDrawer.test.tsxapp/src/components/flows/__tests__/RunItemDataBrowser.test.tsxapp/src/components/flows/canvas/EditableFlowCanvas.tsxapp/src/components/flows/canvas/FlowCanvas.tsxapp/src/components/flows/canvas/FlowNodeComponent.tsxapp/src/components/flows/canvas/FlowValidationBanner.tsxapp/src/components/flows/canvas/NodePalette.tsxapp/src/components/flows/canvas/__tests__/EditableFlowCanvas.runOverlay.test.tsxapp/src/components/flows/canvas/__tests__/EditableFlowCanvas.test.tsxapp/src/components/flows/canvas/__tests__/EditableFlowCanvas.validation.test.tsxapp/src/components/flows/canvas/flowCanvasStyles.cssapp/src/components/flows/canvas/nodeConfig/NodeConfigDrawer.tsxapp/src/components/flows/canvas/nodeConfig/__tests__/NodeConfigDrawer.test.tsxapp/src/components/flows/canvas/nodeConfig/__tests__/nodeConfigForms.test.tsxapp/src/components/flows/canvas/nodeConfig/nodeConfigFields.tsxapp/src/components/flows/canvas/nodeConfig/nodeConfigForms.tsxapp/src/components/flows/canvas/useFlowValidation.tsapp/src/components/flows/useCreateFlow.tsapp/src/components/intelligence/WorkflowsTab.tsxapp/src/components/intelligence/__tests__/WorkflowsTab.test.tsxapp/src/components/skills/CreateSkillModal.tsxapp/src/components/skills/CreateWorkflowForm.tsxapp/src/components/skills/InstallSkillDialog.tsxapp/src/components/skills/SkillsExplorerTab.tsxapp/src/components/skills/UninstallSkillConfirmDialog.tsxapp/src/components/skills/WorkflowRunnerBody.tsxapp/src/components/skills/__tests__/CreateSkillModal.test.tsxapp/src/components/skills/__tests__/CreateWorkflowForm.test.tsxapp/src/components/skills/__tests__/InstallSkillDialog.test.tsxapp/src/components/skills/__tests__/SkillsExplorerTab.test.tsxapp/src/components/skills/__tests__/UninstallSkillConfirmDialog.test.tsxapp/src/components/skills/__tests__/WorkflowRunnerBody.test.tsxapp/src/components/skills/preflightGate.tsapp/src/hooks/useFlowRunProgress.test.tsapp/src/hooks/useFlowRunProgress.tsapp/src/hooks/useWorkflowBuilderChat.test.tsapp/src/hooks/useWorkflowBuilderChat.tsapp/src/lib/flows/canvasDraft.tsapp/src/lib/flows/exportFlow.test.tsapp/src/lib/flows/exportFlow.tsapp/src/lib/flows/flowValidation.test.tsapp/src/lib/flows/flowValidation.tsapp/src/lib/flows/graphAdapter.test.tsapp/src/lib/flows/graphAdapter.tsapp/src/lib/flows/graphDiff.test.tsapp/src/lib/flows/graphDiff.tsapp/src/lib/flows/newFlow.test.tsapp/src/lib/flows/newFlow.tsapp/src/lib/flows/nodeKindMeta.tsapp/src/lib/flows/runItems.test.tsapp/src/lib/flows/runItems.tsapp/src/lib/flows/templates/app-event-route.jsonapp/src/lib/flows/templates/ask-agent.jsonapp/src/lib/flows/templates/daily-digest.jsonapp/src/lib/flows/templates/http-fetch-parse.jsonapp/src/lib/flows/templates/index.tsapp/src/lib/flows/templates/scheduled-scrape.jsonapp/src/lib/flows/templates/templates.test.tsapp/src/lib/flows/templates/webhook-triage.jsonapp/src/lib/flows/workflowBuilderPrompt.test.tsapp/src/lib/flows/workflowBuilderPrompt.tsapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/FlowCanvasPage.tsxapp/src/pages/FlowsPage.test.tsxapp/src/pages/FlowsPage.tsxapp/src/pages/WorkflowNew.test.tsxapp/src/pages/WorkflowNew.tsxapp/src/pages/__tests__/FlowCanvasPage.test.tsxapp/src/pages/__tests__/Skills.channels-grid.test.tsxapp/src/pages/__tests__/Skills.mcp-coming-soon.test.tsxapp/src/pages/__tests__/Skills.meetings-tab.test.tsxapp/src/providers/ChatRuntimeProvider.tsxapp/src/services/api/__tests__/flowsApi.test.tsapp/src/services/api/__tests__/skillsApi.test.tsapp/src/services/api/flowsApi.tsapp/src/services/api/skillsApi.test.tsapp/src/services/api/skillsApi.tsapp/src/test/test-utils.tsxapp/test/e2e/specs/flows.spec.tssrc/core/all.rssrc/core/jsonrpc.rssrc/openhuman/agent/harness/builtin_definitions.rssrc/openhuman/agent/harness/fork_context.rssrc/openhuman/agent/harness/session/builder/factory.rssrc/openhuman/agent/harness/session/builder/setters.rssrc/openhuman/agent/harness/session/runtime.rssrc/openhuman/agent/harness/session/runtime_tests.rssrc/openhuman/agent/harness/session/tests.rssrc/openhuman/agent/harness/session/turn/tools.rssrc/openhuman/agent/harness/session/turn_tests.rssrc/openhuman/agent/harness/session/types.rssrc/openhuman/agent/prompts/render_helpers.rssrc/openhuman/agent/prompts/types.rssrc/openhuman/agent/task_dispatcher/executor.rssrc/openhuman/agent/tools/run_workflow.rssrc/openhuman/agent_registry/agents/loader.rssrc/openhuman/agent_registry/agents/mod.rssrc/openhuman/agent_registry/agents/orchestrator/agent.tomlsrc/openhuman/agent_registry/agents/orchestrator/prompt.rssrc/openhuman/agent_registry/agents/workflow_builder/agent.tomlsrc/openhuman/agent_registry/agents/workflow_builder/mod.rssrc/openhuman/agent_registry/agents/workflow_builder/prompt.mdsrc/openhuman/agent_registry/agents/workflow_builder/prompt.rssrc/openhuman/channels/runtime/startup.rssrc/openhuman/channels/tests/prompt.rssrc/openhuman/context/channels_prompt.rssrc/openhuman/flows/builder_tools.rssrc/openhuman/flows/builder_tools_tests.rssrc/openhuman/flows/mod.rssrc/openhuman/flows/n8n_import.rssrc/openhuman/flows/ops.rssrc/openhuman/flows/ops_tests.rssrc/openhuman/flows/schemas.rssrc/openhuman/flows/store.rssrc/openhuman/flows/store_tests.rssrc/openhuman/flows/tools.rssrc/openhuman/flows/types.rssrc/openhuman/mcp_client/client.rssrc/openhuman/mod.rssrc/openhuman/runtime_node/ops.rssrc/openhuman/runtime_node/types.rssrc/openhuman/skill_registry/ops.rssrc/openhuman/skill_registry/schemas/handlers.rssrc/openhuman/skill_registry/schemas/wire_types.rssrc/openhuman/skill_registry/tools.rssrc/openhuman/skill_runtime/run_machinery.rssrc/openhuman/skill_runtime/schemas.rssrc/openhuman/skills/README.mdsrc/openhuman/skills/bus.rssrc/openhuman/skills/e2e_plumbing_tests.rssrc/openhuman/skills/e2e_run_tests.rssrc/openhuman/skills/mod.rssrc/openhuman/skills/ops.rssrc/openhuman/skills/ops_create.rssrc/openhuman/skills/ops_discover.rssrc/openhuman/skills/ops_install.rssrc/openhuman/skills/ops_parse.rssrc/openhuman/skills/ops_tests.rssrc/openhuman/skills/ops_types.rssrc/openhuman/skills/preflight.rssrc/openhuman/skills/registry.rssrc/openhuman/skills/run_log.rssrc/openhuman/skills/schemas/controller_schemas.rssrc/openhuman/skills/schemas/handlers.rssrc/openhuman/skills/schemas/helpers.rssrc/openhuman/skills/schemas/mod.rssrc/openhuman/skills/schemas/wire_types.rssrc/openhuman/skills/schemas_tests.rssrc/openhuman/skills/tools.rssrc/openhuman/skills/types.rssrc/openhuman/tinyflows/caps.rssrc/openhuman/tools/impl/presentation/tests.rssrc/openhuman/tools/mod.rssrc/openhuman/tools/ops.rssrc/openhuman/tools/ops_tests.rssrc/openhuman/tools/traits.rssrc/openhuman/workspace/ops.rstests/agent_round26_raw_coverage_e2e.rstests/json_rpc_e2e.rstests/x402_twit_sh_live.rsvendor/tinyflows
💤 Files with no reviewable changes (3)
- app/src/pages/WorkflowNew.tsx
- app/src/pages/WorkflowNew.test.tsx
- app/src/AppRoutes.auth.test.tsx
| //! [`crate::openhuman::tinyflows::caps::FlowStateStore`]); the RPC/CLI | ||
| //! controller surface in `schemas` (private, re-exported below). | ||
|
|
||
| pub mod builder_tools; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Keep workflow-builder tools under the domain tool module.
Adding a separate builder_tools module creates a second domain-owned tool implementation surface for flows. Move these tools behind src/openhuman/flows/tools.rs or an allowed submodule re-exported from there. As per coding guidelines, "Domain-owned agent tools must be implemented in that domain's tools.rs and re-exported via src/openhuman/tools/mod.rs; only cross-cutting families belong in tools/impl/."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/openhuman/flows/mod.rs` at line 10, The flow workflow-builder tools
should not be exposed as a separate domain module; move the `builder_tools`
implementation under the flow domain’s `tools.rs` (or a submodule it re-exports)
and remove the standalone `pub mod builder_tools` from `mod.rs`. Update the flow
tool wiring so any builder-related types/functions remain accessible through the
existing `tools` module and are re-exported via `src/openhuman/tools/mod.rs` in
line with the domain-owned tool pattern.
Source: Coding guidelines
There was a problem hiding this comment.
💡 Codex Review
For schema consumers of /schema, this says openhuman.skills_list returns a skills field, but handle_skills_list serializes WorkflowsListResult { workflows: ... } and the frontend/tests read result.workflows. Generated clients following the new schema will see undefined; the schema should match the wire payload, or the handler and frontend need to be renamed together.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (threadId) { | ||
| dispatch(clearRuntimeForThread({ threadId })); | ||
| dispatch(clearThreadInferenceActive(threadId)); |
There was a problem hiding this comment.
Clear the newly created builder thread on send failure
When the first prompt/canvas copilot send creates a new thread and chatSend rejects (for example, the socket drops after the initial connected check or the RPC fails), threadId in this closure is still the previous state (null), so this branch skips clearing the active runtime markers that were set for the local targetThreadId. The hook then stores the new thread id and runtimeActive keeps sending true, leaving the prompt bar/copilot disabled until it is remounted; clear using the local thread id that was actually marked active.
Useful? React with 👍 / 👎.
| type_version: 1, | ||
| name: n8n_name, | ||
| config, | ||
| ports: Vec::new(), |
There was a problem hiding this comment.
Seed imported condition nodes with branch ports
The fresh evidence is this importer still writes ports: Vec::new() for imported IF nodes: when an n8n IF/condition currently has only one branch wired, the canvas derives output handles from declared ports plus existing edges, so it exposes only the already-wired true or false handle and the user cannot add the missing branch from the UI. Condition nodes imported here should declare both true and false ports just like palette-created conditions.
Useful? React with 👍 / 👎.
…mocks WorkflowProposalCard's openInCanvas log leaked proposal.name (user-provided text) — replace with node/edge counts, which is enough to correlate without carrying PII. Also hoist WorkflowProposalCard.test.tsx's mock handles into vi.hoisted(): they were referenced by vi.mock factories (which Vitest hoists above the module body) from plain top-level consts, which only worked because Vitest's compiler special-cases `mock`-prefixed identifiers — not a guarantee (addresses @coderabbitai on WorkflowProposalCard.tsx:56 and .test.tsx:22). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
run() had no protection against a stale validation response overwriting a fresher one — a debounced auto-validate racing a manual validateNow() (or two rapid validateNow() calls) let the last response to RESOLVE win regardless of issue order, which could wrongly unblock or re-block Save after further edits. Adds a monotonic request-id ref: a response only commits (and clears `validating`) if it's still the latest in-flight request; the id also becomes the log's correlation field (addresses @coderabbitai on useFlowValidation.ts:85). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
…fect
setStatuses({}) ran unconditionally at the top of the subscribe effect body
(before the `if (!runId) return`), which react-hooks/set-state-in-effect
(enforced in this codebase) disallows — synchronous setState inside an effect
causes a cascading extra render. Moved the "new runId → drop prior run's node
states" reset to render phase via the standard ref-comparison pattern
(addresses @coderabbitai on useFlowRunProgress.ts:115).
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
The catch block guarded cleanup on the outer `threadId` state, but for a send() call that itself creates a new thread, `threadId` state doesn't update synchronously within that same closure invocation — so a failure after creation (e.g. addMessageLocal/chatSend rejecting) saw the stale `null`, skipped clearRuntimeForThread/clearThreadInferenceActive, and left the new thread's active markers dangling. Hoisted `targetThreadId` above the try so the catch block sees the actual thread this call operated on (addresses @coderabbitai on useWorkflowBuilderChat.ts:143). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
… on import Two n8n-import bugs: (1) json_path_to_jq emitted unquoted dot access for bracket keys with spaces/punctuation (e.g. `["first name"]` -> `.first name`), which is invalid jq — now emits jq's dot-plus-quoted-string form for any key that isn't a bare identifier. (2) the synthesized manual trigger always used the literal id "trigger", which collides and fails validation if the source n8n graph already has a differently-kinded node with that id — now probes for a free `trigger`/`trigger_2`/... id (addresses @coderabbitai on n8n_import.rs:375 and :414). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
handle_skills_list/cancel/update still emitted "[workflows][rpc] ..." tracing labels while every other renamed handler in this file uses "[skills][rpc] ...", making log-based debugging inconsistent for the renamed RPC surface (addresses @coderabbitai outside-diff comment on handlers.rs:32-66). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
… consistency - RunItemDataBrowser: scope="row" is invalid on <td> (silently ignored by assistive tech) — the index cell is now a <th scope="row"> so it actually conveys row-header semantics. - FlowCanvas: hoist the default `meta` fallback to a module-level constant so omitting `meta` doesn't allocate a new object every render, which was defeating EditableFlowCanvas's `useMemo(..., [meta])` and forcing a full graph re-serialization on every parent re-render even with no real edit. - zh-CN: align three new flows.templates.* strings on 智能体 for "agent", matching flows.nodeKind.agent / flows.nodeConfig.agent.* elsewhere in this same PR's namespace instead of the inconsistent 代理 (addresses @coderabbitai on RunItemDataBrowser.tsx:263, FlowCanvas.tsx:104, and zh-CN.ts:3673-3684). Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
|
Responding to the remaining CodeRabbit findings on this review round: Fixed (pushed at
Deferring (flagging rather than silently dropping):
Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU |
There was a problem hiding this comment.
💡 Codex Review
openhuman/src/openhuman/skills/schemas/controller_schemas.rs
Lines 95 to 97 in 5546f1a
The handler still serializes WorkflowsListResult { workflows: ... } and the frontend intentionally reads result.workflows, but the new controller schema advertises a skills output field. Schema-driven clients and /schema consumers calling openhuman.skills_list will look for the wrong field; keep the schema as workflows or change the wire response and clients together.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| controllers.extend(crate::openhuman::javascript::all_javascript_registered_controllers()); | ||
| // Discovered SKILL.md skills and their bundled resources | ||
| controllers.extend(crate::openhuman::workflows::all_workflows_registered_controllers()); | ||
| controllers.extend(crate::openhuman::skills::all_skills_registered_controllers()); |
There was a problem hiding this comment.
Update stale workflows_list probes after rename
With this registry swap, openhuman.workflows_list is no longer registered; I checked the CI Full WDIO skills shard in app/scripts/e2e-run-all-flows.sh, and it still runs app/test/e2e/specs/skill-lifecycle.spec.ts, which calls callOpenhumanRpc('openhuman.workflows_list', {}). That shard will now fail with method-not-found unless the spec is updated to openhuman.skills_list or a compatibility alias is kept for the old method.
Useful? React with 👍 / 👎.
| const initialDirty = useMemo( | ||
| () => JSON.stringify(editorGraph) !== JSON.stringify(persistedGraphRef.current), | ||
| [editorGraph] |
There was a problem hiding this comment.
Mark draft canvases dirty on first render
When the canvas is opened through /flows/draft from an imported file or chat proposal, there is no persisted graph yet, but persistedGraphRef is initialized to that draft graph and this comparison returns false. EditableFlowCanvas disables Save while dirty is false, so users cannot persist an imported/proposed workflow as-is and must make a meaningless edit first; treat flowId === null as initially dirty.
Useful? React with 👍 / 👎.
| const result = await runFlow(flowId); | ||
| log('run: started flow id=%s thread_id=%s', flowId, result.thread_id); | ||
| setActiveRunId(result.thread_id); |
There was a problem hiding this comment.
Subscribe to run progress before awaiting completion
For canvas runs, runFlow calls openhuman.flows_run, and the backend only returns the thread_id after the engine has settled; because activeRunId is set after this await, the useFlowRunProgress subscription mounts after the observer has already emitted the run's progress events. In any nontrivial run, the new live overlay will miss the events it was added to display; the UI needs a run id before awaiting completion, or the run RPC needs to start asynchronously.
Useful? React with 👍 / 👎.
…ls_* The "catalog canonical methods exist in core schema registry" test read src/openhuman/channels/controllers/schemas.rs for the channels_* namespace/ function literals, but tinyhumansai#4557 ("Use tinychannels provider implementations") moved the actual ChannelControllerSchema definitions (namespace: "channels", ...) into the vendored vendor/tinychannels/src/controllers/schemas.rs crate — the in-tree file now only adapts from it. This left the drift guard failing on main independent of this PR (verified: same failure reproduces against main's own copy of both files); fixed by also reading the vendored schema source. Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU
|
FYI: the "Frontend Checks" failure on the previous run ( Fixed at Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU |
There was a problem hiding this comment.
💡 Codex Review
openhuman/src/openhuman/flows/schemas.rs
Line 481 in 9b623ae
This new run lifecycle introduces the terminal cancelled status, but the frontend wire type/status maps and useFlowRunPoller still only know running, completed, pending_approval, and failed. When a run is cancelled or expires via the parked-run TTL, the run drawers index their status label/class maps with an unknown value and the poller never treats it as terminal, so the inspector can keep polling and render undefined status UI; add cancelled to the frontend status union, mappings, and terminal set.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| setBusyKey(key); | ||
| setError(null); | ||
| try { | ||
| const flow = await createFlow(name, graph); |
There was a problem hiding this comment.
Create templates as drafts or disabled flows
When the user picks a bundled template, this path immediately calls flows_create, whose backend default is enabled = true and which binds automatic triggers right away. For the scheduled templates (daily-digest, scheduled-scrape), that means an incomplete starter graph with placeholder tool slugs/URLs can begin firing before the user has reviewed or configured it; create these as drafts/disabled copies, or require an explicit enable after editing.
Useful? React with 👍 / 👎.
|
Re Codex's finding on the Claude-Session: https://claude.ai/code/session_01QpsTTxxjFgDhzuozS28tyU |
Summary
Phases 3–8 of
docs/plans/tinyflows-integration/README.md, on one branch. Turns the tinyflows viewer into a full n8n-class visual builder, adds a prompt-first authoring agent, extends the engine, and renames the misnamed legacy Skills backend.editableprop (drag/connect with port-aware validation, delete, 12-kind node palette), a per-kind node config drawer (+ raw-JSON escape hatch + connection picker), inline validation UX via the existingflows_validate, explicit-Save draft/dirty state with an unsaved-changes guard, and a live run overlay driven by theflow:run_progresssocket event (closes Phase 1's deferred frontend-socket follow-up).WorkflowGraphtemplates + gallery, workflow export + host-validated import with a best-effort n8n importer (flows_importRPC), and "Open in canvas" on the chat proposal card.workflow-builderagent definition + specialized prompt, a 7-tool belt (revise_workflow, read-onlylist_flows/get_flow/get_flow_run,list_flow_connections,search_tool_catalog, sandboxeddry_run_workflow), a FlowsPage prompt bar, a canvas copilot panel with an accept/reject draft diff overlay, and "Fix with agent" from failed runs. The agent never persists — proposals only.flows_duplicate(disabled/unbound copy), run-history retention/pruning (never prunes parked/running), a run-inspector per-item data browser (table⟷JSON +paired_item), and a WDIO create→run→inspect E2E spec.vendor/tinyflows0.3.2→0.5.0): sub-workflow byworkflow_idvia a host-injectedWorkflowResolver(plan item 4b),output_parserschema validation + one-shot LLM auto-fix, a cooperativeCancellationTokeninengine::run, and agent-node tool + output_parser sub-ports (partial). Docs truth-up.workflows::domain" turned out to be the live Skills backend (its README literally starts with "# Skills"), not dead code. Instead of deleting it (which would break Skills), this renames it:workflows::module →skills::, the 11workflows_*RPCs →skills_*, and the frontendworkflowsApi→skillsApi. Storage schema unchanged (no data migration/loss). Frees the "Workflows" name for tinyflows.Problem
After Phases 0–2, tinyflows had a live backend but a read-only UI, no authoring entry points, no prompt-first builder, an engine missing sub-workflow-by-reference / output-parser validation / cancellation, and a confusingly-named legacy domain sharing the "workflows" branding.
Solution
flows_validate,flows_list_connections,flow:run_progress) rather than adding backend surface; the editable canvas reuses the already-writtenxyflowToWorkflowGraphas the save path.build_workflowdelegation edge (the runtime has no UI affordance to target a named agent);ChatRuntimeProvidernow parses proposals from subagent tool results too.sub_workflowstill inlines a child graph and now also resolves by id via a hostWorkflowResolver.Submission Checklist
flows_import, WDIO create→run→inspect spec. Failure paths covered (invalid graph blocks Save; unknown cred fails closed; import placeholder nodes; cancellation).diff-covergate runs in CI's coverage jobs.flows_import/flows_duplicate/skills_*rows can be added if reviewers want them.## Related.docs/plans/tinyflows-integration/README.md(Phases 3–8).Impact
vendor/tinyflowsengine submodule (0.3.2→0.5.0). Requires the submodule branchfeat/sub-workflow-by-idontinyhumansai/tinyflows(pushed).openhuman.workflows_*→openhuman.skills_*. Backend + frontend renamed together in this PR. Storage schema unchanged — no user data migration, no data loss. Theskills_listresult still returns its array under theworkflowsJSON key (unchanged wire field).about_appcategory serde strings still emit"workflows"(withalias="skills") — deliberately deferred.external_effect=false(proposals only, never persists/enables);dry_run_workflowruns against tinyflows mock caps (no real side effects) and is autonomy-tier gated + labeled sandbox.sub_workflowgains an optionalworkflow_id.Related
flows::/openhuman.flows_*(tinyflows),skills::/openhuman.skills_*(renamed),vendor/tinyflowsenginechat_model/memorysub-ports + multi-turn tool loop (Phase 7.1 remainder).skills_{run,cancel,read_run_log,recent_runs,uninstall}coexist with the FE-usedskill_runtime_*/skill_registry_uninstall— decide which survives.about_appcategory serde flip"workflows"→"skills"(coordinated FE/BE).vendor/tinyflowsengine work as a release (0.5.0) and re-vendor to a tag.useBlocker).AI Authored PR Metadata
Linear Issue
Commit & Branch
feat/tinyflows-phase-3plusValidation Run
pnpm --filter openhuman-app format:check— prettier applied to touched files per phasepnpm typecheck— pass (0 errors, full-batch final run)cargo test --lib openhuman::flows::(142); enginecargo testin vendor/tinyflows (260); frontend Vitest (1362 final run)GGML_NATIVE=OFF cargo check --manifest-path Cargo.tomlandapp/src-tauri/Cargo.toml→ both 0 errorspnpm i18n:checkparity green across 14 localesValidation Blocked
command:full WDIO E2E executionerror:requires built app + driver (runs in CI Full)impact:the newflows.spec.tsis authored + typechecked; validated in CI's desktop E2E matrixBehavior Changes
skills_*.Parity Contract
editabledefaults false); engine default paths unchanged (new caps/token are additive); Skills functionality identical post-rename (storage untouched).all.rs), notdispatch.rs; 2s run poller retained as socket fallback.Duplicate / Superseded PR Handling
Summary by CodeRabbit
New Features
Bug Fixes