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
@@ -14,8 +14,8 @@
14
14
  * NOTE: All classes use explicit field declarations (not constructor parameter
15
15
  * properties) to remain compatible with Node.js strip-only TypeScript mode.
16
16
  */
17
- import { truncateToWidth, visibleWidth } from "@cwilson613/pi-tui";
18
- import type { Theme } from "@cwilson613/pi-coding-agent";
17
+ import { truncateToWidth, visibleWidth } from "@styrene-lab/pi-tui";
18
+ import type { Theme } from "@styrene-lab/pi-coding-agent";
19
19
 
20
20
  export interface SciComponent {
21
21
  render(width: number): string[];
@@ -302,3 +302,120 @@ export function sciExpanded(lines: string[], footer: string, theme: Theme): SciE
302
302
  export function sciBanner(glyph: string, label: string, lines: string[], theme: Theme): SciBanner {
303
303
  return new SciBanner(glyph, label, lines, theme);
304
304
  }
305
+
306
+ // ─── SciExitCard ─────────────────────────────────────────────────────────
307
+ //
308
+ // ── ⏛ session:end ──────────────────────────────────────────────────
309
+ // │ main · clean ◈ 80 nodes · 69 implemented
310
+ // │ 📋 2 active changes 🧠 1462 facts (+3) · 94% indexed
311
+ // ╰──────────────────────────────────────────────────────────────────
312
+
313
+ export interface ExitCardData {
314
+ branch?: string;
315
+ dirtyCount?: number;
316
+ designNodes?: number;
317
+ designImplemented?: number;
318
+ designDecided?: number;
319
+ designExploring?: number;
320
+ openspecActive?: string[];
321
+ factCount: number;
322
+ factDelta: number;
323
+ embeddingPct?: number;
324
+ embeddingAvailable: boolean;
325
+ }
326
+
327
+ export class SciExitCard implements SciComponent {
328
+ data: ExitCardData;
329
+ theme: Theme;
330
+
331
+ constructor(data: ExitCardData, theme: Theme) {
332
+ this.data = data;
333
+ this.theme = theme;
334
+ }
335
+
336
+ render(width: number): string[] {
337
+ const th = this.theme;
338
+ const d = this.data;
339
+ const innerW = Math.max(1, width - 4);
340
+
341
+ // Header
342
+ const label = ` ${th.fg("accent", "⏛")} ${th.fg("muted", "session:end")} `;
343
+ const labelVw = visibleWidth(label);
344
+ const headerFill = Math.max(0, width - labelVw - 2);
345
+ const header = th.fg("dim", "──") + label + th.fg("dim", "─".repeat(headerFill));
346
+ const pipe = th.fg("dim", " │");
347
+
348
+ const lines: string[] = [truncateToWidth(header, width)];
349
+
350
+ // Row 1: git + design tree (two columns)
351
+ const gitPart = d.branch
352
+ ? th.fg("success", d.branch) +
353
+ th.fg("dim", " · ") +
354
+ (d.dirtyCount && d.dirtyCount > 0
355
+ ? th.fg("warning", `${d.dirtyCount} dirty`)
356
+ : th.fg("muted", "clean"))
357
+ : null;
358
+
359
+ const dtPart = d.designNodes && d.designNodes > 0
360
+ ? th.fg("accent", "◈") + " " +
361
+ th.fg("muted", `${d.designNodes} nodes`) +
362
+ th.fg("dim", " · ") +
363
+ th.fg("success", `${d.designImplemented ?? 0}✓`) +
364
+ (d.designDecided ? th.fg("dim", " · ") + th.fg("muted", `${d.designDecided}●`) : "") +
365
+ (d.designExploring ? th.fg("dim", " · ") + th.fg("accent", `${d.designExploring}◐`) : "")
366
+ : null;
367
+
368
+ if (gitPart && dtPart) {
369
+ const gitVw = visibleWidth(gitPart);
370
+ const dtVw = visibleWidth(dtPart);
371
+ const gap = Math.max(2, innerW - gitVw - dtVw);
372
+ lines.push(truncateToWidth(pipe + " " + gitPart + " ".repeat(gap) + dtPart, width));
373
+ } else if (gitPart) {
374
+ lines.push(truncateToWidth(pipe + " " + gitPart, width));
375
+ } else if (dtPart) {
376
+ lines.push(truncateToWidth(pipe + " " + dtPart, width));
377
+ }
378
+
379
+ // Row 2: openspec + memory (two columns)
380
+ const osPart = d.openspecActive && d.openspecActive.length > 0
381
+ ? th.fg("muted", `${d.openspecActive.length} active`) +
382
+ th.fg("dim", " ─ ") +
383
+ th.fg("muted", d.openspecActive.slice(0, 3).join(", ")) +
384
+ (d.openspecActive.length > 3 ? th.fg("dim", `…+${d.openspecActive.length - 3}`) : "")
385
+ : null;
386
+
387
+ const deltaStr = d.factDelta > 0
388
+ ? th.fg("success", ` +${d.factDelta}`)
389
+ : d.factDelta < 0
390
+ ? th.fg("warning", ` ${d.factDelta}`)
391
+ : "";
392
+ const idxStr = d.embeddingAvailable && d.embeddingPct !== undefined
393
+ ? th.fg("dim", " · ") + th.fg("muted", `${d.embeddingPct}% indexed`)
394
+ : !d.embeddingAvailable
395
+ ? th.fg("dim", " · ") + th.fg("muted", "semantic off")
396
+ : "";
397
+ const memPart = th.fg("accent", "⌗") + " " +
398
+ th.fg("muted", `${d.factCount} facts`) + deltaStr + idxStr;
399
+
400
+ if (osPart) {
401
+ const osVw = visibleWidth(osPart);
402
+ const memVw = visibleWidth(memPart);
403
+ const gap = Math.max(2, innerW - osVw - memVw);
404
+ lines.push(truncateToWidth(pipe + " " + osPart + " ".repeat(gap) + memPart, width));
405
+ } else {
406
+ lines.push(truncateToWidth(pipe + " " + memPart, width));
407
+ }
408
+
409
+ // Footer rule
410
+ const footerFill = Math.max(0, width - 5);
411
+ lines.push(th.fg("dim", " ╰" + "─".repeat(footerFill)));
412
+
413
+ return lines;
414
+ }
415
+
416
+ invalidate(): void {}
417
+ }
418
+
419
+ export function sciExitCard(data: ExitCardData, theme: Theme): SciExitCard {
420
+ return new SciExitCard(data, theme);
421
+ }
@@ -12,13 +12,13 @@ import type {
12
12
  OpenSpecDashboardState,
13
13
  CleaveState,
14
14
  RecoveryDashboardState,
15
- } from "./dashboard/types.ts";
15
+ } from "../dashboard/types.ts";
16
16
 
