@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
@@ -1,6 +1,7 @@
1
1
  import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
2
2
  import type { SelectionResult, SelectionAction } from './selection-modal.ts';
3
3
  import type { CommandContext } from './command-registry.ts';
4
+ import type { ConfigModal } from './config-modal.ts';
4
5
  import { openTtsProviderPicker, openTtsVoicePicker } from './tts-settings-actions.ts';
5
6
  import { isTextBackspace } from './delete-key-policy.ts';
6
7
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
@@ -93,16 +94,26 @@ export function handleSelectionModalToken(state: SelectionRouteState, token: Inp
93
94
  if (selected) {
94
95
  dispatchSelectionAction(action, selected);
95
96
  }
97
+ } else if (state.selectionModal.allowSearch) {
98
+ // UX-C instant filter: an unclaimed keystroke arms search AND starts
99
+ // the query immediately, instead of silently doing nothing until the
100
+ // user discovers '/' first (the "help search needs '/' arming while
101
+ // the palette filters instantly" evaluator finding — /help has no
102
+ // customActions, so every letter used to be swallowed here). '/'
103
+ // above still works too; this is additive, not a replacement, and
104
+ // never fires for a claimed hotkey letter (checked first, above).
105
+ state.selectionModal.focusSearch();
106
+ state.selectionModal.setQuery(token.value);
96
107
  }
97
108
  }
