omegon 0.6.3 → 0.6.4

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 (69) hide show
  1. package/README.md +12 -10
  2. package/bin/omegon.mjs +40 -0
  3. package/bin/pi.mjs +5 -26
  4. package/extensions/00-secrets/index.ts +146 -39
  5. package/extensions/01-auth/auth.ts +1 -1
  6. package/extensions/01-auth/index.ts +3 -3
  7. package/extensions/auto-compact.ts +1 -1
  8. package/extensions/bootstrap/deps.ts +42 -0
  9. package/extensions/bootstrap/index.ts +326 -110
  10. package/extensions/chronos/index.ts +1 -1
  11. package/extensions/cleave/dispatcher.ts +6 -6
  12. package/extensions/cleave/index.ts +6 -6
  13. package/extensions/cleave/planner.ts +1 -1
  14. package/extensions/cleave/worktree.ts +1 -1
  15. package/extensions/core-renderers.ts +24 -84
  16. package/extensions/dashboard/footer.ts +184 -40
  17. package/extensions/dashboard/git.ts +2 -2
  18. package/extensions/dashboard/index.ts +4 -4
  19. package/extensions/dashboard/overlay-data.ts +5 -5
  20. package/extensions/dashboard/overlay.ts +5 -5
  21. package/extensions/dashboard/render-utils.ts +1 -1
  22. package/extensions/dashboard/types.ts +15 -0
  23. package/extensions/defaults.ts +4 -12
  24. package/extensions/design-tree/dashboard-state.ts +6 -6
  25. package/extensions/design-tree/design-card.ts +3 -3
  26. package/extensions/design-tree/index.ts +64 -44
  27. package/extensions/design-tree/types.ts +4 -2
  28. package/extensions/distill.ts +1 -1
  29. package/extensions/effort/index.ts +137 -10
  30. package/extensions/lib/model-routing.ts +304 -32
  31. package/extensions/lib/operator-fallback.ts +1 -1
  32. package/extensions/lib/operator-profile.ts +1 -1
  33. package/extensions/lib/provider-env.ts +163 -0
  34. package/extensions/{sci-ui.ts → lib/sci-ui.ts} +119 -2
  35. package/extensions/{shared-state.ts → lib/shared-state.ts} +13 -9
  36. package/extensions/lib/slash-command-bridge.ts +1 -1
  37. package/extensions/{types.d.ts → lib/types.d.ts} +3 -3
  38. package/extensions/local-inference/index.ts +1 -1
  39. package/extensions/mcp-bridge/index.ts +1 -1
  40. package/extensions/model-budget.ts +10 -10
  41. package/extensions/offline-driver.ts +11 -4
  42. package/extensions/openspec/archive-gate.ts +1 -1
  43. package/extensions/openspec/branch-cleanup.ts +1 -1
  44. package/extensions/openspec/dashboard-state.ts +3 -3
  45. package/extensions/openspec/index.ts +5 -5
  46. package/extensions/project-memory/factstore.ts +5 -11
  47. package/extensions/project-memory/index.ts +48 -34
  48. package/extensions/project-memory/package.json +1 -1
  49. package/extensions/project-memory/sci-renderers.ts +1 -1
  50. package/extensions/render/index.ts +1 -1
  51. package/extensions/session-log.ts +1 -1
  52. package/extensions/spinner-verbs.ts +1 -1
  53. package/extensions/style.ts +1 -1
  54. package/extensions/terminal-title.ts +3 -3
  55. package/extensions/tool-profile/index.ts +1 -1
  56. package/extensions/vault/index.ts +1 -1
  57. package/extensions/version-check.ts +13 -9
  58. package/extensions/view/index.ts +4 -4
  59. package/extensions/web-search/index.ts +5 -2
  60. package/extensions/web-ui/index.ts +1 -1
  61. package/extensions/web-ui/state.ts +1 -1
  62. package/package.json +8 -7
  63. package/scripts/preinstall.sh +19 -3
  64. package/scripts/publish-pi-mono.sh +92 -0
  65. package/skills/pi-extensions/SKILL.md +2 -2
  66. package/skills/pi-tui/SKILL.md +17 -17
  67. package/skills/typescript/SKILL.md +1 -1
  68. package/themes/alpharius.json +7 -6
  69. /package/extensions/{debug.ts → lib/debug.ts} +0 -0
