refactor(workflows): merge skills into the unified workflows primitive#3324
Conversation
…-issue-crusher, pr-review-shepherd) First phase of the workflows-unify refactor. Drops the 3 bundled default skills and the dev-workflow default-seeding test. seed_default_skills is now a no-op (DEFAULT_SKILLS empty) until we ship example workflows under the unified primitive. Remaining crusher/shepherd references are prose comments (cleaned in later phases) or tmp-dir tests that don't depend on the bundle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes the agent_workflows domain (WORKFLOW.md / phase playbooks) and the phase tooling: workflow_load / workflow_phase / the AgentWorkflow* tools, the phase-keyed lifecycle (on_pick_up_task / on_close_task / on_enter_directory), per-phase scripts/tool-scope/working-dir context, the todos auto-fire stub, the PromptContext.workflows field + session plumbing, the RPC controllers, and the about_app capability entry. Rationale: pickup/close are the dispatcher/orchestrator's job (phases duplicated it); phases only fire on the task-board path so they'd break manual/scheduled runs; and the whole subsystem was unwired (auto-fire stub, no defaults, discovery tool unwired). Guidance is not a primitive — it lives in a workflow's own body / system prompt / memory. Core bin + lib unit tests compile green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aths git mv src/openhuman/skills -> src/openhuman/workflows and rewrite all openhuman::skills paths -> openhuman::workflows. Internal Skill* type names and skills_* RPC methods unchanged in this commit (follow-ups). Runtime workspace data path (<workspace>/skills/) intentionally left as-is — renaming it would break existing user workspaces. Green: cargo check --bin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kill->run_workflow
Controller methods openhuman.skills_{list,run,describe,recent_runs,read_run_log,
read_resource,create,install_from_url,uninstall} -> openhuman.workflows_*;
all_skills_*_controllers/schemas -> all_workflows_*; handle_skills_* ->
handle_workflows_*; the run tool run_skill -> run_workflow (RunWorkflowTool,
RUN_WORKFLOW_TOOL_NAME, file renamed). Runtime workspace data path left as
<workspace>/skills/. Frontend RPC callers updated in Phase 5. Green: cargo check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Phase 2 core)
Renames every skills-domain type identifier (Skill, SkillDefinition, SkillInput,
Skill*Tool, Skill* events, SkillScope/Frontmatter/Registry/Summary/Match, the
Skills*Params/Result RPC wire types, SkillsSection, …) to Workflow*. Three
overloaded idents are deliberately PRESERVED because they belong to the
integration-delegation surface, not the skills domain:
- SkillDelegationTool (the delegate_to_integrations_agent tool)
- SkillsWildcard ({ skills = "*" } integration-toolkit wildcard)
- ToolScope::Skills (integration-toolkit delegation variant)
Lowercase data paths (<workspace>/skills/) and field names left intact.
Test fixtures updated. cargo check --bin + cargo test --lib green; all 169
workflows-domain tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make workflows executable + composable (the unified primitive's core behavior), and align the orchestrator's workflow surface to the new vocabulary. 3a — execution (function-call semantics): - run_log: add `RunOutcome` + `read_terminal_outcome` to parse a run's terminal footer (status + final output) from its log. - schemas: add `await_run_outcome` — runtime polls the log until terminal or a wait budget elapses (the LLM issues one tool call, never busy-waits). - run_workflow tool: spawn a workflow as a subagent, await its result inline up to `wait_seconds` (default 90, max 600), then auto-detach to a `run_id`. `wait_seconds: 0` = fire-and-forget chaining. New `await_workflow(run_id)` re-attaches to a detached run. 3b — discovery: rename the 8 LLM-facing introspection tools to the workflow vocabulary (`list_workflows`, `describe_workflow`, `read_workflow_resource`, `list_workflow_runs`, `read_workflow_run_log`, `create_workflow`, `install_workflow_from_url`, `uninstall_workflow`); `workflow_id` param with `skill_id` accepted as a back-compat alias. Wire the orchestrator allow-list: drop deleted `workflow_load`/`workflow_phase`, swap `run_skill` → `run_workflow` + `await_workflow` + the discovery set. 3c — guardrails (coarse process-global, not per-lineage): spawn backstop, concurrent-await/nesting cap, and a (workflow_id+inputs) re-entrancy lock so a confused LLM can't tip a legitimate A→B→A chain into a tight loop — without blocking legitimate repeated runs. Also: fix stale Phase-2 leftovers — drop deleted `agent_workflow_*` tools from the productivity test lists; pin `ToolCategory::Workflow` wire format to "skill" (it's the integration-tool category that `category_filter` depends on, not a WORKFLOW.md bundle — revisited in Phase 4); update the legacy run_skill→builtin rewrite shim to also match `run_workflow`. Tools suite green (754 passed, single-threaded); workflows domain 171. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integrations_agent drives a single Composio toolkit per spawn. Now that workflows are a first-class primitive the orchestrator discovers and runs (`list_workflows` / `run_workflow` / `await_workflow`, wired in Phase 3), the integrations_agent should carry no workflow surface: - agent.toml: drop the deleted `workflow_load` / `workflow_phase` from its `named` allow-list — tool surface is now composio-only (`composio_list_tools`, `composio_execute`, `file_read`). - prompt.rs: remove the bespoke `## Available Skills` workflow catalogue (and its `render_available_skills` / `xml_escape` helpers). It advertised workflows the agent has no `run_workflow` tool to invoke — a promise it couldn't keep. Tests updated; `ctx_with` drops the now-unused skills arg. Orchestrator-side wiring landed in Phase 3's agent.toml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 renamed the core JSON-RPC controllers skills_*→workflows_*, which broke every frontend caller still invoking `openhuman.skills_*`. Repoint all 9 RPC method strings in skillsApi.ts (list, describe, run, create, install_from_url, uninstall, read_resource, read_run_log, recent_runs) to `openhuman.workflows_*`, and update the unit-test expectations + e2e/ playwright RPC mocks to match. Doc comments in the skills components that referenced the old method names are updated for accuracy. This is the functional FE fix. The cosmetic UI consolidation (Skills→ Workflows page/component renames, Runners→Intelligence, cutting the freshness/centrality/cohesion memory tabs) is still pending. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…emoved pieces Factual corrections tied to the refactor (not taxonomy relabeling — kept deliberately narrow): - `run_skill` → `run_workflow` across all locales that embed the tool name in the skills-runner schedule help text (the tool was renamed in Phase 3). The token is a locale-invariant code identifier. - en `skillsRunner.panelDesc`: drop the "Use Dev Workflow instead" advice (Dev Workflow was removed in Phase 1) and point at the panel's own Schedule section; "skill" → "workflow" in that one sentence. Verified: app tsc --noEmit clean; 146 skills component + skillsApi tests pass. Deliberately NOT done here (founder/translator decisions, flagged in the proposal): the user-facing Skills→Workflows IA rename, route/URL renames, Runners→Intelligence restructure, the orphaned DevWorkflowPanel + its ~60 `settings.devWorkflow.*` keys, and translating new copy into the 12 non-English locales. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… detail, inputs optional+described Per product direction: - Remove the "Runners" sub-tab from the Connections (/skills) page. Its scheduled-jobs overview (SkillsDashboard) is dropped from the tab chrome; the run+catalog surface stays at /skills/run. (SkillsDashboard.tsx is left in place, just unmounted — reversible.) - Run a workflow from its detail: SkillDetailDrawer gains a "Run workflow" CTA that closes the drawer and opens /skills/run?skill=<id> (the existing runner, pre-selected). This is the run entry point now that the Runners tab is gone. SkillsRun's back-target moves /skills?tab=runners → /skills. - CreateSkillForm inputs: additional inputs now default to required:false (optional unless the author ticks Required) and every added input REQUIRES a description (validated; inline error shown; blocks submit). Help text + new i18n key updated (en). Verified: app tsc --noEmit clean; 465 skills + pages tests pass (CreateSkillForm/SkillDetailDrawer/SkillsRun/SkillsRunnerBody included). Note: new i18n values added to en.ts only; the 12 other locales fall back to English until the Phase 6 translation pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d out) The standalone scheduled-jobs dashboard (SkillsDashboard) was the body of the removed Runners tab. It is being phased out, so delete it rather than leave it dormant: - Remove src/pages/SkillsDashboard.tsx + SkillsDashboard.test.tsx. It was orphaned after Phase 5c (only its own test still imported it). - Per-workflow scheduling stays put: ScheduledCronCard + the "Save schedule" / saved-jobs list inside SkillsRunnerBody (/skills/run) are untouched — only the global overview page is gone. - Refresh stale comments in SkillsRunnerBody + ScheduledCronCard that pointed at the deleted file. Verified: app tsc --noEmit clean; 457 skills + pages tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te dead agent_workflows FE Workflows are an intelligence concern, not a connection. Move the unified workflow surface to the Intelligence → Workflows tab and remove it from the Connections page. - New components/intelligence/WorkflowsTab.tsx: the skillsApi-backed workflow surface (list + detail drawer with "Run workflow" CTA + create / install / uninstall). Intelligence's "workflows" tab now renders this. - Connections (Skills.tsx): drop the discovered-workflow cards + their create/install/uninstall modals, drawer, fetch, and toast plumbing. Connections is integrations-only (Composio / channels / MCP) now. - Delete the dead old `agent_workflows` FE: its Rust backend was dissolved in Phase 2 and its `workflowsApi` calls (`workflows_read`/`workflows_phase`) are gone while `workflows_list/create/uninstall` now collide with the SKILL.md controllers. Removed AgentWorkflows page, workflowsApi, workflowsSlice (+ store registration), and components/workflows/* (Card, DetailDrawer, CreateModal, PhaseEditor) + their tests. - /workflows route now redirects to /intelligence?tab=workflows; Intelligence honours `?tab=` for deep-linking. en i18n for `workflows.*` retitled from the dissolved "Agent Workflows" concept to the unified primitive + installFromUrl key. - Tests: deleted the moved Skills.discovered-skills test; added WorkflowsTab coverage. Verified: app tsc --noEmit clean; 1341 tests pass across pages, intelligence, skills, store, services. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flow (add when_to_use) The Workflows-tab create form is now a combo of the two legacy forms: the runners "new skill" form (procedure + declared inputs) and the old agent- workflow form (the `when_to_use` trigger — the "reason to run" a bare procedure md lacks). Core: - CreateSkillParams / WorkflowsCreateParams gain an optional `when_to_use`. create_skill writes it to the workflow's skill.toml; falls back to the description when omitted. A workflow with a distinct trigger but no inputs now still gets a skill.toml so the trigger persists. render_skill_toml's param renamed description→when_to_use to match what it actually writes. Frontend: - CreateSkillInput gains `whenToUse`; createSkill sends it as `when_to_use`. - CreateSkillForm adds an optional "When to use" field between Description and Inputs. (Inputs already: optional-by-default, description-required.) Tests: new Rust test (distinct when_to_use → skill.toml without inputs); new FE test (whenToUse flows into the payload). Verified: 172 workflows tests pass; app tsc clean; CreateSkillForm 16 + skillsApi + WorkflowsTab green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ied primitive
The tab subtitle still carried the dissolved agent-workflows copy
("Lifecycle-bound rule sets…"). Reword to describe the unified runnable
workflow (goal + steps; create / install / run), consistent with the
WorkflowsTab header copy. en only; locales fall back until the i18n pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot divergence)
Adds src/openhuman/workflows/e2e_plumbing_tests.rs — hermetic, no-network
plumbing confirmation for the unified workflow:
- B (passing): a mock-LLM (scripted ScriptedProvider) drives the real agent
tool-loop to call `list_workflows` (sees a seeded project workflow) and
`run_workflow` (unknown id → clean tool error). Confirms both tools are
wired into the orchestrator's tool surface and execute end-to-end.
- C (passing): `await_run_outcome` returns a run's terminal DONE footer, and
auto-detaches (None) when the run outlives the wait budget — the
run_workflow await/detach plumbing.
- A (ignored): create → registry round-trip. Documents a REAL gap the test
surfaced: the create/discover path writes to `.openhuman/skills` (+ home),
but the RUN path (`get_skill` → `load_skills`) scans only `<ws>/skills`.
So a workflow created on the Intelligence tab is discoverable but NOT
runnable ("unknown workflow"). Ignored with a reason; asserts the target
post-fix behaviour. Un-ignore once load_skills is unified with
discover_skills.
The autonomous run that run_workflow spawns builds its provider from config
(no test-injection seam), so these tests confirm everything up to that
boundary; the inner run is covered by the subagent_runner suite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… test seam)
Confirms the genuinely-new execution plumbing with a scripted LLM, no network:
- factory.rs: add a `#[cfg(test)]` process-global provider override
(`test_provider_override`) consulted at the top of `create_chat_provider`.
Inert in release (the check is cfg(test)). Needed because the inner workflow
run is a detached `tokio::spawn` that rebuilds its provider from config — a
thread/task-local seam can't reach it.
- e2e_run_tests.rs (2 tests, #[ignore]+serial — they touch process-global
state: the provider override + OPENHUMAN_WORKSPACE; run with
`--ignored --test-threads=1`):
1. inner workflow run: `spawn_skill_run_background` builds the autonomous
agent, the mock LLM finishes the turn, the run reaches a DONE footer, and
`await_run_outcome` returns it with the agent's output.
2. composition: the orchestrator tool-loop calls `run_workflow` → spawns the
inner run (same mock) → awaits → the inner DONE result flows back into the
conversation; orchestrator then wraps up.
One mock LLM serves both the orchestrator and the inner run, routing by prompt
content. Seeds a runnable workflow at the resolved `<ws>/skills/<id>`.
Default suite unaffected (factory 90, workflows 174 green; the e2e_run tests
are ignored by default).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kflow → Done
Adds test 3 to the mocked-LLM run suite: the complete chain the unified
workflow is meant to support, with a scripted LLM and no network.
create a Todo card on the board → mark Ready → dispatch_card claims it
(→ InProgress) and detaches the autonomous run → the orchestrator (mock)
calls run_workflow → the inner workflow agent (mock) runs to DONE →
await_run_outcome returns it → the orchestrator wraps up → write_back marks
the card Done with the run output as evidence.
Asserts: dispatch returns Running, the card reaches Done, and the run output
("ORCHESTRATOR_DONE") is captured as the card's evidence.
#[ignore]+serial like the others (process-global provider override +
OPENHUMAN_WORKSPACE). Run: cargo test --lib workflows::e2e_run_tests --
--ignored --test-threads=1 → 3 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…"runners") skill→workflow Scoped to exactly what PR tinyhumansai#2882 ("scheduled dashboard + run/new pages …", the runners / new-skill feature that merged into workflows) added — the run-path loader in registry.rs: load_skills → load_workflows get_skill → get_workflow seed_default_skills → seed_default_workflows DEFAULT_SKILLS → DEFAULT_WORKFLOWS Plus their callers (describe_workflow tool, workflows_run RPC, spawn_skill_run_background, task_dispatcher executor resolution, jsonrpc seed). Deliberately NOT touched (verified pre-tinyhumansai#2882 / not the runners): - discover_skills / discover_skills_inner (the original SKILL.md discovery, tinyhumansai#723) and ops_discover::load_skills — the openhuman-skills catalog path - create_skill / read_skill_resource (tinyhumansai#740) - on-disk SKILL.md / skill.toml / skills/ (the tinyhumansai/openhuman-skills registry contract) and all catalog/install interop - the composio "skill" tool category and built-in capability skills Internal-symbol rename only; no behavior change, no on-disk change. Verified: bin compiles; workflows 174 pass; the 3 mocked-LLM lifecycle e2e tests still pass (--ignored --test-threads=1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…th skill→workflow Continues the runner (tinyhumansai#2882) rename — the backend execution path it added, verified introduced by db3fdc2: spawn_skill_run_background → spawn_workflow_run_background SKILL_RUN_MAX_ITERATIONS → WORKFLOW_RUN_MAX_ITERATIONS run-log banner "==== skill_run:" + "skill_run" log labels → "workflow_run" (writer write_header + reader scan_runs + tests changed together — the app's own internal run-log format) Deliberately NOT renamed — `skill_id` stays, because it's a live FE wire key, not an internal label: - WorkflowsRunParams.skill_id (RPC param the frontend sends) - ScannedRun.skill_id (serialized field the frontend reads) - skillsApi sends/reads `skill_id` Renaming it is a coordinated Rust+FE RPC change for a separate pass. On-disk SKILL.md/skill.toml + openhuman-skills interop untouched (the run-log dir <ws>/skills/.runs and the skill.toml filename are unchanged). Verified: bin compiles; workflows 174 pass; 3 mocked-LLM lifecycle e2e tests pass (--ignored --test-threads=1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Rust + FE lockstep) Completes the runner rename — the last skill-named piece was the JSON-RPC wire key shared by the workflows_run / describe / read_resource / recent_runs methods. Renamed in lockstep so the contract stays consistent: Rust (schemas.rs, run_log.rs): - WorkflowsRun/Describe/ReadResource/RecentRunsParams `skill_id` → `workflow_id` - the controller-schema param declarations `name: "skill_id"` → `"workflow_id"` - WorkflowsReadResourceResult / WorkflowRunStarted / ScannedRun `skill_id` → `workflow_id` - run_workflow.rs reads `started.workflow_id` (kept the tool's `skill_id` arg back-compat alias) - tools.rs read_resource response key → `workflow_id` (kept its read_workflow_id / recent-runs `skill_id` aliases) Frontend (skillsApi.ts + test, SkillsRunnerBody): - every `workflows_*` RPC now sends/reads `workflow_id` (snake_case wire key); the camelCase `skillId` JS variables are left as-is (internal FE naming) Left untouched: ops_discover `read_skill_resource` (openhuman-skills discovery), on-disk SKILL.md/skill.toml, and the run_workflow tool's legacy skill_id alias. Verified: bin compiles; workflows 174 + 3 e2e pass; app tsc clean; 494 FE skills/intelligence/api tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rkflows are now runnable The create/list path wrote to `~/.openhuman/skills` + `<ws>/.openhuman/skills`, but the RUN path (`load_workflows` → `get_workflow`) scanned only `<ws>/skills`. So a workflow authored on the Intelligence tab was discoverable but NOT runnable — `run_workflow` → `get_workflow` returned "unknown workflow". Fix: `load_workflows` now enumerates via `discover_skills` (the same roots the create/list path uses — user, project [trusted], legacy; deduped by scope precedence) and loads each one's runnable `WorkflowDefinition`: - `skill.toml` present → parse it (id / when_to_use / [[inputs]] / [github]) + the `SKILL.md` body as the inline system prompt; - `SKILL.md` only → synthesize a minimal definition (id = slug, when_to_use = frontmatter description) so a bare workflow is still runnable. Create → discover → run is now a single path. Un-ignored the gap test (`create_then_registry_roundtrip_preserves_when_to_use_and_inputs`) — it now passes by default and guards the regression. Verified: bin compiles; workflows 175 pass (test A live), agent harness 407, 3 mocked-LLM lifecycle e2e pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nd resolves Done
Covers the "no workflow fits" path: the orchestrator must complete the task
itself rather than calling run_workflow.
- MockLlm.workflow_id is now Option<String>: Some(id) → call run_workflow
(existing tests), None → answer the task directly.
- New test 4 (task_with_no_workflow_runs_directly_and_resolves_done):
dispatch a card with no seeded workflow + a mock that picks none → the
orchestrator answers directly ("TASK_DONE_NO_WORKFLOW") → write_back marks
the card Done. Asserts: card Done, evidence is the direct answer, and
scan_runs is empty (no inner workflow run was ever spawned).
e2e_run suite: 4 passed (--ignored --test-threads=1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ace skill→workflow Scoped strictly to the files the runner PR (tinyhumansai#2882) introduced, per its git --name-status. The openhuman-skills discovery surface (PR tinyhumansai#740/tinyhumansai#833: SkillCard, SkillDetailDrawer, InstallSkillDialog, SkillResourcePreview/Tree, UninstallSkillConfirmDialog, CreateSkillModal, skillsApi) keeps its skill vocabulary — the Workflows tab simply reuses it. Renamed (runner-origin only): - SkillsRunnerBody → WorkflowRunnerBody - SkillsRunnerPanel → WorkflowRunnerPanel - CreateSkillForm → CreateWorkflowForm (the runner's form; CreateSkillModal that wraps it stays skill-named and just updates its import) - pages SkillsRun → WorkflowsRun, SkillNew → WorkflowNew Routes, the ?skill= query param, skillsApi, and all openhuman-skills i18n keys are left untouched. typecheck clean; 50 affected specs green. Co-Authored-By: Claude <noreply@anthropic.com>
…nner keys Scoped to runner-origin (tinyhumansai#2882) i18n keys only — openhuman-skills keys (skills.install/detail/resource/uninstall and the base skills.create.*) are left as-is. - remove 9 dead runner keys across all 14 locales (parity-safe): skills.tabs.runners, skills.dashboard.{title,scheduledHeading,emptyTitle,emptyBody,create,run,loadError}, skills.new.placeholderBody — the standalone scheduled-skills dashboard + Runners tab were phased out - retranslate the 3 live runner values to 'workflow' in en + all 13 locales: skills.dashboard.enable/disable, skills.new.title Note: pnpm i18n:check / i18n:english:check remain red on PRE-EXISTING debt (8 untranslated workflow-surface keys + ~1266 English-leak strings across unrelated domains) that predates this work; not addressed here. Co-Authored-By: Claude <noreply@anthropic.com>
…on capabilities These three entries describe the openhuman-skills Composio connection surface (skill-backed integration status + manual data sync), not the workflow runner. An earlier blanket rename wrongly put 'Workflow' in their names/how-to while the descriptions still said 'skill'. Restore the skill wording so the catalog matches the feature it documents. (The 'workflows' in the Google/Notion/exchange entries is a generic English word — left as-is.) Co-Authored-By: Claude <noreply@anthropic.com>
… dead run_skill tool The runner's Save Schedule path creates a cron *agent* job whose tick prompt told the agent to call the run_skill tool — which no longer exists (renamed to run_workflow). Scheduled workflow runs would mis-fire (agent asked to call a tool not in its set). Point the prompt at run_workflow with an explicit workflow_id; run_workflow still accepts the legacy skill_id alias so the inputs shape is unchanged. Co-Authored-By: Claude <noreply@anthropic.com>
…s/new URL vocabulary now matches the unified primitive. Updates the run/new route paths, the detail-drawer 'Run workflow' CTA, and the ?skill=→?workflow= query param (drawer write + runner read) in lockstep. The /settings/skills-runner dev-options deep link is left unchanged. typecheck clean; 33 affected specs green. Co-Authored-By: Claude <noreply@anthropic.com>
…ches the core Wire-level plumbing bug found by testing: all 10 workflow controllers registered under namespace "skills" → methods resolved as openhuman.skills_* — but the FE (skillsApi) and the JSON-RPC e2e call openhuman.workflows_*. Every FE workflow call (list/create/run/install/uninstall/describe/read_run_log/recent_runs/ read_resource) was failing 'unknown method' against a real core; only unit tests (which mock the RPC / call handlers directly) hid it. Rename the namespace so the methods are openhuman.workflows_* end-to-end. Also: - workflows_create schema was missing the when_to_use param (added to the FE form + handler in the unification, never to the controller schema) → strict param validation rejected FE creates carrying when_to_use. Declare it. - repair json_rpc_workflows_lifecycle_round_trip: it was agent_workflows-era (asserted phases + called dead workflows_read/workflows_phase). Rewrite for the runner model: create→list→describe→uninstall, with when_to_use verified via describe. Fix the stale skills::types::ToolResult import (now workflows::types). - update the skills_uninstall_rpc_e2e test + about_app catalog description + schema namespace assertions to match. Co-Authored-By: Claude <noreply@anthropic.com>
Extends the mock-LLM task e2e (real dispatcher + board + run; mocked LLM only) on the task spine — card → dispatch → run → write-back: - task_run_failure_resolves_card_to_blocked: a failing provider makes the detached run error; assert the card resolves to Blocked with a blocker reason (run_autonomous Err → write_back Blocked). - redispatch_of_claimed_card_is_rejected: dispatch a Ready card (claims → InProgress), then re-dispatch the same stale snapshot; the re-load-before-claim rejects it without spawning a second run (the dedup that stops a re-triggered card double-running). Deterministic (the true-concurrent TOCTOU window noted in dispatch_card is a separate, timing-dependent limitation, not asserted here). read_run_log left out of the task spine by design — a dispatched task reports via card evidence/status, not the runner-page run log. 6/6 e2e pass. Co-Authored-By: Claude <noreply@anthropic.com>
…ap, backstop) Cover the runaway-spawn safety rails that previously had no test: - acquire_await_rejects_the_same_key_reentrantly: same (workflow_id+inputs) key held up the stack → rejected (tight-loop guard); freed on drop. - acquire_await_caps_concurrent_awaits: 8 held → 9th rejected (MAX_ACTIVE_AWAITS nesting/fan-out cap); a freed slot is reusable. - account_spawn_trips_the_process_backstop: drives past TOTAL_SPAWN_BACKSTOP (500) and asserts it trips. Guards share a serial lock (process-global statics). Co-Authored-By: Claude <noreply@anthropic.com>
…kflows-merge # Conflicts: # app/src/lib/i18n/ar.ts # app/src/lib/i18n/bn.ts # app/src/lib/i18n/de.ts # app/src/lib/i18n/es.ts # app/src/lib/i18n/fr.ts # app/src/lib/i18n/hi.ts # app/src/lib/i18n/id.ts # app/src/lib/i18n/it.ts # app/src/lib/i18n/ko.ts # app/src/lib/i18n/pl.ts # app/src/lib/i18n/pt.ts # app/src/lib/i18n/ru.ts # app/src/lib/i18n/zh-CN.ts
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/openhuman/workflows/ops_create.rs (1)
47-89:⚠️ Potential issue | 🟠 Major | ⚡ Quick winReject unsupported
inputs[].typevalues before writingworkflow.toml.The docs here say only
string,integer, andbooleanare supported, but the create/update path never enforces that. A bad RPC caller can persist an arbitrary kind and the runner then has no defined control to render on rediscovery.Suggested fix
fn validate_inputs(inputs: &mut [WorkflowCreateInputDef]) -> Result<(), String> { let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new(); for input in inputs.iter_mut() { let trimmed = input.name.trim(); if trimmed.is_empty() { return Err("input name must not be empty".to_string()); } if !seen.insert(trimmed.to_ascii_lowercase()) { return Err(format!("duplicate input name '{trimmed}'")); } let trimmed = trimmed.to_string(); input.name = trimmed; + input.type_ = match input.type_.as_deref().map(str::trim).filter(|s| !s.is_empty()) { + Some("string" | "integer" | "boolean" as kind) => Some(kind.to_string()), + Some(other) => return Err(format!("unsupported input type '{other}'")), + None => None, + }; } Ok(()) }🤖 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/workflows/ops_create.rs` around lines 47 - 89, Reject and validate unsupported input types before persisting workflow metadata: in the create/update entrypoints (e.g., create_workflow, create_workflow_inner, and workflows_update) iterate over CreateWorkflowParams.inputs (each WorkflowCreateInputDef) and verify the type/kind is one of the allowed values "string", "integer", or "boolean"; if any input has an unsupported type, return a clear error (or reject the request) instead of writing workflow.toml/skill.toml, ensuring the check runs both on create and update paths so invalid kinds never get persisted.src/core/event_bus/events_tests.rs (1)
214-242:⚠️ Potential issue | 🟠 Major | ⚡ Quick winKeep the legacy event domain backward-compatible here.
These assertions lock the workflow lifecycle events onto
"workflow". If any existing filters, dashboards, or trigger rules still key on the historic"skill"domain, this codifies a breaking change with no alias or migration path.Based on learnings, "When reviewing changes to
DomainEvent::variant_name()(or other “stable string” identifiers used by event bus consumers), treat those strings as a compatibility surface."🤖 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/core/event_bus/events_tests.rs` around lines 214 - 242, The test changes lock workflow lifecycle events to the new "workflow" domain and remove the historic "skill" alias, breaking backward compatibility; update the domain-returning logic (e.g., DomainEvent::variant_name / DomainEvent::domain or wherever event domain string is produced) so that WorkflowLoaded, WorkflowStopped, WorkflowStartFailed, and WorkflowExecuted continue to advertise the legacy "skill" alias in addition to "workflow" (for example by returning both names, emitting an alias field, or mapping "skill" to "workflow" on input/output), and adjust tests to assert the presence of the legacy alias so existing filters/dashboards/triggers keep working.src/openhuman/workflows/ops_install.rs (1)
251-266:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCheck all discovered user roots for slug collisions before writing.
This only guards
~/.openhuman/skills/<slug>, but discovery still loads~/.openhuman/workflows/and~/.agents/skills/. If the slug already exists in one of those roots, this install succeeds into a shadowed path and the catalog can remain unchanged.Collision guard sketch
- let skills_root = home - .as_deref() - .ok_or_else(|| "write failed: unable to resolve home directory".to_string())? - .join(".openhuman") - .join("skills"); + let home_dir = home + .as_deref() + .ok_or_else(|| "write failed: unable to resolve home directory".to_string())?; + for existing in [ + home_dir.join(".openhuman").join("workflows").join(&slug), + home_dir.join(".openhuman").join("skills").join(&slug), + home_dir.join(".agents").join("skills").join(&slug), + ] { + if existing.exists() { + return Err(format!( + "workflow already installed as {slug:?} at {}", + existing.display() + )); + } + } + let skills_root = home_dir.join(".openhuman").join("skills");🤖 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/workflows/ops_install.rs` around lines 251 - 266, The current install only checks the single target_dir (skills_root.join(&slug)) for existence, but you must check all discovered user skill roots to prevent shadowing; replace the single exists() guard with a scan over all user-scope roots (e.g. the same roots used by discovery such as the computed skills_root plus the ~/.openhuman/workflows and ~/.agents/skills locations or by calling the existing discover_workflows/discovery helper) and if any root.join(&slug) exists return an Err that names the colliding path; ensure you still compute the intended target_dir for the actual install but abort early if any discovered_root.join(&slug).exists() is true (use slug, skills_root, target_dir and the discovery helper names to locate where to add the loop).
♻️ Duplicate comments (2)
app/src/components/intelligence/WorkflowsTab.tsx (2)
211-223:⚠️ Potential issue | 🟠 Major | ⚡ Quick winUse workflow id (slug) for local uninstall reconciliation.
The
onUninstalledcallback filters the local list byresult.name, but the uninstall flow is explicitly keyed byskill.id(the slug) because display name and slug can diverge. When they do, the removed card stays visible locally until refetch succeeds.🔧 Proposed fix
{uninstallCandidate && ( <UninstallSkillConfirmDialog skill={uninstallCandidate} onClose={() => setUninstallCandidate(null)} onUninstalled={result => { log('uninstalled name=%s', result.name); + const removedId = uninstallCandidate.id; addToast({ type: 'success', title: t('workflows.delete'), message: `"${result.name}" ${t('common.success')}`, }); - setWorkflows(prev => prev.filter(s => s.id !== result.name)); + setWorkflows(prev => prev.filter(s => s.id !== removedId)); void refresh(); }} /> )}🤖 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/intelligence/WorkflowsTab.tsx` around lines 211 - 223, The local uninstall reconciliation currently filters workflows by result.name, which can differ from the workflow slug used as the uninstall key; update the onUninstalled handler in UninstallSkillConfirmDialog to remove the workflow using the slug/id returned by the uninstall (use result.id or the property that represents the workflow slug) — i.e., change the setWorkflows filter from s.id !== result.name to s.id !== result.id (and keep logging/toast using result.name if you want the display name intact).
51-62:⚠️ Potential issue | 🟠 Major | ⚡ Quick winError handling swallows backend failures.
The
refresh()catch block returns[]and clears loading, causing the UI to show "no workflows installed" instead of surfacing the error. This hides outages and makes them appear as if the user has no workflows.💡 Recommended fix
Add error state and render an error panel when backend calls fail:
+ const [loadError, setLoadError] = useState<string | null>(null); + const refresh = useCallback(async (): Promise<SkillSummary[]> => { try { const list = await skillsApi.listSkills(); log('listWorkflows ok count=%d', list.length); + setLoadError(null); setWorkflows(list); return list; } catch (err) { - log('listWorkflows error %s', err instanceof Error ? err.message : String(err)); + const message = err instanceof Error ? err.message : String(err); + log('listWorkflows error %s', message); + setLoadError(message); return []; } finally { setLoading(false); } }, []);Then render an error panel when
loadErroris set instead of the empty state.🤖 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/intelligence/WorkflowsTab.tsx` around lines 51 - 62, The refresh() catch currently swallows backend failures by returning [] and only clearing loading; add a component-level error state (e.g., loadError with setter setLoadError) and in refresh() setLoadError(err) inside the catch (and clear it on success), keep finally to setLoading(false), and update the component render logic to show an error panel when loadError is set instead of rendering the empty "no workflows" state; reference the refresh() function, setWorkflows, setLoading and introduce setLoadError/loadError to locate the changes.
🧹 Nitpick comments (3)
app/src/pages/WorkflowsRun.tsx (1)
17-52: ⚡ Quick winConsider adding unit test coverage for the new page component.
The
WorkflowsRunpage includes navigation logic (conditional back navigation with fallback) that would benefit from unit test coverage to verify the fallback behavior and ensure the page renders correctly. Per guidelines, new behavior inapp/src/should have unit tests via Vitest.A test could verify:
- Renders with correct title and back button labels via
t()- Back button triggers
navigate(-1)when history exists- Back button navigates to
/intelligence?tab=workflowswhen no history- WorkflowRunnerBody is mounted
🤖 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/pages/WorkflowsRun.tsx` around lines 17 - 52, Add Vitest unit tests for the WorkflowsRun component to exercise the conditional back navigation and rendering: write tests that render WorkflowsRun (mocking useT to return predictable labels and mocking WorkflowRunnerBody if needed), assert the title and back button text from t(), simulate clicking the back button with window.history.length > 1 and verify the mocked navigate was called with -1, then simulate window.history.length === 1 and verify navigate was called with '/intelligence?tab=workflows'; also include a test that WorkflowRunnerBody is mounted. Mock useNavigate, useT, and optionally stub WorkflowRunnerBody to isolate behavior.src/openhuman/agent_registry/agents/orchestrator/prompt.md (1)
229-234: 💤 Low valueAdd language specifier to fenced code block.
The code block contains JSON but lacks a language specifier, which affects syntax highlighting and linter compliance.
📝 Proposed fix
-``` +```json "images": [🤖 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/agent_registry/agents/orchestrator/prompt.md` around lines 229 - 234, The fenced code block containing the JSON "images" array needs a language specifier for linters and highlighting; update the opening fence from ``` to ```json so the block that includes the "images" array and its objects (the artifact/file entries and captions) is marked as JSON.src/openhuman/workflows/bus.rs (1)
164-172: ⚡ Quick winRename the subscriber identity to the workflows domain.
TriggeredSkillSubscriberandname()still publish the oldskills::triggered_skilllabel, so bus registration and grep-based tracing keep reporting the retired domain after the rename.Rename sketch
-struct TriggeredSkillSubscriber { +struct TriggeredWorkflowSubscriber { index: Arc<TriggeredWorkflowIndex>, } #[async_trait] -impl EventHandler for TriggeredSkillSubscriber { +impl EventHandler for TriggeredWorkflowSubscriber { fn name(&self) -> &str { - "skills::triggered_skill" + "workflows::triggered_workflow" }As per coding guidelines, "Name the handler struct
<Purpose>Subscriberand thename()return value"<domain>::<purpose>"for grep-friendly tracing output."🤖 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/workflows/bus.rs` around lines 164 - 172, The subscriber struct and its name() still use the old skills domain; rename the struct TriggeredSkillSubscriber to TriggeredWorkflowSubscriber and update its name() to return "workflows::triggered_skill" (i.e., change the returned string from "skills::triggered_skill" to "workflows::triggered_skill") so grep-friendly tracing uses the workflows domain while preserving the purpose label.
🤖 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/pages/WorkflowsRun.tsx`:
- Around line 33-35: The onClick handler in WorkflowsRun.tsx currently uses
window.history.length > 1 to decide between navigate(-1) and
navigate('/intelligence?tab=workflows'), which can still go back to an external
referrer; change the guard to check the in-app history index
(window.history.state?.idx > 0) like useSettingsNavigation does, and only call
navigate(-1) when that condition is true, otherwise navigate to
'/intelligence?tab=workflows' — update the onClick that references navigate(-1)
and window.history.length accordingly to use window.history.state?.idx > 0.
In `@src/openhuman/about_app/types.rs`:
- Around line 11-12: The Workflows enum variant currently has #[serde(rename =
"workflows")] but lacks a backward-compatible alias; add #[serde(alias =
"skills")] next to the rename on the Workflows variant (and repeat the same
alias change for the other affected variant referenced at the 71-71 location) so
serde will accept the legacy "skills" string during deserialization, and
update/add serde tests to assert that deserializing JSON with "skills" yields
the Workflows variant (and likewise for the other affected variant).
In `@src/openhuman/workflows/ops_create.rs`:
- Around line 281-317: When params.overwrite is true, don't call
render_workflow_md on parse failure (which overwrites the user's existing body);
instead, if super::ops_parse::parse_workflow_md(&workflow_md_path) and
super::ops_parse::parse_workflow_md(&legacy_md_path) both return None, read the
raw file contents from workflow_md_path (or legacy_md_path if workflow absent)
and return Some(raw_body) so the existing body is preserved and later combined
with render_workflow_frontmatter; update the preserved_body construction (the
branch assigning preserved_body) to implement this non-frontmatter fallback and
avoid calling render_workflow_md in that overwrite path.
In `@src/openhuman/workflows/ops_discover.rs`:
- Around line 223-233: The discovery currently uses Path::exists which follows
symlinks; update the checks for workflow_md, legacy_md, and legacy_manifest to
reject symlinks before loading: for each path (workflow_md, legacy_md,
legacy_manifest) call std::fs::symlink_metadata(...) and skip the path if
metadata.file_type().is_symlink() (or if symlink_metadata returns an error),
only then check existence/readability and call load_from_workflow_md (or
relevant loader). Reference symbols: workflow_md, legacy_md, legacy_manifest,
load_from_workflow_md, and use std::fs::symlink_metadata /
std::os::unix::fs::FileTypeExt/file_type().is_symlink() to implement the safe
check.
In `@src/openhuman/workflows/ops_install.rs`:
- Around line 408-421: The uninstall resolver only checks ~/.openhuman/workflows
and ~/.openhuman/skills so workflows discovered under ~/.agents/skills never
match and cannot be uninstalled; update the root-resolution logic in
ops_install.rs (the block that builds and .find(...)s roots for the trimmed id)
to include home.join(".agents").join("skills") alongside the existing openhuman
paths, so the same trimmed lookup used by discover_workflows_inner() will locate
workflows under ~/.agents/skills as well; keep the same existence check
(r.join(&trimmed).exists()) and error path unchanged.
In `@src/openhuman/workflows/run_log.rs`:
- Around line 24-66: Add debug-level logging to the cancellation registry: in
register_run_cancel(log the run_id and that a token was created/inserted and
whether it replaced an existing token), in unregister_run_cancel(log the run_id
and whether an entry was removed), and in cancel_run(log the run_id and whether
a token was found and cancelled or missed). Use the same logging crate used
elsewhere (debug! or trace!) and include the run_id and branch decisions
(created/inserted, replaced, removed, cancelled, missed) in the messages so
state transitions in register_run_cancel, unregister_run_cancel, and cancel_run
are observable without changing behavior.
---
Outside diff comments:
In `@src/core/event_bus/events_tests.rs`:
- Around line 214-242: The test changes lock workflow lifecycle events to the
new "workflow" domain and remove the historic "skill" alias, breaking backward
compatibility; update the domain-returning logic (e.g.,
DomainEvent::variant_name / DomainEvent::domain or wherever event domain string
is produced) so that WorkflowLoaded, WorkflowStopped, WorkflowStartFailed, and
WorkflowExecuted continue to advertise the legacy "skill" alias in addition to
"workflow" (for example by returning both names, emitting an alias field, or
mapping "skill" to "workflow" on input/output), and adjust tests to assert the
presence of the legacy alias so existing filters/dashboards/triggers keep
working.
In `@src/openhuman/workflows/ops_create.rs`:
- Around line 47-89: Reject and validate unsupported input types before
persisting workflow metadata: in the create/update entrypoints (e.g.,
create_workflow, create_workflow_inner, and workflows_update) iterate over
CreateWorkflowParams.inputs (each WorkflowCreateInputDef) and verify the
type/kind is one of the allowed values "string", "integer", or "boolean"; if any
input has an unsupported type, return a clear error (or reject the request)
instead of writing workflow.toml/skill.toml, ensuring the check runs both on
create and update paths so invalid kinds never get persisted.
In `@src/openhuman/workflows/ops_install.rs`:
- Around line 251-266: The current install only checks the single target_dir
(skills_root.join(&slug)) for existence, but you must check all discovered user
skill roots to prevent shadowing; replace the single exists() guard with a scan
over all user-scope roots (e.g. the same roots used by discovery such as the
computed skills_root plus the ~/.openhuman/workflows and ~/.agents/skills
locations or by calling the existing discover_workflows/discovery helper) and if
any root.join(&slug) exists return an Err that names the colliding path; ensure
you still compute the intended target_dir for the actual install but abort early
if any discovered_root.join(&slug).exists() is true (use slug, skills_root,
target_dir and the discovery helper names to locate where to add the loop).
---
Duplicate comments:
In `@app/src/components/intelligence/WorkflowsTab.tsx`:
- Around line 211-223: The local uninstall reconciliation currently filters
workflows by result.name, which can differ from the workflow slug used as the
uninstall key; update the onUninstalled handler in UninstallSkillConfirmDialog
to remove the workflow using the slug/id returned by the uninstall (use
result.id or the property that represents the workflow slug) — i.e., change the
setWorkflows filter from s.id !== result.name to s.id !== result.id (and keep
logging/toast using result.name if you want the display name intact).
- Around line 51-62: The refresh() catch currently swallows backend failures by
returning [] and only clearing loading; add a component-level error state (e.g.,
loadError with setter setLoadError) and in refresh() setLoadError(err) inside
the catch (and clear it on success), keep finally to setLoading(false), and
update the component render logic to show an error panel when loadError is set
instead of rendering the empty "no workflows" state; reference the refresh()
function, setWorkflows, setLoading and introduce setLoadError/loadError to
locate the changes.
---
Nitpick comments:
In `@app/src/pages/WorkflowsRun.tsx`:
- Around line 17-52: Add Vitest unit tests for the WorkflowsRun component to
exercise the conditional back navigation and rendering: write tests that render
WorkflowsRun (mocking useT to return predictable labels and mocking
WorkflowRunnerBody if needed), assert the title and back button text from t(),
simulate clicking the back button with window.history.length > 1 and verify the
mocked navigate was called with -1, then simulate window.history.length === 1
and verify navigate was called with '/intelligence?tab=workflows'; also include
a test that WorkflowRunnerBody is mounted. Mock useNavigate, useT, and
optionally stub WorkflowRunnerBody to isolate behavior.
In `@src/openhuman/agent_registry/agents/orchestrator/prompt.md`:
- Around line 229-234: The fenced code block containing the JSON "images" array
needs a language specifier for linters and highlighting; update the opening
fence from ``` to ```json so the block that includes the "images" array and its
objects (the artifact/file entries and captions) is marked as JSON.
In `@src/openhuman/workflows/bus.rs`:
- Around line 164-172: The subscriber struct and its name() still use the old
skills domain; rename the struct TriggeredSkillSubscriber to
TriggeredWorkflowSubscriber and update its name() to return
"workflows::triggered_skill" (i.e., change the returned string from
"skills::triggered_skill" to "workflows::triggered_skill") so grep-friendly
tracing uses the workflows domain while preserving the purpose label.
🪄 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: 26f02882-fbf4-4ece-a68e-f6badecd8269
📒 Files selected for processing (82)
app/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/SkillDetailDrawer.tsxapp/src/components/skills/SkillResourcePreview.tsxapp/src/components/skills/UninstallSkillConfirmDialog.tsxapp/src/components/skills/WorkflowRunnerBody.tsxapp/src/components/skills/__tests__/CreateSkillModal.test.tsxapp/src/components/skills/__tests__/SkillDetailDrawer.test.tsxapp/src/components/skills/__tests__/SkillResourcePreview.test.tsxapp/src/components/skills/__tests__/UninstallSkillConfirmDialog.test.tsxapp/src/components/skills/__tests__/WorkflowRunnerBody.test.tsxapp/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/Intelligence.tsxapp/src/pages/WorkflowsRun.tsxapp/src/services/api/skillsApi.tssrc/core/event_bus/events.rssrc/core/event_bus/events_tests.rssrc/core/jsonrpc.rssrc/openhuman/about_app/README.mdsrc/openhuman/about_app/catalog_data.rssrc/openhuman/about_app/catalog_tests.rssrc/openhuman/about_app/types.rssrc/openhuman/agent/harness/session/builder.rssrc/openhuman/agent/harness/session/runtime.rssrc/openhuman/agent/harness/session/turn.rssrc/openhuman/agent/harness/session/types.rssrc/openhuman/agent/harness/subagent_runner/ops.rssrc/openhuman/agent/profiles.rssrc/openhuman/agent/task_dispatcher.rssrc/openhuman/agent/tools.rssrc/openhuman/agent/tools/run_workflow.rssrc/openhuman/agent_registry/agents/orchestrator/agent.tomlsrc/openhuman/agent_registry/agents/orchestrator/prompt.mdsrc/openhuman/channels/runtime/startup.rssrc/openhuman/composio/tools/direct.rssrc/openhuman/composio/tools/direct_tests.rssrc/openhuman/inference/provider/factory.rssrc/openhuman/todos/ops.rssrc/openhuman/tools/impl/presentation/tests.rssrc/openhuman/tools/ops.rssrc/openhuman/tools/user_filter.rssrc/openhuman/workflows/bus.rssrc/openhuman/workflows/e2e_plumbing_tests.rssrc/openhuman/workflows/e2e_run_tests.rssrc/openhuman/workflows/inject.rssrc/openhuman/workflows/ops.rssrc/openhuman/workflows/ops_create.rssrc/openhuman/workflows/ops_discover.rssrc/openhuman/workflows/ops_install.rssrc/openhuman/workflows/ops_parse.rssrc/openhuman/workflows/ops_tests.rssrc/openhuman/workflows/ops_types.rssrc/openhuman/workflows/registry.rssrc/openhuman/workflows/run_log.rssrc/openhuman/workflows/schemas.rssrc/openhuman/workflows/tools.rssrc/openhuman/workspace/ops.rstests/agent_harness_leftovers_raw_coverage_e2e.rstests/agent_prompts_subagent_raw_coverage_e2e.rstests/agent_round26_raw_coverage_e2e.rstests/agent_session_round24_raw_coverage_e2e.rstests/composio_raw_coverage_e2e.rstests/inference_agent_raw_coverage_e2e.rstests/json_rpc_e2e.rstests/personality_e2e.rstests/tools_approval_channels_raw_coverage_e2e.rstests/tools_channels_raw_coverage_e2e.rs
💤 Files with no reviewable changes (4)
- app/src/components/skills/SkillResourcePreview.tsx
- app/src/components/skills/tests/SkillResourcePreview.test.tsx
- app/src/components/skills/SkillDetailDrawer.tsx
- app/src/components/skills/tests/SkillDetailDrawer.test.tsx
✅ Files skipped from review due to trivial changes (4)
- src/openhuman/about_app/README.md
- app/src/lib/i18n/fr.ts
- app/src/lib/i18n/ar.ts
- app/src/lib/i18n/it.ts
🚧 Files skipped from review as they are similar to previous changes (31)
- src/openhuman/tools/impl/presentation/tests.rs
- src/openhuman/agent/task_dispatcher.rs
- src/openhuman/composio/tools/direct.rs
- app/src/components/intelligence/tests/WorkflowsTab.test.tsx
- src/openhuman/agent/harness/session/runtime.rs
- src/openhuman/workflows/e2e_plumbing_tests.rs
- src/openhuman/channels/runtime/startup.rs
- app/src/lib/i18n/bn.ts
- src/openhuman/composio/tools/direct_tests.rs
- app/src/lib/i18n/zh-CN.ts
- src/openhuman/agent_registry/agents/orchestrator/agent.toml
- src/openhuman/agent/harness/session/types.rs
- src/openhuman/todos/ops.rs
- app/src/lib/i18n/id.ts
- app/src/lib/i18n/es.ts
- src/openhuman/agent/harness/session/builder.rs
- src/openhuman/inference/provider/factory.rs
- app/src/pages/Intelligence.tsx
- app/src/lib/i18n/pl.ts
- src/openhuman/agent/harness/subagent_runner/ops.rs
- src/openhuman/workflows/e2e_run_tests.rs
- app/src/lib/i18n/en.ts
- app/src/lib/i18n/pt.ts
- src/openhuman/agent/profiles.rs
- src/openhuman/agent/harness/session/turn.rs
- app/src/lib/i18n/ko.ts
- src/openhuman/agent/tools/run_workflow.rs
- app/src/lib/i18n/ru.ts
- src/openhuman/agent/tools.rs
- src/openhuman/workflows/ops_tests.rs
- src/openhuman/workflows/schemas.rs
Core: - ops_create: refuse an edit (overwrite) when the existing body can't be parsed instead of clobbering it with the scaffold template (data loss). - ops_install: uninstall now also resolves ~/.agents/skills/, the legacy user root discovery surfaces — else a listed workflow can't be deleted. - ops_discover: reject symlinked manifests (WORKFLOW.md/SKILL.md/skill.json) before loading; exists() follows symlinks → local-file disclosure path. - run_log: read_terminal_outcome now requires the closing `finished:` line, so a half-written footer isn't read as terminal. Plus debug logs around the run-cancel register/cancel/unregister transitions. - user_filter: keep `skill_manage` as a legacy alias for `workflow_manage` so persisted enabled_tool_names snapshots don't silently drop the tools. - startup: install the real triggered-workflow subscriber after discovery (was a no-op shim, so `triggers:` were ignored in the channel runtime). Frontend: - WorkflowsTab: surface listSkills() failures as an error/retry panel instead of the "no workflows" empty state; reconcile uninstall by the workflow id/slug, not the display name. - Intelligence: validate `?tab=` against the visible tab set so a forged `?tab=council` can't open a dev-only tab in prod. Tests: edit-refuses-unparseable-body, uninstall-resolves-agents-skills, symlinked-manifest-rejected, terminal-outcome-requires-finished. Skipped (deferred by maintainer): run_workflow spawn-backstop counting, event_bus variant_name stability, and the about_app catalog updates. Co-Authored-By: Claude <noreply@anthropic.com>
|
Addressed CodeRabbit's review in Core
Frontend
New tests: edit-refuses-unparseable-body, uninstall-resolves- Deferred (per @senamakel on this migration PR): |
Two runner bugs: - Run-now could spawn two runs from rapid clicks: the spawn RPC returns in ~5ms so the re-entry guard released before a second click ~0.5s later. Now the guard is held through a 2.5s cooldown after a successful spawn (released immediately on error) and mirrored into a `runBusy` state that disables the Run buttons, so an accidental double-click can't duplicate. - "Run" on a saved schedule went through cron_run → agent prompt → run_workflow: no visible run appeared and repeat clicks silently errored "cron job already running". It now runs the workflow directly with that schedule's snapshotted inputs (parseScheduledInputs), so a real run shows in Recent runs immediately; errors surface in the schedule error pill. Co-Authored-By: Claude <noreply@anthropic.com>
…kflows-merge # Conflicts: # src/openhuman/agent_registry/agents/orchestrator/agent.toml # src/openhuman/agent_registry/agents/orchestrator/prompt.md
…ents The upstream merge brought three new agents (scheduler / presentation / desktop_control) whose prompt fixtures set both `skills: &[]` and `workflows: &[]`. This PR dissolved the `agent_workflows` domain, so PromptContext no longer has the `workflows` field (only `skills`, now typed `&[Workflow]`). Drop the stale `workflows: &[]` line so the agents compile. (Renaming the surviving `skills` field → `workflows` is a separate follow-up PR.) Co-Authored-By: Claude <noreply@anthropic.com>
|
@coderabbitai review Pushed fixes for the prior review (commit 324d020) plus an upstream re-merge (ca207d6). Addressed in-code: the edit body-clobber guard (ops_create), uninstall reaching ~/.agents/skills (ops_install), symlink-manifest rejection (ops_discover), terminal-outcome footer check + run-cancel debug logs (run_log), skill_manage legacy alias (user_filter), triggered-workflow subscriber wiring (startup), and the FE listSkills error panel / uninstall-by-id / tab-guard. Could you re-scan and resolve the threads that are now addressed? The agent_workflows variant_name/domain + about_app catalog items remain deferred per @senamakel for this migration PR. |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
# List all review comments (unresolved) on PR `#3324`
gh api graphql -f query='
{
repository(owner: "tinyhumansai", name: "openhuman") {
pullRequest(number: 3324) {
reviewThreads(first: 50) {
nodes {
id
isResolved
comments(first: 1) {
nodes {
id
databaseId
body
author { login }
path
line
}
}
}
}
}
}
}' | jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | {threadId: .id, path: .comments.nodes[0].path, line: .comments.nodes[0].line, author: .comments.nodes[0].author.login, body: .comments.nodes[0].body[:200]}'Length of output: 1502 🏁 Script executed: #!/bin/bash
# Get full body of both unresolved threads
gh api graphql -f query='
{
repository(owner: "tinyhumansai", name: "openhuman") {
pullRequest(number: 3324) {
reviewThreads(first: 50) {
nodes {
id
isResolved
comments(first: 3) {
nodes {
databaseId
body
author { login }
}
}
}
}
}
}
}' | jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false)'
echo "---FILE CONTENT---"
# Check current WorkflowsRun.tsx for the navigation pattern
rg -n "history|navigate" app/src/pages/WorkflowsRun.tsxLength of output: 5631
🟡 Still open — fix not yet applied
The code still reads: window.history.length > 1 ? navigate(-1) : navigate('/intelligence?tab=workflows')The suggested fix (align with window.history.state?.idx > 0 ? navigate(-1) : navigate('/intelligence?tab=workflows')🔵 Intentionally deferred — keeping thread open as a reminder
Acknowledged as deferred per Triggering a full re-review of the pushed commits now. 🧠 Learnings used✅ Action performedFull review finished. |
CodeRabbit: `window.history.length > 1` is true even when the only prior entry is an external referrer, so Back could bounce the user out of the app. Use the router history index (`window.history.state?.idx > 0`) — the same guard useSettingsNavigation uses — so navigate(-1) only fires when there's an in-app entry; otherwise fall back to the Workflows tab. Co-Authored-By: Claude <noreply@anthropic.com>
|
Thanks for the re-scan @coderabbitai. ✅ Applied (commit b45d317) — so it won't bounce out of the app when the only prior entry is an external referrer (matches 🔵 Deferred to a follow-up PR (out of scope for this migration, per @senamakel): the |
|
Tip For best results, initiate chat on the files or code changes.
✅ WorkflowsRun.tsx back-nav — fix verified. Using 🔵 Deferred follow-up items — noted. I'll record the new ones below so they aren't lost across PRs. Would you like me to open a GitHub issue to track them together? 🐇 ✨ ✏️ Learnings added
🧠 Learnings used |
The Playwright workflow CRUD spec broke on the runner consolidation: - Creating a workflow now navigates to the runner page, so the delete step has to go back to /workflows (the list) first. - The card's delete lives in the "More actions" menu — open it before clicking delete. - Align selectors to what the components actually emit: workflow-card-<id>-delete → the menu's `workflow-uninstall-<id>`, `alertdialog` → `dialog`, `wf-delete-confirm-btn` → `uninstall-skill-confirm`. Fixes the red "E2E web lane 4/4" shard (the Playwright E2E Gate was already green — it tolerates one shard). Co-Authored-By: Claude <noreply@anthropic.com>
…g-safe e2e - Upstream merge added task_manager_agent (and other specialists) whose prompt fixture set the agent_workflows `workflows: &[]` field this PR removed; drop it (PromptContext keeps only the unified `skills: &[Workflow]`). Same one-line strip applied to each new agent per merge until this lands. - E2E: the workflow create/delete spec uses a slug-safe name, since create stores `name: <slug>` and the runner heading shows that slug. Co-Authored-By: Claude <noreply@anthropic.com>
…t probe The create/delete spec failed at `expect(created?.id).toBeTruthy()` because workflows_list returns the array under `skills` (the kept field name), not `workflows`. Since the test name is already slug-shaped, the created id === name, so use it directly for the delete selectors and drop the RPC round-trip (and its now-unused callCoreRpc import). Heading + final absence assertions still cover create→delete. Co-Authored-By: Claude <noreply@anthropic.com>
- orchestrator: drop `generate_presentation` from its direct tools — upstream moved deck policy to presentation_agent (delegate_make_presentation); tests/orchestrator_presentation_wiring asserts it's not listed directly. - about_app catalog test: this PR renamed the capability ids skills.open_connections_hub / skills.connect_google → workflows.* in catalog_data; update the expected ids in catalog_tests to match. - run_workflow spawn backstop: account a spawn against the process-lifetime cap only AFTER it actually starts (move account_spawn into the Ok arm). Previously unknown-workflow / bad-input rejections burned the budget, so in a long-lived process (the coverage test binary) rejected calls accumulated past 500 and tripped the guard for legitimate runs. Co-Authored-By: Claude <noreply@anthropic.com>
…r gate The runner consolidation + rename added FE lines the Coverage Gate flagged (<80% on the diff). Add targeted Vitest coverage: - skillsApi: updateSkill, cancelRun, createSkill optional-field branches. - WorkflowsTab: card→runner navigation, listSkills error panel + retry, uninstall list-reconcile by id, create→runner. - Intelligence: ?tab= deep link, dev-only tab guard, tab-pill switch. - WorkflowRunnerBody: Stop on a RUNNING recent run (cancelRun), Edit modal from the locked header, "Run" on a saved schedule running directly with its snapshotted inputs (+ the parsed-input chips). - CreateWorkflowForm: edit-mode prefill + submit via updateSkill. - CreateSkillModal: Edit/Save labels in edit mode. Co-Authored-By: Claude <noreply@anthropic.com>
…lias - Replace undefined `selectTab` with `setActiveTab` in PillTabBar onChange - Use existing `isVisibleTab` helper instead of inline array check (removes unused-var error) - Add `alias = "skills"` on CapabilityCategory::Workflows serde so persisted JSON with the old category deserializes correctly
The Intelligence page tests expect the default tab (when no ?tab= param is present) to be 'tasks'. Update the fallback to match the intended behavior from the skills→workflows migration.
The older __tests__/Intelligence.test.tsx expected 'memory' as the default tab and mocked the removed AgentWorkflows module. Updated to expect 'tasks' default and mock WorkflowsTab instead, aligning with the skills→workflows migration.
…erge CreateSkillModal and InstallSkillDialog were removed from Skills.tsx by upstream refactor (tinyhumansai#3324). addToast was also removed upstream. MeetingBotsCard.onToast is optional so drop the prop.
…ev-only
- Re-wire onToast={addToast} to MeetingBotsCard in Skills.tsx.
The prop was dropped after an upstream merge (tinyhumansai#3324); since onToast
is optional, errors and join-success notices were silently discarded.
Adds ToastContainer + local toast state following the same pattern as
Intelligence.tsx.
- Guard ngrok-skip-browser-warning behind IS_DEV in apiClient.ts and
backendHealth.ts. The header was added unconditionally to bypass ngrok
interstitials during local tunnel testing but leaked into all production
requests. It is now emitted only when import.meta.env.DEV is true.
Addresses review comments from M3gA-Mind (tinyhumansai#3363).
…ly cores + spawn-cap regression test Batch C of the skills→workflows internal migration (tinyhumansai#3324 follow-up). C1 (triggered-workflow boot gap): register_triggered_workflow_subscriber previously ran only inside start_channels, which bootstrap_core_runtime skips for web-chat-only desktop installs (no messaging integration) and when OPENHUMAN_DISABLE_CHANNEL_LISTENERS=1 — so any workflow declaring 'triggers:' was silently ignored on those cores. Extracted an idempotent bus::ensure_triggered_workflow_subscriber(workspace) helper backed by a single process-global OnceLock, and call it from BOTH start_channels and bootstrap_core_runtime. The shared OnceLock guarantees exactly one registration regardless of which path runs first. Caveat: the subscriber currently only *matches* triggers and logs — the activation handoff to the integration layer is still pending (TriggeredSkillSubscriber::handle). This enables matching on web-chat-only cores, not yet activation. C2 (spawn-cap backstop): the functional guard already landed in tinyhumansai#3324 (account_spawn runs only in the Ok arm, after spawn_workflow_run_background succeeds; unknown ids are rejected synchronously by registry::get_workflow before accounting). This adds the missing regression test (unknown_workflow_id_does_not_burn_a_spawn_slot) + a #[cfg(test)] guard::total_spawns() reader, asserting a rejected spawn leaves the process-lifetime backstop counter unchanged.
Summary
src/openhuman/skills/→src/openhuman/workflows/, with the RPC surface, agent tools, type idents and FE renamed in lockstep (skills_*→workflows_*,run_skill→run_workflow,Skill*→Workflow*).agent_workflowsdomain + phase machinery (workflow_load/workflow_phasetools removed); the unified create form combines skill + workflow (addswhen_to_use).run_workflowexecution + discovery tool family (list_workflows,describe_workflow,list_workflow_runs,read_workflow_run_log,run_workflow,await_workflow) wired into the orchestrator.Demo
🎥 Loom walkthrough
Problem
The product carried two overlapping primitives — "skills" and "agent workflows" — with parallel RPC namespaces, FE surfaces and agent tools. This split confused the create/run UX and left dead machinery (the QuickJS-era runner, phase tools, scheduled-jobs overview) on the tree.
Solution
Collapse everything onto one workflows domain. The rename is mechanical-but-wide (Rust core + RPC wire keys + FE callers + i18n), staged as discrete commits (domain dir → RPC methods → type idents → execution path → FE → cleanup) so each step is reviewable. The run-path loader is unified with discovery so created workflows are immediately runnable.
Submission Checklist
skippingon this PR; consolidation/rename of existing covered code.## Related— N/A (no new matrix IDs)Closes #NNN— no tracking issue for this refactor.Impact
skills_*→workflows_*andskill_id→workflow_id; FE updated in lockstep. External callers of the oldskills_*RPC names would need updating.Related
AI Authored PR Metadata
Linear Issue
Commit & Branch
refactor/skills-workflows-merged000b5cdValidation Run
pnpm --filter openhuman-app format:check— prettier --check cleanpnpm typecheck— tsc --noEmit cleancargo check --tests+cargo check --bin openhuman-coregreen against currentupstream/main; i18n parity cleancargo fmt --checkclean;cargo checkgreenapp/src-taurichanges in this PR.Validation Blocked
Behavior Changes
Parity Contract
Duplicate / Superseded PR Handling
Summary by CodeRabbit
New Features
Improvements
Refactor
Tests