17
- import type { EffortState } from "./effort/types.ts";
18
- import type { ProviderRoutingPolicy } from "./lib/model-routing.ts";
19
- import type { MemoryInjectionMetrics } from "./project-memory/injection-metrics.ts";
20
- import type { LifecycleMemoryMessage } from "./project-memory/types.ts";
21
- import { getDefaultPolicy } from "./lib/model-routing.ts";
17
+ import type { EffortState } from "../effort/types.ts";
18
+ import type { ProviderRoutingPolicy } from "./model-routing.ts";
19
+ import type { MemoryInjectionMetrics } from "../project-memory/injection-metrics.ts";
20
+ import type { LifecycleMemoryMessage } from "../project-memory/types.ts";
21
+ import { getDefaultPolicy } from "./model-routing.ts";
22
22
 
23
23
  export type RecoveryFailureClassification =
24
24
  | "transient_server_error"
@@ -68,10 +68,10 @@ export type {
68
68
  CleaveStatus,
69
69
  DashboardMode,
70
70
  DashboardState,
71
- } from "./dashboard/types.ts";
71
+ } from "../dashboard/types.ts";
72
72
 
73
73
  // Re-export routing types for consumer convenience
74
- export type { ProviderRoutingPolicy, ResolvedTierModel, ModelTier, ProviderName } from "./lib/model-routing.ts";
74
+ export type { ProviderRoutingPolicy, ResolvedTierModel, ModelTier, ProviderName } from "./model-routing.ts";
75
75
 
76
76
  // Re-export effort types for consumer convenience
77
77
  export type {
@@ -81,7 +81,7 @@ export type {
81
81
  EffortModelTier,
82
82
  EffortName,
83
83
  ThinkingLevel,
84
- } from "./effort/types.ts";
84
+ } from "../effort/types.ts";
85
85
 
86
86
  /** Event channel fired by producers after writing dashboard state. */
87
87
  export const DASHBOARD_UPDATE_EVENT = "dashboard:update" as const;