98
109
  } else if (token.type === 'key') {
99
110
  if (token.logicalName === 'escape') {
100
- if (state.selectionModal.allowSearch && state.selectionModal.searchFocused) {
101
- if (state.selectionModal.query.length > 0) state.selectionModal.setQuery('');
102
- else state.selectionModal.blurSearch();
103
- state.requestRender();
104
- return true;
105
- }
111
+ // UX-C: ONE Escape always closes the modal, regardless of search state.
112
+ // Clearing an in-progress query is Backspace's job, not Esc's — the old
113
+ // two-stage contract (1st Esc clears query, 2nd blurs search, 3rd
114
+ // finally closes) was the "help modal took 3 Escapes after searching"
115
+ // evaluator finding (searchFocused is not modal-specific — every
116
+ // SelectionModal-based picker had the same multi-Esc pattern).
106
117
  state.handleEscape();
107
118
  return true;
108
119
  }
@@ -533,3 +544,122 @@ export function handleProfilePickerToken(state: ProfilePickerRouteState, token:
533
544
  state.requestRender();
534
545
  return true;
535
546
  }
547
+
548
+ type ConfigModalRouteState = {
549
+ configModal: ConfigModal;
550
+ commandContext?: CommandContext;
551
+ requestRender: () => void;
552
+ handleEscape: () => void;
553
+ };
554
+
555
+ /**
556
+ * Route a key to the generic config-modal host (W6.1 MIGRATE-TO-MODAL
557
+ * surfaces). The host owns the reserved navigation keys (Esc, up/down/j/k tab
558
+ * left/right, and now '/' — DEBT-5 item 1's type-to-filter, armed the same
559
+ * way as scrollable-list-panel.ts's opt-in filter and SettingsModal's own
560
+ * '/'-armed search); every other key is offered to the active surface's
561
+ * declarative action table (fireAction handles the two-press confirm for
562
+ * destructive actions). Any unrecognised key is absorbed so the modal stays
563
+ * modal — this is the same "active modal captures all keys" shape as the
564
+ * other modal routers.
565
+ *
566
+ * While the filter is armed (`configModal.isFilterActive()`), printable text
567
+ * tokens go to the query instead of nav/action dispatch — this is WHY 'j'/'k'
568
+ * lose their vim-nav meaning mid-filter (you're typing, not navigating) but
569
+ * Escape, Backspace, and the arrow/tab nav keys keep their ordinary meaning
570
+ * (arrows navigate the FILTERED list; Enter still acts on the selected row).
571
+ */
572
+ export function handleConfigModalToken(state: ConfigModalRouteState, token: InputToken): boolean {
573
+ if (!state.configModal.active) return false;
574
+
575
+ // Every token that reaches the modal is a user interaction — after the
576
+ // first one, structure freezes to interaction boundaries (liveness rule).
577
+ // Before it, renders may sync structure so async onOpen loads appear
578
+ // without a keypress (batch refutation finding 3).
579
+ state.configModal.noteInteraction();
580
+
581
+ const filtering = state.configModal.isFilterActive();
582
+
583
+ if (token.type === 'key') {
584
+ if (token.logicalName === 'escape') {
585
+ // Two-stage Esc, but ONLY while there is a query to clear (the one
586
+ // documented exception to single-Esc-close): clearFilterOrFallthrough
587
+ // returns false on an empty query, so an armed-but-empty filter (or no
588
+ // filter at all) closes the modal in one press, same as every other
589
+ // modal here.
590
+ if (state.configModal.clearFilterOrFallthrough()) {
591
+ state.requestRender();
592
+ return true;
593
+ }
594
+ state.handleEscape();
595
+ return true;
596
+ }
597
+ if (filtering && isTextBackspace(token.logicalName ?? '')) {
598
+ state.configModal.backspaceFilter();
599
+ state.requestRender();
600
+ return true;
601
+ }
602
+ if (!filtering && token.logicalName === '/') {
603
+ state.configModal.activateFilter();
604
+ state.requestRender();
605
+ return true;
606
+ }
607
+ switch (token.logicalName) {
608
+ case 'up':
609
+ state.configModal.moveUp();
610
+ state.requestRender();
611
+ return true;
612
+ case 'down':
613
+ state.configModal.moveDown();
614
+ state.requestRender();
615
+ return true;
616
+ case 'left':
617
+ state.configModal.prevTab();
618
+ state.requestRender();
619
+ return true;
620
+ case 'right':
621
+ case 'tab':
622
+ state.configModal.nextTab();
623
+ state.requestRender();
624
+ return true;
625
+ }
626
+ // Any other 'key' token (e.g. 'enter') falls through to the action table
627
+ // below even while filtering — only printable TEXT tokens are captured
628
+ // by the filter (below), so Enter still fires on the current selection.
629
+ } else if (token.type === 'text') {
630
+ if (!filtering && token.value === '/') {
631
+ state.configModal.activateFilter();
632
+ state.requestRender();
633
+ return true;
634
+ }
635
+ if (filtering) {
636
+ // The WHOLE token value lands in the filter in one call — including a
637
+ // multi-char paste token — never split into per-char nav/action
638
+ // dispatch (the text-capture invariant this item's brief calls out).
639
+ state.configModal.appendFilterText(token.value);
640
+ state.requestRender();
641
+ return true;
642
+ }
643
+ if (token.value === 'j') { state.configModal.moveDown(); state.requestRender(); return true; }
644
+ if (token.value === 'k') { state.configModal.moveUp(); state.requestRender(); return true; }
645
+ }
646
+
647
+ const actionKey = token.type === 'key' ? (token.logicalName ?? '') : token.type === 'text' ? token.value : '';
648
+ const submitInput = state.commandContext?.submitInput;
649
+ const fired = actionKey.length > 0 && state.configModal.fireAction(actionKey, {
650
+ print: (message: string) => state.commandContext?.print(message),
651
+ executeCommand: state.commandContext?.executeCommand,
652
+ openModal: state.commandContext?.openModal,
653
+ ...(submitInput ? { submitInput: (text: string) => submitInput(text) } : {}),
654
+ });
655
+ if (fired) {
656
+ state.requestRender();
657
+ return true;
658
+ }
659
+
660
+ // Unhandled key while a config modal is open: drop any pending confirm and
661
+ // absorb the key (the modal owns the keyboard while it is active).
662
+ state.configModal.clearConfirmOnMiss();
663
+ state.requestRender();
664
+ return true;
665
+ }
@@ -40,12 +40,12 @@ export type EscapeState = ModalStackState & {
40
40
  selectionCallback: ((result: SelectionResult | null) => void) | null;
41
41
  bookmarkModal: ModalStackState['bookmarkModal'] & { open: () => void };
42
42
  contextInspectorModal: ModalStackState['contextInspectorModal'] & { open: () => void };
43
- processModal: ModalStackState['processModal'] & { open: () => void };
44
43
  settingsModal: ModalStackState['settingsModal'] & {
45
44
  editingMode: boolean;
46
45
  cancelEdit: () => void;
47
46
  };
48
47
  mcpWorkspace?: ModalStackState['mcpWorkspace'];
48
+ configModal: ModalStackState['configModal'] & { reopen: () => void };
49
49
  selectionModal: ModalStackState['selectionModal'];
50
50
  autocompleteReset: () => void;
51
51
  autocompleteUpdate?: (query: string) => void;
@@ -120,14 +120,12 @@ export function handleEscape(state: EscapeState): {
120
120
  shortcutsScrollOffset = 0;
121
121
  },
122
122
  closeBookmark: () => state.bookmarkModal.close(),
123
- closeAgentDetail: () => state.agentDetailModal.close(),
124
- closeLiveTail: () => state.liveTailModal.close(),
125
123
  closeSettings: () => state.settingsModal.close(),
126
124
  closeMcpWorkspace: () => state.mcpWorkspace?.close(),
127
125
  closeSessionPicker: () => state.sessionPickerModal.close(),
128
126
  closeProfilePicker: () => state.profilePickerModal.close(),
127
+ closeConfigModal: () => state.configModal.close(),
129
128
  closeContextInspector: () => state.contextInspectorModal.close(),
130
- closeProcess: () => state.processModal.close(),
131
129
  closeModelPicker: () => {
132
130
  state.modelPicker.close();
133
131
  state.restoreOnboardingModelPickerCancelState?.();
@@ -150,8 +148,17 @@ export function handleEscape(state: EscapeState): {
150
148
  if (state.modalStack[i] === 'command') state.modalStack.splice(i, 1);
151
149
  }
152
150
  state.autocompleteReset();
153
- prompt = '';
154
- cursorPos = 0;
151
+ // Esc's palette-dismiss convention: a bare '/' with nothing typed
152
+ // after it IS the palette, so Esc clears it along with the slash
153
+ // (first Esc, one step, done — no lingering '/' to backspace out).
154
+ // Once the user has typed a command name (or anything else) past
155
+ // the slash, Esc's job is only to dismiss the ghost-suggestion
156
+ // overlay; the typed text is real composer content and stays put,
157
+ // just no longer treated as an in-progress command.
158
+ if (prompt === '/') {
159
+ prompt = '';
160
+ cursorPos = 0;
161
+ }
155
162
  },
156
163
  });
