@pellux/goodvibes-tui 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/CHANGELOG.md +36 -5
  2. package/README.md +1 -1
  3. package/docs/foundation-artifacts/knowledge-graphql.graphql +5 -0
  4. package/docs/foundation-artifacts/knowledge-store.sql +33 -0
  5. package/docs/foundation-artifacts/operator-contract.json +4026 -1376
  6. package/package.json +2 -2
  7. package/src/audio/player.ts +91 -5
  8. package/src/audio/spoken-turn-wiring.ts +10 -2
  9. package/src/cli/management-utils.ts +4 -0
  10. package/src/config/credential-availability.ts +1 -1
  11. package/src/config/index.ts +1 -1
  12. package/src/core/turn-cancellation.ts +1 -1
  13. package/src/daemon/cli.ts +4 -4
  14. package/src/daemon/service-commands.ts +8 -8
  15. package/src/export/cost-utils.ts +9 -1
  16. package/src/input/command-registry.ts +16 -5
  17. package/src/input/commands/config.ts +1 -1
  18. package/src/input/commands/control-room-runtime.ts +12 -5
  19. package/src/input/commands/incident-runtime.ts +2 -2
  20. package/src/input/commands/memory.ts +12 -12
  21. package/src/input/commands/planning-runtime.ts +1 -1
  22. package/src/input/commands/recall-bundle.ts +8 -8
  23. package/src/input/commands/recall-capture.ts +3 -3
  24. package/src/input/commands/recall-query.ts +41 -15
  25. package/src/input/commands/recall-review.ts +12 -12
  26. package/src/input/commands/session-content.ts +1 -1
  27. package/src/input/feed-context-factory.ts +1 -1
  28. package/src/input/handler-content-actions.ts +2 -2
  29. package/src/input/handler-feed-routes.ts +2 -2
  30. package/src/input/handler-feed.ts +1 -1
  31. package/src/input/handler-onboarding-daemon-adopt.ts +3 -3
  32. package/src/input/handler-picker-routes.ts +2 -2
  33. package/src/input/handler-shortcuts.ts +1 -1
  34. package/src/input/model-picker.ts +2 -2
  35. package/src/input/onboarding/onboarding-wizard-apply.ts +3 -3
  36. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +3 -3
  37. package/src/input/onboarding/onboarding-wizard-steps.ts +1 -1
  38. package/src/input/session-picker-modal.ts +1 -1
  39. package/src/input/settings-modal-data.ts +56 -2
  40. package/src/main.ts +8 -6
  41. package/src/panels/base-panel.ts +1 -1
  42. package/src/panels/builtin/operations.ts +4 -4
  43. package/src/panels/builtin/shared.ts +4 -4
  44. package/src/panels/cost-tracker-panel.ts +2 -2
  45. package/src/panels/fleet-panel-format.ts +1 -1
  46. package/src/panels/fleet-panel.ts +15 -15
  47. package/src/panels/fleet-read-model.ts +11 -11
  48. package/src/panels/fleet-steer.ts +2 -2
  49. package/src/panels/fleet-stop.ts +2 -2
  50. package/src/panels/fleet-tabs.ts +4 -4
  51. package/src/panels/fleet-transcript.ts +5 -5
  52. package/src/panels/modals/memory-modal.ts +77 -16
  53. package/src/panels/types.ts +1 -1
  54. package/src/renderer/compaction-quality.ts +1 -1
  55. package/src/renderer/fleet-tab-strip.ts +2 -2
  56. package/src/renderer/footer-tips.ts +1 -1
  57. package/src/renderer/model-workspace.ts +1 -1
  58. package/src/renderer/session-picker-modal.ts +4 -4
  59. package/src/renderer/shell-surface.ts +1 -1
  60. package/src/renderer/status-glyphs.ts +3 -3
  61. package/src/renderer/terminal-bg-probe.ts +1 -1
  62. package/src/renderer/theme.ts +2 -2
  63. package/src/renderer/turn-injection.ts +3 -3
  64. package/src/renderer/ui-factory.ts +7 -7
  65. package/src/renderer/ui-primitives.ts +3 -3
  66. package/src/runtime/bootstrap-command-context.ts +4 -0
  67. package/src/runtime/bootstrap-command-parts.ts +4 -1
  68. package/src/runtime/bootstrap-core.ts +3 -3
  69. package/src/runtime/bootstrap-hook-bridge.ts +2 -2
  70. package/src/runtime/bootstrap-shell.ts +18 -7
  71. package/src/runtime/bootstrap.ts +22 -22
  72. package/src/runtime/code-index-services.ts +2 -2
  73. package/src/runtime/index.ts +11 -3
  74. package/src/runtime/legacy-daemon-migration.ts +9 -9
  75. package/src/runtime/memory-fold.ts +1 -1
  76. package/src/runtime/memory-spine-transport.ts +289 -0
  77. package/src/runtime/onboarding/snapshot.ts +3 -3
  78. package/src/runtime/onboarding/types.ts +2 -2
  79. package/src/runtime/operator-token-cleanup.ts +2 -2
  80. package/src/runtime/orchestrator-core-services.ts +29 -4
  81. package/src/runtime/process-lifecycle.ts +19 -5
  82. package/src/runtime/services.ts +27 -28
  83. package/src/runtime/session-inbound-inputs.ts +1 -1
  84. package/src/runtime/session-spine-transport.ts +2 -2
  85. package/src/runtime/ui-services.ts +1 -1
  86. package/src/runtime/workstream-services.ts +1 -1
  87. package/src/runtime/wrfc-persistence.ts +2 -2
  88. package/src/shell/ui-openers.ts +2 -2
  89. package/src/version.ts +1 -1
  90. package/src/audio/spoken-turn-controller.ts +0 -243
  91. package/src/audio/text-chunker.ts +0 -110
