@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.
- package/CHANGELOG.md +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
package/src/input/handler.ts
CHANGED
|
@@ -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
|
-
|
|
276
|
-
|
|
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 {
|
package/src/input/keybindings.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
123
|
-
|
|
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:
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
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
|
-
|
|
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'
|
|
8
|
-
* 'helper'
|
|
9
|
-
* 'tool'
|
|
10
|
-
* 'tts'
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|