taskplane 0.24.31 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,35 +1,47 @@
1
1
  # Taskplane
2
2
 
3
- Multi-agent AI orchestration for [pi](https://github.com/badlogic/pi-mono) — parallel task execution with checkpoint discipline, fresh-context worker loops, cross-model reviews, and automated merges.
3
+ Multi-agent AI orchestration for coding with [pi](https://github.com/badlogic/pi-mono) — parallel task execution, mono- and poly-repo support, fresh-context worker loops, cross-model reviews, automated merges and a killer dashboard!
4
4
 
5
- > **Status:** Experimental / Early — APIs and config formats may change between releases.
5
+ > **Status:** Initial release.
6
6
 
7
7
  ## What It Does
8
8
 
9
- ### STEP 1: Create the tasks
10
- Taskplane turns your coding project into an AI-managed task orchestration system. You simply ask your agent to create tasks using the built-in "create-taskplane-tasks" skill. This skill provides an opinionated task definition template designed to drive successful coding outcomes. Tasks define both the prompt.md and the status.md files that together act as the persistent memory store that allows AI coding agents to survive context resets and succeed with very long running tasks that would typically exhaust an agent's context window.
9
+ Taskplane turns ideas into high-quality code using a proven process of:
11
10
 
12
- ### STEP 2: Run batches of tasks
13
- The system works out the dependancy map for the entire batch of tasks then orchestrates them in waves, with appropriate parallelization and serialization.
11
+ have an idea >> create a spec >> create tasks >> orchestrate tasks >> evaluate the outcome
14
12
 
15
- The taskplane dashboard runs on a local port on your system and gives you elegant visibility into everything that's going on (a stark improvement over TUI-based dashboards).
13
+ ### Taskplane has:
14
+ - A skill for creating tasks that the Taskplane orchestrator can run
15
+ - PROMPT.md/STATUS.md task definition for persistent memory store
16
+ - Support for both monorepo and polyrepo projects
17
+ - Complete parallelized worktree isolation with dependency graphing and segment-level repo isolation
18
+ - 4 agent types: supervisor, worker, reviewer, and merger
19
+ - A deterministic orchestration engine to drive repeatable positive agent outcomes at scale
20
+ - A simple file-based mail system so agents can communicate with each other
21
+ - A killer locally-run web-based dashboard so you can see everything that's going on
16
22
 
17
23
  <img src="docs/images/orchrun-wave2of4-2lanes-withstatus.png" alt="image of taskplane dashboard" width="50%">
18
24
 
25
+ ### STEP 1: Create the tasks
26
+ Taskplane turns your coding project into an AI-managed task orchestration system. You simply ask your agent to create tasks using the built-in "create-taskplane-tasks" skill. This skill provides an opinionated task definition template designed to drive successful coding outcomes. Tasks define both the prompt.md and the status.md files that together act as the persistent memory store that allows AI coding agents to survive context resets and succeed with very long running tasks that would typically exhaust an agent's context window.
27
+
28
+ ### STEP 2: Run batches of tasks
29
+ Taskplane works out the dependency map for an entire batch of tasks then orchestrates them in waves, lanes, and tasks with appropriate parallelization and serialization. Taskplane can do this for both monorepo and polyrepo projects. For polyrepo projects, Taskplane additionally subdivides tasks into repo-aligned segments and uses a segmentation dependency map (DAG) to manage proper repo/worktree isolation and allow for dynamic segment expansion so worker agents can ask the supervisor agent to add additional segments to the dependency map in real time if required.
30
+
19
31
  ### Key Features
20
32
 
21
33
  - **Task Orchestrator** — Parallel multi-task execution using git worktrees for full filesystem isolation. Dependency-aware wave scheduling. Automated merges into a dedicated orch branch — your working branch stays stable until you choose to integrate.
22
- - **Persistent Worker Context** — Workers handle all steps in a single context, auto-detecting the model's context window (1M for Claude 4.6 Opus, 200K for Bedrock). Only iterates on context overflow. Dramatic reduction in spawn count and token cost.
23
- - **Worker-Driven Inline Reviews** — Workers invoke a `review_step` tool at step boundaries. Reviewer agents spawn in tmux sessions with full telemetry. REVISE feedback is addressed inline without losing context.
34
+ - **Persistent Worker Context** — Workers handle all steps in a single context, auto-detecting the model's context window. Only iterates on context overflow. Dramatic reduction in spawn count and token cost.
35
+ - **Worker-Driven Inline Reviews** — Workers invoke a `review_step` tool at step boundaries. Reviewer agents spawn with full telemetry. REVISE feedback is addressed inline without losing context.
24
36
  - **Supervisor Agent** — Conversational supervisor monitors batch progress, handles failures, and can invoke orchestrator commands autonomously (resume, integrate, pause, abort).
25
37
  - **Web Dashboard** — Live browser-based monitoring via `taskplane dashboard`. SSE streaming, lane/task progress, reviewer activity, merge telemetry, batch history.
26
38
  - **Structured Tasks** — PROMPT.md defines the mission, steps, and constraints. STATUS.md tracks progress. Agents follow the plan, not vibes.
27
39
  - **Checkpoint Discipline** — Step boundary commits ensure work is never lost, even if a worker crashes mid-task.
28
40
  - **Cross-Model Review** — Reviewer agent uses a different model than the worker agent (highly recommended, not enforced). Independent quality gate before merge.
29
41
 
30
- ## Install
42
+ ## Installation
31
43
 
32
- Taskplane is a [pi package](https://github.com/badlogic/pi-mono). You need [Node.js](https://nodejs.org/) 22 and [pi](https://github.com/badlogic/pi-mono) installed first.
44
+ Taskplane is a pi package. You need Node.js 22+, pi and Git installed first.
33
45
 
34
46
  ### Prerequisites
35
47
 
@@ -38,51 +50,31 @@ Taskplane is a [pi package](https://github.com/badlogic/pi-mono). You need [Node
38
50
  | [Node.js](https://nodejs.org/) ≥ 22 | Yes | Runtime |
39
51
  | [pi](https://github.com/badlogic/pi-mono) | Yes | Agent framework |
40
52
  | [Git](https://git-scm.com/) | Yes | Version control, worktrees |
41
- | **tmux** | **Strongly recommended** | Required for `/orch` parallel execution |
42
53
 
43
- **tmux** is needed for the orchestrator to spawn parallel worker sessions. Without it, `/orch` will not work. On Windows, Taskplane can install it for you:
44
-
45
- ```bash
46
- taskplane install-tmux
47
- ```
48
-
49
- On macOS: `brew install tmux` · On Linux: `sudo apt install tmux` (or your distro's package manager)
50
-
51
- ### Option A: Global Install (all projects)
54
+ ### Option A: Global Install (all projects - recommended)
52
55
 
53
56
  ```bash
54
57
  pi install npm:taskplane
55
58
  ```
56
59
 
57
- ### Option B: Project-Local Install (recommended for teams)
60
+ ### Option B: Single Project-Local Install
58
61
 
59
62
  ```bash
60
63
  cd my-project
61
64
  pi install -l npm:taskplane
62
65
  ```
63
66
 
64
- Then scaffold your project:
65
-
66
- ```bash
67
- taskplane init
68
- ```
69
-
70
- Verify the installation:
71
-
72
- ```bash
73
- taskplane doctor
74
- ```
75
-
76
67
  ## Quickstart
77
68
 
78
- ### 1. Initialize a project
69
+ ### 1. Initialize a project (scaffolds settings)
79
70
 
80
71
  ```bash
81
72
  cd my-project
82
- taskplane init --preset full
73
+ taskplane init
83
74
  ```
75
+ You'll answer a few questions. You can usually just accept the defaults.
84
76
 
85
- This creates config files in `.pi/`, agent prompts, two example tasks, and adds `.gitignore` entries for runtime artifacts. On first install, init bootstraps global preferences at `~/.pi/agent/taskplane/preferences.json` with thinking defaults set to `high` for worker/reviewer/merger. Interactive init then prompts for worker/reviewer/merger model + thinking defaults (`inherit`, `off`, `minimal`, `low`, `medium`, `high`, `xhigh`). If 2+ providers are available from `pi --list-models`, init recommends cross-provider reviewer/merger selections. Init auto-detects whether you're in a single repo or a multi-repo workspace. See the [install tutorial](docs/tutorials/install.md) for workspace mode and other scenarios.
77
+ This creates config files in `.pi/`, agent prompts, two example tasks, and adds `.gitignore` entries for runtime artifacts. On first install, init bootstraps global preferences at `~/.pi/agent/taskplane/preferences.json` with thinking defaults set to `high` for worker & reviewer, and off for merger. Interactive init then prompts for worker/reviewer/merger model + thinking defaults (`inherit`, `off`, `minimal`, `low`, `medium`, `high`, `xhigh`). If 2+ providers are available from `pi --list-models`, init recommends cross-provider reviewer/merger selections. Init auto-detects whether you're in a single repo or a multi-repo workspace. See the [install tutorial](docs/tutorials/install.md) for workspace mode and other scenarios.
86
78
 
87
79
  Want to reuse model/thinking picks across projects? Run `taskplane config --save-as-defaults` in an initialized project.
88
80
 
@@ -94,7 +86,15 @@ taskplane init --preset full --tasks-root docs/task-management
94
86
 
95
87
  When `--tasks-root` is provided, example task packets are skipped by default. Add `--include-examples` if you explicitly want examples in that folder.
96
88
 
97
- ### 2. Launch the dashboard (recommended)
89
+ ### 2. Check your install with taskplane doctor
90
+
91
+ Verify the installation and scaffolding. You should have all green checkboxes if everything was successful:
92
+
93
+ ```bash
94
+ taskplane doctor
95
+ ```
96
+
97
+ ### 3. Launch the dashboard (recommended)
98
98
 
99
99
  In a separate terminal:
100
100
 
@@ -104,7 +104,7 @@ taskplane dashboard
104
104
 
105
105
  Opens a live web dashboard at `http://localhost:8099` with real-time batch monitoring.
106
106
 
107
- ### 3. Run your first orchestration
107
+ ### 4. Run your first orchestration
108
108
 
109
109
  ```bash
110
110
  pi
@@ -121,7 +121,7 @@ Inside the pi session:
121
121
 
122
122
  `/orch` with no arguments is the universal entry point — it detects your project state and activates the supervisor for guided interaction (onboarding, batch planning, health checks, or retrospective). The default scaffold includes two independent example tasks, so `/orch all` gives you an immediate orchestrator + dashboard experience.
123
123
 
124
- ### 4. Run a single task with isolation
124
+ ### 5. Run a single task with isolation
125
125
 
126
126
  For a single task with full worktree isolation, dashboard, and reviews:
127
127
 
@@ -131,18 +131,12 @@ For a single task with full worktree isolation, dashboard, and reviews:
131
131
 
132
132
  This uses the same orchestrator infrastructure as a full batch — isolated worktree, orch branch, supervisor, dashboard, inline reviews — but for just one task.
133
133
 
134
- > **Deprecated:** The `/task` command is deprecated and will be removed in a future major version. It does not provide worktree isolation, dashboard, or inline reviews. Use `/orch` for all workflows — including single-task execution.
135
-
136
134
  ## Commands
137
135
 
138
136
  ### Pi Session Commands
139
137
 
140
138
  | Command | Description |
141
139
  |---------|-------------|
142
- | `/task <path/to/PROMPT.md>` | ⚠️ **Deprecated.** Execute one task in the current branch/worktree. Use `/orch` instead. |
143
- | `/task-status` | ⚠️ **Deprecated.** Show current task progress. Use `/orch-status` or dashboard. |
144
- | `/task-pause` | ⚠️ **Deprecated.** Pause after current worker iteration finishes. Use `/orch-pause`. |
145
- | `/task-resume` | ⚠️ **Deprecated.** Resume a paused task. Use `/orch-resume`. |
146
140
  | `/orch [<areas\|paths\|all>]` | No args: detect state & guide (onboarding, batch planning, etc.); with args: execute tasks via isolated worktrees |
147
141
  | `/orch-plan <areas\|paths\|all>` | Preview execution plan without running |
148
142
  | `/orch-status` | Show batch progress |
@@ -176,8 +170,7 @@ This uses the same orchestrator infrastructure as a full batch — isolated work
176
170
  │ │ │
177
171
  ┌────▼────┐ ┌──▼─────┐ ┌──▼─────┐
178
172
  │ Lane 1 │ │ Lane 2 │ │ Lane 3 │ ← Git worktrees
179
- /task │ │ /task │ │ /task │ (isolated)
180
- │ Worker │ │ Worker │ │ Worker │
173
+ Worker │ │ Worker │ │ Worker │ (isolated)
181
174
  │ Review │ │ Review │ │ Review │
182
175
  └────┬────┘ └──┬─────┘ └──┬─────┘
183
176
  │ │ │
package/bin/taskplane.mjs CHANGED
@@ -316,93 +316,6 @@ function detectStack(projectRoot) {
316
316
 
317
317
  // ─── YAML Generation ────────────────────────────────────────────────────────
318
318
 
319
- function generateTaskRunnerYaml(vars) {
320
- return `# ═══════════════════════════════════════════════════════════════════════
321
- # Task Runner Configuration — ${vars.project_name}
322
- # ═══════════════════════════════════════════════════════════════════════
323
- #
324
- # This file configures the /task command (task-runner extension).
325
- # Edit freely — this file is owned by you, not the package.
326
-
327
- # ── Task Areas ────────────────────────────────────────────────────────
328
- # Define where tasks live. Each area has a folder path, ID prefix, and
329
- # a CONTEXT.md file that provides domain context to agents.
330
-
331
- task_areas:
332
- ${vars.default_area}:
333
- path: "${vars.tasks_root}"
334
- prefix: "${vars.default_prefix}"
335
- context: "${vars.tasks_root}/CONTEXT.md"
336
-
337
- # ── Reference Docs ────────────────────────────────────────────────────
338
- # Docs that tasks can reference in their "Context to Read First" section.
339
- # Add your project's architecture docs, API specs, etc.
340
-
341
- reference_docs: {}
342
-
343
- # ── Standards ─────────────────────────────────────────────────────────
344
- # Coding standards and rules. Agents follow these during implementation.
345
-
346
- standards: {}
347
-
348
- # ── Testing ───────────────────────────────────────────────────────────
349
- # Commands that agents run to verify their work.
350
-
351
- testing:
352
- commands:${vars.test_cmd ? `\n unit: "${vars.test_cmd}"` : ""}${vars.build_cmd ? `\n build: "${vars.build_cmd}"` : ""}
353
- `;
354
- }
355
-
356
- function generateOrchestratorYaml(vars) {
357
- return `# ═══════════════════════════════════════════════════════════════════════
358
- # Parallel Task Orchestrator Configuration — ${vars.project_name}
359
- # ═══════════════════════════════════════════════════════════════════════
360
- #
361
- # This file configures the /orch commands (task-orchestrator extension).
362
- # Edit freely — this file is owned by you, not the package.
363
-
364
- orchestrator:
365
- max_lanes: ${vars.max_lanes}
366
- worktree_location: "subdirectory"
367
- worktree_prefix: "${vars.worktree_prefix}"
368
- batch_id_format: "timestamp"
369
- spawn_mode: "${vars.spawn_mode}"
370
- session_prefix: "${vars.session_prefix}"
371
-
372
- dependencies:
373
- source: "prompt"
374
- cache: true
375
-
376
- assignment:
377
- strategy: "affinity-first"
378
- size_weights:
379
- S: 1
380
- M: 2
381
- L: 4
382
-
383
- pre_warm:
384
- auto_detect: false
385
- commands: {}
386
- always: []
387
-
388
- merge:
389
- model: ""
390
- tools: "read,write,edit,bash,grep,find,ls"
391
- verify: []
392
- order: "fewest-files-first"
393
-
394
- failure:
395
- on_task_failure: "skip-dependents"
396
- on_merge_failure: "pause"
397
- stall_timeout: 30
398
- max_worker_minutes: 30
399
- abort_grace_period: 60
400
-
401
- monitoring:
402
- poll_interval: 5
403
- `;
404
- }
405
-
406
319
  function buildTestingCommands(vars) {
407
320
  const commands = {};
408
321
  if (vars.test_cmd) commands.unit = vars.test_cmd;
@@ -964,6 +877,27 @@ async function autoCommitTaskFiles(projectRoot, tasksRoot) {
964
877
  }
965
878
 
966
879
  function discoverTaskAreaMetadata(projectRoot, configRoot = projectRoot, configPrefix = ".pi") {
880
+ // Prefer taskplane-config.json; fall back to task-runner.yaml for legacy projects
881
+ const jsonPath = path.join(configRoot, configPrefix, "taskplane-config.json");
882
+ if (fs.existsSync(jsonPath)) {
883
+ try {
884
+ const config = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
885
+ const areas = config?.taskRunner?.taskAreas;
886
+ if (areas && typeof areas === "object" && !Array.isArray(areas)) {
887
+ const paths = new Set();
888
+ const contexts = new Set();
889
+ const areaRepoIds = {};
890
+ for (const [areaName, area] of Object.entries(areas)) {
891
+ if (!area || typeof area !== "object") continue;
892
+ if (typeof area.path === "string" && area.path) paths.add(area.path);
893
+ if (typeof area.context === "string" && area.context) contexts.add(area.context);
894
+ if (typeof area.repoId === "string" && area.repoId) areaRepoIds[areaName] = area.repoId;
895
+ }
896
+ return { paths: [...paths], contexts: [...contexts], areaRepoIds };
897
+ }
898
+ } catch { /* fall through to YAML */ }
899
+ }
900
+
967
901
  const runnerPath = path.join(configRoot, configPrefix, "task-runner.yaml");
968
902
  if (!fs.existsSync(runnerPath)) return { paths: [], contexts: [], areaRepoIds: {} };
969
903
 
@@ -1132,9 +1066,10 @@ async function cmdUninstall(args) {
1132
1066
  console.log(`\n${c.bold}Taskplane Uninstall${c.reset}\n`);
1133
1067
 
1134
1068
  const managedFiles = [
1069
+ ".pi/taskplane-config.json",
1070
+ ".pi/taskplane.json",
1135
1071
  ".pi/task-runner.yaml",
1136
1072
  ".pi/task-orchestrator.yaml",
1137
- ".pi/taskplane.json",
1138
1073
  ".pi/agents/task-worker.md",
1139
1074
  ".pi/agents/task-reviewer.md",
1140
1075
  ".pi/agents/task-merger.md",
@@ -1194,7 +1129,7 @@ async function cmdUninstall(args) {
1194
1129
  for (const f of sidecarsToDelete) console.log(` - remove ${f.rel}`);
1195
1130
  for (const d of taskDirsToDelete) console.log(` - remove dir ${d.rel}`);
1196
1131
  if (removeTasks && taskDirsToDelete.length === 0) {
1197
- console.log(` ${c.dim}No task area directories found from .pi/task-runner.yaml.${c.reset}`);
1132
+ console.log(` ${c.dim}No task area directories found in config.${c.reset}`);
1198
1133
  }
1199
1134
  if (!removeTasks) {
1200
1135
  console.log(` ${c.dim}Task directories are preserved by default (use --remove-tasks to delete them).${c.reset}`);
@@ -1637,6 +1572,10 @@ async function cmdInit(args) {
1637
1572
  const tasksRootIdx = args.indexOf("--tasks-root");
1638
1573
  const tasksRootRaw = tasksRootIdx !== -1 ? args[tasksRootIdx + 1] : null;
1639
1574
 
1575
+ if (preset && preset !== "minimal" && preset !== "full") {
1576
+ die(`Unknown preset: "${preset}". Valid presets are: minimal, full`);
1577
+ }
1578
+
1640
1579
  if (noExamplesFlag && includeExamples) {
1641
1580
  die("Choose either --no-examples or --include-examples, not both.");
1642
1581
  }
@@ -1670,7 +1609,7 @@ async function cmdInit(args) {
1670
1609
 
1671
1610
  // ── Mode auto-detection ──────────────────────────────────────
1672
1611
  const detection = detectInitMode(projectRoot);
1673
- const isPreset = preset === "minimal" || preset === "full" || preset === "runner-only";
1612
+ const isPreset = preset === "minimal" || preset === "full";
1674
1613
 
1675
1614
  // Error path: not a git repo and no git repos found
1676
1615
  if (detection.mode === "error") {
@@ -1911,7 +1850,7 @@ async function cmdInit(args) {
1911
1850
 
1912
1851
  // ── Gather config values (workspace mode) ───────────────────
1913
1852
  let vars;
1914
- if (preset === "minimal" || preset === "full" || preset === "runner-only") {
1853
+ if (preset === "minimal" || preset === "full") {
1915
1854
  vars = getPresetVars(preset, projectRoot, tasksRootOverride);
1916
1855
  console.log(` Using preset: ${c.cyan}${preset}${c.reset}`);
1917
1856
  if (tasksRootOverride) {
@@ -1955,22 +1894,6 @@ async function cmdInit(args) {
1955
1894
  );
1956
1895
  }
1957
1896
 
1958
- // Task runner config
1959
- writeFile(
1960
- path.join(taskplaneDir, "task-runner.yaml"),
1961
- generateTaskRunnerYaml(vars),
1962
- { skipIfExists, label: `${configRepoName}/.taskplane/task-runner.yaml` }
1963
- );
1964
-
1965
- // Orchestrator config (skip for runner-only preset)
1966
- if (preset !== "runner-only") {
1967
- writeFile(
1968
- path.join(taskplaneDir, "task-orchestrator.yaml"),
1969
- generateOrchestratorYaml(vars),
1970
- { skipIfExists, label: `${configRepoName}/.taskplane/task-orchestrator.yaml` }
1971
- );
1972
- }
1973
-
1974
1897
  // Project config JSON (taskplane-config.json)
1975
1898
  const projectConfig = generateProjectConfig(vars, initAgentConfig);
1976
1899
  writeFile(
@@ -2109,10 +2032,8 @@ async function cmdInit(args) {
2109
2032
  console.log(` git push && ${c.dim}[create PR / merge to default branch]${c.reset}\n`);
2110
2033
  console.log(`${c.bold}Quick start:${c.reset}`);
2111
2034
  console.log(` ${c.cyan}pi${c.reset} # start pi (taskplane auto-loads)`);
2112
- if (preset !== "runner-only") {
2113
- console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2114
- console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2115
- }
2035
+ console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2036
+ console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2116
2037
  if (inferTaskplaneInstallScope() === "global") {
2117
2038
  console.log(` ${c.cyan}taskplane config --save-as-defaults${c.reset} # save these agent defaults for future inits`);
2118
2039
  }
@@ -2139,7 +2060,7 @@ async function cmdInit(args) {
2139
2060
 
2140
2061
  // Gather config values
2141
2062
  let vars;
2142
- if (preset === "minimal" || preset === "full" || preset === "runner-only") {
2063
+ if (preset === "minimal" || preset === "full") {
2143
2064
  vars = getPresetVars(preset, projectRoot, tasksRootOverride);
2144
2065
  console.log(` Using preset: ${c.cyan}${preset}${c.reset}`);
2145
2066
  if (tasksRootOverride) {
@@ -2180,23 +2101,7 @@ async function cmdInit(args) {
2180
2101
  );
2181
2102
  }
2182
2103
 
2183
- // Task runner config
2184
- writeFile(
2185
- path.join(projectRoot, ".pi", "task-runner.yaml"),
2186
- generateTaskRunnerYaml(vars),
2187
- { skipIfExists, label: ".pi/task-runner.yaml" }
2188
- );
2189
-
2190
- // Orchestrator config (skip for runner-only preset)
2191
- if (preset !== "runner-only") {
2192
- writeFile(
2193
- path.join(projectRoot, ".pi", "task-orchestrator.yaml"),
2194
- generateOrchestratorYaml(vars),
2195
- { skipIfExists, label: ".pi/task-orchestrator.yaml" }
2196
- );
2197
- }
2198
-
2199
- // Unified project config JSON
2104
+ // Project config JSON
2200
2105
  writeFile(
2201
2106
  path.join(projectRoot, ".pi", "taskplane-config.json"),
2202
2107
  JSON.stringify(generateProjectConfig(vars, initAgentConfig), null, 2) + "\n",
@@ -2271,10 +2176,8 @@ async function cmdInit(args) {
2271
2176
  console.log(`\n${OK} ${c.bold}Taskplane initialized!${c.reset}\n`);
2272
2177
  console.log(`${c.bold}Quick start:${c.reset}`);
2273
2178
  console.log(` ${c.cyan}pi${c.reset} # start pi (taskplane auto-loads)`);
2274
- if (preset !== "runner-only") {
2275
- console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2276
- console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2277
- }
2179
+ console.log(` ${c.cyan}/orch${c.reset} # start the taskplane supervisor`);
2180
+ console.log(` ${c.cyan}/orch all${c.reset} # run all open tasks`);
2278
2181
  if (inferTaskplaneInstallScope() === "global") {
2279
2182
  console.log(` ${c.cyan}taskplane config --save-as-defaults${c.reset} # save these agent defaults for future inits`);
2280
2183
  }
@@ -2341,10 +2244,8 @@ function printFileList(vars, noExamples, preset, exampleTemplateDirs = [], proje
2341
2244
  ".pi/agents/task-reviewer.md",
2342
2245
  ".pi/agents/task-merger.md",
2343
2246
  ".pi/agents/supervisor.md",
2344
- ".pi/task-runner.yaml",
2247
+ ".pi/taskplane-config.json",
2345
2248
  ];
2346
- if (preset !== "runner-only") files.push(".pi/task-orchestrator.yaml");
2347
- files.push(".pi/taskplane-config.json");
2348
2249
  files.push(".pi/taskplane.json");
2349
2250
  files.push(`${vars.tasks_root}/CONTEXT.md`);
2350
2251
  if (!noExamples) {
@@ -2380,10 +2281,8 @@ function printWorkspaceFileList(vars, noExamples, preset, exampleTemplateDirs, c
2380
2281
  `${prefix}/agents/task-reviewer.md`,
2381
2282
  `${prefix}/agents/task-merger.md`,
2382
2283
  `${prefix}/agents/supervisor.md`,
2383
- `${prefix}/task-runner.yaml`,
2284
+ `${prefix}/taskplane-config.json`,
2384
2285
  ];
2385
- if (preset !== "runner-only") files.push(`${prefix}/task-orchestrator.yaml`);
2386
- files.push(`${prefix}/taskplane-config.json`);
2387
2286
  files.push(`${prefix}/taskplane.json`);
2388
2287
  files.push(`${prefix}/workspace.json`);
2389
2288
  files.push(`${configRepoName}/${vars.tasks_root}/CONTEXT.md`);
@@ -2883,15 +2782,20 @@ function cmdDoctor() {
2883
2782
  // Check project config (common — both modes)
2884
2783
  console.log();
2885
2784
  const hasUnifiedJson = fs.existsSync(path.join(configLocation.root, configLocation.prefix, "taskplane-config.json"));
2785
+ const hasYamlFallback = !hasUnifiedJson && (
2786
+ fs.existsSync(path.join(configLocation.root, configLocation.prefix, "task-runner.yaml")) ||
2787
+ fs.existsSync(path.join(configLocation.root, configLocation.prefix, "task-orchestrator.yaml"))
2788
+ );
2886
2789
  const configFiles = [
2887
- { path: "taskplane-config.json", required: false, hide: false },
2888
- // YAML configs are legacy fallback — hide when taskplane-config.json exists
2889
- { path: "task-runner.yaml", required: !hasUnifiedJson, hide: hasUnifiedJson },
2890
- { path: "task-orchestrator.yaml", required: !hasUnifiedJson, hide: hasUnifiedJson },
2790
+ // JSON is required unless legacy YAML exists as fallback
2791
+ { path: "taskplane-config.json", required: !hasYamlFallback, hide: false },
2792
+ // YAML configs are legacy fallback only shown when JSON config is missing
2793
+ { path: "task-runner.yaml", required: false, hide: hasUnifiedJson },
2794
+ { path: "task-orchestrator.yaml", required: false, hide: hasUnifiedJson },
2891
2795
  { path: "agents/task-worker.md", required: true, hide: false },
2892
2796
  { path: "agents/task-reviewer.md", required: true, hide: false },
2893
2797
  { path: "agents/task-merger.md", required: true, hide: false },
2894
- // supervisor.md is created by /orch; taskplane.json is created at runtime
2798
+ // supervisor.md is optional (scaffolded by init but may be absent in older projects); taskplane.json is created at runtime
2895
2799
  { path: "agents/supervisor.md", required: false, hide: true },
2896
2800
  { path: "taskplane.json", required: false, hide: true },
2897
2801
  ];
@@ -2979,7 +2883,7 @@ function cmdDoctor() {
2979
2883
  console.log(` ${OK} area '${areaName}' repo_id: ${repoId}`);
2980
2884
  } else {
2981
2885
  console.log(` ${FAIL} area '${areaName}' repo_id '${repoId}' does not match any workspace repo [AREA_REPO_ID_UNKNOWN]`);
2982
- console.log(` ${c.dim}→ Available repos: ${knownRepoIds.join(", ")}. Fix repo_id in ${configLocation.label}/task-runner.yaml${c.reset}`);
2886
+ console.log(` ${c.dim}→ Available repos: ${knownRepoIds.join(", ")}. Fix repoId in ${configLocation.label}/taskplane-config.json${c.reset}`);
2983
2887
  issues++;
2984
2888
  }
2985
2889
  }
@@ -3239,7 +3143,7 @@ ${c.bold}Commands:${c.reset}
3239
3143
  ${c.cyan}help${c.reset} Show this help message
3240
3144
 
3241
3145
  ${c.bold}Init options:${c.reset}
3242
- --preset <name> Use a preset: minimal, full, runner-only
3146
+ --preset <name> Use a preset: minimal, full
3243
3147
  --tasks-root <path> Relative tasks directory to use (e.g. docs/task-management)
3244
3148
  --no-examples Skip example tasks scaffolding
3245
3149
  --include-examples With --tasks-root, include example tasks (default is skip)
@@ -3261,7 +3165,7 @@ ${c.bold}Uninstall options:${c.reset}
3261
3165
  --package-only Only remove installed package (skip project cleanup)
3262
3166
  --local Force package uninstall from project-local scope
3263
3167
  --global Force package uninstall from global scope
3264
- --remove-tasks Also remove task area directories from task-runner.yaml
3168
+ --remove-tasks Also remove task area directories
3265
3169
  --all Equivalent to --package + --remove-tasks
3266
3170
 
3267
3171
  ${c.bold}Examples:${c.reset}
@@ -180,9 +180,9 @@ function showCopyToast(text) {
180
180
  }
181
181
 
182
182
  function copySessionId(sessionName) {
183
+ // Retained for potential future use but no longer rendered in the UI.
183
184
  navigator.clipboard.writeText(sessionName).then(() => {
184
185
  showCopyToast(`session ${sessionName}`);
185
- // Flash the button
186
186
  const btn = document.querySelector(`[data-session="${sessionName}"]`);
187
187
  if (btn) {
188
188
  btn.classList.add("copied");
@@ -649,7 +649,7 @@ function renderLanesTasks(batch, sessions) {
649
649
  const laneSessionId = lane.laneSessionId;
650
650
  const v2Alive = isLaneAliveV2(lane.laneNumber);
651
651
  const alive = v2Alive !== null ? v2Alive : sessionSet.has(laneSessionId);
652
- const sessionChip = `session: ${laneSessionId}`;
652
+
653
653
 
654
654
  // Lane header
655
655
  html += `<div class="lane-group">`;
@@ -670,11 +670,7 @@ function renderLanesTasks(batch, sessions) {
670
670
  // View button: shows conversation stream when available
671
671
  const isViewingConv = viewerMode === 'conversation' && viewerTarget === laneSessionId;
672
672
  html += ` <button class="session-view-btn${isViewingConv ? ' active' : ''}" onclick="viewConversation('${escapeHtml(laneSessionId)}')" title="View worker conversation">👁 View</button>`;
673
- if (alive) {
674
- html += ` <span class="session-cmd" data-session="${escapeHtml(laneSessionId)}" onclick="copySessionId('${escapeHtml(laneSessionId)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span>`;
675
- } else {
676
- html += ` <span class="session-cmd dead-session">${escapeHtml(sessionChip)}</span>`;
677
- }
673
+
678
674
  html += ` </div>`;
679
675
  html += `</div>`;
680
676
 
@@ -999,12 +995,7 @@ function renderMergeAgents(batch, sessions) {
999
995
  // Full telemetry cell
1000
996
  html += `<td class="merge-telemetry-cell">${mergeTelemetryHtml(mergeTel, effectiveAlive)}</td>`;
1001
997
  html += `<td>`;
1002
- if (effectiveAlive) {
1003
- const sessionChip = `session: ${effectiveSession}`;
1004
- html += `<span class="session-cmd" data-session="${escapeHtml(effectiveSession)}" onclick="copySessionId('${escapeHtml(effectiveSession)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span>`;
1005
- } else {
1006
- html += '<span class="merge-no-data">—</span>';
1007
- }
998
+ html += '<span class="merge-no-data">—</span>';
1008
999
  html += `</td>`;
1009
1000
  html += `<td class="merge-detail-cell">${mr.failureReason ? escapeHtml(mr.failureReason) : "—"}</td>`;
1010
1001
  html += `</tr>`;
@@ -1039,14 +1030,13 @@ function renderMergeAgents(batch, sessions) {
1039
1030
  if (shownSessions.has(sess)) continue;
1040
1031
 
1041
1032
  const sessTel = telemetry[sess] || null;
1042
- const sessionChip = `session: ${sess}`;
1043
1033
  html += `<tr>`;
1044
1034
  html += `<td class="merge-wave-cell">—</td>`;
1045
1035
  html += `<td><span class="status-badge status-running"><span class="status-dot running"></span> merging</span></td>`;
1046
1036
  html += `<td class="merge-session-cell">${escapeHtml(sess)}</td>`;
1047
1037
  // Full telemetry cell for active merge session
1048
1038
  html += `<td class="merge-telemetry-cell">${mergeTelemetryHtml(sessTel, true)}</td>`;
1049
- html += `<td><span class="session-cmd" data-session="${escapeHtml(sess)}" onclick="copySessionId('${escapeHtml(sess)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span></td>`;
1039
+ html += `<td>—</td>`;
1050
1040
  html += `<td>—</td>`;
1051
1041
  html += `</tr>`;
1052
1042
  }
@@ -16,6 +16,7 @@
16
16
  * @module orch/engine-worker
17
17
  */
18
18
  import type {
19
+ AllocatedLane,
19
20
  EngineEvent,
20
21
  MonitorState,
21
22
  OrchBatchPhase,
@@ -71,6 +72,8 @@ export interface SerializedBatchState {
71
72
  startedAt: number;
72
73
  endedAt: number | null;
73
74
  errors: string[];
75
+ /** Active lanes for the current wave (synced so /orch-sessions works). */
76
+ currentLanes: AllocatedLane[];
74
77
  }
75
78
 
76
79
  /**
@@ -164,6 +167,7 @@ function serializeBatchState(state: OrchBatchRuntimeState): SerializedBatchState
164
167
  startedAt: state.startedAt,
165
168
  endedAt: state.endedAt,
166
169
  errors: [...state.errors],
170
+ currentLanes: state.currentLanes,
167
171
  };
168
172
  }
169
173
 
@@ -192,6 +196,7 @@ export function applySerializedState(
192
196
  batchState.startedAt = serialized.startedAt;
193
197
  batchState.endedAt = serialized.endedAt;
194
198
  batchState.errors = [...serialized.errors];
199
+ batchState.currentLanes = serialized.currentLanes ?? [];
195
200
  }
196
201
 
197
202
  // ── Engine main (runs when launched as a forked child process) ───────
@@ -4720,7 +4720,39 @@ export default function (pi: ExtensionAPI) {
4720
4720
  if (!requireExecCtx(ctx)) return;
4721
4721
 
4722
4722
  try {
4723
- await openSettingsTui(ctx, execCtx!.workspaceRoot, execCtx!.pointer?.configRoot);
4723
+ // Capture the workspace root for reload consistency — use the same
4724
+ // root the settings TUI writes to, not ctx.cwd which may differ.
4725
+ const reloadCwd = execCtx!.workspaceRoot;
4726
+ await openSettingsTui(ctx, execCtx!.workspaceRoot, execCtx!.pointer?.configRoot, () => {
4727
+ // Reload live config from disk so changes take effect immediately
4728
+ // without requiring a session restart.
4729
+ // Build everything into temporaries first, then commit atomically
4730
+ // so a partial failure doesn't leave mixed-generation state.
4731
+ try {
4732
+ const freshCtx = buildExecutionContext(reloadCwd, loadOrchestratorConfig, loadTaskRunnerConfig);
4733
+ let freshSupervisor: SupervisorConfig;
4734
+ try {
4735
+ freshSupervisor = loadSupervisorConfig(
4736
+ freshCtx.repoRoot,
4737
+ freshCtx.pointer?.configRoot,
4738
+ );
4739
+ } catch {
4740
+ freshSupervisor = { ...DEFAULT_SUPERVISOR_CONFIG };
4741
+ }
4742
+ // Atomic commit — all or nothing
4743
+ execCtx = freshCtx;
4744
+ orchConfig = freshCtx.orchestratorConfig;
4745
+ runnerConfig = freshCtx.taskRunnerConfig;
4746
+ supervisorConfig = freshSupervisor;
4747
+ } catch {
4748
+ // Non-fatal — config was saved to disk but live reload failed.
4749
+ // Existing in-memory config is preserved unchanged.
4750
+ ctx.ui.notify(
4751
+ "⚠️ Saved to disk but live reload failed. Restart to apply.",
4752
+ "warn",
4753
+ );
4754
+ }
4755
+ });
4724
4756
  } catch (err: any) {
4725
4757
  ctx.ui.notify(`❌ Failed to load settings: ${err.message}`, "error");
4726
4758
  }
@@ -98,34 +98,36 @@ export const SECTIONS: SectionDef[] = [
98
98
  { configPath: "orchestrator.orchestrator.worktreeLocation", label: "Worktree Location", control: "toggle", layer: "L1", fieldType: "enum", values: ["sibling", "subdirectory"], description: "Where lane worktree directories are created" },
99
99
  { configPath: "orchestrator.orchestrator.worktreePrefix", label: "Worktree Prefix", control: "input", layer: "L1", fieldType: "string", description: "Prefix for worktree directory names" },
100
100
  { configPath: "orchestrator.orchestrator.batchIdFormat", label: "Batch ID Format", control: "toggle", layer: "L1", fieldType: "enum", values: ["timestamp", "sequential"], description: "Batch ID format for logs/branch naming" },
101
- // spawn_mode removed from Orchestrator section — Runtime V2 is subprocess-only.
102
- // The user-facing spawn mode setting is under Worker (controls /task behavior).
103
101
  { configPath: "orchestrator.orchestrator.sessionPrefix", label: "Session Prefix", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "sessionPrefix", description: "Prefix for orchestrator session names" },
104
102
  { configPath: "orchestrator.orchestrator.operatorId", label: "Operator ID", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "operatorId", description: "Operator identifier (empty = auto-detect)" },
105
103
  { configPath: "orchestrator.orchestrator.integration", label: "Integration", control: "picker", layer: "L1", fieldType: "enum", values: ["manual", "supervised", "auto"], description: "How completed batches are integrated. manual = user runs /orch-integrate. supervised = supervisor proposes plan, asks confirmation. auto = supervisor executes without asking." },
106
104
  ],
107
105
  },
108
106
  {
109
- name: "Dependencies",
107
+ name: "Agent: Supervisor",
110
108
  fields: [
111
- { configPath: "orchestrator.dependencies.source", label: "Dep Source", control: "toggle", layer: "L1", fieldType: "enum", values: ["prompt", "agent"], description: "Dependency extraction source" },
112
- { configPath: "orchestrator.dependencies.cache", label: "Dep Cache", control: "toggle", layer: "L1", fieldType: "boolean", values: ["true", "false"], description: "Cache dependency analysis results" },
109
+ { configPath: "orchestrator.supervisor.model", label: "Supervisor Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "supervisorModel", description: "Supervisor model (inherit = use session model)" },
110
+ { configPath: "orchestrator.supervisor.autonomy", label: "Autonomy Level", control: "picker", layer: "L1", fieldType: "enum", values: ["interactive", "supervised", "autonomous"], description: "Recovery action confirmation behavior" },
113
111
  ],
114
112
  },
115
113
  {
116
- name: "Assignment",
114
+ name: "Agent: Worker",
117
115
  fields: [
118
- { configPath: "orchestrator.assignment.strategy", label: "Strategy", control: "toggle", layer: "L1", fieldType: "enum", values: ["affinity-first", "round-robin", "load-balanced"], description: "Lane assignment strategy" },
116
+ { configPath: "taskRunner.worker.model", label: "Worker Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "workerModel", description: "Worker model (inherit = use session model)" },
117
+ { configPath: "taskRunner.worker.tools", label: "Worker Tools", control: "input", layer: "L1", fieldType: "string", description: "Worker tool allowlist" },
118
+ { configPath: "taskRunner.worker.thinking", label: "Worker Thinking", control: "picker", layer: "L1", fieldType: "string", description: "Worker thinking mode" },
119
119
  ],
120
120
  },
121
121
  {
122
- name: "Pre-Warm",
122
+ name: "Agent: Reviewer",
123
123
  fields: [
124
- { configPath: "orchestrator.preWarm.autoDetect", label: "Auto-Detect", control: "toggle", layer: "L1", fieldType: "boolean", values: ["true", "false"], description: "Enable automatic pre-warm command detection" },
124
+ { configPath: "taskRunner.reviewer.model", label: "Reviewer Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "reviewerModel", description: "Reviewer model (inherit = use session model)" },
125
+ { configPath: "taskRunner.reviewer.tools", label: "Reviewer Tools", control: "input", layer: "L1", fieldType: "string", description: "Reviewer tool allowlist" },
126
+ { configPath: "taskRunner.reviewer.thinking", label: "Reviewer Thinking", control: "picker", layer: "L1", fieldType: "string", description: "Reviewer thinking mode" },
125
127
  ],
126
128
  },
127
129
  {
128
- name: "Merge",
130
+ name: "Agent: Merge",
129
131
  fields: [
130
132
  { configPath: "orchestrator.merge.model", label: "Merge Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "mergeModel", description: "Merge-agent model (inherit = use session model)" },
131
133
  { configPath: "orchestrator.merge.tools", label: "Merge Tools", control: "input", layer: "L1", fieldType: "string", description: "Merge-agent tool allowlist" },
@@ -134,6 +136,18 @@ export const SECTIONS: SectionDef[] = [
134
136
  { configPath: "orchestrator.merge.timeoutMinutes", label: "Merge Timeout (minutes)", control: "input", layer: "L1", fieldType: "number", description: "Max time for merge agent to complete. Increase for large batches (default: 10)" },
135
137
  ],
136
138
  },
139
+ {
140
+ name: "Context Limits",
141
+ fields: [
142
+ { configPath: "taskRunner.context.workerContextWindow", label: "Context Window", control: "input", layer: "L1", fieldType: "number", description: "Worker context window size" },
143
+ { configPath: "taskRunner.context.warnPercent", label: "Warn %", control: "input", layer: "L1", fieldType: "number", description: "Context utilization warn threshold (%)" },
144
+ { configPath: "taskRunner.context.killPercent", label: "Kill %", control: "input", layer: "L1", fieldType: "number", description: "Context utilization hard-stop threshold (%)" },
145
+ { configPath: "taskRunner.context.maxWorkerIterations", label: "Max Iterations", control: "input", layer: "L1", fieldType: "number", description: "Max worker iterations per step" },
146
+ { configPath: "taskRunner.context.maxReviewCycles", label: "Max Review Cycles", control: "input", layer: "L1", fieldType: "number", description: "Max revise loops per review stage" },
147
+ { configPath: "taskRunner.context.noProgressLimit", label: "No Progress Limit", control: "input", layer: "L1", fieldType: "number", description: "Max no-progress iterations before failure" },
148
+ { configPath: "taskRunner.context.maxWorkerMinutes", label: "Max Worker Min (ctx)", control: "input", layer: "L1", fieldType: "number", optional: true, description: "Per-worker wall-clock cap (minutes, empty = no cap)" },
149
+ ],
150
+ },
137
151
  {
138
152
  name: "Failure Policy",
139
153
  fields: [
@@ -145,46 +159,28 @@ export const SECTIONS: SectionDef[] = [
145
159
  ],
146
160
  },
147
161
  {
148
- name: "Monitoring",
149
- fields: [
150
- { configPath: "orchestrator.monitoring.pollInterval", label: "Poll Interval (sec)", control: "input", layer: "L1", fieldType: "number", description: "Poll interval for lane/task monitoring (seconds)" },
151
- ],
152
- },
153
- {
154
- name: "Supervisor",
162
+ name: "Dependencies",
155
163
  fields: [
156
- { configPath: "orchestrator.supervisor.model", label: "Supervisor Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "supervisorModel", description: "Supervisor model (inherit = use session model)" },
157
- { configPath: "orchestrator.supervisor.autonomy", label: "Autonomy Level", control: "picker", layer: "L1", fieldType: "enum", values: ["interactive", "supervised", "autonomous"], description: "Recovery action confirmation behavior" },
164
+ { configPath: "orchestrator.dependencies.source", label: "Dep Source", control: "toggle", layer: "L1", fieldType: "enum", values: ["prompt", "agent"], description: "Dependency extraction source" },
165
+ { configPath: "orchestrator.dependencies.cache", label: "Dep Cache", control: "toggle", layer: "L1", fieldType: "boolean", values: ["true", "false"], description: "Cache dependency analysis results" },
158
166
  ],
159
167
  },
160
168
  {
161
- name: "Worker",
169
+ name: "Assignment",
162
170
  fields: [
163
- { configPath: "taskRunner.worker.model", label: "Worker Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "workerModel", description: "Worker model (inherit = use session model)" },
164
- { configPath: "taskRunner.worker.tools", label: "Worker Tools", control: "input", layer: "L1", fieldType: "string", description: "Worker tool allowlist" },
165
- { configPath: "taskRunner.worker.thinking", label: "Worker Thinking", control: "picker", layer: "L1", fieldType: "string", description: "Worker thinking mode" },
166
- // spawnMode removed — /task is deprecated, Runtime V2 is subprocess-only.
167
- // { configPath: "taskRunner.worker.spawnMode" ... } was here.
171
+ { configPath: "orchestrator.assignment.strategy", label: "Strategy", control: "toggle", layer: "L1", fieldType: "enum", values: ["affinity-first", "round-robin", "load-balanced"], description: "Lane assignment strategy" },
168
172
  ],
169
173
  },
170
174
  {
171
- name: "Reviewer",
175
+ name: "Pre-Warm",
172
176
  fields: [
173
- { configPath: "taskRunner.reviewer.model", label: "Reviewer Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "reviewerModel", description: "Reviewer model (inherit = use session model)" },
174
- { configPath: "taskRunner.reviewer.tools", label: "Reviewer Tools", control: "input", layer: "L1", fieldType: "string", description: "Reviewer tool allowlist" },
175
- { configPath: "taskRunner.reviewer.thinking", label: "Reviewer Thinking", control: "picker", layer: "L1", fieldType: "string", description: "Reviewer thinking mode" },
177
+ { configPath: "orchestrator.preWarm.autoDetect", label: "Auto-Detect", control: "toggle", layer: "L1", fieldType: "boolean", values: ["true", "false"], description: "Enable automatic pre-warm command detection" },
176
178
  ],
177
179
  },
178
180
  {
179
- name: "Context Limits",
181
+ name: "Monitoring",
180
182
  fields: [
181
- { configPath: "taskRunner.context.workerContextWindow", label: "Context Window", control: "input", layer: "L1", fieldType: "number", description: "Worker context window size" },
182
- { configPath: "taskRunner.context.warnPercent", label: "Warn %", control: "input", layer: "L1", fieldType: "number", description: "Context utilization warn threshold (%)" },
183
- { configPath: "taskRunner.context.killPercent", label: "Kill %", control: "input", layer: "L1", fieldType: "number", description: "Context utilization hard-stop threshold (%)" },
184
- { configPath: "taskRunner.context.maxWorkerIterations", label: "Max Iterations", control: "input", layer: "L1", fieldType: "number", description: "Max worker iterations per step" },
185
- { configPath: "taskRunner.context.maxReviewCycles", label: "Max Review Cycles", control: "input", layer: "L1", fieldType: "number", description: "Max revise loops per review stage" },
186
- { configPath: "taskRunner.context.noProgressLimit", label: "No Progress Limit", control: "input", layer: "L1", fieldType: "number", description: "Max no-progress iterations before failure" },
187
- { configPath: "taskRunner.context.maxWorkerMinutes", label: "Max Worker Min (ctx)", control: "input", layer: "L1", fieldType: "number", optional: true, description: "Per-worker wall-clock cap (minutes, empty = no cap)" },
183
+ { configPath: "orchestrator.monitoring.pollInterval", label: "Poll Interval (sec)", control: "input", layer: "L1", fieldType: "number", description: "Poll interval for lane/task monitoring (seconds)" },
188
184
  ],
189
185
  },
190
186
  {
@@ -1166,9 +1162,10 @@ export async function openSettingsTui(
1166
1162
  ctx: ExtensionContext,
1167
1163
  configRoot: string,
1168
1164
  pointerConfigRoot?: string,
1165
+ onConfigChanged?: () => void,
1169
1166
  ): Promise<void> {
1170
1167
  // Load current config state — refreshed each time we return to the top level
1171
- await showSectionSelectorLoop(ctx, configRoot, pointerConfigRoot);
1168
+ await showSectionSelectorLoop(ctx, configRoot, pointerConfigRoot, onConfigChanged);
1172
1169
  }
1173
1170
 
1174
1171
  /**
@@ -1200,6 +1197,7 @@ async function showSectionSelectorLoop(
1200
1197
  ctx: ExtensionContext,
1201
1198
  configRoot: string,
1202
1199
  pointerConfigRoot?: string,
1200
+ onConfigChanged?: () => void,
1203
1201
  ): Promise<void> {
1204
1202
  while (true) {
1205
1203
  const state = loadConfigState(configRoot, pointerConfigRoot);
@@ -1257,7 +1255,7 @@ async function showSectionSelectorLoop(
1257
1255
  if (section.readOnly) {
1258
1256
  await showAdvancedSection(ctx, state.mergedConfig);
1259
1257
  } else {
1260
- await showSectionSettingsLoop(ctx, section, configRoot, pointerConfigRoot);
1258
+ await showSectionSettingsLoop(ctx, section, configRoot, pointerConfigRoot, onConfigChanged);
1261
1259
  }
1262
1260
  }
1263
1261
  }
@@ -1339,6 +1337,7 @@ async function showSectionSettingsLoop(
1339
1337
  section: SectionDef,
1340
1338
  configRoot: string,
1341
1339
  pointerConfigRoot?: string,
1340
+ onConfigChanged?: () => void,
1342
1341
  ): Promise<void> {
1343
1342
  while (true) {
1344
1343
  const state = loadConfigState(configRoot, pointerConfigRoot);
@@ -1438,9 +1437,13 @@ async function showSectionSettingsLoop(
1438
1437
  } else {
1439
1438
  writeGlobalPreference(toGlobalPreferencePath(field), typedValue);
1440
1439
  }
1440
+ // Notify caller to reload in-memory config from disk
1441
+ if (onConfigChanged) {
1442
+ try { onConfigChanged(); } catch { /* non-fatal */ }
1443
+ }
1444
+
1441
1445
  ctx.ui.notify(
1442
- `✅ ${field.label} updated.\n` +
1443
- `ℹ Restart session to apply changes.`,
1446
+ `✅ ${field.label} updated.`,
1444
1447
  "info",
1445
1448
  );
1446
1449
 
@@ -1494,9 +1497,38 @@ async function showSectionSettingsOnce(
1494
1497
  description: field.description,
1495
1498
  };
1496
1499
 
1497
- // Toggle fields get values array for cycling
1500
+ // Toggle fields: use cycling for 2 values (boolean-like), submenu for 3+
1498
1501
  if (field.control === "toggle" && field.values) {
1499
- item.values = field.values.map((v) => `${v} ${sourceBadge}`);
1502
+ if (field.values.length <= 2) {
1503
+ item.values = field.values.map((v) => `${v} ${sourceBadge}`);
1504
+ } else {
1505
+ // 3+ values: use a submenu so the user can pick any option,
1506
+ // not just cycle to the next one and immediately commit.
1507
+ item.submenu = (_currentValue: string, done: (selected?: string) => void) => {
1508
+ const selectItems: SelectItem[] = field.values!.map((v) => ({
1509
+ value: `${v} ${sourceBadge}`,
1510
+ label: v,
1511
+ }));
1512
+ // Return SelectList directly — Container doesn't forward
1513
+ // handleInput to children, which would freeze the TUI.
1514
+ const list = new SelectList(
1515
+ selectItems,
1516
+ Math.min(selectItems.length + 1, 10),
1517
+ {
1518
+ selectedPrefix: (t: string) => `\x1b[36m${t}\x1b[0m`,
1519
+ selectedText: (t: string) => `\x1b[36m${t}\x1b[0m`,
1520
+ description: (t: string) => `\x1b[2m${t}\x1b[0m`,
1521
+ scrollInfo: (t: string) => `\x1b[2m${t}\x1b[0m`,
1522
+ noMatch: (t: string) => `\x1b[33m${t}\x1b[0m`,
1523
+ },
1524
+ );
1525
+ const currentIdx = field.values!.indexOf(displayValue);
1526
+ if (currentIdx >= 0) list.setSelectedIndex(currentIdx);
1527
+ list.onSelect = (selected) => done(selected.value);
1528
+ list.onCancel = () => done();
1529
+ return list;
1530
+ };
1531
+ }
1500
1532
  }
1501
1533
 
1502
1534
  // Input fields: use a single-value cycling pattern instead of a submenu.
@@ -1649,15 +1681,16 @@ function truncateLine(text: string, width: number): string {
1649
1681
  */
1650
1682
  const SECTION_CONFIG_PREFIXES: Record<string, string[]> = {
1651
1683
  "Orchestrator": ["orchestrator.orchestrator"],
1684
+ "Agent: Supervisor": ["orchestrator.supervisor"],
1685
+ "Agent: Worker": ["taskRunner.worker"],
1686
+ "Agent: Reviewer": ["taskRunner.reviewer"],
1687
+ "Agent: Merge": ["orchestrator.merge"],
1688
+ "Context Limits": ["taskRunner.context"],
1689
+ "Failure Policy": ["orchestrator.failure"],
1652
1690
  "Dependencies": ["orchestrator.dependencies"],
1653
1691
  "Assignment": ["orchestrator.assignment"],
1654
1692
  "Pre-Warm": ["orchestrator.preWarm"],
1655
- "Merge": ["orchestrator.merge"],
1656
- "Failure Policy": ["orchestrator.failure"],
1657
1693
  "Monitoring": ["orchestrator.monitoring"],
1658
- "Worker": ["taskRunner.worker"],
1659
- "Reviewer": ["taskRunner.reviewer"],
1660
- "Context Limits": ["taskRunner.context"],
1661
1694
  };
1662
1695
 
1663
1696
  /**
@@ -951,7 +951,7 @@ export function triggerSupervisorIntegration(
951
951
  }],
952
952
  display: "Integration plan ready — awaiting operator confirmation",
953
953
  },
954
- { triggerTurn: true, deliverAs: "nextTurn" },
954
+ { triggerTurn: true },
955
955
  );
956
956
 
957
957
  // TP-043 R004: Defer summary until after integration completes (or operator declines).
@@ -2834,7 +2834,11 @@ export async function activateSupervisor(
2834
2834
  ],
2835
2835
  display: `Supervisor activated — ${routingContext.routingState}`,
2836
2836
  },
2837
- { triggerTurn: true, deliverAs: "nextTurn" },
2837
+ // triggerTurn starts an LLM turn immediately when the agent is idle.
2838
+ // Do NOT use deliverAs:"nextTurn" — that queues the message for a
2839
+ // future turn instead of starting one, causing the terminal to hang
2840
+ // until the user sends input.
2841
+ { triggerTurn: true },
2838
2842
  );
2839
2843
  return;
2840
2844
  }
@@ -2886,7 +2890,9 @@ export async function activateSupervisor(
2886
2890
  ],
2887
2891
  display: "Supervisor activated" + (batchState.batchId ? ` for batch ${batchState.batchId}` : ""),
2888
2892
  },
2889
- { triggerTurn: true, deliverAs: "nextTurn" },
2893
+ // triggerTurn starts an LLM turn immediately when the agent is idle.
2894
+ // Do NOT use deliverAs:"nextTurn" here — see routing path comment.
2895
+ { triggerTurn: true },
2890
2896
  );
2891
2897
  }
2892
2898
 
@@ -3032,7 +3038,7 @@ export async function transitionToRoutingMode(
3032
3038
  }],
3033
3039
  display: `Supervisor — ${routingContext.routingState}`,
3034
3040
  },
3035
- { triggerTurn: true, deliverAs: "nextTurn" },
3041
+ { triggerTurn: true },
3036
3042
  );
3037
3043
  }
3038
3044
 
@@ -4241,7 +4247,7 @@ export function startEventTailer(
4241
4247
  content: [{ type: "text", text }],
4242
4248
  display: text.replace(/\*\*/g, "").substring(0, 80),
4243
4249
  },
4244
- { triggerTurn: true, deliverAs: "nextTurn" },
4250
+ { triggerTurn: true },
4245
4251
  );
4246
4252
  };
4247
4253
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.24.31",
3
+ "version": "0.25.0",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -1,93 +0,0 @@
1
- # ═══════════════════════════════════════════════════════════════════════
2
- # Parallel Task Orchestrator Configuration
3
- # ═══════════════════════════════════════════════════════════════════════
4
- #
5
- # Copy this file to `.pi/task-orchestrator.yaml` in your project and
6
- # customize it. The orchestrator reads BOTH this file and task-runner.yaml.
7
- #
8
- # - task-runner.yaml → task areas, reference docs, worker/reviewer
9
- # - task-orchestrator.yaml → lane count, worktrees, merge, failure policy
10
- #
11
- # This template is intentionally conservative so it is safe to adapt to a
12
- # wide range of repositories.
13
- # ═══════════════════════════════════════════════════════════════════════
14
-
15
- # ── Orchestrator Core ─────────────────────────────────────────────────
16
-
17
- orchestrator:
18
- # Maximum parallel lanes (worktrees)
19
- max_lanes: 3
20
-
21
- # Where to create worktree directories.
22
- # "sibling" = ../{prefix}-{opId}-{N} (e.g. ../project-wt-alice-1)
23
- # "subdirectory" = .worktrees/{prefix}-{opId}-{N} (e.g. .worktrees/project-wt-alice-1)
24
- worktree_location: "subdirectory"
25
- worktree_prefix: "project-wt"
26
-
27
- # Batch ID format used in branch names and logs.
28
- batch_id_format: "timestamp"
29
-
30
- # Runtime V2 execution backend (subprocess-only)
31
- spawn_mode: "subprocess"
32
-
33
- # Prefix for orchestrator session names.
34
- session_prefix: "orch"
35
-
36
- # Optional operator identifier for team-scale collision resistance.
37
- # Auto-detected from OS username if empty. Set explicitly in CI or
38
- # when multiple operators share the same machine.
39
- # operator_id: ""
40
-
41
- # ── Dependency Analysis ───────────────────────────────────────────────
42
-
43
- dependencies:
44
- # "prompt" = parse dependencies from PROMPT.md
45
- # "agent" = use an agent to analyze tasks
46
- source: "prompt"
47
- cache: true
48
-
49
- # ── Lane Assignment ───────────────────────────────────────────────────
50
-
51
- assignment:
52
- strategy: "affinity-first"
53
- size_weights:
54
- S: 1
55
- M: 2
56
- L: 4
57
-
58
- # ── Pre-warming ───────────────────────────────────────────────────────
59
-
60
- # Disabled by default. Add commands that fit your stack if you want to use it.
61
- pre_warm:
62
- auto_detect: false
63
- commands: {}
64
- always: []
65
-
66
- # ── Merge ─────────────────────────────────────────────────────────────
67
-
68
- merge:
69
- model: "" # empty = inherit from parent pi session
70
- tools: "read,write,edit,bash,grep,find,ls"
71
-
72
- # Verification commands to run after each merge.
73
- # Add only the commands that are safe and relevant for your project.
74
- verify: []
75
-
76
- order: "fewest-files-first"
77
-
78
- # Merge agent timeout in minutes. Increase for large batches with many files.
79
- timeout_minutes: 90
80
-
81
- # ── Failure Handling ──────────────────────────────────────────────────
82
-
83
- failure:
84
- on_task_failure: "skip-dependents"
85
- on_merge_failure: "pause"
86
- stall_timeout: 30
87
- max_worker_minutes: 30
88
- abort_grace_period: 60
89
-
90
- # ── Monitoring ────────────────────────────────────────────────────────
91
-
92
- monitoring:
93
- poll_interval: 5
@@ -1,94 +0,0 @@
1
- # ═══════════════════════════════════════════════════════════════════════
2
- # Task Runner Configuration
3
- # ═══════════════════════════════════════════════════════════════════════
4
- #
5
- # Copy this file to `.pi/task-runner.yaml` in your project and customize it.
6
- # This template is intentionally generic — replace the example paths, test
7
- # commands, and task areas with ones that fit your repository.
8
- # ═══════════════════════════════════════════════════════════════════════
9
-
10
- # ── Project ───────────────────────────────────────────────────────────
11
-
12
- project:
13
- name: "Your Project"
14
- description: "Short description of your project"
15
-
16
- paths:
17
- tasks: "tasks"
18
- architecture: "docs/architecture.md"
19
-
20
- # ── Verification Commands ─────────────────────────────────────────────
21
-
22
- # Add the commands your project uses for validation. Keep only the ones
23
- # that are relevant to your stack.
24
- testing:
25
- commands:
26
- test: "npm test"
27
- build: "npm run build"
28
- lint: "npm run lint"
29
-
30
- # ── Standards ─────────────────────────────────────────────────────────
31
-
32
- standards:
33
- docs:
34
- - "README.md"
35
- - "CONTRIBUTING.md"
36
- rules:
37
- - "Keep changes scoped to the task"
38
- - "Update documentation when behavior changes"
39
- - "Prefer typed interfaces over unstructured data"
40
- - "Avoid destructive changes unless explicitly requested"
41
-
42
- # Per-area standards overrides. Omit or delete if you do not need them.
43
- standards_overrides: {}
44
-
45
- # ── Runner Settings ───────────────────────────────────────────────────
46
-
47
- worker:
48
- model: "" # empty = inherit from parent pi session
49
- tools: "read,write,edit,bash,grep,find,ls"
50
- thinking: "" # empty = inherit from parent pi session
51
- # spawn_mode: "subprocess" # currently supported runtime mode
52
-
53
- reviewer:
54
- model: ""
55
- tools: "read,write,bash,grep,find,ls"
56
- thinking: "off"
57
-
58
- context:
59
- # worker_context_window: 200000 # 0 or omit = auto-detect from model registry; set explicitly to override
60
- warn_percent: 85
61
- kill_percent: 95
62
- max_worker_iterations: 20
63
- max_review_cycles: 2
64
- no_progress_limit: 3
65
- # max_worker_minutes: 30 # optional wall-clock guard for long worker runs
66
-
67
- # ── Task Creation / Discovery ─────────────────────────────────────────
68
-
69
- # Define the task areas that exist in your project.
70
- task_areas:
71
- general:
72
- path: "taskplane-tasks"
73
- prefix: "TP"
74
- context: "taskplane-tasks/CONTEXT.md"
75
-
76
- # Reference docs available for higher-context task prompts.
77
- reference_docs:
78
- overview: "README.md"
79
- architecture: "docs/architecture.md"
80
- contributing: "CONTRIBUTING.md"
81
-
82
- # Docs that should never be loaded during task execution.
83
- never_load:
84
- - "PROGRESS.md"
85
- - "HANDOFF-LOG.md"
86
-
87
- # Self-documentation targets (where agents should log useful discoveries).
88
- self_doc_targets:
89
- tech_debt: "CONTEXT.md ## Technical Debt / Future Work"
90
-
91
- # Docs requiring explicit user approval to modify.
92
- protected_docs:
93
- - "docs/"
94
- - "templates/"