@pellux/goodvibes-tui 1.9.1 → 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 +16 -5
  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 +1 -1
  8. package/src/config/index.ts +1 -1
  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 +3 -3
  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 +1 -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
@@ -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
@@ -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[] = [];
@@ -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.
@@ -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.
@@ -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.
@@ -57,7 +57,7 @@ export interface ProcessLifecycleDeps {
57
57
  readonly unsubs: ReadonlyArray<() => void>;
58
58
  readonly getRecoveryInterval: () => ReturnType<typeof setInterval> | null;
59
59
  readonly setRecoveryInterval: (value: ReturnType<typeof setInterval> | null) => void;
60
- readonly getStopSpokenOutputForExit: () => (() => void) | null;
60
+ readonly getStopSpokenOutputForExit: () => (() => void | Promise<void>) | null;
61
61
  }
62
62
 
63
63
  export interface ProcessLifecycleHandlers {
@@ -153,7 +153,16 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
153
153
  // must not re-run teardown or double-fire ctx.shutdown.
154
154
  if (exiting) return;
155
155
  exiting = true;
156
- getStopSpokenOutputForExit()?.();
156
+ // Exit lets the spoken audio the user is already hearing finish inside a
157
+ // short bounded window (capped inside stopForExit, under the 3s shutdown
158
+ // budget below) while the rest of teardown proceeds; queued-but-unplayed
159
+ // speech is dropped. Deliberate interrupts (Ctrl+C, /tts stop) still cut
160
+ // instantly through controller.stop() before this path is ever reached.
161
+ let spokenOutputDrain: Promise<void> = Promise.resolve();
162
+ try {
163
+ spokenOutputDrain = Promise.resolve(getStopSpokenOutputForExit()?.()).then(() => undefined);
164
+ } catch { /* non-fatal to exit */ }
165
+ spokenOutputDrain = spokenOutputDrain.catch(() => undefined);
157
166
  unsubs.forEach(fn => fn());
158
167
  const interval = getRecoveryInterval();
159
168
  if (interval !== null) { clearInterval(interval); setRecoveryInterval(null); }
@@ -169,9 +178,14 @@ export function installProcessLifecycle(deps: ProcessLifecycleDeps): ProcessLife
169
178
  try {
170
179
  // Race the graceful shutdown against a hard timeout — externalServices.stop() can hang
171
180
  // and we must still exit; deferredStartup.drain only budgets 100ms internally.
172
- await Promise.race([
173
- ctx.shutdown({ ...snapshot, ...buildPersistedSessionContext(snapshot.messages, ctx.conversation.getTitleSource(), buildSessionContinuityHints()) }).then(() => { shutdownOk = true; }),
174
- new Promise<void>((resolve) => setTimeout(resolve, 3000)),
181
+ // The spoken-audio drain runs concurrently and is internally capped below
182
+ // this budget, so it never extends the exit beyond the hard timeout.
183
+ await Promise.all([
184
+ spokenOutputDrain,
185
+ Promise.race([
186
+ ctx.shutdown({ ...snapshot, ...buildPersistedSessionContext(snapshot.messages, ctx.conversation.getTitleSource(), buildSessionContinuityHints()) }).then(() => { shutdownOk = true; }),
187
+ new Promise<void>((resolve) => setTimeout(resolve, 3000)),
188
+ ]),
175
189
  ]);
176
190
  } catch (err) {
177
191
  logger.debug('ctx.shutdown error during exitApp (non-fatal)', { error: summarizeError(err) });
@@ -222,12 +222,12 @@ export interface RuntimeServices {
222
222
  readonly agentOrchestrator: AgentOrchestrator;
223
223
  readonly wrfcController: WrfcController;
224
224
  readonly processManager: ProcessManager;
225
- /** Wave 4 (wo703): the phase/work-item orchestration engine — see runtime/workstream-services.ts. */
225
+ /** The phase/work-item orchestration engine — see runtime/workstream-services.ts. */
226
226
  readonly orchestrationEngine: OrchestrationEngine;
227
227
  readonly workstreamCommands: WorkstreamCommandService;
228
- /** Wave 5 (wo804): the repo source-tree code index — see runtime/code-index-services.ts. */
228
+ /** The repo source-tree code index — see runtime/code-index-services.ts. */
229
229
  readonly codeIndexStore: CodeIndexStore;
230
- readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // Wave-5 Stage B tool-site reindex
230
+ readonly codeIndexReindexScheduler: CodeIndexReindexScheduler; // tool-site reindex
231
231
  /** W2.1/W2.2: unified live process registry (agents, WRFC chains, workflows, watchers, background processes) backing the Fleet panel. */
232
232
  readonly processRegistry: ProcessRegistry;
233
233
  readonly modeManager: ModeManager;
@@ -338,7 +338,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
338
338
  executor: agentOrchestrator,
339
339
  configManager,
340
340
  });
341
- agentOrchestrator.setConversationSink({ // Wave-3 Part C6 bridge (mirrors the SDK's own createRuntimeServices)
341
+ agentOrchestrator.setConversationSink({ // Conversation-snapshot bridge (mirrors the SDK's own createRuntimeServices)
342
342
  register: (agentId, source) => agentManager.registerConversationSource(agentId, source),
343
343
  release: (agentId) => agentManager.releaseConversationSource(agentId),
344
344
  });
@@ -387,7 +387,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
387
387
  });
388
388
  const artifactStore = new ArtifactStore({ configManager });
389
389
  const memoryEmbeddingRegistry = new MemoryEmbeddingProviderRegistry({ configManager });
390
- // W6-C2 (E6): open the ONE home-scoped canonical store; legacy per-project TUI memory folds in at boot (foldTuiLegacyMemory).
390
+ // Open the ONE home-scoped canonical store; legacy per-project TUI memory folds in at boot (foldTuiLegacyMemory).
391
391
  const memoryDbPath = resolveCanonicalMemoryDbPath(homeDirectory);
392
392
  const memoryStore = new MemoryStore(memoryDbPath, {
393
393
  embeddingRegistry: memoryEmbeddingRegistry,
@@ -595,18 +595,18 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
595
595
  artifactStore,
596
596
  });
597
597
  const processManager = new ProcessManager();
598
- // Wave 4 (wo703): the phase/work-item orchestration engine, constructed
598
+ // The phase/work-item orchestration engine, constructed
599
599
  // before the process registry so its fleet nodes (workstream/phase/
600
600
  // work-item) can be folded in below via the registry's optional
601
601
  // orchestrationEngine dep.
602
602
  const { orchestrationEngine, workstreamCommands } = createWorkstreamServices({
603
603
  agentManager, configManager, adaptivePlanner, runtimeBus: options.runtimeBus, projectRoot: workingDirectory,
604
604
  });
605
- // Wave 5 (wo804): repo source-tree code index, sharing memoryEmbeddingRegistry
605
+ // Repo source-tree code index, sharing memoryEmbeddingRegistry
606
606
  // with MemoryStore above. Auto-build is config-gated (default off) — see
607
607
  // code-index-services.ts's header doc.
608
608
  const { codeIndexStore, codeIndexReindexScheduler } = createCodeIndexServices({ workingDirectory, configManager, memoryEmbeddingRegistry });
609
- const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; // Wave-5 Stage B seam (agent here; main via orchestrator-core-services.ts)
609
+ const codeInjectionOrchestratorDeps = { codeIndex: codeIndexStore, isCodeInjectionSettingEnabled: () => isCodeInjectionSettingEnabled(configManager), codeIndexReindexScheduler }; // Code-injection seam (agent here; main via orchestrator-core-services.ts)
610
610
  // W2.1/W2.2: one shared process registry aggregating the managers above —
611
611
  // the Fleet panel (panels/fleet-read-model.ts) is its first consumer.
612
612
  // Constructed once here (not per-consumer) so the coalesced tick and the
@@ -614,15 +614,15 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
614
614
  const processRegistry = createProcessRegistry({
615
615
  agentManager,
616
616
  wrfcController,
617
- orchestrationEngine, // Wave 4 (wo703): folds workstream/phase/work-item nodes into the fleet
618
- codeIndexService: codeIndexStore, // Wave 5 (wo804): folds a single 'code-index' node into the fleet
617
+ orchestrationEngine, // Folds workstream/phase/work-item nodes into the fleet
618
+ codeIndexService: codeIndexStore, // Folds a single 'code-index' node into the fleet
619
619
  processManager,
620
620
  watcherRegistry,
621
621
  workflow,
622
622
  approvalBroker,
623
623
  sessionBroker,
624
- messageBus: agentMessageBus, // Wave-3: backs steer()/`steerable` (wo612 builds the composer UI on top)
625
- automationManager, // Wave 6 (wo-F item d4): folds /schedule AutomationJobs into the fleet as 'schedule' nodes
624
+ messageBus: agentMessageBus, // Backs steer()/`steerable` (the Fleet steer composer builds on top)
625
+ automationManager, // Folds /schedule AutomationJobs into the fleet as 'schedule' nodes
626
626
  runtimeBus: options.runtimeBus,
627
627
  // Honest pricing: never fabricate a cost for an unrecognized model.
628
628
  priceUsage: (model, usage) => {
@@ -681,7 +681,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
681
681
  remoteRunnerRegistry,
682
682
  knowledgeService,
683
683
  memoryRegistry,
684
- ...codeInjectionOrchestratorDeps, // Wave-5 Stage B: agent-run code injection + tool-site reindex
684
+ ...codeInjectionOrchestratorDeps, // Agent-run code injection + tool-site reindex
685
685
  archetypeLoader,
686
686
  configManager,
687
687
  providerRegistry,
@@ -790,7 +790,7 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
790
790
  workspaceCheckpointManager,
791
791
  integrationHelpers,
792
792
  async rerootStores(newWorkingDir: string): Promise<void> {
793
- // W6-C2 (E6): the memory store is the home-scoped canonical cross-surface store and
793
+ // The memory store is the home-scoped canonical cross-surface store and
794
794
  // deliberately does NOT reroot per-project (that would re-silo memory, the E6
795
795
  // regression). Only working-tree-bound stores (code index, project index) reroot.
796
796
  await codeIndexStore.reroot(newWorkingDir, codeIndexDbPath(newWorkingDir));
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * session-inbound-inputs.ts
3
3
  *
4
- * D3 (One-Platform Wave 2, live-surface steer delivery the inbound half of the
4
+ * Live-surface steer delivery (the inbound half of the
5
5
  * session spine): the TUI's INBOUND path for steer/follow-up inputs that another
6
6
  * live surface (e.g. the webui) queued against THIS TUI's session.
7
7
  *
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * session-spine-transport.ts
3
3
  *
4
- * W3-T1 (One-Platform Wave 3, TUI session-spine cutover): the thin typed-client
4
+ * The TUI session-spine cutover: the thin typed-client
5
5
  * adapter that lets the TUI's bootstrap drive the SDK's
6
6
  * `@pellux/goodvibes-sdk/platform/runtime/session-spine` `SessionSpineClient`
7
- * (extracted in W3-S4) via its injected `SpineTransport` seam.
7
+ * via its injected `SpineTransport` seam.
8
8
  *
9
9
  * The TUI is SDK-clean here: `SpineSessionsClient` is exactly the narrow wire
10
10
  * surface the old TUI-local `session-spine-client.ts` declared (structurally
@@ -114,7 +114,7 @@ export interface UiRuntimeSharedServices {
114
114
  readonly remoteSupervisor: RuntimeServices['remoteSupervisor'] & RemoteSupervisor;
115
115
  /** W2.2: the shared live process registry backing the Fleet panel. */
116
116
  readonly processRegistry: RuntimeServices['processRegistry'];
117
- /** Wave-3 (W3.2): the shared runtime event bus — the Fleet panel subscribes to its 'communication' domain for the honest steer-consumed signal. */
117
+ /** The shared runtime event bus — the Fleet panel subscribes to its 'communication' domain for the honest steer-consumed signal. */
118
118
  readonly runtimeBus: RuntimeServices['runtimeBus'];
119
119
  };
120
120
  }
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // workstream-services.ts — Wave 4 (wo703)
2
+ // workstream-services.ts — phase/work-item orchestration engine
3
3
  //
4
4
  // Constructs the TUI's OrchestrationEngine instance (@pellux/goodvibes-sdk/
5
5
  // platform/orchestration, landed on SDK main as wo701/W4.1) and a thin
@@ -19,7 +19,7 @@
19
19
  * additionally exposes the recovered set for inspection.
20
20
  *
21
21
  * A chain that looked interrupted (non-terminal) in the old snapshot can be
22
- * reaped to terminal IN PLACE by `WrfcController.importChain` (Wave 6, wo-F:
22
+ * reaped to terminal IN PLACE by `WrfcController.importChain` (the zombie-reap pass:
23
23
  * no member agent survived the restart — see wrfc-controller.ts). This
24
24
  * module always re-checks `chain.state` AFTER the import call, never the
25
25
  * pre-import classification, so a reaped chain is treated as history, not
@@ -209,7 +209,7 @@ class WrfcPersistenceImpl implements WrfcPersistence {
209
209
  // chain. The accessor is optional for read-only test doubles.
210
210
  //
211
211
  // NOTE: importChain may reap this chain to a terminal state IN PLACE
212
- // (Wave 6, wo-F zombie-reap: no member agent survived the restart) —
212
+ // (zombie reap: no member agent survived the restart) —
213
213
  // always read chain.state AFTER this call below, never the pre-import
214
214
  // classification captured by candidateInterrupted.
215
215
  this.controller.importChain?.(chain);