taskplane 0.11.0 → 0.13.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 +4 -0
- package/extensions/reviewer-extension.ts +119 -0
- package/extensions/task-runner.ts +388 -43
- package/extensions/taskplane/engine.ts +35 -13
- package/extensions/taskplane/merge.ts +358 -2
- package/extensions/taskplane/supervisor-primer.md +35 -3
- package/extensions/taskplane/supervisor.ts +23 -0
- package/extensions/taskplane/types.ts +141 -0
- package/package.json +2 -1
- package/templates/agents/local/task-reviewer.md +1 -0
- package/templates/agents/task-reviewer.md +26 -0
package/dashboard/server.cjs
CHANGED
|
@@ -437,6 +437,10 @@ function loadTelemetryData(batchState) {
|
|
|
437
437
|
for (const event of events) {
|
|
438
438
|
switch (event.type) {
|
|
439
439
|
case "message_end": {
|
|
440
|
+
// A successful message_end means any prior retry resolved.
|
|
441
|
+
// Clear retryActive to prevent stale retry badges from persisting
|
|
442
|
+
// across batches or after transient API errors recover.
|
|
443
|
+
acc.retryActive = false;
|
|
440
444
|
const usage = event.message?.usage;
|
|
441
445
|
if (usage) {
|
|
442
446
|
acc.inputTokens += usage.input || 0;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent Reviewer Extension — TP-057
|
|
3
|
+
*
|
|
4
|
+
* Provides the `wait_for_review` tool that enables a reviewer agent to stay
|
|
5
|
+
* alive across multiple review requests within a single task. The tool blocks
|
|
6
|
+
* (via filesystem polling) until the task-runner signals a new review request
|
|
7
|
+
* or shutdown.
|
|
8
|
+
*
|
|
9
|
+
* Signal protocol:
|
|
10
|
+
* - `.reviews/.review-signal-{NNN}` — new review request available
|
|
11
|
+
* - `.reviews/.review-shutdown` — reviewer should exit cleanly
|
|
12
|
+
* - `.reviews/request-R{NNN}.md` — review request content
|
|
13
|
+
*
|
|
14
|
+
* Environment:
|
|
15
|
+
* - REVIEWER_SIGNAL_DIR — path to .reviews/ directory (required)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
19
|
+
import { Type } from "@mariozechner/pi-ai";
|
|
20
|
+
import { existsSync, readFileSync } from "fs";
|
|
21
|
+
import { join } from "path";
|
|
22
|
+
import {
|
|
23
|
+
REVIEWER_POLL_INTERVAL_MS,
|
|
24
|
+
REVIEWER_WAIT_TIMEOUT_MS,
|
|
25
|
+
REVIEWER_SHUTDOWN_SIGNAL,
|
|
26
|
+
REVIEWER_SIGNAL_PREFIX,
|
|
27
|
+
} from "./taskplane/types.ts";
|
|
28
|
+
|
|
29
|
+
// ── Extension ────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export default function reviewerExtension(pi: ExtensionAPI) {
|
|
32
|
+
const signalDir = process.env.REVIEWER_SIGNAL_DIR;
|
|
33
|
+
|
|
34
|
+
if (!signalDir) {
|
|
35
|
+
// Not running in persistent reviewer mode — skip tool registration.
|
|
36
|
+
// This allows the extension to be loaded in non-persistent contexts
|
|
37
|
+
// without error (fallback fresh-spawn mode).
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Counter tracking which signal number to watch for next. */
|
|
42
|
+
let nextSignalNum = 1;
|
|
43
|
+
|
|
44
|
+
pi.registerTool({
|
|
45
|
+
name: "wait_for_review",
|
|
46
|
+
label: "Wait for Review",
|
|
47
|
+
description:
|
|
48
|
+
"Block until the next review request is available, then return its content. " +
|
|
49
|
+
"Call this after completing each review to wait for the next one. " +
|
|
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)",
|
|
52
|
+
promptGuidelines: [
|
|
53
|
+
"Call wait_for_review() to receive each review request.",
|
|
54
|
+
"After writing your review to the specified output file, call wait_for_review() again.",
|
|
55
|
+
"When it returns 'SHUTDOWN', exit cleanly — the task is complete.",
|
|
56
|
+
"Reference your previous reviews when relevant (e.g., 'I flagged X in Step 1 — checking if addressed').",
|
|
57
|
+
],
|
|
58
|
+
parameters: Type.Object({}),
|
|
59
|
+
async execute() {
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
const signalNum = String(nextSignalNum).padStart(3, "0");
|
|
62
|
+
const signalPath = join(signalDir, `${REVIEWER_SIGNAL_PREFIX}${signalNum}`);
|
|
63
|
+
const shutdownPath = join(signalDir, REVIEWER_SHUTDOWN_SIGNAL);
|
|
64
|
+
|
|
65
|
+
// Poll for signal file or shutdown
|
|
66
|
+
while (true) {
|
|
67
|
+
// Check for shutdown signal first
|
|
68
|
+
if (existsSync(shutdownPath)) {
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: "text" as const, text: "SHUTDOWN — The task is complete. Exit cleanly." }],
|
|
71
|
+
details: undefined,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check for review signal
|
|
76
|
+
if (existsSync(signalPath)) {
|
|
77
|
+
// Signal found — read the request file path from signal content.
|
|
78
|
+
// Signal file content is the request filename (e.g., "request-R003.md").
|
|
79
|
+
const signalContent = readFileSync(signalPath, "utf-8").trim();
|
|
80
|
+
const requestPath = join(signalDir, signalContent);
|
|
81
|
+
|
|
82
|
+
if (!existsSync(requestPath)) {
|
|
83
|
+
// Signal fired but request file doesn't exist (race condition or error)
|
|
84
|
+
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
|
+
}],
|
|
90
|
+
details: undefined,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const requestContent = readFileSync(requestPath, "utf-8");
|
|
95
|
+
nextSignalNum++;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text" as const, text: requestContent }],
|
|
99
|
+
details: undefined,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check timeout
|
|
104
|
+
if (Date.now() - startTime > REVIEWER_WAIT_TIMEOUT_MS) {
|
|
105
|
+
return {
|
|
106
|
+
content: [{
|
|
107
|
+
type: "text" as const,
|
|
108
|
+
text: "TIMEOUT — No review request received within the timeout period. Exit cleanly.",
|
|
109
|
+
}],
|
|
110
|
+
details: undefined,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Wait before next poll
|
|
115
|
+
await new Promise(resolve => setTimeout(resolve, REVIEWER_POLL_INTERVAL_MS));
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|