@@ -131,6 +131,10 @@ interface SharedState {
131
131
 
132
132
  /** Per-request retry ledger for bounded recovery decisions across core and extension-driven retries. */
133
133
  recoveryRetryCounts?: Record<string, number>;
134
+
135
+ /** Set by bootstrap when first-run is detected. Other extensions should suppress
136
+ * redundant "no providers" warnings when this is true — bootstrap handles guidance. */
137
+ bootstrapPending?: boolean;
134
138
  }
135
139
 
136
140
  // Initialize once on first import, reuse thereafter via global symbol.
@@ -1,6 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import type { Static } from "@sinclair/typebox";
3
- import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, RegisteredCommand, ToolDefinition } from "@cwilson613/pi-coding-agent";
3
+ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, RegisteredCommand, ToolDefinition } from "@styrene-lab/pi-coding-agent";
4
4
 
5
5
  export const SIDE_EFFECT_CLASSES = [
6
6
  "read",
@@ -1,7 +1,7 @@
1
- import "@cwilson613/pi-coding-agent";
2
- import type { SlashCommandBridgeMetadata, SlashCommandBridgeResult, SlashCommandExecutionContext } from "./lib/slash-command-bridge.js";
1
+ import "@styrene-lab/pi-coding-agent";
2
+ import type { SlashCommandBridgeMetadata, SlashCommandBridgeResult, SlashCommandExecutionContext } from "./slash-command-bridge.js";
3
3
 
4
- declare module "@cwilson613/pi-coding-agent" {
4
+ declare module "@styrene-lab/pi-coding-agent" {
5
5
  interface RegisteredCommand {
6
6
  bridge?: SlashCommandBridgeMetadata;
7
7
  structuredExecutor?: (args: string, ctx: SlashCommandExecutionContext) => Promise<SlashCommandBridgeResult>;
@@ -21,7 +21,7 @@
21
21
  */
22
22
 
23
23
  import { execSync, spawn, type ChildProcess } from "node:child_process";
24
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
24
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
25
25
  import { Type } from "@sinclair/typebox";
26
26
  import { StringEnum } from "../lib/typebox-helpers";
27
27
 
@@ -1,6 +1,6 @@
1
1
  // @secret GITHUB_TOKEN "GitHub personal access token for MCP server auth (Scribe, etc.)"
2
2
 
3
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
3
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
4
4
  import { Type } from "@sinclair/typebox";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -18,14 +18,14 @@
18
18
  */
19
19
 
20
20
  import { createHash } from "node:crypto";
21
- import type { ExtensionAPI, ExtensionContext } from "@cwilson613/pi-coding-agent";
22
- import type { ImageContent, Model, TextContent } from "@cwilson613/pi-ai";
23
- import { DASHBOARD_UPDATE_EVENT, sharedState } from "./shared-state.ts";
24
- import type { RecoveryEvent, RecoveryFailureClassification } from "./shared-state.ts";
21
+ import type { ExtensionAPI, ExtensionContext } from "@styrene-lab/pi-coding-agent";
22
+ import type { ImageContent, Model, TextContent } from "@styrene-lab/pi-ai";
23
+ import { DASHBOARD_UPDATE_EVENT, sharedState } from "./lib/shared-state.ts";
24
+ import type { RecoveryEvent, RecoveryFailureClassification } from "./lib/shared-state.ts";
25
25
  import type { RecoveryAction, RecoveryCooldownSummary, RecoveryDashboardState, RecoveryTarget } from "./dashboard/types.ts";
26
26
  import { tierConfig } from "./effort/tiers.ts";
27
27
  import type { EffortLevel } from "./effort/types.ts";
28
- import { clampThinkingLevel, classifyUpstreamFailure, getDefaultPolicy, getTierDisplayLabel, resolveTier, type CapabilityRuntimeState, type ModelTier, type RegistryModel, type UpstreamFailureClassification } from "./lib/model-routing.ts";
28
+ import { clampThinkingLevel, classifyUpstreamFailure, getDefaultPolicy, getViableModels, getTierDisplayLabel, resolveTier, type CapabilityRuntimeState, type ModelTier, type RegistryModel, type UpstreamFailureClassification } from "./lib/model-routing.ts";
29
29
  import { writeLastUsedModel } from "./lib/model-preferences.ts";
30
30
  import { loadOperatorRuntimeState, readOperatorProfile, toCapabilityProfile, toCapabilityRuntimeState } from "./lib/operator-profile.ts";
31
31
  import { buildFallbackGuidance, explainTierResolutionFailure, planRecoveryForModel, recordTransientFailureForModel, type RecoveryPlan } from "./lib/operator-fallback.ts";
@@ -420,7 +420,7 @@ async function applyRecoveryPlan(
420
420
  }
421
421
 
422
422
  async function switchTo(tier: TierName, pi: ExtensionAPI, ctx: ExtensionContext): Promise<RegistryModel | null> {
423
- const all = ctx.modelRegistry.getAll() as unknown as RegistryModel[];
423
+ const all = getViableModels(ctx.modelRegistry);
424
424
  const { policy, profile, runtimeState } = getResolverInputs(ctx);
425
425
  const resolved = resolveTier(tier, all, policy, runtimeState, profile);
426
426
  if (!resolved) return null;
@@ -443,7 +443,7 @@ function currentTierName(ctx: ExtensionContext): TierName | null {
443
443
  const model = ctx.model;
444
444
  if (!model) return null;
445
445
  // Resolve the current model against the registry using the shared resolver
446
- const all = ctx.modelRegistry.getAll() as unknown as RegistryModel[];
446
+ const all = getViableModels(ctx.modelRegistry);
447
447
  const { policy, profile, runtimeState } = getResolverInputs(ctx);
448
448
  for (const tier of ["gloriana", "victory", "retribution", "local"] as TierName[]) {
449
449
  const resolved = resolveTier(tier, all, policy, runtimeState, profile);
@@ -498,7 +498,7 @@ export default function (pi: ExtensionAPI) {
498
498
 
499
499
  const persistedRuntimeState = recordTransientFailureForModel(ctx.cwd, ctx.model, errorMessage);
500
500
  const { policy, profile } = getResolverInputs(ctx);
501
- const models = ctx.modelRegistry.getAll() as unknown as RegistryModel[];
501
+ const models = getViableModels(ctx.modelRegistry);
502
502
  const runtimeState = persistedRuntimeState ?? toCapabilityRuntimeState(loadOperatorRuntimeState(ctx.cwd));
503
503
  const plan = planRecoveryForModel(ctx.model, errorMessage, models, policy, profile, runtimeState ?? {}, Date.now());
504
504
  const guidance = buildFallbackGuidance(ctx.model, models, policy, profile, runtimeState ?? {}, Date.now());
@@ -659,7 +659,7 @@ export default function (pi: ExtensionAPI) {
659
659
  const { policy, profile, runtimeState } = getResolverInputs(ctx);
660
660
  const failure = explainTierResolutionFailure(
661
661
  tier,
662
- ctx.modelRegistry.getAll() as unknown as RegistryModel[],
662
+ getViableModels(ctx.modelRegistry),
663
663
  policy,
664
664
  profile,
665
665
  runtimeState,
@@ -739,7 +739,7 @@ export default function (pi: ExtensionAPI) {
739
739
  const { policy, profile, runtimeState } = getResolverInputs(ctx);
740
740
  const failure = explainTierResolutionFailure(
741
741
  tier,
742
- ctx.modelRegistry.getAll() as unknown as RegistryModel[],
742
+ getViableModels(ctx.modelRegistry),
743
743
  policy,
744
744
  profile,
745
745
  runtimeState,
@@ -1,13 +1,14 @@
1
1
  // @config LOCAL_INFERENCE_URL "Ollama / OpenAI-compatible inference server URL" [default: http://localhost:11434]
2
2
 
3
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
4
- import { Text } from "@cwilson613/pi-tui";
3
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
4
+ import { Text } from "@styrene-lab/pi-tui";
5
5
  import { Type } from "@sinclair/typebox";
6
6
  import {
7
7
  KNOWN_MODELS,
8
8
  PREFERRED_ORDER,
9
9
  PREFERRED_ORDER_CODE,
10
10
  } from "./lib/local-models.ts";
11
+ import { filterDeprecated, type RegistryModel } from "./lib/model-routing.ts";
11
12
 
12
13
  // Re-export so existing importers (effort, cleave) continue to work.
13
14
  export { PREFERRED_ORDER, PREFERRED_ORDER_CODE };
@@ -204,7 +205,7 @@ export async function restoreCloudDriver(
204
205
 
205
206
  // If no saved model, find the best available gloriana-class model by prefix
206
207
  if (!modelId) {
207
- const all = ctx.modelRegistry.getAll();
208
+ const all = filterDeprecated(ctx.modelRegistry.getAvailable() as unknown as RegistryModel[]);
208
209
  const topTier = all
209
210
  .filter((m: any) => m.provider === "anthropic" && m.id.startsWith("claude-opus"))
210
211
  .sort((a: any, b: any) => b.id.localeCompare(a.id));
@@ -284,7 +285,13 @@ export default function (pi: ExtensionAPI) {
284
285
  parts.push("🏠 Ollama: not running");
285
286
  }
286
287
 
287
- ctx.ui.notify(parts.join(" | "), anthropicOk ? "info" : "warning");
288
+ // Suppress noisy status during first-run bootstrap handles guidance
289
+ // Suppress the "unreachable" warning during first-run (bootstrap handles it),
290
+ // but always show success status so the operator sees their provider state.
291
+ const { sharedState } = await import("./lib/shared-state.ts");
292
+ if (anthropicOk || !sharedState.bootstrapPending) {
293
+ ctx.ui.notify(parts.join(" | "), anthropicOk ? "info" : "warning");
294
+ }
288
295
 
289
296
  // Save starting cloud model
290
297
  const current = ctx.model;
@@ -154,7 +154,7 @@ export function transitionDesignNodesOnArchive(cwd: string, changeName: string):
154
154
  const transitioned: string[] = [];
155
155
 
156
156
  for (const node of resolveBoundDesignNodes(cwd, changeName)) {
157
- const transitionable = node.status === "implementing" || node.status === "decided";
157
+ const transitionable = node.status === "implementing" || node.status === "decided" || node.status === "resolved";
158
158
  if (!transitionable) continue;
159
159
  const sections = getNodeSections(node);
160
160
  writeNodeDocument({ ...node, status: "implemented" }, sections);
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
2
2
 
3
3
  /**
4
4
  * Delete local git branches that are fully merged into HEAD.
@@ -1,7 +1,7 @@
1
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
2
2
 
3
- import { sharedState, DASHBOARD_UPDATE_EVENT } from "../shared-state.ts";
4
- import { debug } from "../debug.ts";
3
+ import { sharedState, DASHBOARD_UPDATE_EVENT } from "../lib/shared-state.ts";
4
+ import { debug } from "../lib/debug.ts";
5
5
  import { listChanges } from "./spec.ts";
6
6
  import { buildLifecycleSummary } from "./lifecycle.ts";
7
7
 
@@ -18,12 +18,12 @@
18
18
  * openspec_manage — Agent-callable change lifecycle operations
19
19
  */
20
20
 
21
- import type { ExtensionAPI, ExtensionContext } from "@cwilson613/pi-coding-agent";
21
+ import type { ExtensionAPI, ExtensionContext } from "@styrene-lab/pi-coding-agent";
22
22
  import { Type } from "@sinclair/typebox";
23
23
  import { StringEnum } from "../lib/typebox-helpers.ts";
24
- import { Text } from "@cwilson613/pi-tui";
25
- import { sciCall, sciLoading, sciOk, sciErr, sciExpanded } from "../sci-ui.ts";
26
- import { sciBanner } from "../sci-ui.ts";
24
+ import { Text } from "@styrene-lab/pi-tui";
25
+ import { sciCall, sciLoading, sciOk, sciErr, sciExpanded } from "../lib/sci-ui.ts";
26
+ import { sciBanner } from "../lib/sci-ui.ts";
27
27
  import * as fs from "node:fs";
28
28
  import * as path from "node:path";
29
29
  import { getSharedBridge, buildSlashCommandResult, type BridgedSlashCommand, type SlashCommandExecutionContext } from "../lib/slash-command-bridge.ts";
@@ -63,7 +63,7 @@ import {
63
63
  import { scanDesignDocs } from "../design-tree/tree.ts";
64
64
  import { emitDesignTreeState } from "../design-tree/dashboard-state.ts";
65
65
  import { emitArchiveCandidates, emitReconcileCandidates } from "./lifecycle-emitter.ts";
66
- import { sharedState } from "../shared-state.ts";
66
+ import { sharedState } from "../lib/shared-state.ts";
67
67
 
68
68
  interface AssessmentState {
69
69
  record: AssessmentRecord | null;
@@ -1428,10 +1428,6 @@ export class FactStore {
1428
1428
  created_at: fact.created_at,
1429
1429
  source: fact.source,
1430
1430
  content_hash: fact.content_hash,
1431
- confidence: fact.confidence,
1432
- last_reinforced: fact.last_reinforced,
1433
- reinforcement_count: fact.reinforcement_count,
1434
- decay_rate: fact.decay_rate,
1435
1431
  supersedes: fact.supersedes,
1436
1432
  }));
1437
1433
  }
@@ -1449,10 +1445,6 @@ export class FactStore {
1449
1445
  target_fact_id: edge.target_fact_id,
1450
1446
  relation: edge.relation,
1451
1447
  description: edge.description,
1452
- confidence: edge.confidence,
1453
- last_reinforced: edge.last_reinforced,
1454
- reinforcement_count: edge.reinforcement_count,
1455
- decay_rate: edge.decay_rate,
1456
1448
  source_mind: edge.source_mind,
1457
1449
  target_mind: edge.target_mind,
1458
1450
  }));
@@ -1497,9 +1489,11 @@ export class FactStore {
1497
1489
  // Map from imported fact ID → local fact ID (for edge remapping)
1498
1490
  const factIdMap = new Map<string, string>();
1499
1491
 
1500
- // Pre-dedup: merge=union in git can produce multiple lines with the same id
1501
- // but different metadata (reinforcement_count, last_reinforced). Keep only the
1502
- // line with the highest reinforcement_count per id to prevent churn.
1492
+ // Pre-dedup: merge=union in git can produce multiple lines with the same id.
1493
+ // Legacy exports may differ in volatile scoring metadata
1494
+ // (reinforcement_count, last_reinforced); newer stable exports may be byte-identical
1495
+ // apart from line duplication. Prefer the record with stronger legacy metadata when
1496
+ // present, otherwise keep the first durable record encountered.
1503
1497
  const dedupedRecords: any[] = [];
1504
1498
  const seenById = new Map<string, number>(); // id → index in dedupedRecords
1505
1499
  for (const line of jsonl.split("\n")) {
@@ -42,12 +42,13 @@
42
42
 
43
43
  import * as path from "node:path";
44
44
  import * as os from "node:os";
45
- import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext, SessionMessageEntry } from "@cwilson613/pi-coding-agent";
46
- import { DynamicBorder } from "@cwilson613/pi-coding-agent";
45
+ import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext, SessionMessageEntry } from "@styrene-lab/pi-coding-agent";
46
+ import { DynamicBorder } from "@styrene-lab/pi-coding-agent";
47
47
  import { sciCall, sciOk, sciErr, sciExpanded, sciLoading } from "./sci-renderers.ts";
48
+ import { sciExitCard, type ExitCardData } from "../lib/sci-ui.ts";
48
49
  import { StringEnum } from "../lib/typebox-helpers";
49
50
  import { Type } from "@sinclair/typebox";
50
- import { Container, type SelectItem, SelectList, Text } from "@cwilson613/pi-tui";
51
+ import { Container, type SelectItem, SelectList, Text } from "@styrene-lab/pi-tui";
51
52
  import { FactStore, parseExtractionOutput, GLOBAL_DECAY, type MindRecord, type Fact } from "./factstore.ts";
52
53
  import { embed, isEmbeddingAvailable, resolveEmbeddingProvider, MODEL_DIMS, type EmbeddingProvider } from "./embeddings.ts";
53
54
  import { DEFAULT_CONFIG, type MemoryConfig, type LifecycleMemoryCandidate } from "./types.ts";
@@ -68,8 +69,8 @@ import {
68
69
  import { runExtractionV2, runGlobalExtraction, killActiveExtraction, killAllSubprocesses, generateEpisode, generateEpisodeDirect, generateEpisodeWithFallback, buildTemplateEpisode, runSectionPruningPass, type SessionTelemetry } from "./extraction-v2.ts";
69
70
  import { migrateToFactStore, needsMigration, markMigrated } from "./migration.ts";
70
71
  import { SECTIONS } from "./template.ts";
71
- import { serializeConversation, convertToLlm } from "@cwilson613/pi-coding-agent";
72
- import { sharedState } from "../shared-state.ts";
72
+ import { serializeConversation, convertToLlm } from "@styrene-lab/pi-coding-agent";
73
+ import { sharedState } from "../lib/shared-state.ts";
73
74
  import {
74
75
  ingestLifecycleCandidate,
75
76
  ingestLifecycleCandidatesBatch,
@@ -81,6 +82,7 @@ import {
81
82
  resolveTier,
82
83
  getTierDisplayLabel,
83
84
  getDefaultPolicy,
85
+ getViableModels,
84
86
  type ModelTier,
85
87
  type RegistryModel
86
88
  } from "../lib/model-routing.ts";
@@ -881,13 +883,18 @@ export default function (pi: ExtensionAPI) {
881
883
  .slice(0, 10);
882
884
 
883
885
  // Merge: recent episodes + recency window + core sections (deduplicated)
886
+ // Budget-capped to prevent context overflow on large fact stores.
887
+ const STARTUP_MAX_CHARS = 12_000; // ~3K tokens — leaves room for system prompt + design-tree
888
+ let startupChars = 0;
884
889
  const startupFactIds = new Set<string>();
885
890
  const startupFacts: typeof allFacts = [];
886
891
  for (const f of [...coreFacts, ...archFacts, ...recentFacts]) {
887
- if (!startupFactIds.has(f.id)) {
888
- startupFacts.push(f);
889
- startupFactIds.add(f.id);
890
- }
892
+ if (startupFactIds.has(f.id)) continue;
893
+ const cost = f.content.length + 20;
894
+ if (startupChars + cost > STARTUP_MAX_CHARS) break;
895
+ startupFacts.push(f);
896
+ startupFactIds.add(f.id);
897
+ startupChars += cost;
891
898
  }
892
899
 
893
900
  if (recentEpisodes.length > 0 || startupFacts.length > 0) {
@@ -1062,7 +1069,7 @@ export default function (pi: ExtensionAPI) {
1062
1069
  return await tryLocalCompaction(localModel, prep, customInstructions, combinedSignal);
1063
1070
  } else {
1064
1071
  // Use cloud model via model registry
1065
- const all = ctx.modelRegistry.getAll() as unknown as RegistryModel[];
1072
+ const all = getViableModels(ctx.modelRegistry);
1066
1073
  const policy = sharedState.routingPolicy ?? getDefaultPolicy();
1067
1074
  const resolved = resolveTier(tier, all, policy);
1068
1075
 
@@ -3305,6 +3312,11 @@ export default function (pi: ExtensionAPI) {
3305
3312
  },
3306
3313
  });
3307
3314
 
3315
+ pi.registerMessageRenderer("session-exit", (_message, _options, theme) => {
3316
+ const data = (_message.details ?? {}) as ExitCardData;
3317
+ return sciExitCard(data, theme);
3318
+ });
3319
+
3308
3320
  pi.registerCommand("exit", {
3309
3321
  description: "Run memory extraction and exit gracefully (avoids /reload terminal corruption)",
3310
3322
  handler: async (_args, ctx) => {
@@ -3391,22 +3403,28 @@ export default function (pi: ExtensionAPI) {
3391
3403
  exitEpisodeDone = true;
3392
3404
  }
3393
3405
 
3394
- // Build session-end summary from shared state
3395
- const summaryLines: string[] = [];
3406
+ // Build session-end card data
3407
+ const exitData: ExitCardData = {
3408
+ factCount: factsAfter,
3409
+ factDelta: delta,
3410
+ embeddingAvailable,
3411
+ };
3396
3412
 
3397
3413
  // Git state
3398
3414
  try {
3399
3415
  const branchResult = await pi.exec("git", ["branch", "--show-current"], { timeout: 3_000, cwd: ctx.cwd });
3400
3416
  const statusResult = await pi.exec("git", ["status", "--short"], { timeout: 3_000, cwd: ctx.cwd });
3401
- const branchName = branchResult.stdout.trim();
3402
- const dirtyCount = statusResult.stdout.trim().split("\n").filter(Boolean).length;
3403
- summaryLines.push(`🔀 ${branchName}${dirtyCount > 0 ? ` · ${dirtyCount} dirty` : " · clean"}`);
3417
+ exitData.branch = branchResult.stdout.trim();
3418
+ exitData.dirtyCount = statusResult.stdout.trim().split("\n").filter(Boolean).length;
3404
3419
  } catch { /* ignore */ }
3405
3420
 
3406
3421
  // Design tree
3407
3422
  const dt = sharedState.designTree;
3408
3423
  if (dt && dt.nodeCount > 0) {
3409
- summaryLines.push(`🌳 Design: ${dt.nodeCount} nodes (${dt.decidedCount} decided, ${dt.exploringCount} exploring)`);
3424
+ exitData.designNodes = dt.nodeCount;
3425
+ exitData.designImplemented = dt.implementedCount;
3426
+ exitData.designDecided = dt.decidedCount;
3427
+ exitData.designExploring = dt.exploringCount;
3410
3428
  }
3411
3429
 
3412
3430
  // OpenSpec
@@ -3414,30 +3432,26 @@ export default function (pi: ExtensionAPI) {
3414
3432
  if (os && os.changes.length > 0) {
3415
3433
  const active = os.changes.filter(c => c.stage !== "archived");
3416
3434
  if (active.length > 0) {
3417
- summaryLines.push(`📋 OpenSpec: ${active.length} active — ${active.map(c => c.name).join(", ")}`);
3435
+ exitData.openspecActive = active.map(c => c.name);
3418
3436
  }
3419
3437
  }
3420
3438
 
3421
- // Memory + embedding coverage
3422
- const vecCount = store ? store.countFactVectors(mind) : 0;
3423
- const coveragePct = factsAfter > 0 ? Math.round((vecCount / factsAfter) * 100) : 100;
3424
- const embeddingInfo = embeddingAvailable
3425
- ? ` · ${coveragePct}% indexed`
3426
- : " · semantic search off";
3427
- const memLine = delta > 0
3428
- ? `🧠 ${factsAfter} facts (+${delta} new)${embeddingInfo}`
3429
- : `🧠 ${factsAfter} facts${embeddingInfo}`;
3430
- summaryLines.push(memLine);
3431
-
3432
- if (summaryLines.length > 0) {
3433
- ctx.ui.notify(summaryLines.join("\n"), "info");
3434
- await new Promise(r => setTimeout(r, 300));
3439
+ // Embedding coverage
3440
+ if (store && embeddingAvailable) {
3441
+ const vecCount = store.countFactVectors(mind);
3442
+ exitData.embeddingPct = factsAfter > 0 ? Math.round((vecCount / factsAfter) * 100) : 100;
3435
3443
  }
3436
3444
 
3437
- ctx.ui.notify("Goodbye!", "info");
3445
+ // Render as a proper sci-ui card in the conversation
3446
+ pi.sendMessage({
3447
+ customType: "session-exit",
3448
+ content: "Session ended.",
3449
+ details: exitData,
3450
+ display: true,
3451
+ });
3438
3452
 
3439
- // Small delay so the notification renders
3440
- await new Promise(r => setTimeout(r, 200));
3453
+ // Let the card render before shutdown tears down the TUI
3454
+ await new Promise(r => setTimeout(r, 500));
3441
3455
 
3442
3456
  // ctx.shutdown() is fire-and-forget internally (sets shutdownRequested flag
3443
3457
  // and calls void this.shutdown() in interactive mode). We must keep this
@@ -3,7 +3,7 @@
3
3
  "version": "1.0.0",
4
4
  "type": "commonjs",
5
5
  "dependencies": {
6
- "@cwilson613/pi-tui": "0.57.1-cwilson613.1",
6
+ "@styrene-lab/pi-tui": "0.57.1-cwilson613.1",
7
7
  "better-sqlite3": "^12.6.2"
8
8
  }
9
9
  }
@@ -4,4 +4,4 @@
4
4
  * Re-exports the shared sci-ui primitives and adds memory-specific
5
5
  * formatting helpers for structured card rendering.
6
6
  */
7
- export { sciCall, sciOk, sciErr, sciExpanded, sciLoading } from "../sci-ui.ts";
7
+ export { sciCall, sciOk, sciErr, sciExpanded, sciLoading } from "../lib/sci-ui.ts";
@@ -45,7 +45,7 @@ import {
45
45
  composeNativeDiagram,
46
46
  rasterizeSvgToPng,
47
47
  } from "./native-diagrams/index.ts";
48
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
48
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
49
49
  import { Type } from "@sinclair/typebox";
50
50
 
51
51
  // ---------------------------------------------------------------------------
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { existsSync, readFileSync } from "node:fs";
14
14
  import { join, basename } from "node:path";
15
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
15
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
16
16
 
17
17
  const SESSION_LOG_HEADER = `# Session Log
18
18
 
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
1
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
2
2
 
3
3
  const verbs = [
4
4
  "Communing with the Machine Spirit",
@@ -5,7 +5,7 @@
5
5
  * Subcommands: (none), palette, d2, excalidraw, check <file>
6
6
  */
7
7
 
8
- import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
8
+ import type { ExtensionAPI } from "@styrene-lab/pi-coding-agent";
9
9
 
10
10
  // ---------------------------------------------------------------------------
11
11
  // Palette data — single source of truth