@gavin-hu/my-pi-agent 0.16.0 → 0.18.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 (200) hide show
  1. package/README.md +29 -15
  2. package/README.zh-CN.md +27 -15
  3. package/extensions/bytefly/README.md +195 -115
  4. package/extensions/bytefly/footer/collect/entries.ts +1 -6
  5. package/extensions/bytefly/footer/collect/snapshot.ts +23 -9
  6. package/extensions/bytefly/footer/price/price.ts +12 -8
  7. package/extensions/bytefly/footer/register.ts +13 -1
  8. package/extensions/bytefly/footer/types.ts +11 -0
  9. package/extensions/bytefly/footer/unpriced.ts +36 -0
  10. package/extensions/bytefly/header/component.ts +15 -29
  11. package/extensions/bytefly/header/identity.ts +71 -0
  12. package/extensions/bytefly/header/register.ts +9 -3
  13. package/extensions/bytefly/index.ts +13 -5
  14. package/extensions/bytefly/message/format.ts +20 -13
  15. package/extensions/bytefly/message/register.ts +8 -7
  16. package/extensions/bytefly/notify/types.ts +1 -1
  17. package/extensions/bytefly/panel/commands.ts +46 -25
  18. package/extensions/bytefly/panel/format.ts +49 -14
  19. package/extensions/bytefly/panel/join.ts +35 -9
  20. package/extensions/bytefly/panel/settings.ts +29 -39
  21. package/extensions/bytefly/panel/skills.ts +51 -0
  22. package/extensions/bytefly/panel/tui/extensions-view.ts +7 -61
  23. package/extensions/bytefly/panel/tui/frame.ts +9 -1
  24. package/extensions/bytefly/panel/tui/list-view.ts +72 -0
  25. package/extensions/bytefly/panel/tui/nav.ts +71 -3
  26. package/extensions/bytefly/panel/tui/settings-section.ts +250 -0
  27. package/extensions/bytefly/panel/tui/skills-view.ts +50 -0
  28. package/extensions/bytefly/panel/tui.ts +223 -331
  29. package/extensions/bytefly/panel/types.ts +26 -8
  30. package/extensions/bytefly/picker/discovery.ts +32 -27
  31. package/extensions/bytefly/picker/resources.ts +16 -8
  32. package/extensions/bytefly/picker/runtime.ts +62 -34
  33. package/extensions/bytefly/picker/toggle.ts +16 -10
  34. package/extensions/bytefly/picker/types.ts +31 -13
  35. package/extensions/bytefly/update/format.ts +66 -8
  36. package/extensions/bytefly/update/patch.ts +66 -0
  37. package/extensions/bytefly/update/register.ts +4 -11
  38. package/extensions/bytefly/update/release.ts +65 -0
  39. package/extensions/bytefly/update/state.ts +14 -0
  40. package/extensions/bytefly/update/suppress.ts +47 -29
  41. package/extensions/bytefly/update/types.ts +15 -5
  42. package/extensions/check/tui.ts +2 -0
  43. package/extensions/context/README.md +21 -11
  44. package/extensions/context/format.ts +10 -8
  45. package/extensions/context/report.ts +21 -8
  46. package/extensions/context/tui.ts +26 -10
  47. package/extensions/context/types.ts +5 -3
  48. package/extensions/file-browse/README.md +89 -22
  49. package/extensions/file-browse/chrome.ts +83 -0
  50. package/extensions/file-browse/client.ts +74 -459
  51. package/extensions/file-browse/config.ts +12 -3
  52. package/extensions/file-browse/diff-html.ts +94 -0
  53. package/extensions/file-browse/html.ts +26 -152
  54. package/extensions/file-browse/live-client.ts +47 -1
  55. package/extensions/file-browse/live.ts +35 -14
  56. package/extensions/file-browse/palette.ts +224 -0
  57. package/extensions/file-browse/rev.ts +42 -0
  58. package/extensions/file-browse/router.ts +27 -6
  59. package/extensions/file-browse/server.ts +7 -0
  60. package/extensions/file-browse/styles.ts +10 -3
  61. package/extensions/file-browse/tree.ts +381 -0
  62. package/extensions/file-read/README.md +4 -1
  63. package/extensions/file-read/extract/odt.ts +7 -5
  64. package/extensions/goal/plan-route.ts +8 -2
  65. package/extensions/goal/tools.ts +6 -11
  66. package/extensions/job/README.md +7 -5
  67. package/extensions/job/index.ts +35 -52
  68. package/extensions/job/tools.ts +51 -77
  69. package/extensions/memory/README.md +3 -3
  70. package/extensions/memory/tools.ts +4 -6
  71. package/extensions/memory/tui.ts +5 -1
  72. package/extensions/plan/README.md +333 -84
  73. package/extensions/plan/commands.ts +73 -7
  74. package/extensions/plan/feedback.ts +300 -63
  75. package/extensions/plan/index.ts +1 -1
  76. package/extensions/plan/plan-index.ts +4 -2
  77. package/extensions/plan/plan-text.ts +18 -1
  78. package/extensions/plan/plans.ts +184 -58
  79. package/extensions/plan/review-prompt.ts +93 -0
  80. package/extensions/plan/review.ts +95 -95
  81. package/extensions/plan/runtime.ts +4 -2
  82. package/extensions/plan/split.ts +87 -0
  83. package/extensions/plan/steps.ts +123 -107
  84. package/extensions/plan/tools/cancel.ts +79 -0
  85. package/extensions/plan/tools/enter.ts +17 -12
  86. package/extensions/plan/tools/exit.ts +148 -59
  87. package/extensions/plan/tools/steps.ts +7 -19
  88. package/extensions/plan/tools/write.ts +29 -31
  89. package/extensions/plan/tools.ts +6 -3
  90. package/extensions/plan/tui.ts +60 -15
  91. package/extensions/plan/types.ts +45 -3
  92. package/extensions/plan/web-chrome.ts +102 -0
  93. package/extensions/plan/web-client.ts +177 -446
  94. package/extensions/plan/web-collab.ts +207 -0
  95. package/extensions/plan/web-comments.ts +269 -0
  96. package/extensions/plan/web-markdown.ts +222 -0
  97. package/extensions/plan/web-page.ts +131 -19
  98. package/extensions/plan/web-router.ts +148 -32
  99. package/extensions/plan/web-share-session.ts +197 -0
  100. package/extensions/plan/web-share.ts +169 -0
  101. package/extensions/plan/web-streams.ts +124 -0
  102. package/extensions/plan/web-tokens.ts +63 -0
  103. package/extensions/plan/web.ts +193 -45
  104. package/extensions/question/README.md +3 -0
  105. package/extensions/question/dialogs.ts +91 -1
  106. package/extensions/question/tools.ts +2 -1
  107. package/extensions/rewind/checklist.ts +4 -0
  108. package/extensions/rewind/review-tui.ts +3 -1
  109. package/extensions/rewind/rewind-tui.ts +4 -0
  110. package/extensions/session-bus/README.md +247 -0
  111. package/extensions/session-bus/commands.ts +165 -0
  112. package/extensions/session-bus/format.ts +124 -0
  113. package/extensions/session-bus/index.ts +69 -0
  114. package/extensions/session-bus/notice.ts +58 -0
  115. package/extensions/session-bus/protocol.ts +134 -0
  116. package/extensions/session-bus/result.ts +243 -0
  117. package/extensions/session-bus/rows.ts +169 -0
  118. package/extensions/session-bus/runtime.ts +575 -0
  119. package/extensions/session-bus/store.ts +329 -0
  120. package/extensions/session-bus/team.ts +102 -0
  121. package/extensions/session-bus/tools.ts +227 -0
  122. package/extensions/session-bus/tui.ts +394 -0
  123. package/extensions/session-bus/types.ts +98 -0
  124. package/extensions/subagent/README.md +26 -13
  125. package/extensions/subagent/format.ts +72 -25
  126. package/extensions/subagent/index.ts +21 -47
  127. package/extensions/subagent/orchestrate.ts +18 -41
  128. package/extensions/subagent/render.ts +29 -42
  129. package/extensions/subagent/stream.ts +24 -18
  130. package/extensions/subagent/types.ts +7 -0
  131. package/extensions/todo/tools.ts +5 -6
  132. package/extensions/todo/tui.ts +5 -1
  133. package/extensions/watch/README.md +9 -5
  134. package/extensions/watch/index.ts +25 -44
  135. package/extensions/watch/tools.ts +31 -59
  136. package/extensions/wechat/README.md +45 -36
  137. package/extensions/wechat/bridge.ts +46 -48
  138. package/extensions/wechat/client.ts +1 -4
  139. package/extensions/wechat/commands.ts +3 -3
  140. package/extensions/wechat/deliver.ts +57 -37
  141. package/extensions/wechat/interaction.ts +54 -18
  142. package/extensions/wechat/login-screen.ts +2 -3
  143. package/extensions/wechat/notices.ts +9 -7
  144. package/extensions/wechat/peer-worker.ts +19 -34
  145. package/extensions/wechat/presence.ts +3 -4
  146. package/extensions/wechat/prompt.ts +53 -23
  147. package/extensions/wechat/reply-pipeline.ts +12 -4
  148. package/extensions/wechat/slash.ts +14 -0
  149. package/extensions/wechat/status-screen.ts +2 -2
  150. package/extensions/wechat/tools.ts +3 -11
  151. package/extensions/worktree/README.md +48 -12
  152. package/extensions/worktree/commands.ts +109 -65
  153. package/extensions/worktree/enter.ts +1 -1
  154. package/extensions/worktree/exit.ts +11 -1
  155. package/extensions/worktree/git.ts +5 -0
  156. package/extensions/worktree/lifecycle.ts +9 -1
  157. package/extensions/worktree/registry.ts +11 -5
  158. package/extensions/worktree/screen.ts +263 -0
  159. package/extensions/worktree/status.ts +84 -40
  160. package/lib/README.md +12 -7
  161. package/lib/claims/overlap.ts +43 -0
  162. package/lib/config.ts +12 -0
  163. package/lib/format.ts +6 -2
  164. package/lib/http/router.ts +40 -13
  165. package/lib/http/server.ts +27 -13
  166. package/lib/interaction.ts +28 -1
  167. package/lib/notice.ts +29 -2
  168. package/lib/plan-entry.ts +7 -2
  169. package/lib/plans/sections.ts +172 -0
  170. package/lib/plans/setting.ts +52 -0
  171. package/lib/plans/split.ts +215 -0
  172. package/lib/plans/store.ts +19 -0
  173. package/lib/plans/team.ts +151 -0
  174. package/lib/qr.ts +64 -0
  175. package/lib/repo-key.ts +74 -0
  176. package/lib/report.ts +117 -25
  177. package/lib/requirements.ts +1 -2
  178. package/lib/tool-result.ts +54 -4
  179. package/lib/tui/README.md +30 -8
  180. package/lib/tui/chrome.ts +27 -6
  181. package/lib/tui/dock-screen.ts +91 -19
  182. package/lib/tui/gauge.ts +9 -5
  183. package/lib/tui/line-pane.ts +15 -4
  184. package/lib/tui/tool.ts +23 -4
  185. package/lib/usage.ts +26 -0
  186. package/lib/web/README.md +131 -0
  187. package/lib/web/base.ts +19 -2
  188. package/lib/web/components.ts +10 -6
  189. package/lib/web/layout.ts +2 -0
  190. package/lib/web/markup.ts +2 -1
  191. package/lib/web/runtime.ts +103 -25
  192. package/lib/web/tokens.ts +23 -7
  193. package/package.json +5 -2
  194. package/skills/changelog/SKILL.md +57 -45
  195. package/skills/init/SKILL.md +80 -45
  196. package/skills/init/references/agents-md-quality.md +26 -2
  197. package/skills/init/references/interview-questions.md +36 -14
  198. package/skills/next/SKILL.md +209 -0
  199. package/skills/parallel-dev/SKILL.md +150 -0
  200. package/extensions/wechat/qrcode.ts +0 -46
