@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
@@ -1,134 +1,35 @@
1
- export const GLYPHS = {
2
- frame: {
3
- topLeft: '┌',
4
- topRight: '┐',
5
- bottomLeft: '└',
6
- bottomRight: '┘',
7
- horizontal: '─',
8
- vertical: '│',
9
- teeLeft: '├',
10
- teeRight: '┤',
11
- teeTop: '┬',
12
- teeBottom: '┴',
13
- cross: '┼',
14
- },
15
- surface: {
16
- top: '▄',
17
- bottom: '▀',
18
- cursor: '█',
19
- altCursor: '▌',
20
- },
21
- navigation: {
22
- selected: '▸',
23
- collapsed: '▸',
24
- expanded: '▾',
25
- up: '',
26
- down: '↓',
27
- moreAbove: '▲',
28
- moreBelow: '▼',
29
- next: '→',
30
- back: '←',
31
- pipeSeparator: '│',
32
- },
33
- status: {
34
- success: '✓',
35
- failure: '✕',
36
- pending: '•',
37
- active: '●',
38
- idle: '◌',
39
- info: '○',
40
- warn: '⚠',
41
- blocked: '⊘',
42
- skipped: '◇',
43
- review: '◈',
44
- retry: '↻',
45
- handoff: '⇢',
46
- reference: '↗',
47
- partial: '◐',
48
- dualPane: '◆',
49
- star: '★',
50
- },
51
- meter: {
52
- filled: '█',
53
- medium: '▓',
54
- light: '▒',
55
- empty: '░',
56
- spark: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'],
57
- },
58
- } as const;
1
+ // ---------------------------------------------------------------------------
2
+ // ui-primitives.ts — glyph registry + tone-token table.
3
+ //
4
+ // W6-P1: these four tables (GLYPHS, UI_TONES, DIFF_TONES, SPINNER_FRAMES) are
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
8
+ // share ONE source (Mike's move-to-SDK ruling — machinery needed by 2+
9
+ // surfaces => SDK). The TUI was the reference these values were lifted from
10
+ // verbatim, so this swap is byte-identical. See
11
+ // docs/decisions/2026-07-05-presentation-contract-sdk-extraction.md in the SDK.
12
+ //
13
+ // Re-exported under the historical names (GLYPHS, UI_TONES) so every existing
14
+ // importer keeps working with no call-site churn. UI_TONES is the dark-mode
15
+ // tone table (== resolveTones('dark')); light is resolved via theme.ts's
16
+ // activeUiTones() / resolveUiTones(), which composes the mode dimension over
17
+ // this dark constant unchanged.
18
+ // ---------------------------------------------------------------------------
19
+
20
+ import {
21
+ GLYPHS,
22
+ TONE_TOKENS,
23
+ DIFF_TONES,
24
+ SPINNER_FRAMES,
25
+ } from '@pellux/goodvibes-sdk/platform/presentation';
26
+
27
+ export { GLYPHS, DIFF_TONES, SPINNER_FRAMES };
59
28
 