@@ -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
@@ -2,6 +2,7 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
3
3
  import type { ConversationManager } from '../core/conversation';
4
4
  import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
5
+ import type { MemorySpineClient } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
5
6
  import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
6
7
  import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
7
8
  import type { PanelManager } from '../panels/panel-manager.ts';
@@ -107,6 +108,7 @@ export type CreateBootstrapCommandContextOptions = {
107
108
  operatorClient?: OperatorClient;
108
109
  peerClient?: PeerClient;
109
110
  knowledgeApi?: KnowledgeApi;
111
+ memorySpine?: MemorySpineClient;
110
112
  hookApi?: HookApi;
111
113
  mcpApi?: McpApi;
112
114
  opsApi?: OpsApi;
@@ -195,6 +197,7 @@ export function createBootstrapCommandContext(
195
197
  operatorClient,
196
198
  peerClient,
197
199
  knowledgeApi,
200
+ memorySpine,
198
201
  hookApi,
199
202
  mcpApi,
200
203
  opsApi,
@@ -279,6 +282,7 @@ export function createBootstrapCommandContext(
279
282
  peerClient,
280
283
  providerApi,
281
284
  knowledgeApi,
285
+ memorySpine,
282
286
  hookApi,
283
287
  mcpApi,
284
288
  opsApi,
@@ -3,6 +3,7 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
3
3
  import type { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
4
4
  import type { ConversationManager } from '../core/conversation';
5
5
  import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
6
+ import type { MemorySpineClient } from '@pellux/goodvibes-sdk/platform/runtime/memory-spine';
6
7
  import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
7
8
  import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
8
9
  import type { PanelManager, PanelDeepLinkTarget } from '../panels/panel-manager.ts';
@@ -133,6 +134,7 @@ export interface BootstrapCommandSectionOptions {
133
134
  readonly peerClient?: PeerClient;
134
135
  readonly providerApi?: ProviderApi;
135
136
  readonly knowledgeApi?: KnowledgeApi;
137
+ readonly memorySpine?: MemorySpineClient;
136
138
  readonly hookApi?: HookApi;
137
139
  readonly mcpApi?: McpApi;
138
140
  readonly opsApi?: OpsApi;
@@ -421,7 +423,7 @@ export function createBootstrapCommandExtensionsSection(
421
423
  export function createBootstrapCommandClientsSection(
422
424
  options: Pick<
423
425
  BootstrapCommandSectionOptions,
424
- 'operatorClient' | 'peerClient' | 'providerApi' | 'knowledgeApi' | 'hookApi' | 'mcpApi' | 'opsApi' | 'directTransport'
426
+ 'operatorClient' | 'peerClient' | 'providerApi' | 'knowledgeApi' | 'memorySpine' | 'hookApi' | 'mcpApi' | 'opsApi' | 'directTransport'
425
427
  >,
426
428
  ): BootstrapCommandClientSection {
427
429
  return {
@@ -429,6 +431,7 @@ export function createBootstrapCommandClientsSection(
429
431
  peer: options.peerClient,
430
432
  providerApi: options.providerApi,
431
433
  knowledgeApi: options.knowledgeApi,
434
+ memorySpine: options.memorySpine,
432
435
  hookApi: options.hookApi,
433
436
  mcpApi: options.mcpApi,
434
437
  opsApi: options.opsApi,
@@ -119,7 +119,7 @@ export interface BootstrapCoreState {
119
119
  readonly runtimeSessionIdRef: { value: string };
120
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. */
121
121
  readonly sessionSpine: SessionSpineClient;
122
- /** D3: inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
122
+ /** Inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
123
123
  readonly sessionInboundInputs: SessionInboundInputPoller;
124
124
  /** Cache-backed read facade; bootstrap.ts drives its mode (embedded/external/local-only) from the same HostServiceMode as the spine above. */
125
125
  readonly sessionUnionCache: SessionUnionCache;
@@ -408,7 +408,7 @@ export async function initializeBootstrapCore(
408
408
  void memoryStore.save();
409
409
  memoryStore.close();
410
410
  });
411
- // W6-C2 (E6): fold this project's legacy per-project TUI memory into the home-scoped canonical store, ONCE, AFTER init(). Idempotent and non-fatal.
411
+ // Fold this project's legacy per-project TUI memory into the home-scoped canonical store, ONCE, AFTER init(). Idempotent and non-fatal.
412
412
  await runBootMemoryFold(memoryStore, services.memoryEmbeddingRegistry, workingDir, logger);
413
413
 
414
414
  const renderRequestRef = { value: (): void => {} };
@@ -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[] = [];
@@ -1,5 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
  import { readBudgetAlertUsd, BUDGET_ALERT_USD_DEFAULT } from '../export/cost-utils.ts';
3
+ import { refreshMemoryRecallSnapshot } from './orchestrator-core-services.ts';
3
4
  import { sumConversationUsage, type ConversationManager } from '../core/conversation';
4
5
  import type { Orchestrator } from '../core/orchestrator';
5
6
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
@@ -48,7 +49,7 @@ export interface BootstrapShellOptions {
48
49
  readonly runtimeBus: RuntimeEventBus;
49
50
  readonly runtimeStore: RuntimeStore;
50
51
  readonly services: RuntimeServices;
51
- /** S3c: dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
52
+ /** Dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
52
53
  readonly sessionSpine: SessionSpineClient;
53
54
  readonly conversation: ConversationManager;
54
55
  readonly runtime: MutableRuntimeState;
@@ -181,7 +182,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
181
182
  forensicsRegistry,
182
183
  policyRuntimeState,
183
184
  approvalBroker: services.approvalBroker,
184
- // S3d: panels read the cross-surface union facade, not the raw local broker.
185
+ // Panels read the cross-surface union facade, not the raw local broker.
185
186
  sessionBroker: uiServices.sessions.sessionBroker,
186
187
  automationManager: services.automationManager,
187
188
  getControlPlaneRecentEvents,
@@ -189,7 +190,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
189
190
  componentHealthMonitor: services.componentHealthMonitor,
190
191
  worktreeRegistry: services.worktreeRegistry,
191
192
  sandboxSessionRegistry: services.sandboxSessionRegistry,
192
- memoryRegistry: services.memoryRegistry,
193
+ // Memory modal reads via the spine client, not the raw registry (see builtin/shared.ts).
194
+ memoryRegistry: services.memorySpine,
193
195
  uiServices,
194
196
  pluginManager: services.pluginManager,
195
197
  hookDispatcher: services.hookDispatcher,
@@ -289,7 +291,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
289
291
  codeIndexStore: services.codeIndexStore,
290
292
  codeIndexReindexScheduler: services.codeIndexReindexScheduler,
291
293
  isPassiveCodeInjectionFlagEnabled: () => services.featureFlags.isEnabled('agent-passive-code-injection'),
292
- // Wave 5 (wo805): expose the MAIN session's per-turn passive-injection ring
294
+ // Expose the MAIN session's per-turn passive-injection ring
293
295
  // so `/recall injections` (no agent id) renders it — see recall-review.ts.
294
296
  getMainSessionTurnInjections: () => orchestrator.getTurnInjections(),
295
297
  changeTracker: services.sessionChangeTracker,
@@ -299,6 +301,10 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
299
301
  operatorClient: directTransport.operator,
300
302
  peerClient: directTransport.peer,
301
303
  knowledgeApi,
304
+ // /recall's browse/link/queue/export/import subcommands and the per-turn
305
+ // knowledge-injection read route through the spine client, not the raw
306
+ // local registry, so they fully detach when a daemon is adopted.
307
+ memorySpine: services.memorySpine,
302
308
  hookApi,
303
309
  mcpApi,
304
310
  opsApi,
@@ -309,9 +315,14 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
309
315
  loadSystemPrompt: () => loadBootstrapSystemPrompt(configManager),
310
316
  activatePlan: (_planId, task) => {
311
317
  setTimeout(() => {
312
- orchestrator.handleUserInput(task).catch((err) => {
313
- logger.debug('activatePlan handler failed', { error: summarizeError(err) });
314
- });
318
+ void (async () => {
319
+ // Refresh the recall snapshot before this plan-driven turn — see
320
+ // the matching comment in main.ts's submitInput.
321
+ await refreshMemoryRecallSnapshot(services);
322
+ orchestrator.handleUserInput(task).catch((err) => {
323
+ logger.debug('activatePlan handler failed', { error: summarizeError(err) });
324
+ });
325
+ })();
315
326
  }, 50);
316
327
  },
317
328
  completeModelSelectionSideEffect,
@@ -39,6 +39,7 @@ import { startExternalServices, type ExternalServicesHandle, type HostServiceSta
39
39
  import { createHttpTransport } from '@/runtime/index.ts';
40
40
  import { foldLegacySpineStore, deriveSpineFooterStatus } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
41
41
  import { createTuiSpineTransport, type SpineSessionsClient } from './session-spine-transport.ts';
42
+ import { syncMemorySpineToHostStatus, type MemorySpineActiveRef } from './memory-spine-transport.ts';
42
43
  import { pruneStaleOperatorTokens } from '@pellux/goodvibes-sdk/platform/pairing';
43
44
  import { resolveDaemonCompanionToken, workspaceOperatorTokenCandidates } from './operator-token-cleanup.ts';
44
45
  import type { UiRuntimeServices } from './ui-services.ts';
@@ -46,7 +47,7 @@ import { createDeferredStartupCoordinator } from '@/runtime/index.ts';
46
47
  import { initializeBootstrapCore } from './bootstrap-core.ts';
47
48
  import { createBootstrapShell } from './bootstrap-shell.ts';
48
49
  import { announceResumeState } from './resume-notice.ts';
49
- import { buildSharedOrchestratorCoreServices } from './orchestrator-core-services.ts';
50
+ import { buildSharedOrchestratorCoreServices, refreshMemoryRecallSnapshot } from './orchestrator-core-services.ts';
50
51
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
51
52
  import { DaemonServer } from '@pellux/goodvibes-sdk/platform/daemon';
52
53
  import { HttpListener } from '@pellux/goodvibes-sdk/platform/daemon';
@@ -199,7 +200,7 @@ export async function bootstrapRuntime(
199
200
  pluginManager,
200
201
  } = services;
201
202
 
202
- // D4b: a liveness flip is only PAINTED once something calls requestRender();
203
+ // A liveness flip is only PAINTED once something calls requestRender();
203
204
  // without this, the footer's spine segment sat correct-but-undrawn until
204
205
  // incidental activity redrew it (minutes, during an idle stretch).
205
206
  sessionUnionCache.setOnTransition(() => requestRender());
@@ -238,9 +239,9 @@ export async function bootstrapRuntime(
238
239
  },
239
240
  });
240
241
  conversationFollowUpRef.value = (item) => orchestrator.enqueueConversationFollowUp(item);
241
- // Wire orchestratorHandleUserInputRef so COMPANION_MESSAGE_RECEIVED fires a real LLM turn.
242
+ // Wire orchestratorHandleUserInputRef so COMPANION_MESSAGE_RECEIVED fires a real LLM turn (after a pre-turn recall-snapshot refresh; see main.ts's submitInput).
242
243
  orchestratorHandleUserInputRef.value = (text: string, options?: OrchestratorUserInputOptions) => {
243
- orchestrator.handleUserInput(text, undefined, options).catch((err: unknown) => {
244
+ void refreshMemoryRecallSnapshot(services).then(() => orchestrator.handleUserInput(text, undefined, options)).catch((err: unknown) => {
244
245
  logger.debug('companion handleUserInput safety catch', { error: String(err) });
245
246
  });
246
247
  };
@@ -300,13 +301,10 @@ export async function bootstrapRuntime(
300
301
  wrfcPersistence.rehydrate();
301
302
  const commandRegistry = shell.commandRegistry;
302
303
  const commandContext = shell.commandContext;
303
- // Boot resume notice (UX-D item 1): after rehydrate() so chain history is
304
- // ready, before the operator can type anything. Fire-and-forget the
305
- // checkpoint-count lookup is async (WorkspaceCheckpointManager.list() awaits
306
- // its own init()), and this must not block the rest of bootstrap the way
307
- // main.ts's `void workspaceCheckpointManager.init().catch(() => {})`
308
- // deliberately doesn't either. Local file I/O only; resolves well before a
309
- // human can react to the first rendered frame.
304
+ // Boot resume notice (UX-D item 1): after rehydrate() so chain history is ready, before
305
+ // the operator can type anything. Fire-and-forget, same as main.ts's non-blocking
306
+ // `void workspaceCheckpointManager.init().catch(() => {})` — local file I/O only,
307
+ // resolves well before a human can react to the first rendered frame.
310
308
  void announceResumeState({
311
309
  workingDirectory: services.workingDirectory,
312
310
  homeDirectory: services.homeDirectory,
@@ -390,7 +388,13 @@ export async function bootstrapRuntime(
390
388
  // 'unavailable') also stays local-only and honest. Only 'external' (a separately-
391
389
  // running daemon this TUI adopted) activates the wire mirror.
392
390
  let spineActiveForBaseUrl: string | null = null;
391
+ // Same adoption signal drives the memory spine (WO memory-adopt) — see
392
+ // memory-spine-transport.ts. 'embedded' (this process hosts its own daemon)
393
+ // stays local: the daemon's canonical store IS this process's own
394
+ // memoryRegistry, so there is no wire hop to make.
395
+ const memorySpineActiveRef: MemorySpineActiveRef = { value: null };
393
396
  const syncSessionSpineToHostStatus = (daemonStatus: HostServiceStatus, sharedDaemonToken: string): void => {
397
+ syncMemorySpineToHostStatus(services.memorySpine, daemonStatus.mode, daemonStatus.baseUrl, sharedDaemonToken, memorySpineActiveRef, logger);
394
398
  if (daemonStatus.mode !== 'external') {
395
399
  if (spineActiveForBaseUrl !== null) {
396
400
  sessionSpine.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
@@ -413,7 +417,7 @@ export async function bootstrapRuntime(
413
417
  close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
414
418
  };
415
419
  sessionSpine.activate(createTuiSpineTransport(sessionsClient));
416
- // D3: adopt the same daemon's wire for the INBOUND steer path — collect
420
+ // Adopt the same daemon's wire for the INBOUND steer path — collect
417
421
  // steer/follow-up inputs another live surface queued for this session and
418
422
  // inject them into the turn machinery (acking delivery on the wire).
419
423
  sessionInboundInputs.activate({
@@ -450,14 +454,10 @@ export async function bootstrapRuntime(
450
454
  httpListenerPortInUse: hostServiceIsBlocked(httpListenerStatus),
451
455
  daemonStatus,
452
456
  httpListenerStatus,
453
- // Honest session-spine posture, independent of daemonRunning —
454
- // 'external'-adopted-but-currently-unreachable degrades to 'offline' here
455
- // even though daemonRunning might still read true from a stale handle.
456
- // D4: sessionSpine.status() alone is ACTIVITY-gated (only updates on a
457
- // register/heartbeat/close), so after the daemon dies mid-idle it would
458
- // keep reading 'online'. Derive the footer status from the union cache's
459
- // 5s liveness probe too — one signal, no new timer — so offline surfaces
460
- // within one refresh interval of the daemon dying.
457
+ // Honest session-spine posture, independent of daemonRunning — degrades to 'offline'
458
+ // when adopted-but-unreachable even though daemonRunning might still read a stale
459
+ // handle as true; sessionSpine.status() alone is activity-gated, so it's derived
460
+ // together with the union cache's 5s liveness probe (one signal, no new timer).
461
461
  sessionSpineActive: sessionSpine.active,
462
462
  sessionSpineStatus: deriveSpineFooterStatus(sessionSpine.status(), sessionUnionCache.crossSurfaceView),
463
463
  };
@@ -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.
@@ -9,7 +9,7 @@ import {
9
9
  } from '@pellux/goodvibes-sdk/platform/state';
10
10
 
11
11
  /**
12
- * 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
13
13
  * cross-surface store. Called once at boot AFTER `memoryStore.init()` so records written
14
14
  * before unification survive. Id-keyed and idempotent — a re-run imports nothing new and
15
15
  * never deletes the legacy file. Returns the report so boot can log what moved.