@pellux/goodvibes-tui 0.18.23 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +7 -3
- package/src/core/conversation-rendering.ts +8 -6
- package/src/core/orchestrator.ts +1 -1
- package/src/input/commands/diff-runtime.ts +6 -5
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/session-content.ts +1 -1
- package/src/input/commands/shell-core.ts +3 -2
- package/src/input/commands/skills-runtime.ts +2 -2
- package/src/input/commands/subscription-runtime.ts +4 -4
- package/src/input/handler.ts +8 -10
- package/src/input/panel-integration-actions.ts +2 -1
- package/src/input/settings-modal-types.ts +60 -0
- package/src/input/settings-modal.ts +83 -65
- package/src/panels/agent-inspector-panel.ts +10 -9
- package/src/panels/agent-logs-panel.ts +26 -6
- package/src/panels/approval-panel.ts +1 -0
- package/src/panels/automation-control-panel.ts +1 -0
- package/src/panels/base-panel.ts +108 -3
- package/src/panels/communication-panel.ts +1 -0
- package/src/panels/context-visualizer-panel.ts +2 -0
- package/src/panels/control-plane-panel.ts +1 -0
- package/src/panels/diff-panel.ts +2 -0
- package/src/panels/file-explorer-panel.ts +51 -31
- package/src/panels/file-preview-panel.ts +57 -35
- package/src/panels/git-panel.ts +12 -13
- package/src/panels/hooks-panel.ts +3 -1
- package/src/panels/incident-review-panel.ts +4 -2
- package/src/panels/knowledge-panel.ts +75 -107
- package/src/panels/local-auth-panel.ts +1 -0
- package/src/panels/marketplace-panel.ts +51 -69
- package/src/panels/mcp-panel.ts +3 -1
- package/src/panels/memory-panel.ts +90 -158
- package/src/panels/ops-control-panel.ts +1 -0
- package/src/panels/orchestration-panel.ts +70 -51
- package/src/panels/panel-list-panel.ts +5 -4
- package/src/panels/panel-manager.ts +3 -0
- package/src/panels/plan-dashboard-panel.ts +2 -0
- package/src/panels/plugins-panel.ts +1 -0
- package/src/panels/polish.ts +51 -2
- package/src/panels/provider-accounts-panel.ts +1 -0
- package/src/panels/provider-health-panel.ts +6 -8
- package/src/panels/routes-panel.ts +3 -1
- package/src/panels/schedule-panel.ts +7 -6
- package/src/panels/scrollable-list-panel.ts +19 -2
- package/src/panels/security-panel.ts +17 -15
- package/src/panels/services-panel.ts +6 -4
- package/src/panels/session-browser-panel.ts +19 -18
- package/src/panels/settings-sync-panel.ts +3 -1
- package/src/panels/skills-panel.ts +114 -230
- package/src/panels/subscription-panel.ts +1 -0
- package/src/panels/system-messages-panel.ts +147 -141
- package/src/panels/tasks-panel.ts +1 -0
- package/src/panels/token-budget-panel.ts +2 -0
- package/src/panels/watchers-panel.ts +1 -0
- package/src/panels/worktree-panel.ts +1 -0
- package/src/panels/wrfc-panel.ts +2 -0
- package/src/renderer/agent-detail-modal.ts +2 -2
- package/src/renderer/ansi-sanitize.ts +76 -0
- package/src/renderer/buffer.ts +12 -1
- package/src/renderer/help-overlay.ts +14 -3
- package/src/renderer/settings-modal-helpers.ts +27 -0
- package/src/renderer/settings-modal.ts +18 -1
- package/src/renderer/status-glyphs.ts +21 -0
- package/src/renderer/status-token.ts +4 -8
- package/src/renderer/tool-call.ts +4 -3
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap.ts +7 -8
- package/src/runtime/diagnostics/panels/policy.ts +2 -1
- package/src/shell/ui-openers.ts +1 -1
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,40 @@ All notable changes to GoodVibes TUI.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.19.0] — 2026-04-18
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Upgraded `@pellux/goodvibes-sdk` from 0.19.6 to 0.21.1 (soak-period release).
|
|
11
|
+
TUI adaptation required: regenerated `docs/foundation-artifacts/operator-contract.json`
|
|
12
|
+
to match the updated `buildOperatorContract()` output in the new SDK version.
|
|
13
|
+
`peer-contract.json`, `knowledge-graphql.graphql`, and `knowledge-store.sql` were
|
|
14
|
+
unchanged by this SDK bump.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Wave B panel migration: migrated 5 panels (knowledge, marketplace, memory,
|
|
18
|
+
system-messages, orchestration) to `ScrollableListPanel<T>`/`SearchableListPanel<T>`
|
|
19
|
+
generics; added `docs/panel-authoring.md` as the canonical panel authoring guide.
|
|
20
|
+
- Wave C-α reliability pass: F-perf-01 (trackedRender on 5 hot panels),
|
|
21
|
+
F-perf-02 (async panel fs + skills-panel de-blocking), F-perf-03 (timer registry
|
|
22
|
+
+ 5-panel zombie-timer leak prevention), F-errors-02 (observable async failures —
|
|
23
|
+
no silent `.catch(() => {})`), F-sec-02 (ANSI escape sanitization at
|
|
24
|
+
tool-call untrusted-content entry points).
|
|
25
|
+
- `src/input/settings-modal-types.ts`: extracted `SettingsCategory`,
|
|
26
|
+
`SETTINGS_CATEGORIES`, `SettingEntry`, `FlagEntry`, `McpEntry`,
|
|
27
|
+
`SubscriptionEntry` type definitions out of the settings-modal module.
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
- Postinstall patcher from `@pellux/goodvibes-sdk@0.21.1` mitigates three
|
|
31
|
+
minimatch ReDoS advisories in the consumer install tree.
|
|
32
|
+
- Added `overrides: { minimatch: ^10.2.5 }` to TUI's own `package.json`
|
|
33
|
+
so `npm audit` reports clean for the TUI install tree independently.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
- Foundation artifacts gate now passes: `operator-contract.json` updated to
|
|
37
|
+
match SDK 0.21.1 `buildOperatorContract()` canonical output.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
7
41
|
## [0.18.23] — 2026-04-16
|
|
8
42
|
|
|
9
43
|
### Wave 4α+β performance bundle + α review follow-ups + regression fixes
|
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.
|
|
3
|
+
"version": "0.19.0",
|
|
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",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@anthropic-ai/vertex-sdk": "^0.16.0",
|
|
90
90
|
"@ast-grep/napi": "^0.42.0",
|
|
91
91
|
"@aws/bedrock-token-generator": "^1.1.0",
|
|
92
|
-
"@pellux/goodvibes-sdk": "0.
|
|
92
|
+
"@pellux/goodvibes-sdk": "0.21.1",
|
|
93
93
|
"bash-language-server": "^5.6.0",
|
|
94
94
|
"fuse.js": "^7.1.0",
|
|
95
95
|
"graphql": "^16.13.2",
|
|
@@ -115,8 +115,12 @@
|
|
|
115
115
|
"typescript": "^5.9.3"
|
|
116
116
|
},
|
|
117
117
|
"trustedDependencies": [
|
|
118
|
+
"@pellux/goodvibes-sdk",
|
|
118
119
|
"tree-sitter-css",
|
|
119
120
|
"tree-sitter-javascript",
|
|
120
121
|
"tree-sitter-python"
|
|
121
|
-
]
|
|
122
|
+
],
|
|
123
|
+
"overrides": {
|
|
124
|
+
"minimatch": "^10.2.5"
|
|
125
|
+
}
|
|
122
126
|
}
|
|
@@ -110,12 +110,14 @@ export function renderConversationAssistantMessage(
|
|
|
110
110
|
// determines `numWidth` (digit count) and thus `gutterW` (gutter column width).
|
|
111
111
|
// 2. Render pass: render at `width - gutterW` with the gutter factored in.
|
|
112
112
|
//
|
|
113
|
-
// Single-pass is not
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
113
|
+
// Single-pass is not pursued here. It would require either a pessimistic
|
|
114
|
+
// `numWidth=6` (fits 999,999 lines, but wastes 3-4 gutter columns on typical
|
|
115
|
+
// messages) or rendering the numbered output into a scratch buffer and trimming.
|
|
116
|
+
// Neither is clearly better than the current two-pass measurement approach.
|
|
117
|
+
// The 4α commit message claim that this "eliminates double-parse when line
|
|
118
|
+
// numbers are enabled" was inaccurate: 4α eliminated the legacy
|
|
119
|
+
// `renderMarkdown()` duplicate used for code-block line-number mode ('code').
|
|
120
|
+
// The 'all' mode double-call is a deliberate design choice and remains unchanged.
|
|
119
121
|
const measureWidth = showAllLineNumbers ? width : 0;
|
|
120
122
|
const totalLines = showAllLineNumbers
|
|
121
123
|
? renderMarkdownTracked(message.content, measureWidth, { codeBlockLineNumbers: false }).lines.length
|
package/src/core/orchestrator.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Thin TUI wrapper — re-exports the SDK Orchestrator unchanged.
|
|
2
2
|
// The SDK class already contains all behaviour including getSpinner().
|
|
3
|
-
export { Orchestrator } from '@pellux/goodvibes-sdk/platform/core/orchestrator';
|
|
3
|
+
export { Orchestrator, type OrchestratorOptions } from '@pellux/goodvibes-sdk/platform/core/orchestrator';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
2
3
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
3
4
|
import { requirePanelManager, requireSessionChangeTracker, requireShellPaths } from './runtime-services.ts';
|
|
4
5
|
|
|
@@ -86,7 +87,7 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
86
87
|
return (await new Response(proc.stdout).text()).trim().split('\n').filter(Boolean);
|
|
87
88
|
})();
|
|
88
89
|
if (workingChangedFiles.length > 0) {
|
|
89
|
-
enrichSemanticDiff(diffPanel, workingChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch(() => {});
|
|
90
|
+
enrichSemanticDiff(diffPanel, workingChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
90
91
|
}
|
|
91
92
|
break;
|
|
92
93
|
}
|
|
@@ -111,7 +112,7 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
111
112
|
return (await new Response(stagedProc.stdout).text()).trim().split('\n').filter(Boolean);
|
|
112
113
|
})();
|
|
113
114
|
if (stagedChangedFiles.length > 0) {
|
|
114
|
-
enrichSemanticDiff(diffPanel, stagedChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch(() => {});
|
|
115
|
+
enrichSemanticDiff(diffPanel, stagedChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
break;
|
|
@@ -126,7 +127,7 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
126
127
|
return (await new Response(proc.stdout).text()).trim().split('\n').filter(Boolean);
|
|
127
128
|
})();
|
|
128
129
|
if (headChangedFiles.length > 0) {
|
|
129
|
-
enrichSemanticDiff(diffPanel, headChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch(() => {});
|
|
130
|
+
enrichSemanticDiff(diffPanel, headChangedFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
130
131
|
}
|
|
131
132
|
break;
|
|
132
133
|
}
|
|
@@ -137,7 +138,7 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
137
138
|
ctx.print(`Loading session diff (${sessionFiles.length} file${sessionFiles.length === 1 ? '' : 's'} changed this session)...`);
|
|
138
139
|
await diffPanel.showFileDiffs(sessionFiles, 'HEAD');
|
|
139
140
|
ctx.print(`Diff panel updated: ${sessionFiles.length} session file${sessionFiles.length === 1 ? '' : 's'}.`);
|
|
140
|
-
enrichSemanticDiff(diffPanel, sessionFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch(() => {});
|
|
141
|
+
enrichSemanticDiff(diffPanel, sessionFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
141
142
|
} else {
|
|
142
143
|
ctx.print('No session changes tracked yet. Showing diff vs HEAD...');
|
|
143
144
|
await diffPanel.showGitDiff('HEAD');
|
|
@@ -148,7 +149,7 @@ export function registerDiffRuntimeCommands(registry: CommandRegistry): void {
|
|
|
148
149
|
return (await new Response(proc.stdout).text()).trim().split('\n').filter(Boolean);
|
|
149
150
|
})();
|
|
150
151
|
if (fallbackFiles.length > 0) {
|
|
151
|
-
enrichSemanticDiff(diffPanel, fallbackFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch(() => {});
|
|
152
|
+
enrichSemanticDiff(diffPanel, fallbackFiles, 'HEAD', () => ctx.renderRequest(), workingDirectory).catch((err) => { logger.debug('semantic diff enrichment failed', { err }); });
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
break;
|
|
@@ -64,7 +64,7 @@ export function registerGuidanceRuntimeCommands(registry: CommandRegistry): void
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const providerApi = requireProviderApi(ctx);
|
|
67
|
-
const currentModel = await providerApi.getCurrentModel().catch(() => null);
|
|
67
|
+
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
68
68
|
const llmMessages = ctx.session.conversationManager.getMessagesForLLM();
|
|
69
69
|
const readModels = requireReadModels(ctx);
|
|
70
70
|
const session = readModels.session.getSnapshot();
|
|
@@ -240,7 +240,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
240
240
|
if (sub === 'maintenance') {
|
|
241
241
|
const session = readModels.session.getSnapshot();
|
|
242
242
|
const providerApi = requireProviderApi(ctx);
|
|
243
|
-
const currentModel = await providerApi.getCurrentModel().catch(() => null);
|
|
243
|
+
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
244
244
|
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
245
245
|
? ctx.session.conversationManager.getMessagesForLLM()
|
|
246
246
|
: [];
|
|
@@ -369,7 +369,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
369
369
|
|
|
370
370
|
const session = readModels.session.getSnapshot();
|
|
371
371
|
const providerApi = requireProviderApi(ctx);
|
|
372
|
-
const currentModel = await providerApi.getCurrentModel().catch(() => null);
|
|
372
|
+
const currentModel = await providerApi.getCurrentModel().catch(() => null); // best-effort: null handled as unknown context window
|
|
373
373
|
const llmMessages = typeof ctx.session.conversationManager.getMessagesForLLM === 'function'
|
|
374
374
|
? ctx.session.conversationManager.getMessagesForLLM()
|
|
375
375
|
: [];
|
|
@@ -22,7 +22,7 @@ export async function buildSetupReviewSnapshot(ctx: CommandContext): Promise<Set
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const skills = discoverSkills(shellPaths);
|
|
25
|
+
const skills = await discoverSkills(shellPaths);
|
|
26
26
|
const security = requireReadModels(ctx).security.getSnapshot();
|
|
27
27
|
const plugins = security.plugins;
|
|
28
28
|
const mcpServers = security.mcpServers;
|
|
@@ -161,7 +161,7 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
161
161
|
|
|
162
162
|
registry.register({
|
|
163
163
|
name: 'undo',
|
|
164
|
-
aliases: [
|
|
164
|
+
aliases: [],
|
|
165
165
|
description: 'Undo last action. /undo file — revert last file write/edit. /undo — remove last conversation turn.',
|
|
166
166
|
usage: '[file]',
|
|
167
167
|
argsHint: '[file]',
|
|
@@ -5,6 +5,7 @@ import { REASONING_BUDGET_MAP } from '@pellux/goodvibes-sdk/platform/providers/i
|
|
|
5
5
|
import { executeWriteQuit } from './quit-shared.ts';
|
|
6
6
|
import { compactConversation, requireKeybindingsManager, requireProviderApi } from './runtime-services.ts';
|
|
7
7
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
|
|
8
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
8
9
|
|
|
9
10
|
export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
10
11
|
registry.register({
|
|
@@ -34,7 +35,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
34
35
|
ctx.platform.configManager.set('provider.model', selected.registryKey);
|
|
35
36
|
ctx.platform.configManager.set('provider.provider', selected.providerId);
|
|
36
37
|
ctx.print(`Switched to model: ${selected.displayName} (${selected.providerId})`);
|
|
37
|
-
void providerApi.recordModelUsage(selected.registryKey).catch(() =>
|
|
38
|
+
void providerApi.recordModelUsage(selected.registryKey).catch((err) => { logger.debug('model usage record failed', { err }); });
|
|
38
39
|
} catch (e) {
|
|
39
40
|
ctx.print(`Error: ${summarizeError(e)}`);
|
|
40
41
|
}
|
|
@@ -202,7 +203,7 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
202
203
|
|
|
203
204
|
registry.register({
|
|
204
205
|
name: 'quit',
|
|
205
|
-
aliases: ['
|
|
206
|
+
aliases: [':q'],
|
|
206
207
|
description: 'Exit the application',
|
|
207
208
|
handler(_args, ctx) {
|
|
208
209
|
ctx.exit();
|
|
@@ -19,7 +19,7 @@ export function registerSkillsRuntimeCommands(registry: CommandRegistry): void {
|
|
|
19
19
|
aliases: ['skill'],
|
|
20
20
|
description: 'Inspect installed skill packs',
|
|
21
21
|
usage: '[open|list|show <name>|origins|browse [query]|installed|catalog-review <id>|publish-local <id> <path> <summary...>|unpublish <id>|install-hint <catalog-id>|install <id> [project|user]|update <id> [project|user]|uninstall <id> [project|user]]',
|
|
22
|
-
handler(args, ctx) {
|
|
22
|
+
async handler(args, ctx) {
|
|
23
23
|
const sub = args[0] ?? 'open';
|
|
24
24
|
if (sub === 'open' || sub === 'panel') {
|
|
25
25
|
if (ctx.showPanel) ctx.showPanel('skills');
|
|
@@ -31,7 +31,7 @@ export function registerSkillsRuntimeCommands(registry: CommandRegistry): void {
|
|
|
31
31
|
}
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
const skills = discoverSkills(requireShellPaths(ctx));
|
|
34
|
+
const skills = await discoverSkills(requireShellPaths(ctx));
|
|
35
35
|
const ecosystemPaths = requireEcosystemCatalogPaths(ctx);
|
|
36
36
|
if (sub === 'list') {
|
|
37
37
|
if (skills.length === 0) {
|
|
@@ -174,7 +174,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
|
|
|
174
174
|
const openBrowser = !flags.has('--no-browser');
|
|
175
175
|
const useManualMode = flags.has('--manual');
|
|
176
176
|
if (provider === 'openai' && resolved.source === 'builtin') {
|
|
177
|
-
const started = beginOpenAICodexLogin();
|
|
177
|
+
const started = await beginOpenAICodexLogin();
|
|
178
178
|
manager.savePending({
|
|
179
179
|
provider,
|
|
180
180
|
state: started.state,
|
|
@@ -191,7 +191,7 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
|
|
|
191
191
|
host: '127.0.0.1',
|
|
192
192
|
port: 1455,
|
|
193
193
|
path: '/auth/callback',
|
|
194
|
-
}).catch(() => null);
|
|
194
|
+
}).catch(() => null); // best-effort: listener creation is optional; null triggers manual flow
|
|
195
195
|
} catch {
|
|
196
196
|
listener = null;
|
|
197
197
|
}
|
|
@@ -272,14 +272,14 @@ export function registerSubscriptionRuntimeCommands(registry: CommandRegistry):
|
|
|
272
272
|
host: resolved.oauth.localCallback.host,
|
|
273
273
|
port: resolved.oauth.localCallback.port,
|
|
274
274
|
path: resolved.oauth.localCallback.path,
|
|
275
|
-
}).catch(() => null);
|
|
275
|
+
}).catch(() => null); // best-effort: local callback listener is optional; null falls back to manual redirect
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
if (listener) {
|
|
279
279
|
activeConfig = { ...activeConfig, redirectUri: listener.redirectUri };
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
const started = manager.beginOAuthLogin(provider, activeConfig);
|
|
282
|
+
const started = await manager.beginOAuthLogin(provider, activeConfig);
|
|
283
283
|
if (listener) {
|
|
284
284
|
listener.setExpectedState(started.pending.state);
|
|
285
285
|
}
|
package/src/input/handler.ts
CHANGED
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
} from './handler-picker-routes.ts';
|
|
82
82
|
import { handleGlobalShortcutToken } from './handler-shortcuts.ts';
|
|
83
83
|
import { feedInputTokens } from './handler-feed.ts';
|
|
84
|
-
import { buildInitialFeedContext } from './feed-context-factory.ts';
|
|
84
|
+
import { buildInitialFeedContext, syncFeedContextMutableFields } from './feed-context-factory.ts';
|
|
85
85
|
import { handlePanelIntegrationAction as runPanelIntegrationAction } from './panel-integration-actions.ts';
|
|
86
86
|
import type { Panel } from '../panels/types.ts';
|
|
87
87
|
import type { UiRuntimeServices } from '../runtime/ui-services.ts';
|
|
@@ -292,15 +292,13 @@ export class InputHandler {
|
|
|
292
292
|
|
|
293
293
|
/** Sync mutable handler fields back into feedContext after in-feed mutations. */
|
|
294
294
|
private syncFeedContextMutableFields(): void {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
ctx.nextPasteId = this.nextPasteId; ctx.nextImageId = this.nextImageId;
|
|
303
|
-
ctx.mouseDownRow = this.mouseDownRow; ctx.mouseDownCol = this.mouseDownCol;
|
|
295
|
+
const h = this;
|
|
296
|
+
syncFeedContextMutableFields({ prompt: h.prompt, cursorPos: h.cursorPos, commandMode: h.commandMode,
|
|
297
|
+
panelFocused: h.panelFocused, indicatorFocused: h.indicatorFocused, helpOverlayActive: h.helpOverlayActive,
|
|
298
|
+
helpScrollOffset: h.helpScrollOffset, shortcutsOverlayActive: h.shortcutsOverlayActive,
|
|
299
|
+
shortcutsScrollOffset: h.shortcutsScrollOffset, selectionCallback: h.selectionCallback,
|
|
300
|
+
nextPasteId: h.nextPasteId, nextImageId: h.nextImageId, mouseDownRow: h.mouseDownRow,
|
|
301
|
+
mouseDownCol: h.mouseDownCol, contentWidth: h.contentWidth }, this.feedContext);
|
|
304
302
|
}
|
|
305
303
|
|
|
306
304
|
/** Wire in the InputHistory instance. Optional; disables history navigation if unset. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CommandContext } from './command-registry.ts';
|
|
2
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
2
3
|
import type { Panel } from '../panels/types.ts';
|
|
3
4
|
import type { PanelManager } from '../panels/panel-manager.ts';
|
|
4
5
|
import { FileExplorerPanel } from '../panels/file-explorer-panel.ts';
|
|
@@ -69,7 +70,7 @@ export function handlePanelIntegrationAction(
|
|
|
69
70
|
const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
|
|
70
71
|
const [name, ...args] = parts;
|
|
71
72
|
if (!name) return false;
|
|
72
|
-
void commandContext.executeCommand(name, args).catch(() => {});
|
|
73
|
+
void commandContext.executeCommand(name, args).catch((err) => { logger.debug('approval panel command dispatch failed', { err }); });
|
|
73
74
|
return true;
|
|
74
75
|
}
|
|
75
76
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config/schema';
|
|
2
|
+
import type { ProviderAuthFreshness, ProviderAuthRoute } from '@pellux/goodvibes-sdk/platform/runtime/provider-accounts/registry';
|
|
3
|
+
import type { FeatureFlag, FlagState } from '@pellux/goodvibes-sdk/platform/runtime/feature-flags/types';
|
|
4
|
+
|
|
5
|
+
export type SettingsCategory = 'display' | 'ui' | 'provider' | 'subscriptions' | 'behavior' | 'storage' | 'permissions' | 'mcp' | 'sandbox' | 'danger' | 'tools' | 'flags' | 'network';
|
|
6
|
+
|
|
7
|
+
export const SETTINGS_CATEGORIES: SettingsCategory[] = [
|
|
8
|
+
'display',
|
|
9
|
+
'ui',
|
|
10
|
+
'provider',
|
|
11
|
+
'subscriptions',
|
|
12
|
+
'behavior',
|
|
13
|
+
'storage',
|
|
14
|
+
'permissions',
|
|
15
|
+
'mcp',
|
|
16
|
+
'sandbox',
|
|
17
|
+
'danger',
|
|
18
|
+
'tools',
|
|
19
|
+
'flags',
|
|
20
|
+
'network',
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export interface SettingEntry {
|
|
24
|
+
setting: ConfigSetting;
|
|
25
|
+
currentValue: unknown;
|
|
26
|
+
isDefault: boolean;
|
|
27
|
+
effectiveSource?: 'default' | 'local' | 'synced' | 'managed';
|
|
28
|
+
locked?: boolean;
|
|
29
|
+
conflict?: boolean;
|
|
30
|
+
sourceLabel?: string;
|
|
31
|
+
lockReason?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FlagEntry {
|
|
35
|
+
flag: FeatureFlag;
|
|
36
|
+
state: FlagState;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface McpEntry {
|
|
40
|
+
name: string;
|
|
41
|
+
connected: boolean;
|
|
42
|
+
role: 'general' | 'docs' | 'filesystem' | 'git' | 'database' | 'browser' | 'automation' | 'ops' | 'remote';
|
|
43
|
+
trustMode: 'constrained' | 'ask-on-risk' | 'allow-all' | 'blocked';
|
|
44
|
+
allowedPaths: string[];
|
|
45
|
+
allowedHosts: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SubscriptionEntry {
|
|
49
|
+
provider: string;
|
|
50
|
+
state: 'active' | 'pending' | 'available';
|
|
51
|
+
tokenType?: string;
|
|
52
|
+
expiresAt?: number;
|
|
53
|
+
oauthConfigured: boolean;
|
|
54
|
+
activeRoute?: ProviderAuthRoute;
|
|
55
|
+
preferredRoute?: ProviderAuthRoute;
|
|
56
|
+
authFreshness?: ProviderAuthFreshness;
|
|
57
|
+
routeReason?: string;
|
|
58
|
+
issues?: string[];
|
|
59
|
+
nextActions?: string[];
|
|
60
|
+
}
|
|
@@ -23,67 +23,23 @@ import type { FeatureFlag, FlagState } from '@pellux/goodvibes-sdk/platform/runt
|
|
|
23
23
|
import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp/registry';
|
|
24
24
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
25
25
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
'danger',
|
|
44
|
-
'tools',
|
|
45
|
-
'flags',
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
export interface SettingEntry {
|
|
49
|
-
setting: ConfigSetting;
|
|
50
|
-
currentValue: unknown;
|
|
51
|
-
isDefault: boolean;
|
|
52
|
-
effectiveSource?: 'default' | 'local' | 'synced' | 'managed';
|
|
53
|
-
locked?: boolean;
|
|
54
|
-
conflict?: boolean;
|
|
55
|
-
sourceLabel?: string;
|
|
56
|
-
lockReason?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/** A single feature flag entry for the flags tab. */
|
|
60
|
-
export interface FlagEntry {
|
|
61
|
-
flag: FeatureFlag;
|
|
62
|
-
state: FlagState;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface McpEntry {
|
|
66
|
-
name: string;
|
|
67
|
-
connected: boolean;
|
|
68
|
-
role: 'general' | 'docs' | 'filesystem' | 'git' | 'database' | 'browser' | 'automation' | 'ops' | 'remote';
|
|
69
|
-
trustMode: 'constrained' | 'ask-on-risk' | 'allow-all' | 'blocked';
|
|
70
|
-
allowedPaths: string[];
|
|
71
|
-
allowedHosts: string[];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface SubscriptionEntry {
|
|
75
|
-
provider: string;
|
|
76
|
-
state: 'active' | 'pending' | 'available';
|
|
77
|
-
tokenType?: string;
|
|
78
|
-
expiresAt?: number;
|
|
79
|
-
oauthConfigured: boolean;
|
|
80
|
-
activeRoute?: ProviderAuthRoute;
|
|
81
|
-
preferredRoute?: ProviderAuthRoute;
|
|
82
|
-
authFreshness?: ProviderAuthFreshness;
|
|
83
|
-
routeReason?: string;
|
|
84
|
-
issues?: string[];
|
|
85
|
-
nextActions?: string[];
|
|
86
|
-
}
|
|
26
|
+
import {
|
|
27
|
+
SETTINGS_CATEGORIES,
|
|
28
|
+
type FlagEntry,
|
|
29
|
+
type McpEntry,
|
|
30
|
+
type SettingEntry,
|
|
31
|
+
type SettingsCategory,
|
|
32
|
+
type SubscriptionEntry,
|
|
33
|
+
} from './settings-modal-types.ts';
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
SETTINGS_CATEGORIES,
|
|
37
|
+
type FlagEntry,
|
|
38
|
+
type McpEntry,
|
|
39
|
+
type SettingEntry,
|
|
40
|
+
type SettingsCategory,
|
|
41
|
+
type SubscriptionEntry,
|
|
42
|
+
} from './settings-modal-types.ts';
|
|
87
43
|
|
|
88
44
|
/**
|
|
89
45
|
* Map a config key to the model picker target it should open, or null if the
|
|
@@ -151,6 +107,13 @@ export class SettingsModal {
|
|
|
151
107
|
/** Provider subscription entries (populated when subscriptions tab is active). */
|
|
152
108
|
public subscriptionEntries: SubscriptionEntry[] = [];
|
|
153
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Set after a network-category save that touches controlPlane or httpListener
|
|
112
|
+
* config keys. Renderer reads this to display a transient restart notice.
|
|
113
|
+
* Cleared on next open() or close().
|
|
114
|
+
*/
|
|
115
|
+
public lastSaveTriggeredRestart: 'control-plane' | 'http-listener' | 'web' | null = null;
|
|
116
|
+
|
|
154
117
|
private configManager: ConfigManager | null = null;
|
|
155
118
|
private featureFlagManager: FeatureFlagManager | null = null;
|
|
156
119
|
private mcpRegistry: McpRegistry | null = null;
|
|
@@ -185,6 +148,7 @@ export class SettingsModal {
|
|
|
185
148
|
this.editBuffer = '';
|
|
186
149
|
this.mcpAllowAllConfirmationTarget = null;
|
|
187
150
|
this.subscriptionLogoutConfirmationTarget = null;
|
|
151
|
+
this.lastSaveTriggeredRestart = null;
|
|
188
152
|
this.active = true;
|
|
189
153
|
}
|
|
190
154
|
|
|
@@ -194,6 +158,7 @@ export class SettingsModal {
|
|
|
194
158
|
this.editBuffer = '';
|
|
195
159
|
this.mcpAllowAllConfirmationTarget = null;
|
|
196
160
|
this.subscriptionLogoutConfirmationTarget = null;
|
|
161
|
+
this.lastSaveTriggeredRestart = null;
|
|
197
162
|
this.serviceRegistry = null;
|
|
198
163
|
}
|
|
199
164
|
|
|
@@ -548,7 +513,15 @@ export class SettingsModal {
|
|
|
548
513
|
for (const setting of CONFIG_SCHEMA) {
|
|
549
514
|
const rawCat = setting.key.split('.')[0] as string;
|
|
550
515
|
// Route helper.* settings into the tools group for unified display
|
|
551
|
-
|
|
516
|
+
// Route controlPlane.* and httpListener.* into the network group
|
|
517
|
+
let cat: SettingsCategory;
|
|
518
|
+
if (rawCat === 'helper') {
|
|
519
|
+
cat = 'tools';
|
|
520
|
+
} else if (rawCat === 'controlPlane' || rawCat === 'httpListener' || rawCat === 'web') {
|
|
521
|
+
cat = 'network';
|
|
522
|
+
} else {
|
|
523
|
+
cat = rawCat as SettingsCategory;
|
|
524
|
+
}
|
|
552
525
|
if (!this.groups.has(cat)) continue;
|
|
553
526
|
const currentValue = configManager.get(setting.key as ConfigKey);
|
|
554
527
|
const resolved = getResolvedSettingLookup(configManager, setting.key as ConfigKey)?.entry;
|
|
@@ -720,17 +693,62 @@ export class SettingsModal {
|
|
|
720
693
|
/** Returns [] for the flags category (flags use flagEntries instead). */
|
|
721
694
|
private _currentItems(): SettingEntry[] {
|
|
722
695
|
if (this.currentCategory === 'flags' || this.currentCategory === 'mcp' || this.currentCategory === 'subscriptions') return [];
|
|
723
|
-
|
|
696
|
+
const items = this.groups.get(this.currentCategory) ?? [];
|
|
697
|
+
if (this.currentCategory === 'network') {
|
|
698
|
+
// Hide host fields when the corresponding hostMode is not 'custom'
|
|
699
|
+
return items.filter(entry => {
|
|
700
|
+
if (entry.setting.key === 'controlPlane.host') {
|
|
701
|
+
const hostMode = this.configManager?.get('controlPlane.hostMode');
|
|
702
|
+
return hostMode === 'custom';
|
|
703
|
+
}
|
|
704
|
+
if (entry.setting.key === 'httpListener.host') {
|
|
705
|
+
const hostMode = this.configManager?.get('httpListener.hostMode');
|
|
706
|
+
return hostMode === 'custom';
|
|
707
|
+
}
|
|
708
|
+
if (entry.setting.key === 'web.host') {
|
|
709
|
+
const hostMode = this.configManager?.get('web.hostMode');
|
|
710
|
+
return hostMode === 'custom';
|
|
711
|
+
}
|
|
712
|
+
return true;
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
return items;
|
|
724
716
|
}
|
|
725
717
|
|
|
726
718
|
private _setValue(key: ConfigKey, value: unknown): void {
|
|
727
719
|
if (!this.configManager) return;
|
|
720
|
+
// Diff previous value before writing — avoids false restart notices on no-op saves
|
|
721
|
+
const previousValue = this.configManager.get(key);
|
|
722
|
+
const isRestartKey = ['host', 'port', 'hostMode', 'enabled'].includes(key.split('.')[1] ?? '');
|
|
728
723
|
try {
|
|
729
724
|
this.configManager.setDynamic(key, value);
|
|
730
725
|
// Update the cached entry in-place — avoids full schema re-scan on each edit
|
|
731
726
|
const rawCat = key.split('.')[0] as string;
|
|
732
|
-
//
|
|
733
|
-
|
|
727
|
+
// Resolve the display category from the key prefix
|
|
728
|
+
let cat: SettingsCategory;
|
|
729
|
+
if (rawCat === 'helper') {
|
|
730
|
+
cat = 'tools';
|
|
731
|
+
} else if (rawCat === 'controlPlane') {
|
|
732
|
+
cat = 'network';
|
|
733
|
+
// SDK auto-restarts the daemon server on controlPlane binding changes
|
|
734
|
+
if (isRestartKey && previousValue !== value) {
|
|
735
|
+
this.lastSaveTriggeredRestart = 'control-plane';
|
|
736
|
+
}
|
|
737
|
+
} else if (rawCat === 'httpListener') {
|
|
738
|
+
cat = 'network';
|
|
739
|
+
// SDK auto-restarts the HTTP listener on binding changes
|
|
740
|
+
if (isRestartKey && previousValue !== value) {
|
|
741
|
+
this.lastSaveTriggeredRestart = 'http-listener';
|
|
742
|
+
}
|
|
743
|
+
} else if (rawCat === 'web') {
|
|
744
|
+
cat = 'network';
|
|
745
|
+
// SDK auto-restarts the web server on binding changes
|
|
746
|
+
if (isRestartKey && previousValue !== value) {
|
|
747
|
+
this.lastSaveTriggeredRestart = 'web';
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
750
|
+
cat = rawCat as SettingsCategory;
|
|
751
|
+
}
|
|
734
752
|
const entries = this.groups.get(cat);
|
|
735
753
|
if (entries) {
|
|
736
754
|
const entry = entries.find(e => e.setting.key === key);
|