@pellux/goodvibes-tui 0.19.24 → 0.19.26
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 +13 -0
- package/README.md +5 -5
- package/bin/goodvibes +10 -0
- package/bin/goodvibes-daemon +10 -0
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -2
- package/src/cli/bundle-command.ts +225 -0
- package/src/cli/completion.ts +90 -0
- package/src/cli/config-overrides.ts +159 -0
- package/src/cli/endpoints.ts +63 -0
- package/src/cli/entrypoint.ts +169 -0
- package/src/cli/help.ts +301 -0
- package/src/cli/index.ts +11 -0
- package/src/cli/management-commands.ts +426 -0
- package/src/cli/management.ts +719 -0
- package/src/cli/network-posture.ts +46 -0
- package/src/cli/package-verification.ts +119 -0
- package/src/cli/parser.ts +369 -0
- package/src/cli/provider-classification.ts +107 -0
- package/src/cli/redaction.ts +105 -0
- package/src/cli/service-command.ts +45 -0
- package/src/cli/service-posture.ts +247 -0
- package/src/cli/status.ts +382 -0
- package/src/cli/surface-command.ts +248 -0
- package/src/cli/tui-startup.ts +32 -0
- package/src/cli/types.ts +69 -0
- package/src/cli-flags.ts +18 -55
- package/src/config/index.ts +1 -1
- package/src/config/secrets.ts +44 -0
- package/src/daemon/cli.ts +62 -11
- package/src/input/command-registry.ts +3 -0
- package/src/input/commands/guidance-runtime.ts +9 -4
- package/src/input/commands/local-runtime.ts +21 -7
- package/src/input/commands/local-setup.ts +31 -38
- package/src/input/commands/onboarding-runtime.ts +14 -0
- package/src/input/commands/runtime-services.ts +9 -0
- package/src/input/commands.ts +2 -0
- package/src/input/feed-context-factory.ts +8 -1
- package/src/input/handler-feed.ts +13 -8
- package/src/input/handler-interactions.ts +266 -0
- package/src/input/handler-modal-stack.ts +23 -3
- package/src/input/handler-modal-token-routes.ts +23 -1
- package/src/input/handler-onboarding.ts +696 -0
- package/src/input/handler-picker-routes.ts +15 -7
- package/src/input/handler-ui-state.ts +58 -0
- package/src/input/handler.ts +120 -246
- package/src/input/onboarding/handler-onboarding-routes.ts +105 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +211 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +148 -0
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +712 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +218 -0
- package/src/input/onboarding/onboarding-wizard-rules.ts +224 -0
- package/src/input/onboarding/onboarding-wizard-state.ts +354 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +642 -0
- package/src/input/onboarding/onboarding-wizard-types.ts +170 -0
- package/src/input/onboarding/onboarding-wizard.ts +594 -0
- package/src/main.ts +32 -39
- package/src/panels/builtin/operations.ts +0 -10
- package/src/panels/index.ts +0 -1
- package/src/renderer/conversation-overlays.ts +6 -0
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/onboarding/onboarding-wizard.ts +533 -0
- package/src/runtime/bootstrap-core.ts +1 -0
- package/src/runtime/bootstrap.ts +123 -0
- package/src/runtime/onboarding/apply.ts +685 -0
- package/src/runtime/onboarding/derivation.ts +495 -0
- package/src/runtime/onboarding/index.ts +7 -0
- package/src/runtime/onboarding/markers.ts +161 -0
- package/src/runtime/onboarding/snapshot.ts +400 -0
- package/src/runtime/onboarding/state.ts +140 -0
- package/src/runtime/onboarding/types.ts +402 -0
- package/src/runtime/onboarding/verify.ts +233 -0
- package/src/runtime/ui-services.ts +16 -0
- package/src/shell/ui-openers.ts +12 -2
- package/src/version.ts +1 -1
- package/src/panels/welcome-panel.ts +0 -64
package/src/input/commands.ts
CHANGED
|
@@ -53,6 +53,7 @@ import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.
|
|
|
53
53
|
import { registerIntelligenceRuntimeCommands } from './commands/intelligence-runtime.ts';
|
|
54
54
|
import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
|
|
55
55
|
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
56
|
+
import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
59
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
@@ -100,6 +101,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
100
101
|
registerIntelligenceRuntimeCommands(registry);
|
|
101
102
|
registerConversationRuntimeCommands(registry);
|
|
102
103
|
registerQrcodeRuntimeCommands(registry);
|
|
104
|
+
registerOnboardingRuntimeCommands(registry);
|
|
103
105
|
registerLocalRuntimeCommands(registry);
|
|
104
106
|
registerSessionWorkflowCommands(registry);
|
|
105
107
|
registerDiscoveryRuntimeCommands(registry);
|
|
@@ -31,10 +31,12 @@ import type { BookmarkModal } from './bookmark-modal.ts';
|
|
|
31
31
|
import type { SettingsModal } from './settings-modal.ts';
|
|
32
32
|
import type { SessionPickerModal } from './session-picker-modal.ts';
|
|
33
33
|
import type { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
34
|
+
import type { OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
|
|
34
35
|
import type { WrappedPromptInfo } from './handler-prompt-buffer.ts';
|
|
35
36
|
import type { Panel } from '../panels/types.ts';
|
|
36
37
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
37
38
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
39
|
+
import type { ModelPickerTarget } from './model-picker.ts';
|
|
38
40
|
|
|
39
41
|
/**
|
|
40
42
|
* Initial mutable scalar values for InputFeedContext.
|
|
@@ -77,7 +79,7 @@ export interface FeedContextMutableInit {
|
|
|
77
79
|
* `profilePickerModal` — modal objects constructed once
|
|
78
80
|
* - `filePicker`, `modelPicker`, `processModal`, `liveTailModal`,
|
|
79
81
|
* `agentDetailModal`, `contextInspectorModal`, `blockActionsMenu`,
|
|
80
|
-
* `searchManager`, `historySearch` — service objects constructed once
|
|
82
|
+
* `searchManager`, `historySearch`, `onboardingWizard` — service objects constructed once
|
|
81
83
|
* - `panelManager`, `keybindingsManager` — from uiServices, stable
|
|
82
84
|
* - `modalStack` — reference to the handler's shared array
|
|
83
85
|
* - `getHistory`, `getViewportHeight`, `getScrollTop`, `scroll`, `exitApp` — callbacks
|
|
@@ -104,6 +106,7 @@ export interface FeedContextStableRefs {
|
|
|
104
106
|
autocomplete: AutocompleteEngine | null;
|
|
105
107
|
filePicker: FilePickerModal;
|
|
106
108
|
modelPicker: ModelPickerModal;
|
|
109
|
+
onboardingWizard: OnboardingWizardController;
|
|
107
110
|
processModal: ProcessModal;
|
|
108
111
|
liveTailModal: LiveTailModal;
|
|
109
112
|
agentDetailModal: AgentDetailModal;
|
|
@@ -148,6 +151,9 @@ export interface FeedContextClosures {
|
|
|
148
151
|
findMarkerAtPos: (pos: number) => { start: number; end: number } | null;
|
|
149
152
|
cleanupMarkerRegistry: (text: string) => void;
|
|
150
153
|
expandPrompt: (text: string) => string | import('@pellux/goodvibes-sdk/platform/providers/interface').ContentPart[];
|
|
154
|
+
openModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') => boolean;
|
|
155
|
+
onModelPickerCommit: () => boolean;
|
|
156
|
+
onOnboardingAction: (action: import('./onboarding/onboarding-wizard.ts').OnboardingWizardAction) => void;
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
/**
|
|
@@ -233,4 +239,5 @@ export function syncFeedContextMutableFields(
|
|
|
233
239
|
ctx.nextImageId = fields.nextImageId;
|
|
234
240
|
ctx.mouseDownRow = fields.mouseDownRow;
|
|
235
241
|
ctx.mouseDownCol = fields.mouseDownCol;
|
|
242
|
+
ctx.contentWidth = fields.contentWidth;
|
|
236
243
|
}
|
|
@@ -18,6 +18,8 @@ import { BookmarkModal } from './bookmark-modal.ts';
|
|
|
18
18
|
import { SettingsModal } from './settings-modal.ts';
|
|
19
19
|
import { SessionPickerModal } from './session-picker-modal.ts';
|
|
20
20
|
import { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
21
|
+
import type { OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
|
|
22
|
+
import type { OnboardingWizardAction } from './onboarding/onboarding-wizard.ts';
|
|
21
23
|
import {
|
|
22
24
|
IMAGE_EXTENSIONS,
|
|
23
25
|
formatFileSize,
|
|
@@ -39,6 +41,7 @@ import { handlePanelIntegrationAction } from './panel-integration-actions.ts';
|
|
|
39
41
|
import { SelectionManager } from './selection.ts';
|
|
40
42
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
41
43
|
import type { KeybindingsManager } from './keybindings.ts';
|
|
44
|
+
import type { ModelPickerTarget } from './model-picker.ts';
|
|
42
45
|
|
|
43
46
|
/**
|
|
44
47
|
* InputFeedContext — The single long-lived context object passed to feedInputTokens
|
|
@@ -66,8 +69,8 @@ import type { KeybindingsManager } from './keybindings.ts';
|
|
|
66
69
|
* - `pasteRegistry`, `imageRegistry` — owned Maps, never replaced
|
|
67
70
|
* - `selectionModal`, `bookmarkModal`, `settingsModal`, `sessionPickerModal`,
|
|
68
71
|
* `profilePickerModal` — modal objects constructed once in InputHandler constructor
|
|
69
|
-
* - `filePicker`, `modelPicker`, `
|
|
70
|
-
* `contextInspectorModal`, `blockActionsMenu`, `searchManager`, `historySearch` —
|
|
72
|
+
* - `filePicker`, `modelPicker`, `onboardingWizard`, `processModal`, `liveTailModal`,
|
|
73
|
+
* `agentDetailModal`, `contextInspectorModal`, `blockActionsMenu`, `searchManager`, `historySearch` —
|
|
71
74
|
* service objects constructed once
|
|
72
75
|
* - `panelManager`, `keybindingsManager` — from uiServices, stable for app lifetime
|
|
73
76
|
* - `modalStack` — reference to the handler's shared array (mutated in place)
|
|
@@ -108,6 +111,7 @@ export interface InputFeedContext {
|
|
|
108
111
|
autocomplete: AutocompleteEngine | null;
|
|
109
112
|
readonly filePicker: FilePickerModal;
|
|
110
113
|
readonly modelPicker: ModelPickerModal;
|
|
114
|
+
readonly onboardingWizard: OnboardingWizardController;
|
|
111
115
|
readonly processModal: ProcessModal;
|
|
112
116
|
readonly liveTailModal: LiveTailModal;
|
|
113
117
|
readonly agentDetailModal: AgentDetailModal;
|
|
@@ -148,6 +152,9 @@ export interface InputFeedContext {
|
|
|
148
152
|
readonly findMarkerAtPos: (pos: number) => { start: number; end: number } | null;
|
|
149
153
|
readonly cleanupMarkerRegistry: (text: string) => void;
|
|
150
154
|
readonly expandPrompt: (text: string) => string | import('@pellux/goodvibes-sdk/platform/providers/interface').ContentPart[];
|
|
155
|
+
readonly openModelPickerWithTarget: (target: ModelPickerTarget, source?: 'settings' | 'onboarding') => boolean;
|
|
156
|
+
readonly onModelPickerCommit: () => boolean;
|
|
157
|
+
readonly onOnboardingAction: (action: OnboardingWizardAction) => void;
|
|
151
158
|
readonly exitApp: () => void;
|
|
152
159
|
}
|
|
153
160
|
|
|
@@ -173,6 +180,7 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
173
180
|
settingsModal: context.settingsModal,
|
|
174
181
|
sessionPickerModal: context.sessionPickerModal,
|
|
175
182
|
profilePickerModal: context.profilePickerModal,
|
|
183
|
+
onboardingWizard: context.onboardingWizard,
|
|
176
184
|
helpOverlayActive: context.helpOverlayActive,
|
|
177
185
|
helpScrollOffset: context.helpScrollOffset,
|
|
178
186
|
shortcutsOverlayActive: context.shortcutsOverlayActive,
|
|
@@ -204,12 +212,9 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
204
212
|
searchManager: context.searchManager,
|
|
205
213
|
scroll: context.scroll,
|
|
206
214
|
getScrollTop: context.getScrollTop,
|
|
207
|
-
openModelPickerWithTarget: context.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
context.commandContext!.openModelPicker!();
|
|
211
|
-
}
|
|
212
|
-
: undefined,
|
|
215
|
+
openModelPickerWithTarget: context.openModelPickerWithTarget,
|
|
216
|
+
onModelPickerCommit: context.onModelPickerCommit,
|
|
217
|
+
onOnboardingAction: context.onOnboardingAction,
|
|
213
218
|
}, token);
|
|
214
219
|
context.selectionCallback = modalRoute.selectionCallback;
|
|
215
220
|
context.helpOverlayActive = modalRoute.helpOverlayActive;
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { buildProviderAccountSnapshot } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
2
|
+
import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
3
|
+
import { collectOnboardingSnapshot } from '../runtime/onboarding/index.ts';
|
|
4
|
+
import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, handleBlockCopy, handleBlockRerun, handleBlockSave, handleBlockToggle, handleBookmark, handleClipboardPaste, handleCopy, handleCtrlC, handleDiffApply, registerPaste } from './handler-content-actions.ts';
|
|
5
|
+
import { clearModalStack, handleEscape, modalOpened } from './handler-modal-stack.ts';
|
|
6
|
+
import { openOnboardingWizardState, type OpenOnboardingWizardOptions } from './handler-ui-state.ts';
|
|
7
|
+
import type { InputHandler } from './handler.ts';
|
|
8
|
+
|
|
9
|
+
export function openOnboardingWizardForHandler(
|
|
10
|
+
handler: InputHandler,
|
|
11
|
+
modeOrOptions: OnboardingWizardMode | OpenOnboardingWizardOptions = 'new',
|
|
12
|
+
): void {
|
|
13
|
+
const options = typeof modeOrOptions === 'string' ? { mode: modeOrOptions } : modeOrOptions;
|
|
14
|
+
if (!handler.modalStack.includes('onboarding')) handler.modalOpened('onboarding');
|
|
15
|
+
handler.clearOnboardingModelPickerCancelState();
|
|
16
|
+
openOnboardingWizardState(handler.onboardingWizard, options);
|
|
17
|
+
const hydrationSerial = ++handler.onboardingHydrationSerial;
|
|
18
|
+
if (options.preload === undefined) {
|
|
19
|
+
handler.onboardingWizard.beginRuntimeHydration();
|
|
20
|
+
void handler.hydrateOnboardingWizardFromRuntime(hydrationSerial);
|
|
21
|
+
}
|
|
22
|
+
handler.requestRender();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function hydrateOnboardingWizardFromRuntimeForHandler(handler: InputHandler, hydrationSerial: number): Promise<void> {
|
|
26
|
+
try {
|
|
27
|
+
const snapshot = await collectOnboardingSnapshot({
|
|
28
|
+
config: handler.uiServices.platform.configManager,
|
|
29
|
+
shellPaths: handler.uiServices.environment.shellPaths,
|
|
30
|
+
acknowledgementScope: 'project',
|
|
31
|
+
subscriptions: handler.uiServices.platform.subscriptionManager,
|
|
32
|
+
secrets: handler.uiServices.platform.secretsManager,
|
|
33
|
+
auth: handler.uiServices.platform.localUserAuthManager,
|
|
34
|
+
services: handler.uiServices.platform.serviceRegistry,
|
|
35
|
+
surfaces: {
|
|
36
|
+
list: () => handler.uiServices.platform.surfaceRegistry.syncConfiguredSurfaces(),
|
|
37
|
+
},
|
|
38
|
+
providerAccounts: {
|
|
39
|
+
loadSnapshot: () => buildProviderAccountSnapshot({
|
|
40
|
+
providerRegistry: handler.uiServices.providers.providerRegistry,
|
|
41
|
+
serviceRegistry: handler.uiServices.platform.serviceRegistry,
|
|
42
|
+
subscriptionManager: handler.uiServices.platform.subscriptionManager,
|
|
43
|
+
secretsManager: handler.uiServices.platform.secretsManager,
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
if (!handler.onboardingWizard.active || hydrationSerial !== handler.onboardingHydrationSerial) return;
|
|
48
|
+
handler.onboardingWizard.hydrateRuntimeState({ snapshot }, { resetValues: true });
|
|
49
|
+
handler.requestRender();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (!handler.onboardingWizard.active || hydrationSerial !== handler.onboardingHydrationSerial) return;
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
handler.onboardingWizard.failRuntimeHydration(message);
|
|
54
|
+
handler.commandContext?.print?.(`Onboarding runtime snapshot failed: ${message}`);
|
|
55
|
+
handler.requestRender();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function registerPasteForHandler(handler: InputHandler, content: string): string {
|
|
60
|
+
const result = registerPaste({
|
|
61
|
+
pasteRegistry: handler.pasteRegistry,
|
|
62
|
+
nextPasteId: handler.nextPasteId,
|
|
63
|
+
imageRegistry: handler.imageRegistry,
|
|
64
|
+
nextImageId: handler.nextImageId,
|
|
65
|
+
}, content, handler.uiServices.environment.shellPaths.workingDirectory);
|
|
66
|
+
handler.nextPasteId = result.nextPasteId;
|
|
67
|
+
handler.nextImageId = result.nextImageId;
|
|
68
|
+
return result.marker;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* expandPrompt - Replaces paste markers with actual content.
|
|
73
|
+
* If image markers are present, returns ContentPart[] for multimodal delivery.
|
|
74
|
+
* Otherwise returns a plain string.
|
|
75
|
+
*/
|
|
76
|
+
export function expandPromptForHandler(handler: InputHandler, text: string) {
|
|
77
|
+
return expandPrompt(handler.pasteRegistry, handler.imageRegistry, text, handler.uiServices.environment.shellPaths.workingDirectory);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* getImageAttachments - Returns a copy of the current image registry.
|
|
82
|
+
* Callers can use this to attach images when building LLM messages.
|
|
83
|
+
*/
|
|
84
|
+
export function getImageAttachmentsForHandler(handler: InputHandler): Map<string, { data: string; mediaType: string }> {
|
|
85
|
+
return new Map(handler.imageRegistry);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* findMarkerAtPos - Returns the start/end of an atomic marker if pos is inside one.
|
|
90
|
+
* Used to make backspace/delete/arrow treat markers as single units.
|
|
91
|
+
*/
|
|
92
|
+
/**
|
|
93
|
+
* cleanupMarkerRegistry - If the given marker text is an IMAGE marker,
|
|
94
|
+
* parses its ID and removes it from imageRegistry.
|
|
95
|
+
*/
|
|
96
|
+
export function cleanupMarkerRegistryForHandler(handler: InputHandler, markerText: string): void {
|
|
97
|
+
cleanupMarkerRegistry(handler.imageRegistry, markerText);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function findMarkerAtPosForHandler(handler: InputHandler, pos: number): { start: number; end: number } | null {
|
|
101
|
+
return findMarkerAtPos(handler.prompt, pos);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function handleCopyForHandler(handler: InputHandler): void {
|
|
105
|
+
handleCopy(handler.selection, handler.getHistory, handler.requestRender, () => {
|
|
106
|
+
handler.lastCopyTime = Date.now();
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* handleBlockCopy - Ctrl+Y: Copy the content of the nearest code/tool block.
|
|
112
|
+
*/
|
|
113
|
+
export function handleBlockCopyForHandler(handler: InputHandler): void {
|
|
114
|
+
handleBlockCopy(handler.conversationManager, handler.getScrollTop, handler.requestRender, () => {
|
|
115
|
+
handler.lastBlockCopyTime = Date.now();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* handleBookmark - Ctrl+B: Toggle bookmark on the nearest block.
|
|
121
|
+
*/
|
|
122
|
+
export function handleBookmarkForHandler(handler: InputHandler): void {
|
|
123
|
+
handleBookmark(handler.conversationManager, handler.getScrollTop, handler.requestRender, handler.uiServices.shell.bookmarkManager);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* handleBlockSave - Ctrl+S: Save nearest block content to a file.
|
|
128
|
+
*/
|
|
129
|
+
export function handleBlockSaveForHandler(handler: InputHandler): void {
|
|
130
|
+
handleBlockSave(handler.conversationManager, handler.getScrollTop, handler.requestRender, handler.uiServices.shell.bookmarkManager);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* executeBlockAction - Execute a block action ID on the nearest block.
|
|
135
|
+
* Called when the user selects an action from the BlockActionsMenu.
|
|
136
|
+
*/
|
|
137
|
+
export function executeBlockActionForHandler(handler: InputHandler, actionId: string): void {
|
|
138
|
+
switch (actionId) {
|
|
139
|
+
case 'copy': handler.handleBlockCopy(); break;
|
|
140
|
+
case 'bookmark': handler.handleBookmark(); break;
|
|
141
|
+
case 'toggle': handler.handleBlockToggle(); break;
|
|
142
|
+
case 'apply': handler.handleDiffApply(); break;
|
|
143
|
+
case 'rerun': handler.handleBlockRerun(); break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* handleBlockRerun - Re-run the tool call for the nearest tool block.
|
|
149
|
+
* Emits a tool-rerun event for the orchestrator to handle.
|
|
150
|
+
*/
|
|
151
|
+
export function handleBlockRerunForHandler(handler: InputHandler): void {
|
|
152
|
+
handleBlockRerun(handler.conversationManager, handler.getScrollTop, handler.requestRender);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* handleBlockToggle - Tab (non-command mode): Toggle collapse of nearest block.
|
|
157
|
+
*/
|
|
158
|
+
export function handleBlockToggleForHandler(handler: InputHandler): void {
|
|
159
|
+
handleBlockToggle(handler.conversationManager, handler.getScrollTop, handler.requestRender);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* handleDiffApply - Ctrl+A when a diff block is nearest: request approval and apply the diff.
|
|
164
|
+
* Returns true if a diff was found and applied (so caller can skip default Ctrl+A).
|
|
165
|
+
*/
|
|
166
|
+
export function handleDiffApplyForHandler(handler: InputHandler): boolean {
|
|
167
|
+
return handleDiffApply(
|
|
168
|
+
handler.conversationManager,
|
|
169
|
+
handler.getScrollTop,
|
|
170
|
+
handler.commandContext,
|
|
171
|
+
handler.requestRender,
|
|
172
|
+
() => `diff-apply-${Date.now()}`,
|
|
173
|
+
'write',
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Handle Ctrl+C:
|
|
179
|
+
* - If prompt has text: clear it
|
|
180
|
+
* - If prompt is empty and LLM is thinking: cancel generation
|
|
181
|
+
* - If prompt is empty and idle: show exit notice (double = exit)
|
|
182
|
+
*/
|
|
183
|
+
export function handleCtrlCForHandler(handler: InputHandler): void {
|
|
184
|
+
handleCtrlC(
|
|
185
|
+
handler.prompt,
|
|
186
|
+
() => handler.saveUndoState(),
|
|
187
|
+
(value) => { handler.prompt = value; },
|
|
188
|
+
(value) => { handler.cursorPos = value; },
|
|
189
|
+
handler.commandContext?.cancelGeneration,
|
|
190
|
+
handler.exitApp,
|
|
191
|
+
handler.requestRender,
|
|
192
|
+
handler.lastCtrlCTime,
|
|
193
|
+
(value) => { handler.lastCtrlCTime = value; },
|
|
194
|
+
(value) => { handler.showExitNotice = value; },
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Handle Escape:
|
|
200
|
+
* - If prompt has text: clear it
|
|
201
|
+
* - If prompt is empty: cancel generation (double-tap not needed)
|
|
202
|
+
*/
|
|
203
|
+
/**
|
|
204
|
+
* Record that a modal has been opened and push it onto the navigation stack.
|
|
205
|
+
* Call this EVERY time a modal opens (except inside openModal()).
|
|
206
|
+
*
|
|
207
|
+
* @param name - The modal identifier (e.g. 'settings', 'help', 'process').
|
|
208
|
+
*/
|
|
209
|
+
export function modalOpenedForHandler(handler: InputHandler, name: string): void {
|
|
210
|
+
modalOpened(handler, name);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Clear the modal navigation stack on non-modal user input (e.g. submit).
|
|
215
|
+
*/
|
|
216
|
+
export function clearModalStackForHandler(handler: InputHandler): void {
|
|
217
|
+
clearModalStack(handler.modalStack);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function handleEscapeForHandler(handler: InputHandler): void {
|
|
221
|
+
const result = handleEscape({
|
|
222
|
+
helpOverlayActive: handler.helpOverlayActive,
|
|
223
|
+
shortcutsOverlayActive: handler.shortcutsOverlayActive,
|
|
224
|
+
bookmarkModal: handler.bookmarkModal,
|
|
225
|
+
agentDetailModal: handler.agentDetailModal,
|
|
226
|
+
liveTailModal: handler.liveTailModal,
|
|
227
|
+
settingsModal: handler.settingsModal,
|
|
228
|
+
sessionPickerModal: handler.sessionPickerModal,
|
|
229
|
+
profilePickerModal: handler.profilePickerModal,
|
|
230
|
+
contextInspectorModal: handler.contextInspectorModal,
|
|
231
|
+
processModal: handler.processModal,
|
|
232
|
+
modelPicker: handler.modelPicker,
|
|
233
|
+
filePicker: handler.filePicker,
|
|
234
|
+
blockActionsMenu: handler.blockActionsMenu,
|
|
235
|
+
selectionModal: handler.selectionModal,
|
|
236
|
+
onboardingWizard: handler.onboardingWizard,
|
|
237
|
+
commandMode: handler.commandMode,
|
|
238
|
+
modalStack: handler.modalStack,
|
|
239
|
+
modalReturnFocus: handler.modalReturnFocus,
|
|
240
|
+
panelFocused: handler.panelFocused,
|
|
241
|
+
indicatorFocused: handler.indicatorFocused,
|
|
242
|
+
prompt: handler.prompt,
|
|
243
|
+
cursorPos: handler.cursorPos,
|
|
244
|
+
requestRender: handler.requestRender,
|
|
245
|
+
saveUndoState: () => handler.saveUndoState(),
|
|
246
|
+
cancelGeneration: handler.commandContext?.cancelGeneration,
|
|
247
|
+
selectionCallback: handler.selectionCallback,
|
|
248
|
+
autocompleteReset: () => handler.autocomplete?.reset(),
|
|
249
|
+
autocompleteUpdate: (query: string) => handler.autocomplete?.update(query),
|
|
250
|
+
helpScrollOffset: handler.helpScrollOffset,
|
|
251
|
+
shortcutsScrollOffset: handler.shortcutsScrollOffset,
|
|
252
|
+
clearOnboardingModelPickerCancelState: () => handler.clearOnboardingModelPickerCancelState(),
|
|
253
|
+
restoreOnboardingModelPickerCancelState: () => handler.restoreOnboardingModelPickerCancelState(),
|
|
254
|
+
});
|
|
255
|
+
handler.prompt = result.prompt;
|
|
256
|
+
handler.cursorPos = result.cursorPos;
|
|
257
|
+
handler.commandMode = result.commandMode;
|
|
258
|
+
handler.helpOverlayActive = result.helpOverlayActive;
|
|
259
|
+
handler.helpScrollOffset = result.helpScrollOffset;
|
|
260
|
+
handler.shortcutsOverlayActive = result.shortcutsOverlayActive;
|
|
261
|
+
handler.shortcutsScrollOffset = result.shortcutsScrollOffset;
|
|
262
|
+
handler.selectionCallback = result.selectionCallback;
|
|
263
|
+
handler.panelFocused = result.panelFocused;
|
|
264
|
+
handler.indicatorFocused = result.indicatorFocused;
|
|
265
|
+
handler.modalReturnFocus = result.modalReturnFocus;
|
|
266
|
+
}
|
|
@@ -17,6 +17,8 @@ export function modalOpened(state: ModalStackState, name: string): void {
|
|
|
17
17
|
if (getActiveModalName(state) === null && state.modalStack.length > 0) {
|
|
18
18
|
state.modalStack.length = 0;
|
|
19
19
|
}
|
|
20
|
+
if (state.modalStack[state.modalStack.length - 1] === name) return;
|
|
21
|
+
if (state.modalStack.includes(name)) return;
|
|
20
22
|
if (state.modalStack.length === 0) {
|
|
21
23
|
state.modalReturnFocus = state.indicatorFocused ? 'indicator' : state.panelFocused ? 'panel' : 'prompt';
|
|
22
24
|
}
|
|
@@ -46,6 +48,8 @@ export type EscapeState = ModalStackState & {
|
|
|
46
48
|
selectionModal: ModalStackState['selectionModal'];
|
|
47
49
|
autocompleteReset: () => void;
|
|
48
50
|
autocompleteUpdate?: (query: string) => void;
|
|
51
|
+
clearOnboardingModelPickerCancelState?: () => void;
|
|
52
|
+
restoreOnboardingModelPickerCancelState?: () => void;
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
export function handleEscape(state: EscapeState): {
|
|
@@ -59,6 +63,7 @@ export function handleEscape(state: EscapeState): {
|
|
|
59
63
|
selectionCallback: ((result: SelectionResult | null) => void) | null;
|
|
60
64
|
panelFocused: boolean;
|
|
61
65
|
indicatorFocused: boolean;
|
|
66
|
+
modalReturnFocus: NonNullable<ModalStackState['modalReturnFocus']>;
|
|
62
67
|
} {
|
|
63
68
|
let prompt = state.prompt;
|
|
64
69
|
let cursorPos = state.cursorPos;
|
|
@@ -70,6 +75,7 @@ export function handleEscape(state: EscapeState): {
|
|
|
70
75
|
let selectionCallback = state.selectionCallback;
|
|
71
76
|
let panelFocused = state.panelFocused;
|
|
72
77
|
let indicatorFocused = state.indicatorFocused;
|
|
78
|
+
let modalReturnFocus: NonNullable<ModalStackState['modalReturnFocus']> = state.modalReturnFocus ?? 'prompt';
|
|
73
79
|
|
|
74
80
|
const restoreFocus = (): void => {
|
|
75
81
|
if (state.modalStack.length > 0 || getActiveModalName({
|
|
@@ -78,8 +84,9 @@ export function handleEscape(state: EscapeState): {
|
|
|
78
84
|
shortcutsOverlayActive,
|
|
79
85
|
commandMode,
|
|
80
86
|
}) !== null) return;
|
|
81
|
-
panelFocused =
|
|
82
|
-
indicatorFocused =
|
|
87
|
+
panelFocused = modalReturnFocus === 'panel';
|
|
88
|
+
indicatorFocused = modalReturnFocus === 'indicator';
|
|
89
|
+
modalReturnFocus = 'prompt';
|
|
83
90
|
state.modalReturnFocus = 'prompt';
|
|
84
91
|
};
|
|
85
92
|
|
|
@@ -97,6 +104,7 @@ export function handleEscape(state: EscapeState): {
|
|
|
97
104
|
selectionCallback,
|
|
98
105
|
panelFocused,
|
|
99
106
|
indicatorFocused,
|
|
107
|
+
modalReturnFocus,
|
|
100
108
|
};
|
|
101
109
|
}
|
|
102
110
|
|
|
@@ -118,7 +126,10 @@ export function handleEscape(state: EscapeState): {
|
|
|
118
126
|
closeProfilePicker: () => state.profilePickerModal.close(),
|
|
119
127
|
closeContextInspector: () => state.contextInspectorModal.close(),
|
|
120
128
|
closeProcess: () => state.processModal.close(),
|
|
121
|
-
closeModelPicker: () =>
|
|
129
|
+
closeModelPicker: () => {
|
|
130
|
+
state.modelPicker.close();
|
|
131
|
+
state.restoreOnboardingModelPickerCancelState?.();
|
|
132
|
+
},
|
|
122
133
|
closeFilePicker: () => state.filePicker.close(),
|
|
123
134
|
closeBlockActions: () => state.blockActionsMenu.close(),
|
|
124
135
|
closeSelection: () => {
|
|
@@ -127,6 +138,10 @@ export function handleEscape(state: EscapeState): {
|
|
|
127
138
|
state.selectionModal.close();
|
|
128
139
|
cb?.(null);
|
|
129
140
|
},
|
|
141
|
+
closeOnboarding: () => {
|
|
142
|
+
state.onboardingWizard?.close();
|
|
143
|
+
state.clearOnboardingModelPickerCancelState?.();
|
|
144
|
+
},
|
|
130
145
|
closeCommandMode: () => {
|
|
131
146
|
commandMode = false;
|
|
132
147
|
for (let i = state.modalStack.length - 1; i >= 0; i--) {
|
|
@@ -146,6 +161,7 @@ export function handleEscape(state: EscapeState): {
|
|
|
146
161
|
openBookmark: () => state.bookmarkModal.open(),
|
|
147
162
|
openProcess: () => state.processModal.open(),
|
|
148
163
|
openContextInspector: () => state.contextInspectorModal.open(),
|
|
164
|
+
openOnboarding: () => state.onboardingWizard?.reopen(),
|
|
149
165
|
openCommandMode: () => {
|
|
150
166
|
commandMode = true;
|
|
151
167
|
prompt = '/';
|
|
@@ -176,6 +192,7 @@ export function handleEscape(state: EscapeState): {
|
|
|
176
192
|
selectionCallback,
|
|
177
193
|
panelFocused,
|
|
178
194
|
indicatorFocused,
|
|
195
|
+
modalReturnFocus,
|
|
179
196
|
};
|
|
180
197
|
}
|
|
181
198
|
|
|
@@ -200,6 +217,7 @@ export function handleEscape(state: EscapeState): {
|
|
|
200
217
|
selectionCallback,
|
|
201
218
|
panelFocused,
|
|
202
219
|
indicatorFocused,
|
|
220
|
+
modalReturnFocus,
|
|
203
221
|
};
|
|
204
222
|
}
|
|
205
223
|
|
|
@@ -218,6 +236,7 @@ export function handleEscape(state: EscapeState): {
|
|
|
218
236
|
selectionCallback,
|
|
219
237
|
panelFocused,
|
|
220
238
|
indicatorFocused,
|
|
239
|
+
modalReturnFocus,
|
|
221
240
|
};
|
|
222
241
|
}
|
|
223
242
|
|
|
@@ -233,5 +252,6 @@ export function handleEscape(state: EscapeState): {
|
|
|
233
252
|
selectionCallback,
|
|
234
253
|
panelFocused,
|
|
235
254
|
indicatorFocused,
|
|
255
|
+
modalReturnFocus,
|
|
236
256
|
};
|
|
237
257
|
}
|
|
@@ -15,7 +15,9 @@ import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
|
15
15
|
import type { FilePickerModal } from './file-picker.ts';
|
|
16
16
|
import type { BlockActionsMenu, BlockActionId } from '../renderer/block-actions.ts';
|
|
17
17
|
import type { SearchManager } from './search.ts';
|
|
18
|
+
import type { OnboardingWizardAction, OnboardingWizardController } from './onboarding/onboarding-wizard.ts';
|
|
18
19
|
import { handleHistorySearchToken, handleOverlayToken, handleSearchModeToken } from './handler-ui-state.ts';
|
|
20
|
+
import { handleOnboardingWizardToken } from './onboarding/handler-onboarding-routes.ts';
|
|
19
21
|
import {
|
|
20
22
|
handleBookmarkModalToken,
|
|
21
23
|
handleProfilePickerToken,
|
|
@@ -41,6 +43,7 @@ export type ModalTokenRouteState = {
|
|
|
41
43
|
settingsModal: SettingsModal;
|
|
42
44
|
sessionPickerModal: SessionPickerModal;
|
|
43
45
|
profilePickerModal: ProfilePickerModal;
|
|
46
|
+
onboardingWizard: OnboardingWizardController;
|
|
44
47
|
helpOverlayActive: boolean;
|
|
45
48
|
helpScrollOffset: number;
|
|
46
49
|
shortcutsOverlayActive: boolean;
|
|
@@ -73,7 +76,14 @@ export type ModalTokenRouteState = {
|
|
|
73
76
|
scroll: (delta: number) => void;
|
|
74
77
|
getScrollTop: () => number;
|
|
75
78
|
/** Callback to open the model picker with a specific target (helper or tool). Optional — only wired when available. */
|
|
76
|
-
openModelPickerWithTarget?: (
|
|
79
|
+
openModelPickerWithTarget?: (
|
|
80
|
+
target: import('./model-picker.ts').ModelPickerTarget,
|
|
81
|
+
source?: 'settings' | 'onboarding',
|
|
82
|
+
) => boolean;
|
|
83
|
+
clearOnboardingModelPickerCancelState?: () => void;
|
|
84
|
+
restoreOnboardingModelPickerCancelState?: () => void;
|
|
85
|
+
onModelPickerCommit?: () => boolean;
|
|
86
|
+
onOnboardingAction?: (action: OnboardingWizardAction) => void;
|
|
77
87
|
};
|
|
78
88
|
|
|
79
89
|
export function handleModalTokenRoutes(state: ModalTokenRouteState, token: InputToken): {
|
|
@@ -173,6 +183,18 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
173
183
|
getViewportHeight: state.getViewportHeight,
|
|
174
184
|
requestRender: state.requestRender,
|
|
175
185
|
handleEscape: state.handleEscape,
|
|
186
|
+
onModelPickerCommit: state.onModelPickerCommit,
|
|
187
|
+
}, token)) {
|
|
188
|
+
return withState(state, true);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (handleOnboardingWizardToken({
|
|
192
|
+
onboardingWizard: state.onboardingWizard,
|
|
193
|
+
getViewportHeight: state.getViewportHeight,
|
|
194
|
+
requestRender: state.requestRender,
|
|
195
|
+
handleEscape: state.handleEscape,
|
|
196
|
+
openModelPickerWithTarget: state.openModelPickerWithTarget,
|
|
197
|
+
onAction: state.onOnboardingAction,
|
|
176
198
|
}, token)) {
|
|
177
199
|
return withState(state, true);
|
|
178
200
|
}
|