@pellux/goodvibes-tui 1.9.0 → 1.9.2

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 (75) hide show
  1. package/CHANGELOG.md +24 -2
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/src/audio/player.ts +91 -5
  5. package/src/audio/spoken-turn-controller.ts +30 -2
  6. package/src/audio/spoken-turn-wiring.ts +3 -0
  7. package/src/config/credential-availability.ts +122 -0
  8. package/src/config/index.ts +15 -0
  9. package/src/core/turn-cancellation.ts +1 -1
  10. package/src/daemon/cli.ts +4 -4
  11. package/src/daemon/service-commands.ts +8 -8
  12. package/src/input/command-registry.ts +5 -5
  13. package/src/input/commands/config.ts +1 -1
  14. package/src/input/commands/memory.ts +1 -1
  15. package/src/input/commands/planning-runtime.ts +1 -1
  16. package/src/input/commands/recall-review.ts +2 -2
  17. package/src/input/commands/session-content.ts +1 -1
  18. package/src/input/feed-context-factory.ts +1 -1
  19. package/src/input/handler-content-actions.ts +2 -2
  20. package/src/input/handler-feed-routes.ts +2 -2
  21. package/src/input/handler-feed.ts +1 -1
  22. package/src/input/handler-onboarding-daemon-adopt.ts +3 -3
  23. package/src/input/handler-picker-routes.ts +2 -2
  24. package/src/input/handler-shortcuts.ts +1 -1
  25. package/src/input/model-picker.ts +2 -2
  26. package/src/input/onboarding/onboarding-wizard-apply.ts +3 -3
  27. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +3 -3
  28. package/src/input/onboarding/onboarding-wizard-steps.ts +1 -1
  29. package/src/input/session-picker-modal.ts +1 -1
  30. package/src/input/settings-modal-data.ts +2 -2
  31. package/src/main.ts +5 -4
  32. package/src/panels/base-panel.ts +1 -1
  33. package/src/panels/builtin/operations.ts +4 -4
  34. package/src/panels/builtin/shared.ts +1 -1
  35. package/src/panels/fleet-panel-format.ts +1 -1
  36. package/src/panels/fleet-panel.ts +15 -15
  37. package/src/panels/fleet-read-model.ts +11 -11
  38. package/src/panels/fleet-steer.ts +2 -2
  39. package/src/panels/fleet-stop.ts +2 -2
  40. package/src/panels/fleet-tabs.ts +4 -4
  41. package/src/panels/fleet-transcript.ts +5 -5
  42. package/src/panels/types.ts +1 -1
  43. package/src/renderer/compaction-quality.ts +1 -1
  44. package/src/renderer/fleet-tab-strip.ts +2 -2
  45. package/src/renderer/footer-tips.ts +1 -1
  46. package/src/renderer/model-workspace.ts +1 -1
  47. package/src/renderer/session-picker-modal.ts +4 -4
  48. package/src/renderer/shell-surface.ts +1 -1
  49. package/src/renderer/status-glyphs.ts +3 -3
  50. package/src/renderer/terminal-bg-probe.ts +1 -1
  51. package/src/renderer/theme.ts +2 -2
  52. package/src/renderer/turn-injection.ts +3 -3
  53. package/src/renderer/ui-factory.ts +7 -7
  54. package/src/renderer/ui-primitives.ts +3 -3
  55. package/src/runtime/bootstrap-core.ts +15 -15
  56. package/src/runtime/bootstrap-hook-bridge.ts +2 -2
  57. package/src/runtime/bootstrap-shell.ts +3 -3
  58. package/src/runtime/bootstrap.ts +5 -5
  59. package/src/runtime/code-index-services.ts +2 -2
  60. package/src/runtime/index.ts +11 -3
  61. package/src/runtime/legacy-daemon-migration.ts +9 -9
  62. package/src/runtime/memory-fold.ts +30 -1
  63. package/src/runtime/onboarding/snapshot.ts +3 -3
  64. package/src/runtime/onboarding/types.ts +2 -2
  65. package/src/runtime/operator-token-cleanup.ts +2 -2
  66. package/src/runtime/orchestrator-core-services.ts +2 -2
  67. package/src/runtime/process-lifecycle.ts +19 -5
  68. package/src/runtime/services.ts +14 -14
  69. package/src/runtime/session-inbound-inputs.ts +1 -1
  70. package/src/runtime/session-spine-transport.ts +2 -2
  71. package/src/runtime/ui-services.ts +1 -1
  72. package/src/runtime/workstream-services.ts +1 -1
  73. package/src/runtime/wrfc-persistence.ts +2 -2
  74. package/src/shell/ui-openers.ts +2 -2
  75. package/src/version.ts +1 -1
@@ -1,7 +1,7 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // fleet-tab-strip.ts
3
3
  //
4
- // Wave-3 (W3.1 Part C3) — renders FleetPanel's session-tab strip by reusing
4
+ // Renders FleetPanel's session-tab strip by reusing
5
5
  // renderTabStrip exactly as panel-workspace-bar.ts does for the workspace
6
6
  // tab bar. Deliberately a SEPARATE, visually distinct strip from the
7
7
  // workspace bar: workspace tabs switch PANELS, fleet session tabs switch