@@ -1,15 +1,16 @@
1
1
  /**
2
- * Core Tool Renderers — Sci-UI rendering for pi built-in tools.
2
+ * Core Tool Renderers — Sci-UI rendering for Omegon's custom tools.
3
3
  *
4
4
  * Uses registerToolRenderer() to attach renderCall/renderResult
5
- * to built-in tools (bash, read, edit, write) without replacing them.
5
+ * to tools that have no built-in rendering in pi-mono.
6
6
  *
7
- * The built-in renderers handle syntax highlighting, diffs, and streaming —
8
- * we only override the COLLAPSED view to match the Sci-UI visual language.
9
- * Expanded views fall through to the built-in renderer.
7
+ * IMPORTANT: Do NOT register renderers for built-in tools (read, edit, write,
8
+ * bash, grep, find, ls). The built-in renderer provides syntax highlighting,
9
+ * diffs, and streaming output. Registering even just renderCall causes pi to
10
+ * take the custom renderer path, losing all built-in content rendering.
10
11
  */
11
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
12
- import { sciCall, sciOk, sciErr, sciLoading } from "./sci-ui.ts";
12
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
13
+ import { sciCall, sciOk, sciErr, sciLoading } from "./lib/sci-ui.ts";
13
14
 
14
15
  /** Shorten a file path for display — keep last 2-3 segments. */
