@plannotator/pi-extension 0.16.4 → 0.16.6
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 +26 -0
- package/generated/review-core.ts +18 -1
- package/index.ts +78 -229
- package/package.json +3 -1
- package/plannotator-browser.ts +250 -0
- package/plannotator-events.ts +316 -0
- package/plannotator.html +3 -3
- package/review-editor.html +67 -67
- package/server/serverPlan.ts +45 -17
- package/server/serverReview.ts +2 -2
package/README.md
CHANGED
|
@@ -148,6 +148,28 @@ Use these inside `systemPrompt` strings:
|
|
|
148
148
|
|
|
149
149
|
Run `/plannotator-review` to open your current git changes in the code review UI. Annotate specific lines, switch between diff views (uncommitted, staged, last commit, branch), and submit feedback that gets sent to the agent.
|
|
150
150
|
|
|
151
|
+
### Shared Plannotator event API
|
|
152
|
+
|
|
153
|
+
Plannotator also listens on the shared `plannotator:request` event channel so other extensions can reuse the same browser review flows without importing Plannotator internals.
|
|
154
|
+
|
|
155
|
+
Supported actions and payloads:
|
|
156
|
+
|
|
157
|
+
- `plan-review`: `{ planContent, planFilePath? }`
|
|
158
|
+
- `review-status`: `{ reviewId }`
|
|
159
|
+
- `code-review`: `{ cwd?, defaultBranch?, diffType? }`
|
|
160
|
+
- `annotate`: `{ filePath, markdown?, mode?, folderPath? }`
|
|
161
|
+
- `annotate-last`: `{ markdown? }`
|
|
162
|
+
- `archive`: `{ customPlanPath? }`
|
|
163
|
+
|
|
164
|
+
Plan review is asynchronous:
|
|
165
|
+
|
|
166
|
+
- callers send `plannotator:request` with action `plan-review`
|
|
167
|
+
- Plannotator opens the browser review and immediately responds with `{ status: "handled", result: { status: "pending", reviewId } }`
|
|
168
|
+
- when the human approves or rejects in the browser, Plannotator emits `plannotator:review-result` with `{ reviewId, approved, feedback, savedPath?, agentSwitch?, permissionMode? }`
|
|
169
|
+
- callers can query `review-status` with the same `reviewId` to recover from startup races or session restarts
|
|
170
|
+
|
|
171
|
+
The other shared actions remain request/response flows. Payloads are intentionally minimal and only include fields the shared implementation actually uses.
|
|
172
|
+
|
|
151
173
|
### Markdown annotation
|
|
152
174
|
|
|
153
175
|
Run `/plannotator-annotate <file.md>` to open any markdown file in the annotation UI. Useful for reviewing documentation or design specs with the agent.
|
|
@@ -156,6 +178,10 @@ Run `/plannotator-annotate <file.md>` to open any markdown file in the annotatio
|
|
|
156
178
|
|
|
157
179
|
Run `/plannotator-last` to annotate the agent's most recent response. The message opens in the annotation UI where you can highlight text, add comments, and send structured feedback back to the agent.
|
|
158
180
|
|
|
181
|
+
### Archive browser
|
|
182
|
+
|
|
183
|
+
The Plannotator archive browser is available through the shared event API as `archive`, which opens the saved plan/decision browser for future callers. The orchestrator does not expose a dedicated archive command yet.
|
|
184
|
+
|
|
159
185
|
### Progress tracking
|
|
160
186
|
|
|
161
187
|
During execution, the agent marks completed steps with `[DONE:n]` markers. Progress is shown in the status line and as a checklist widget in the terminal.
|
package/generated/review-core.ts
CHANGED
|
@@ -14,7 +14,9 @@ export type DiffType =
|
|
|
14
14
|
| "unstaged"
|
|
15
15
|
| "last-commit"
|
|
16
16
|
| "branch"
|
|
17
|
-
| `worktree:${string}
|
|
17
|
+
| `worktree:${string}`
|
|
18
|
+
| "p4-default"
|
|
19
|
+
| `p4-changelist:${string}`;
|
|
18
20
|
|
|
19
21
|
export interface DiffOption {
|
|
20
22
|
id: string;
|
|
@@ -33,6 +35,7 @@ export interface GitContext {
|
|
|
33
35
|
diffOptions: DiffOption[];
|
|
34
36
|
worktrees: WorktreeInfo[];
|
|
35
37
|
cwd?: string;
|
|
38
|
+
vcsType?: "git" | "p4";
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export interface DiffResult {
|
|
@@ -511,3 +514,17 @@ export async function gitResetFile(
|
|
|
511
514
|
validateFilePath(filePath);
|
|
512
515
|
await ensureGitSuccess(runtime, ["reset", "HEAD", "--", filePath], cwd);
|
|
513
516
|
}
|
|
517
|
+
|
|
518
|
+
export function parseP4DiffType(
|
|
519
|
+
diffType: string,
|
|
520
|
+
): { changelist: string | "default" } | null {
|
|
521
|
+
if (diffType === "p4-default") return { changelist: "default" };
|
|
522
|
+
if (diffType.startsWith("p4-changelist:")) {
|
|
523
|
+
return { changelist: diffType.slice("p4-changelist:".length) };
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export function isP4DiffType(diffType: string): boolean {
|
|
529
|
+
return parseP4DiffType(diffType) !== null;
|
|
530
|
+
}
|
package/index.ts
CHANGED
|
@@ -19,10 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import type { AgentMessage, ThinkingLevel } from "@mariozechner/pi-agent-core";
|
|
25
|
-
import type { AssistantMessage, TextContent } from "@mariozechner/pi-ai";
|
|
22
|
+
import { resolve } from "node:path";
|
|
23
|
+
import type { ThinkingLevel } from "@mariozechner/pi-agent-core";
|
|
26
24
|
import { Type } from "@mariozechner/pi-ai";
|
|
27
25
|
import type {
|
|
28
26
|
ExtensionAPI,
|
|
@@ -38,17 +36,18 @@ import {
|
|
|
38
36
|
import { planDenyFeedback } from "./generated/feedback-templates.js";
|
|
39
37
|
import { hasMarkdownFiles } from "./generated/resolve-file.js";
|
|
40
38
|
import { FILE_BROWSER_EXCLUDED } from "./generated/reference-common.js";
|
|
41
|
-
import { openBrowser } from "./server/network.js";
|
|
42
39
|
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
getLastAssistantMessageText,
|
|
41
|
+
hasPlanBrowserHtml,
|
|
42
|
+
hasReviewBrowserHtml,
|
|
43
|
+
getStartupErrorMessage,
|
|
44
|
+
openArchiveBrowserAction,
|
|
45
|
+
openCodeReview,
|
|
46
|
+
openLastMessageAnnotation,
|
|
47
|
+
openMarkdownAnnotation,
|
|
48
|
+
openPlanReviewBrowser,
|
|
49
|
+
registerPlannotatorEventListeners,
|
|
50
|
+
} from "./plannotator-events.js";
|
|
52
51
|
import {
|
|
53
52
|
getToolsForPhase,
|
|
54
53
|
PLAN_SUBMIT_TOOL,
|
|
@@ -58,35 +57,6 @@ import {
|
|
|
58
57
|
|
|
59
58
|
// ── Types ──────────────────────────────────────────────────────────────
|
|
60
59
|
|
|
61
|
-
/** Common interface for servers that can wait for a user decision in a browser. */
|
|
62
|
-
interface DecisionServer<T> {
|
|
63
|
-
url: string;
|
|
64
|
-
stop: () => void;
|
|
65
|
-
waitForDecision: () => Promise<T>;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Load HTML at runtime (jiti doesn't support import attributes)
|
|
69
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
70
|
-
let planHtmlContent = "";
|
|
71
|
-
let reviewHtmlContent = "";
|
|
72
|
-
try {
|
|
73
|
-
planHtmlContent = readFileSync(
|
|
74
|
-
resolve(__dirname, "plannotator.html"),
|
|
75
|
-
"utf-8",
|
|
76
|
-
);
|
|
77
|
-
} catch {
|
|
78
|
-
// HTML not built yet — browser features will be unavailable
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
reviewHtmlContent = readFileSync(
|
|
82
|
-
resolve(__dirname, "review-editor.html"),
|
|
83
|
-
"utf-8",
|
|
84
|
-
);
|
|
85
|
-
} catch {
|
|
86
|
-
// HTML not built yet — review feature will be unavailable
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
60
|
|
|
91
61
|
type SavedPhaseState = {
|
|
92
62
|
activeTools: string[];
|
|
@@ -100,43 +70,24 @@ type PersistedPlannotatorState = {
|
|
|
100
70
|
savedState?: SavedPhaseState;
|
|
101
71
|
};
|
|
102
72
|
|
|
103
|
-
|
|
73
|
+
type AssistantTextBlock = { type?: string; text?: string };
|
|
74
|
+
|
|
75
|
+
type AssistantMessageLike = {
|
|
76
|
+
role?: unknown;
|
|
77
|
+
content?: unknown;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function isAssistantMessage(m: AssistantMessageLike): m is { role: "assistant"; content: AssistantTextBlock[] } {
|
|
104
81
|
return m.role === "assistant" && Array.isArray(m.content);
|
|
105
82
|
}
|
|
106
83
|
|
|
107
|
-
function getTextContent(message:
|
|
84
|
+
function getTextContent(message: { content: AssistantTextBlock[] }): string {
|
|
108
85
|
return message.content
|
|
109
|
-
.filter((block): block is
|
|
86
|
+
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
|
110
87
|
.map((block) => block.text)
|
|
111
88
|
.join("\n");
|
|
112
89
|
}
|
|
113
90
|
|
|
114
|
-
/**
|
|
115
|
-
* Open browser for user review, wait for decision, then stop server.
|
|
116
|
-
* Handles remote session notification automatically.
|
|
117
|
-
*/
|
|
118
|
-
async function runBrowserReview<T>(
|
|
119
|
-
server: DecisionServer<T>,
|
|
120
|
-
ctx: ExtensionContext,
|
|
121
|
-
): Promise<T> {
|
|
122
|
-
const browserResult = openBrowser(server.url);
|
|
123
|
-
if (browserResult.isRemote) {
|
|
124
|
-
ctx.ui.notify(
|
|
125
|
-
`Remote session. Open manually: ${browserResult.url}`,
|
|
126
|
-
"info",
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const result = await server.waitForDecision();
|
|
131
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
132
|
-
server.stop();
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function getStartupErrorMessage(err: unknown): string {
|
|
137
|
-
return err instanceof Error ? err.message : "Unknown error";
|
|
138
|
-
}
|
|
139
|
-
|
|
140
91
|
function getPlanReviewAvailabilityWarning(options: { hasUI: boolean; hasPlanHtml: boolean }): string | null {
|
|
141
92
|
const { hasUI, hasPlanHtml } = options;
|
|
142
93
|
if (hasUI && hasPlanHtml) return null;
|
|
@@ -151,6 +102,7 @@ function getPlanReviewAvailabilityWarning(options: { hasUI: boolean; hasPlanHtml
|
|
|
151
102
|
|
|
152
103
|
export default function plannotator(pi: ExtensionAPI): void {
|
|
153
104
|
let phase: Phase = "idle";
|
|
105
|
+
void registerPlannotatorEventListeners(pi);
|
|
154
106
|
let planFilePath = "PLAN.md";
|
|
155
107
|
let checklistItems: ChecklistItem[] = [];
|
|
156
108
|
let savedState: SavedPhaseState | null = null;
|
|
@@ -296,7 +248,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
296
248
|
ctx.ui.notify(
|
|
297
249
|
`Plannotator: planning mode enabled. Write your plan to ${planFilePath}.`,
|
|
298
250
|
);
|
|
299
|
-
const warning = getPlanReviewAvailabilityWarning({ hasUI: ctx.hasUI, hasPlanHtml:
|
|
251
|
+
const warning = getPlanReviewAvailabilityWarning({ hasUI: ctx.hasUI, hasPlanHtml: hasPlanBrowserHtml() });
|
|
300
252
|
if (warning) {
|
|
301
253
|
ctx.ui.notify(warning, "warning");
|
|
302
254
|
}
|
|
@@ -383,58 +335,34 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
383
335
|
pi.registerCommand("plannotator-review", {
|
|
384
336
|
description: "Open interactive code review for current changes",
|
|
385
337
|
handler: async (_args, ctx) => {
|
|
386
|
-
if (!
|
|
338
|
+
if (!hasReviewBrowserHtml()) {
|
|
387
339
|
ctx.ui.notify(
|
|
388
|
-
"
|
|
340
|
+
"Code review UI not available. Run 'bun run build' in the pi-extension directory.",
|
|
389
341
|
"error",
|
|
390
342
|
);
|
|
391
343
|
return;
|
|
392
344
|
}
|
|
393
345
|
|
|
394
|
-
ctx.ui.notify("Opening code review UI...", "info");
|
|
395
|
-
|
|
396
|
-
let server: ReviewServerResult;
|
|
397
346
|
try {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
htmlContent: reviewHtmlContent,
|
|
413
|
-
sharingEnabled: process.env.PLANNOTATOR_SHARE !== "disabled",
|
|
414
|
-
shareBaseUrl: process.env.PLANNOTATOR_SHARE_URL || undefined,
|
|
415
|
-
});
|
|
347
|
+
const result = await openCodeReview(ctx);
|
|
348
|
+
if (result.feedback) {
|
|
349
|
+
if (result.approved) {
|
|
350
|
+
pi.sendUserMessage(
|
|
351
|
+
`# Code Review\n\nCode review completed — no changes requested.`,
|
|
352
|
+
);
|
|
353
|
+
} else {
|
|
354
|
+
pi.sendUserMessage(
|
|
355
|
+
`${result.feedback}\n\nPlease address this feedback.`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
ctx.ui.notify("Code review closed (no feedback).", "info");
|
|
360
|
+
}
|
|
416
361
|
} catch (err) {
|
|
417
362
|
ctx.ui.notify(
|
|
418
363
|
`Failed to start code review UI: ${getStartupErrorMessage(err)}`,
|
|
419
364
|
"error",
|
|
420
365
|
);
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const result = await runBrowserReview(server, ctx);
|
|
425
|
-
|
|
426
|
-
if (result.feedback) {
|
|
427
|
-
if (result.approved) {
|
|
428
|
-
pi.sendUserMessage(
|
|
429
|
-
`# Code Review\n\nCode review completed — no changes requested.`,
|
|
430
|
-
);
|
|
431
|
-
} else {
|
|
432
|
-
pi.sendUserMessage(
|
|
433
|
-
`${result.feedback}\n\nPlease address this feedback.`,
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
} else {
|
|
437
|
-
ctx.ui.notify("Code review closed (no feedback).", "info");
|
|
438
366
|
}
|
|
439
367
|
},
|
|
440
368
|
});
|
|
@@ -447,7 +375,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
447
375
|
ctx.ui.notify("Usage: /plannotator-annotate <file.md | folder/>", "error");
|
|
448
376
|
return;
|
|
449
377
|
}
|
|
450
|
-
if (!
|
|
378
|
+
if (!hasPlanBrowserHtml()) {
|
|
451
379
|
ctx.ui.notify(
|
|
452
380
|
"Annotation UI not available. Run 'bun run build' in the pi-extension directory.",
|
|
453
381
|
"error",
|
|
@@ -472,7 +400,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
472
400
|
|
|
473
401
|
let markdown: string;
|
|
474
402
|
let folderPath: string | undefined;
|
|
475
|
-
let mode:
|
|
403
|
+
let mode: "annotate" | "annotate-folder" | undefined;
|
|
476
404
|
|
|
477
405
|
if (isFolder) {
|
|
478
406
|
if (!hasMarkdownFiles(absolutePath, FILE_BROWSER_EXCLUDED)) {
|
|
@@ -488,38 +416,23 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
488
416
|
ctx.ui.notify(`Opening annotation UI for ${filePath}...`, "info");
|
|
489
417
|
}
|
|
490
418
|
|
|
491
|
-
let server: AnnotateServerResult;
|
|
492
419
|
try {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
420
|
+
const result = await openMarkdownAnnotation(ctx, absolutePath, markdown, mode ?? "annotate", folderPath);
|
|
421
|
+
if (result.feedback) {
|
|
422
|
+
const header = isFolder
|
|
423
|
+
? `# Markdown Annotations\n\nFolder: ${absolutePath}\n\n`
|
|
424
|
+
: `# Markdown Annotations\n\nFile: ${absolutePath}\n\n`;
|
|
425
|
+
pi.sendUserMessage(
|
|
426
|
+
`${header}${result.feedback}\n\nPlease address the annotation feedback above.`,
|
|
427
|
+
);
|
|
428
|
+
} else {
|
|
429
|
+
ctx.ui.notify("Annotation closed (no feedback).", "info");
|
|
430
|
+
}
|
|
504
431
|
} catch (err) {
|
|
505
432
|
ctx.ui.notify(
|
|
506
433
|
`Failed to start annotation UI: ${getStartupErrorMessage(err)}`,
|
|
507
434
|
"error",
|
|
508
435
|
);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
const result = await runBrowserReview(server, ctx);
|
|
513
|
-
|
|
514
|
-
if (result.feedback) {
|
|
515
|
-
const header = isFolder
|
|
516
|
-
? `# Markdown Annotations\n\nFolder: ${absolutePath}\n\n`
|
|
517
|
-
: `# Markdown Annotations\n\nFile: ${absolutePath}\n\n`;
|
|
518
|
-
pi.sendUserMessage(
|
|
519
|
-
`${header}${result.feedback}\n\nPlease address the annotation feedback above.`,
|
|
520
|
-
);
|
|
521
|
-
} else {
|
|
522
|
-
ctx.ui.notify("Annotation closed (no feedback).", "info");
|
|
523
436
|
}
|
|
524
437
|
},
|
|
525
438
|
});
|
|
@@ -527,7 +440,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
527
440
|
pi.registerCommand("plannotator-last", {
|
|
528
441
|
description: "Annotate the last assistant message",
|
|
529
442
|
handler: async (_args, ctx) => {
|
|
530
|
-
if (!
|
|
443
|
+
if (!hasPlanBrowserHtml()) {
|
|
531
444
|
ctx.ui.notify(
|
|
532
445
|
"Annotation UI not available. Run 'bun run build' in the pi-extension directory.",
|
|
533
446
|
"error",
|
|
@@ -535,23 +448,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
535
448
|
return;
|
|
536
449
|
}
|
|
537
450
|
|
|
538
|
-
const
|
|
539
|
-
let lastText: string | null = null;
|
|
540
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
541
|
-
const entry = entries[i] as { type: string; message?: AgentMessage };
|
|
542
|
-
if (
|
|
543
|
-
entry.type === "message" &&
|
|
544
|
-
entry.message &&
|
|
545
|
-
isAssistantMessage(entry.message)
|
|
546
|
-
) {
|
|
547
|
-
const text = getTextContent(entry.message);
|
|
548
|
-
if (text.trim()) {
|
|
549
|
-
lastText = text;
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
451
|
+
const lastText = await getLastAssistantMessageText(ctx);
|
|
555
452
|
if (!lastText) {
|
|
556
453
|
ctx.ui.notify("No assistant message found in session.", "error");
|
|
557
454
|
return;
|
|
@@ -559,34 +456,20 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
559
456
|
|
|
560
457
|
ctx.ui.notify("Opening annotation UI for last message...", "info");
|
|
561
458
|
|
|
562
|
-
let server: AnnotateServerResult;
|
|
563
459
|
try {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
pasteApiUrl: process.env.PLANNOTATOR_PASTE_URL || undefined,
|
|
573
|
-
});
|
|
460
|
+
const result = await openLastMessageAnnotation(ctx, lastText);
|
|
461
|
+
if (result.feedback) {
|
|
462
|
+
pi.sendUserMessage(
|
|
463
|
+
`# Message Annotations\n\n${result.feedback}\n\nPlease address the annotation feedback above.`,
|
|
464
|
+
);
|
|
465
|
+
} else {
|
|
466
|
+
ctx.ui.notify("Annotation closed (no feedback).", "info");
|
|
467
|
+
}
|
|
574
468
|
} catch (err) {
|
|
575
469
|
ctx.ui.notify(
|
|
576
470
|
`Failed to start annotation UI: ${getStartupErrorMessage(err)}`,
|
|
577
471
|
"error",
|
|
578
472
|
);
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
const result = await runBrowserReview(server, ctx);
|
|
583
|
-
|
|
584
|
-
if (result.feedback) {
|
|
585
|
-
pi.sendUserMessage(
|
|
586
|
-
`# Message Annotations\n\n${result.feedback}\n\nPlease address the annotation feedback above.`,
|
|
587
|
-
);
|
|
588
|
-
} else {
|
|
589
|
-
ctx.ui.notify("Annotation closed (no feedback).", "info");
|
|
590
473
|
}
|
|
591
474
|
},
|
|
592
475
|
});
|
|
@@ -594,7 +477,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
594
477
|
pi.registerCommand("plannotator-archive", {
|
|
595
478
|
description: "Browse saved plan decisions",
|
|
596
479
|
handler: async (_args, ctx) => {
|
|
597
|
-
if (!
|
|
480
|
+
if (!hasPlanBrowserHtml()) {
|
|
598
481
|
ctx.ui.notify(
|
|
599
482
|
"Archive UI not available. Run 'bun run build' in the pi-extension directory.",
|
|
600
483
|
"error",
|
|
@@ -604,39 +487,15 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
604
487
|
|
|
605
488
|
ctx.ui.notify("Opening plan archive...", "info");
|
|
606
489
|
|
|
607
|
-
let server: PlanServerResult;
|
|
608
490
|
try {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
htmlContent: planHtmlContent,
|
|
612
|
-
origin: "pi",
|
|
613
|
-
mode: "archive",
|
|
614
|
-
sharingEnabled: process.env.PLANNOTATOR_SHARE !== "disabled",
|
|
615
|
-
shareBaseUrl: process.env.PLANNOTATOR_SHARE_URL || undefined,
|
|
616
|
-
pasteApiUrl: process.env.PLANNOTATOR_PASTE_URL || undefined,
|
|
617
|
-
});
|
|
491
|
+
await openArchiveBrowserAction(ctx);
|
|
492
|
+
ctx.ui.notify("Archive browser closed.", "info");
|
|
618
493
|
} catch (err) {
|
|
619
494
|
ctx.ui.notify(
|
|
620
495
|
`Failed to start archive: ${getStartupErrorMessage(err)}`,
|
|
621
496
|
"error",
|
|
622
497
|
);
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const browserResult = openBrowser(server.url);
|
|
627
|
-
if (browserResult.isRemote) {
|
|
628
|
-
ctx.ui.notify(
|
|
629
|
-
`Remote session. Open manually: ${browserResult.url}`,
|
|
630
|
-
"info",
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
if (server.waitForDone) {
|
|
635
|
-
await server.waitForDone();
|
|
636
498
|
}
|
|
637
|
-
await new Promise((r) => setTimeout(r, 1500));
|
|
638
|
-
server.stop();
|
|
639
|
-
ctx.ui.notify("Archive browser closed.", "info");
|
|
640
499
|
},
|
|
641
500
|
});
|
|
642
501
|
|
|
@@ -663,7 +522,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
663
522
|
description: "Brief summary of the plan for the user's review",
|
|
664
523
|
}),
|
|
665
524
|
),
|
|
666
|
-
}),
|
|
525
|
+
}) as any,
|
|
667
526
|
|
|
668
527
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
669
528
|
// Guard: must be in planning phase
|
|
@@ -712,7 +571,7 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
712
571
|
checklistItems = parseChecklist(planContent);
|
|
713
572
|
|
|
714
573
|
// Non-interactive or no HTML: auto-approve
|
|
715
|
-
if (!ctx.hasUI || !
|
|
574
|
+
if (!ctx.hasUI || !hasPlanBrowserHtml()) {
|
|
716
575
|
phase = "executing";
|
|
717
576
|
|
|
718
577
|
|
|
@@ -730,17 +589,9 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
730
589
|
};
|
|
731
590
|
}
|
|
732
591
|
|
|
733
|
-
|
|
734
|
-
let server: PlanServerResult;
|
|
592
|
+
let result: Awaited<ReturnType<typeof openPlanReviewBrowser>>;
|
|
735
593
|
try {
|
|
736
|
-
|
|
737
|
-
plan: planContent,
|
|
738
|
-
htmlContent: planHtmlContent,
|
|
739
|
-
origin: "pi",
|
|
740
|
-
sharingEnabled: process.env.PLANNOTATOR_SHARE !== "disabled",
|
|
741
|
-
shareBaseUrl: process.env.PLANNOTATOR_SHARE_URL || undefined,
|
|
742
|
-
pasteApiUrl: process.env.PLANNOTATOR_PASTE_URL || undefined,
|
|
743
|
-
});
|
|
594
|
+
result = await openPlanReviewBrowser(ctx, planContent);
|
|
744
595
|
} catch (err) {
|
|
745
596
|
const message = `Failed to start plan review UI: ${getStartupErrorMessage(err)}`;
|
|
746
597
|
ctx.ui.notify(message, "error");
|
|
@@ -750,8 +601,6 @@ export default function plannotator(pi: ExtensionAPI): void {
|
|
|
750
601
|
};
|
|
751
602
|
}
|
|
752
603
|
|
|
753
|
-
const result = await runBrowserReview(server, ctx);
|
|
754
|
-
|
|
755
604
|
if (result.approved) {
|
|
756
605
|
phase = "executing";
|
|
757
606
|
|
|
@@ -980,7 +829,7 @@ Execute each step in order. After completing a step, include [DONE:n] in your re
|
|
|
980
829
|
|
|
981
830
|
return {
|
|
982
831
|
messages: event.messages.filter((m) => {
|
|
983
|
-
const msg = m as
|
|
832
|
+
const msg = m as { customType?: string; role?: string; content?: unknown };
|
|
984
833
|
if (msg.customType === "plannotator-context") return false;
|
|
985
834
|
if (msg.role !== "user") return true;
|
|
986
835
|
|
|
@@ -992,7 +841,7 @@ Execute each step in order. After completing a step, include [DONE:n] in your re
|
|
|
992
841
|
return !content.some(
|
|
993
842
|
(c) =>
|
|
994
843
|
c.type === "text" &&
|
|
995
|
-
(c as
|
|
844
|
+
(c as { text?: string }).text?.includes("[PLANNOTATOR -"),
|
|
996
845
|
);
|
|
997
846
|
}
|
|
998
847
|
return true;
|
|
@@ -1003,9 +852,9 @@ Execute each step in order. After completing a step, include [DONE:n] in your re
|
|
|
1003
852
|
// Track execution progress
|
|
1004
853
|
pi.on("turn_end", async (event, ctx) => {
|
|
1005
854
|
if (phase !== "executing" || checklistItems.length === 0) return;
|
|
1006
|
-
if (!isAssistantMessage(event.message)) return;
|
|
855
|
+
if (!isAssistantMessage(event.message as AssistantMessageLike)) return;
|
|
1007
856
|
|
|
1008
|
-
const text = getTextContent(event.message);
|
|
857
|
+
const text = getTextContent(event.message as { content: AssistantTextBlock[] });
|
|
1009
858
|
if (markCompletedSteps(text, checklistItems) > 0) {
|
|
1010
859
|
updateStatus(ctx);
|
|
1011
860
|
updateWidget(ctx);
|
|
@@ -1101,9 +950,9 @@ Execute each step in order. After completing a step, include [DONE:n] in your re
|
|
|
1101
950
|
if (
|
|
1102
951
|
entry.type === "message" &&
|
|
1103
952
|
"message" in entry &&
|
|
1104
|
-
isAssistantMessage(entry.message as
|
|
953
|
+
isAssistantMessage(entry.message as AssistantMessageLike)
|
|
1105
954
|
) {
|
|
1106
|
-
const text = getTextContent(entry.message as
|
|
955
|
+
const text = getTextContent(entry.message as { content: AssistantTextBlock[] });
|
|
1107
956
|
markCompletedSteps(text, checklistItems);
|
|
1108
957
|
}
|
|
1109
958
|
}
|
|
@@ -1117,7 +966,7 @@ Execute each step in order. After completing a step, include [DONE:n] in your re
|
|
|
1117
966
|
|
|
1118
967
|
if (phase === "planning") {
|
|
1119
968
|
checklistItems = [];
|
|
1120
|
-
const warning = getPlanReviewAvailabilityWarning({ hasUI: ctx.hasUI, hasPlanHtml:
|
|
969
|
+
const warning = getPlanReviewAvailabilityWarning({ hasUI: ctx.hasUI, hasPlanHtml: hasPlanBrowserHtml() });
|
|
1121
970
|
if (warning) {
|
|
1122
971
|
ctx.ui.notify(warning, "warning");
|
|
1123
972
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plannotator/pi-extension",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Plannotator extension for Pi coding agent - interactive plan review with visual annotation",
|
|
6
6
|
"author": "backnotprop",
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"index.ts",
|
|
24
|
+
"plannotator-browser.ts",
|
|
25
|
+
"plannotator-events.ts",
|
|
24
26
|
"server.ts",
|
|
25
27
|
"tool-scope.ts",
|
|
26
28
|
"config.ts",
|