@@ -23,7 +23,7 @@ const LABEL_FG = UI_TONES.fg.secondary;
23
23
  /**
24
24
  * Render the fleet session-tab strip, or `null` when there are no attached
25
25
  * tabs — the panel omits the strip entirely in that case (root-tab-only),
26
- * which is what keeps the pre-Wave-3 fleet-panel goldens byte-identical.
26
+ * which is what keeps the pre-session-tab fleet-panel goldens byte-identical.
27
27
  */
28
28
  export function renderFleetTabStrip(
29
29
  state: FleetTabsState,
@@ -29,7 +29,7 @@ export function isAgentActive(composerStatus: string | undefined): boolean {
29
29
  // shell/ui-openers.ts). The bare noun 'panels' undersold that; naming the verb
30
30
  // keeps the tip honest about what the chord actually does.
31
31
  const TIP_PANELS = 'Ctrl+P toggle panels';
32
- // W6.2 e: F2 now opens the Fleet panel (the process modal was retired), so the
32
+ // F2 now opens the Fleet panel (the process modal was retired), so the
33
33
  // tip names 'fleet', not 'processes'.
34
34
  const TIP_PROCESSES = 'F2 fleet';
35
35
  const TIP_HELP = '? help';
@@ -189,7 +189,7 @@ function detailLines(picker: ModelPickerModal, width: number): string[] {
189
189
  } else {
190
190
  lines.push(`Context cap overrides the detected local-model context window for this selection.`);
191
191
  }
192
- // W3-T2: a visible cursor glyph on the query itself when search is focused —
192
+ // A visible cursor glyph on the query itself when search is focused —
193
193
  // the only prior affordance was a footer-hint text swap ('/ search' vs.
194
194
  // 'Typing filters search...'), buried at the end of a long hint line and
195
195
  // easy to miss (confirmed via live tmux repro). This puts the "you are
@@ -6,7 +6,7 @@
6
6
  * - name, timestamp (formatted), message count
7
7
  * Footer hints: [Enter] Load [d] Delete [Esc] Close
8
8
  *
9
- * W3-T2: when the modal was wired with a cross-surface session union
9
+ * When the modal was wired with a cross-surface session union
10
10
  * (`modal.crossSurfaceView.mode !== 'local'`), an additional read-only
11
11
  * "Cross-surface sessions" section is appended, badged kind/status/project
12
12
  * (parity with the webui SessionsView) with one of three honest states —
@@ -66,7 +66,7 @@ function isReapedRecord(record: SharedSessionRecord): boolean {
66
66
  }
67
67
 
68
68
  /**
69
- * Wave-4 UX-lens note: 'reaped' names a mechanism (the idle-session sweep),
69
+ * UX-lens note: 'reaped' names a mechanism (the idle-session sweep),
70
70
  * not a state a first-time reader can guess — the webui pairs its own
71
71
  * 'reaped' badge with a tooltip explaining it
72
72
  * (SessionsView.tsx: "Closed by the idle-session sweep — reopens
@@ -96,7 +96,7 @@ const MAX_CROSS_SURFACE_ROWS = 5;
96
96
  /**
97
97
  * The exact honest note for the current state, or null when the union view
98
98
  * needs no caveat (fresh/embedded with rows). Precedence: offline > stale >
99
- * true-empty — the three states from the W3-T2 brief, never collapsed into
99
+ * true-empty — the three designed states, never collapsed into
100
100
  * each other (an offline view never silently renders as "no sessions yet").
101
101
  */
102
102
  function crossSurfaceNote(view: SessionPickerModal['crossSurfaceView'], rowCount: number): string | null {
@@ -238,7 +238,7 @@ export function renderSessionPickerModal(
238
238
  }
239
239
  }
240
240
 
241
- // W3-T2: cross-surface session union — visible only when a sessionBroker
241
+ // Cross-surface session union — visible only when a sessionBroker
242
242
  // was wired (mode !== 'local'); absent entirely otherwise, so the box size
243
243
  // and content of every pre-existing (local-only) caller is unaffected.
244
244
  if (modal.crossSurfaceView.mode !== 'local') {
@@ -48,7 +48,7 @@ export interface ShellFooterBuildOptions {
48
48
  */
49
49
  readonly compact?: boolean;
50
50
  /**
51
- * S3d: cross-surface session-spine posture for the context-info segment.
51
+ * Cross-surface session-spine posture for the context-info segment.
52
52
  * Set ONLY in adopted-daemon mode ('online'/'offline'); left undefined in
53
53
  * embedded/local mode so no segment renders.
54
54
  */
@@ -4,9 +4,9 @@
4
4
  // Extracted as a neutral module so both status-token.ts and polish.ts can
5
5
  // import from here without creating a circular ESM dependency.
6
6
  //
7
- // W6-P1: STATE_GLYPHS is no longer hardcoded here. It is the SDK presentation
8
- // contract (@pellux/goodvibes-sdk/platform/presentation, landed by W4-S1 and
9
- // already adopted by the agent in W4-R4), aliased to GLYPHS.status so the
7
+ // STATE_GLYPHS is no longer hardcoded here. It is the SDK presentation
8
+ // contract (@pellux/goodvibes-sdk/platform/presentation, and
9
+ // already adopted by the agent), aliased to GLYPHS.status so the
10
10
  // four semantic glyphs are spelled out in exactly one place and can never
11
11
  // drift from the registry again. Re-exported under the historical name so
12
12
  // status-token.ts and polish.ts import unchanged.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * terminal-bg-probe — OSC 11 terminal-background detection for `auto` theme mode.
3
3
  *
4
- * DEBT-2 / F5. On startup, when appearance is `auto` and stdout is a TTY, we ask
4
+ * DEBT-2. On startup, when appearance is `auto` and stdout is a TTY, we ask
5
5
  * the terminal for its background colour with an OSC 11 query and classify the
6
6
  * reply as light or dark. Everything about this is best-effort and conservative:
7
7
  * any timeout, any unparseable reply, any ambiguity resolves to DARK, which is
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Dark mode values are the historically used colours.
9
9
  * Light mode values are defined now for correctness parity; they are consumed
10
- * when background-detection (F5 / terminal-bg-probe) lands and passes the
10
+ * when background-detection (terminal-bg-probe) lands and passes the
11
11
  * resolved mode down. Callers that do not yet have mode detection MUST call
12
12
  * resolveTheme('dark') as the safe default.
13
13
  *
@@ -242,7 +242,7 @@ export function resolveUiTones(mode: ThemeMode): Readonly<UiToneTokens> {
242
242
  }
243
243
 
244
244
  // ===========================================================================
245
- // Active-mode runtime (DEBT-2 / F5 terminal-bg-probe landing).
245
+ // Active-mode runtime (DEBT-2 terminal-bg-probe landing).
246
246
  //
247
247
  // The mode is decided ONCE at startup — from appearance config (display.themeMode
248
248
  // forced dark/light) or the terminal-background probe (auto) — and is then stable
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Per-turn knowledge injection record rendering (Wave-5 W5.2, wo803).
2
+ * Per-turn knowledge injection record rendering.
3
3
  *
4
4
  * The SDK's passive per-turn retrieval engine (wo801, W5.1 —
5
5
  * packages/sdk/src/platform/agents/turn-knowledge-injection.ts) stores one
@@ -13,7 +13,7 @@
13
13
  * the entry type here is DERIVED from `AgentRecord` rather than imported by
14
14
  * name — this needs no SDK export change.
15
15
  *
16
- * Reality check (updated wo805): as of Wave-5 wo805 the TUI's main interactive
16
+ * Reality check (since updated): the TUI's main interactive
17
17
  * session DOES route through this engine. The SDK `Orchestrator` runs per-turn
18
18
  * passive injection on the evolving primary conversation and records each turn
19
19
  * on its own bounded ring, exposed via `Orchestrator.getTurnInjections()` — the
@@ -68,7 +68,7 @@ export function formatTurnInjectionEntry(entry: TurnInjectionEntry): string {
68
68
  : '';
69
69
  // The retrieval query is part of the record's honesty contract — without it
70
70
  // an injected line can't be traced back to WHY those ids were retrieved
71
- // (Wave-5 replay flagged the omission). Truncated to keep the line scannable.
71
+ // (a replay finding flagged the omission). Truncated to keep the line scannable.
72
72
  const queryStr = entry.query ? ` for ${JSON.stringify(truncateQuery(entry.query))}` : '';
73
73
  return (
74
74
  ` turn ${entry.turn}: injected ${labelInjectedIds(entry)}${queryStr} ` +
@@ -195,7 +195,7 @@ export class UIFactory {
195
195
  composerFlags?: readonly string[],
196
196
  composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote',
197
197
  compact: boolean = false,
198
- // S3d: honest cross-surface spine posture. Defined ONLY in adopted-daemon
198
+ // Honest cross-surface spine posture. Defined ONLY in adopted-daemon
199
199
  // mode (undefined in embedded/local), so the segment is absent otherwise.
200
200
  sessionSpineStatus?: 'online' | 'offline',
201
201
  ): Line[] {
@@ -442,7 +442,7 @@ export class UIFactory {
442
442
  if (model) {
443
443
  ctxParts.push({ text: model + (provider ? ` (${provider})` : ''), priority: 0 });
444
444
  }
445
- // S3d: cross-surface spine posture — plain words, no blame. Adopted mode only.
445
+ // Cross-surface spine posture — plain words, no blame. Adopted mode only.
446
446
  if (sessionSpineStatus) ctxParts.push({ text: `spine:${sessionSpineStatus}`, priority: 1 });
447
447
  if (toolCount) ctxParts.push({ text: `${toolCount} tools`, priority: 2 });
448
448
  // Labeled "notify" (not "hitl") — /mode (aliased /hitl) governs UX
@@ -484,11 +484,11 @@ export class UIFactory {
484
484
  return lines;
485
485
  }
486
486
 
487
- // W6-P1: the rotating "thinking" phrase pool and the honest waiting-state
487
+ // The rotating "thinking" phrase pool and the honest waiting-state
488
488
  // wording (approval/reconnecting/pre-first-token/stalled/thinking) are no
489
489
  // longer minted here. They come from the SDK presentation contract's
490
- // waitingPhrase() (@pellux/goodvibes-sdk/platform/presentation, landed by
491
- // W4-S1, already adopted by the agent in W4-R4) — see createThinkingFragment
490
+ // waitingPhrase() (@pellux/goodvibes-sdk/platform/presentation, already
491
+ // adopted by the agent) — see createThinkingFragment
492
492
  // below. This renderer still decides WHICH state applies from its own
493
493
  // stall/reconnect/approval signals (computeStallInfo/computeRenderStallInfo
494
494
  // stay renderer-local per the extraction decision record); only the exact
@@ -544,8 +544,8 @@ export class UIFactory {
544
544
  // long enough to be misleading (THINKING_STALL_FREEZE_MS). Decide WHICH
545
545
  // honest waiting state applies (renderer-local — this signal computation
546
546
  // stays here per the extraction decision record), then defer the exact
547
- // wording to the SDK presentation contract's waitingPhrase() (W6-P1;
548
- // mirrors the agent's W4-R4 adoption). Precedence matches the contract:
547
+ // wording to the SDK presentation contract's waitingPhrase() (which
548
+ // mirrors the agent's adoption). Precedence matches the contract:
549
549
  // approval > reconnecting > pre-first-token > stalled > thinking.
550
550
  const isStalled = stallInfo !== undefined && stallInfo.msSinceLastDelta >= THINKING_STALL_FREEZE_MS;
551
551
  let state: WaitingState;
@@ -1,10 +1,10 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // ui-primitives.ts — glyph registry + tone-token table.
3
3
  //
4
- // W6-P1: these four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
4
+ // These four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
5
5
  // no longer minted locally. They are the SDK presentation contract
6
- // (@pellux/goodvibes-sdk/platform/presentation, landed by W4-S1 and already
7
- // adopted by the agent in W4-R4), consumed here so the TUI and the agent
6
+ // (@pellux/goodvibes-sdk/platform/presentation, already
7
+ // adopted by the agent), consumed here so the TUI and the agent
8
8
  // share ONE source (Mike's move-to-SDK ruling — machinery needed by 2+
9
9
  // surfaces => SDK). The TUI was the reference these values were lifted from
10
10
  // verbatim, so this swap is byte-identical. See
@@ -27,6 +27,7 @@ import { loadBootstrapSystemPrompt, syncConfiguredServices } from '@/runtime/ind
27
27
  import { registerBootstrapHookBridge } from '@/runtime/index.ts';
28
28
  import { registerBootstrapRuntimeEvents } from '@/runtime/index.ts';
29
29
  import { createRuntimeServices, type RuntimeServices } from './services.ts';
30
+ import { runBootMemoryFold } from './memory-fold.ts';
30
31
  import { setPricingSource } from '../export/cost-utils.ts';
31
32
  import { createUiRuntimeServices, type UiRuntimeServices } from './ui-services.ts';
32
33
  import { join } from 'node:path';
@@ -118,7 +119,7 @@ export interface BootstrapCoreState {
118
119
  readonly runtimeSessionIdRef: { value: string };
119
120
  /** Cross-surface identity mirror; permanently dormant for embedded/local-only (docs/decisions/2026-07-06-session-spine-mode-branch-is-permanent.md), activated by bootstrap.ts only for an adopted 'external' daemon. */
120
121
  readonly sessionSpine: SessionSpineClient;
121
- /** D3: inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
122
+ /** Inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
122
123
  readonly sessionInboundInputs: SessionInboundInputPoller;
123
124
  /** Cache-backed read facade; bootstrap.ts drives its mode (embedded/external/local-only) from the same HostServiceMode as the spine above. */
124
125
  readonly sessionUnionCache: SessionUnionCache;
@@ -407,24 +408,23 @@ export async function initializeBootstrapCore(
407
408
  void memoryStore.save();
408
409
  memoryStore.close();
409
410
  });
411
+ // Fold this project's legacy per-project TUI memory into the home-scoped canonical store, ONCE, AFTER init(). Idempotent and non-fatal.
412
+ await runBootMemoryFold(memoryStore, services.memoryEmbeddingRegistry, workingDir, logger);
410
413
 
411
414
  const renderRequestRef = { value: (): void => {} };
412
- // R1: Coalescing render scheduler — collapses N requestRender() calls into 1
413
- // and enforces a 16ms minimum interval to cap repaints at ~60fps.
414
- //
415
- // renderScheduled stays set for the ENTIRE window (until run() executes), so
416
- // requestRender() calls that arrive on later event-loop ticks within the same
417
- // 16ms window are coalesced into the one already-pending tail render instead
418
- // of each queuing their own setTimeout. (The streaming hot path drives its own
419
- // direct repaints and does not flow through this scheduler.)
415
+ // R1: Coalescing render scheduler — collapses N requestRender() calls into 1 and
416
+ // enforces a 16ms minimum interval to cap repaints at ~60fps. renderScheduled stays
417
+ // set for the ENTIRE window (until run() executes), so requestRender() calls arriving
418
+ // on later event-loop ticks within the same 16ms window coalesce into the one
419
+ // already-pending tail render instead of each queuing their own setTimeout. (The
420
+ // streaming hot path drives its own direct repaints and skips this scheduler.)
420
421
  let renderScheduled = false;
421
422
  let lastRenderTime = 0;
422
423
  const RENDER_INTERVAL_MS = 16;
423
- // run() performs the actual render. It clears renderScheduled FIRST — even if
424
- // the render callback throws — otherwise a single render exception would wedge
425
- // the entire TUI (no future requestRender() call would schedule anything). The
426
- // renderer is expected to surface failures via its own error path; we log at
427
- // error so the next requestRender() can still reschedule.
424
+ // run() performs the actual render. It clears renderScheduled FIRST — even if the
425
+ // render callback throws — otherwise a single render exception would wedge the entire
426
+ // TUI (no future requestRender() would schedule anything); we log at error so the
427
+ // next requestRender() can still reschedule.
428
428
  const run = (): void => {
429
429
  renderScheduled = false;
430
430
  lastRenderTime = Date.now();
@@ -644,7 +644,7 @@ export async function initializeBootstrapCore(
644
644
  },
645
645
  ));
646
646
 
647
- // D3: inbound steer delivery — see createBootstrapInboundInputPoller's doc comment.
647
+ // Inbound steer delivery — see createBootstrapInboundInputPoller's doc comment.
648
648
  const sessionInboundInputs = createBootstrapInboundInputPoller({
649
649
  runtimeSessionIdRef, routeOrBuffer, orchestratorHandleUserInputRef, conversation, requestRender,
650
650
  });
@@ -23,7 +23,7 @@ export interface ResumeSessionOptions {
23
23
  readonly requestRender: () => void;
24
24
  readonly onSessionIdChanged?: (sessionId: string) => void;
25
25
  readonly sharedSessionBroker: Pick<SharedSessionBroker, 'reopenSession'>;
26
- /** S3c: fire-and-forget daemon-spine mirror. Reopen (not register) is the
26
+ /** Fire-and-forget daemon-spine mirror. Reopen (not register) is the
27
27
  * ONLY resume-time verb — see the SDK session-spine client.ts header doc. */
28
28
  readonly sessionSpine: Pick<SessionSpineClient, 'reopen'>;
29
29
  readonly project: string;
@@ -80,7 +80,7 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
80
80
  if (meta?.provider) options.runtime.provider = meta.provider;
81
81
  options.writeLastSessionPointer(sessionId);
82
82
  void options.sharedSessionBroker.reopenSession(sessionId).catch((err) => { logger.debug('session broker reopen session failed', { err }); });
83
- // S3c: fire-and-forget spine mirror (reopen:true — the user resume verb).
83
+ // Fire-and-forget spine mirror (reopen:true — the user resume verb).
84
84
  options.sessionSpine.reopen({ sessionId, project: options.project, title: options.conversation.title || meta.title });
85
85
  options.conversation.log(`Resumed session: ${sessionId}`, { fg: '135' });
86
86
  const reopenedPanels: string[] = [];
@@ -48,7 +48,7 @@ export interface BootstrapShellOptions {
48
48
  readonly runtimeBus: RuntimeEventBus;
49
49
  readonly runtimeStore: RuntimeStore;
50
50
  readonly services: RuntimeServices;
51
- /** S3c: dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
51
+ /** Dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
52
52
  readonly sessionSpine: SessionSpineClient;
53
53
  readonly conversation: ConversationManager;
54
54
  readonly runtime: MutableRuntimeState;
@@ -181,7 +181,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
181
181
  forensicsRegistry,
182
182
  policyRuntimeState,
183
183
  approvalBroker: services.approvalBroker,
184
- // S3d: panels read the cross-surface union facade, not the raw local broker.
184
+ // Panels read the cross-surface union facade, not the raw local broker.
185
185
  sessionBroker: uiServices.sessions.sessionBroker,
186
186
  automationManager: services.automationManager,
187
187
  getControlPlaneRecentEvents,
@@ -289,7 +289,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
289
289
  codeIndexStore: services.codeIndexStore,
290
290
  codeIndexReindexScheduler: services.codeIndexReindexScheduler,
291
291
  isPassiveCodeInjectionFlagEnabled: () => services.featureFlags.isEnabled('agent-passive-code-injection'),
292
- // Wave 5 (wo805): expose the MAIN session's per-turn passive-injection ring
292
+ // Expose the MAIN session's per-turn passive-injection ring
293
293
  // so `/recall injections` (no agent id) renders it — see recall-review.ts.
294
294
  getMainSessionTurnInjections: () => orchestrator.getTurnInjections(),
295
295
  changeTracker: services.sessionChangeTracker,
@@ -199,7 +199,7 @@ export async function bootstrapRuntime(
199
199
  pluginManager,
200
200
  } = services;
201
201
 
202
- // D4b: a liveness flip is only PAINTED once something calls requestRender();
202
+ // A liveness flip is only PAINTED once something calls requestRender();
203
203
  // without this, the footer's spine segment sat correct-but-undrawn until
204
204
  // incidental activity redrew it (minutes, during an idle stretch).
205
205
  sessionUnionCache.setOnTransition(() => requestRender());
@@ -413,7 +413,7 @@ export async function bootstrapRuntime(
413
413
  close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
414
414
  };
415
415
  sessionSpine.activate(createTuiSpineTransport(sessionsClient));
416
- // D3: adopt the same daemon's wire for the INBOUND steer path — collect
416
+ // Adopt the same daemon's wire for the INBOUND steer path — collect
417
417
  // steer/follow-up inputs another live surface queued for this session and
418
418
  // inject them into the turn machinery (acking delivery on the wire).
419
419
  sessionInboundInputs.activate({
@@ -453,7 +453,7 @@ export async function bootstrapRuntime(
453
453
  // Honest session-spine posture, independent of daemonRunning —
454
454
  // 'external'-adopted-but-currently-unreachable degrades to 'offline' here
455
455
  // even though daemonRunning might still read true from a stale handle.
456
- // D4: sessionSpine.status() alone is ACTIVITY-gated (only updates on a
456
+ // sessionSpine.status() alone is ACTIVITY-gated (only updates on a
457
457
  // register/heartbeat/close), so after the daemon dies mid-idle it would
458
458
  // keep reading 'online'. Derive the footer status from the union cache's
459
459
  // 5s liveness probe too — one signal, no new timer — so offline surfaces
@@ -568,7 +568,7 @@ export async function bootstrapRuntime(
568
568
  // authenticate against the embedded daemon this surface starts.
569
569
  const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
570
570
  const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
571
- // F3 resolution (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
571
+ // Fix (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
572
572
  // token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
573
573
  // The prune is best-effort — it silently skips missing files, no-ops when tokens
574
574
  // already match, and records un-deletable candidates in `failedPaths` for logging.
@@ -741,7 +741,7 @@ export async function bootstrapRuntime(
741
741
  // a no-op when the spine was never activated (embedded/local-only topology).
742
742
  sessionSpine.close(runtime.sessionId);
743
743
  sessionSpine.dispose();
744
- sessionInboundInputs.dispose(); // D3: stop the inbound steer poll interval on exit
744
+ sessionInboundInputs.dispose(); // stop the inbound steer poll interval on exit
745
745
  sessionUnionCache.dispose(); // stop the wire-refresh interval on exit
746
746
  await deferredStartup.drain(100);
747
747
  if (externalServicesPromise) {
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // code-index-services.ts — Wave 5 (wo804, W5.3 Stage A TUI wiring)
2
+ // code-index-services.ts — repo source-tree code index (TUI wiring)
3
3
  //
4
4
  // Constructs the TUI's repo source-tree code index: CodeIndexStore
5
5
  // (@pellux/goodvibes-sdk/platform/state, landed on SDK main as wo802/W5.3
@@ -81,7 +81,7 @@ export function isCodeInjectionSettingEnabled(configManager: Pick<ConfigManager,
81
81
  export interface CodeIndexServices {
82
82
  readonly codeIndexStore: CodeIndexStore;
83
83
  /**
84
- * Wave-5 Stage B tool-site incremental reindex scheduler, bound to codeIndexStore and
84
+ * Tool-site incremental reindex scheduler, bound to codeIndexStore and
85
85
  * gated live on storage.codeIndexEnabled. Threaded into both SDK orchestrators (main +
86
86
  * agent) so a successful write/edit debounces an incremental reindex, and into the command
87
87
  * context so /codebase status can report the last reindex activity honestly.
@@ -274,9 +274,17 @@ export const reachableFrom = operations.reachableFrom;
274
274
  export const evaluateSessionMaintenance = operations.evaluateSessionMaintenance;
275
275
  export const formatSessionMaintenanceLines = operations.formatSessionMaintenanceLines;
276
276
  export const getGuidanceMode = operations.getGuidanceMode;
277
- export const DEFAULT_RETENTION_CONFIG = operations.DEFAULT_RETENTION_CONFIG;
278
- export const RetentionPolicy = operations.RetentionPolicy;
279
- export const SnapshotPruner = operations.SnapshotPruner;
277
+ // Snapshot-retention symbols (SnapshotPruner, RetentionPolicy,
278
+ // DEFAULT_RETENTION_CONFIG) are intentionally NOT re-exported here. No app code
279
+ // consumes them only the retention unit test does, and it now imports them
280
+ // straight from the SDK `operations` namespace. Re-exporting them created a
281
+ // second top-level binding named `SnapshotPruner` that collided with the SDK's
282
+ // own `class SnapshotPruner`, forcing the bundler to rename ours to
283
+ // `SnapshotPruner2` and emit a fragile `SnapshotPruner2 = operations.SnapshotPruner`
284
+ // module-init assignment. `bun build --compile` occasionally drops that renamed
285
+ // `var` declaration on the darwin-arm64 target, so the compiled binary died at
286
+ // startup with `ReferenceError: SnapshotPruner2 is not defined`. Dropping the
287
+ // re-export removes the collision (and the same latent hazard for the other two).
280
288
  export const buildPersistedSessionContext = operations.buildPersistedSessionContext;
281
289
  export const buildLocalReturnContextSummary = operations.buildLocalReturnContextSummary;
282
290
  export const formatReturnContextForDisplay = operations.formatReturnContextForDisplay;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * W4-D1: the legacy `goodvibes-daemon.service` detect/migrate engine, shared
2
+ * The legacy `goodvibes-daemon.service` detect/migrate engine, shared
3
3
  * by the daemon CLI (`src/daemon/service-commands.ts`, the `migrate-service`
4
4
  * subcommand) and the interactive TUI's onboarding guided UX
5
5
  * (`src/input/handler-onboarding-daemon-adopt.ts`).
@@ -11,8 +11,8 @@
11
11
  * depending on CLI/daemon entrypoint concerns), and `src/runtime/**` is the
12
12
  * shared, entrypoint-agnostic layer both sides are already allowed to import.
13
13
  *
14
- * Wave 3 (W3 Finding 4) shipped DETECT + DISCLOSE only: a read-only check for
15
- * the prior D7a-era systemd unit name plus a manual-removal hint, never
14
+ * An earlier release shipped DETECT + DISCLOSE only: a read-only check for
15
+ * the prior generation's systemd unit name plus a manual-removal hint, never
16
16
  * touching it. This module adds the guided, CONSENTED migration itself.
17
17
  * Design constraints, all load-bearing (see also the per-function docs):
18
18
  * - NEVER auto-migrate. Without explicit consent nothing runs except a
@@ -63,7 +63,7 @@ export const MANAGED_SERVICE_NAME = 'goodvibes';
63
63
  export const MANAGED_SERVICE_DESCRIPTION = 'GoodVibes daemon (shared session broker + companion host)';
64
64
 
65
65
  /**
66
- * F2 follow-up: resolve the unit name the SDK's `PlatformServiceManager`
66
+ * Follow-up: resolve the unit name the SDK's `PlatformServiceManager`
67
67
  * would actually manage, from config alone — for callers that need the
68
68
  * honest display name BEFORE any manager/status exists (the onboarding
69
69
  * wizard's detection banner resolves this at snapshot-collection time and
@@ -218,7 +218,7 @@ export function detectLegacyUnit(input: DetectLegacyUnitInput): LegacyUnitInfo {
218
218
  }
219
219
 
220
220
  /**
221
- * F1: the unit name `PlatformServiceManager` is ACTUALLY about to mutate can
221
+ * The unit name `PlatformServiceManager` is ACTUALLY about to mutate can
222
222
  * differ from `MANAGED_SERVICE_NAME` / `definitionOverride.name`. The SDK's
223
223
  * internal `resolveServiceName()` — used by `install()`, `uninstall()`, and
224
224
  * `status()` alike to compute the unit file PATH — resolves from the
@@ -303,7 +303,7 @@ export interface RunLegacyDaemonMigrationParams {
303
303
  }
304
304
 
305
305
  /**
306
- * F1 belt-and-braces guard: throws if the resolved unit `status` is the
306
+ * Belt-and-braces guard: throws if the resolved unit `status` is the
307
307
  * legacy unit. Called immediately before the two mutation calls
308
308
  * (`manager.install()`, and `manager.uninstall()` on the failed-health
309
309
  * rollback path) that would otherwise write to or remove that path. This is
@@ -343,7 +343,7 @@ export async function runLegacyDaemonMigration(
343
343
  const { trackedServiceName } = params;
344
344
  // Computed once, up front, and reused for every branch below (this is the
345
345
  // exact same single call each branch made individually before — see the
346
- // F1 fix note on `resolveManagedUnitName` for why the name/path it reports
346
+ // fix note on `resolveManagedUnitName` for why the name/path it reports
347
347
  // can differ from `trackedServiceName`).
348
348
  const currentStatus = manager.status();
349
349
  const resolvedUnitName = resolveManagedUnitName(currentStatus);
@@ -393,7 +393,7 @@ export async function runLegacyDaemonMigration(
393
393
  };
394
394
  }
395
395
 
396
- // F1: before any mutation, confirm the unit PlatformServiceManager is
396
+ // Before any mutation, confirm the unit PlatformServiceManager is
397
397
  // actually about to install/uninstall isn't the legacy unit itself. This
398
398
  // happens when the host's `service.serviceName` config key is set to the
399
399
  // legacy unit's own name — see `resolveManagedUnitName`'s doc comment for
@@ -440,7 +440,7 @@ export async function runLegacyDaemonMigration(
440
440
 
441
441
  // Consented: new-up-then-old-down. The legacy unit is not touched until the
442
442
  // new unit is verified healthy.
443
- // Belt-and-braces (F1): the collision check above already returns before
443
+ // Belt-and-braces: the collision check above already returns before
444
444
  // reaching here whenever the resolved unit is the legacy one — this
445
445
  // re-asserts the same invariant right at the mutation site so a future
446
446
  // change to the check above can never silently reopen the hole.
@@ -1,6 +1,7 @@
1
1
  import { join } from 'node:path';
2
2
  import {
3
3
  foldMemoryStores,
4
+ formatMemoryFoldReport,
4
5
  type LegacyMemorySource,
5
6
  type MemoryEmbeddingProviderRegistry,
6
7
  type MemoryFoldReport,
@@ -8,7 +9,7 @@ import {
8
9
  } from '@pellux/goodvibes-sdk/platform/state';
9
10
 
10
11
  /**
11
- * W6-C2 (E6): fold the TUI's legacy per-project memory store into the canonical
12
+ * Fold the TUI's legacy per-project memory store into the canonical
12
13
  * cross-surface store. Called once at boot AFTER `memoryStore.init()` so records written
13
14
  * before unification survive. Id-keyed and idempotent — a re-run imports nothing new and
14
15
  * never deletes the legacy file. Returns the report so boot can log what moved.
@@ -24,3 +25,31 @@ export async function foldTuiLegacyMemory(
24
25
  ];
25
26
  return foldMemoryStores(memoryStore, sources, { embeddingRegistry: memoryEmbeddingRegistry });
26
27
  }
28
+
29
+ /** A minimal boot logger seam (matches the SDK logger's info/warn shape). */
30
+ export interface BootFoldLogger {
31
+ info(message: string, meta?: unknown): void;
32
+ warn(message: string, meta?: unknown): void;
33
+ }
34
+
35
+ /**
36
+ * Run the legacy TUI memory fold at boot and surface the report. Never blocks or
37
+ * fails boot: a fold error degrades to a warn and the canonical store serves on.
38
+ * Only logs the report when something actually moved (or a source failed), so a
39
+ * clean boot stays quiet.
40
+ */
41
+ export async function runBootMemoryFold(
42
+ memoryStore: MemoryStore,
43
+ memoryEmbeddingRegistry: MemoryEmbeddingProviderRegistry,
44
+ workingDirectory: string,
45
+ log: BootFoldLogger,
46
+ ): Promise<void> {
47
+ try {
48
+ const report = await foldTuiLegacyMemory(memoryStore, memoryEmbeddingRegistry, workingDirectory);
49
+ if (report.totalImported > 0 || report.failedSources.length > 0) {
50
+ log.info(`[bootstrap] memory fold: ${formatMemoryFoldReport(report)}`);
51
+ }
52
+ } catch (err) {
53
+ log.warn('memory fold at bootstrap failed (non-fatal; canonical store unaffected)', { err });
54
+ }
55
+ }
@@ -403,12 +403,12 @@ export async function collectOnboardingSnapshot(
403
403
  snapshot: authSnapshotResult.value,
404
404
  },
405
405
  bindSettings: {
406
- // danger.daemon (removed Wave 6 — see docs/decisions/2026-07-05-daemon-by-default.md)
407
- // used to gate the pre-Wave-2 opt-in daemon posture; this site read it RAW
406
+ // danger.daemon (since removed — see docs/decisions/2026-07-05-daemon-by-default.md)
407
+ // used to gate the earlier opt-in daemon posture; this site read it RAW
408
408
  // (not through resolveDaemonEnabled) so the wizard's network-mode
409
409
  // classification tracked "did the user explicitly request the legacy
410
410
  // dangerous posture" rather than "does the daemon run" (which has defaulted
411
- // true unconditionally since Wave 2, and would misclassify every default,
411
+ // true unconditionally since daemon-by-default landed, and would misclassify every default,
412
412
  // local-only install as server-mode if read here). Every realistic config
413
413
  // already evaluated this to `false` (unset, or an explicit `danger.daemon:
414
414
  // false` both did); the alias's removal migration preserves the one case
@@ -126,7 +126,7 @@ export interface OnboardingSurfacesSnapshot {
126
126
  }
127
127
 
128
128
  /**
129
- * W4-D1 wizard wiring: read-only detection of a legacy `goodvibes-daemon.service`
129
+ * Wizard wiring: read-only detection of a legacy `goodvibes-daemon.service`
130
130
  * systemd unit (see `../legacy-daemon-migration.ts`'s `LegacyUnitInfo`), carried
131
131
  * on the snapshot so the Network step can show the guided migration action only
132
132
  * when there is actually something to migrate. Never implies anything was
@@ -137,7 +137,7 @@ export interface OnboardingLegacyDaemonSnapshot {
137
137
  readonly active: boolean;
138
138
  readonly path: string;
139
139
  /**
140
- * F2 follow-up: the unit name this tool actually manages on this host,
140
+ * Follow-up: the unit name this tool actually manages on this host,
141
141
  * resolved from the `service.serviceName` config key at snapshot-collection
142
142
  * time (`resolveConfiguredServiceName`, `../legacy-daemon-migration.ts`) —
143
143
  * so the wizard's detection banner names the real unit instead of the
@@ -4,7 +4,7 @@
4
4
  * Shared helper that enumerates the legacy workspace-scoped `operator-tokens.json`
5
5
  * locations the TUI has written at various pre-0.21.28 versions. Used by both the
6
6
  * in-process bootstrap path (`src/runtime/bootstrap.ts`) and the standalone daemon
7
- * CLI (`src/daemon/cli.ts`) so F3 (stale-token pruning) has a single source of
7
+ * CLI (`src/daemon/cli.ts`) so stale-token pruning has a single source of
8
8
  * truth for where to look.
9
9
  *
10
10
  * Adding a new legacy location: append to `workspaceOperatorTokenCandidates` and
@@ -32,7 +32,7 @@ export function workspaceOperatorTokenCandidates(workingDirectory: string): read
32
32
  }
33
33
 
34
34
  /**
35
- * F1 (adopt-an-already-running-external-daemon): resolve the operator/companion
35
+ * External-daemon adoption: resolve the operator/companion
36
36
  * token this TUI process uses to authenticate with its daemon, honoring
37
37
  * `GOODVIBES_DAEMON_TOKEN` as a non-interactive override.
38
38
  *
@@ -24,7 +24,7 @@ export type OrchestratorCoreServicesSource = Pick<
24
24
  * cacheHitTracker; main: favoritesStore) — setCoreServices() merges, so the
25
25
  * later main.ts call only overlays, never erases.
26
26
  *
27
- * Wave-5 (wo805) BLOCKER regression guard: `memoryRegistry` here is what turns
27
+ * BLOCKER regression guard: `memoryRegistry` here is what turns
28
28
  * on per-turn passive knowledge injection for the MAIN interactive session —
29
29
  * the SDK turn loop hard-gates on `coreServices.memoryRegistry` (undefined is
30
30
  * a silent no-op: Orchestrator.getTurnInjections() stays empty forever and
@@ -48,7 +48,7 @@ export function buildSharedOrchestratorCoreServices(input: {
48
48
  sessionLineageTracker: services.sessionLineageTracker,
49
49
  idempotencyStore: services.idempotencyStore,
50
50
  memoryRegistry: services.memoryRegistry,
51
- // Wave-5 Stage B — main-session code auto-injection + tool-site reindex. Injection is
51
+ // Main-session code auto-injection + tool-site reindex. Injection is
52
52
  // additionally gated by the default-off `agent-passive-code-injection` flag inside the
53
53
  // SDK; here we supply the source, the live storage.codeIndexEnabled predicate, and the
54
54
  // reindex scheduler.