taskplane 0.3.0 → 0.4.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 +14 -5
- package/bin/gitignore-patterns.mjs +78 -0
- package/bin/taskplane.mjs +1356 -34
- package/dashboard/server.cjs +13 -1
- package/extensions/task-runner.ts +212 -57
- package/extensions/taskplane/config-loader.ts +860 -0
- package/extensions/taskplane/config-schema.ts +468 -0
- package/extensions/taskplane/config.ts +37 -93
- package/extensions/taskplane/engine.ts +2 -0
- package/extensions/taskplane/extension.ts +19 -0
- package/extensions/taskplane/merge.ts +12 -4
- package/extensions/taskplane/resume.ts +23 -14
- package/extensions/taskplane/settings-tui.ts +1387 -0
- package/extensions/taskplane/types.ts +81 -1
- package/extensions/taskplane/workspace.ts +204 -10
- package/package.json +1 -1
- package/skills/create-taskplane-task/SKILL.md +7 -5
- package/skills/create-taskplane-task/references/prompt-template.md +4 -3
- package/templates/agents/local/task-merger.md +27 -0
- package/templates/agents/local/task-reviewer.md +29 -0
- package/templates/agents/local/task-worker.md +30 -0
- package/templates/agents/task-worker.md +45 -31
- package/templates/config/task-orchestrator.yaml +3 -0
|
@@ -261,6 +261,8 @@ export function buildMergeRequest(
|
|
|
261
261
|
* @param repoRoot - Main repository root (merge happens here)
|
|
262
262
|
* @param mergeRequestPath - Path to the merge request temp file
|
|
263
263
|
* @param config - Orchestrator config (for model, tools)
|
|
264
|
+
* @param stateRoot - Root for state files (batch state, merge results). Stays at workspace root.
|
|
265
|
+
* @param agentRoot - Root for agent prompts. When pointer is resolved, this is the config repo's agent dir. Falls back to `<stateRoot>/.pi/agents/` or `<repoRoot>/.pi/agents/`.
|
|
264
266
|
* @throws MergeError if spawn fails after retries
|
|
265
267
|
*/
|
|
266
268
|
export function spawnMergeAgent(
|
|
@@ -270,6 +272,7 @@ export function spawnMergeAgent(
|
|
|
270
272
|
mergeRequestPath: string,
|
|
271
273
|
config: OrchestratorConfig,
|
|
272
274
|
stateRoot?: string,
|
|
275
|
+
agentRoot?: string,
|
|
273
276
|
): void {
|
|
274
277
|
execLog("merge", sessionName, "preparing to spawn merge agent", {
|
|
275
278
|
mergeWorkDir,
|
|
@@ -304,7 +307,7 @@ export function spawnMergeAgent(
|
|
|
304
307
|
"pi --no-session",
|
|
305
308
|
modelArgs,
|
|
306
309
|
toolsArgs,
|
|
307
|
-
`--append-system-prompt ${shellQuote(join(stateRoot ?? repoRoot, ".pi", "agents", "task-merger.md"))}`,
|
|
310
|
+
`--append-system-prompt ${shellQuote(agentRoot ? join(agentRoot, "task-merger.md") : join(stateRoot ?? repoRoot, ".pi", "agents", "task-merger.md"))}`,
|
|
308
311
|
`@${shellQuote(mergeRequestPath)}`,
|
|
309
312
|
].filter(Boolean).join(" ");
|
|
310
313
|
|
|
@@ -509,6 +512,7 @@ export function mergeWave(
|
|
|
509
512
|
batchId: string,
|
|
510
513
|
baseBranch: string,
|
|
511
514
|
stateRoot?: string,
|
|
515
|
+
agentRoot?: string,
|
|
512
516
|
): MergeWaveResult {
|
|
513
517
|
const startTime = Date.now();
|
|
514
518
|
const tmuxPrefix = config.orchestrator.tmux_prefix;
|
|
@@ -648,10 +652,11 @@ export function mergeWave(
|
|
|
648
652
|
writeFileSync(requestFilePath, mergeRequestContent, "utf-8");
|
|
649
653
|
|
|
650
654
|
// Spawn merge agent in the isolated merge worktree
|
|
651
|
-
spawnMergeAgent(sessionName, repoRoot, mergeWorkDir, requestFilePath, config, stateRoot);
|
|
655
|
+
spawnMergeAgent(sessionName, repoRoot, mergeWorkDir, requestFilePath, config, stateRoot, agentRoot);
|
|
652
656
|
|
|
653
|
-
// Wait for result
|
|
654
|
-
const
|
|
657
|
+
// Wait for result — use configured timeout (default 10 min, was 5 min)
|
|
658
|
+
const timeoutMs = (config.merge.timeout_minutes ?? 10) * 60 * 1000;
|
|
659
|
+
const mergeResult = waitForMergeResult(resultFilePath, sessionName, timeoutMs);
|
|
655
660
|
|
|
656
661
|
// Clean up request file (leave result file for debugging)
|
|
657
662
|
try {
|
|
@@ -894,6 +899,7 @@ export function mergeWaveByRepo(
|
|
|
894
899
|
baseBranch: string,
|
|
895
900
|
workspaceConfig?: WorkspaceConfig | null,
|
|
896
901
|
stateRoot?: string,
|
|
902
|
+
agentRoot?: string,
|
|
897
903
|
): MergeWaveResult {
|
|
898
904
|
const startTime = Date.now();
|
|
899
905
|
|
|
@@ -947,6 +953,7 @@ export function mergeWaveByRepo(
|
|
|
947
953
|
batchId,
|
|
948
954
|
baseBranch,
|
|
949
955
|
stateRoot,
|
|
956
|
+
agentRoot,
|
|
950
957
|
);
|
|
951
958
|
// Attach empty repoResults for consistent shape
|
|
952
959
|
return { ...result, repoResults: [] };
|
|
@@ -991,6 +998,7 @@ export function mergeWaveByRepo(
|
|
|
991
998
|
batchId,
|
|
992
999
|
groupBaseBranch,
|
|
993
1000
|
stateRoot,
|
|
1001
|
+
agentRoot,
|
|
994
1002
|
);
|
|
995
1003
|
|
|
996
1004
|
// Accumulate lane results
|
|
@@ -499,14 +499,19 @@ export async function resumeOrchBatch(
|
|
|
499
499
|
onNotify: (message: string, level: "info" | "warning" | "error") => void,
|
|
500
500
|
onMonitorUpdate?: MonitorUpdateCallback,
|
|
501
501
|
workspaceConfig?: WorkspaceConfig | null,
|
|
502
|
+
workspaceRoot?: string,
|
|
503
|
+
agentRoot?: string,
|
|
502
504
|
): Promise<void> {
|
|
503
505
|
const repoRoot = cwd;
|
|
506
|
+
// State files (.pi/batch-state.json, lane-state, etc.) belong in the workspace root,
|
|
507
|
+
// which is where .pi/ config lives. In repo mode, stateRoot === repoRoot.
|
|
508
|
+
const stateRoot = workspaceRoot ?? cwd;
|
|
504
509
|
const prefix = orchConfig.orchestrator.tmux_prefix;
|
|
505
510
|
|
|
506
511
|
// ── 1. Load persisted state ──────────────────────────────────
|
|
507
512
|
let persistedState: PersistedBatchState | null;
|
|
508
513
|
try {
|
|
509
|
-
persistedState = loadBatchState(
|
|
514
|
+
persistedState = loadBatchState(stateRoot);
|
|
510
515
|
} catch (err: unknown) {
|
|
511
516
|
if (err instanceof StateFileError) {
|
|
512
517
|
onNotify(
|
|
@@ -898,6 +903,8 @@ export async function resumeOrchBatch(
|
|
|
898
903
|
batchState.batchId,
|
|
899
904
|
batchState.baseBranch,
|
|
900
905
|
workspaceConfig,
|
|
906
|
+
stateRoot,
|
|
907
|
+
agentRoot,
|
|
901
908
|
);
|
|
902
909
|
|
|
903
910
|
if (reExecMergeResult.status === "succeeded") {
|
|
@@ -989,7 +996,7 @@ export async function resumeOrchBatch(
|
|
|
989
996
|
}
|
|
990
997
|
}
|
|
991
998
|
|
|
992
|
-
persistRuntimeState("resume-reconciliation", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery ?? null,
|
|
999
|
+
persistRuntimeState("resume-reconciliation", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery ?? null, stateRoot);
|
|
993
1000
|
|
|
994
1001
|
// ── 10. Continue wave execution ──────────────────────────────
|
|
995
1002
|
// We need to execute remaining waves starting from resumeWaveIndex.
|
|
@@ -1001,13 +1008,13 @@ export async function resumeOrchBatch(
|
|
|
1001
1008
|
// Check pause signal
|
|
1002
1009
|
if (batchState.pauseSignal.paused) {
|
|
1003
1010
|
batchState.phase = "paused";
|
|
1004
|
-
persistRuntimeState("pause-before-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1011
|
+
persistRuntimeState("pause-before-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1005
1012
|
onNotify(`⏸️ Batch paused before wave ${waveIdx + 1}.`, "warning");
|
|
1006
1013
|
break;
|
|
1007
1014
|
}
|
|
1008
1015
|
|
|
1009
1016
|
batchState.currentWaveIndex = waveIdx;
|
|
1010
|
-
persistRuntimeState("wave-index-change", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1017
|
+
persistRuntimeState("wave-index-change", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1011
1018
|
|
|
1012
1019
|
// Get wave tasks, filtering out completed/failed/blocked ones.
|
|
1013
1020
|
let waveTasks = persistedState.wavePlan[waveIdx].filter(
|
|
@@ -1043,7 +1050,7 @@ export async function resumeOrchBatch(
|
|
|
1043
1050
|
const handleResumeMonitorUpdate: MonitorUpdateCallback = (monitorState) => {
|
|
1044
1051
|
const changed = syncTaskOutcomesFromMonitor(monitorState, allTaskOutcomes);
|
|
1045
1052
|
if (changed) {
|
|
1046
|
-
persistRuntimeState("task-transition", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1053
|
+
persistRuntimeState("task-transition", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1047
1054
|
}
|
|
1048
1055
|
onMonitorUpdate?.(monitorState);
|
|
1049
1056
|
};
|
|
@@ -1068,7 +1075,7 @@ export async function resumeOrchBatch(
|
|
|
1068
1075
|
encounteredRepoRoots.add(resolveRepoRoot(lane.repoId, repoRoot, workspaceConfig));
|
|
1069
1076
|
}
|
|
1070
1077
|
if (seedPendingOutcomesForAllocatedLanes(lanes, allTaskOutcomes)) {
|
|
1071
|
-
persistRuntimeState("wave-lanes-allocated", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1078
|
+
persistRuntimeState("wave-lanes-allocated", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1072
1079
|
}
|
|
1073
1080
|
},
|
|
1074
1081
|
workspaceConfig,
|
|
@@ -1105,7 +1112,7 @@ export async function resumeOrchBatch(
|
|
|
1105
1112
|
batchState.blockedTaskIds.add(blocked);
|
|
1106
1113
|
}
|
|
1107
1114
|
|
|
1108
|
-
persistRuntimeState("wave-execution-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1115
|
+
persistRuntimeState("wave-execution-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1109
1116
|
|
|
1110
1117
|
const elapsedSec = Math.round((waveResult.endedAt - waveResult.startedAt) / 1000);
|
|
1111
1118
|
onNotify(
|
|
@@ -1123,13 +1130,13 @@ export async function resumeOrchBatch(
|
|
|
1123
1130
|
if (waveResult.stoppedEarly) {
|
|
1124
1131
|
if (waveResult.policyApplied === "stop-all") {
|
|
1125
1132
|
batchState.phase = "stopped";
|
|
1126
|
-
persistRuntimeState("stop-all", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1133
|
+
persistRuntimeState("stop-all", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1127
1134
|
onNotify(ORCH_MESSAGES.orchBatchStopped(batchState.batchId, "stop-all"), "error");
|
|
1128
1135
|
break;
|
|
1129
1136
|
}
|
|
1130
1137
|
if (waveResult.policyApplied === "stop-wave") {
|
|
1131
1138
|
batchState.phase = "stopped";
|
|
1132
|
-
persistRuntimeState("stop-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1139
|
+
persistRuntimeState("stop-wave", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1133
1140
|
onNotify(ORCH_MESSAGES.orchBatchStopped(batchState.batchId, "stop-wave"), "error");
|
|
1134
1141
|
break;
|
|
1135
1142
|
}
|
|
@@ -1163,7 +1170,7 @@ export async function resumeOrchBatch(
|
|
|
1163
1170
|
|
|
1164
1171
|
if (mergeableLaneCount > 0) {
|
|
1165
1172
|
batchState.phase = "merging";
|
|
1166
|
-
persistRuntimeState("merge-start", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1173
|
+
persistRuntimeState("merge-start", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1167
1174
|
onNotify(ORCH_MESSAGES.orchMergeStart(waveIdx + 1, mergeableLaneCount), "info");
|
|
1168
1175
|
|
|
1169
1176
|
mergeResult = mergeWaveByRepo(
|
|
@@ -1175,6 +1182,8 @@ export async function resumeOrchBatch(
|
|
|
1175
1182
|
batchState.batchId,
|
|
1176
1183
|
batchState.baseBranch,
|
|
1177
1184
|
workspaceConfig,
|
|
1185
|
+
stateRoot,
|
|
1186
|
+
agentRoot,
|
|
1178
1187
|
);
|
|
1179
1188
|
batchState.mergeResults.push(mergeResult);
|
|
1180
1189
|
|
|
@@ -1223,7 +1232,7 @@ export async function resumeOrchBatch(
|
|
|
1223
1232
|
}
|
|
1224
1233
|
|
|
1225
1234
|
batchState.phase = "executing";
|
|
1226
|
-
persistRuntimeState("merge-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1235
|
+
persistRuntimeState("merge-complete", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1227
1236
|
} else if (mixedOutcomeLanes.length > 0) {
|
|
1228
1237
|
const mixedIds = mixedOutcomeLanes.map(l => `lane-${l.laneNumber}`).join(", ");
|
|
1229
1238
|
mergeResult = {
|
|
@@ -1255,7 +1264,7 @@ export async function resumeOrchBatch(
|
|
|
1255
1264
|
|
|
1256
1265
|
batchState.phase = policyResult.targetPhase;
|
|
1257
1266
|
batchState.errors.push(policyResult.errorMessage);
|
|
1258
|
-
persistRuntimeState(policyResult.persistTrigger, batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1267
|
+
persistRuntimeState(policyResult.persistTrigger, batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1259
1268
|
onNotify(policyResult.notifyMessage, policyResult.notifyLevel);
|
|
1260
1269
|
preserveWorktreesForResume = true;
|
|
1261
1270
|
break;
|
|
@@ -1325,7 +1334,7 @@ export async function resumeOrchBatch(
|
|
|
1325
1334
|
}
|
|
1326
1335
|
}
|
|
1327
1336
|
|
|
1328
|
-
persistRuntimeState("batch-terminal", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery,
|
|
1337
|
+
persistRuntimeState("batch-terminal", batchState, wavePlan, latestAllocatedLanes, allTaskOutcomes, discovery, stateRoot);
|
|
1329
1338
|
|
|
1330
1339
|
if (batchState.phase === "paused" || batchState.phase === "stopped") {
|
|
1331
1340
|
execLog("resume", batchState.batchId, "resumed batch ended in non-terminal state", { phase: batchState.phase });
|
|
@@ -1344,7 +1353,7 @@ export async function resumeOrchBatch(
|
|
|
1344
1353
|
|
|
1345
1354
|
if (batchState.phase === "completed") {
|
|
1346
1355
|
try {
|
|
1347
|
-
deleteBatchState(
|
|
1356
|
+
deleteBatchState(stateRoot);
|
|
1348
1357
|
execLog("state", batchState.batchId, "state file deleted on clean resume completion");
|
|
1349
1358
|
} catch {
|
|
1350
1359
|
// Best-effort
|