60
29
  /**
61
30
  * UI_TONES — the single chrome/color-token source for src/renderer and
62
31
  * src/panels. Concrete (dark-mode) values; resolveUiTones(mode) in theme.ts
63
- * is the single mode-resolved read path — this object is the 'dark' entry.
64
- */
65
- export const UI_TONES = {
66
- fg: {
67
- primary: '#e2e8f0',
68
- secondary: '#cbd5e1',
69
- muted: '#94a3b8',
70
- dim: '#475569',
71
- inverse: '#0f172a',
72
- /** Empty-state / placeholder foreground (formerly DEFAULT_PANEL_PALETTE.empty literal). */
73
- empty: '#334155',
74
- },
75
- bg: {
76
- base: '#11131a',
77
- surface: '#161a22',
78
- title: '#0f172a',
79
- section: '#18202b',
80
- summary: '#1b2430',
81
- selected: '#223049',
82
- input: '#1e293b',
83
- warning: '#2b2116',
84
- error: '#2a161b',
85
- success: '#14241b',
86
- /** Fullscreen/shell footer background. */
87
- footer: '#111827',
88
- },
89
- state: {
90
- info: '#38bdf8',
91
- good: '#22c55e',
92
- warn: '#f59e0b',
93
- bad: '#ef4444',
94
- blocked: '#f97316',
95
- active: '#60a5fa',
96
- /** Single canonical reasoning/thinking purple (replaces #9945FF and ad-hoc purples). */
97
- reasoning: '#a855f7',
98
- },
99
- accent: {
100
- browser: '#7dd3fc',
101
- control: '#22d3ee',
102
- inspector: '#c4b5fd',
103
- workflow: '#fbbf24',
104
- conversation: '#93c5fd',
105
- /** Neon brand accent — header/splash/thinking gradient only. */
106
- brand: '#00ffff',
107
- gradientStart: '#00ffff',
108
- gradientEnd: '#d000ff',
109
- },
110
- /** Canonical border stroke color for fullscreen/panel chrome. */
111
- border: '#64748b',
112
- } as const;
113
-
114
- /**
115
- * Diff surface accent color shared across the three diff-rendering surfaces —
116
- * the conversation diff view (diff-view.ts), the file diff panel
117
- * (diff-panel.ts), and the git panel's inline diff (git-panel.ts). Hunk-header
118
- * blue has no matching UI_TONES.state/accent role, so WO-204 gives it one
119
- * named token here (value converged on diff-panel.ts's pre-existing hunk
120
- * color, the only one of the three surfaces that predates this token; add/del
121
- * on all three surfaces already converge on UI_TONES.state.good/bad).
32
+ * is the single mode-resolved read path — this object is the 'dark' entry
33
+ * (== resolveTones('dark') from the SDK presentation contract).
122
34
  */
123
- export const DIFF_TONES = {
124
- // add/del carry diff-panel.ts's shipped colors: the diff panel is the only
125
- // diff surface users have ever seen (diff-view.ts was unwired from v0.9.6
126
- // until WO-204), so its look is the reference — the conversation surface
127
- // adopts it, never the other way around.
128
- add: '#00ff88',
129
- del: '#ff4444',
130
- hunk: '#88aaff',
131
- } as const;
132
-
133
- /** Single spinner-frame source — layout.ts and progress.ts both re-export this. */
134
- export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] as const;
35
+ export const UI_TONES = TONE_TOKENS;
@@ -123,6 +123,8 @@ export type CreateBootstrapCommandContextOptions = {
123
123
  hydrateSessionUsage?: () => void;
124
124
  workstreamEngine?: import('./workstream-services.ts').WorkstreamCommandService;
125
125
  codeIndexStore?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexStore;
126
+ codeIndexReindexScheduler?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexReindexScheduler;
127
+ isPassiveCodeInjectionFlagEnabled?: () => boolean;
126
128
  getMainSessionTurnInjections?: () => readonly import('../renderer/turn-injection.ts').TurnInjectionEntry[];
127
129
  };
128
130
 
@@ -184,6 +186,8 @@ export function createBootstrapCommandContext(
184
186
  hydrateSessionUsage,
185
187
  workstreamEngine,
186
188
  codeIndexStore,
189
+ codeIndexReindexScheduler,
190
+ isPassiveCodeInjectionFlagEnabled,
187
191
  getMainSessionTurnInjections,
188
192
  planManager,
189
193
  adaptivePlanner,
@@ -244,6 +248,8 @@ export function createBootstrapCommandContext(
244
248
  hydrateSessionUsage,
245
249
  workstreamEngine,
246
250
  codeIndexStore,
251
+ codeIndexReindexScheduler,
252
+ isPassiveCodeInjectionFlagEnabled,
247
253
  getMainSessionTurnInjections,
248
254
  });
