taskplane 0.29.2 → 0.30.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/bin/gitignore-patterns.mjs +11 -8
- package/bin/rpc-wrapper.mjs +410 -357
- package/bin/taskplane.mjs +533 -250
- package/extensions/reviewer-extension.ts +17 -11
- package/extensions/taskplane/abort.ts +50 -18
- package/extensions/taskplane/agent-bridge-extension.ts +232 -105
- package/extensions/taskplane/agent-host.ts +224 -97
- package/extensions/taskplane/cleanup.ts +71 -42
- package/extensions/taskplane/config-loader.ts +142 -58
- package/extensions/taskplane/config-schema.ts +6 -13
- package/extensions/taskplane/config.ts +10 -2
- package/extensions/taskplane/diagnostic-reports.ts +59 -47
- package/extensions/taskplane/diagnostics.ts +13 -13
- package/extensions/taskplane/discovery.ts +35 -61
- package/extensions/taskplane/engine-worker.ts +53 -46
- package/extensions/taskplane/engine.ts +1760 -602
- package/extensions/taskplane/execution.ts +426 -206
- package/extensions/taskplane/extension.ts +1073 -598
- package/extensions/taskplane/formatting.ts +136 -124
- package/extensions/taskplane/git.ts +0 -2
- package/extensions/taskplane/lane-runner.ts +542 -311
- package/extensions/taskplane/mailbox.ts +57 -49
- package/extensions/taskplane/merge.ts +662 -383
- package/extensions/taskplane/messages.ts +109 -51
- package/extensions/taskplane/migrations.ts +1 -1
- package/extensions/taskplane/path-resolver.ts +8 -9
- package/extensions/taskplane/persistence.ts +425 -262
- package/extensions/taskplane/process-registry.ts +36 -7
- package/extensions/taskplane/quality-gate.ts +107 -55
- package/extensions/taskplane/resume.ts +774 -267
- package/extensions/taskplane/sessions.ts +1 -1
- package/extensions/taskplane/settings-tui.ts +505 -164
- package/extensions/taskplane/sidecar-telemetry.ts +25 -10
- package/extensions/taskplane/supervisor.ts +477 -270
- package/extensions/taskplane/task-executor-core.ts +178 -53
- package/extensions/taskplane/types.ts +186 -108
- package/extensions/taskplane/verification.ts +27 -22
- package/extensions/taskplane/waves.ts +59 -43
- package/extensions/taskplane/workspace.ts +14 -12
- package/extensions/taskplane/worktree.ts +218 -196
- package/package.json +14 -2
|
@@ -48,7 +48,8 @@ export default function reviewerExtension(pi: ExtensionAPI) {
|
|
|
48
48
|
"Block until the next review request is available, then return its content. " +
|
|
49
49
|
"Call this after completing each review to wait for the next one. " +
|
|
50
50
|
"Returns 'SHUTDOWN' when the task is complete and you should exit.",
|
|
51
|
-
promptSnippet:
|
|
51
|
+
promptSnippet:
|
|
52
|
+
"wait_for_review() — block until the next review request arrives (persistent reviewer mode)",
|
|
52
53
|
promptGuidelines: [
|
|
53
54
|
"Call wait_for_review() to receive each review request.",
|
|
54
55
|
"After writing your review to the specified output file, call wait_for_review() again.",
|
|
@@ -82,11 +83,14 @@ export default function reviewerExtension(pi: ExtensionAPI) {
|
|
|
82
83
|
if (!existsSync(requestPath)) {
|
|
83
84
|
// Signal fired but request file doesn't exist (race condition or error)
|
|
84
85
|
return {
|
|
85
|
-
content: [
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text" as const,
|
|
89
|
+
text:
|
|
90
|
+
`ERROR — Signal file ${REVIEWER_SIGNAL_PREFIX}${signalNum} found but ` +
|
|
91
|
+
`${signalContent} does not exist. Waiting for next signal.`,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
90
94
|
details: undefined,
|
|
91
95
|
};
|
|
92
96
|
}
|
|
@@ -103,16 +107,18 @@ export default function reviewerExtension(pi: ExtensionAPI) {
|
|
|
103
107
|
// Check timeout
|
|
104
108
|
if (Date.now() - startTime > REVIEWER_WAIT_TIMEOUT_MS) {
|
|
105
109
|
return {
|
|
106
|
-
content: [
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: "text" as const,
|
|
113
|
+
text: "TIMEOUT — No review request received within the timeout period. Exit cleanly.",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
110
116
|
details: undefined,
|
|
111
117
|
};
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
// Wait before next poll
|
|
115
|
-
await new Promise(resolve => setTimeout(resolve, REVIEWER_POLL_INTERVAL_MS));
|
|
121
|
+
await new Promise((resolve) => setTimeout(resolve, REVIEWER_POLL_INTERVAL_MS));
|
|
116
122
|
}
|
|
117
123
|
},
|
|
118
124
|
});
|
|
@@ -8,7 +8,18 @@ import { join } from "path";
|
|
|
8
8
|
import { execLog, killV2LaneAgents, resolveCanonicalTaskPaths } from "./execution.ts";
|
|
9
9
|
import { killMergeAgentV2, killAllMergeAgentsV2 } from "./merge.ts";
|
|
10
10
|
import { deleteBatchState, persistRuntimeState } from "./persistence.ts";
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
AbortActionStep,
|
|
13
|
+
AbortErrorCode,
|
|
14
|
+
AbortLaneResult,
|
|
15
|
+
AbortMode,
|
|
16
|
+
AbortResult,
|
|
17
|
+
AbortTargetSession,
|
|
18
|
+
AllocatedLane,
|
|
19
|
+
OrchBatchRuntimeState,
|
|
20
|
+
PersistedBatchState,
|
|
21
|
+
PersistedLaneRecord,
|
|
22
|
+
} from "./types.ts";
|
|
12
23
|
|
|
13
24
|
// ── Abort Pure Functions ─────────────────────────────────────────────
|
|
14
25
|
|
|
@@ -37,7 +48,7 @@ export function selectAbortTargetSessions(
|
|
|
37
48
|
// Filter to only lane and merge sessions for the exact orchestrator prefix.
|
|
38
49
|
// Handles both repo-mode (`<prefix>-lane-<N>`) and workspace-mode
|
|
39
50
|
// (`<prefix>-<repoId>-lane-<N>`) session name formats.
|
|
40
|
-
const targetNames = allSessionNames.filter(name => {
|
|
51
|
+
const targetNames = allSessionNames.filter((name) => {
|
|
41
52
|
const prefixWithDash = `${prefix}-`;
|
|
42
53
|
if (!name.startsWith(prefixWithDash)) return false;
|
|
43
54
|
const suffix = name.slice(prefixWithDash.length);
|
|
@@ -78,7 +89,10 @@ export function selectAbortTargetSessions(
|
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
// Build lookup from runtime lanes
|
|
81
|
-
const runtimeLookup = new Map<
|
|
92
|
+
const runtimeLookup = new Map<
|
|
93
|
+
string,
|
|
94
|
+
{ laneId: string; taskId: string | null; worktreePath: string; taskFolder: string | null }
|
|
95
|
+
>();
|
|
82
96
|
for (const lane of runtimeLanes) {
|
|
83
97
|
const currentTask = lane.tasks.length > 0 ? lane.tasks[0] : null;
|
|
84
98
|
runtimeLookup.set(lane.laneSessionId, {
|
|
@@ -90,7 +104,7 @@ export function selectAbortTargetSessions(
|
|
|
90
104
|
});
|
|
91
105
|
}
|
|
92
106
|
|
|
93
|
-
return targetNames.map(sessionName => {
|
|
107
|
+
return targetNames.map((sessionName) => {
|
|
94
108
|
const runtime = runtimeLookup.get(sessionName);
|
|
95
109
|
const persisted = persistedLookup.get(sessionName);
|
|
96
110
|
|
|
@@ -184,7 +198,6 @@ export function discoverAbortSessionNames(
|
|
|
184
198
|
return [...names];
|
|
185
199
|
}
|
|
186
200
|
|
|
187
|
-
|
|
188
201
|
// ── Abort Orchestration Functions ────────────────────────────────────
|
|
189
202
|
|
|
190
203
|
/**
|
|
@@ -207,10 +220,18 @@ export function writeWrapUpFiles(
|
|
|
207
220
|
if (!target.taskFolderInWorktree) {
|
|
208
221
|
// Skip child sessions (workers, reviewers) — only main lane sessions have task folders
|
|
209
222
|
// Also skip merge sessions (no task folder)
|
|
210
|
-
if (
|
|
223
|
+
if (
|
|
224
|
+
target.sessionName.endsWith("-worker") ||
|
|
225
|
+
target.sessionName.endsWith("-reviewer") ||
|
|
226
|
+
target.sessionName.includes("merge")
|
|
227
|
+
) {
|
|
211
228
|
results.push({ sessionName: target.sessionName, written: false, error: null });
|
|
212
229
|
} else {
|
|
213
|
-
results.push({
|
|
230
|
+
results.push({
|
|
231
|
+
sessionName: target.sessionName,
|
|
232
|
+
written: false,
|
|
233
|
+
error: "No task folder resolved",
|
|
234
|
+
});
|
|
214
235
|
}
|
|
215
236
|
continue;
|
|
216
237
|
}
|
|
@@ -220,7 +241,11 @@ export function writeWrapUpFiles(
|
|
|
220
241
|
|
|
221
242
|
// Ensure directory exists
|
|
222
243
|
if (!existsSync(target.taskFolderInWorktree)) {
|
|
223
|
-
results.push({
|
|
244
|
+
results.push({
|
|
245
|
+
sessionName: target.sessionName,
|
|
246
|
+
written: false,
|
|
247
|
+
error: `Task folder does not exist: ${target.taskFolderInWorktree}`,
|
|
248
|
+
});
|
|
224
249
|
continue;
|
|
225
250
|
}
|
|
226
251
|
|
|
@@ -262,7 +287,7 @@ export async function waitForSessionExit(
|
|
|
262
287
|
const deadline = Date.now() + gracePeriodMs;
|
|
263
288
|
while (Date.now() < deadline) {
|
|
264
289
|
const sleepMs = Math.max(1, Math.min(pollIntervalMs, deadline - Date.now()));
|
|
265
|
-
await new Promise(r => setTimeout(r, sleepMs));
|
|
290
|
+
await new Promise((r) => setTimeout(r, sleepMs));
|
|
266
291
|
}
|
|
267
292
|
|
|
268
293
|
return { exited: [], remaining: [...sessionNames] };
|
|
@@ -357,7 +382,11 @@ export async function executeAbort(
|
|
|
357
382
|
repoRoot,
|
|
358
383
|
);
|
|
359
384
|
} catch (err) {
|
|
360
|
-
execLog(
|
|
385
|
+
execLog(
|
|
386
|
+
"abort",
|
|
387
|
+
batchState.batchId,
|
|
388
|
+
`Failed to persist state during abort: ${err instanceof Error ? err.message : String(err)}`,
|
|
389
|
+
);
|
|
361
390
|
}
|
|
362
391
|
|
|
363
392
|
// TP-108: Kill all V2 merge agents (process-owned, not TMUX)
|
|
@@ -370,7 +399,11 @@ export async function executeAbort(
|
|
|
370
399
|
// Step 3: Discover target sessions from Runtime V2 state sources.
|
|
371
400
|
const allSessionNames = discoverAbortSessionNames(prefix, persistedState, batchState.currentLanes);
|
|
372
401
|
if (allSessionNames.length === 0) {
|
|
373
|
-
execLog(
|
|
402
|
+
execLog(
|
|
403
|
+
"abort",
|
|
404
|
+
batchState.batchId,
|
|
405
|
+
`No abort targets discovered for prefix "${prefix}" from runtime/persisted state.`,
|
|
406
|
+
);
|
|
374
407
|
}
|
|
375
408
|
|
|
376
409
|
// Step 4: Select and enrich target sessions
|
|
@@ -400,7 +433,7 @@ export async function executeAbort(
|
|
|
400
433
|
}
|
|
401
434
|
|
|
402
435
|
// Step 5b: Wait for sessions to exit
|
|
403
|
-
const allTargetNames = targets.map(t => t.sessionName);
|
|
436
|
+
const allTargetNames = targets.map((t) => t.sessionName);
|
|
404
437
|
const waitResult = await waitForSessionExit(allTargetNames, gracePeriodMs, pollIntervalMs);
|
|
405
438
|
gracefulExits = waitResult.exited.length;
|
|
406
439
|
|
|
@@ -414,7 +447,7 @@ export async function executeAbort(
|
|
|
414
447
|
for (const kr of killResults) {
|
|
415
448
|
killResultBySession.set(kr.sessionName, { killed: kr.killed, error: kr.error });
|
|
416
449
|
}
|
|
417
|
-
const killFailures = killResults.filter(kr => !kr.killed);
|
|
450
|
+
const killFailures = killResults.filter((kr) => !kr.killed);
|
|
418
451
|
if (killFailures.length > 0) {
|
|
419
452
|
errors.push({
|
|
420
453
|
code: "ABORT_KILL_FAILED",
|
|
@@ -426,7 +459,7 @@ export async function executeAbort(
|
|
|
426
459
|
// Build lane results
|
|
427
460
|
const exitedSet = new Set(waitResult.exited);
|
|
428
461
|
for (const target of targets) {
|
|
429
|
-
const wrapUp = wrapUpResults.find(wr => wr.sessionName === target.sessionName);
|
|
462
|
+
const wrapUp = wrapUpResults.find((wr) => wr.sessionName === target.sessionName);
|
|
430
463
|
const wasGraceful = exitedSet.has(target.sessionName);
|
|
431
464
|
const killResult = killResultBySession.get(target.sessionName);
|
|
432
465
|
const sessionKilled = wasGraceful || killResult?.killed === true;
|
|
@@ -443,7 +476,7 @@ export async function executeAbort(
|
|
|
443
476
|
}
|
|
444
477
|
} else {
|
|
445
478
|
// Hard mode: kill all immediately
|
|
446
|
-
const allTargetNames = targets.map(t => t.sessionName);
|
|
479
|
+
const allTargetNames = targets.map((t) => t.sessionName);
|
|
447
480
|
const killResults = killOrchSessions(allTargetNames, {
|
|
448
481
|
stateRoot: repoRoot,
|
|
449
482
|
batchId: batchState.batchId,
|
|
@@ -452,7 +485,7 @@ export async function executeAbort(
|
|
|
452
485
|
for (const kr of killResults) {
|
|
453
486
|
killResultBySession.set(kr.sessionName, { killed: kr.killed, error: kr.error });
|
|
454
487
|
}
|
|
455
|
-
const killFailures = killResults.filter(kr => !kr.killed);
|
|
488
|
+
const killFailures = killResults.filter((kr) => !kr.killed);
|
|
456
489
|
if (killFailures.length > 0) {
|
|
457
490
|
errors.push({
|
|
458
491
|
code: "ABORT_KILL_FAILED",
|
|
@@ -490,7 +523,7 @@ export async function executeAbort(
|
|
|
490
523
|
return {
|
|
491
524
|
mode,
|
|
492
525
|
sessionsFound: targets.length,
|
|
493
|
-
sessionsKilled: laneResults.filter(lr => lr.sessionKilled).length,
|
|
526
|
+
sessionsKilled: laneResults.filter((lr) => lr.sessionKilled).length,
|
|
494
527
|
gracefulExits,
|
|
495
528
|
laneResults,
|
|
496
529
|
wrapUpFailures,
|
|
@@ -499,4 +532,3 @@ export async function executeAbort(
|
|
|
499
532
|
durationMs: Date.now() - startTime,
|
|
500
533
|
};
|
|
501
534
|
}
|
|
502
|
-
|