@pellux/goodvibes-tui 0.18.13 → 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 +122 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -2
- 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-feed.ts +6 -0
- package/src/input/handler-modal-routes.ts +19 -2
- package/src/input/handler-modal-token-routes.ts +3 -0
- package/src/input/handler-picker-routes.ts +4 -2
- package/src/input/model-picker.ts +11 -0
- package/src/input/settings-modal.ts +31 -3
- package/src/panels/agent-logs-panel.ts +23 -24
- package/src/panels/builtin/session.ts +66 -0
- package/src/panels/builtin/shared.ts +1 -1
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-accounts-panel.ts +23 -27
- package/src/panels/qr-panel.ts +182 -0
- package/src/panels/scrollable-list-panel.ts +407 -0
- package/src/panels/services-panel.ts +1 -1
- package/src/panels/subscription-panel.ts +1 -1
- package/src/panels/worktree-panel.ts +20 -19
- package/src/renderer/qr-renderer.ts +117 -0
- package/src/renderer/settings-modal-helpers.ts +122 -0
- package/src/renderer/settings-modal.ts +147 -111
- package/src/runtime/bootstrap-command-context.ts +1 -1
- package/src/runtime/bootstrap-command-parts.ts +31 -15
- package/src/runtime/bootstrap.ts +6 -1
- package/src/runtime/diagnostics/panels/index.ts +5 -5
- package/src/runtime/services.ts +1 -1
- package/src/runtime/store/domains/domain-read-matrix.ts +0 -2
- package/src/runtime/ui-events.ts +1 -46
- 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/version.ts +1 -1
- package/src/config/service-registry.ts +0 -1
- package/src/config/subscription-providers.ts +0 -1
- 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 -1
- package/src/runtime/store/domains/permissions.ts +0 -1
- package/src/runtime/store/helpers/reducers/conversation.ts +0 -1
- package/src/runtime/store/helpers/reducers/lifecycle.ts +0 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,128 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.18.17] — 2026-04-16
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **Companion pairing token registered with embedded daemon**: `src/runtime/bootstrap.ts` now loads the persistent companion-pairing token via `getOrCreateCompanionToken('tui')` and passes it as `sharedDaemonToken` to `startExternalServices`. The TUI's QR panel advertises this token as the bearer for phone pairing; before this fix, the embedded daemon was started with no shared token and rejected every scanned token with `authenticated: false, authMode: "invalid"`
|
|
12
|
+
- **QR code visual alignment**: `src/renderer/qr-renderer.ts` now uses `leftPad = 1` (down from 2) and prepends a single top quiet-band row. The QR's finder patterns now register symmetrically on both axes; previous rendering was mis-aligned by one cell horizontally and had no top quiet band
|
|
13
|
+
|
|
14
|
+
### Dependencies
|
|
15
|
+
|
|
16
|
+
- Bumped `@pellux/goodvibes-sdk` 0.18.36 → 0.18.37, picking up: `sharedDaemonToken`/`sharedHttpListenerToken` factory options on `startHostServices`, bootstrap credential drift detection that warns when `auth-bootstrap.txt` falls out of sync with `auth-users.json`
|
|
17
|
+
- Regenerated `docs/foundation-artifacts/*` against SDK 0.18.37
|
|
18
|
+
|
|
19
|
+
### Tests
|
|
20
|
+
|
|
21
|
+
- Updated `src/test/runtime/bootstrap-services.test.ts` `daemonEnable`/`listenerEnable` expectations to include the new second argument (`undefined` when no shared token is supplied)
|
|
22
|
+
- Test suite: 437/437 passing, typecheck clean, architecture check green
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## [0.18.16] — 2026-04-16
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
- **resolveToolLLM tests**: enabled `tools.llmEnabled` by default in `createTestManagers()` (`src/test/helpers/test-managers.ts`) so tool LLM resolution tests exercise the resolution logic directly; previously every test hit the gate and resolved to `null`
|
|
31
|
+
- **Domain boundary contract test (GC-ARCH-001)**: removed `'conversation'` and `'permissions'` from the `DOMAINS` array in `src/runtime/store/domains/domain-read-matrix.ts` — the files they referenced were deleted in 0.18.15 and the filesystem↔array consistency check was failing
|
|
32
|
+
|
|
33
|
+
### Architecture
|
|
34
|
+
|
|
35
|
+
- **settings-modal decomposition**: extracted 10 pure helpers (`formatValue`, `valueColor`, `flagStateColor`, `mcpTrustColor`, `subscriptionStateColor`, `inferSubscriptionRouteReason`, `CATEGORY_LABELS`, `SETTING_LABELS`, `getSettingLabel`, `describeUiRouting`) into `src/renderer/settings-modal-helpers.ts`. `settings-modal.ts` drops from 844 → 737 lines, back under the 800-line architecture cap
|
|
36
|
+
|
|
37
|
+
### Dependencies
|
|
38
|
+
|
|
39
|
+
- Bumped `@pellux/goodvibes-sdk` 0.18.33 → 0.18.36, picking up: daemon shutdown symmetry, event bus iteration fix, atomic session writes, rate limiter TTL + LRU + sweep, `fetchWithTimeout` helper, restored port honoring in `resolveHostBinding` for `local`/`network` hostModes, and restored constructor-injected port/host in `resolveDaemonFacadeRuntime`
|
|
40
|
+
- Regenerated `docs/foundation-artifacts/*` against the new SDK
|
|
41
|
+
|
|
42
|
+
### Tests & Checks
|
|
43
|
+
|
|
44
|
+
- Test suite: 437/437 passing (was 431/437 after 0.18.15)
|
|
45
|
+
- Architecture check: passing (was failing with `settings-modal.ts` 844 > 800-line cap)
|
|
46
|
+
- Typecheck: clean
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## [0.18.15] — 2026-04-16
|
|
51
|
+
|
|
52
|
+
### Correctness Fix
|
|
53
|
+
|
|
54
|
+
- **daemon SIGINT/SIGTERM drain**: `src/daemon/cli.ts` — added AbortController signaling for in-flight requests, 15-second `Promise.race` shutdown deadline, hard `process.exit(1)` if deadline exceeded, debounced double-signal guard (`shutdownInFlight` flag)
|
|
55
|
+
|
|
56
|
+
### Dead Code Removal (Tier 3 items 15-17)
|
|
57
|
+
|
|
58
|
+
- Deleted 15 TUI mirror files with zero external importers:
|
|
59
|
+
- `src/runtime/diagnostics/index.ts`, `provider.ts`, `actions.ts`
|
|
60
|
+
- `src/runtime/diagnostics/panels/agents.ts`, `events.ts`, `health.ts`, `tasks.ts`, `tool-calls.ts`
|
|
61
|
+
- `src/runtime/store/helpers/reducers.ts` (barrel) and 4 sub-reducers
|
|
62
|
+
- `src/runtime/store/domains/permissions.ts`, `conversation.ts`
|
|
63
|
+
- Updated `src/runtime/diagnostics/panels/index.ts` to re-export deleted panels from SDK
|
|
64
|
+
|
|
65
|
+
### Config Re-export Shim Inlining (Tier 3 item 18)
|
|
66
|
+
|
|
67
|
+
- Deleted `src/config/service-registry.ts` and `src/config/subscription-providers.ts` (1-line SDK re-exports)
|
|
68
|
+
- Updated 29 call sites to import directly from `@pellux/goodvibes-sdk/platform/config/*`
|
|
69
|
+
|
|
70
|
+
### SDK Consolidation — UI Read Models (Tier 3 items 19-20)
|
|
71
|
+
|
|
72
|
+
- Converted 9 TUI mirror files to 1-line SDK re-exports (preserving all call-site import paths):
|
|
73
|
+
- `ui-events.ts`, `ui-service-queries.ts`, `ui-read-model-helpers.ts`
|
|
74
|
+
- `ui-read-models-observability.ts` and 4 observability sub-files (maintenance, options, remote, security, system)
|
|
75
|
+
- Skipped (TUI-specific divergence): `ui-services.ts` (uses TUI `SecretsManager` subclass), `ui-read-models.ts` (depends on TUI `RuntimeServices`)
|
|
76
|
+
- panel-resources drift: TUI version (119 lines) uses `panel-health-monitor.ts`; SDK version (152 lines) uses `component-health-monitor.js` — different monitor interfaces, TUI-specific binding kept
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## [0.18.14] — 2026-04-16
|
|
81
|
+
|
|
82
|
+
### Panel Navigation Overhaul
|
|
83
|
+
|
|
84
|
+
- Created `ScrollableListPanel<T>` and `SearchableListPanel<T>` base classes in `src/panels/scrollable-list-panel.ts`
|
|
85
|
+
- Migrated 30 panels from hand-coded scroll/cursor management to the shared base classes
|
|
86
|
+
- All list panels now have consistent navigation: up/down/j/k, pageup/pagedown, home/end/g/G, enter to select
|
|
87
|
+
- Selection is always visible within the viewport — guaranteed by `getVisibleWindow()` from `surface-layout.ts`
|
|
88
|
+
- Removed ~150 lines of duplicated scroll boilerplate across panels
|
|
89
|
+
|
|
90
|
+
### Modal Viewport Fixes
|
|
91
|
+
|
|
92
|
+
- Fixed modal sizing: height is exactly 45% of viewport (both min and max — all modals same size), width is 50% with 25% minimum
|
|
93
|
+
- Fixed modal scroll/selection: 6 modal/overlay files updated to use shared `getVisibleWindow()` instead of inline scroll math
|
|
94
|
+
- Autocomplete overlay, file picker, bookmark modal, session picker, profile picker, and live tail modal all use the same viewport function
|
|
95
|
+
|
|
96
|
+
### Settings Modal: Tools Tab
|
|
97
|
+
|
|
98
|
+
- Added proper tools tab UI with "Tool LLM" and "Helper Model" section headers
|
|
99
|
+
- Helper config keys (`helper.enabled`, `helper.globalProvider`, `helper.globalModel`) now routed into the tools tab
|
|
100
|
+
- Boolean settings display as [on]/[off] toggles
|
|
101
|
+
- Selecting a provider/model setting opens the full model picker instead of a text field
|
|
102
|
+
- Model picker now supports 3 target modes: main, helper, and tool
|
|
103
|
+
- Selecting a helper/tool model auto-enables the feature (`helper.enabled: true` / `tools.llmEnabled: true`)
|
|
104
|
+
|
|
105
|
+
### QR Code Pairing for Companion Apps
|
|
106
|
+
|
|
107
|
+
- Added `/qrcode` command (aliases `/qr`, `/pair`) that opens a QR code panel
|
|
108
|
+
- QR panel displays connection info (daemon URL, token, username) + scannable QR code
|
|
109
|
+
- QR rendered using Unicode half-block characters (▀/▄/█) for compact terminal display
|
|
110
|
+
- Supports `r` to regenerate token (invalidates old one) and `c` to copy token to clipboard
|
|
111
|
+
- Daemon standalone mode (`goodvibes-daemon`) now prints QR + connection info to stdout on startup
|
|
112
|
+
- Companion tokens persist to `.goodvibes/tui/companion-token.json` with `gv_` prefix
|
|
113
|
+
- Built on SDK 0.18.30 pairing module
|
|
114
|
+
|
|
115
|
+
### Health Monitoring Rename
|
|
116
|
+
|
|
117
|
+
- Renamed `panelHealthMonitor` to `componentHealthMonitor` across 20 files to align with SDK 0.18.29's generic naming
|
|
118
|
+
- Deprecated `Panel*` type aliases preserved for backward compatibility
|
|
119
|
+
|
|
120
|
+
### SDK 0.18.30 Update
|
|
121
|
+
|
|
122
|
+
- Updated to `@pellux/goodvibes-sdk@0.18.30`
|
|
123
|
+
- Consumes new pairing module, `tools.llmEnabled` config, and all 0.18.29 boundary cleanup
|
|
124
|
+
|
|
125
|
+
### Verification
|
|
126
|
+
|
|
127
|
+
- Full typecheck passes: `bun x tsc --noEmit` — 0 errors
|
|
128
|
+
|
|
7
129
|
## [0.18.13] — 2026-04-16
|
|
8
130
|
|
|
9
131
|
### SDK/TUI Boundary Separation
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/mgd34msu/goodvibes-tui/actions/workflows/ci.yml)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://github.com/mgd34msu/goodvibes-tui)
|
|
6
6
|
|
|
7
7
|
A terminal-native AI coding, operations, automation, knowledge, and integration console with a typed runtime, omnichannel surfaces, structured memory/knowledge, and a raw ANSI renderer.
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-tui",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.17",
|
|
4
4
|
"description": "Terminal-native GoodVibes product for coding, operations, automation, knowledge, channels, and daemon-backed control-plane workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/main.ts",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"src",
|
|
17
17
|
"!src/test",
|
|
18
18
|
"!src/**/*.test.ts",
|
|
19
|
+
"!src/**/__tests__",
|
|
19
20
|
"scripts/postinstall.js",
|
|
20
21
|
"README.md",
|
|
21
22
|
"CHANGELOG.md",
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
89
90
|
"@ast-grep/napi": "^0.42.0",
|
|
90
91
|
"@aws/bedrock-token-generator": "^1.1.0",
|
|
91
|
-
"@pellux/goodvibes-sdk": "0.18.
|
|
92
|
+
"@pellux/goodvibes-sdk": "0.18.37",
|
|
92
93
|
"bash-language-server": "^5.6.0",
|
|
93
94
|
"fuse.js": "^7.1.0",
|
|
94
95
|
"graphql": "^16.13.2",
|
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);
|
|
@@ -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
|
}
|
|
@@ -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();
|
|
@@ -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;
|
|
@@ -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);
|