taskplane 0.18.0 → 0.19.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/dashboard/server.cjs
CHANGED
|
@@ -391,8 +391,18 @@ function loadTelemetryData(batchState) {
|
|
|
391
391
|
// Determine the key (tmux prefix)
|
|
392
392
|
let prefix;
|
|
393
393
|
if (parsed.role === "merger") {
|
|
394
|
-
// Merge agent —
|
|
395
|
-
|
|
394
|
+
// Merge agent — derive prefix from lane naming so it matches the tmux
|
|
395
|
+
// session name used by the client (e.g. "orch-henrylach-merge-1").
|
|
396
|
+
// Lane sessions: "orch-{opId}-lane-{N}" → merge sessions: "orch-{opId}-merge-{N}".
|
|
397
|
+
const firstLanePrefix = Object.values(laneToPrefix)[0]; // e.g. "orch-henrylach-lane-1"
|
|
398
|
+
const opPrefix = firstLanePrefix?.replace(/-lane-\d+$/, ""); // "orch-henrylach"
|
|
399
|
+
if (parsed.mergeNumber != null && opPrefix) {
|
|
400
|
+
prefix = `${opPrefix}-merge-${parsed.mergeNumber}`;
|
|
401
|
+
} else if (parsed.mergeNumber != null) {
|
|
402
|
+
prefix = `orch-merge-${parsed.mergeNumber}`;
|
|
403
|
+
} else {
|
|
404
|
+
prefix = "orch-merge";
|
|
405
|
+
}
|
|
396
406
|
} else if (parsed.laneNumber != null && laneToPrefix[parsed.laneNumber]) {
|
|
397
407
|
prefix = laneToPrefix[parsed.laneNumber];
|
|
398
408
|
} else if (parsed.laneNumber != null) {
|
|
@@ -1111,8 +1111,24 @@ function generateReviewRequest(
|
|
|
1111
1111
|
}
|
|
1112
1112
|
|
|
1113
1113
|
function extractVerdict(reviewContent: string): string {
|
|
1114
|
+
// Primary: standard format "### Verdict: APPROVE|REVISE|RETHINK"
|
|
1114
1115
|
const match = reviewContent.match(/###?\s*Verdict[:\s]*(APPROVE|REVISE|RETHINK)/i);
|
|
1115
|
-
|
|
1116
|
+
if (match) return match[1].toUpperCase();
|
|
1117
|
+
|
|
1118
|
+
// TP-068: Tolerate non-standard verdict formats from models that don't
|
|
1119
|
+
// follow the exact template (e.g., "Changes requested", "Needs revision").
|
|
1120
|
+
const lower = reviewContent.toLowerCase();
|
|
1121
|
+
if (/\b(changes?\s+requested|needs?\s+revision|please\s+revise|must\s+revise)\b/.test(lower)) {
|
|
1122
|
+
return "REVISE";
|
|
1123
|
+
}
|
|
1124
|
+
if (/\b(looks?\s+good|no\s+issues?\s+found|approved?)\b/.test(lower)) {
|
|
1125
|
+
return "APPROVE";
|
|
1126
|
+
}
|
|
1127
|
+
if (/\b(fundamentally\s+wrong|rethink|reconsider\s+the\s+approach)\b/.test(lower)) {
|
|
1128
|
+
return "RETHINK";
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
return "UNKNOWN";
|
|
1116
1132
|
}
|
|
1117
1133
|
|
|
1118
1134
|
// ── Subagent Spawner ─────────────────────────────────────────────────
|
|
@@ -2304,8 +2320,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
2304
2320
|
// Initial prompt tells the reviewer to call wait_for_review
|
|
2305
2321
|
const initialPrompt =
|
|
2306
2322
|
"You are a persistent reviewer for this task. " +
|
|
2307
|
-
"
|
|
2308
|
-
"
|
|
2323
|
+
"Use the `wait_for_review` tool now to receive your first review request. " +
|
|
2324
|
+
"IMPORTANT: `wait_for_review` is a REGISTERED EXTENSION TOOL — call it " +
|
|
2325
|
+
"the same way you call `read`, `write`, `edit`, or `grep`. " +
|
|
2326
|
+
"Do NOT run it via `bash` or any shell command. " +
|
|
2327
|
+
"After writing each review, use `wait_for_review` again for the next one.";
|
|
2309
2328
|
|
|
2310
2329
|
const spawned = spawnAgentTmux({
|
|
2311
2330
|
sessionName,
|
|
@@ -2375,8 +2394,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
2375
2394
|
/**
|
|
2376
2395
|
* Poll for the verdict file to appear (written by the reviewer).
|
|
2377
2396
|
* Same pattern as the original review_step handler.
|
|
2397
|
+
*
|
|
2398
|
+
* Early-exit detection (TP-068): If the reviewer exits within 30s
|
|
2399
|
+
* of spawn without producing a verdict, it likely failed to use the
|
|
2400
|
+
* wait_for_review tool correctly (e.g., called it via bash). This
|
|
2401
|
+
* triggers a faster fallback instead of waiting 30 minutes.
|
|
2378
2402
|
*/
|
|
2379
|
-
async function pollForVerdict(): Promise<string> {
|
|
2403
|
+
async function pollForVerdict(spawnTime?: number): Promise<string> {
|
|
2404
|
+
const EARLY_EXIT_THRESHOLD_MS = 30_000; // 30 seconds
|
|
2380
2405
|
const verdictTimeout = 30 * 60 * 1000; // 30 minutes
|
|
2381
2406
|
const pollStart = Date.now();
|
|
2382
2407
|
while (Date.now() - pollStart < verdictTimeout) {
|
|
@@ -2385,6 +2410,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
2385
2410
|
}
|
|
2386
2411
|
// Also check if persistent reviewer died while we're waiting
|
|
2387
2412
|
if (state.persistentReviewerSession && !isPersistentReviewerAlive()) {
|
|
2413
|
+
// TP-068: Detect early exit as tool compatibility failure
|
|
2414
|
+
if (spawnTime && (Date.now() - spawnTime) < EARLY_EXIT_THRESHOLD_MS) {
|
|
2415
|
+
throw new Error(
|
|
2416
|
+
"Persistent reviewer exited within 30s of spawn without producing a verdict — " +
|
|
2417
|
+
"wait_for_review tool may not be supported by this model (e.g., called via bash instead of as a registered tool)"
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2388
2420
|
throw new Error("Persistent reviewer session died while waiting for verdict");
|
|
2389
2421
|
}
|
|
2390
2422
|
await new Promise(r => setTimeout(r, 2000));
|
|
@@ -2406,7 +2438,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
2406
2438
|
state.persistentReviewerSignalNum = 0;
|
|
2407
2439
|
}
|
|
2408
2440
|
|
|
2441
|
+
// Track spawn time for early-exit detection (TP-068)
|
|
2442
|
+
let spawnTime: number | undefined;
|
|
2409
2443
|
if (needsSpawn) {
|
|
2444
|
+
spawnTime = Date.now();
|
|
2410
2445
|
spawnPersistentReviewer();
|
|
2411
2446
|
// Give the reviewer a moment to start and call wait_for_review
|
|
2412
2447
|
await new Promise(r => setTimeout(r, 5000));
|
|
@@ -2415,8 +2450,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
2415
2450
|
// Signal the reviewer with the new request
|
|
2416
2451
|
signalPersistentReviewer();
|
|
2417
2452
|
|
|
2418
|
-
// Poll for the verdict file
|
|
2419
|
-
const reviewContent = await pollForVerdict();
|
|
2453
|
+
// Poll for the verdict file (pass spawnTime for early-exit detection)
|
|
2454
|
+
const reviewContent = await pollForVerdict(spawnTime);
|
|
2420
2455
|
|
|
2421
2456
|
// Stop the per-review timer
|
|
2422
2457
|
if (state.reviewerTimer) clearInterval(state.reviewerTimer);
|
|
@@ -2570,18 +2605,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
2570
2605
|
details: undefined,
|
|
2571
2606
|
};
|
|
2572
2607
|
} catch (fallbackErr: any) {
|
|
2573
|
-
// Both persistent and fallback failed
|
|
2608
|
+
// Both persistent and fallback failed — TP-068: clear logging
|
|
2574
2609
|
clearInterval(state.reviewerTimer);
|
|
2575
2610
|
clearReviewerState();
|
|
2576
2611
|
state.reviewerStatus = "error";
|
|
2577
2612
|
writeLaneState(state);
|
|
2578
2613
|
updateWidgets();
|
|
2579
2614
|
|
|
2615
|
+
const skipMsg = `⚠️ Reviews skipped for Step ${stepNum} — reviewer model could not process ${reviewType} review request. Both persistent and fallback modes failed.`;
|
|
2616
|
+
console.error(`[task-runner] ${skipMsg}`);
|
|
2580
2617
|
logExecution(statusPath, `Reviewer R${num}`,
|
|
2581
|
-
`${
|
|
2618
|
+
`${skipMsg} Error: ${fallbackErr?.message || fallbackErr}`);
|
|
2619
|
+
|
|
2620
|
+
// TP-068: Ensure shutdown signal is written even on double failure
|
|
2621
|
+
try {
|
|
2622
|
+
const shutdownPath = join(reviewsDir, REVIEWER_SHUTDOWN_SIGNAL);
|
|
2623
|
+
if (!existsSync(reviewsDir)) mkdirSync(reviewsDir, { recursive: true });
|
|
2624
|
+
writeFileSync(shutdownPath, "shutdown");
|
|
2625
|
+
} catch {}
|
|
2582
2626
|
|
|
2583
2627
|
return {
|
|
2584
|
-
content: [{ type: "text" as const, text: `UNAVAILABLE —
|
|
2628
|
+
content: [{ type: "text" as const, text: `UNAVAILABLE — ${skipMsg}` }],
|
|
2585
2629
|
details: undefined,
|
|
2586
2630
|
};
|
|
2587
2631
|
}
|
package/package.json
CHANGED
|
@@ -16,7 +16,7 @@ name: task-reviewer
|
|
|
16
16
|
- Verdict format (APPROVE / REVISE)
|
|
17
17
|
- Review file output conventions
|
|
18
18
|
- Plan granularity guidance
|
|
19
|
-
- Persistent reviewer mode (wait_for_review tool workflow)
|
|
19
|
+
- Persistent reviewer mode (wait_for_review registered tool workflow — NOT bash)
|
|
20
20
|
|
|
21
21
|
Add project-specific review criteria below. Common examples:
|
|
22
22
|
- Required test coverage thresholds
|
|
@@ -17,12 +17,17 @@ You are a **persistent reviewer** that stays alive across all review requests fo
|
|
|
17
17
|
a task. This preserves your context — you remember what you reviewed in earlier
|
|
18
18
|
steps and can reference previous findings.
|
|
19
19
|
|
|
20
|
-
1.
|
|
20
|
+
1. Use the `wait_for_review` tool to receive your first review request.
|
|
21
|
+
IMPORTANT: `wait_for_review` is a REGISTERED EXTENSION TOOL — call it
|
|
22
|
+
the same way you call `read`, `write`, `edit`, or `grep`. Do NOT run it
|
|
23
|
+
via `bash`, `shell`, or any other command-line tool. It is NOT a shell
|
|
24
|
+
command.
|
|
21
25
|
2. The request specifies an **output file path** — you MUST write your review there
|
|
22
26
|
3. Use your tools to explore the codebase — read files, run `git diff`, check patterns
|
|
23
27
|
4. **Use the `write` tool to create the output file with your review**
|
|
24
28
|
5. Use the appropriate verdict: APPROVE, REVISE, or RETHINK
|
|
25
|
-
6.
|
|
29
|
+
6. Use the `wait_for_review` tool again to receive the next request.
|
|
30
|
+
(Same rule: call it as a registered tool, never via bash.)
|
|
26
31
|
7. Repeat until you receive a `SHUTDOWN` signal, then exit cleanly
|
|
27
32
|
|
|
28
33
|
**Cross-step awareness:** When reviewing later steps, reference your earlier
|