@pellux/goodvibes-tui 1.7.0 → 1.9.1

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 (140) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +10 -9
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/config/credential-availability.ts +122 -0
  12. package/src/config/index.ts +15 -0
  13. package/src/core/composer-state.ts +11 -3
  14. package/src/core/conversation-line-cache.ts +27 -3
  15. package/src/core/conversation-rendering.ts +71 -14
  16. package/src/core/stream-event-wiring.ts +20 -1
  17. package/src/core/system-message-noise.ts +87 -0
  18. package/src/core/system-message-router.ts +68 -1
  19. package/src/core/turn-cancellation.ts +7 -2
  20. package/src/core/turn-event-wiring.ts +10 -2
  21. package/src/daemon/cli.ts +29 -2
  22. package/src/daemon/handlers/register.ts +8 -1
  23. package/src/daemon/service-commands.ts +329 -0
  24. package/src/input/autocomplete.ts +27 -1
  25. package/src/input/command-registry.ts +46 -4
  26. package/src/input/commands/codebase-runtime.ts +46 -6
  27. package/src/input/commands/config.ts +43 -3
  28. package/src/input/commands/health-runtime.ts +9 -1
  29. package/src/input/commands/memory.ts +68 -35
  30. package/src/input/commands/operator-panel-runtime.ts +31 -7
  31. package/src/input/commands/planning-runtime.ts +95 -6
  32. package/src/input/commands/qrcode-runtime.ts +25 -5
  33. package/src/input/commands/remote-runtime-setup.ts +5 -3
  34. package/src/input/commands/session-content.ts +20 -9
  35. package/src/input/commands/settings-sync-runtime.ts +15 -3
  36. package/src/input/commands/shell-core.ts +10 -1
  37. package/src/input/commands/workstream-runtime.ts +168 -18
  38. package/src/input/config-modal-types.ts +15 -1
  39. package/src/input/config-modal.ts +227 -12
  40. package/src/input/feed-context-factory.ts +3 -0
  41. package/src/input/handler-command-route.ts +10 -3
  42. package/src/input/handler-content-actions.ts +17 -2
  43. package/src/input/handler-feed-routes.ts +43 -121
  44. package/src/input/handler-feed.ts +32 -4
  45. package/src/input/handler-modal-routes.ts +78 -13
  46. package/src/input/handler-modal-stack.ts +11 -2
  47. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  48. package/src/input/handler-onboarding.ts +71 -59
  49. package/src/input/handler-picker-routes.ts +15 -8
  50. package/src/input/handler-shortcuts.ts +59 -9
  51. package/src/input/handler.ts +6 -1
  52. package/src/input/keybindings.ts +6 -5
  53. package/src/input/model-picker.ts +19 -2
  54. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  55. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  56. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  57. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  58. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  59. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  60. package/src/input/panel-mouse-geometry.ts +97 -0
  61. package/src/input/panel-paste-flood-guard.ts +86 -0
  62. package/src/input/selection-modal.ts +11 -0
  63. package/src/input/session-picker-modal.ts +44 -4
  64. package/src/input/settings-modal-data.ts +51 -2
  65. package/src/input/settings-modal-types.ts +4 -2
  66. package/src/main.ts +28 -27
  67. package/src/panels/builtin/shared.ts +9 -3
  68. package/src/panels/fleet-deep-link.ts +31 -0
  69. package/src/panels/fleet-panel-format.ts +62 -0
  70. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  71. package/src/panels/fleet-panel.ts +89 -131
  72. package/src/panels/fleet-read-model.ts +43 -0
  73. package/src/panels/fleet-steer.ts +35 -2
  74. package/src/panels/fleet-stop.ts +29 -1
  75. package/src/panels/modals/keybindings-modal.ts +16 -1
  76. package/src/panels/modals/modal-theme.ts +35 -29
  77. package/src/panels/modals/pairing-modal.ts +25 -6
  78. package/src/panels/modals/planning-modal.ts +43 -21
  79. package/src/panels/modals/work-plan-modal.ts +28 -0
  80. package/src/panels/panel-manager.ts +15 -4
  81. package/src/panels/polish-core.ts +38 -25
  82. package/src/panels/project-planning-answer-actions.ts +8 -13
  83. package/src/panels/types.ts +24 -0
  84. package/src/permissions/prompt.ts +160 -13
  85. package/src/renderer/autocomplete-overlay.ts +28 -2
  86. package/src/renderer/compositor.ts +2 -3
  87. package/src/renderer/config-modal.ts +19 -5
  88. package/src/renderer/footer-tips.ts +5 -1
  89. package/src/renderer/fullscreen-primitives.ts +32 -22
  90. package/src/renderer/git-status.ts +3 -1
  91. package/src/renderer/layout.ts +0 -4
  92. package/src/renderer/markdown.ts +7 -3
  93. package/src/renderer/modal-factory.ts +25 -20
  94. package/src/renderer/model-workspace.ts +18 -3
  95. package/src/renderer/overlay-box.ts +21 -17
  96. package/src/renderer/process-indicator.ts +14 -3
  97. package/src/renderer/selection-modal-overlay.ts +6 -1
  98. package/src/renderer/session-picker-modal.ts +196 -3
  99. package/src/renderer/settings-modal-helpers.ts +2 -0
  100. package/src/renderer/settings-modal.ts +7 -0
  101. package/src/renderer/shell-surface.ts +8 -1
  102. package/src/renderer/status-glyphs.ts +14 -15
  103. package/src/renderer/system-message.ts +15 -3
  104. package/src/renderer/terminal-bg-probe.ts +339 -0
  105. package/src/renderer/terminal-escapes.ts +20 -0
  106. package/src/renderer/theme-mode-config.ts +67 -0
  107. package/src/renderer/theme.ts +91 -1
  108. package/src/renderer/thinking.ts +11 -3
  109. package/src/renderer/tool-call.ts +15 -9
  110. package/src/renderer/tool-result-summary.ts +148 -0
  111. package/src/renderer/turn-injection.ts +22 -3
  112. package/src/renderer/ui-factory.ts +154 -85
  113. package/src/renderer/ui-primitives.ts +30 -129
  114. package/src/runtime/bootstrap-command-context.ts +6 -0
  115. package/src/runtime/bootstrap-command-parts.ts +8 -4
  116. package/src/runtime/bootstrap-core.ts +46 -24
  117. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  118. package/src/runtime/bootstrap-shell.ts +19 -1
  119. package/src/runtime/bootstrap.ts +118 -5
  120. package/src/runtime/code-index-services.ts +25 -2
  121. package/src/runtime/legacy-daemon-migration.ts +516 -0
  122. package/src/runtime/memory-fold.ts +55 -0
  123. package/src/runtime/onboarding/derivation.ts +7 -2
  124. package/src/runtime/onboarding/snapshot.ts +27 -1
  125. package/src/runtime/onboarding/types.ts +31 -1
  126. package/src/runtime/operator-token-cleanup.ts +82 -1
  127. package/src/runtime/orchestrator-core-services.ts +10 -0
  128. package/src/runtime/resume-notice.ts +209 -0
  129. package/src/runtime/services.ts +12 -8
  130. package/src/runtime/session-inbound-inputs.ts +252 -0
  131. package/src/runtime/session-spine-transport.ts +64 -0
  132. package/src/runtime/terminal-output-guard.ts +15 -8
  133. package/src/runtime/ui-services.ts +19 -3
  134. package/src/runtime/workstream-services.ts +160 -28
  135. package/src/runtime/wrfc-persistence.ts +124 -17
  136. package/src/shell/blocking-input.ts +46 -3
  137. package/src/shell/recovery-input-helpers.ts +170 -1
  138. package/src/shell/ui-openers.ts +42 -9
  139. package/src/utils/terminal-width.ts +52 -0
  140. package/src/version.ts +1 -1
