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.
Files changed (41) hide show
  1. package/bin/gitignore-patterns.mjs +11 -8
  2. package/bin/rpc-wrapper.mjs +410 -357
  3. package/bin/taskplane.mjs +533 -250
  4. package/extensions/reviewer-extension.ts +17 -11
  5. package/extensions/taskplane/abort.ts +50 -18
  6. package/extensions/taskplane/agent-bridge-extension.ts +232 -105
  7. package/extensions/taskplane/agent-host.ts +224 -97
  8. package/extensions/taskplane/cleanup.ts +71 -42
  9. package/extensions/taskplane/config-loader.ts +142 -58
  10. package/extensions/taskplane/config-schema.ts +6 -13
  11. package/extensions/taskplane/config.ts +10 -2
  12. package/extensions/taskplane/diagnostic-reports.ts +59 -47
  13. package/extensions/taskplane/diagnostics.ts +13 -13
  14. package/extensions/taskplane/discovery.ts +35 -61
  15. package/extensions/taskplane/engine-worker.ts +53 -46
  16. package/extensions/taskplane/engine.ts +1760 -602
  17. package/extensions/taskplane/execution.ts +426 -206
  18. package/extensions/taskplane/extension.ts +1073 -598
  19. package/extensions/taskplane/formatting.ts +136 -124
  20. package/extensions/taskplane/git.ts +0 -2
  21. package/extensions/taskplane/lane-runner.ts +542 -311
  22. package/extensions/taskplane/mailbox.ts +57 -49
  23. package/extensions/taskplane/merge.ts +662 -383
  24. package/extensions/taskplane/messages.ts +109 -51
  25. package/extensions/taskplane/migrations.ts +1 -1
  26. package/extensions/taskplane/path-resolver.ts +8 -9
  27. package/extensions/taskplane/persistence.ts +425 -262
  28. package/extensions/taskplane/process-registry.ts +36 -7
  29. package/extensions/taskplane/quality-gate.ts +107 -55
  30. package/extensions/taskplane/resume.ts +774 -267
  31. package/extensions/taskplane/sessions.ts +1 -1
  32. package/extensions/taskplane/settings-tui.ts +505 -164
  33. package/extensions/taskplane/sidecar-telemetry.ts +25 -10
  34. package/extensions/taskplane/supervisor.ts +477 -270
  35. package/extensions/taskplane/task-executor-core.ts +178 -53
  36. package/extensions/taskplane/types.ts +186 -108
  37. package/extensions/taskplane/verification.ts +27 -22
  38. package/extensions/taskplane/waves.ts +59 -43
  39. package/extensions/taskplane/workspace.ts +14 -12
  40. package/extensions/taskplane/worktree.ts +218 -196
  41. 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: "wait_for_review() — block until the next review request arrives (persistent reviewer mode)",
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
- type: "text" as const,
87
- text: `ERROR Signal file ${REVIEWER_SIGNAL_PREFIX}${signalNum} found but ` +
88
- `${signalContent} does not exist. Waiting for next signal.`,
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
- type: "text" as const,
108
- text: "TIMEOUT No review request received within the timeout period. Exit cleanly.",
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 { AbortActionStep, AbortErrorCode, AbortLaneResult, AbortMode, AbortResult, AbortTargetSession, AllocatedLane, OrchBatchRuntimeState, PersistedBatchState, PersistedLaneRecord } from "./types.ts";
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<string, { laneId: string; taskId: string | null; worktreePath: string; taskFolder: string | null }>();
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 (target.sessionName.endsWith("-worker") || target.sessionName.endsWith("-reviewer") || target.sessionName.includes("merge")) {
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({ sessionName: target.sessionName, written: false, error: "No task folder resolved" });
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({ sessionName: target.sessionName, written: false, error: `Task folder does not exist: ${target.taskFolderInWorktree}` });
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("abort", batchState.batchId, `Failed to persist state during abort: ${err instanceof Error ? err.message : String(err)}`);
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("abort", batchState.batchId, `No abort targets discovered for prefix "${prefix}" from runtime/persisted state.`);
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
-