taskplane 0.24.30 → 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 +41 -48
- package/bin/taskplane.mjs +51 -147
- package/dashboard/public/app.js +32 -18
- package/dashboard/public/style.css +4 -5
- package/dashboard/server.cjs +3 -0
- package/extensions/taskplane/engine-worker.ts +5 -0
- package/extensions/taskplane/engine.ts +160 -6
- package/extensions/taskplane/execution.ts +38 -9
- package/extensions/taskplane/extension.ts +33 -1
- package/extensions/taskplane/lane-runner.ts +46 -1
- package/extensions/taskplane/settings-tui.ts +81 -48
- package/extensions/taskplane/supervisor.ts +74 -40
- package/extensions/taskplane/types.ts +1 -1
- package/extensions/taskplane/waves.ts +108 -2
- package/extensions/taskplane/worktree.ts +114 -0
- package/package.json +1 -1
- package/templates/agents/supervisor.md +1 -1
- package/templates/agents/task-worker.md +7 -0
- package/templates/config/task-orchestrator.yaml +0 -93
- package/templates/config/task-runner.yaml +0 -94
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
|
|
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:**
|
|
5
|
+
> **Status:** Initial release.
|
|
6
6
|
|
|
7
7
|
## What It Does
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
23
|
-
- **Worker-Driven Inline Reviews** — Workers invoke a `review_step` tool at step boundaries. Reviewer agents spawn
|
|
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
|
-
##
|
|
42
|
+
## Installation
|
|
31
43
|
|
|
32
|
-
Taskplane is a
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
│
|
|
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
|
|
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"
|
|
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"
|
|
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
|
-
|
|
2113
|
-
|
|
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"
|
|
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
|
-
//
|
|
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
|
-
|
|
2275
|
-
|
|
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/
|
|
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}/
|
|
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
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
{ path: "task-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}
|
package/dashboard/public/app.js
CHANGED
|
@@ -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");
|
|
@@ -455,6 +455,27 @@ function renderSummary(batch) {
|
|
|
455
455
|
const wavePlan = batch.wavePlan || [tasks.map(t => t.taskId)]; // fallback: single wave
|
|
456
456
|
const currentWaveIdx = batch.currentWaveIndex || 0;
|
|
457
457
|
|
|
458
|
+
// TP-148: Build wave segment context — for each task appearing in multiple waves,
|
|
459
|
+
// determine which segment corresponds to each wave appearance.
|
|
460
|
+
const taskWaveAppearance = new Map(); // taskId → count of appearances so far
|
|
461
|
+
const waveSegmentLabels = wavePlan.map((taskIds) => {
|
|
462
|
+
const labels = new Map(); // taskId → label string
|
|
463
|
+
for (const tid of taskIds) {
|
|
464
|
+
const task = taskMap.get(tid);
|
|
465
|
+
const segmentIds = task?.segmentIds;
|
|
466
|
+
if (!segmentIds || segmentIds.length <= 1) continue;
|
|
467
|
+
const count = (taskWaveAppearance.get(tid) || 0);
|
|
468
|
+
taskWaveAppearance.set(tid, count + 1);
|
|
469
|
+
const segId = segmentIds[count];
|
|
470
|
+
if (segId) {
|
|
471
|
+
const parsed = parseSegmentId(segId);
|
|
472
|
+
const repo = parsed ? parsed.repoId : "";
|
|
473
|
+
labels.set(tid, `${tid} (segment ${count + 1}/${segmentIds.length}: ${repo})`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return labels;
|
|
477
|
+
});
|
|
478
|
+
|
|
458
479
|
// Compute per-wave and overall checkbox totals
|
|
459
480
|
let batchChecked = 0, batchTotal = 0;
|
|
460
481
|
const waveStats = wavePlan.map((taskIds, waveIdx) => {
|
|
@@ -503,7 +524,10 @@ function renderSummary(batch) {
|
|
|
503
524
|
const fillWidth = isDone ? 100 : fillPct;
|
|
504
525
|
const segClass = isCurrent ? "wave-seg-current" : isFuture ? "wave-seg-future" : "";
|
|
505
526
|
|
|
506
|
-
|
|
527
|
+
// TP-148: Use segment-aware labels in tooltip when available
|
|
528
|
+
const segLabels = waveSegmentLabels[ws.waveIdx] || new Map();
|
|
529
|
+
const tooltipTasks = ws.taskIds.map(tid => segLabels.get(tid) || tid).join(', ');
|
|
530
|
+
barHtml += `<div class="wave-seg ${segClass}" style="width:${segWidthPct.toFixed(1)}%" title="W${ws.waveIdx + 1}: ${ws.checked}/${ws.total} checkboxes (${tooltipTasks})">`;
|
|
507
531
|
barHtml += ` <div class="wave-seg-fill ${fillClass}" style="width:${fillWidth.toFixed(1)}%"></div>`;
|
|
508
532
|
barHtml += ` <span class="wave-seg-label">W${ws.waveIdx + 1}</span>`;
|
|
509
533
|
barHtml += `</div>`;
|
|
@@ -625,7 +649,7 @@ function renderLanesTasks(batch, sessions) {
|
|
|
625
649
|
const laneSessionId = lane.laneSessionId;
|
|
626
650
|
const v2Alive = isLaneAliveV2(lane.laneNumber);
|
|
627
651
|
const alive = v2Alive !== null ? v2Alive : sessionSet.has(laneSessionId);
|
|
628
|
-
|
|
652
|
+
|
|
629
653
|
|
|
630
654
|
// Lane header
|
|
631
655
|
html += `<div class="lane-group">`;
|
|
@@ -646,11 +670,7 @@ function renderLanesTasks(batch, sessions) {
|
|
|
646
670
|
// View button: shows conversation stream when available
|
|
647
671
|
const isViewingConv = viewerMode === 'conversation' && viewerTarget === laneSessionId;
|
|
648
672
|
html += ` <button class="session-view-btn${isViewingConv ? ' active' : ''}" onclick="viewConversation('${escapeHtml(laneSessionId)}')" title="View worker conversation">👁 View</button>`;
|
|
649
|
-
|
|
650
|
-
html += ` <span class="session-cmd" data-session="${escapeHtml(laneSessionId)}" onclick="copySessionId('${escapeHtml(laneSessionId)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span>`;
|
|
651
|
-
} else {
|
|
652
|
-
html += ` <span class="session-cmd dead-session">${escapeHtml(sessionChip)}</span>`;
|
|
653
|
-
}
|
|
673
|
+
|
|
654
674
|
html += ` </div>`;
|
|
655
675
|
html += `</div>`;
|
|
656
676
|
|
|
@@ -975,12 +995,7 @@ function renderMergeAgents(batch, sessions) {
|
|
|
975
995
|
// Full telemetry cell
|
|
976
996
|
html += `<td class="merge-telemetry-cell">${mergeTelemetryHtml(mergeTel, effectiveAlive)}</td>`;
|
|
977
997
|
html += `<td>`;
|
|
978
|
-
|
|
979
|
-
const sessionChip = `session: ${effectiveSession}`;
|
|
980
|
-
html += `<span class="session-cmd" data-session="${escapeHtml(effectiveSession)}" onclick="copySessionId('${escapeHtml(effectiveSession)}')" title="Copy session ID">${escapeHtml(sessionChip)}</span>`;
|
|
981
|
-
} else {
|
|
982
|
-
html += '<span class="merge-no-data">—</span>';
|
|
983
|
-
}
|
|
998
|
+
html += '<span class="merge-no-data">—</span>';
|
|
984
999
|
html += `</td>`;
|
|
985
1000
|
html += `<td class="merge-detail-cell">${mr.failureReason ? escapeHtml(mr.failureReason) : "—"}</td>`;
|
|
986
1001
|
html += `</tr>`;
|
|
@@ -1015,14 +1030,13 @@ function renderMergeAgents(batch, sessions) {
|
|
|
1015
1030
|
if (shownSessions.has(sess)) continue;
|
|
1016
1031
|
|
|
1017
1032
|
const sessTel = telemetry[sess] || null;
|
|
1018
|
-
const sessionChip = `session: ${sess}`;
|
|
1019
1033
|
html += `<tr>`;
|
|
1020
1034
|
html += `<td class="merge-wave-cell">—</td>`;
|
|
1021
1035
|
html += `<td><span class="status-badge status-running"><span class="status-dot running"></span> merging</span></td>`;
|
|
1022
1036
|
html += `<td class="merge-session-cell">${escapeHtml(sess)}</td>`;
|
|
1023
1037
|
// Full telemetry cell for active merge session
|
|
1024
1038
|
html += `<td class="merge-telemetry-cell">${mergeTelemetryHtml(sessTel, true)}</td>`;
|
|
1025
|
-
html += `<td
|
|
1039
|
+
html += `<td>—</td>`;
|
|
1026
1040
|
html += `<td>—</td>`;
|
|
1027
1041
|
html += `</tr>`;
|
|
1028
1042
|
}
|
|
@@ -1144,7 +1158,7 @@ function renderMailboxAuditEvent(evt) {
|
|
|
1144
1158
|
} else {
|
|
1145
1159
|
// Unknown event type — render generically
|
|
1146
1160
|
direction = evt.from ? `${escapeHtml(evt.from)}` : '';
|
|
1147
|
-
preview = JSON.stringify(evt)
|
|
1161
|
+
preview = JSON.stringify(evt);
|
|
1148
1162
|
}
|
|
1149
1163
|
|
|
1150
1164
|
return `<div class="message-row">`
|
|
@@ -1175,7 +1189,7 @@ function renderMailboxDirMessage(msg) {
|
|
|
1175
1189
|
else if (msg._status === 'reply-acked') statusBadge = '<span class="msg-badge msg-delivered">reply (acked)</span>';
|
|
1176
1190
|
else statusBadge = '';
|
|
1177
1191
|
const typeBadge = `<span class="msg-badge msg-type">${escapeHtml(msg.type || '')}</span>`;
|
|
1178
|
-
const preview =
|
|
1192
|
+
const preview = msg.content || '';
|
|
1179
1193
|
const broadcastTag = msg._isBroadcast ? ' <span class="msg-badge msg-type">broadcast</span>' : '';
|
|
1180
1194
|
|
|
1181
1195
|
return `<div class="message-row">`
|
|
@@ -1787,9 +1787,9 @@ body {
|
|
|
1787
1787
|
}
|
|
1788
1788
|
.message-row {
|
|
1789
1789
|
display: flex;
|
|
1790
|
-
align-items:
|
|
1790
|
+
align-items: flex-start;
|
|
1791
1791
|
gap: 8px;
|
|
1792
|
-
padding:
|
|
1792
|
+
padding: 6px 8px;
|
|
1793
1793
|
font-size: 0.8rem;
|
|
1794
1794
|
border-radius: 4px;
|
|
1795
1795
|
background: var(--bg-secondary);
|
|
@@ -1826,9 +1826,8 @@ body {
|
|
|
1826
1826
|
}
|
|
1827
1827
|
.msg-preview {
|
|
1828
1828
|
color: var(--text-primary);
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
white-space: nowrap;
|
|
1829
|
+
white-space: pre-wrap;
|
|
1830
|
+
word-break: break-word;
|
|
1832
1831
|
flex: 1;
|
|
1833
1832
|
}
|
|
1834
1833
|
.msg-rate-limited {
|