249
255
  const provider = createBootstrapCommandProviderSection({
@@ -5,7 +5,7 @@ import type { ConversationManager } from '../core/conversation';
5
5
  import type { KnowledgeApi } from '@pellux/goodvibes-sdk/platform/knowledge';
6
6
  import type { HookApi } from '@pellux/goodvibes-sdk/platform/hooks';
7
7
  import type { McpApi } from '@pellux/goodvibes-sdk/platform/mcp';
8
- import type { PanelManager } from '../panels/panel-manager.ts';
8
+ import type { PanelManager, PanelDeepLinkTarget } from '../panels/panel-manager.ts';
9
9
  import type { ProviderApi } from '@pellux/goodvibes-sdk/platform/providers';
10
10
  import type { OpsApi } from '@/runtime/index.ts';
11
11
  import type { MutableRuntimeState } from '@/runtime/index.ts';
@@ -118,6 +118,8 @@ export interface BootstrapCommandSectionOptions {
118
118
  readonly hydrateSessionUsage?: () => void;
119
119
  readonly workstreamEngine?: import('./workstream-services.ts').WorkstreamCommandService;
120
120
  readonly codeIndexStore?: CodeIndexStore;
121
+ readonly codeIndexReindexScheduler?: import('@pellux/goodvibes-sdk/platform/state').CodeIndexReindexScheduler;
122
+ readonly isPassiveCodeInjectionFlagEnabled?: () => boolean;
121
123
  readonly getMainSessionTurnInjections?: () => readonly import('../renderer/turn-injection.ts').TurnInjectionEntry[];
122
124
  readonly agentManager?: ShellAgentManagerService;
123
125
  readonly modeManager?: ShellModeManagerService;
@@ -190,7 +192,7 @@ export function createBootstrapCommandActions(
190
192
  localUserAuthManager,
191
193
  } = options;
192
194
 
193
- const showPanel = (panelId: string, pane?: 'top' | 'bottom') => {
195
+ const showPanel = (panelId: string, pane?: 'top' | 'bottom', target?: PanelDeepLinkTarget) => {
194
196
  // W6.1 (the purge): a MIGRATE-TO-MODAL id resolves to a modal, not a panel.
195
197
  // panelManager.open() fires the injected openModal callback and returns a
196
198
  // no-op sentinel — so skip panelManager.show() (which would reveal an empty
@@ -198,7 +200,7 @@ export function createBootstrapCommandActions(
198
200
  // showPanel-based front-door (openHooksPanel/openSecurityPanel/… and the
199
201
  // migrated command runtimes) opening the modal cleanly.
200
202
  const redirected = panelManager.getModalRedirect(panelId) !== undefined;
201
- panelManager.open(panelId, pane);
203
+ panelManager.open(panelId, pane, target);
202
204
  if (!redirected) panelManager.show();
203
205
  requestRender();
204
206
  };
@@ -321,7 +323,7 @@ export function createBootstrapCommandActions(
321
323
  export function createBootstrapCommandSessionSection(
322
324
  options: Pick<
323
325
  BootstrapCommandSectionOptions,
324
- 'conversation' | 'runtime' | 'sessionManager' | 'sessionMemoryStore' | 'sessionLineageTracker' | 'wrfcController' | 'changeTracker' | 'hydrateSessionUsage' | 'workstreamEngine' | 'codeIndexStore' | 'getMainSessionTurnInjections'
326
+ 'conversation' | 'runtime' | 'sessionManager' | 'sessionMemoryStore' | 'sessionLineageTracker' | 'wrfcController' | 'changeTracker' | 'hydrateSessionUsage' | 'workstreamEngine' | 'codeIndexStore' | 'codeIndexReindexScheduler' | 'isPassiveCodeInjectionFlagEnabled' | 'getMainSessionTurnInjections'
325
327
  >,
326
328
  ): BootstrapCommandSessionSection {
327
329
  return {
@@ -335,6 +337,8 @@ export function createBootstrapCommandSessionSection(
335
337
  hydrateSessionUsage: options.hydrateSessionUsage,
336
338
  workstreamEngine: options.workstreamEngine,
337
339
  codeIndexStore: options.codeIndexStore,
340
+ codeIndexReindexScheduler: options.codeIndexReindexScheduler,
341
+ isPassiveCodeInjectionFlagEnabled: options.isPassiveCodeInjectionFlagEnabled,
338
342
  getMainSessionTurnInjections: options.getMainSessionTurnInjections,
339
343
  };
340
344
  }
@@ -27,12 +27,15 @@ import { loadBootstrapSystemPrompt, syncConfiguredServices } from '@/runtime/ind
27
27
  import { registerBootstrapHookBridge } from '@/runtime/index.ts';
28
28
  import { registerBootstrapRuntimeEvents } from '@/runtime/index.ts';
29
29
  import { createRuntimeServices, type RuntimeServices } from './services.ts';
30
+ import { runBootMemoryFold } from './memory-fold.ts';
30
31
  import { setPricingSource } from '../export/cost-utils.ts';
31
32
  import { createUiRuntimeServices, type UiRuntimeServices } from './ui-services.ts';
32
33
  import { join } from 'node:path';
33
34
  import { installWrfcAgentToolGuard } from '../tools/wrfc-agent-guard.ts';
34
35
  import { createWrfcPersistence, type WrfcPersistence } from './wrfc-persistence.ts';
35
36
  import type { SystemMessagePriority } from '../core/system-message-router.ts';
37
+ import { SessionSpineClient, SessionUnionCache, TUI_SPINE_PARTICIPANT } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
38
+ import { SessionInboundInputPoller, createBootstrapInboundInputPoller } from './session-inbound-inputs.ts';
36
39
 
37
40
  // ---------------------------------------------------------------------------
38
41
  // Pre-router buffer
@@ -114,6 +117,12 @@ export interface BootstrapCoreState {
114
117
  readonly requestRender: () => void;
115
118
  readonly setRenderRequest: (fn: () => void) => void;
116
119
  readonly runtimeSessionIdRef: { value: string };
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
+ readonly sessionSpine: SessionSpineClient;
122
+ /** D3: inbound steer/follow-up delivery; dormant until bootstrap.ts activates it. */
123
+ readonly sessionInboundInputs: SessionInboundInputPoller;
124
+ /** Cache-backed read facade; bootstrap.ts drives its mode (embedded/external/local-only) from the same HostServiceMode as the spine above. */
125
+ readonly sessionUnionCache: SessionUnionCache;
117
126
  /**
118
127
  * WRFC chain persistence — call `rehydrate()` once after the SystemMessageRouter
119
128
  * is wired so interrupted chains from a previous process are surfaced to the operator.
@@ -127,17 +136,8 @@ export type CompanionMessagePayload = Extract<SessionEvent, { type: 'COMPANION_M
127
136
  // Operator narration of inbound channel events
128
137
  // ---------------------------------------------------------------------------
129
138
 
130
- /**
131
- * Narrate an inbound channel event to the operator via the SystemMessageRouter.
132
- *
133
- * When an external surface (GitHub, Slack, ntfy, etc.) triggers an agent turn,
134
- * this function produces a human-readable system message so the operator can
135
- * observe which event caused the turn. Returns null for internal/companion
136
- * sources that do not need operator narration.
137
- *
138
- * @param event - The normalized inbound event descriptor.
139
- * @returns A narration string, or null if no narration is appropriate.
140
- */
139
+ /** Narrate an inbound channel event (GitHub, Slack, ntfy, etc.) that triggered an
140
+ * agent turn, via the SystemMessageRouter null for internal/companion sources. */
141
141
  export function narrateInboundEvent(event: {
142
142
  source: string;
143
143
  metadata: Readonly<Record<string, unknown>> | undefined;
@@ -293,6 +293,12 @@ export async function initializeBootstrapCore(
293
293
  surfaceRegistry,
294
294
  watcherRegistry,
295
295
  } = services;
296
+ // Permanently dormant for embedded/local-only (nothing to mirror to); bootstrap.ts activates it only for an adopted 'external' daemon.
297
+ const sessionSpine = new SessionSpineClient({ participant: TUI_SPINE_PARTICIPANT, recordKind: 'tui' });
298
+ // Cache-backed read facade over the local broker (passthrough until bootstrap.ts marks it
299
+ // embedded, or activates the adopted-daemon wire union for 'external').
300
+ // selfSessionIds keeps local authoritative for our own wire-mirrored session even when local/wire ids diverge (D-TUI-1).
301
+ const sessionUnionCache = new SessionUnionCache({ local: sharedSessionBroker, selfSessionIds: () => sessionSpine.mirroredSessionIds });
296
302
 
297
303
  routeBindings.attachRuntime({ runtimeBus, runtimeStore: store });
298
304
  surfaceRegistry.attachRuntime(store);
@@ -324,6 +330,7 @@ export async function initializeBootstrapCore(
324
330
  const uiServices = createUiRuntimeServices(services, {
325
331
  forensicsRegistry,
326
332
  getControlPlaneRecentEvents,
333
+ sessionUnionCache,
327
334
  });
328
335
 
329
336
  const conversation = new ConversationManager(() => {
@@ -401,24 +408,23 @@ export async function initializeBootstrapCore(
401
408
  void memoryStore.save();
402
409
  memoryStore.close();
403
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.
412
+ await runBootMemoryFold(memoryStore, services.memoryEmbeddingRegistry, workingDir, logger);
404
413
 
405
414
  const renderRequestRef = { value: (): void => {} };
406
- // R1: Coalescing render scheduler — collapses N requestRender() calls into 1
407
- // and enforces a 16ms minimum interval to cap repaints at ~60fps.
408
- //
409
- // renderScheduled stays set for the ENTIRE window (until run() executes), so
410
- // requestRender() calls that arrive on later event-loop ticks within the same
411
- // 16ms window are coalesced into the one already-pending tail render instead
412
- // of each queuing their own setTimeout. (The streaming hot path drives its own
413
- // direct repaints and does not flow through this scheduler.)
415
+ // R1: Coalescing render scheduler — collapses N requestRender() calls into 1 and
416
+ // enforces a 16ms minimum interval to cap repaints at ~60fps. renderScheduled stays
417
+ // set for the ENTIRE window (until run() executes), so requestRender() calls arriving
418
+ // on later event-loop ticks within the same 16ms window coalesce into the one
419
+ // already-pending tail render instead of each queuing their own setTimeout. (The
420
+ // streaming hot path drives its own direct repaints and skips this scheduler.)
414
421
  let renderScheduled = false;
415
422
  let lastRenderTime = 0;
416
423
  const RENDER_INTERVAL_MS = 16;
417
- // run() performs the actual render. It clears renderScheduled FIRST — even if
418
- // the render callback throws — otherwise a single render exception would wedge
419
- // the entire TUI (no future requestRender() call would schedule anything). The
420
- // renderer is expected to surface failures via its own error path; we log at
421
- // error so the next requestRender() can still reschedule.
424
+ // run() performs the actual render. It clears renderScheduled FIRST — even if the
425
+ // render callback throws — otherwise a single render exception would wedge the entire
426
+ // TUI (no future requestRender() would schedule anything); we log at error so the
427
+ // next requestRender() can still reschedule.
422
428
  const run = (): void => {
423
429
  renderScheduled = false;
424
430
  lastRenderTime = Date.now();
@@ -427,6 +433,10 @@ export async function initializeBootstrapCore(
427
433
  } catch (err) {
428
434
  logger.error('Render threw; next requestRender will reschedule', { error: String(err) });
429
435
  }
436
+ // Debounced spine heartbeat on turn/render activity (no-op while dormant, i.e.
437
+ // embedded/local-only): a cheap synchronous no-op unless its own internal window has
438
+ // elapsed (at most one wire call per heartbeatMinIntervalMs) — safe on the hot path.
439
+ sessionSpine.heartbeat(runtimeSessionIdRef.value);
430
440
  };
431
441
  const requestRender = (): void => {
432
442
  if (renderScheduled) return;
@@ -634,6 +644,11 @@ export async function initializeBootstrapCore(
634
644
  },
635
645
  ));
636
646
 
647
+ // D3: inbound steer delivery — see createBootstrapInboundInputPoller's doc comment.
648
+ const sessionInboundInputs = createBootstrapInboundInputPoller({
649
+ runtimeSessionIdRef, routeOrBuffer, orchestratorHandleUserInputRef, conversation, requestRender,
650
+ });
651
+
637
652
  providerRegistry.startWatching(runtimeBus);
638
653
 
639
654
  // W2.3: attach the SAME WebhookNotifier instance that `/notify add|remove|clear`
@@ -724,6 +739,10 @@ export async function initializeBootstrapCore(
724
739
  lastSeenAt: Date.now(),
725
740
  },
726
741
  }).catch((err) => { logger.debug('session broker create session failed at bootstrap', { err }); });
742
+ // Fire-and-forget spine mirror — a permanent parallel-write posture alongside (never
743
+ // instead of) the still-authoritative local SharedSessionBroker above. Dormant/queued
744
+ // until bootstrap.ts's deferred task activates it for an adopted 'external' daemon.
745
+ sessionSpine.register({ sessionId: runtime.sessionId, project: services.workingDirectory, title: 'Terminal UI session' });
727
746
 
728
747
  domainDispatch.syncSessionState({
729
748
  id: userSessionId,
@@ -774,5 +793,8 @@ export async function initializeBootstrapCore(
774
793
  },
775
794
  runtimeSessionIdRef,
776
795
  wrfcPersistence,
796
+ sessionSpine,
797
+ sessionInboundInputs,
798
+ sessionUnionCache,
777
799
  };
778
800
  }
@@ -9,6 +9,7 @@ import { HelperModel } from '@pellux/goodvibes-sdk/platform/config';
9
9
  import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
10
10
  import { formatReturnContextForDisplay, getReturnContextMode, maybeAssistReturnContextSummary } from '@/runtime/index.ts';
11
11
  import type { SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane';
12
+ import type { SessionSpineClient } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
12
13
  import type { SessionManager } from '@pellux/goodvibes-sdk/platform/sessions';
13
14
  import type { PanelManager } from '../panels/panel-manager.ts';
14
15
  import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
@@ -22,6 +23,10 @@ export interface ResumeSessionOptions {
22
23
  readonly requestRender: () => void;
23
24
  readonly onSessionIdChanged?: (sessionId: string) => void;
24
25
  readonly sharedSessionBroker: Pick<SharedSessionBroker, 'reopenSession'>;
26
+ /** S3c: fire-and-forget daemon-spine mirror. Reopen (not register) is the
27
+ * ONLY resume-time verb — see the SDK session-spine client.ts header doc. */
28
+ readonly sessionSpine: Pick<SessionSpineClient, 'reopen'>;
29
+ readonly project: string;
25
30
  readonly writeLastSessionPointer: (sessionId: string) => void;
26
31
  readonly hookDispatcher: HookDispatcher;
27
32
  readonly sessionManager: SessionManager;
@@ -75,6 +80,8 @@ export function createResumeSessionHandler(options: ResumeSessionOptions): (sess
75
80
  if (meta?.provider) options.runtime.provider = meta.provider;
76
81
  options.writeLastSessionPointer(sessionId);
77
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).
84
+ options.sessionSpine.reopen({ sessionId, project: options.project, title: options.conversation.title || meta.title });
78
85
  options.conversation.log(`Resumed session: ${sessionId}`, { fg: '135' });
79
86
  const reopenedPanels: string[] = [];
80
87
  if (meta.returnContext?.openPanels?.length) {
@@ -32,6 +32,7 @@ import type { PolicyRuntimeState } from '@/runtime/index.ts';
32
32
  import type { TaskManager } from '@/runtime/index.ts';
33
33
  import type { UiRuntimeServices } from './ui-services.ts';
34
34
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
35
+ import type { SessionSpineClient } from '@pellux/goodvibes-sdk/platform/runtime/session-spine';
35
36
 
36
37
  export interface BootstrapShellState {
37
38
  readonly commandRegistry: CommandRegistry;
@@ -47,6 +48,8 @@ export interface BootstrapShellOptions {
47
48
  readonly runtimeBus: RuntimeEventBus;
48
49
  readonly runtimeStore: RuntimeStore;
49
50
  readonly services: RuntimeServices;
51
+ /** S3c: dormant until bootstrap.ts activates it for an adopted 'external' daemon. */
52
+ readonly sessionSpine: SessionSpineClient;
50
53
  readonly conversation: ConversationManager;
51
54
  readonly runtime: MutableRuntimeState;
52
55
  readonly orchestrator: Orchestrator;
@@ -70,6 +73,7 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
70
73
  runtimeBus,
71
74
  runtimeStore,
72
75
  services,
76
+ sessionSpine,
73
77
  conversation,
74
78
  runtime,
75
79
  orchestrator,
@@ -103,6 +107,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
103
107
  requestRender,
104
108
  onSessionIdChanged,
105
109
  sharedSessionBroker: services.sessionBroker,
110
+ sessionSpine,
111
+ project: services.workingDirectory,
106
112
  writeLastSessionPointer,
107
113
  hookDispatcher: services.hookDispatcher,
108
114
  sessionManager: services.sessionManager,
@@ -175,7 +181,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
175
181
  forensicsRegistry,
176
182
  policyRuntimeState,
177
183
  approvalBroker: services.approvalBroker,
178
- sessionBroker: services.sessionBroker,
184
+ // S3d: panels read the cross-surface union facade, not the raw local broker.
185
+ sessionBroker: uiServices.sessions.sessionBroker,
179
186
  automationManager: services.automationManager,
180
187
  getControlPlaneRecentEvents,
181
188
  tokenAuditor: services.tokenAuditor,
@@ -207,6 +214,15 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
207
214
  if (kind === 'operational') return ui.operationalMessages;
208
215
  return ui.systemMessages;
209
216
  },
217
+ {
218
+ // Suppress stale WRFC replay re-notifications for chains that can no
219
+ // longer act — gone (killed/removed → getChain null) or terminal
220
+ // (passed/failed). (UX-B item 1c.)
221
+ isChainTerminal: (chainId) => {
222
+ const chain = services.wrfcController.getChain(chainId);
223
+ return chain === null || chain.state === 'passed' || chain.state === 'failed';
224
+ },
225
+ },
210
226
  );
211
227
  orchestrator.setSystemMessageRouter(systemMessageRouter);
212
228
 
@@ -271,6 +287,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
271
287
  wrfcController: services.wrfcController,
272
288
  workstreamEngine: services.workstreamCommands,
273
289
  codeIndexStore: services.codeIndexStore,
290
+ codeIndexReindexScheduler: services.codeIndexReindexScheduler,
291
+ isPassiveCodeInjectionFlagEnabled: () => services.featureFlags.isEnabled('agent-passive-code-injection'),
274
292
  // Wave 5 (wo805): expose the MAIN session's per-turn passive-injection ring
275
293
  // so `/recall injections` (no agent id) renders it — see recall-review.ts.
276
294
  getMainSessionTurnInjections: () => orchestrator.getTurnInjections(),