15
16
  function shortenPath(p: string | null | undefined, maxLen = 55): string {
@@ -22,83 +23,22 @@ function shortenPath(p: string | null | undefined, maxLen = 55): string {
22
23
  }
23
24
 
24
25
  export default function coreRenderers(pi: ExtensionAPI): void {
25
- // ── Read ──────────────────────────────────────────────────────────────
26
- pi.registerToolRenderer("read", {
27
- renderCall(args: any, theme: any) {
28
- const p = shortenPath(args?.file_path ?? args?.path);
29
- let range = "";
30
- if (args?.offset != null || args?.limit != null) {
31
- const start = args.offset ?? 1;
32
- const end = args.limit != null ? start + args.limit - 1 : "";
33
- range = `:${start}${end ? `-${end}` : ""}`;
34
- }
35
- return sciCall("read", `${p}${range}`, theme);
36
- },
37
- // renderResult omitted — built-in handles syntax highlighting + truncation
38
- });
39
-
40
- // ── Edit ──────────────────────────────────────────────────────────────
41
- pi.registerToolRenderer("edit", {
42
- renderCall(args: any, theme: any) {
43
- const p = shortenPath(args?.file_path ?? args?.path);
44
- // Show the size of the change: lines changed
45
- const oldLines = (args?.old_text ?? args?.oldText ?? "").split("\n").length;
46
- const newLines = (args?.new_text ?? args?.newText ?? "").split("\n").length;
47
- const delta = newLines - oldLines;
48
- const deltaStr = delta === 0 ? `${oldLines}L` : delta > 0 ? `+${delta}L` : `${delta}L`;
49
- return sciCall("edit", `${p} (${deltaStr})`, theme);
50
- },
51
- // renderResult omitted — built-in handles diff rendering
52
- });
53
-
54
- // ── Write ─────────────────────────────────────────────────────────────
55
- pi.registerToolRenderer("write", {
56
- renderCall(args: any, theme: any) {
57
- const p = shortenPath(args?.file_path ?? args?.path);
58
- const content = args?.content ?? "";
59
- const lines = content.split("\n").length;
60
- return sciCall("write", `${p} (${lines}L)`, theme);
61
- },
62
- // renderResult omitted — built-in handles syntax highlighting
63
- });
64
-
65
- // ── Bash ──────────────────────────────────────────────────────────────
66
- pi.registerToolRenderer("bash", {
67
- renderCall(args: any, theme: any) {
68
- const cmd = args?.command ?? "";
69
- // Truncate long commands
70
- const display = cmd.length > 70 ? cmd.slice(0, 67) + "…" : cmd;
71
- return sciCall("bash", display, theme);
72
- },
73
- // renderResult omitted — built-in handles output display + truncation
74
- });
75
-
76
- // ── Grep ──────────────────────────────────────────────────────────────
77
- pi.registerToolRenderer("grep", {
78
- renderCall(args: any, theme: any) {
79
- const pattern = args?.pattern ?? "";
80
- const p = shortenPath(args?.path);
81
- const glob = args?.glob ? ` (${args.glob})` : "";
82
- return sciCall("grep", `/${pattern}/ in ${p}${glob}`, theme);
83
- },
84
- });
85
-
86
- // ── Find ──────────────────────────────────────────────────────────────
87
- pi.registerToolRenderer("find", {
88
- renderCall(args: any, theme: any) {
89
- const pattern = args?.pattern ?? "";
90
- const p = shortenPath(args?.path);
91
- return sciCall("find", `${pattern} in ${p}`, theme);
92
- },
93
- });
94
-
95
- // ── Ls ─────────────────────────────────────────────────────────────────
96
- pi.registerToolRenderer("ls", {
97
- renderCall(args: any, theme: any) {
98
- const p = shortenPath(args?.path || ".");
99
- return sciCall("ls", p, theme);
100
- },
101
- });
26
+ // registerToolRenderer was added in pi-mono 0965ae87 — gracefully skip
27
+ // if the published pi version doesn't have it yet.
28
+ if (typeof (pi as any).registerToolRenderer !== "function") {
29
+ return;
30
+ }
31
+
32
+ // ─── Built-in tools (read, edit, write, bash, grep, find, ls) ────────
33
+ // NOT registered here. The built-in renderer handles:
34
+ // read → syntax-highlighted code, line ranges, truncation
35
+ // edit → colored diffs with context, line numbers
36
+ // write syntax-highlighted content preview
37
+ // bash → streaming output, metadata (cwd, duration, exit code)
38
+ // grep → highlighted matches
39
+ // find → directory listing
40
+ // ls → directory listing with limits
41
+ // Registering renderCall would cause pi to skip all of this.
102
42
 
103
43
  // ── View ──────────────────────────────────────────────────────────────
104
44
  pi.registerToolRenderer("view", {
@@ -10,17 +10,17 @@
10
10
  * Reads ExtensionContext for token stats, model, context usage.
11
11
  */
12
12
 
13
- import type { Component } from "@cwilson613/pi-tui";
14
- import type { Theme, ThemeColor } from "@cwilson613/pi-coding-agent";
15
- import type { ReadonlyFooterDataProvider } from "@cwilson613/pi-coding-agent";
16
- import type { ExtensionContext } from "@cwilson613/pi-coding-agent";
17
- import type { TUI } from "@cwilson613/pi-tui";
18
- import { truncateToWidth, visibleWidth } from "@cwilson613/pi-tui";
13
+ import type { Component } from "@styrene-lab/pi-tui";
14
+ import type { Theme, ThemeColor } from "@styrene-lab/pi-coding-agent";
15
+ import type { ReadonlyFooterDataProvider } from "@styrene-lab/pi-coding-agent";
16
+ import type { ExtensionContext } from "@styrene-lab/pi-coding-agent";
17
+ import type { TUI } from "@styrene-lab/pi-tui";
18
+ import { truncateToWidth, visibleWidth } from "@styrene-lab/pi-tui";
19
19
  import { leftRight, mergeColumns, padRight } from "./render-utils.ts";
20
20
  import { buildBranchTreeLines, readLocalBranches } from "./git.ts";
21
- import type { DashboardState, RecoveryCooldownSummary, RecoveryDashboardState } from "./types.ts";
22
- import { sharedState } from "../shared-state.ts";
23
- import { debug } from "../debug.ts";
21
+ import type { DashboardModelRoleSummary, DashboardState, RecoveryCooldownSummary, RecoveryDashboardState } from "./types.ts";
22
+ import { sharedState } from "../lib/shared-state.ts";
23
+ import { debug } from "../lib/debug.ts";
24
24
  import { linkDashboardFile, linkOpenSpecArtifact, linkOpenSpecChange } from "./uri-helper.ts";
25
25
  import { designSpecBadge } from "./overlay-data.ts";
26
26
  import { buildContextGaugeModel } from "./context-gauge.ts";
@@ -91,6 +91,8 @@ function summarizeCooldown(cooldowns: RecoveryCooldownSummary[] | undefined): st
91
91
  const CLEAVE_STALE_MS = 30_000;
92
92
  /** Recovery notices auto-suppress in compact mode after this many ms with no new error. */
93
93
  const RECOVERY_STALE_MS = 45_000;
94
+ const RAISED_NARROW_WIDTH = 100;
95
+ const RAISED_WIDE_WIDTH = 140;
94
96
 
95
97
  type PrioritySegment = {
96
98
  text: string;
@@ -141,6 +143,16 @@ function composePrimaryMetaLine(
141
143
  ], separator);
142
144
  }
143
145
 
146
+ function normalizeLocalModelLabel(model: string): { canonical: string; alias?: string } {
147
+ if (model === "devstral-small-2:24b") {
148
+ return { canonical: "Devstral 24B", alias: "devstral-small-2:24b" };
149
+ }
150
+ if (model === "devstral:24b") {
151
+ return { canonical: "Devstral 24B" };
152
+ }
153
+ return { canonical: model };
154
+ }
155
+
144
156
  export class DashboardFooter implements Component {
145
157
  private tui: TUI;
146
158
  private theme: Theme;
@@ -221,7 +233,8 @@ export class DashboardFooter implements Component {
221
233
  if (dt && dt.nodeCount > 0) {
222
234
  if (ultraWide && dt.focusedNode) {
223
235
  // Ultra-wide: show focused node title inline
224
- const statusIcon = dt.focusedNode.status === "decided" ? ""
236
+ const statusIcon = dt.focusedNode.status === "resolved" ? ""
237
+ : dt.focusedNode.status === "decided" ? "●"
225
238
  : dt.focusedNode.status === "implementing" ? "⚙"
226
239
  : dt.focusedNode.status === "exploring" ? "◐"
227
240
  : "○";
@@ -352,7 +365,9 @@ export class DashboardFooter implements Component {
352
365
  // ── Raised Mode (Layer 1) ─────────────────────────────────────
353
366
 
354
367
  private renderRaised(width: number): string[] {
355
- return width >= 120 ? this.renderRaisedWide(width) : this.renderRaisedStacked(width);
368
+ if (width < RAISED_NARROW_WIDTH) return this.renderRaisedNarrow(width);
369
+ if (width < RAISED_WIDE_WIDTH) return this.renderRaisedMedium(width);
370
+ return this.renderRaisedWide(width);
356
371
  }
357
372
 
358
373
  /**
@@ -451,10 +466,10 @@ export class DashboardFooter implements Component {
451
466
  }
452
467
 
453
468
  /**
454
- * Stacked layout for narrow terminals (<120 cols).
455
- * All sections rendered full-width inside a corner-bounded box.
469
+ * Narrow layout for terminals under 100 cols.
470
+ * Lifecycle/work stays above; lower dashboard uses stacked summary cards.
456
471
  */
457
- private renderRaisedStacked(width: number): string[] {
472
+ private renderRaisedNarrow(width: number): string[] {
458
473
  const innerWidth = width - 4;
459
474
  const branchLines = this.buildBranchTree(innerWidth);
460
475
  const [topLine = "", ...extraBranchLines] = branchLines;
@@ -480,12 +495,9 @@ export class DashboardFooter implements Component {
480
495
  }
481
496
 
482
497
  /**
483
- * Wide layout (≥120 cols) — two-column content inside a corner-bounded box.
484
- * Left: Design tree + Recovery + Cleave (active work context)
485
- * Right: Implementation (spec/task progress)
486
- * Footer zone: shared meta, memory, footer data
498
+ * Medium layout (100–139 cols) — two-column work area with compact summary cards below.
487
499
  */
488
- private renderRaisedWide(width: number): string[] {
500
+ private renderRaisedMedium(width: number): string[] {
489
501
  const innerWidth = width - 4;
490
502
  const leftColWidth = Math.floor((innerWidth - 1) / 2);
491
503
  const rightColWidth = innerWidth - leftColWidth - 1;
@@ -515,6 +527,37 @@ export class DashboardFooter implements Component {
515
527
  return this.renderBoxed(contentLines, this.buildFooterZone(innerWidth), topLine, width);
516
528
  }
517
529
 
530
+ /**
531
+ * Wide layout (140+ cols) keeps the same work summary above but gives the
532
+ * lower footer zone enough width to render distinct horizontal summary cards.
533
+ */
534
+ private renderRaisedWide(width: number): string[] {
535
+ const innerWidth = width - 4;
536
+ const leftColWidth = Math.floor((innerWidth - 1) / 2);
537
+ const rightColWidth = innerWidth - leftColWidth - 1;
538
+ const colDivider = this.theme.fg("dim", BOX.v);
539
+
540
+ const branchLines = this.buildBranchTree(innerWidth);
541
+ const [topLine = "", ...extraBranchLines] = branchLines;
542
+ const alignedBranchLines = extraBranchLines.map((l) => " " + l);
543
+
544
+ const leftLines = [
545
+ ...this.buildDesignTreeLines(leftColWidth),
546
+ ...this.buildRecoveryLines(leftColWidth),
547
+ ...this.buildCleaveLines(leftColWidth),
548
+ ];
549
+ const rightLines = this.buildOpenSpecLines(rightColWidth);
550
+
551
+ const contentLines: string[] = [
552
+ ...alignedBranchLines,
553
+ ...(leftLines.length > 0 || rightLines.length > 0
554
+ ? mergeColumns(leftLines, rightLines, leftColWidth, rightColWidth, colDivider)
555
+ : []),
556
+ ];
557
+
558
+ return this.renderBoxed(contentLines, this.buildFooterZone(innerWidth), topLine, width);
559
+ }
560
+
518
561
  // ── HUD Footer Zone (raised mode) ────────────────────────────
519
562
 
520
563
  /**
@@ -710,35 +753,135 @@ export class DashboardFooter implements Component {
710
753
  return lines;
711
754
  }
712
755
 
713
- /**
714
- * Assemble the full HUD footer zone from the three named sections.
715
- * Sections collapse when they have no data to show.
716
- */
717
- private buildFooterZone(width: number): string[] {
718
- // Keep token cache current (not called in compact mode — intentional).
719
- this._updateTokenCache();
756
+ private buildModelTopologySummaries(): DashboardModelRoleSummary[] {
757
+ const ctx = this.ctxRef;
758
+ const summaries: DashboardModelRoleSummary[] = [];
759
+ const memoryStatus = this.footerData.getExtensionStatuses().get("memory") ?? "";
760
+ const offlineStatus = this.footerData.getExtensionStatuses().get("offline-driver") ?? "";
761
+
762
+ if (ctx?.model) {
763
+ summaries.push({
764
+ role: "driver",
765
+ label: "Driver",
766
+ model: ctx.model.id,
767
+ source: ctx.model.provider === "local" ? "local" : "cloud",
768
+ state: offlineStatus.includes("OFFLINE:") ? "offline" : "active",
769
+ detail: this.footerData.getAvailableProviderCount() > 1 ? ctx.model.provider : undefined,
770
+ });
771
+ }
720
772
 
721
- const zone: string[] = [];
773
+ const effort = sharedState.effort;
774
+ if (memoryStatus || effort?.resolvedExtractionModelId) {
775
+ const extractionModel = effort?.resolvedExtractionModelId ?? effort?.extraction ?? "?";
776
+ const extractionLocal = extractionModel.includes(":") || effort?.extraction === "local";
777
+ summaries.push({
778
+ role: "extraction",
779
+ label: "Extraction",
780
+ model: extractionModel,
781
+ source: extractionLocal ? "local" : "cloud",
782
+ state: extractionLocal ? "ready" : "active",
783
+ });
784
+ }
785
+
786
+ if (memoryStatus) {
787
+ const embedMatch = memoryStatus.match(/semantic/i);
788
+ summaries.push({
789
+ role: "embeddings",
790
+ label: "Embeddings",
791
+ model: embedMatch ? "semantic retrieval" : "available",
792
+ source: "unknown",
793
+ state: "ready",
794
+ });
795
+ }
722
796
 
723
- const contextLines = this.buildHudContextLines(width);
724
- if (contextLines.length > 0) {
725
- zone.push(this.buildHudSectionDivider("context", width));
726
- zone.push(...contextLines);
797
+ if (offlineStatus.includes("OFFLINE:")) {
798
+ const raw = sanitizeStatusText(offlineStatus).replace(/^.*OFFLINE:\s*/i, "");
799
+ summaries.push({
800
+ role: "fallback",
801
+ label: "Fallback",
802
+ model: raw || "local fallback",
803
+ source: "local",
804
+ state: "offline",
805
+ });
727
806
  }
728
807
 
729
- const memLine = this.buildHudMemoryLine(width);
730
- if (memLine) {
731
- zone.push(this.buildHudSectionDivider("memory", width));
732
- zone.push(memLine);
808
+ return summaries;
809
+ }
810
+
811
+ private formatModelTopologyLine(summary: DashboardModelRoleSummary, width: number, compact = false): string {
812
+ const theme = this.theme;
813
+ const sourceBadge = summary.source === "local"
814
+ ? theme.fg("accent", "local")
815
+ : summary.source === "cloud"
816
+ ? theme.fg("muted", "cloud")
817
+ : theme.fg("dim", summary.source);
818
+ const stateBadge = summary.state === "active"
819
+ ? theme.fg("success", "active")
820
+ : summary.state === "offline"
821
+ ? theme.fg("warning", "offline")
822
+ : summary.state === "fallback"
823
+ ? theme.fg("warning", "fallback")
824
+ : theme.fg("dim", summary.state);
825
+ const normalized = normalizeLocalModelLabel(summary.model);
826
+ const alias = normalized.alias ? theme.fg("dim", `alias ${normalized.alias}`) : "";
827
+ const primary = compact
828
+ ? `${theme.fg("accent", summary.label)} ${theme.fg("muted", normalized.canonical)}`
829
+ : `${theme.fg("accent", summary.label)} ${theme.fg("dim", "·")} ${theme.fg("muted", normalized.canonical)}`;
830
+ return truncateToWidth(composePrimaryMetaLine(width, primary, [sourceBadge, stateBadge, summary.detail ? theme.fg("dim", summary.detail) : "", alias]), width, "…");
831
+ }
832
+
833
+ private buildSummaryCard(title: string, lines: string[], width: number): string[] {
834
+ if (lines.length === 0) return [];
835
+ return [this.buildHudSectionDivider(title, width), ...lines.map((line) => truncateToWidth(` ${line}`, width, "…"))];
836
+ }
837
+
838
+ private buildFooterZone(width: number): string[] {
839
+ this._updateTokenCache();
840
+
841
+ const contextCard = this.buildSummaryCard("context", this.buildHudContextLines(Math.max(1, width - 2)).map((l) => l.trimStart()), width);
842
+ const modelCard = this.buildSummaryCard(
843
+ "models",
844
+ this.buildModelTopologySummaries().map((s) => this.formatModelTopologyLine(s, Math.max(1, width - 2), width < 70)),
845
+ width,
846
+ );
847
+ const memoryCard = this.buildSummaryCard("memory", (() => {
848
+ const line = this.buildHudMemoryLine(Math.max(1, width - 2));
849
+ return line ? [line.trimStart()] : [];
850
+ })(), width);
851
+ const systemCard = this.buildSummaryCard("system", this.buildHudSystemLines(Math.max(1, width - 2)).map((l) => l.trimStart()), width);
852
+ const recoveryCard = this.buildSummaryCard("recovery", this.buildRecoveryLines(Math.max(1, width - 2)).map((l) => l.trimStart()), width);
853
+
854
+ if (width < RAISED_NARROW_WIDTH) {
855
+ return [
856
+ ...contextCard,
857
+ ...modelCard,
858
+ ...memoryCard,
859
+ ...(recoveryCard.length > 0 ? recoveryCard : []),
860
+ ...systemCard,
861
+ ];
733
862
  }
734
863
 
735
- const systemLines = this.buildHudSystemLines(width);
736
- if (systemLines.length > 0) {
737
- zone.push(this.buildHudSectionDivider("system", width));
738
- zone.push(...systemLines);
864
+ if (width < RAISED_WIDE_WIDTH) {
865
+ const left = [...contextCard, ...memoryCard];
866
+ const right = [...modelCard, ...(recoveryCard.length > 0 ? recoveryCard : []), ...systemCard];
867
+ const colWidth = Math.floor((width - 1) / 2);
868
+ const rightWidth = width - colWidth - 1;
869
+ return mergeColumns(left, right, colWidth, rightWidth, this.theme.fg("dim", BOX.v));
739
870
  }
740
871
 
741
- return zone;
872
+ const cards = [contextCard, modelCard, memoryCard, recoveryCard.length > 0 ? recoveryCard : systemCard];
873
+ const totalCols = cards.length;
874
+ const colWidth = Math.floor((width - (totalCols - 1)) / totalCols);
875
+ const divider = this.theme.fg("dim", BOX.v);
876
+ let merged = cards[0] ?? [];
877
+ let usedWidth = colWidth;
878
+ for (let i = 1; i < cards.length; i++) {
879
+ const remainingCols = totalCols - i;
880
+ const nextWidth = i === cards.length - 1 ? width - usedWidth - 1 : colWidth;
881
+ merged = mergeColumns(merged, cards[i] ?? [], usedWidth, nextWidth, divider);
882
+ usedWidth += 1 + nextWidth;
883
+ }
884
+ return merged;
742
885
  }
743
886
 
744
887
  // ── Section builders (shared by stacked + wide layouts) ───────
@@ -938,6 +1081,7 @@ export class DashboardFooter implements Component {
938
1081
  private nodeStatusIcon(status: string): string {
939
1082
  const theme = this.theme;
940
1083
  switch (status) {
1084
+ case "resolved": return theme.fg("success", "◉");
941
1085
  case "decided": return theme.fg("success", "●");
942
1086
  case "implementing": return theme.fg("accent", "⚙");
943
1087
  case "implemented": return theme.fg("success", "✓");
@@ -7,8 +7,8 @@
7
7
 
8
8
  import * as fs from "node:fs";
9
9
  import * as path from "node:path";
10
- import { visibleWidth } from "@cwilson613/pi-tui";
11
- import type { Theme } from "@cwilson613/pi-coding-agent";
10
+ import { visibleWidth } from "@styrene-lab/pi-tui";
11
+ import type { Theme } from "@styrene-lab/pi-coding-agent";
12
12
 
13
13
  // Shared ASCII-compat flag — same logic as footer.ts
14
14
  const useAscii = (() => {
@@ -14,14 +14,14 @@
14
14
  * Subscribes to "dashboard:update" events for live re-rendering.
15
15
  */
16
16
 
17
- import type { ExtensionAPI, ExtensionContext } from "@cwilson613/pi-coding-agent";
18
- import type { OverlayHandle } from "@cwilson613/pi-tui";
19
- import { DASHBOARD_UPDATE_EVENT } from "../shared-state.ts";
17
+ import type { ExtensionAPI, ExtensionContext } from "@styrene-lab/pi-coding-agent";
18
+ import type { OverlayHandle } from "@styrene-lab/pi-tui";
19
+ import { DASHBOARD_UPDATE_EVENT } from "../lib/shared-state.ts";
20
20
  import { getSharedBridge, buildSlashCommandResult } from "../lib/slash-command-bridge.ts";
21
21
  import { DashboardFooter } from "./footer.ts";
22
22
  import { DashboardOverlay, showDashboardOverlay } from "./overlay.ts";
23
23
  import type { DashboardState, DashboardMode } from "./types.ts";
24
- import { debug } from "../debug.ts";
24
+ import { debug } from "../lib/debug.ts";
25
25
 
26
26
  /** Valid /dashboard subcommands for tab completion (legacy) */
27
27
  const DASHBOARD_SUBCOMMANDS = ["compact", "raised", "panel", "focus", "open"];
@@ -5,7 +5,7 @@
5
5
  * All rendering/theme concerns are abstracted via the ThemeFn callback.
6
6
  */
7
7
 
8
- import { sharedState } from "../shared-state.ts";
8
+ import { sharedState } from "../lib/shared-state.ts";
9
9
  import type { CleaveState, DesignAssessmentResult, DesignSpecBindingState, DesignTreeDashboardState, OpenSpecDashboardState } from "./types.ts";
10
10
  import type { ProviderRoutingPolicy } from "../lib/model-routing.ts";
11
11
  import {
@@ -527,10 +527,10 @@ function effortItems(effort: any | undefined, expandedKeys: Set<string>): ListIt
527
527
  if (!expandedKeys.has(key)) return items;
528
528
 
529
529
  const fields: Array<[string, string]> = [
530
- ["level", String(effort.level ?? "?")],
531
- ["driver", effort.driverModel ?? "?"],
532
- ["extract", effort.extractionModel ?? "?"],
533
- ["thinking", effort.thinkingLevel ?? "?"],
530
+ ["level", String(effort.level ?? "?")],
531
+ ["driver", effort.driverModel ?? "?"],
532
+ ["extraction", effort.extractionModel ?? effort.resolvedExtractionModelId ?? "?"],
533
+ ["thinking", effort.thinkingLevel ?? "?"],
534
534
  ];
535
535
  for (const [label, val] of fields) {
536
536
  items.push({
@@ -17,11 +17,11 @@
17
17
  */
18
18
 
19
19
  import { spawn } from "node:child_process";
20
- import type { ExtensionContext } from "@cwilson613/pi-coding-agent";
21
- import type { Theme } from "@cwilson613/pi-coding-agent";
22
- import type { TUI } from "@cwilson613/pi-tui";
23
- import { matchesKey, truncateToWidth, visibleWidth } from "@cwilson613/pi-tui";
24
- import { DASHBOARD_UPDATE_EVENT, sharedState } from "../shared-state.ts";
20
+ import type { ExtensionContext } from "@styrene-lab/pi-coding-agent";
21
+ import type { Theme } from "@styrene-lab/pi-coding-agent";
22
+ import type { TUI } from "@styrene-lab/pi-tui";
23
+ import { matchesKey, truncateToWidth, visibleWidth } from "@styrene-lab/pi-tui";
24
+ import { DASHBOARD_UPDATE_EVENT, sharedState } from "../lib/shared-state.ts";
25
25
  import {
26
26
  TABS,
27
27
  MAX_CONTENT_LINES,
@@ -1,4 +1,4 @@
1
- import { truncateToWidth, visibleWidth } from "@cwilson613/pi-tui";
1
+ import { truncateToWidth, visibleWidth } from "@styrene-lab/pi-tui";
2
2
 
3
3
  /**
4
4
  * Pad string `s` to exactly `width` visible columns using visibleWidth().
@@ -180,6 +180,21 @@ export interface RecoveryDashboardState {
180
180
  cooldowns?: RecoveryCooldownSummary[];
181
181
  }
182
182
 
183
+ // ── Dashboard model-topology summaries ──────────────────────
184
+
185
+ export type DashboardModelRole = "driver" | "embeddings" | "extraction" | "fallback";
186
+ export type DashboardModelSource = "cloud" | "local" | "mixed" | "unknown";
187
+ export type DashboardModelState = "active" | "ready" | "fallback" | "offline" | "legacy-alias" | "unknown";
188
+
189
+ export interface DashboardModelRoleSummary {
190
+ role: DashboardModelRole;
191
+ label: string;
192
+ model: string;
193
+ source: DashboardModelSource;
194
+ state: DashboardModelState;
195
+ detail?: string;
196
+ }
197
+
183
198
  // ── Dashboard UI ─────────────────────────────────────────────
184
199
 
185
200
  export type DashboardMode = "compact" | "raised" | "panel" | "focused";
@@ -12,7 +12,7 @@
12
12
  import * as fs from "node:fs";
13
13
  import * as path from "node:path";
14
14
  import * as crypto from "node:crypto";
15
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
15
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
16
16
 
17
17
  /**
18
18
  * Resolve the agent directory the same way pi's getAgentDir() does.
@@ -161,17 +161,9 @@ export default function (pi: ExtensionAPI) {
161
161
  syncKittyTheme(() => {}); // silent in non-UI mode (e.g. pi -p children)
162
162
  }
163
163
 
164
- // --- Terminal tab title branding ---
165
- // Replace the core π symbol with Ω in the terminal tab title.
166
- // This fires after the core title is set, so it overwrites it.
167
- if (ctx.hasUI) {
168
- const sessionName = ctx.sessionManager.getSessionName();
169
- const cwdBasename = path.basename(ctx.cwd);
170
- const title = sessionName
171
- ? `Ω - ${sessionName} - ${cwdBasename}`
172
- : `Ω - ${cwdBasename}`;
173
- ctx.ui.setTitle(title);
174
- }
164
+ // Terminal title is owned entirely by extensions/terminal-title.ts.
165
+ // Do NOT set title here it creates a startup flash before the dynamic
166
+ // title takes over, and the branding prefix was inconsistent here vs π there).
175
167
 
176
168
  // --- Theme default ---
177
169
  try {
@@ -1,15 +1,15 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
 
4
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
4
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
5
5
 
6
6
  import type { DesignNode, DesignTree } from "./types.ts";
7
7
  import { getAllOpenQuestions, countAcceptanceCriteria } from "./tree.ts";
8
- import { sharedState, DASHBOARD_UPDATE_EVENT } from "../shared-state.ts";
9
- import type { DesignTreeDashboardState } from "../shared-state.ts";
8
+ import { sharedState, DASHBOARD_UPDATE_EVENT } from "../lib/shared-state.ts";
9
+ import type { DesignTreeDashboardState } from "../lib/shared-state.ts";
10
10
  import type { DesignAssessmentResult, DesignPipelineCounts } from "../dashboard/types.ts";
11
11
  import type { DesignSpecBinding } from "../openspec/archive-gate.ts";
12
- import { debug } from "../debug.ts";
12
+ import { debug } from "../lib/debug.ts";
13
13
 
14
14
  /** Read assessment.json from openspec/design/<id>/assessment.json if it exists. */
15
15
  function readAssessmentResult(cwd: string, nodeId: string): DesignAssessmentResult | null {
@@ -75,7 +75,7 @@ export function emitDesignTreeState(pi: ExtensionAPI, dt: DesignTree, focused: D
75
75
 
76
76
  const enrichedNodes = nodes.map((n) => {
77
77
  const isSeedLike = n.status === "seed";
78
- const isActivePhase = ["exploring", "decided", "implementing"].includes(n.status);
78
+ const isActivePhase = ["exploring", "resolved", "decided", "implementing"].includes(n.status);
79
79
  // W3 fix: deferred/blocked also receive the neutral sentinel (not undefined)
80
80
  const isPassive = n.status === "deferred" || n.status === "blocked";
81
81
 
@@ -97,7 +97,7 @@ export function emitDesignTreeState(pi: ExtensionAPI, dt: DesignTree, focused: D
97
97
 
98
98
  // Accumulate pipeline counts
99
99
  // C3 fix: deferred/blocked fall into needsSpec so funnel totals reconcile
100
- if (n.status === "decided") {
100
+ if (n.status === "decided" || n.status === "resolved") {
101
101
  pipelineCounts.decided++;
102
102
  } else if (n.status === "implementing") {
103
103
  pipelineCounts.implementing++;
@@ -26,9 +26,9 @@
26
26
  * ╰──────────────────────────────────────────────────────────────────
27
27
  */
28
28
 
29
- import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@cwilson613/pi-tui";
30
- import type { Component } from "@cwilson613/pi-tui";
31
- import type { Theme } from "@cwilson613/pi-coding-agent";
29
+ import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@styrene-lab/pi-tui";
30
+ import type { Component } from "@styrene-lab/pi-tui";
31
+ import type { Theme } from "@styrene-lab/pi-coding-agent";
32
32
  import { STATUS_ICONS, STATUS_COLORS, ISSUE_TYPE_ICONS, PRIORITY_LABELS } from "./types.ts";
33
33
  import type { NodeStatus, IssueType, Priority } from "./types.ts";
34
34