@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 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.
@@ -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 { dirname, resolve } from "node:path";
23
- import { fileURLToPath } from "node:url";
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
- type AnnotateServerResult,
44
- getGitContext,
45
- type PlanServerResult,
46
- type ReviewServerResult,
47
- runGitDiff,
48
- startAnnotateServer,
49
- startPlanReviewServer,
50
- startReviewServer,
51
- } from "./server.js";
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
- function isAssistantMessage(m: AgentMessage): m is AssistantMessage {
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: AssistantMessage): string {
84
+ function getTextContent(message: { content: AssistantTextBlock[] }): string {
108
85
  return message.content
109
- .filter((block): block is TextContent => block.type === "text")
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: Boolean(planHtmlContent) });
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 (!reviewHtmlContent) {
338
+ if (!hasReviewBrowserHtml()) {
387
339
  ctx.ui.notify(
388
- "Review UI not available. Run 'bun run build' in the pi-extension directory.",
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 gitCtx = await getGitContext();
399
- const {
400
- patch: rawPatch,
401
- label: gitRef,
402
- error,
403
- } = await runGitDiff("uncommitted", gitCtx.defaultBranch);
404
-
405
- server = await startReviewServer({
406
- rawPatch,
407
- gitRef,
408
- error,
409
- origin: "pi",
410
- diffType: "uncommitted",
411
- gitContext: gitCtx,
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 (!planHtmlContent) {
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: string | undefined;
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
- server = await startAnnotateServer({
494
- markdown,
495
- filePath: absolutePath,
496
- origin: "pi",
497
- mode,
498
- folderPath,
499
- htmlContent: planHtmlContent,
500
- sharingEnabled: process.env.PLANNOTATOR_SHARE !== "disabled",
501
- shareBaseUrl: process.env.PLANNOTATOR_SHARE_URL || undefined,
502
- pasteApiUrl: process.env.PLANNOTATOR_PASTE_URL || undefined,
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 (!planHtmlContent) {
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 entries = ctx.sessionManager.getEntries();
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
- server = await startAnnotateServer({
565
- markdown: lastText,
566
- filePath: "last-message",
567
- origin: "pi",
568
- mode: "annotate-last",
569
- htmlContent: planHtmlContent,
570
- sharingEnabled: process.env.PLANNOTATOR_SHARE !== "disabled",
571
- shareBaseUrl: process.env.PLANNOTATOR_SHARE_URL || undefined,
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 (!planHtmlContent) {
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
- server = await startPlanReviewServer({
610
- plan: "",
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 || !planHtmlContent) {
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
- // Start browser-based plan review server
734
- let server: PlanServerResult;
592
+ let result: Awaited<ReturnType<typeof openPlanReviewBrowser>>;
735
593
  try {
736
- server = await startPlanReviewServer({
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 AgentMessage & { customType?: string };
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 TextContent).text?.includes("[PLANNOTATOR -"),
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 AgentMessage)
953
+ isAssistantMessage(entry.message as AssistantMessageLike)
1105
954
  ) {
1106
- const text = getTextContent(entry.message as AssistantMessage);
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: Boolean(planHtmlContent) });
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.4",
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",