@pellux/goodvibes-tui 1.1.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  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/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -19,16 +19,15 @@ import type { SelectionResult, SelectionAction } from './selection-modal.ts';
19
19
  import { SearchManager } from './search.ts';
20
20
  import { InputHistory, HistorySearch } from './input-history.ts';
21
21
  import type { BlockMeta, ConversationManager } from '../core/conversation';
22
- import { ProcessModal } from '../renderer/process-modal.ts';
23
- import { LiveTailModal } from '../renderer/live-tail-modal.ts';
24
22
  import { BlockActionsMenu } from '../renderer/block-actions.ts';
25
- import { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
26
23
  import { ContextInspectorModal } from '../renderer/context-inspector.ts';
27
24
  import { BookmarkModal } from './bookmark-modal.ts';
28
25
  import { SettingsModal } from './settings-modal.ts';
29
26
  import { McpWorkspace } from './mcp-workspace.ts';
30
27
  import { SessionPickerModal } from './session-picker-modal.ts';
28
+ import { ConfigModal } from './config-modal.ts';
31
29
  import { ProfilePickerModal } from './profile-picker-modal.ts';
30
+ import type { PanelBurstGuardState } from './panel-paste-flood-guard.ts';
32
31
  import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
33
32
  import {
34
33
  applyOnboardingRequest,
@@ -106,9 +105,7 @@ import {
106
105
  handleBlockActionsToken,
107
106
  handleEscapeOnlyModalToken,
108
107
  handleFilePickerToken,
109
- handleLiveTailToken,
110
108
  handleModelPickerToken,
111
- handleProcessModalToken,
112
109
  } from './handler-picker-routes.ts';
113
110
  import { handleGlobalShortcutToken } from './handler-shortcuts.ts';
114
111
  import { feedInputTokens } from './handler-feed.ts';
@@ -184,13 +181,13 @@ export class InputHandler implements InputHandlerLike {
184
181
  public modelPicker: ModelPickerModal;
185
182
  public selectionModal = new SelectionModal();
186
183
  public searchManager = new SearchManager();
187
- public processModal: ProcessModal;
188
- public liveTailModal: LiveTailModal;
189
- public agentDetailModal: AgentDetailModal;
190
184
  public contextInspectorModal = new ContextInspectorModal();
191
185
  public bookmarkModal: BookmarkModal;
192
186
  public blockActionsMenu = new BlockActionsMenu();
193
187
  public settingsModal = new SettingsModal();
188
+ public configModal = new ConfigModal();
189
+ /** DEBT-5 item 5 — paste-flood guard state, mutated in place across tokens (never reallocated). */
190
+ private panelBurstGuard: PanelBurstGuardState = { timestamps: [], suspended: false, hintShown: false };
194
191
  public mcpWorkspace = new McpWorkspace();
195
192
  public onboardingWizard = new OnboardingWizardController();
196
193
  public onboardingModelPickerCancelSnapshot: OnboardingWizardSnapshot | null = null;
@@ -272,25 +269,10 @@ export class InputHandler implements InputHandlerLike {
272
269
  uiServices.providers.benchmarkStore,
273
270
  uiServices.providers.providerRegistry,
274
271
  );
275
- this.processModal = new ProcessModal({
276
- agentManager: uiServices.agents.agentManager,
277
- processManager: uiServices.shell.processManager,
278
- wrfcController: uiServices.agents.wrfcController,
279
- });
280
- this.liveTailModal = new LiveTailModal({
281
- agentManager: uiServices.agents.agentManager,
282
- processManager: uiServices.shell.processManager,
283
- });
284
- this.agentDetailModal = new AgentDetailModal({
285
- agentManager: uiServices.agents.agentManager,
286
- agentMessageBus: uiServices.agents.agentMessageBus,
287
- sessionLogPathResolver: (agentId) => uiServices.environment.shellPaths.resolveProjectPath('tui', 'sessions', `${agentId}.jsonl`),
288
- // SDK 0.23.0: supply wrfcController so the modal can show constraint data
289
- wrfcController: uiServices.agents.wrfcController,
290
- cancelAgent: (agentId: string) => uiServices.agents.agentManager.cancel(agentId),
291
- });
272
+ // W6.1 retirement: ProcessModal/LiveTailModal/AgentDetailModal were removed
273
+ // — F2 now opens the Fleet panel, which subsumes the live process tree.
292
274
  this.bookmarkModal = new BookmarkModal(uiServices.shell.bookmarkManager);
293
- this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager);
275
+ this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager, uiServices.sessions.sessionBroker);
294
276
  this.profilePickerModal = new ProfilePickerModal(uiServices.shell.profileManager);
295
277
  this.initFeedContext();
296
278
  }
