@pellux/goodvibes-tui 0.18.12 → 0.18.17
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 +172 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -2
- package/src/config/index.ts +1 -138
- package/src/core/conversation-rendering.ts +3 -3
- package/src/core/conversation.ts +176 -423
- package/src/core/history.ts +45 -0
- package/src/core/orchestrator.ts +3 -735
- package/src/core/system-message-router.ts +19 -58
- package/src/daemon/cli.ts +82 -6
- package/src/input/command-registry.ts +2 -0
- package/src/input/commands/control-room-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +20 -0
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-content-actions.ts +2 -2
- package/src/input/handler-feed.ts +7 -1
- package/src/input/handler-modal-routes.ts +19 -2
- package/src/input/handler-modal-token-routes.ts +4 -1
- package/src/input/handler-picker-routes.ts +4 -2
- package/src/input/handler-ui-state.ts +1 -1
- package/src/input/handler.ts +1 -1
- package/src/input/model-picker.ts +11 -0
- package/src/input/search.ts +1 -1
- package/src/input/selection.ts +2 -2
- package/src/input/settings-modal.ts +31 -3
- package/src/main.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +3 -3
- package/src/panels/agent-logs-panel.ts +26 -27
- package/src/panels/approval-panel.ts +2 -2
- package/src/panels/automation-control-panel.ts +3 -3
- package/src/panels/base-panel.ts +14 -14
- package/src/panels/builtin/operations.ts +1 -1
- package/src/panels/builtin/session.ts +67 -1
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/cockpit-panel.ts +2 -2
- package/src/panels/communication-panel.ts +3 -3
- package/src/panels/context-visualizer-panel.ts +2 -2
- package/src/panels/control-plane-panel.ts +3 -3
- package/src/panels/cost-tracker-panel.ts +3 -3
- package/src/panels/debug-panel.ts +2 -2
- package/src/panels/diff-panel.ts +2 -2
- package/src/panels/docs-panel.ts +1 -1
- package/src/panels/eval-panel.ts +2 -2
- package/src/panels/file-explorer-panel.ts +3 -3
- package/src/panels/file-preview-panel.ts +3 -3
- package/src/panels/forensics-panel.ts +2 -2
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/hooks-panel.ts +3 -3
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/intelligence-panel.ts +2 -2
- package/src/panels/knowledge-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +2 -2
- package/src/panels/marketplace-panel.ts +1 -1
- package/src/panels/mcp-panel.ts +3 -3
- package/src/panels/memory-panel.ts +1 -1
- package/src/panels/ops-control-panel.ts +3 -3
- package/src/panels/ops-strategy-panel.ts +2 -2
- package/src/panels/orchestration-panel.ts +2 -2
- package/src/panels/panel-list-panel.ts +6 -6
- package/src/panels/plan-dashboard-panel.ts +1 -1
- package/src/panels/plugins-panel.ts +2 -2
- package/src/panels/policy-panel.ts +2 -2
- package/src/panels/polish.ts +3 -3
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-accounts-panel.ts +25 -29
- package/src/panels/provider-health-panel.ts +2 -2
- package/src/panels/provider-stats-panel.ts +3 -3
- package/src/panels/qr-panel.ts +182 -0
- package/src/panels/remote-panel.ts +3 -3
- package/src/panels/routes-panel.ts +3 -3
- package/src/panels/sandbox-panel.ts +2 -2
- package/src/panels/schedule-panel.ts +1 -1
- package/src/panels/scrollable-list-panel.ts +407 -0
- package/src/panels/security-panel.ts +2 -2
- package/src/panels/services-panel.ts +3 -3
- package/src/panels/session-browser-panel.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/skills-panel.ts +6 -6
- package/src/panels/subscription-panel.ts +3 -3
- package/src/panels/symbol-outline-panel.ts +3 -3
- package/src/panels/system-messages-panel.ts +4 -4
- package/src/panels/tasks-panel.ts +2 -2
- package/src/panels/thinking-panel.ts +3 -3
- package/src/panels/token-budget-panel.ts +1 -1
- package/src/panels/tool-inspector-panel.ts +3 -3
- package/src/panels/types.ts +5 -5
- package/src/panels/watchers-panel.ts +3 -3
- package/src/panels/welcome-panel.ts +1 -1
- package/src/panels/worktree-panel.ts +22 -21
- package/src/panels/wrfc-panel.ts +3 -3
- package/src/permissions/prompt.ts +3 -22
- package/src/plugins/loader.ts +15 -304
- package/src/renderer/agent-detail-modal.ts +1 -1
- package/src/renderer/autocomplete-overlay.ts +2 -2
- package/src/renderer/bookmark-modal.ts +1 -1
- package/src/renderer/bottom-bar.ts +2 -2
- package/src/renderer/buffer.ts +1 -1
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/context-inspector.ts +1 -1
- package/src/renderer/conversation-layout.ts +2 -2
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/conversation-surface.ts +2 -2
- package/src/renderer/diff-view.ts +2 -2
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +2 -2
- package/src/renderer/file-tree.ts +2 -2
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/history-search-overlay.ts +2 -2
- package/src/renderer/live-tail-modal.ts +1 -1
- package/src/renderer/markdown.ts +2 -2
- package/src/renderer/modal-factory.ts +3 -3
- package/src/renderer/model-picker-overlay.ts +2 -2
- package/src/renderer/overlay-box.ts +2 -2
- package/src/renderer/panel-composite.ts +1 -1
- package/src/renderer/panel-picker-overlay.ts +2 -2
- package/src/renderer/panel-tab-bar.ts +1 -1
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +2 -2
- package/src/renderer/process-modal.ts +1 -1
- package/src/renderer/profile-picker-modal.ts +2 -2
- package/src/renderer/progress.ts +2 -2
- package/src/renderer/qr-renderer.ts +117 -0
- package/src/renderer/search-overlay.ts +2 -2
- package/src/renderer/selection-modal-overlay.ts +2 -2
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +122 -0
- package/src/renderer/settings-modal.ts +149 -113
- package/src/renderer/shell-surface.ts +1 -1
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tab-strip.ts +2 -2
- package/src/renderer/text-layout.ts +1 -1
- package/src/renderer/thinking.ts +1 -1
- package/src/renderer/tool-call.ts +2 -2
- package/src/renderer/ui-factory.ts +2 -2
- package/src/runtime/bootstrap-command-context.ts +5 -6
- package/src/runtime/bootstrap-command-parts.ts +32 -18
- package/src/runtime/bootstrap-core.ts +3 -2
- package/src/runtime/bootstrap-hook-bridge.ts +15 -174
- package/src/runtime/bootstrap-shell.ts +4 -4
- package/src/runtime/bootstrap.ts +7 -2
- package/src/runtime/context.ts +4 -20
- package/src/runtime/diagnostics/panels/index.ts +6 -6
- package/src/runtime/diagnostics/panels/ops.ts +1 -1
- package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
- package/src/runtime/perf/panel-contracts.ts +32 -0
- package/src/runtime/perf/panel-health-monitor.ts +18 -0
- package/src/runtime/services.ts +5 -5
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -2
- package/src/runtime/store/selectors/index.ts +11 -6
- package/src/runtime/store/state.ts +12 -4
- package/src/runtime/ui-events.ts +1 -0
- package/src/runtime/ui-read-model-helpers.ts +1 -32
- package/src/runtime/ui-read-models-observability-maintenance.ts +1 -81
- package/src/runtime/ui-read-models-observability-options.ts +1 -5
- package/src/runtime/ui-read-models-observability-remote.ts +1 -73
- package/src/runtime/ui-read-models-observability-security.ts +1 -172
- package/src/runtime/ui-read-models-observability-system.ts +1 -217
- package/src/runtime/ui-read-models-observability.ts +1 -59
- package/src/runtime/ui-service-queries.ts +1 -114
- package/src/runtime/ui-services.ts +1 -1
- package/src/shell/ui-openers.ts +1 -1
- package/src/tools/index.ts +1 -186
- package/src/types/grid.ts +48 -0
- package/src/utils/clipboard.ts +21 -0
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +185 -0
- package/src/version.ts +1 -1
- package/src/config/service-registry.ts +0 -1
- package/src/config/subscription-providers.ts +0 -127
- package/src/daemon/facade-composition.ts +0 -398
- package/src/daemon/facade.ts +0 -638
- package/src/daemon/surface-policy.ts +0 -60
- package/src/daemon/types.ts +0 -191
- package/src/runtime/diagnostics/actions.ts +0 -776
- package/src/runtime/diagnostics/index.ts +0 -99
- package/src/runtime/diagnostics/panels/agents.ts +0 -252
- package/src/runtime/diagnostics/panels/events.ts +0 -188
- package/src/runtime/diagnostics/panels/health.ts +0 -242
- package/src/runtime/diagnostics/panels/tasks.ts +0 -251
- package/src/runtime/diagnostics/panels/tool-calls.ts +0 -267
- package/src/runtime/diagnostics/provider.ts +0 -262
- package/src/runtime/store/domains/conversation.ts +0 -181
- package/src/runtime/store/domains/permissions.ts +0 -143
- package/src/runtime/store/helpers/reducers/conversation.ts +0 -228
- package/src/runtime/store/helpers/reducers/lifecycle.ts +0 -440
- package/src/runtime/store/helpers/reducers/shared.ts +0 -60
- package/src/runtime/store/helpers/reducers/sync.ts +0 -555
- package/src/runtime/store/helpers/reducers.ts +0 -30
- package/src/runtime/ui-read-models-core.ts +0 -95
- package/src/runtime/ui-read-models-operations.ts +0 -203
|
@@ -32,43 +32,19 @@ import { getConfigSnapshot } from '../config/index.ts';
|
|
|
32
32
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
33
33
|
import type { ConversationManager } from './conversation';
|
|
34
34
|
import type { SystemMessagesPanel, SystemMessagePriority } from '../panels/system-messages-panel.ts';
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
* Classify a message as high or low priority based on content.
|
|
49
|
-
* Used by routeAuto() when the caller doesn't specify priority.
|
|
50
|
-
*
|
|
51
|
-
* @internal
|
|
52
|
-
*/
|
|
53
|
-
function classifyPriority(message: string): SystemMessagePriority {
|
|
54
|
-
return HIGH_PRIORITY_RE.test(message) ? 'high' : 'low';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export type SystemMessageKind = 'system' | 'operational' | 'wrfc';
|
|
58
|
-
export type SystemMessageTarget = 'conversation' | 'panel' | 'both';
|
|
59
|
-
|
|
60
|
-
function defaultTargetForKind(kind: SystemMessageKind): SystemMessageTarget {
|
|
61
|
-
if (kind === 'wrfc') return 'both';
|
|
62
|
-
return 'panel';
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function classifyKind(message: string): SystemMessageKind {
|
|
66
|
-
if (/^\[WRFC\]/i.test(message)) return 'wrfc';
|
|
67
|
-
if (/^\[(Scan|Local|Agents|MCP|Plugin|Hook|Tool|Exec|Remote|Bridge|Approval)\]/i.test(message)) {
|
|
68
|
-
return 'operational';
|
|
69
|
-
}
|
|
70
|
-
return 'system';
|
|
71
|
-
}
|
|
35
|
+
import {
|
|
36
|
+
classifySystemMessageKind,
|
|
37
|
+
classifySystemMessagePriority,
|
|
38
|
+
defaultSystemMessageTarget,
|
|
39
|
+
resolveSystemMessageDelivery,
|
|
40
|
+
type SystemMessageKind,
|
|
41
|
+
type SystemMessageTarget,
|
|
42
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/system-message-policy';
|
|
43
|
+
|
|
44
|
+
export type {
|
|
45
|
+
SystemMessageKind,
|
|
46
|
+
SystemMessageTarget,
|
|
47
|
+
} from '@pellux/goodvibes-sdk/platform/runtime/system-message-policy';
|
|
72
48
|
|
|
73
49
|
function targetForKind(
|
|
74
50
|
configManager: Pick<ConfigManager, 'getRaw'>,
|
|
@@ -80,21 +56,6 @@ function targetForKind(
|
|
|
80
56
|
return ui.systemMessages;
|
|
81
57
|
}
|
|
82
58
|
|
|
83
|
-
function resolveDelivery(
|
|
84
|
-
target: SystemMessageTarget,
|
|
85
|
-
hasPanel: boolean,
|
|
86
|
-
): { readonly toPanel: boolean; readonly toConversation: boolean } {
|
|
87
|
-
if (target === 'both') {
|
|
88
|
-
return { toPanel: hasPanel, toConversation: true };
|
|
89
|
-
}
|
|
90
|
-
if (target === 'conversation') {
|
|
91
|
-
return { toPanel: false, toConversation: true };
|
|
92
|
-
}
|
|
93
|
-
return hasPanel
|
|
94
|
-
? { toPanel: true, toConversation: false }
|
|
95
|
-
: { toPanel: false, toConversation: true };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
59
|
// ---------------------------------------------------------------------------
|
|
99
60
|
// SystemMessageRouter
|
|
100
61
|
// ---------------------------------------------------------------------------
|
|
@@ -107,7 +68,7 @@ export class SystemMessageRouter {
|
|
|
107
68
|
constructor(
|
|
108
69
|
private readonly conversation: ConversationManager,
|
|
109
70
|
private panel: SystemMessagesPanel | null,
|
|
110
|
-
private readonly getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget =
|
|
71
|
+
private readonly getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
|
|
111
72
|
) {}
|
|
112
73
|
|
|
113
74
|
// ── Public API ────────────────────────────────────────────────────────────
|
|
@@ -127,7 +88,7 @@ export class SystemMessageRouter {
|
|
|
127
88
|
kind: SystemMessageKind,
|
|
128
89
|
): void {
|
|
129
90
|
const target = this.getTargetForKind(kind);
|
|
130
|
-
const delivery =
|
|
91
|
+
const delivery = resolveSystemMessageDelivery(target, this.panel !== null);
|
|
131
92
|
if (delivery.toPanel) {
|
|
132
93
|
this.panel?.push(message, priority);
|
|
133
94
|
}
|
|
@@ -137,7 +98,7 @@ export class SystemMessageRouter {
|
|
|
137
98
|
}
|
|
138
99
|
|
|
139
100
|
routeSystemMessage(message: string, priority: SystemMessagePriority): void {
|
|
140
|
-
this.routeTypedSystemMessage(message, priority,
|
|
101
|
+
this.routeTypedSystemMessage(message, priority, classifySystemMessageKind(message));
|
|
141
102
|
}
|
|
142
103
|
|
|
143
104
|
/**
|
|
@@ -149,8 +110,8 @@ export class SystemMessageRouter {
|
|
|
149
110
|
* @param message - Message text.
|
|
150
111
|
*/
|
|
151
112
|
routeAuto(message: string): void {
|
|
152
|
-
const priority =
|
|
153
|
-
this.routeTypedSystemMessage(message, priority,
|
|
113
|
+
const priority: SystemMessagePriority = classifySystemMessagePriority(message);
|
|
114
|
+
this.routeTypedSystemMessage(message, priority, classifySystemMessageKind(message));
|
|
154
115
|
}
|
|
155
116
|
|
|
156
117
|
/**
|
|
@@ -204,7 +165,7 @@ export class SystemMessageRouter {
|
|
|
204
165
|
export function createSystemMessageRouter(
|
|
205
166
|
conversation: ConversationManager,
|
|
206
167
|
panel: SystemMessagesPanel | null = null,
|
|
207
|
-
getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget =
|
|
168
|
+
getTargetForKind: (kind: SystemMessageKind) => SystemMessageTarget = defaultSystemMessageTarget,
|
|
208
169
|
): SystemMessageRouter {
|
|
209
170
|
return new SystemMessageRouter(conversation, panel, getTargetForKind);
|
|
210
171
|
}
|
package/src/daemon/cli.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
1
|
+
import { homedir, networkInterfaces } from 'node:os';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
3
4
|
import { RuntimeEventBus } from '@pellux/goodvibes-sdk/platform/runtime/events/index';
|
|
4
5
|
import { createRuntimeStore } from '../runtime/store/index.ts';
|
|
@@ -8,6 +9,13 @@ import { HttpListener } from '@pellux/goodvibes-sdk/platform/daemon/http-listene
|
|
|
8
9
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
9
10
|
import { GlobalNetworkTransportInstaller } from '@pellux/goodvibes-sdk/platform/runtime/network/index';
|
|
10
11
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
|
|
12
|
+
import {
|
|
13
|
+
getOrCreateCompanionToken,
|
|
14
|
+
buildCompanionConnectionInfo,
|
|
15
|
+
encodeConnectionPayload,
|
|
16
|
+
formatConnectionBlock,
|
|
17
|
+
} from '@pellux/goodvibes-sdk/platform/pairing/index';
|
|
18
|
+
import { generateQrMatrix, renderQrToString } from '@pellux/goodvibes-sdk/platform/pairing/qr-generator';
|
|
11
19
|
|
|
12
20
|
type DaemonCliOwnership = {
|
|
13
21
|
readonly workingDirectory: string;
|
|
@@ -19,6 +27,32 @@ type DaemonCliTokens = {
|
|
|
19
27
|
readonly httpToken: string | undefined;
|
|
20
28
|
};
|
|
21
29
|
|
|
30
|
+
function getLocalNetworkIp(): string {
|
|
31
|
+
const nets = networkInterfaces();
|
|
32
|
+
for (const name of Object.keys(nets)) {
|
|
33
|
+
for (const net of nets[name] ?? []) {
|
|
34
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
35
|
+
return net.address;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return 'localhost';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readBootstrapPassword(credentialPath: string): string | undefined {
|
|
43
|
+
try {
|
|
44
|
+
const content = readFileSync(credentialPath, 'utf-8');
|
|
45
|
+
for (const line of content.split('\n')) {
|
|
46
|
+
if (line.startsWith('password=')) {
|
|
47
|
+
return line.slice('password='.length).trim();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
// credential file may not exist yet
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
function resolveDaemonCliOwnership(): DaemonCliOwnership {
|
|
23
57
|
return {
|
|
24
58
|
workingDirectory: process.cwd(),
|
|
@@ -58,26 +92,68 @@ async function main(): Promise<void> {
|
|
|
58
92
|
});
|
|
59
93
|
const { daemonToken, httpToken } = readDaemonCliTokens(process.env);
|
|
60
94
|
|
|
61
|
-
|
|
62
|
-
|
|
95
|
+
// If no explicit daemon token is set, use the companion token so mobile apps can connect.
|
|
96
|
+
const companionTokenRecord = getOrCreateCompanionToken('tui');
|
|
97
|
+
const effectiveDaemonToken = daemonToken ?? companionTokenRecord.token;
|
|
98
|
+
const effectiveHttpToken = httpToken ?? effectiveDaemonToken;
|
|
99
|
+
|
|
100
|
+
daemon.enable({ daemon: true }, effectiveDaemonToken);
|
|
101
|
+
listener.enable({ httpListener: true }, effectiveHttpToken);
|
|
63
102
|
|
|
64
103
|
await Promise.all([
|
|
65
104
|
daemon.start(),
|
|
66
105
|
config.get('danger.httpListener') ? listener.start() : Promise.resolve(),
|
|
67
106
|
]);
|
|
68
107
|
|
|
108
|
+
const abortController = new AbortController();
|
|
109
|
+
|
|
69
110
|
const shutdown = async (): Promise<void> => {
|
|
70
|
-
|
|
111
|
+
abortController.abort();
|
|
112
|
+
const SHUTDOWN_DEADLINE_MS = 15_000;
|
|
113
|
+
const timeout = new Promise<'timeout'>((resolve) =>
|
|
114
|
+
setTimeout(() => resolve('timeout'), SHUTDOWN_DEADLINE_MS)
|
|
115
|
+
);
|
|
116
|
+
const stop = Promise.allSettled([listener.stop(), daemon.stop()]).then(() => 'done' as const);
|
|
117
|
+
const result = await Promise.race([stop, timeout]);
|
|
118
|
+
if (result === 'timeout') {
|
|
119
|
+
logger.warn('shutdown deadline exceeded — forcing exit');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
71
122
|
process.exit(0);
|
|
72
123
|
};
|
|
73
124
|
|
|
74
|
-
|
|
75
|
-
|
|
125
|
+
let shutdownInFlight = false;
|
|
126
|
+
const handleSignal = (): void => {
|
|
127
|
+
if (shutdownInFlight) return;
|
|
128
|
+
shutdownInFlight = true;
|
|
129
|
+
void shutdown();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
process.on('SIGINT', handleSignal);
|
|
133
|
+
process.on('SIGTERM', handleSignal);
|
|
76
134
|
|
|
77
135
|
logger.info('goodvibes daemon host started', {
|
|
78
136
|
daemon: config.get('danger.daemon'),
|
|
79
137
|
httpListener: config.get('danger.httpListener'),
|
|
80
138
|
});
|
|
139
|
+
|
|
140
|
+
// Print companion connection info + QR code to stdout.
|
|
141
|
+
// Use the config-driven control plane port, not a hardcoded default.
|
|
142
|
+
const daemonPort = config.get('controlPlane.port');
|
|
143
|
+
const daemonHost = String(process.env.GOODVIBES_DAEMON_HOST ?? getLocalNetworkIp());
|
|
144
|
+
const daemonUrl = `http://${daemonHost}:${daemonPort}`;
|
|
145
|
+
const bootstrapPassword = readBootstrapPassword(userAuth.getBootstrapCredentialPath());
|
|
146
|
+
const connectionInfo = buildCompanionConnectionInfo({
|
|
147
|
+
daemonUrl,
|
|
148
|
+
token: companionTokenRecord.token,
|
|
149
|
+
password: bootstrapPassword,
|
|
150
|
+
surface: 'tui',
|
|
151
|
+
});
|
|
152
|
+
const payload = encodeConnectionPayload(connectionInfo);
|
|
153
|
+
const qrMatrix = generateQrMatrix(payload);
|
|
154
|
+
const qrString = renderQrToString(qrMatrix);
|
|
155
|
+
// eslint-disable-next-line no-console
|
|
156
|
+
console.log(formatConnectionBlock(connectionInfo, qrString));
|
|
81
157
|
}
|
|
82
158
|
|
|
83
159
|
void main().catch(async (error) => {
|
|
@@ -62,6 +62,8 @@ export interface CommandUiActions {
|
|
|
62
62
|
model: { id: string; provider: string; displayName: string; registryKey: string };
|
|
63
63
|
effort: string;
|
|
64
64
|
contextCap?: number | null;
|
|
65
|
+
/** Which config target to write the selected model to. Defaults to 'main'. */
|
|
66
|
+
target?: import('./model-picker.ts').ModelPickerTarget;
|
|
65
67
|
}) => void;
|
|
66
68
|
clearScreen?: () => void;
|
|
67
69
|
activatePlan?: (planId: string, task: string) => void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
2
|
import { buildMcpAttackPathReview } from '@pellux/goodvibes-sdk/platform/runtime/mcp/index';
|
|
3
3
|
import { buildKnowledgeInjectionPrompt, selectKnowledgeForTask } from '@pellux/goodvibes-sdk/platform/state/knowledge-injection';
|
|
4
|
-
import { listBuiltinSubscriptionProviders } from '
|
|
4
|
+
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config/subscription-providers';
|
|
5
5
|
import { requireReadModels, requireSubscriptionManager, requireTokenAuditor } from './runtime-services.ts';
|
|
6
6
|
import { getMemoryApi } from './recall-query.ts';
|
|
7
7
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ServiceRegistry } from '
|
|
1
|
+
import { ServiceRegistry } from '@pellux/goodvibes-sdk/platform/config/service-registry';
|
|
2
2
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
3
3
|
import { evaluateSessionMaintenance, formatSessionMaintenanceLines } from '@pellux/goodvibes-sdk/platform/runtime/session-maintenance';
|
|
4
4
|
import { estimateConversationTokens } from '@pellux/goodvibes-sdk/platform/core/context-compaction';
|
|
@@ -5,7 +5,7 @@ import { discoverSkills } from '../../panels/skills-panel.ts';
|
|
|
5
5
|
import { buildSandboxReview, isRunningInWsl } from '@pellux/goodvibes-sdk/platform/runtime/sandbox/manager';
|
|
6
6
|
import { renderQemuWrapperTemplate } from '@pellux/goodvibes-sdk/platform/runtime/sandbox/qemu-wrapper-template';
|
|
7
7
|
import { getPluginDirectories } from '../../plugins/loader';
|
|
8
|
-
import { listBuiltinSubscriptionProviders } from '
|
|
8
|
+
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config/subscription-providers';
|
|
9
9
|
import type { SetupReviewSnapshot } from './local-setup-transfer.ts';
|
|
10
10
|
import { requireProviderApi, requireReadModels, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
|
|
11
11
|
|
|
@@ -2,7 +2,7 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
3
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
4
|
import { VERSION } from '../../version.ts';
|
|
5
|
-
import { listBuiltinSubscriptionProviders } from '
|
|
5
|
+
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config/subscription-providers';
|
|
6
6
|
import { handleLocalAuthCommand } from './local-auth-runtime.ts';
|
|
7
7
|
import { buildAuthInspectionSnapshot, inspectProviderAuth } from '@pellux/goodvibes-sdk/platform/runtime/auth/inspection';
|
|
8
8
|
import { requireProfileManager, requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
+
import { openCommandPanel } from './runtime-services.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register the /qrcode command.
|
|
6
|
+
*
|
|
7
|
+
* Opens the QR Code panel which displays a scannable QR code for
|
|
8
|
+
* companion app pairing, along with connection URL, token, and username.
|
|
9
|
+
*/
|
|
10
|
+
export function registerQrcodeRuntimeCommands(registry: CommandRegistry): void {
|
|
11
|
+
registry.register({
|
|
12
|
+
name: 'qrcode',
|
|
13
|
+
aliases: ['qr', 'pair'],
|
|
14
|
+
description: 'Open the QR code panel for companion app pairing',
|
|
15
|
+
usage: '',
|
|
16
|
+
handler(_args, ctx) {
|
|
17
|
+
openCommandPanel(ctx, 'qr-code');
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -4,7 +4,7 @@ import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
|
4
4
|
import { createOAuthLocalListener } from '@pellux/goodvibes-sdk/platform/config/oauth-local-listener';
|
|
5
5
|
import { beginOpenAICodexLogin, exchangeOpenAICodexCode } from '@pellux/goodvibes-sdk/platform/config/openai-codex-auth';
|
|
6
6
|
import type { OAuthProviderConfig, ProviderSubscription } from '@pellux/goodvibes-sdk/platform/config/subscriptions';
|
|
7
|
-
import { getSubscriptionProviderConfig, listAvailableSubscriptionProviders } from '
|
|
7
|
+
import { getSubscriptionProviderConfig, listAvailableSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config/subscription-providers';
|
|
8
8
|
import { inspectProviderAuth } from '@pellux/goodvibes-sdk/platform/runtime/auth/inspection';
|
|
9
9
|
import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils/open-external';
|
|
10
10
|
import { requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
|
package/src/input/commands.ts
CHANGED
|
@@ -52,6 +52,7 @@ import { registerProviderAccountsRuntimeCommands } from './commands/provider-acc
|
|
|
52
52
|
import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.ts';
|
|
53
53
|
import { registerIntelligenceRuntimeCommands } from './commands/intelligence-runtime.ts';
|
|
54
54
|
import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
|
|
55
|
+
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
58
|
* registerBuiltinCommands - Register all built-in slash commands into the registry.
|
|
@@ -98,6 +99,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
98
99
|
registerLocalAuthRuntimeCommands(registry);
|
|
99
100
|
registerIntelligenceRuntimeCommands(registry);
|
|
100
101
|
registerConversationRuntimeCommands(registry);
|
|
102
|
+
registerQrcodeRuntimeCommands(registry);
|
|
101
103
|
registerLocalRuntimeCommands(registry);
|
|
102
104
|
registerSessionWorkflowCommands(registry);
|
|
103
105
|
registerDiscoveryRuntimeCommands(registry);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { copyToClipboard, pasteFromClipboard, pasteImageFromClipboard } from '
|
|
3
|
-
import type { InfiniteBuffer } from '
|
|
2
|
+
import { copyToClipboard, pasteFromClipboard, pasteImageFromClipboard } from '../utils/clipboard.ts';
|
|
3
|
+
import type { InfiniteBuffer } from '../core/history.ts';
|
|
4
4
|
import type { ConversationManager } from '../core/conversation';
|
|
5
5
|
import type { PermissionCategory } from '@pellux/goodvibes-sdk/platform/permissions/manager';
|
|
6
6
|
import type { ContentPart } from '@pellux/goodvibes-sdk/platform/providers/interface';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core/tokenizer';
|
|
2
|
-
import type { InfiniteBuffer } from '
|
|
2
|
+
import type { InfiniteBuffer } from '../core/history.ts';
|
|
3
3
|
import type { CommandContext, CommandRegistry } from './command-registry.ts';
|
|
4
4
|
import { AutocompleteEngine } from './autocomplete.ts';
|
|
5
5
|
import { FilePickerModal } from './file-picker.ts';
|
|
@@ -166,6 +166,12 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
166
166
|
searchManager: context.searchManager,
|
|
167
167
|
scroll: context.scroll,
|
|
168
168
|
getScrollTop: context.getScrollTop,
|
|
169
|
+
openModelPickerWithTarget: context.commandContext?.openModelPicker
|
|
170
|
+
? (target: import('./model-picker.ts').ModelPickerTarget) => {
|
|
171
|
+
context.modelPicker.target = target;
|
|
172
|
+
context.commandContext!.openModelPicker!();
|
|
173
|
+
}
|
|
174
|
+
: undefined,
|
|
169
175
|
}, token);
|
|
170
176
|
context.selectionCallback = modalRoute.selectionCallback;
|
|
171
177
|
context.helpOverlayActive = modalRoute.helpOverlayActive;
|
|
@@ -215,7 +215,10 @@ type SettingsRouteState = {
|
|
|
215
215
|
nextCategory: () => void;
|
|
216
216
|
editBackspace: () => void;
|
|
217
217
|
editChar: (char: string) => void;
|
|
218
|
+
pendingModelPickerTarget: import('./model-picker.ts').ModelPickerTarget | null;
|
|
218
219
|
};
|
|
220
|
+
/** Called when the settings modal requests the model picker for a non-main target. */
|
|
221
|
+
openModelPickerWithTarget?: (target: import('./model-picker.ts').ModelPickerTarget) => void;
|
|
219
222
|
requestRender: () => void;
|
|
220
223
|
handleEscape: () => void;
|
|
221
224
|
};
|
|
@@ -231,7 +234,14 @@ export function handleSettingsModalToken(state: SettingsRouteState, token: Input
|
|
|
231
234
|
if (token.logicalName === 'enter' || (token.logicalName === 'space' && !state.settingsModal.editingMode)) {
|
|
232
235
|
if (state.settingsModal.editingMode) state.settingsModal.commitEdit();
|
|
233
236
|
else if (state.settingsModal.currentCategory === 'flags') state.settingsModal.toggleSelectedFlag();
|
|
234
|
-
else
|
|
237
|
+
else {
|
|
238
|
+
state.settingsModal.activateSelected();
|
|
239
|
+
const pickerTarget = state.settingsModal.pendingModelPickerTarget;
|
|
240
|
+
if (pickerTarget !== null) {
|
|
241
|
+
state.settingsModal.pendingModelPickerTarget = null;
|
|
242
|
+
state.openModelPickerWithTarget?.(pickerTarget);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
235
245
|
} else if ((token.logicalName === 'left' || token.logicalName === 'right') && !state.settingsModal.editingMode) {
|
|
236
246
|
state.settingsModal.adjustSelected(token.logicalName, token.shift ? 10 : 1);
|
|
237
247
|
} else if (token.logicalName === 'up') state.settingsModal.moveUp();
|
|
@@ -241,7 +251,14 @@ export function handleSettingsModalToken(state: SettingsRouteState, token: Input
|
|
|
241
251
|
} else if (token.type === 'text') {
|
|
242
252
|
if (token.value === ' ' && !state.settingsModal.editingMode) {
|
|
243
253
|
if (state.settingsModal.currentCategory === 'flags') state.settingsModal.toggleSelectedFlag();
|
|
244
|
-
else
|
|
254
|
+
else {
|
|
255
|
+
state.settingsModal.activateSelected();
|
|
256
|
+
const pickerTarget = state.settingsModal.pendingModelPickerTarget;
|
|
257
|
+
if (pickerTarget !== null) {
|
|
258
|
+
state.settingsModal.pendingModelPickerTarget = null;
|
|
259
|
+
state.openModelPickerWithTarget?.(pickerTarget);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
245
262
|
} else if (state.settingsModal.editingMode) {
|
|
246
263
|
state.settingsModal.editChar(token.value);
|
|
247
264
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core/tokenizer';
|
|
2
|
-
import type { InfiniteBuffer } from '
|
|
2
|
+
import type { InfiniteBuffer } from '../core/history.ts';
|
|
3
3
|
import type { SelectionResult, SelectionModal } from './selection-modal.ts';
|
|
4
4
|
import type { BookmarkModal } from './bookmark-modal.ts';
|
|
5
5
|
import type { SettingsModal } from './settings-modal.ts';
|
|
@@ -72,6 +72,8 @@ export type ModalTokenRouteState = {
|
|
|
72
72
|
searchManager: SearchManager;
|
|
73
73
|
scroll: (delta: number) => void;
|
|
74
74
|
getScrollTop: () => number;
|
|
75
|
+
/** Callback to open the model picker with a specific target (helper or tool). Optional — only wired when available. */
|
|
76
|
+
openModelPickerWithTarget?: (target: import('./model-picker.ts').ModelPickerTarget) => void;
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
export function handleModalTokenRoutes(state: ModalTokenRouteState, token: InputToken): {
|
|
@@ -117,6 +119,7 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
117
119
|
|
|
118
120
|
if (handleSettingsModalToken({
|
|
119
121
|
settingsModal: state.settingsModal,
|
|
122
|
+
openModelPickerWithTarget: state.openModelPickerWithTarget,
|
|
120
123
|
requestRender: state.requestRender,
|
|
121
124
|
handleEscape: state.handleEscape,
|
|
122
125
|
}, token)) {
|
|
@@ -57,9 +57,11 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
57
57
|
if (selected.reasoningEffort && selected.reasoningEffort.length > 0) {
|
|
58
58
|
state.modelPicker.showEffortPicker(selected, currentEffort);
|
|
59
59
|
} else {
|
|
60
|
+
const target = state.modelPicker.target;
|
|
60
61
|
state.commandContext?.completeModelSelection?.({
|
|
61
62
|
model: selected,
|
|
62
63
|
effort: currentEffort,
|
|
64
|
+
target,
|
|
63
65
|
});
|
|
64
66
|
state.modelPicker.close();
|
|
65
67
|
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
@@ -76,7 +78,7 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
76
78
|
} else if (mode === 'effort') {
|
|
77
79
|
const model = state.modelPicker.pendingModel;
|
|
78
80
|
const effort = state.modelPicker.effortLevels[idx];
|
|
79
|
-
if (model && effort) state.commandContext?.completeModelSelection?.({ model, effort });
|
|
81
|
+
if (model && effort) state.commandContext?.completeModelSelection?.({ model, effort, target: state.modelPicker.target });
|
|
80
82
|
state.modelPicker.close();
|
|
81
83
|
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
82
84
|
} else if (mode === 'contextCap') {
|
|
@@ -86,7 +88,7 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
86
88
|
const parsedCap = rawInput.length > 0 ? parseInt(rawInput, 10) : null;
|
|
87
89
|
const validCap = parsedCap !== null && parsedCap > 0 && parsedCap <= 10_000_000 ? parsedCap : null;
|
|
88
90
|
const effort = state.commandContext?.session.runtime.reasoningEffort ?? 'medium';
|
|
89
|
-
state.commandContext?.completeModelSelection?.({ model: capModel, effort, contextCap: validCap });
|
|
91
|
+
state.commandContext?.completeModelSelection?.({ model: capModel, effort, contextCap: validCap, target: state.modelPicker.target });
|
|
90
92
|
}
|
|
91
93
|
state.modelPicker.close();
|
|
92
94
|
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core/tokenizer';
|
|
2
|
-
import type { InfiniteBuffer } from '
|
|
2
|
+
import type { InfiniteBuffer } from '../core/history.ts';
|
|
3
3
|
import type { SearchManager } from './search.ts';
|
|
4
4
|
import type { HistorySearch } from './input-history.ts';
|
|
5
5
|
|
package/src/input/handler.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InputTokenizer } from '@pellux/goodvibes-sdk/platform/core/tokenizer';
|
|
2
2
|
import { SelectionManager } from './selection.ts';
|
|
3
|
-
import type { InfiniteBuffer } from '
|
|
3
|
+
import type { InfiniteBuffer } from '../core/history.ts';
|
|
4
4
|
import type { CommandRegistry, CommandContext } from './command-registry.ts';
|
|
5
5
|
import { AutocompleteEngine } from './autocomplete.ts';
|
|
6
6
|
import { FilePickerModal } from './file-picker.ts';
|
|
@@ -7,6 +7,14 @@ import type { ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers/
|
|
|
7
7
|
|
|
8
8
|
export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Which config keys the model picker writes to on commit.
|
|
12
|
+
* 'main' → provider.provider + provider.model (default)
|
|
13
|
+
* 'helper' → helper.globalProvider + helper.globalModel (+ helper.enabled: true)
|
|
14
|
+
* 'tool' → tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
|
|
15
|
+
*/
|
|
16
|
+
export type ModelPickerTarget = 'main' | 'helper' | 'tool';
|
|
17
|
+
|
|
10
18
|
/**
|
|
11
19
|
* Pricing tier filter.
|
|
12
20
|
* 'paid' matches ModelDefinition tiers 'standard' and 'premium' for forward-compat
|
|
@@ -129,6 +137,8 @@ export class ModelPickerModal {
|
|
|
129
137
|
|
|
130
138
|
public active = false;
|
|
131
139
|
public mode: PickerMode = 'model';
|
|
140
|
+
/** Which config target this picker session will write to on commit. */
|
|
141
|
+
public target: ModelPickerTarget = 'main';
|
|
132
142
|
public searchFocused = false;
|
|
133
143
|
/** Tracks the mode we came from, for back-navigation. */
|
|
134
144
|
public previousMode: PickerMode | null = null;
|
|
@@ -288,6 +298,7 @@ export class ModelPickerModal {
|
|
|
288
298
|
close(): void {
|
|
289
299
|
this.active = false;
|
|
290
300
|
this.mode = 'model';
|
|
301
|
+
this.target = 'main';
|
|
291
302
|
this.models = [];
|
|
292
303
|
this.providers = [];
|
|
293
304
|
this.pendingModel = null;
|
package/src/input/search.ts
CHANGED
package/src/input/selection.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { InfiniteBuffer } from '
|
|
2
|
-
import type { Cell } from '
|
|
1
|
+
import type { InfiniteBuffer } from '../core/history.ts';
|
|
2
|
+
import type { Cell } from '../types/grid.ts';
|
|
3
3
|
|
|
4
4
|
export interface SelectionPoint {
|
|
5
5
|
col: number;
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { CONFIG_SCHEMA, type ConfigSetting, type ConfigKey, type PersistedFlagState } from '@pellux/goodvibes-sdk/platform/config/schema';
|
|
14
|
+
import type { ModelPickerTarget } from './model-picker.ts';
|
|
14
15
|
import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config/manager';
|
|
15
16
|
import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config/subscriptions';
|
|
16
|
-
import { listBuiltinSubscriptionProviders } from '
|
|
17
|
+
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config/subscription-providers';
|
|
17
18
|
import type { ProviderAuthFreshness, ProviderAuthRoute } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
18
19
|
import { getResolvedSettingLookup } from '@pellux/goodvibes-sdk/platform/runtime/settings/control-plane';
|
|
19
20
|
import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
|
|
@@ -84,6 +85,16 @@ export interface SubscriptionEntry {
|
|
|
84
85
|
nextActions?: string[];
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Map a config key to the model picker target it should open, or null if the
|
|
90
|
+
* setting should use the normal inline text-edit flow.
|
|
91
|
+
*/
|
|
92
|
+
function _modelPickerTargetForKey(key: string): ModelPickerTarget | null {
|
|
93
|
+
if (key === 'helper.globalProvider' || key === 'helper.globalModel') return 'helper';
|
|
94
|
+
if (key === 'tools.llmProvider' || key === 'tools.llmModel') return 'tool';
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
function roundToPrecision(value: number, precision: number): number {
|
|
88
99
|
const factor = 10 ** precision;
|
|
89
100
|
return Math.round(value * factor) / factor;
|
|
@@ -121,6 +132,12 @@ export class SettingsModal {
|
|
|
121
132
|
public editBuffer = '';
|
|
122
133
|
/** Server awaiting explicit allow-all confirmation, if any. */
|
|
123
134
|
public mcpAllowAllConfirmationTarget: string | null = null;
|
|
135
|
+
/**
|
|
136
|
+
* Set by activateSelected() when the highlighted setting should open the
|
|
137
|
+
* model picker rather than entering inline text edit mode.
|
|
138
|
+
* Consumed and cleared by the route handler after each Enter/Space action.
|
|
139
|
+
*/
|
|
140
|
+
public pendingModelPickerTarget: ModelPickerTarget | null = null;
|
|
124
141
|
/** Provider awaiting explicit logout confirmation, if any. */
|
|
125
142
|
public subscriptionLogoutConfirmationTarget: string | null = null;
|
|
126
143
|
|
|
@@ -307,6 +324,13 @@ export class SettingsModal {
|
|
|
307
324
|
|
|
308
325
|
const { setting } = entry;
|
|
309
326
|
|
|
327
|
+
// Delegate provider/model picker settings to the model picker UI
|
|
328
|
+
const pickerTarget = _modelPickerTargetForKey(setting.key);
|
|
329
|
+
if (pickerTarget !== null) {
|
|
330
|
+
this.pendingModelPickerTarget = pickerTarget;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
310
334
|
if (setting.type === 'boolean') {
|
|
311
335
|
const newVal = !entry.currentValue;
|
|
312
336
|
this._setValue(setting.key, newVal);
|
|
@@ -522,7 +546,9 @@ export class SettingsModal {
|
|
|
522
546
|
}
|
|
523
547
|
|
|
524
548
|
for (const setting of CONFIG_SCHEMA) {
|
|
525
|
-
const
|
|
549
|
+
const rawCat = setting.key.split('.')[0] as string;
|
|
550
|
+
// Route helper.* settings into the tools group for unified display
|
|
551
|
+
const cat = (rawCat === 'helper' ? 'tools' : rawCat) as SettingsCategory;
|
|
526
552
|
if (!this.groups.has(cat)) continue;
|
|
527
553
|
const currentValue = configManager.get(setting.key as ConfigKey);
|
|
528
554
|
const resolved = getResolvedSettingLookup(configManager, setting.key as ConfigKey)?.entry;
|
|
@@ -702,7 +728,9 @@ export class SettingsModal {
|
|
|
702
728
|
try {
|
|
703
729
|
this.configManager.setDynamic(key, value);
|
|
704
730
|
// Update the cached entry in-place — avoids full schema re-scan on each edit
|
|
705
|
-
const
|
|
731
|
+
const rawCat = key.split('.')[0] as string;
|
|
732
|
+
// helper.* entries are stored in the tools group
|
|
733
|
+
const cat = (rawCat === 'helper' ? 'tools' : rawCat) as SettingsCategory;
|
|
706
734
|
const entries = this.groups.get(cat);
|
|
707
735
|
if (entries) {
|
|
708
736
|
const entry = entries.find(e => e.setting.key === key);
|
package/src/main.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { Compositor } from './renderer/compositor.ts';
|
|
7
|
-
import { type Line } from '
|
|
7
|
+
import { type Line } from './types/grid.ts';
|
|
8
8
|
import { UIFactory } from './renderer/ui-factory.ts';
|
|
9
9
|
import { Orchestrator } from './core/orchestrator';
|
|
10
10
|
import { InputHandler } from './input/handler.ts';
|