@@ -6,9 +6,11 @@
6
6
  * the active Markdown theme, so headings, lists, code, links, and quotes match
7
7
  * the rest of the application; plan-authored control characters are stripped
8
8
  * first so the file cannot drive the terminal. Task checkboxes are recolored
9
- * and the footer colors the primary actions. It returns the chosen action
10
- * through `onClose` so the caller can act
11
- * only after the screen is gone and input focus is free again.
9
+ * and the footer colors the primary actions. Approving leaves plan mode and
10
+ * starts the work, so it asks `y/N` first through the shared `ConfirmState`;
11
+ * the plan stays mounted, and `Esc` backs out of the question before it backs
12
+ * out of the review. It returns the chosen action through `onClose` so the
13
+ * caller can act only after the screen is gone and input focus is free again.
12
14
  */
13
15
 
14
16
  import { basename } from "node:path";
@@ -27,6 +29,7 @@ import {
27
29
  type TuiMouseEventResult,
28
30
  } from "@earendil-works/pi-tui";
29
31
  import { sanitizeLine } from "../../lib/format.ts";
32
+ import { renderTeam, type PlanUnit } from "../../lib/plans/team.ts";
30
33
  import {
31
34
  editorTheme,
32
35
  screenHint,
@@ -35,17 +38,18 @@ import {
35
38
  type ViewportRowsSource,
36
39
  } from "../../lib/tui/chrome.ts";
37
40
  import { isCloseKey } from "../../lib/tui/cursor.ts";
41
+ import { ConfirmState } from "../../lib/tui/confirm.ts";
38
42
  import { gaugeBar } from "../../lib/tui/gauge.ts";
39
43
  import { GLYPHS } from "../../lib/tui/glyphs.ts";
40
- import { titleRule } from "../../lib/tui/rules.ts";
44
+ import { plainRule, titleRule } from "../../lib/tui/rules.ts";
41
45
  import { emptyState, checkbox } from "../../lib/tui/rows.ts";
42
46
  import { paint } from "../../lib/tui/tokens.ts";
43
47
  import { planTitle, type StoredPlan } from "./plans.ts";
44
- import { changedSections, sanitizePlanText, type SectionDigest } from "./plan-text.ts";
48
+ import { changedSections, planDisplayText, type SectionDigest } from "./plan-text.ts";
45
49
  import { extractPlanSections } from "./steps.ts";
46
50
 
47
51
  /** What the user chose from the read/review screen. */
48
- export type PlanViewAction = "approve" | "refine" | "keep" | "browser";
52
+ export type PlanViewAction = "approve" | "refine" | "keep" | "browser" | "cancel";
49
53
 
50
54
  /** A plan section's rendered start line, for navigation and the current-section label. */
51
55
  interface SectionOffset {
@@ -83,11 +87,16 @@ export interface PlanViewOptions {
83
87
  tui?: TUI;
84
88
  /** Markdown theme for the body; defaults to Pi's active theme. */
85
89
  markdownTheme?: MarkdownTheme;
90
+ /**
91
+ * The split approval would write. Rendered as a read-only section after the
92
+ * plan, because the `## Team` section itself only exists after approval.
93
+ */
94
+ split?: { lead: string; units: readonly PlanUnit[]; verdict: "pair" | "team" };
86
95
  }
87
96
 
88
97
  /** Rows the screen shows when the terminal height is unknown. */
89
98
  const DEFAULT_ROWS = 20;
90
- /** Header, blanks, footer, and trailing rows around a non-empty plan. */
99
+ /** Header, blanks, footer, and closing rule around a non-empty plan. */
91
100
  const CHROME_ROWS = 5;
92
101
  /** Narrowest width where Markdown's 2-column padding still fits the body. */
93
102
  const MIN_PADDED_WIDTH = 6;
@@ -97,13 +106,14 @@ const BODY_PADDING = 2;
97
106
  const GAUGE_SEGMENTS = 10;
98
107
  /** Footer hints shared by the review and the inline editor's primary actions. */
99
108
  const PRIMARY_HINTS: ScreenHint[] = [
100
- "Esc keep planning",
101
- { text: "a approve & run", color: "success" },
109
+ "Esc keep plan",
110
+ { text: "a approve", color: "success" },
102
111
  { text: "r refine", color: "accent" },
103
112
  { text: "w browser", color: "accent" },
113
+ { text: "x exit plan", color: "warning" },
104
114
  ];
105
115
  /** Scroll keys appended to the review footer while the plan overflows. */
106
- const SCROLL_HINTS: ScreenHint[] = ["↑/↓ or k/j scroll", "space/b page", "g/G ends"];
116
+ const SCROLL_HINTS: ScreenHint[] = ["j/k scroll", "space page", "g/G ends"];
107
117
  /** Section-jump hint, shown when the plan has more than one heading section. */
108
118
  const SECTION_HINTS: ScreenHint[] = ["n/p section"];
109
119
 
@@ -174,6 +184,8 @@ export class PlanViewComponent implements Component {
174
184
  private cache: BodyCache | null = null;
175
185
  /** True while the inline refine editor owns the keyboard. */
176
186
  private editing = false;
187
+ /** The y/N approval question, shared with the other destructive screens. */
188
+ private readonly confirm = new ConfirmState();
177
189
  /** The inline refine editor, built lazily once a TUI handle is available. */
178
190
  private editor: Editor | null = null;
179
191
 
@@ -194,7 +206,7 @@ export class PlanViewComponent implements Component {
194
206
  const padding = width >= MIN_PADDED_WIDTH ? BODY_PADDING : 0;
195
207
  const base = this.options.markdownTheme ?? getMarkdownTheme();
196
208
  const theme = planMarkdownTheme(this.theme, base);
197
- const current = sanitizePlanText(this.options.plan.content);
209
+ const current = planDisplayText(this.options.plan.content);
198
210
  const changed = changedSections(this.options.baseline, current);
199
211
  const chunks = extractPlanSections(current);
200
212
  const lines: string[] = [];
@@ -215,10 +227,31 @@ export class PlanViewComponent implements Component {
215
227
  // chunk omits before the next section.
216
228
  if (index < chunks.length - 1 && lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
217
229
  });
230
+ const split = this.splitSection();
231
+ if (split !== undefined) {
232
+ if (lines.length > 0 && lines[lines.length - 1] !== "") lines.push("");
233
+ const rendered = new Markdown(split, padding, 0, theme)
234
+ .render(width)
235
+ .map((line) => truncateToWidth(line.trimEnd(), width));
236
+ sections.push({ title: "Split", offset: lines.length });
237
+ lines.push(...rendered);
238
+ }
218
239
  this.cache = { width, lines, sections, changedCount: changed.filter(Boolean).length };
219
240
  return this.cache;
220
241
  }
221
242
 
243
+ /**
244
+ * The split as a read-only section: the same table approval writes, so what the
245
+ * user approves is what the plan file will carry.
246
+ */
247
+ private splitSection(): string | undefined {
248
+ const split = this.options.split;
249
+ if (split === undefined) return undefined;
250
+ const owners = split.units.filter((unit) => !unit.lead).length;
251
+ const summary = `${split.verdict} · lead + ${owners} owner${owners === 1 ? "" : "s"} · written on approval`;
252
+ return ["## Split", "", summary, "", renderTeam({ lead: split.lead, units: [...split.units] })].join("\n");
253
+ }
254
+
222
255
  /** The section whose heading is at or above the viewport top. */
223
256
  private currentSection(sections: SectionOffset[]): SectionOffset | undefined {
224
257
  let current: SectionOffset | undefined;
@@ -313,6 +346,12 @@ export class PlanViewComponent implements Component {
313
346
  this.options.requestRender();
314
347
  return;
315
348
  }
349
+ // A pending question owns the keyboard, so a scroll key cannot slip past it
350
+ // and `Esc` backs out of the question rather than the review.
351
+ if (this.confirm.pending) {
352
+ if (this.confirm.resolve(data) !== undefined) this.options.requestRender();
353
+ return;
354
+ }
316
355
  if (isCloseKey(data)) {
317
356
  this.options.onClose("keep");
318
357
  return;
@@ -329,9 +368,12 @@ export class PlanViewComponent implements Component {
329
368
  else if (matchesKey(data, Key.end) || data === "G") this.scrollTo(this.maxOffset);
330
369
  else if (data === "n") this.jumpSection(1);
331
370
  else if (data === "p") this.jumpSection(-1);
332
- else if (data === "a") this.options.onClose("approve");
333
- else if (data === "r") this.startRefine();
371
+ else if (data === "a") {
372
+ this.confirm.ask("Approve and run the plan? y/N", () => this.options.onClose("approve"));
373
+ this.options.requestRender();
374
+ } else if (data === "r") this.startRefine();
334
375
  else if (data === "w") this.options.onClose("browser");
376
+ else if (data === "x") this.options.onClose("cancel");
335
377
  }
336
378
 
337
379
  /** Wheel scrolling in fullscreen; regular mode leaves the wheel to the terminal. */
@@ -398,12 +440,15 @@ export class PlanViewComponent implements Component {
398
440
  } else {
399
441
  if (this.maxOffset > 0) lines.push(scrollRow(theme, w, this.offset, end, body.length, sectionLabel));
400
442
  else lines.push("");
443
+ const pending = this.confirm.pending;
401
444
  const sectionHints = sections.length > 1 ? SECTION_HINTS : [];
402
445
  const hints: ScreenHint[] =
403
446
  this.maxOffset > 0 ? [...PRIMARY_HINTS, ...SCROLL_HINTS, ...sectionHints] : PRIMARY_HINTS;
404
- lines.push(screenHint(theme, w, hints));
447
+ lines.push(
448
+ pending ? truncateToWidth(` ${paint(theme, "warn", pending.prompt)}`, w) : screenHint(theme, w, hints),
449
+ );
405
450
  }
406
- lines.push("");
451
+ lines.push(plainRule(theme, w));
407
452
  return lines;
408
453
  }
409
454
  }
@@ -6,6 +6,32 @@ import type { PlanAnnotation } from "./feedback.ts";
6
6
  import type { SectionDigest } from "./plan-text.ts";
7
7
  import type { StepUpdate } from "./steps.ts";
8
8
 
9
+ /**
10
+ * Who is looking at a running review: the session that opened it (`owner`, the
11
+ * only role that can approve), or someone the link was shared with (`reviewer`,
12
+ * who may comment).
13
+ */
14
+ export type ReviewRole = "owner" | "reviewer";
15
+
16
+ /**
17
+ * What a review token names: its role, and — for a reviewer — the name it posts
18
+ * under, bound the first time that link writes.
19
+ */
20
+ export interface ReviewCaller {
21
+ role: ReviewRole;
22
+ /** The reviewer's name, once this link has written; absent for the owner and
23
+ * for a reviewer that has not commented yet. */
24
+ name?: string;
25
+ }
26
+
27
+ /**
28
+ * One frame on the review's event stream. `comment`, `plan`, and `share` say a
29
+ * page should re-read the island; `closed` ends the review for everyone, and
30
+ * `revoked` tells a reviewer the owner stopped sharing. The frame carries no
31
+ * state of its own, so the island stays the one contract.
32
+ */
33
+ export type ReviewFrame = "comment" | "plan" | "share" | "closed" | "revoked";
34
+
9
35
  /** Persisted (custom entry) plan-mode state. */
10
36
  export interface PlanModeEntry {
11
37
  enabled: boolean;
@@ -38,7 +64,7 @@ export interface EnterPlanModeDetails {
38
64
  export interface WritePlanDetails {
39
65
  /** Absolute path of the written plan file. */
40
66
  path: string;
41
- /** Path relative to the working directory, when inside it. */
67
+ /** How the path is shown: `~`-shortened, since plans live outside the project. */
42
68
  relativePath: string;
43
69
  /** Size of the written plan in bytes. */
44
70
  bytes: number;
@@ -48,7 +74,7 @@ export interface WritePlanDetails {
48
74
  export interface SetPlanStepsDetails {
49
75
  /** Absolute path of the plan file that was updated. */
50
76
  path: string;
51
- /** Path relative to the working directory, for transcript display. */
77
+ /** How the path is shown: `~`-shortened. */
52
78
  relativePath: string;
53
79
  /** The checkbox changes that were applied, in call order. */
54
80
  updates: StepUpdate[];
@@ -66,7 +92,7 @@ export interface ExitPlanModeDetails {
66
92
  plan: string;
67
93
  /** Absolute path of the reviewed plan file. */
68
94
  planPath?: string;
69
- /** Path relative to the working directory, for transcript display. */
95
+ /** How the path is shown: `~`-shortened. */
70
96
  relativePath?: string;
71
97
  /** True when the user asked to refine the plan instead of approving. */
72
98
  refined?: boolean;
@@ -74,6 +100,22 @@ export interface ExitPlanModeDetails {
74
100
  refinement?: string;
75
101
  /** Comments the review carried when it approved or requested a refinement. */
76
102
  annotations?: PlanAnnotation[];
103
+ /** True when the user left plan mode without approving the plan. */
104
+ cancelled?: boolean;
77
105
  /** True when no interactive UI was available to ask for approval. */
78
106
  unavailable?: boolean;
107
+ /** The team approval wrote into the plan, when the plan was split. */
108
+ team?: {
109
+ lead: string;
110
+ units: Array<{ name: string; paths: string[]; test: string; dependsOn: string[] }>;
111
+ verdict: "pair" | "team";
112
+ };
113
+ /** True when the model proposed a split while `plan.split` was off. */
114
+ splitOff?: boolean;
115
+ }
116
+
117
+ /** Structured result carried in `cancel_plan_mode`'s `details`. */
118
+ export interface CancelPlanModeDetails {
119
+ /** True when plan mode was on and has now been turned off. */
120
+ cancelled: boolean;
79
121
  }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The review page's chrome: what the page says about itself.
3
+ *
4
+ * It is a fragment of the same IIFE `web-client.ts` ships (`WEB_CHROME_JS` is
5
+ * interpolated into `REVIEW_CLIENT_JS`), kept in its own module so the state
6
+ * chip, the status line, and the header are read together and away from the
7
+ * document rendering. It paints only text it was handed.
8
+ */
9
+
10
+ export const WEB_CHROME_JS = String.raw` function setStatus(text, kind) {
11
+ if (!statusEl) return;
12
+ statusEl.textContent = text || "";
13
+ statusEl.title = text || "";
14
+ statusEl.className = kind ? "bf-status bf-status--" + kind : "bf-status";
15
+ }
16
+
17
+ // The always-visible header chip: connection, comment count, and whether
18
+ // there are edits the file does not have yet.
19
+ function updateState() {
20
+ if (!stateEl) return;
21
+ var label;
22
+ var tone = "";
23
+ if (approved) {
24
+ label = "Approved";
25
+ tone = "success";
26
+ } else if (saved) {
27
+ label = "Saved";
28
+ tone = "success";
29
+ } else if (closed) {
30
+ // Settled by the user, not by a dropped stream: never flash Offline.
31
+ label = "Closed";
32
+ } else if (connected === true) {
33
+ label = "Connected";
34
+ tone = "success";
35
+ } else if (everConnected) {
36
+ // Only after the stream has opened once: an error before that is a
37
+ // transient initial connect, so keep showing Connecting rather than Offline.
38
+ label = "Offline";
39
+ tone = "danger";
40
+ } else {
41
+ label = "Connecting\u2026";
42
+ }
43
+ var count = annotations.length;
44
+ label += " \u00b7 " + count + (count === 1 ? " comment" : " comments");
45
+ if (dirty && !saved && !approved && !closed) {
46
+ label += " \u00b7 " + (role === "reviewer" ? "unposted" : "unsaved");
47
+ if (!tone) tone = "warn";
48
+ }
49
+ stateEl.textContent = label;
50
+ stateEl.className = "bf-chip bf-chip--accent review-state" + (tone ? " bf-chip--" + tone : "");
51
+ }
52
+
53
+ function renderHeader() {
54
+ var titleEl = document.getElementById("plan-title");
55
+ if (titleEl && data.title) {
56
+ titleEl.textContent = data.title;
57
+ titleEl.title = data.title;
58
+ }
59
+ if (data.title) document.title = "Plan review \u00b7 " + data.title;
60
+ }
61
+
62
+ function updateProgress() {
63
+ if (!progressEl) return;
64
+ var doc = document.scrollingElement || document.documentElement;
65
+ var top = doc ? doc.scrollTop : 0;
66
+ var height = doc ? doc.scrollHeight : 0;
67
+ var view = doc ? doc.clientHeight : 0;
68
+ if (planEl && planEl.scrollHeight > planEl.clientHeight + 4) {
69
+ top = planEl.scrollTop; height = planEl.scrollHeight; view = planEl.clientHeight;
70
+ }
71
+ var max = height - view;
72
+ var percent = max > 0 ? Math.min(100, Math.max(0, (top / max) * 100)) : 0;
73
+ progressEl.style.width = percent + "%";
74
+ }
75
+
76
+ function lockActions() {
77
+ if (approveButton) approveButton.disabled = true;
78
+ if (saveButton) saveButton.disabled = true;
79
+ if (postButton) postButton.disabled = true;
80
+ if (cancelButton) cancelButton.disabled = true;
81
+ }
82
+
83
+ // A conflict means the file moved under the page; only a reload can re-anchor.
84
+ function offerReload() {
85
+ if (document.getElementById("reload")) return;
86
+ var controls = document.querySelector(".review-controls");
87
+ if (!controls) return;
88
+ var reload = el("button");
89
+ reload.type = "button";
90
+ reload.id = "reload";
91
+ reload.className = "bf-btn bf-btn--primary";
92
+ reload.textContent = "Reload";
93
+ reload.addEventListener("click", function () { window.location.reload(); });
94
+ controls.appendChild(reload);
95
+ }
96
+
97
+ // The stream exists only to notice a closed tab. Once the review is settled it
98
+ // has no job, and leaving it open retries a dead port until the tab closes.
99
+ function settleStream() {
100
+ if (stream && stream.close) { stream.close(); stream = null; }
101
+ }
102
+ `;