@@ -322,6 +304,7 @@ export class InputHandler implements InputHandlerLike {
322
304
  mcpWorkspace: this.mcpWorkspace,
323
305
  sessionPickerModal: this.sessionPickerModal,
324
306
  profilePickerModal: this.profilePickerModal,
307
+ configModal: this.configModal,
325
308
  historySearch: this.historySearch,
326
309
  commandRegistry: this.commandRegistry,
327
310
  commandContext: this.commandContext,
@@ -329,9 +312,6 @@ export class InputHandler implements InputHandlerLike {
329
312
  filePicker: this.filePicker,
330
313
  modelPicker: this.modelPicker,
331
314
  onboardingWizard: this.onboardingWizard,
332
- processModal: this.processModal,
333
- liveTailModal: this.liveTailModal,
334
- agentDetailModal: this.agentDetailModal,
335
315
  contextInspectorModal: this.contextInspectorModal,
336
316
  blockActionsMenu: this.blockActionsMenu,
337
317
  searchManager: this.searchManager,
@@ -341,6 +321,8 @@ export class InputHandler implements InputHandlerLike {
341
321
  panelManager: this.uiServices.shell.panelManager,
342
322
  keybindingsManager: this.uiServices.shell.keybindingsManager,
343
323
  killRing: this.killRing,
324
+ focusTracker: this.uiServices.platform.focusTracker,
325
+ panelBurstGuard: this.panelBurstGuard,
344
326
  getHistory: this.getHistory,
345
327
  getViewportHeight: this.getViewportHeight,
346
328
  getScrollTop: this.getScrollTop,
@@ -421,6 +403,7 @@ export class InputHandler implements InputHandlerLike {
421
403
  preSelectId?: string;
422
404
  allowSearch?: boolean;
423
405
  customActions?: Map<string, SelectionAction>;
406
+ primaryVerbLabel?: string;
424
407
  } | undefined,
425
408
  callback: SelectionModalCallback,
426
409
  ): void {
@@ -88,7 +88,7 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
88
88
  'panel-tab-7': 'Jump to workspace panel tab 7',
89
89
  'panel-tab-8': 'Jump to workspace panel tab 8',
90
90
  'panel-tab-9': 'Jump to workspace panel tab 9',
91
- 'panel-ops': 'Open the Ops Control panel',
91
+ 'panel-ops': 'Open, focus, or close the Fleet panel (toggle)',
92
92
  'panel-focus-toggle': 'Switch keyboard focus between top and bottom pane',
93
93
  'history-search': 'Reverse input history search',
94
94
  'search': 'Toggle conversation search',
@@ -119,8 +119,14 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
119
119
  'panel-picker': [{ key: 'p', ctrl: true }],
120
120
  'panel-close': [{ key: 'x', ctrl: true }],
121
121
  'panel-close-all': [{ key: 'x', ctrl: true, shift: true }],
122
- 'panel-tab-next': [{ key: ']', ctrl: true }],
123
- 'panel-tab-prev': [{ key: '[', ctrl: true }],
122
+ // Ctrl+] stays the primary next-tab chord; Ctrl+PageDown is added as a second
123
+ // binding. Ctrl+[ was REMOVED as prev-tab: it is byte 0x1B (ESC), so the
124
+ // legacy tokenizer path emits it as 'escape' — the binding never matched on
125
+ // most terminals AND the key fired Escape (a split-brain chord). Ctrl+PageUp
126
+ // replaces it: Ctrl+PageUp/PageDown tokenize consistently (\x1b[5;5~ /
127
+ // \x1b[6;5~ -> pageup/pagedown ctrl:true) in both the legacy and CSI-u paths.
128
+ 'panel-tab-next': [{ key: ']', ctrl: true }, { key: 'pagedown', ctrl: true }],
129
+ 'panel-tab-prev': [{ key: 'pageup', ctrl: true }],
124
130
  // Alt+1..9: jump directly to the Nth workspace panel tab (across both panes).
125
131
  // The tokenizer delivers Alt as the token's `meta` modifier; comboMatches /
126
132
  // lookup treat `meta` as an alias for `alt`, so these alt-combos route through
@@ -134,10 +140,11 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
134
140
  'panel-tab-7': [{ key: '7', alt: true }],
135
141
  'panel-tab-8': [{ key: '8', alt: true }],
136
142
  'panel-tab-9': [{ key: '9', alt: true }],
137
- // Ctrl+O: open the Ops Control panel (operator intervention console).
138
- // Routed globally in handleGlobalShortcutToken: prefers commandContext.openOpsPanel()
139
- // when the operator-control-plane feature flag wired it, else falls back to opening
140
- // the always-registered 'ops-control' panel type directly via the panel manager.
143
+ // Ctrl+O: TOGGLE the Fleet panel (open+focus / bring-to-front+focus / close
144
+ // see toggleFleetPanel in handler-shortcuts.ts, UX-C). The former Ops
145
+ // Control panel was retired to an 'ops-control' -> 'fleet' alias (W6.1); the
146
+ // binding is KEPT (repointed, not removed) so the Ctrl+O muscle memory still
147
+ // lands somewhere useful. Routed globally in handleGlobalShortcutToken.
141
148
  'panel-ops': [{ key: 'o', ctrl: true }],
142
149
  // Ctrl+G: toggle keyboard focus between the top and bottom panes. Ctrl+G is
143
150
  // otherwise unbound in the default table.
@@ -355,7 +362,15 @@ export class KeybindingsManager {
355
362
  if (combo.ctrl) parts.push('Ctrl');
356
363
  if (combo.alt) parts.push('Alt');
357
364
  if (combo.shift) parts.push('Shift');
358
- parts.push(combo.key.length === 1 ? combo.key.toUpperCase() : combo.key);
365
+ // Friendly display for named keys with a conventional abbreviation, so a
366
+ // chord like Ctrl+PageUp reads consistently with the "PageUp / PageDn"
367
+ // scroll help text. Single chars upper-case; other named keys pass through.
368
+ const KEY_DISPLAY: Record<string, string> = { pageup: 'PageUp', pagedown: 'PageDn' };
369
+ parts.push(
370
+ combo.key.length === 1
371
+ ? combo.key.toUpperCase()
372
+ : KEY_DISPLAY[combo.key] ?? combo.key,
373
+ );
359
374
  return parts.join('+');
360
375
  }
361
376
 
@@ -1,15 +1,17 @@
1
1
  import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
2
2
 
3
- export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap';
3
+ export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap' | 'embeddingProvider';
4
4
 
5
5
  /**
6
6
  * Which config keys the model picker writes to on commit.
7
- * 'main' -> provider.provider + provider.model (default)
8
- * 'helper' -> helper.globalProvider + helper.globalModel (+ helper.enabled: true)
9
- * 'tool' -> tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
10
- * 'tts' -> tts.llmProvider + tts.llmModel
7
+ * 'main' -> provider.provider + provider.model (default)
8
+ * 'helper' -> helper.globalProvider + helper.globalModel (+ helper.enabled: true)
9
+ * 'tool' -> tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
10
+ * 'tts' -> tts.llmProvider + tts.llmModel
11
+ * 'embeddings' -> provider.embeddingProvider, via MemoryEmbeddingProviderRegistry.setDefaultProvider()
12
+ * (not an LLM route — no model concept, see ModelPickerTargetInfo.configuredNote)
11
13
  */
12
- export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts';
14
+ export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts' | 'embeddings';
13
15
 
14
16
  export type ModelPickerFocusPane = 'targets' | 'items';
15
17
 
@@ -21,6 +23,22 @@ export interface ModelPickerTargetInfo {
21
23
  readonly model: string;
22
24
  readonly enabled: boolean;
23
25
  readonly inherited: boolean;
26
+ /**
27
+ * Honest override for the "Current:" summary line. Used by the 'embeddings'
28
+ * target (which has no model concept — only a provider id + dimensions +
29
+ * configured state) so the renderer never prints a phantom "model:" value.
30
+ * When set, this replaces the computed provider:model route text.
31
+ */
32
+ readonly configuredNote?: string;
33
+ }
34
+
35
+ /** One entry in the embedding-provider picker list (PickerMode 'embeddingProvider'). */
36
+ export interface EmbeddingProviderPickerEntry {
37
+ readonly id: string;
38
+ readonly label: string;
39
+ readonly dimensions: number;
40
+ readonly configured: boolean;
41
+ readonly detail?: string;
24
42
  }
25
43
 
26
44
  /**
@@ -7,6 +7,7 @@ import type {
7
7
  BenchmarkSort,
8
8
  CapabilityFilter,
9
9
  CategoryFilter,
10
+ EmbeddingProviderPickerEntry,
10
11
  FilteredModelsCache,
11
12
  FilteredProvidersCache,
12
13
  GroupByMode,
@@ -40,6 +41,7 @@ export type {
40
41
  BenchmarkSort,
41
42
  CapabilityFilter,
42
43
  CategoryFilter,
44
+ EmbeddingProviderPickerEntry,
43
45
  GroupByMode,
44
46
  ModelFamily,
45
47
  ModelPickerFocusPane,
@@ -86,6 +88,10 @@ export class ModelPickerModal {
86
88
  public models: ModelDefinition[] = [];
87
89
  public providers: string[] = [];
88
90
  public effortLevels: string[] = [];
91
+ /** Entries for PickerMode 'embeddingProvider' — the 'embeddings' target's own tiny item list. */
92
+ public embeddingProviders: EmbeddingProviderPickerEntry[] = [];
93
+ /** Mode to restore when navigating the targets rail away from 'embeddings'. */
94
+ private nonEmbeddingMode: PickerMode = 'model';
89
95
  /** The model chosen in model-mode, awaiting effort selection. */
90
96
  public pendingModel: ModelDefinition | null = null;
91
97
  /** The model awaiting context cap input (contextCap mode). */
@@ -148,9 +154,20 @@ export class ModelPickerModal {
148
154
  }
149
155
 
150
156
  setTarget(target: ModelPickerTarget): void {
157
+ const previousTarget = this.target;
151
158
  this.target = target;
152
159
  const idx = this.targetInfos.findIndex((entry) => entry.target === target);
153
160
  this.targetIndex = idx >= 0 ? idx : this.targetIndex;
161
+ // The 'embeddings' target routes through its own tiny item-list mode
162
+ // (embedding providers are not ModelDefinition-shaped) rather than
163
+ // whatever LLM mode the picker session was already in. Remember the
164
+ // mode we came from so tabbing back to another target restores it.
165
+ if (target === 'embeddings' && this.mode !== 'embeddingProvider') {
166
+ this.nonEmbeddingMode = this.mode;
167
+ this.mode = 'embeddingProvider';
168
+ } else if (previousTarget === 'embeddings' && target !== 'embeddings' && this.mode === 'embeddingProvider') {
169
+ this.mode = this.nonEmbeddingMode;
170
+ }
154
171
  this.alignSelectionToTarget();
155
172
  }
156
173
 
@@ -171,6 +188,13 @@ export class ModelPickerModal {
171
188
  this.selectedIndex = modelIdx >= 0 ? modelIdx : 0;
172
189
  this.scrollOffset = 0;
173
190
  this._scrollToSelection(20);
191
+ return;
192
+ }
193
+ if (this.mode === 'embeddingProvider') {
194
+ const providerIdx = this.embeddingProviders.findIndex((provider) => provider.id === info.provider);
195
+ this.selectedIndex = providerIdx >= 0 ? providerIdx : 0;
196
+ this.scrollOffset = 0;
197
+ this._scrollToSelection(20);
174
198
  }
175
199
  }
176
200
 
@@ -237,7 +261,22 @@ export class ModelPickerModal {
237
261
  }
238
262
  }
239
263
 
240
- /** Open showing all models — entry point for /model */
264
+ /**
265
+ * Open showing all models — entry point for /model.
266
+ *
267
+ * W3-T2: search starts FOCUSED. Live tmux repro (cc-w3-t2) showed the actual
268
+ * friction wasn't the filter itself (it already works — fuzzy multi-word
269
+ * substring over id/displayName/provider) but that with search unfocused by
270
+ * default, typing a search term went character-by-character into single-key
271
+ * shortcuts instead (g=group, c=capability, a=available-only, b=benchmark) —
272
+ * e.g. typing "claude" silently cycled the capability filter (on 'c') and
273
+ * toggled available-only off (on 'a') while the visible list never
274
+ * filtered, with no error and no indication anything happened. Reaching
275
+ * search required already knowing to press '/' or Up at the top row.
276
+ * Starting focused makes "just start typing" work the way every other
277
+ * picker in this codebase (and everywhere else) already behaves; Down
278
+ * still blurs search into list navigation (see handleModelPickerToken).
279
+ */
241
280
  openAllModels(models: ModelDefinition[], currentModelId: string): void {
242
281
  this.models = models;
243
282
  this.mode = 'model';
@@ -245,6 +284,7 @@ export class ModelPickerModal {
245
284
  this.pendingModel = null;
246
285
  this.focusPane = 'items';
247
286
  this.searchFocused = false;
287
+ this.focusSearch();
248
288
  this.query = '';
249
289
  this.categoryFilter = 'all';
250
290
  this.capabilityFilter = 'none';
@@ -255,7 +295,7 @@ export class ModelPickerModal {
255
295
  this.scrollOffset = 0;
256
296
  }
257
297
 
258
- /** Open showing providers first — entry point for /provider */
298
+ /** Open showing providers first — entry point for /provider. W3-T2: search starts focused — see openAllModels(). */
259
299
  openProviders(providers: string[], currentProvider: string): void {
260
300
  this.previousMode = null;
261
301
  this.providers = providers;
@@ -264,6 +304,7 @@ export class ModelPickerModal {
264
304
  this.pendingModel = null;
265
305
  this.focusPane = 'items';
266
306
  this.searchFocused = false;
307
+ this.focusSearch();
267
308
  this.query = '';
268
309
  this.categoryFilter = 'all';
269
310
  this.capabilityFilter = 'none';
@@ -273,6 +314,27 @@ export class ModelPickerModal {
273
314
  this.scrollOffset = 0;
274
315
  }
275
316
 
317
+ /**
318
+ * Open the embedding-provider list — entry point for the 'embeddings' target.
319
+ * Deliberately NOT routed through openAllModels (ModelDefinition-shaped) or
320
+ * openProviders (LLM-provider label resolution) — embedding providers are a
321
+ * flat {id, label, dimensions, configured} list with no model concept, and
322
+ * reusing either existing opener would mislabel or drop unrecognized ids.
323
+ */
324
+ openEmbeddingProviders(providers: EmbeddingProviderPickerEntry[], currentId: string): void {
325
+ this.previousMode = null;
326
+ this.embeddingProviders = providers;
327
+ this.mode = 'embeddingProvider';
328
+ this.active = true;
329
+ this.pendingModel = null;
330
+ this.focusPane = 'items';
331
+ this.searchFocused = false;
332
+ this.query = '';
333
+ const idx = providers.findIndex((provider) => provider.id === currentId);
334
+ this.selectedIndex = idx >= 0 ? idx : 0;
335
+ this.scrollOffset = 0;
336
+ }
337
+
276
338
  /** Transition to model list filtered by provider (called from provider mode Enter). */
277
339
  showModelsForProvider(models: ModelDefinition[], _provider: string): void {
278
340
  this.previousMode = 'provider';
@@ -311,6 +373,8 @@ export class ModelPickerModal {
311
373
  this.targetIndex = 0;
312
374
  this.models = [];
313
375
  this.providers = [];
376
+ this.embeddingProviders = [];
377
+ this.nonEmbeddingMode = 'model';
314
378
  this.pendingModel = null;
315
379
  this.contextCapPendingModel = null;
316
380
  this.contextCapQuery = '';
@@ -473,6 +537,16 @@ export class ModelPickerModal {
473
537
  this.providerItemsCache = cache;
474
538
  return result;
475
539
  }
540
+ if (this.mode === 'embeddingProvider') {
541
+ // Tiny list (typically 2-4 entries) — no caching infrastructure needed.
542
+ // Unconfigured providers are shown honestly (isConfigured: false), never hidden.
543
+ return this.embeddingProviders.map((provider) => ({
544
+ id: provider.id,
545
+ label: provider.label,
546
+ detail: `${provider.dimensions}d${provider.configured ? '' : ' · unconfigured'}`,
547
+ isConfigured: provider.configured,
548
+ }));
549
+ }
476
550
  // effort mode
477
551
  return buildEffortItems(this.effortLevels);
478
552
  }
@@ -481,6 +555,7 @@ export class ModelPickerModal {
481
555
  getItemCount(): number {
482
556
  if (this.mode === 'model') return this.getFilteredModels().length;
483
557
  if (this.mode === 'provider') return this.getFilteredProviders().length;
558
+ if (this.mode === 'embeddingProvider') return this.embeddingProviders.length;
484
559
  return this.effortLevels.length;
485
560
  }
486
561
 
@@ -27,6 +27,13 @@ export function runtimePortDiagnostic(
27
27
  if (status.mode === 'unavailable') {
28
28
  return `The configured endpoint ${status.baseUrl} is unavailable after startup or restart.${reason}`;
29
29
  }
30
+ if (status.mode === 'incompatible') {
31
+ // A GoodVibes daemon IS on the port, but it speaks an incompatible wire
32
+ // version, so this TUI neither adopted it nor started a competing daemon.
33
+ // status.reason already names the port and both versions (SDK side).
34
+ const version = status.version ? ` version ${status.version}` : '';
35
+ return `A GoodVibes service${version} is running at ${status.baseUrl} but is not wire-compatible with this TUI, so it was not adopted.${reason}`;
36
+ }
30
37
  if (status.mode === 'external') {
31
38
  const version = status.version ? ` version ${status.version}` : '';
32
39
  return `An existing GoodVibes service was verified at ${status.baseUrl}${version}.`;
@@ -62,7 +69,9 @@ export function isRuntimeEndpointOccupyingConfiguredPort(
62
69
  endpoint: OnboardingRuntimeEndpoint,
63
70
  ): boolean {
64
71
  const status = getRuntimeEndpointStatus(state, endpoint);
65
- if (status) return status.mode === 'embedded' || status.mode === 'external' || status.mode === 'blocked';
72
+ // 'incompatible' means a GoodVibes daemon holds the configured port (we just
73
+ // did not adopt it) — the port IS occupied, so report it as such.
74
+ if (status) return status.mode === 'embedded' || status.mode === 'external' || status.mode === 'blocked' || status.mode === 'incompatible';
66
75
  return endpoint === 'daemon'
67
76
  ? state?.daemonRunning === true || state?.daemonPortInUse === true
68
77
  : state?.httpListenerRunning === true || state?.httpListenerPortInUse === true;
@@ -85,7 +85,14 @@ export function buildOnboardingApplyRequest(controller: OnboardingWizardControll
85
85
  setConfig('service.enabled', hasServers);
86
86
  setConfig('service.autostart', hasServers);
87
87
  setConfig('service.restartOnFailure', true);
88
- setConfig('danger.daemon', hasServers);
88
+ // One-Platform daemon-by-default (SDK docs/decisions/2026-07-05-daemon-by-default.md):
89
+ // the loopback-only cross-surface session daemon is safe-by-design (Wave 1:
90
+ // loopback bind, auth-gated, rate-limited) and runs regardless of whether
91
+ // browser/LAN/webhook/external-app capabilities are selected here — it is no
92
+ // longer bundled with those network-exposing surfaces, so onboarding leaves
93
+ // daemon.enabled untouched and lets the SDK's own default-true (or an existing
94
+ // explicit user override) govern. (The deprecated danger.daemon alias this
95
+ // comment used to also name was removed in Wave 6.)
89
96
  setConfig('controlPlane.enabled', hasServers);
90
97
  setConfig('danger.httpListener', httpListener);
91
98
  setConfig('web.enabled', browserAccess);
@@ -18,7 +18,10 @@ export const DEFAULT_CAPABILITIES: readonly OnboardingStep1CapabilityItem[] = [
18
18
  id: 'local-tui-only',
19
19
  label: 'Local TUI Only (No Servers)',
20
20
  selected: true,
21
- detail: 'Use GoodVibes only in this terminal. No browser access, background service, HTTP listener, external app surface, or network setup.',
21
+ // Honest wording (One-Platform daemon-by-default): this avoids browser/LAN/
22
+ // webhook/external-app exposure, but the loopback-only GoodVibes daemon
23
+ // still runs by default for cross-surface visibility on this machine.
24
+ detail: 'Use GoodVibes only in this terminal. No browser access, HTTP listener, external app surface, or network setup. A loopback-only background daemon still runs by default so sessions here stay visible to other GoodVibes surfaces on this machine — turn it off in Settings > daemon if you want none at all.',
22
25
  },
23
26
  {
24
27
  id: 'browser-access',
@@ -0,0 +1,136 @@
1
+ /**
2
+ * F1 (One-Platform Wave 2 friction fix): the Network step's daemon-source choice
3
+ * — start a new daemon owned by this TUI (default) vs connect to one that is
4
+ * already running elsewhere with a known token. Extracted from
5
+ * onboarding-wizard-steps.ts to keep that file under the architecture line cap.
6
+ */
7
+ import { legacyUnitNote, MANAGED_SERVICE_NAME } from '../../runtime/legacy-daemon-migration.ts';
8
+ import { normalizeText } from './onboarding-wizard-helpers.ts';
9
+ import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
10
+ import type { OnboardingWizardFieldDefinition } from './onboarding-wizard-types.ts';
11
+
12
+ export const DAEMON_SOURCE_FIELD_ID = 'network.daemon-source';
13
+
14
+ /** Current daemon-source selection ('start' default, or 'adopt'). */
15
+ export function getDaemonSource(controller: OnboardingWizardControllerLike): string {
16
+ return controller.getStringFieldValue(DAEMON_SOURCE_FIELD_ID, 'start');
17
+ }
18
+
19
+ /**
20
+ * Push the daemon-source radio, plus the host/port/token/connect fields when
21
+ * 'adopt' is selected, onto the Network step's field list. Independent of the
22
+ * local/custom network mode — adopting someone else's daemon is a client-side
23
+ * choice about which daemon THIS TUI talks to, not about how THIS TUI's own
24
+ * daemon binds to the network.
25
+ */
26
+ export function pushDaemonAdoptionFields(
27
+ fields: OnboardingWizardFieldDefinition[],
28
+ controller: OnboardingWizardControllerLike,
29
+ daemonSource: string,
30
+ bindSettings: { readonly controlPlane: { readonly host?: string | undefined; readonly port?: number | undefined } } | undefined,
31
+ ): void {
32
+ fields.push({
33
+ kind: 'radio',
34
+ id: DAEMON_SOURCE_FIELD_ID,
35
+ label: 'GoodVibes daemon source',
36
+ hint: 'Start a new daemon owned by this TUI (default), or connect to one that is already running elsewhere with a known token.',
37
+ options: [
38
+ { id: 'start', label: 'Start a new daemon', hint: 'This TUI starts and owns its own daemon (default).' },
39
+ { id: 'adopt', label: 'Connect to an existing running daemon', hint: 'Point this TUI at a daemon someone already started, using its host, port, and token.' },
40
+ ],
41
+ defaultValue: 'start',
42
+ });
43
+ if (daemonSource !== 'adopt') return;
44
+ fields.push(
45
+ {
46
+ kind: 'text',
47
+ id: 'network.adopt-daemon-host',
48
+ label: 'Existing daemon host',
49
+ hint: 'Host or IP address of the already-running GoodVibes daemon.',
50
+ placeholder: '127.0.0.1',
51
+ defaultValue: controller.getStringFieldValue('network.adopt-daemon-host', normalizeText(bindSettings?.controlPlane.host) || '127.0.0.1'),
52
+ },
53
+ {
54
+ kind: 'text',
55
+ id: 'network.adopt-daemon-port',
56
+ label: 'Existing daemon port',
57
+ hint: 'Port the already-running GoodVibes daemon is bound to.',
58
+ placeholder: '3421',
59
+ defaultValue: controller.getStringFieldValue('network.adopt-daemon-port', String(bindSettings?.controlPlane.port ?? 3421)),
60
+ },
61
+ {
62
+ kind: 'masked',
63
+ id: 'network.adopt-daemon-token',
64
+ label: 'Existing daemon token',
65
+ hint: "Bearer token that daemon was started with. Connecting writes it into this home directory's operator-tokens.json so this TUI authenticates as that daemon instead of minting its own.",
66
+ placeholder: 'gv_...',
67
+ defaultValue: controller.getStringFieldValue('network.adopt-daemon-token', ''),
68
+ },
69
+ {
70
+ kind: 'action',
71
+ id: 'network.adopt-daemon-connect',
72
+ label: 'Connect to this daemon now',
73
+ hint: 'Apply the host/port above, install the token, and verify the connection immediately (before Apply at the end of the wizard). Result prints to the feed below.',
74
+ action: 'connect-existing-daemon',
75
+ defaultValue: 'Connect',
76
+ },
77
+ );
78
+ }
79
+
80
+ /**
81
+ * W4-D1: the guided, visible entry point for migrating a detected legacy
82
+ * `goodvibes-daemon.service` unit — closes the fast-follow-up flagged when
83
+ * `handleMigrateLegacyDaemonServiceForHandler` (handler-onboarding-daemon-adopt.ts)
84
+ * shipped fully built but not wired to any onboarding control. Independent of
85
+ * the daemon-source radio above: this offers to retire an old unmanaged unit
86
+ * regardless of whether the wizard is set to start a new daemon or adopt an
87
+ * existing one.
88
+ *
89
+ * Visible ONLY when the runtime snapshot's read-only `legacyDaemon` detection
90
+ * (collected via `detectLegacyUnit`, see `../../runtime/legacy-daemon-migration.ts`)
91
+ * found a unit file present — never rendered speculatively. Confirm-gated on
92
+ * top of the engine's own consent requirement: a checklist toggle must be
93
+ * checked before the action executes the real migration (`confirmMigration:
94
+ * true`); left unchecked, pressing the action still works but only prints the
95
+ * engine's dry-run plan (`confirmMigration: false`) — see
96
+ * `runLegacyDaemonMigration`'s honest dry-run branch. The action label and
97
+ * hint change to make which mode is armed unambiguous before it is pressed.
98
+ */
99
+ export function pushLegacyDaemonMigrationFields(
100
+ fields: OnboardingWizardFieldDefinition[],
101
+ controller: OnboardingWizardControllerLike,
102
+ ): void {
103
+ const legacy = controller.runtimeSnapshot?.legacyDaemon;
104
+ if (!legacy || !legacy.present) return;
105
+
106
+ fields.push({
107
+ kind: 'status',
108
+ id: 'network.migrate-legacy-daemon-detected',
109
+ label: 'Legacy daemon service detected',
110
+ // F2 follow-up: name the unit this host actually resolves (carried on the
111
+ // snapshot from `resolveConfiguredServiceName` at collection time) — the
112
+ // MANAGED_SERVICE_NAME fallback only covers snapshots built without it.
113
+ hint: legacyUnitNote(legacy, legacy.trackedServiceName ?? MANAGED_SERVICE_NAME),
114
+ defaultValue: legacy.active ? 'Installed and running' : 'Installed (not active)',
115
+ });
116
+
117
+ const confirmFieldId = 'network.migrate-legacy-daemon-confirm';
118
+ const confirmed = controller.getBooleanFieldValue(confirmFieldId, false);
119
+ fields.push({
120
+ kind: 'checklist',
121
+ id: confirmFieldId,
122
+ label: 'Confirm migration execution',
123
+ hint: 'When checked, the action below performs the real migration: install and start the new managed service, verify it comes up healthy, and only then stop, disable, and remove the legacy unit. Leave unchecked to preview the plan without changing anything.',
124
+ defaultValue: false,
125
+ });
126
+ fields.push({
127
+ kind: 'action',
128
+ id: 'network.migrate-legacy-daemon',
129
+ label: confirmed ? 'Migrate legacy daemon service now' : 'Preview migration plan',
130
+ hint: confirmed
131
+ ? 'Executes the migration now: new-up-then-old-down, never touching the legacy unit until the new one is verified healthy. Result prints to the feed below.'
132
+ : 'Prints the migration plan without changing anything. Check "Confirm migration execution" above to arm the real migration.',
133
+ action: 'migrate-legacy-daemon-service',
134
+ defaultValue: confirmed ? 'Migrate' : 'Preview',
135
+ });
136
+ }
@@ -9,6 +9,7 @@ import {
9
9
  type ExternalSurfaceSpec,
10
10
  } from './onboarding-wizard-external-surfaces.ts';
11
11
  import { countSelected, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
12
+ import { getDaemonSource, pushDaemonAdoptionFields, pushLegacyDaemonMigrationFields } from './onboarding-wizard-network-adopt.ts';
12
13
  import type { OnboardingWizardControllerLike } from './onboarding-wizard-types.ts';
13
14
  import type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardActionFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardExternalSurfaceStepId, OnboardingWizardFieldDefinition, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardStepDefinition } from './onboarding-wizard-types.ts';
14
15
 
@@ -123,7 +124,7 @@ export function buildCapabilitiesStep(controller: OnboardingWizardControllerLike
123
124
  id: 'capabilities.clear',
124
125
  action: 'clear-capabilities',
125
126
  label: 'Use Local TUI Only (No Servers)',
126
- hint: 'Clear all server-backed capabilities and keep GoodVibes in this terminal only.',
127
+ hint: 'Clear all server-backed capabilities: no browser access, LAN reachability, webhooks/events, or external app surfaces. The loopback-only background daemon still runs by default.',
127
128
  defaultValue: 'Action',
128
129
  },
129
130
  ];
@@ -518,11 +519,7 @@ export function buildNetworkStep(controller: OnboardingWizardControllerLike): On
518
519
  const listenerEnabled = controller.shouldExposeHttpListenerNetworkFields();
519
520
  const listenerWillApply = controller.shouldEnableHttpListener();
520
521
  const controlPlaneRemote = controller.shouldExposeControlPlaneNetwork();
521
- const networkEnabled = {
522
- controlPlane: controlPlaneRemote,
523
- httpListener: listenerEnabled,
524
- web: browserEnabled,
525
- };
522
+ const networkEnabled = { controlPlane: controlPlaneRemote, httpListener: listenerEnabled, web: browserEnabled };
526
523
  const mode = controller.getStringFieldValue('network.mode', controller.runtimeDerived.step1_5NetworkMode);
527
524
  const custom = mode === 'custom';
528
525
  const fields: OnboardingWizardFieldDefinition[] = [
@@ -536,6 +533,11 @@ export function buildNetworkStep(controller: OnboardingWizardControllerLike): On
536
533
  },
537
534
  ];
538
535
 
536
+ // F1: recognize an adoptable already-running daemon (see onboarding-wizard-network-adopt.ts).
537
+ const daemonSource = getDaemonSource(controller);
538
+ pushDaemonAdoptionFields(fields, controller, daemonSource, bindSettings);
539
+ pushLegacyDaemonMigrationFields(fields, controller);
540
+
539
541
  if (custom) {
540
542
  const sharedIpField: OnboardingWizardChecklistFieldDefinition = {
541
543
  kind: 'checklist',
@@ -635,6 +637,7 @@ export function buildNetworkStep(controller: OnboardingWizardControllerLike): On
635
637
  summaryTitle: 'Bind posture',
636
638
  summaryLines: [
637
639
  `Mode: ${custom ? 'custom' : 'local network default'}`,
640
+ `Daemon source: ${daemonSource === 'adopt' ? 'connect to an existing running daemon' : 'start a new daemon'}`,
638
641
  `Browser surface: ${browserEnabled ? 'enabled' : 'not selected'}`,
639
642
  `HTTP listener: ${listenerWillApply ? 'enabled' : listenerEnabled ? 'available for selected external apps' : 'not selected'}`,
640
643
  ],
@@ -53,7 +53,9 @@ export type OnboardingWizardAction =
53
53
  | 'cloudflare-verify'
54
54
  | 'cloudflare-disable'
55
55
  | 'start-openai-subscription'
56
- | 'finish-openai-subscription';
56
+ | 'finish-openai-subscription'
57
+ | 'connect-existing-daemon'
58
+ | 'migrate-legacy-daemon-service';
57
59
 
58
60
  export type OnboardingWizardApplyFeedbackSeverity = 'info' | 'warning' | 'error';
59
61