diff --git a/docs/agent-profile-schema.md b/docs/agent-profile-schema.md index 5933bae5..caa4bb74 100644 --- a/docs/agent-profile-schema.md +++ b/docs/agent-profile-schema.md @@ -68,15 +68,9 @@ provider: cursor provider: copilot ``` -Unsupported or custom providers are rejected. DevSpace maps providers to their -native integration: - -- `codex`: Codex SDK -- `claude`: Claude Code SDK -- `opencode`: OpenCode SDK -- `pi`: Pi RPC mode -- `cursor`: ACP -- `copilot`: ACP +Unsupported or custom providers are rejected. Use a provider name reported by +`devspace agents targets`; availability depends on the local tools installed on +the machine. ### `model` @@ -134,20 +128,26 @@ Recommended body content: The Subagent skill teaches only: ```bash +devspace agents targets devspace agents ls devspace agents run "" devspace agents show ``` -`open_workspace` exposes compact profile metadata: +`open_workspace` exposes compact profile metadata. Provider entries contain only +the provider name; profiles retain their configured defaults because those are +the model's selection inputs: ```json { - "name": "reviewer", - "description": "Read-only reviewer for bugs, security risks, and missing tests.", - "provider": "codex", - "model": "gpt-5.4", - "effort": "high" + "agentProviders": [{ "name": "codex" }], + "agents": [{ + "name": "reviewer", + "description": "Read-only reviewer for bugs, security risks, and missing tests.", + "provider": "codex", + "model": "gpt-5.4", + "effort": "high" + }] } ``` @@ -163,5 +163,5 @@ profile. - Inferring changed files, tests, or diffs from worker output. - Exposing raw provider transcripts by default. - Teaching the model provider-specific CLIs. -- First-class MCP agent tools. Future tools should wrap the same provider - adapter registry used by `devspace agents`. +- MCP agent and workflow execution tools. Use the CLI from a shell-capable + harness; MCP remains the workspace/file/shell surface. diff --git a/docs/chatgpt-coding-workflow.md b/docs/chatgpt-coding-workflow.md index ad591163..c2b425db 100644 --- a/docs/chatgpt-coding-workflow.md +++ b/docs/chatgpt-coding-workflow.md @@ -87,16 +87,17 @@ DevSpace discovers standard Agent Skills from: It also includes: -- the package-managed `subagents` skill when the Subagents capability is enabled -- the package-managed `dynamic-workflows` skill when the Dynamic Workflows capability is enabled +- the bundled `subagents` skill when the Subagents capability is enabled +- the bundled `dynamic-workflows` skill when the Dynamic Workflows capability is enabled - `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` - additional paths from `DEVSPACE_SKILL_PATHS` -When Subagents are enabled, DevSpace discovers agent profiles +When Subagents or Dynamic Workflows are enabled, DevSpace discovers agent profiles from `~/.devspace/agents/*.md` and project `.devspace/agents/*.md`. -`open_workspace` exposes a compact catalog with profile names, descriptions, -providers, and optional models/effort levels so the model can choose a configured agent -without seeing provider-specific launch details. +`open_workspace` exposes a compact catalog with profile names, descriptions, and +optional model/effort defaults so the model can choose a configured agent +without seeing provider-specific launch details. Provider entries contain only +their names, and unavailable providers or dependent profiles are omitted. Example profiles are packaged under `examples/agents/` for users who want starter templates. Copy or adapt them into one of the active profile directories @@ -119,10 +120,16 @@ host or discover it with `devspace agents targets`. `devspace agents ls` lists existing subagent sessions for the current workspace. Set `DEVSPACE_WORKFLOWS=1` to enable Dynamic Workflows independently. When the -variable is omitted, Dynamic Workflows follows the effective Subagents setting, -including persisted config and any environment override. Disabled features are -omitted from the `open_workspace` schema and response rather than returned as -empty capability arrays. +variable is omitted, Dynamic Workflows follows the effective Subagents setting +for backwards compatibility. Disabled features are omitted from the +`open_workspace` schema and response rather than returned as empty capability +arrays. + +Subagents and Dynamic Workflows execute through the DevSpace CLI. The bundled +skills installed by `devspace init` describe the commands and are available to +ChatGPT, Claude, and coding harnesses that can run shell commands. MCP does not +register separate agent or workflow execution tools; use its ordinary `bash` +tool when the host needs to invoke a CLI command. ## Tool Names @@ -158,16 +165,9 @@ a PTY, or send Ctrl-C. Set `tty: true` only for commands that need a terminal. By default, `DEVSPACE_WIDGETS=full`. -In that mode, DevSpace attaches widget UI to the exposed workspace, workflow, -file, edit, and shell tools. The `open_workspace` dropdown presents the opened -root, loaded skills and instructions, available agent providers/profiles, and -currently active workflows for that workspace. - -Dynamic Workflow views are read-only. They refresh through app-only MCP tools -and show observed phases, agent calls, replay state, worktree isolation, errors, -and recent activity. When the host supports MCP Apps fullscreen display mode, -the card offers an **Open dashboard** presentation control. It does not add -cancel, resume, apply, or cleanup actions. +In that mode, DevSpace attaches widget UI to the exposed workspace, file, edit, +and shell tools. The `open_workspace` dropdown presents the opened root, loaded +skills and instructions, and compact agent/workflow summaries when enabled. The aggregate `show_changes` tool is not exposed by default. diff --git a/docs/configuration.md b/docs/configuration.md index a7b20b8d..cf55000a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -93,7 +93,7 @@ sessions. | --- | --- | | `DEVSPACE_SKILLS` | Set to `0` to hide skills. Enabled by default. | | `DEVSPACE_SUBAGENTS` | Set to `1` to expose configured agent profiles as Subagents. Experimental and disabled by default. | -| `DEVSPACE_WORKFLOWS` | Experimental Dynamic Workflows gate. When unset, it follows the effective Subagents setting, including persisted config and any environment override. | +| `DEVSPACE_WORKFLOWS` | Set to `1` to enable Dynamic Workflows independently. When unset, it follows the effective Subagents setting for backwards compatibility. | | `DEVSPACE_AGENT_DIR` | Defaults to `~/.codex`; its `skills` child is loaded for compatibility. | | `DEVSPACE_SKILL_PATHS` | Optional comma-separated additional skill directories. | @@ -105,33 +105,36 @@ DevSpace discovers standard Agent Skills from: It also includes: -- the package-managed `subagents` skill when the Subagents capability is enabled -- the package-managed `dynamic-workflows` skill when the Dynamic Workflows capability is enabled +- the bundled `subagents` skill when the Subagents capability is enabled +- the bundled `dynamic-workflows` skill when the Dynamic Workflows capability is enabled - `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` - additional paths from `DEVSPACE_SKILL_PATHS` User and project skills with the same name take precedence over bundled skills. -DevSpace does not copy bundled skills into `~/.devspace/skills` during setup. +`devspace init` asks about Subagents and Dynamic Workflows separately and +installs enabled skills into `~/.devspace/skills` (or the configured +`DEVSPACE_CONFIG_DIR/skills`). A user-owned directory is preserved; only a +directory previously marked as DevSpace-managed is updated. -When Subagents are enabled, DevSpace discovers agent profiles +When Subagents or Dynamic Workflows are enabled, DevSpace discovers agent profiles from: - `~/.devspace/agents/*.md` - project `.devspace/agents/*.md` `open_workspace` returns a compact catalog containing profile names, -descriptions, providers, and optional models/effort levels so the host model can choose an -agent without reading provider-specific launch details. `devspace agents ls` +descriptions, and optional model/effort defaults so the host model can choose an +agent without reading provider-specific launch details. Provider entries contain +only their names. `devspace agents ls` lists existing subagent sessions for the current workspace, scoped by the workspace environment injected into shell commands. The `subagents` skill teaches the model to discover targets with `devspace agents targets`, then use the minimal `devspace agents run`, `devspace agents show`, and `devspace agents ls` workflow. -Provider availability is detected at runtime. DevSpace does not persist probe -timestamps, availability snapshots, or an experimental provider enable-list in -`config.json`. Final provider policy and onboarding are deferred until the -Subagents and Dynamic Workflows features are finalized. +Provider availability is detected at runtime. Unavailable providers and profiles +that depend on them are omitted from `open_workspace` and `devspace agents +targets`. The enabled feature switches are persisted in `config.json`. Starter profile templates are available under `examples/agents/`. Copy or adapt them into one of the active profile directories before use. diff --git a/docs/dynamic-workflow/devspace/plan.md b/docs/dynamic-workflow/devspace/plan.md index 4a73627f..ca2aa572 100644 --- a/docs/dynamic-workflow/devspace/plan.md +++ b/docs/dynamic-workflow/devspace/plan.md @@ -1,7 +1,12 @@ # DevSpace Dynamic Workflow Engine — Plan -Builds on the locked bigger-model plan. Scope = **this worktree only**. -Subagents stay **CLI-only**. Workflows get **CLI + MCP** over shared primitives. +Builds on the locked bigger-model plan. Scope = **this worktree only**. + +> Current product contract: Subagents and Dynamic Workflows are both +> CLI-only. The MCP workflow registration described in older design notes is +> intentionally not part of the shipped surface. Hosts that can call MCP use +> the ordinary shell tool to invoke the same CLI, while coding harnesses call +> the CLI directly. --- @@ -11,13 +16,13 @@ Subagents stay **CLI-only**. Workflows get **CLI + MCP** over shared primitives. |---|---| | No MCP `agent_run` / `agent_wait` / `agent_show` | Subagent feature surface remains `devspace agents *` (+ skill + shell). | | Workflow workers call adapters **in-process** | `runLocalAgentProvider` / same registry as CLI worker. No shell-out to `agents run` for `agent()`. | -| No dashboard v1 | Events via store drain + CLI `--follow` / MCP status long-poll. | +| No dashboard v1 | Events via the store and CLI `--follow`; hosts can poll with the CLI. | | CC script API parity | `meta`, `agent`, `parallel`, `pipeline`, `phase`, `log`, `args`, `budget`, `workflow` + determinism bans. | | Yolo sub-agents | Fixed write-capable adapter policy; **no** `writeMode` on `agent()`. | | `isolation: 'worktree'` | **Must-have** on `agent()` (CC-like); default shared checkout. | | `effort` (not `thinking`) | Profiles, CLI, store, adapters, `agent()` opts — rename across stack. | | `budget` stub v1 | `{ total: null, spent: () => 0, remaining: () => Infinity }`. | -| Dual surface | `devspace workflow *` **and** MCP `run_workflow` / `workflow_status` / `workflow_cancel`. | +| CLI surface | `devspace workflow *` is the execution surface for every harness. | | All 6 providers v1 | codex/claude/opencode/pi/cursor/copilot via existing adapters. | | Provider policy | Runtime uses currently available providers in stable product order. Durable provider policy and onboarding are deferred. | | Resume-by-replay right after engine core | Same milestone order as locked plan. | @@ -32,7 +37,7 @@ A) One-shot subagents (existing, unchanged API) → detached __worker → adapters → local_agent_sessions B) Dynamic workflows (new) - host MCP / CLI → run row + spawn workflow __worker + host shell → devspace workflow → run row + spawn workflow __worker → sandboxed script → agent() → adapters (in-process) → workflow_* tables (not local_agent_sessions) @@ -45,11 +50,11 @@ B) Dynamic workflows (new) ## 2. Architecture ``` -┌─ CLI: workflow run|status|cancel|ls ─┐ ┌─ MCP: run_workflow|status|cancel ─┐ -│ parse / create run / spawn │ │ same primitives via workflow-tools │ -└──────────────────┬───────────────────┘ └──────────────────┬────────────────┘ - ▼ │ - WorkflowStore (SQLite WAL) ◄────────────────────────┘ +┌─ CLI: workflow run|status|cancel|ls ─┐ +│ parse / create run / spawn │ +└──────────────────┬───────────────────┘ + ▼ + WorkflowStore (SQLite WAL) │ │ detached: node cli.js workflow __worker ▼ @@ -81,16 +86,15 @@ Keep their file split (flat `src/`): | `workflow-replay.ts` | resume cache | | `workflow-schema.ts` | Ajv + retries | | `workflow-files.ts` | named + persist scriptPath | -| `workflow-tools.ts` | MCP registration | +| `workflow-tools.ts` | retired MCP registration (not shipped) | | `skills/dynamic-workflows/SKILL.md` | teaching | DB migration **v4** (v3 = `local_agent_sessions` ✓). Tables: `workflow_runs`, `workflow_events`, `workflow_agent_calls` as specified. Spawn pattern copy `spawnAgentWorker` (detached, stdio ignore, unref). -API semantics: keep their CC-parity table (throws vs parallel→null, pipeline stages, ALS for phase, nested workflow depth 1, budget stub). - -MCP contracts + yield windows: keep (status max ~110s matches `MAX_POLL_YIELD_MS`). +API semantics: keep their CC-parity table (throws vs parallel→null, pipeline +stages, ALS for phase, nested workflow depth 1, budget stub). Milestones 1→8: keep order and verifiability. @@ -104,7 +108,8 @@ In SKILL + serverInstructions + tool descriptions: - Workflows = multi-agent **graphs**. - One-off second opinions = still `devspace agents run` (CLI/skill). -- Do **not** tell models to implement workflows by shelling many `agents run` when `run_workflow` exists. +- Do **not** tell models to implement workflows by shelling many `agents run`; + use one `devspace workflow` command for a graph. ### 4.2 `agent()` backend = adapters, not CLI @@ -121,7 +126,7 @@ runProvider({ provider, prompt, workspace, model, effort, providerSessionId? }) ### 4.3 Provider resolution now; policy later -Current experimental runtime: +Current runtime: - Probe provider availability at execution time. - Resolve `opts.provider` → `meta.defaultProvider` → first available provider @@ -130,28 +135,27 @@ Current experimental runtime: persist them in user configuration. - Unknown or unavailable explicit providers fail that `agent()` call. -The final onboarding release may add an ordered array of provider policy -objects with `id`, `enabled`, `defaultModel`, and `defaultEffort`. That contract -is deliberately deferred so the workflow stack does not publish an unfinished -configuration shape. +Onboarding controls whether the CLI capabilities are enabled. Provider +availability remains a live local check; disabled or unavailable providers are +not advertised to the model. ### 4.4 Skills gating fix (required, not optional) -Bundled `subagents` and `dynamic-workflows` skills remain package-managed. -User/project copies win on name collision. Setup does not copy bundled skills -into `~/.devspace/skills`, which prevents generated copies from shadowing later -package updates. The legacy `subagent-delegation` name is suppressed. +`devspace init` asks about Subagents and Dynamic Workflows independently and +installs enabled skills into `~/.devspace/skills`. A user-owned copy wins on +name collision; only directories marked as DevSpace-managed are updated. The +legacy `subagent-delegation` name is suppressed. -### 4.5 MCP vs CLI symmetry +### 4.5 CLI lifecycle -| Op | CLI | MCP | -|---|---|---| -| Start | `workflow run --file\|--name\|--resume` | `run_workflow` | -| Poll | `status --follow` | `workflow_status` long-poll | -| Cancel | `cancel` | `workflow_cancel` | -| List | `ls` | (optional later; status by id enough v1) | +| Op | CLI | +|---|---| +| Start | `workflow run --file\|--name\|--resume` | +| Poll | `status --follow` | +| Cancel | `cancel` | +| List | `ls` | -Same store. Detached worker survives MCP session death (critical acceptance test). +Same store. Detached workers continue when the parent shell exits. ### 4.6 Replay: document deliberate CC divergence @@ -180,7 +184,8 @@ Document `PI_AGENT_TIMEOUT_MS = 120_000` in SKILL. Follow-up: make configurable ### 4.10 Script authoring feedback -`run_workflow` / CLI parse **before** spawn. Syntax/meta errors return cheat-sheet snippet (tool desc + error). Line numbers preserved via export-strip + lineOffset. +CLI commands parse **before** spawn. Syntax/meta errors return a concise +usage hint. Line numbers are preserved via export-strip + lineOffset. ### 4.11 Concurrency @@ -288,10 +293,10 @@ Determinism bans: `Date.now`, `Math.random`, argless `new Date` → `WorkflowDet | **4 Worker+CLI** | router, spawn, heartbeat, cancel, files | `--follow` log-only + 1 real provider; kill -9 → reap; cancel → group empty | | **5 Resume** | replay + `--resume` | cancel mid-run; resume shows cached prefix events | | **6 Schema** | ajv enforce + retries | bad JSON → schema_retry → success/exhaust | -| **7 MCP** | 3 tools + server wiring | Inspector: run+status; **kill MCP, worker still finishes** | +| **7 CLI hardening** | scope, flags, lifecycle | run/status/cancel/list stay workspace-scoped; worker survives parent exit | | **8 Teach** | skill, seed, skills.ts fix, instructions | fresh + pre-seeded config both advertise skill | -E2E: `npm test` + `npm run typecheck`; live fan-out 2 providers CLI; same MCP; cancel+resume. +E2E: `npm test` + `npm run typecheck`; live fan-out 2 providers through the CLI; cancel+resume. --- @@ -305,9 +310,7 @@ E2E: `npm test` + `npm run typecheck`; live fan-out 2 providers CLI; same MCP; c | `cli.ts` `spawnAgentWorker` / `agents __worker` | copy for `workflow __worker` | | `process-platform.terminateProcessTree` | hard cancel | | `db/client` WAL + busy_timeout 5000 | multi-process journal | -| `server.ts` `registerAppTool` + workflow capability gate | tools only if workflows are enabled | -| `skills.ts` | independent package-managed skill gates | -| `process-sessions` yield bounds | MCP status yield caps | +| `skills.ts` | independent CLI skill gates | --- @@ -320,8 +323,8 @@ E2E: `npm test` + `npm run typecheck`; live fan-out 2 providers CLI; same MCP; c | Pi 120s cap | SKILL note | | Replay key fallback ≠ CC | document | | Laptop sleep heartbeat false fail | `kill(pid,0)` before reap | -| Host model still shells `agents run` for graphs | skill + tool cheat-sheet steer to `run_workflow` | -| Long MCP poll vs proxy timeouts | yield ≤110s; client re-calls status | +| Host model shells many `agents run` calls for graphs | skill steers it to one `workflow run` command | +| Parent shell exits during a run | detached worker plus status polling | --- @@ -347,20 +350,21 @@ E2E: `npm test` + `npm run typecheck`; live fan-out 2 providers CLI; same MCP; c 4. Wire CLI worker to real adapters. 5. Replay. 6. Schema. -7. MCP. +7. CLI contract and workspace scoping. 8. Skill/docs/gating. -Do not open MCP before CLI smoke — debug path must work headless without a host. +Keep CLI smoke tests independent of any host integration; the same path must +work headlessly. --- ## Resolved questions (see also [primitives-spec.md](./primitives-spec.md)) -1. **Default provider:** `opts.provider` → `meta.defaultProvider` → first live provider in stable product order. Final provider defaults and enablement are deferred to onboarding finalization. +1. **Default provider:** `opts.provider` → `meta.defaultProvider` → first live provider in stable product order. Unavailable providers are omitted. 2. **writeMode:** **not in v1 API**; skill teaches prompt-based RO/write. 3. **Isolation:** **`isolation?: 'worktree'` is v1 must-have** on `agent()`; default shared; no auto-merge. 4. **Effort rename:** `thinking` → **`effort`** across profiles, CLI, store, adapters, `agent()` opts, cache keys. -5. **MCP list:** skip; **CLI** `workflow ls` yes. +5. **Run list:** **CLI** `workflow ls` is workspace-scoped. 6. **Size caps:** transport/storage bounds (§8 of primitives-spec); not “coverage” truncation. 7. **Nested workflow:** CC-like `name | { scriptPath }`, depth 1, shared journal/semaphore. 8. **Cancel:** cooperative flag → then group-kill. diff --git a/docs/dynamic-workflow/devspace/primitives-spec.md b/docs/dynamic-workflow/devspace/primitives-spec.md index 219408f3..f495fa45 100644 --- a/docs/dynamic-workflow/devspace/primitives-spec.md +++ b/docs/dynamic-workflow/devspace/primitives-spec.md @@ -1,7 +1,9 @@ # DevSpace Dynamic Workflow — Primitives & API Spec -Implementation + contract spec for every surface, inspired by Claude Code’s Workflow environment. -Pairs with [plan.md](./plan.md). Subagents remain CLI-only; this document is **workflow only**. +Implementation + contract spec for the CLI workflow surface, inspired by +Claude Code’s Workflow environment. +Pairs with [plan.md](./plan.md). Subagents remain CLI-only; this document is +**workflow only**. --- @@ -10,12 +12,12 @@ Pairs with [plan.md](./plan.md). Subagents remain CLI-only; this document is **w | Goal | Surface | |---|---| | DW for coding agents that lack Workflow (pi, codex, opencode, cursor, …) | **CLI + skill** — host agent authors script, runs `devspace workflow *` | -| ChatGPT as orchestrator, not implementer | **MCP workflow tools** behind the workflow capability gate — plan + `run_workflow` / status / cancel | -| Ship both in dev | One engine; two entrypoints; converge later on performance/UX | +| ChatGPT as orchestrator, not implementer | Use the ordinary MCP shell tool to invoke the CLI; no workflow execution tools are registered. | +| Ship one execution surface | One engine behind `devspace workflow`; every harness gets the same CLI contract. | ``` coding agent ── skill + CLI ──► engine ── agent() ──► adapters -ChatGPT ── MCP tools ──► engine ── agent() ──► adapters +ChatGPT ── MCP shell ──► CLI ──► engine ── agent() ──► adapters ``` --- @@ -26,7 +28,7 @@ ChatGPT ── MCP tools ──► engine ── agent() ──► adapter |---|---|---| | 1 | Default provider | Runtime: `opts.provider` → `meta.defaultProvider` → first currently available provider in stable product order. Final provider policy is deferred. | | 2 | Access / writeMode | **Not in v1 API.** No `writeMode`. Skill teaches **prompt-based** RO vs write. Isolation handles *where* writes land (see isolation). | -| 3 | List runs | **No MCP list tool v1.** **CLI** `devspace workflow ls` yes. | +| 3 | List runs | **CLI** `devspace workflow ls`. | | 4 | Size caps | Soft/hard bounds on journal + results (§8). | | 5 | Nested `workflow()` | CC-inspired: `name \| { scriptPath }`, depth 1, shared journal/semaphore (§7.8). | | 6 | Cancel | Cooperative flag → worker abort → hard `terminateProcessTree` (§9). | @@ -41,7 +43,7 @@ ChatGPT ── MCP tools ──► engine ── agent() ──► adapter | CC concept | CC behavior (model-facing) | DevSpace v1 | |---|---|---| -| `Workflow` tool | Host tool; async; script/name/scriptPath/args/resume | CLI `workflow run` + MCP `run_workflow` | +| `Workflow` tool | Host tool; async; script/name/scriptPath/args/resume | CLI `workflow run` | | `export const meta` | Pure literal; name, description, phases | Same + optional `defaultProvider`, `concurrency` | | `agent(prompt, opts)` | Spawn worker; string or schema object; null on skip/death in combinators | Same return contract; **throw** on failure; `parallel` → null | | `opts.schema` | StructuredOutput / validated object | Ajv enforce + retry in engine | @@ -50,7 +52,7 @@ ChatGPT ── MCP tools ──► engine ── agent() ──► adapter | Access / sandbox | Session permission mode; not `writeMode` on agent() | Prompt RO/write + **isolation for write containment** | | `pipeline` | No barrier; per-item chains | Same | | `parallel` | Barrier; null slots | Same | -| `phase` / `log` | Progress UX | Journal events + CLI follow / MCP drain | +| `phase` / `log` | Progress UX | Journal events + CLI follow | | `args` | Verbatim tool args | Same | | `budget` | Shared host token hard ceiling | **Stub** `{ total: null, spent:0, remaining: Infinity }` | | `workflow()` | Nested name/scriptPath; depth 1; shared caps | Same spirit | @@ -64,7 +66,7 @@ ChatGPT ── MCP tools ──► engine ── agent() ──► adapter ### Current experimental contract -There is no user-facing `agentProviders` block and no +There is no user-facing provider capability block and no `DEVSPACE_AGENT_PROVIDERS` environment variable. DevSpace probes implemented providers at runtime, keeps availability details in memory, and orders usable providers by `LOCAL_AGENT_PROVIDERS`: @@ -145,24 +147,19 @@ devspace workflow __worker # hidden Spawn: same pattern as `agents __worker` (detached, stdio ignore, unref). Inputs only from run row. -### 4.2 MCP (togglable with the workflow capability) +### 4.2 MCP hosts -| Tool | Input | Output (conceptual) | -|---|---|---| -| `run_workflow` | `workspaceId`, `script?` \| `name?` \| `resumeFromRunId?`, `args?`, `yieldTimeMs?` | `{ runId, status, events, nextSeq, result? }` after parse+spawn+short yield | -| `workflow_status` | `runId`, `sinceSeq?`, `yieldTimeMs?` | long-poll events / terminal | -| `workflow_cancel` | `runId` | `{ runId, status }` | - -**No** `workflow_ls` on MCP v1. -**No** `agent_*` MCP tools. - -Tool description embeds ~25-line API cheat-sheet (CC-style education in-band). +MCP hosts do not receive workflow execution tools. When a host needs to run a +workflow, it invokes the same CLI through its ordinary shell tool and polls +with `workflow status --follow` or `workflow status`. This keeps long-running +work out of MCP request timeouts and gives shell-capable coding harnesses the +same contract. ### 4.3 Skill -`skills/dynamic-workflows/SKILL.md` (package-managed; not copied on init): +`skills/dynamic-workflows/SKILL.md` (installed by `devspace init` when enabled): -- When to use CLI vs when host is ChatGPT (MCP). +- CLI commands, options, and workflow use cases. - Full primitive reference. - Prompt patterns for read-only vs write (instead of writeMode). - Provider list / default fallback. @@ -241,8 +238,8 @@ Allowed: normal JS, `JSON`, `Array`, `Map`, `Set`, `Date.parse`, `new Date(isoSt | Source | Resolution | |---|---| -| Inline (`--file` content / MCP `script`) | Persist to `/workflows/runs/.js` | -| Named (`--name` / MCP `name`) | (1) `/.devspace/workflows/.js` (2) `~/.devspace/workflows/.js` | +| File (`--file` / `--script-path`) | Persist to `/workflows/runs/.js` | +| Named (`--name`) | (1) `/.devspace/workflows/.js` (2) `~/.devspace/workflows/.js` | | Resume | Load persisted path on prior run (user may edit that copy) | Name sanitization: `^[a-z0-9-]+$`. @@ -457,7 +454,7 @@ function phase(title: string): void | Effect | Sets **current phase** for subsequent agents without `opts.phase`. | | Events | Journal `phase_started` (and optional end on next phase). | | Concurrency | **AsyncLocalStorage** so concurrent pipeline chains don’t race. | -| UI | CLI `--follow` / MCP events group by phase; match `meta.phases[].title` when possible. | +| UI | CLI `--follow` groups events by phase; match `meta.phases[].title` when possible. | ```js function phase(title) { @@ -496,7 +493,6 @@ const args: unknown // frozen; from run input; undefined if omitted | Rule | Spec | |---|---| -| MCP | Pass real JSON object/array — not stringified JSON string. | | CLI | `--arg k=v` → object; values JSON-parsed when valid. | | Freeze | `Object.freeze` deep where practical. | | Resume | Same args required for max cache hits when prompts embed args. | @@ -515,7 +511,7 @@ const budget = Object.freeze({ }) ``` -| Future | Wire `total` from CLI/MCP optional `maxAgentCalls` or token directive; hard-throw when exceeded. | +| Future | Wire `total` from a CLI option or token directive; hard-throw when exceeded. | | v1 | Shape present so scripts/skills match CC; loops must still use dry-round or count, not infinite budget loops. | Skill warns: do not `while (budget.remaining() > x)` without other exit — remaining is Infinity. @@ -751,7 +747,7 @@ return pipeline( | store | `workflow-store.ts` | seq, reap, cancel | | replay | `workflow-replay.ts` | deterministic call-index prefix | | CLI | `cli.ts` | run/status/cancel/ls/__worker | -| MCP | `workflow-tools.ts` | yield, survive disconnect | +| CLI | `workflow-cli.ts` | run/status/cancel/list and workspace scope | | skill | `skills/dynamic-workflows` | education | | providers config | `user-config` / init / availability | ordered default | @@ -759,12 +755,12 @@ return pipeline( ## 16. Non-goals recap (v1) -- MCP raw agent tools +- Raw agent execution tools outside the CLI - `writeMode` on `agent()` (isolation **is** in scope) - Auto-merge of worktrees into source checkout - Real host token budget - Auto file-change / diff events per stage -- MCP run list +- Workflow dashboard - Dashboard - Dual-write `local_agent_sessions` diff --git a/docs/gotchas.md b/docs/gotchas.md index 18ec1912..1062794c 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -201,22 +201,22 @@ DevSpace looks in standard Agent Skills locations: It also checks compatibility and custom paths: -- the package-managed `subagents` skill when the Subagents capability is enabled -- the package-managed `dynamic-workflows` skill when the Dynamic Workflows capability is enabled +- the bundled `subagents` skill when the Subagents capability is enabled +- the bundled `dynamic-workflows` skill when the Dynamic Workflows capability is enabled - `DEVSPACE_AGENT_DIR/skills`, defaulting to `~/.codex/skills` - additional paths from `DEVSPACE_SKILL_PATHS` -When the Subagents capability is enabled, DevSpace loads agent profiles from +When the Subagents or Dynamic Workflows capability is enabled, DevSpace loads agent profiles from `~/.devspace/agents/*.md` and project `.devspace/agents/*.md`, then exposes a compact profile catalog through `open_workspace`. The bundled `subagents` skill can also discover the same usable targets through `devspace agents targets` in CLI-only hosts. `devspace agents ls` lists existing subagent sessions, not profile definitions. -Bundled skills remain package-managed and are not copied into -`~/.devspace/skills`. A user-owned skill with the same name intentionally -overrides the bundled copy. The legacy `subagent-delegation` name is no longer -advertised. +`devspace init` installs enabled bundled skills into `~/.devspace/skills` (or +the configured `DEVSPACE_CONFIG_DIR/skills`) and updates only directories it +previously marked as managed. A user-owned skill with the same name is +preserved. The legacy `subagent-delegation` name is no longer advertised. Packaged agent profile examples under `examples/agents/` are starter templates. Copy or adapt them into one of the active profile directories before use. diff --git a/docs/setup.md b/docs/setup.md index e332f216..3595ac30 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -22,7 +22,10 @@ Run: npx @waishnav/devspace init ``` -The setup flow asks one question at a time. +The setup flow asks one question at a time, including separate prompts for CLI +Subagents and Dynamic Workflows. Enabled skills are copied to +`~/.devspace/skills` (or the configured `DEVSPACE_CONFIG_DIR/skills`) so coding +harnesses can use them through their shell tools. ### Project Roots diff --git a/package.json b/package.json index 026cd630..f8b9f5a5 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dev": "node scripts/dev-server.mjs", "postinstall": "node scripts/fix-node-pty-permissions.mjs", "start": "node dist/cli.js serve", - "test": "tsx src/config.test.ts && tsx src/open-workspace-capabilities.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-capabilities.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-resolution.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/cli-workspace.test.ts && tsx src/pi-tools.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/review-checkpoints.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts && tsx src/workflow-contracts.test.ts && tsx src/workflow-errors.test.ts && tsx src/workflow-types.test.ts && tsx src/workflow-store.test.ts && tsx src/workflow-lifecycle.test.ts && tsx src/workflow-view.test.ts && tsx src/workflow-ui.test.ts && tsx src/workflow-tui.test.ts && tsx src/workflow-script.test.ts && tsx src/workflow-sandbox.test.ts && tsx src/workflow-engine.test.ts && tsx src/workflow-files.test.ts && tsx src/workflow-launch.test.ts && tsx src/workflow-replay.test.ts && tsx src/workflow-schema.test.ts", + "test": "tsx src/config.test.ts && tsx src/open-workspace-capabilities.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-capabilities.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-resolution.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/cli-workspace.test.ts && tsx src/pi-tools.test.ts && tsx src/skills.test.ts && tsx src/skill-installer.test.ts && tsx src/workspaces.test.ts && tsx src/review-checkpoints.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts && tsx src/workflow-contracts.test.ts && tsx src/workflow-errors.test.ts && tsx src/workflow-types.test.ts && tsx src/workflow-store.test.ts && tsx src/workflow-lifecycle.test.ts && tsx src/workflow-view.test.ts && tsx src/workflow-ui.test.ts && tsx src/workflow-tui.test.ts && tsx src/workflow-script.test.ts && tsx src/workflow-sandbox.test.ts && tsx src/workflow-engine.test.ts && tsx src/workflow-files.test.ts && tsx src/workflow-launch.test.ts && tsx src/workflow-replay.test.ts && tsx src/workflow-schema.test.ts", "typecheck": "tsc -p tsconfig.json --noEmit" }, "keywords": [], diff --git a/src/cli.ts b/src/cli.ts index d3ad8ccf..d36076d6 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -36,14 +36,17 @@ import { resolveLocalAgentExecution } from "./local-agent-resolution.js"; import { createLocalAgentStore, type LocalAgentRecord } from "./local-agent-store.js"; import { generateOwnerToken, + devspaceSkillsDir, loadDevspaceFiles, resolveSubagentsFlag, + resolveWorkflowsFlag, writeDevspaceAuth, writeDevspaceConfig, type DevspaceUserConfig, } from "./user-config.js"; import { expandHomePath } from "./roots.js"; import { shutdownHttpServer } from "./server-shutdown.js"; +import { installManagedSkills } from "./skill-installer.js"; import { runWorkflowCommand } from "./workflow-cli.js"; import { @@ -184,12 +187,22 @@ async function runInit({ force }: { force: boolean }): Promise { validate: validateRequiredPublicBaseUrl, })); + const subagents = await confirmPrompt({ + message: "Enable CLI subagents?", + initialValue: resolveSubagentsFlag(files.config) ?? false, + }); + const workflows = await confirmPrompt({ + message: "Enable Dynamic Workflows?", + initialValue: resolveWorkflowsFlag({ ...files.config, subagents }) ?? subagents, + }); + const config: DevspaceUserConfig = { host: files.config.host ?? "127.0.0.1", port, allowedRoots, publicBaseUrl, - subagents: resolveSubagentsFlag(files.config), + subagents, + workflows, }; const auth = { ownerToken: files.auth.ownerToken ?? generateOwnerToken(), @@ -197,6 +210,11 @@ async function runInit({ force }: { force: boolean }): Promise { const configPath = writeDevspaceConfig(config); const authPath = writeDevspaceAuth(auth); + const installedSkills = await installManagedSkills({ + destination: devspaceSkillsDir(), + subagents, + workflows, + }); const lines = [ `Config: ${configPath}`, `Auth: ${authPath}`, @@ -204,6 +222,12 @@ async function runInit({ force }: { force: boolean }): Promise { ...(publicBaseUrl ? [`Public MCP URL: ${publicBaseUrl}/mcp`] : []), ]; prompts.note(lines.join("\n"), "DevSpace configured"); + if (installedSkills.length > 0) { + prompts.note( + installedSkills.map((skill) => `${skill.name}: ${skill.status} (${skill.path})`).join("\n"), + "CLI skills", + ); + } prompts.note( [ `Owner password: ${auth.ownerToken}`, @@ -249,7 +273,7 @@ async function serve(): Promise { } console.log("auth: Owner password approval required"); console.log(`logging: ${config.logging.level} ${config.logging.format}`); - if (config.subagents) { + if (config.subagents || config.workflows) { console.log(`subagent providers: ${formatLocalAgentProviderAvailabilitySummary(localAgentProviders)}`); } }); @@ -291,7 +315,7 @@ async function runDoctor(): Promise { console.log(`Allowed hosts: ${config.allowedHosts.join(", ")}`); console.log(`Subagents: ${config.subagents ? "enabled" : "disabled"}`); console.log(`Workflows: ${config.workflows ? "enabled" : "disabled"}`); - if (config.subagents) { + if (config.subagents || config.workflows) { const snapshot = getLocalAgentProviderAvailabilitySnapshot(); console.log( `Agent providers (live): ${formatLocalAgentProviderAvailabilitySummary(snapshot)}`, @@ -338,14 +362,14 @@ function printHelp(): void { "Usage:", " devspace Run first-time setup if needed, then start the server", " devspace serve Start the server", - " devspace init Create or update ~/.devspace/config.json and auth.json", + " devspace init Create or update config, auth, and enabled CLI skills", " devspace doctor Show config, runtime, and native dependency status", " devspace config get Print persisted config", " devspace config set publicBaseUrl ", " devspace agents ls List subagent sessions", " devspace agents run [--model ] ", " devspace agents show ", - " devspace workflow run|status|cancel|ls", + " devspace workflow run|status|cancel|ls|calls|call", " devspace -v, --version Print the installed version", "", "For temporary tunnels:", @@ -697,6 +721,12 @@ async function textPrompt(options: TextPromptOptions): Promise { return value || options.defaultValue; } +async function confirmPrompt(options: Parameters[0]): Promise { + const result = await prompts.confirm(options); + if (prompts.isCancel(result)) throw new SetupCancelledError(); + return Boolean(result); +} + function validatePort(value: string | undefined): string | undefined { const port = Number(value); return Number.isInteger(port) && port >= 1 && port <= 65535 diff --git a/src/config.test.ts b/src/config.test.ts index ad916270..42d574bd 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -3,7 +3,7 @@ import { mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { loadConfig } from "./config.js"; -import { resolveSubagentsFlag } from "./user-config.js"; +import { resolveSubagentsFlag, resolveWorkflowsFlag } from "./user-config.js"; const emptyConfigDir = mkdtempSync(join(tmpdir(), "devspace-empty-config-test-")); const baseEnv = { @@ -49,6 +49,11 @@ assert.equal(resolveSubagentsFlag({}, {}), undefined); assert.equal(resolveSubagentsFlag({ subagents: true }, {}), true); assert.equal(resolveSubagentsFlag({ subagents: true }, { DEVSPACE_SUBAGENTS: "0" }), false); assert.equal(resolveSubagentsFlag({}, { DEVSPACE_SUBAGENTS: "1" }), true); +assert.equal(resolveWorkflowsFlag({}, {}), undefined); +assert.equal(resolveWorkflowsFlag({ subagents: true }, {}), true); +assert.equal(resolveWorkflowsFlag({ subagents: true, workflows: false }, {}), false); +assert.equal(resolveWorkflowsFlag({}, { DEVSPACE_WORKFLOWS: "1" }), true); +assert.equal(resolveWorkflowsFlag({ workflows: true }, { DEVSPACE_WORKFLOWS: "0" }), false); assert.throws( () => loadConfig({ ...baseEnv, DEVSPACE_WIDGETS: "invalid" }), @@ -169,6 +174,7 @@ writeFileSync( allowedRoots: [process.cwd()], publicBaseUrl: "https://devspace.example.com", subagents: true, + workflows: false, }), ); writeFileSync( @@ -183,6 +189,7 @@ assert.equal(fileConfig.port, 8787); assert.equal(fileConfig.oauth.ownerToken, "persisted-owner-token-long-enough"); assert.equal(fileConfig.publicBaseUrl, "https://devspace.example.com"); assert.equal(fileConfig.subagents, true); +assert.equal(fileConfig.workflows, false); assert.deepEqual(fileConfig.allowedHosts, [ "localhost", "127.0.0.1", diff --git a/src/config.ts b/src/config.ts index 974e7b88..5fad6903 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,7 +3,12 @@ import { join, resolve } from "node:path"; import { expandHomePath } from "./roots.js"; import type { LoggingConfig, LogFormat, LogLevel } from "./logger.js"; import type { OAuthConfig } from "./oauth-provider.js"; -import { devspaceAgentsDir, devspaceSkillsDir, loadDevspaceFiles } from "./user-config.js"; +import { + devspaceAgentsDir, + devspaceSkillsDir, + loadDevspaceFiles, + resolveWorkflowsFlag, +} from "./user-config.js"; export type ToolMode = "minimal" | "full" | "codex"; export type WidgetMode = "off" | "changes" | "full"; @@ -219,12 +224,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig { env.DEVSPACE_SUBAGENTS === undefined ? files.config.subagents === true : parseBoolean(env.DEVSPACE_SUBAGENTS); - // Experimental compatibility: workflows follow the existing subagents gate - // unless explicitly overridden for runtime testing. - const workflows = - env.DEVSPACE_WORKFLOWS === undefined - ? subagents - : parseBoolean(env.DEVSPACE_WORKFLOWS); + const workflows = resolveWorkflowsFlag({ ...files.config, subagents }, env) ?? false; return { host, diff --git a/src/server.ts b/src/server.ts index 427f8f7f..71d6dae1 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1882,7 +1882,7 @@ if (await isMainModule()) { console.log(`request logging: ${config.logging.requests ? "enabled" : "disabled"}`); console.log(`asset logging: ${config.logging.assets ? "enabled" : "disabled"}`); console.log(`trust proxy: ${config.logging.trustProxy ? "enabled" : "disabled"}`); - if (config.subagents) { + if (config.subagents || config.workflows) { console.log(`subagent providers: ${formatLocalAgentProviderAvailabilitySummary(localAgentProviders)}`); } }); diff --git a/src/skill-installer.test.ts b/src/skill-installer.test.ts new file mode 100644 index 00000000..71e320fd --- /dev/null +++ b/src/skill-installer.test.ts @@ -0,0 +1,37 @@ +import assert from "node:assert/strict"; +import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { bundledSkillsRoot, installManagedSkills, readManagedSkillMarker } from "./skill-installer.js"; + +const root = await mkdtemp(join(tmpdir(), "devspace-skill-installer-")); +try { + const destination = join(root, "skills"); + const first = await installManagedSkills({ + destination, + subagents: true, + workflows: false, + sourceRoot: bundledSkillsRoot(), + }); + assert.deepEqual(first.map((result) => result.name), ["subagents"]); + assert.equal(first[0]?.status, "installed"); + assert.match(await readFile(join(destination, "subagents", "SKILL.md"), "utf8"), /devspace agents run/); + assert.ok(await readManagedSkillMarker(join(destination, "subagents"))); + + const userOwned = join(destination, "dynamic-workflows"); + await mkdir(userOwned, { recursive: true }); + await writeFile(join(userOwned, "SKILL.md"), "user-owned\n"); + const second = await installManagedSkills({ + destination, + subagents: true, + workflows: true, + sourceRoot: bundledSkillsRoot(), + }); + assert.equal(second.find((result) => result.name === "subagents")?.status, "updated"); + assert.equal(second.find((result) => result.name === "dynamic-workflows")?.status, "preserved"); + assert.equal(await readFile(join(userOwned, "SKILL.md"), "utf8"), "user-owned\n"); +} finally { + await rm(root, { recursive: true, force: true }); +} + +console.log("skill-installer.test.ts: ok"); diff --git a/src/skill-installer.ts b/src/skill-installer.ts new file mode 100644 index 00000000..1046dbf4 --- /dev/null +++ b/src/skill-installer.ts @@ -0,0 +1,83 @@ +import { access, cp, mkdir, readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const MANAGED_MARKER = ".devspace-managed"; +const MANAGED_SKILLS = ["subagents", "dynamic-workflows"] as const; + +export type ManagedSkillName = (typeof MANAGED_SKILLS)[number]; + +export interface InstallManagedSkillsInput { + destination: string; + subagents: boolean; + workflows: boolean; + sourceRoot?: string; +} + +export interface ManagedSkillInstallResult { + name: ManagedSkillName; + status: "installed" | "updated" | "preserved"; + path: string; +} + +export async function installManagedSkills( + input: InstallManagedSkillsInput, +): Promise { + const sourceRoot = input.sourceRoot ?? bundledSkillsRoot(); + const enabled = new Set([ + ...(input.subagents ? ["subagents" as const] : []), + ...(input.workflows ? ["dynamic-workflows" as const] : []), + ]); + if (enabled.size === 0) return []; + + await mkdir(input.destination, { recursive: true }); + const results: ManagedSkillInstallResult[] = []; + for (const name of MANAGED_SKILLS) { + if (!enabled.has(name)) continue; + results.push(await installOneManagedSkill(sourceRoot, input.destination, name)); + } + return results; +} + +export function bundledSkillsRoot(): string { + return fileURLToPath(new URL("../skills", import.meta.url)); +} + +async function installOneManagedSkill( + sourceRoot: string, + destinationRoot: string, + name: ManagedSkillName, +): Promise { + const source = join(sourceRoot, name); + const destination = join(destinationRoot, name); + const marker = join(destination, MANAGED_MARKER); + const exists = await pathExists(destination); + if (exists && !(await pathExists(marker))) { + return { name, status: "preserved", path: destination }; + } + + await cp(source, destination, { recursive: true, force: true }); + await writeFile(marker, "Managed by DevSpace.\n", { encoding: "utf8", mode: 0o600 }); + return { + name, + status: exists ? "updated" : "installed", + path: destination, + }; +} + +async function pathExists(path: string): Promise { + try { + await access(path); + return true; + } catch { + return false; + } +} + +export async function readManagedSkillMarker(path: string): Promise { + try { + return await readFile(join(path, MANAGED_MARKER), "utf8"); + } catch { + return undefined; + } +} diff --git a/src/user-config.ts b/src/user-config.ts index 970685cf..1a4abaa4 100644 --- a/src/user-config.ts +++ b/src/user-config.ts @@ -19,6 +19,7 @@ export interface DevspaceUserConfig { worktreeRoot?: string; agentDir?: string; subagents?: boolean; + workflows?: boolean; } export interface DevspaceAuthConfig { @@ -105,6 +106,18 @@ export function resolveSubagentsFlag( return ["1", "true", "yes", "on"].includes(env.DEVSPACE_SUBAGENTS.toLowerCase()); } +export function resolveWorkflowsFlag( + config: Pick, + env: NodeJS.ProcessEnv = process.env, +): boolean | undefined { + if (env.DEVSPACE_WORKFLOWS !== undefined) { + return ["1", "true", "yes", "on"].includes(env.DEVSPACE_WORKFLOWS.toLowerCase()); + } + // Older config files only had the subagents toggle. Preserve their behavior + // while allowing new setups to persist an independent workflow choice. + return config.workflows ?? config.subagents; +} + function readJsonFile(filePath: string): T { try { return JSON.parse(readFileSync(filePath, "utf8")) as T;