@@ -36,12 +36,16 @@ import {
36
36
  import { scheduleBackgroundMcpDiscovery, startBackgroundProviderRegistration } from '@/runtime/index.ts';
37
37
  import { restoreSavedModel } from '@/runtime/index.ts';
38
38
  import { startExternalServices, type ExternalServicesHandle, type HostServiceStatus } from '@/runtime/index.ts';
39
- import { getOrCreateCompanionToken, pruneStaleOperatorTokens } from '@pellux/goodvibes-sdk/platform/pairing';
40
- import { workspaceOperatorTokenCandidates } from './operator-token-cleanup.ts';
39
+ import { createHttpTransport } from '@/runtime/index.ts';
40
+ import { foldLegacySpineStore, deriveSpineFooterStatus } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
41
+ import { createTuiSpineTransport, type SpineSessionsClient } from './session-spine-transport.ts';
42
+ import { pruneStaleOperatorTokens } from '@pellux/goodvibes-sdk/platform/pairing';
43
+ import { resolveDaemonCompanionToken, workspaceOperatorTokenCandidates } from './operator-token-cleanup.ts';
41
44
  import type { UiRuntimeServices } from './ui-services.ts';
42
45
  import { createDeferredStartupCoordinator } from '@/runtime/index.ts';
43
46
  import { initializeBootstrapCore } from './bootstrap-core.ts';
44
47
  import { createBootstrapShell } from './bootstrap-shell.ts';
48
+ import { announceResumeState } from './resume-notice.ts';
45
49
  import { buildSharedOrchestratorCoreServices } from './orchestrator-core-services.ts';
46
50
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
47
51
  import { DaemonServer } from '@pellux/goodvibes-sdk/platform/daemon';
@@ -183,6 +187,9 @@ export async function bootstrapRuntime(
183
187
  setRenderRequest,
184
188
  runtimeSessionIdRef,
185
189
  wrfcPersistence,
190
+ sessionSpine,
191
+ sessionInboundInputs,
192
+ sessionUnionCache,
186
193
  } = await initializeBootstrapCore(stdout, options, (limit) => controlPlaneRecentEventsRef.value(limit));
187
194
  const providerRegistry = services.providerRegistry;
188
195
  const {
@@ -192,6 +199,11 @@ export async function bootstrapRuntime(
192
199
  pluginManager,
193
200
  } = services;
194
201
 
202
+ // D4b: a liveness flip is only PAINTED once something calls requestRender();
203
+ // without this, the footer's spine segment sat correct-but-undrawn until
204
+ // incidental activity redrew it (minutes, during an idle stretch).
205
+ sessionUnionCache.setOnTransition(() => requestRender());
206
+
195
207
  // ── Phase 6: Orchestrator + AcpManager ───────────────────────────────────
196
208
 
197
209
  // Mutable function refs so main.ts can patch these after constructing the scroll/viewport state.
@@ -262,6 +274,7 @@ export async function bootstrapRuntime(
262
274
  runtimeBus,
263
275
  runtimeStore: store,
264
276
  services,
277
+ sessionSpine,
265
278
  conversation,
266
279
  runtime,
267
280
  orchestrator,
@@ -287,6 +300,25 @@ export async function bootstrapRuntime(
287
300
  wrfcPersistence.rehydrate();
288
301
  const commandRegistry = shell.commandRegistry;
289
302
  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.
310
+ void announceResumeState({
311
+ workingDirectory: services.workingDirectory,
312
+ homeDirectory: services.homeDirectory,
313
+ surfaceRoot: 'tui',
314
+ sessionManager: services.sessionManager,
315
+ checkpointManager: services.workspaceCheckpointManager,
316
+ chainHistory: wrfcPersistence.knownChains,
317
+ memoryAvailable: Boolean(commandContext.clients?.knowledgeApi?.memory),
318
+ router: systemMessageRouter,
319
+ }).catch(() => {
320
+ // Best-effort — never let the resume notice block or crash boot.
321
+ });
290
322
  const gitStatusProvider = shell.gitStatusProvider;
291
323
  const inputHistory = shell.inputHistory;
292
324
  const lastGitInfoRef = shell.lastGitInfoRef;
@@ -343,7 +375,70 @@ export async function bootstrapRuntime(
343
375
 
344
376
  const hostServiceIsActive = (status: HostServiceStatus): boolean => status.mode === 'embedded' || status.mode === 'external';
345
377
 
346
- const hostServiceIsBlocked = (status: HostServiceStatus): boolean => status.mode === 'blocked';
378
+ // 'blocked' (occupied by an unverified process) and 'incompatible' (occupied
379
+ // by a GoodVibes daemon we refused to adopt on a wire-version mismatch) both
380
+ // mean the configured port is held and unusable by this TUI instance.
381
+ const hostServiceIsBlocked = (status: HostServiceStatus): boolean => status.mode === 'blocked' || status.mode === 'incompatible';
382
+
383
+ // PERMANENT DESIGN (docs/decisions/2026-07-06-session-spine-mode-branch-is-permanent.md):
384
+ // 'embedded'/'external' are two distinct, both-supported daemon topologies, not
385
+ // migration stages — the ONE client-selection point for the session spine, driven by
386
+ // the SAME authoritative HostServiceMode adopt-or-start already computed above.
387
+ // 'embedded' (daemon.embedInProcess=true) means THIS process's own SharedSessionBroker
388
+ // already IS the daemon's broker — nothing to mirror TO, so the spine stays
389
+ // permanently dormant. Every other mode ('disabled'/'blocked'/'incompatible'/
390
+ // 'unavailable') also stays local-only and honest. Only 'external' (a separately-
391
+ // running daemon this TUI adopted) activates the wire mirror.
392
+ let spineActiveForBaseUrl: string | null = null;
393
+ const syncSessionSpineToHostStatus = (daemonStatus: HostServiceStatus, sharedDaemonToken: string): void => {
394
+ if (daemonStatus.mode !== 'external') {
395
+ if (spineActiveForBaseUrl !== null) {
396
+ sessionSpine.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
397
+ sessionInboundInputs.deactivate(`daemon mode changed to '${daemonStatus.mode}'`);
398
+ spineActiveForBaseUrl = null;
399
+ }
400
+ // Keep the read facade honest per topology (permanent, not staged): 'embedded'
401
+ // means this process's broker IS the daemon's broker (local reads are the whole
402
+ // truth); every other non-external mode is local-only/dormant.
403
+ if (daemonStatus.mode === 'embedded') sessionUnionCache.markEmbedded();
404
+ else sessionUnionCache.deactivate(`daemon mode '${daemonStatus.mode}'`);
405
+ logger.info(`[bootstrap] session spine: daemon mode '${daemonStatus.mode}' — local-only (no spine mirror)`);
406
+ return;
407
+ }
408
+ const baseUrl = daemonStatus.baseUrl;
409
+ if (spineActiveForBaseUrl === baseUrl) return; // already wired to this exact adopted daemon
410
+ const httpTransport = createHttpTransport({ baseUrl, authToken: sharedDaemonToken });
411
+ const sessionsClient: SpineSessionsClient = {
412
+ register: (input) => httpTransport.operator.sessions.register(input),
413
+ close: (sessionId) => httpTransport.operator.sessions.close(sessionId),
414
+ };
415
+ sessionSpine.activate(createTuiSpineTransport(sessionsClient));
416
+ // D3: adopt the same daemon's wire for the INBOUND steer path — collect
417
+ // steer/follow-up inputs another live surface queued for this session and
418
+ // inject them into the turn machinery (acking delivery on the wire).
419
+ sessionInboundInputs.activate({
420
+ listInputs: async (sessionId, opts) => ({
421
+ inputs: await httpTransport.operator.sessions.inputs(sessionId, opts.limit, { state: opts.state, since: opts.since }),
422
+ }),
423
+ deliverInput: (sessionId, inputId, opts) => httpTransport.operator.sessions.deliverInput(sessionId, inputId, opts),
424
+ });
425
+ // 'external' only: adopt the same daemon's wire as the read facade's cross-surface
426
+ // union source (interval-refreshed; served synchronously to panels).
427
+ sessionUnionCache.activate({ list: (limit) => httpTransport.operator.sessions.list(limit) });
428
+ spineActiveForBaseUrl = baseUrl;
429
+ logger.info(`[bootstrap] session spine: adopted external daemon at ${baseUrl} — mirroring session identity`);
430
+ // Legacy fold: one-time (marker-guarded) import of this project's own
431
+ // pre-spine control-plane sessions.json into the now-adopted daemon.
432
+ const legacyStorePath = services.shellPaths.resolveProjectPath('tui', 'control-plane', 'sessions.json');
433
+ const fold = foldLegacySpineStore(sessionSpine, {
434
+ storePath: legacyStorePath,
435
+ markerPath: `${legacyStorePath}.spine-migrated`,
436
+ project: services.workingDirectory,
437
+ });
438
+ if (fold.folded > 0) {
439
+ logger.info(`[bootstrap] session spine: folded ${fold.folded} legacy local session(s) into the adopted daemon`);
440
+ }
441
+ };
347
442
 
348
443
  const inspectExternalServices = () => {
349
444
  const daemonStatus = externalServices.daemonStatus;
@@ -355,6 +450,16 @@ export async function bootstrapRuntime(
355
450
  httpListenerPortInUse: hostServiceIsBlocked(httpListenerStatus),
356
451
  daemonStatus,
357
452
  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.
461
+ sessionSpineActive: sessionSpine.active,
462
+ sessionSpineStatus: deriveSpineFooterStatus(sessionSpine.status(), sessionUnionCache.crossSurfaceView),
358
463
  };
359
464
  };
360
465
 
@@ -413,7 +518,7 @@ export async function bootstrapRuntime(
413
518
  await waitForConfigDrivenRestarts(externalServices);
414
519
  await externalServices.stop();
415
520
  const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
416
- const companionTokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
521
+ const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
417
522
  externalServicesPromise = startExternalServices(
418
523
  configManager,
419
524
  runtimeBus,
@@ -423,6 +528,7 @@ export async function bootstrapRuntime(
423
528
  );
424
529
  externalServices = await externalServicesPromise;
425
530
  controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
531
+ syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
426
532
  requestRender();
427
533
  return inspectExternalServices();
428
534
  },
@@ -461,7 +567,7 @@ export async function bootstrapRuntime(
461
567
  // bearer, so tokens scanned from the /qrcode panel's QR actually
462
568
  // authenticate against the embedded daemon this surface starts.
463
569
  const daemonHomeDir = join(services.homeDirectory, '.goodvibes', 'daemon');
464
- const companionTokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
570
+ const companionTokenRecord = resolveDaemonCompanionToken(daemonHomeDir);
465
571
  // F3 resolution (TUI 0.19.20): remove stale pre-0.21.28 workspace-scoped operator
466
572
  // token files so only the canonical <daemonHomeDir>/operator-tokens.json survives.
467
573
  // The prune is best-effort — it silently skips missing files, no-ops when tokens
@@ -486,6 +592,7 @@ export async function bootstrapRuntime(
486
592
  );
487
593
  externalServices = await externalServicesPromise;
488
594
  controlPlaneRecentEventsRef.value = (limit) => externalServices.listRecentControlPlaneEvents(limit);
595
+ syncSessionSpineToHostStatus(externalServices.daemonStatus, companionTokenRecord.token);
489
596
  requestRender();
490
597
  },
491
598
  onError: (error) => {
@@ -630,6 +737,12 @@ export async function bootstrapRuntime(
630
737
  runtimeUnsubs.forEach((fn) => fn());
631
738
  runtimeUnsubs.length = 0;
632
739
  forensicsCollector.dispose();
740
+ // Honest close on exit — fire-and-forget (never blocks shutdown);
741
+ // a no-op when the spine was never activated (embedded/local-only topology).
742
+ sessionSpine.close(runtime.sessionId);
743
+ sessionSpine.dispose();
744
+ sessionInboundInputs.dispose(); // D3: stop the inbound steer poll interval on exit
745
+ sessionUnionCache.dispose(); // stop the wire-refresh interval on exit
633
746
  await deferredStartup.drain(100);
634
747
  if (externalServicesPromise) {
635
748
  try {
@@ -38,7 +38,7 @@
38
38
  // ---------------------------------------------------------------------------
39
39
 
40
40
  import { join } from 'node:path';
41
- import { CodeIndexStore } from '@pellux/goodvibes-sdk/platform/state';
41
+ import { CodeIndexStore, CodeIndexReindexScheduler } from '@pellux/goodvibes-sdk/platform/state';
42
42
  import type { MemoryEmbeddingProviderRegistry } from '@pellux/goodvibes-sdk/platform/state';
43
43
  import type { ConfigKey, ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
44
44
  import { readBooleanConfig } from '../core/alert-gating.ts';
@@ -67,8 +67,26 @@ export interface CodeIndexServicesDeps {
67
67
  readonly memoryEmbeddingRegistry: MemoryEmbeddingProviderRegistry;
68
68
  }
69
69
 
70
+ /**
71
+ * Whether Stage B code auto-injection / tool-site reindex may run: the SAME
72
+ * storage.codeIndexEnabled switch that gates auto-build. Read live so a runtime
73
+ * /config toggle takes effect without restart. (The separate, default-off
74
+ * `agent-passive-code-injection` feature flag gates injection additionally; this
75
+ * is only the storage-side setting the SDK asks embedders to respect.)
76
+ */
77
+ export function isCodeInjectionSettingEnabled(configManager: Pick<ConfigManager, 'get'>): boolean {
78
+ return isCodeIndexAutoStartEnabled(configManager);
79
+ }
80
+
70
81
  export interface CodeIndexServices {
71
82
  readonly codeIndexStore: CodeIndexStore;
83
+ /**
84
+ * Wave-5 Stage B tool-site incremental reindex scheduler, bound to codeIndexStore and
85
+ * gated live on storage.codeIndexEnabled. Threaded into both SDK orchestrators (main +
86
+ * agent) so a successful write/edit debounces an incremental reindex, and into the command
87
+ * context so /codebase status can report the last reindex activity honestly.
88
+ */
89
+ readonly codeIndexReindexScheduler: CodeIndexReindexScheduler;
72
90
  }
73
91
 
74
92
  /** Absolute path to the TUI's code-index sqlite file, sibling to memory.sqlite under .goodvibes/tui/. */
@@ -108,5 +126,10 @@ export function createCodeIndexServices(deps: CodeIndexServicesDeps): CodeIndexS
108
126
  if (isCodeIndexAutoStartEnabled(deps.configManager)) {
109
127
  codeIndexStore.scheduleBuild();
110
128
  }
111
- return { codeIndexStore };
129
+ const codeIndexReindexScheduler = new CodeIndexReindexScheduler({
130
+ target: codeIndexStore,
131
+ workingDirectory: deps.workingDirectory,
132
+ isEnabled: () => isCodeInjectionSettingEnabled(deps.configManager),
133
+ });
134
+ return { codeIndexStore, codeIndexReindexScheduler };
112
135
  }