157
164
  };
@@ -161,8 +168,8 @@ export function handleEscape(state: EscapeState): {
161
168
  openHelp: () => { helpOverlayActive = true; },
162
169
  openShortcuts: () => { shortcutsOverlayActive = true; },
163
170
  openBookmark: () => state.bookmarkModal.open(),
164
- openProcess: () => state.processModal.open(),
165
171
  openContextInspector: () => state.contextInspectorModal.open(),
172
+ openConfigModal: () => state.configModal.reopen(),
166
173
  openMcpWorkspace: () => state.mcpWorkspace?.reopen(),
167
174
  openOnboarding: () => state.onboardingWizard?.reopen(),
168
175
  openCommandMode: () => {
@@ -5,13 +5,11 @@ import type { BookmarkModal } from './bookmark-modal.ts';
5
5
  import type { SettingsModal } from './settings-modal.ts';
6
6
  import type { SessionPickerModal } from './session-picker-modal.ts';
7
7
  import type { ProfilePickerModal } from './profile-picker-modal.ts';
8
+ import type { ConfigModal } from './config-modal.ts';
8
9
  import type { HistorySearch } from './input-history.ts';
9
10
  import type { ModelPickerModal } from './model-picker.ts';
10
11
  import { handleMcpWorkspaceToken, type McpWorkspace } from './mcp-workspace.ts';
11
12
  import type { CommandContext } from './command-registry.ts';
12
- import type { LiveTailModal } from '../renderer/live-tail-modal.ts';
13
- import type { ProcessModal } from '../renderer/process-modal.ts';
14
- import type { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
15
13
  import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
16
14
  import type { FilePickerModal } from './file-picker.ts';
17
15
  import type { BlockActionsMenu, BlockActionId } from '../renderer/block-actions.ts';
@@ -21,6 +19,7 @@ import { handleHistorySearchToken, handleOverlayToken, handleSearchModeToken } f
21
19
  import { handleOnboardingWizardToken } from './onboarding/handler-onboarding-routes.ts';
22
20
  import {
23
21
  handleBookmarkModalToken,
22
+ handleConfigModalToken,
24
23
  handleProfilePickerToken,
25
24
  handleSelectionModalToken,
26
25
  handleSessionPickerToken,
@@ -30,9 +29,7 @@ import {
30
29
  handleBlockActionsToken,
31
30
  handleEscapeOnlyModalToken,
32
31
  handleFilePickerToken,
33
- handleLiveTailToken,
34
32
  handleModelPickerToken,
35
- handleProcessModalToken,
36
33
  } from './handler-picker-routes.ts';
37
34
 
38
35
  export type ModalTokenRouteState = {
@@ -47,6 +44,7 @@ export type ModalTokenRouteState = {
47
44
  mcpWorkspace: McpWorkspace;
48
45
  sessionPickerModal: SessionPickerModal;
49
46
  profilePickerModal: ProfilePickerModal;
47
+ configModal: ConfigModal;
50
48
  onboardingWizard: OnboardingWizardController;
51
49
  helpOverlayActive: boolean;
52
50
  helpScrollOffset: number;
@@ -61,9 +59,6 @@ export type ModalTokenRouteState = {
61
59
  getViewportHeight: () => number;
62
60
  requestRender: () => void;
63
61
  handleEscape: () => void;
64
- liveTailModal: LiveTailModal;
65
- processModal: ProcessModal;
66
- agentDetailModal: AgentDetailModal;
67
62
  contextInspectorModal: ContextInspectorModal;
68
63
  modalOpened: (name: string) => void;
69
64
  filePicker: FilePickerModal;
@@ -177,6 +172,15 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
177
172
  return withState(state, true);
178
173
  }
179
174
 
175
+ if (handleConfigModalToken({
176
+ configModal: state.configModal,
177
+ commandContext: state.commandContext,
178
+ requestRender: state.requestRender,
179
+ handleEscape: state.handleEscape,
180
+ }, token)) {
181
+ return withState(state, true);
182
+ }
183
+
180
184
  const overlayState = {
181
185
  helpOverlayActive: state.helpOverlayActive,
182
186
  helpScrollOffset: state.helpScrollOffset,
@@ -223,36 +227,10 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
223
227
  return withState(state, true);
224
228
  }
225
229
 
226
- if (handleLiveTailToken({
227
- liveTailModal: state.liveTailModal,
228
- processModal: state.processModal,
229
- requestRender: state.requestRender,
230
- handleEscape: state.handleEscape,
231
- }, token)) {
232
- return withState(state, true);
233
- }
234
-
235
- // Agent detail modal: route c + confirm keys before escape-close.
236
- // handleKey() consumes confirm-flow keys (y, Enter, n, Esc) and the 'c'
237
- // initiator; unhandled keys (including Esc when no confirm is pending)
238
- // fall through to escape-close below.
239
- if (state.agentDetailModal.active) {
240
- const keyStr: string =
241
- token.type === 'key' ? (token.logicalName ?? '') :
242
- token.type === 'text' ? token.value : '';
243
- if (keyStr && state.agentDetailModal.handleKey(keyStr)) {
244
- state.requestRender();
245
- return withState(state, true);
246
- }
247
- // 'c' was not consumed (non-cancellable), or any other key.
248
- // Esc closes the modal; all other keys are absorbed by the active modal.
249
- if (token.type === 'key' && token.logicalName === 'escape') {
250
- state.handleEscape();
251
- return withState(state, true);
252
- }
253
- state.requestRender();
254
- return withState(state, true);
255
- }
230
+ // W6.1 retirement: the live-tail, agent-detail, and process-modal token
231
+ // routes were removed with those modals (F2 now opens Fleet, which subsumes
232
+ // the live process tree; the three modals were structurally unreachable after
233
+ // the F2 repoint and were deleted).
256
234
 
257
235
  if (handleEscapeOnlyModalToken({
258
236
  active: state.contextInspectorModal.active,
@@ -262,17 +240,6 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
262
240
  return withState(state, true);
263
241
  }
264
242
 
265
- if (handleProcessModalToken({
266
- processModal: state.processModal,
267
- liveTailModal: state.liveTailModal,
268
- agentDetailModal: state.agentDetailModal,
269
- modalOpened: state.modalOpened,
270
- requestRender: state.requestRender,
271
- handleEscape: state.handleEscape,
272
- }, token)) {
273
- return withState(state, true);
274
- }
275
-
276
243
  const filePickerState = {
277
244
  filePicker: state.filePicker,
278
245
  prompt: state.prompt,
@@ -0,0 +1,149 @@
1
+ /**
2
+ * F1 (One-Platform Wave 2 friction fix): the onboarding wizard's "Connect to
3
+ * this daemon now" action, wired from the Network step's `network.daemon-source`
4
+ * = 'adopt' fields. Extracted from handler-onboarding.ts to keep that file
5
+ * under the architecture line cap.
6
+ */
7
+ import { join } from 'node:path';
8
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
9
+ import { resolveDaemonCompanionToken } from '../runtime/operator-token-cleanup.ts';
10
+ import {
11
+ buildManagedDaemonServiceManager,
12
+ detectLegacyUnit,
13
+ resolveInstalledDaemonBinary,
14
+ runLegacyDaemonMigration,
15
+ MANAGED_SERVICE_NAME,
16
+ } from '../runtime/legacy-daemon-migration.ts';
17
+ import type { InputHandlerLike as InputHandler } from './handler-types.ts';
18
+ import {
19
+ formatRuntimeActiveSuccessMessage,
20
+ getRuntimeEndpointStatus,
21
+ isRuntimeEndpointActive,
22
+ runtimePortDiagnostic,
23
+ } from './onboarding/onboarding-runtime-status.ts';
24
+
25
+ /**
26
+ * Immediately (not deferred to the final wizard Apply):
27
+ * 1. Installs the pasted token into <homeDirectory>/.goodvibes/daemon/operator-tokens.json
28
+ * via `resolveDaemonCompanionToken` — the same durable-persistence logic
29
+ * the `GOODVIBES_DAEMON_TOKEN` non-interactive override uses.
30
+ * 2. Points `controlPlane.host`/`controlPlane.port` at the pasted host/port.
31
+ * 3. Restarts the external-services controller and reports back, honestly,
32
+ * whether the daemon at that host/port was actually adopted — reusing
33
+ * the same `onboarding-runtime-status.ts` helpers the normal
34
+ * start-a-new-daemon path uses to report success/failure after Apply.
35
+ *
36
+ * Feedback is printed to the conversation feed (matching the existing
37
+ * start-openai-subscription action's pattern) rather than stored as wizard
38
+ * field state, because a 'status' field's value is recomputed fresh from the
39
+ * step builder on every render and is not a place to durably stash an
40
+ * out-of-band result.
41
+ */
42
+ export async function handleConnectExistingDaemonForHandler(handler: InputHandler): Promise<void> {
43
+ if (handler.onboardingApplyPending) return;
44
+ const host = handler.onboardingWizard.getStringFieldValue('network.adopt-daemon-host', '127.0.0.1').trim() || '127.0.0.1';
45
+ const port = handler.onboardingWizard.getPortFieldValue('network.adopt-daemon-port', 3421);
46
+ const token = handler.onboardingWizard.getStringFieldValue('network.adopt-daemon-token', '').trim();
47
+ if (token.length === 0) {
48
+ handler.commandContext?.print?.('Connect to existing daemon: paste that daemon\'s token first.');
49
+ return;
50
+ }
51
+
52
+ const externalServices = handler.uiServices.platform.externalServices;
53
+ if (!externalServices) {
54
+ handler.commandContext?.print?.('Connect to existing daemon: background service controller is unavailable in this build.');
55
+ return;
56
+ }
57
+
58
+ handler.onboardingApplyPending = true;
59
+ handler.requestRender();
60
+ try {
61
+ const daemonHomeDir = join(handler.uiServices.environment.homeDirectory, '.goodvibes', 'daemon');
62
+ resolveDaemonCompanionToken(daemonHomeDir, token);
63
+ handler.uiServices.platform.configManager.setDynamic('controlPlane.host', host);
64
+ handler.uiServices.platform.configManager.setDynamic('controlPlane.port', port);
65
+
66
+ const state = await externalServices.restart();
67
+ const binding = { label: 'GoodVibes daemon', host, port };
68
+ if (isRuntimeEndpointActive(state, 'daemon')) {
69
+ handler.commandContext?.print?.([
70
+ 'Connect to existing daemon: succeeded.',
71
+ ` ${formatRuntimeActiveSuccessMessage('daemon', state)}`,
72
+ ' This host/port and token are applied now; Apply at the end of the wizard will persist them.',
73
+ ].join('\n'));
74
+ } else {
75
+ const status = getRuntimeEndpointStatus(state, 'daemon');
76
+ handler.commandContext?.print?.([
77
+ 'Connect to existing daemon: could not verify a connection.',
78
+ ` ${runtimePortDiagnostic(binding, undefined, status)}`,
79
+ ' Check the host, port, and token, then try again.',
80
+ ].join('\n'));
81
+ }
82
+ } catch (error) {
83
+ handler.commandContext?.print?.(`Connect to existing daemon failed: ${summarizeError(error)}`);
84
+ } finally {
85
+ handler.onboardingApplyPending = false;
86
+ handler.requestRender();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * W4-D1: the guided UX entry point for migrating a legacy
92
+ * `goodvibes-daemon.service` unit (Wave 3 shipped detect+disclose only; this
93
+ * closes that inheritance). This is a thin wrapper over
94
+ * `detectLegacyUnit` + `runLegacyDaemonMigration`
95
+ * (`../runtime/legacy-daemon-migration.ts`) — the actual migration mechanics
96
+ * (never auto-migrate, new-up-then-old-down with a health check before the
97
+ * legacy unit is touched, adopt-or-warn for an unrecognized process on the
98
+ * port) live there and are exercised against fakes in
99
+ * `src/test/daemon/service-commands.test.ts`; this function only builds the
100
+ * same `PlatformServiceManager` the daemon CLI's `migrate-service` subcommand
101
+ * builds (via the shared `buildManagedDaemonServiceManager`, so a migration
102
+ * triggered from either surface installs the identical unit) and prints the
103
+ * honest result lines to the conversation feed, mirroring
104
+ * `handleConnectExistingDaemonForHandler`'s pattern above.
105
+ *
106
+ * This lives in `src/input/` rather than importing
107
+ * `../daemon/service-commands.ts` directly because the architecture gate's
108
+ * `input-no-entrypoints` rule forbids `src/input/**` from depending on
109
+ * `src/daemon/**` (input must stay a pure event-handling layer) — the shared
110
+ * engine lives in the entrypoint-agnostic `src/runtime/` layer instead.
111
+ *
112
+ * `confirm` is caller-supplied rather than read from a fixed field name here
113
+ * so this stays reusable from any surface: the onboarding wizard's Network
114
+ * step (`onboarding-wizard-network-adopt.ts`'s `pushLegacyDaemonMigrationFields`,
115
+ * dispatched from `handler-onboarding.ts`'s `migrate-legacy-daemon-service`
116
+ * action) reads its own `network.migrate-legacy-daemon-confirm` checklist
117
+ * toggle and passes it through as this parameter — unchecked previews the
118
+ * dry-run plan (`confirm: false`), checked executes the real migration
119
+ * (`confirm: true`).
120
+ */
121
+ export async function handleMigrateLegacyDaemonServiceForHandler(handler: InputHandler, confirm: boolean): Promise<void> {
122
+ if (handler.onboardingApplyPending) return;
123
+
124
+ handler.onboardingApplyPending = true;
125
+ handler.requestRender();
126
+ try {
127
+ const homeDirectory = handler.uiServices.environment.homeDirectory;
128
+ const configManager = handler.uiServices.platform.configManager;
129
+ const host = String(configManager.get('controlPlane.host') ?? '127.0.0.1');
130
+ const port = Number(configManager.get('controlPlane.port') ?? 3421);
131
+ const binaryPath = resolveInstalledDaemonBinary({ moduleUrl: import.meta.url });
132
+
133
+ const manager = buildManagedDaemonServiceManager({ binaryPath, homeDir: homeDirectory, host, port });
134
+ const legacy = detectLegacyUnit({ homeDir: homeDirectory });
135
+ const result = await runLegacyDaemonMigration(
136
+ { host, port, trackedServiceName: MANAGED_SERVICE_NAME, confirmMigration: confirm },
137
+ manager,
138
+ legacy,
139
+ );
140
+ handler.commandContext?.print?.(
141
+ ['Migrate legacy daemon service:', ...result.lines.map((line: string) => ` ${line}`)].join('\n'),
142
+ );
143
+ } catch (error) {
144
+ handler.commandContext?.print?.(`Migrate legacy daemon service failed: ${summarizeError(error)}`);
145
+ } finally {
146
+ handler.onboardingApplyPending = false;
147
+ handler.requestRender();
148
+ }
149
+ }