@pellux/goodvibes-tui 0.19.53 → 0.19.55
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 +35 -0
- package/README.md +10 -13
- package/docs/foundation-artifacts/knowledge-store.sql +27 -0
- package/docs/foundation-artifacts/operator-contract.json +15736 -7265
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +4 -1
- package/src/input/command-args-hint.ts +36 -0
- package/src/input/command-registry.ts +3 -1
- package/src/input/commands/config.ts +7 -521
- package/src/input/commands/knowledge.ts +111 -1
- package/src/input/commands/local-runtime.ts +0 -80
- package/src/input/commands/operator-runtime.ts +3 -3
- package/src/input/commands/planning-runtime.ts +83 -34
- package/src/input/commands/shell-core.ts +2 -34
- package/src/input/commands/tts-runtime.ts +1 -389
- package/src/input/commands.ts +0 -2
- package/src/input/handler-modal-routes.ts +61 -7
- package/src/input/handler-modal-token-routes.ts +1 -0
- package/src/input/handler-picker-routes.ts +50 -4
- package/src/input/model-picker-provider-filter.ts +28 -0
- package/src/input/model-picker-types.ts +12 -0
- package/src/input/model-picker.ts +65 -23
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-behavior.ts +2 -0
- package/src/input/settings-modal-subscriptions.ts +95 -0
- package/src/input/settings-modal-types.ts +50 -3
- package/src/input/settings-modal.ts +106 -134
- package/src/input/tts-settings-actions.ts +100 -0
- package/src/main.ts +50 -45
- package/src/panels/builtin/agent.ts +15 -0
- package/src/panels/builtin/shared.ts +17 -0
- package/src/panels/project-planning-panel.ts +370 -0
- package/src/planning/project-planning-coordinator.ts +249 -0
- package/src/renderer/compositor.ts +2 -1
- package/src/renderer/conversation-overlays.ts +4 -5
- package/src/renderer/model-workspace.ts +488 -0
- package/src/renderer/settings-modal-helpers.ts +16 -1
- package/src/renderer/settings-modal.ts +616 -716
- package/src/runtime/bootstrap-command-context.ts +6 -0
- package/src/runtime/bootstrap-command-parts.ts +5 -0
- package/src/runtime/bootstrap-shell.ts +2 -0
- package/src/runtime/services.ts +33 -2
- package/src/runtime/terminal-output-guard.ts +228 -0
- package/src/runtime/ui-services.ts +4 -0
- package/src/shell/ui-openers.ts +59 -3
- package/src/utils/clipboard.ts +2 -1
- package/src/version.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +0 -104
|
@@ -72,6 +72,8 @@ export type CreateBootstrapCommandContextOptions = {
|
|
|
72
72
|
integrationHelpers?: IntegrationHelperService;
|
|
73
73
|
automationManager?: ShellAutomationManagerRuntimeService;
|
|
74
74
|
knowledgeService?: KnowledgeService;
|
|
75
|
+
projectPlanningService?: import('@pellux/goodvibes-sdk/platform/knowledge/index').ProjectPlanningService;
|
|
76
|
+
projectPlanningProjectId?: string;
|
|
75
77
|
providerOptimizer?: import('@pellux/goodvibes-sdk/platform/providers/optimizer').ProviderOptimizer;
|
|
76
78
|
pluginManager?: PluginManager;
|
|
77
79
|
hookWorkbench?: HookWorkbench;
|
|
@@ -138,6 +140,8 @@ export function createBootstrapCommandContext(
|
|
|
138
140
|
integrationHelpers,
|
|
139
141
|
automationManager,
|
|
140
142
|
knowledgeService,
|
|
143
|
+
projectPlanningService,
|
|
144
|
+
projectPlanningProjectId,
|
|
141
145
|
providerOptimizer,
|
|
142
146
|
pluginManager,
|
|
143
147
|
hookWorkbench,
|
|
@@ -227,6 +231,8 @@ export function createBootstrapCommandContext(
|
|
|
227
231
|
panelManager,
|
|
228
232
|
profileManager,
|
|
229
233
|
bookmarkManager,
|
|
234
|
+
projectPlanningService,
|
|
235
|
+
projectPlanningProjectId,
|
|
230
236
|
}, shellServices);
|
|
231
237
|
const platform = createBootstrapCommandPlatformSection({ configManager, voiceProviderRegistry, voiceService }, shellServices);
|
|
232
238
|
const extensions = createBootstrapCommandExtensionsSection({
|
|
@@ -85,6 +85,8 @@ export interface BootstrapCommandSectionOptions {
|
|
|
85
85
|
readonly memoryRegistry?: MemoryRegistry;
|
|
86
86
|
readonly integrationHelpers?: IntegrationHelperService;
|
|
87
87
|
readonly knowledgeService?: KnowledgeService;
|
|
88
|
+
readonly projectPlanningService?: import('@pellux/goodvibes-sdk/platform/knowledge/index').ProjectPlanningService;
|
|
89
|
+
readonly projectPlanningProjectId?: string;
|
|
88
90
|
readonly pluginManager?: PluginManager;
|
|
89
91
|
readonly hookWorkbench?: HookWorkbench;
|
|
90
92
|
readonly providerOptimizer?: import('@pellux/goodvibes-sdk/platform/providers/optimizer').ProviderOptimizer;
|
|
@@ -312,6 +314,7 @@ export function createBootstrapCommandWorkspaceSection(
|
|
|
312
314
|
options: Pick<
|
|
313
315
|
BootstrapCommandSectionOptions,
|
|
314
316
|
'keybindingsManager' | 'fileUndoManager' | 'panelManager' | 'profileManager' | 'bookmarkManager'
|
|
317
|
+
| 'projectPlanningService' | 'projectPlanningProjectId'
|
|
315
318
|
>,
|
|
316
319
|
shellServices: BootstrapCommandShellServices,
|
|
317
320
|
): BootstrapCommandWorkspaceSection {
|
|
@@ -321,6 +324,8 @@ export function createBootstrapCommandWorkspaceSection(
|
|
|
321
324
|
panelManager: options.panelManager,
|
|
322
325
|
profileManager: options.profileManager,
|
|
323
326
|
bookmarkManager: options.bookmarkManager,
|
|
327
|
+
projectPlanningService: options.projectPlanningService,
|
|
328
|
+
projectPlanningProjectId: options.projectPlanningProjectId,
|
|
324
329
|
...shellServices.workspace,
|
|
325
330
|
};
|
|
326
331
|
}
|
|
@@ -196,6 +196,8 @@ export function createBootstrapShell(options: BootstrapShellOptions): BootstrapS
|
|
|
196
196
|
integrationHelpers: services.integrationHelpers,
|
|
197
197
|
automationManager: services.automationManager,
|
|
198
198
|
knowledgeService: services.knowledgeService,
|
|
199
|
+
projectPlanningService: services.projectPlanningService,
|
|
200
|
+
projectPlanningProjectId: services.projectPlanningProjectId,
|
|
199
201
|
providerOptimizer: services.providerOptimizer,
|
|
200
202
|
pluginManager: services.pluginManager,
|
|
201
203
|
hookWorkbench: services.hookWorkbench,
|
package/src/runtime/services.ts
CHANGED
|
@@ -9,7 +9,16 @@ import { ChannelDeliveryRouter } from '@pellux/goodvibes-sdk/platform/channels/d
|
|
|
9
9
|
import { ApprovalBroker, GatewayMethodCatalog, SharedSessionBroker } from '@pellux/goodvibes-sdk/platform/control-plane/index';
|
|
10
10
|
import { WatcherRegistry } from '@pellux/goodvibes-sdk/platform/watchers/index';
|
|
11
11
|
import { ArtifactStore } from '@pellux/goodvibes-sdk/platform/artifacts/index';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
HomeGraphService,
|
|
14
|
+
KnowledgeService,
|
|
15
|
+
KnowledgeSemanticService,
|
|
16
|
+
KnowledgeStore,
|
|
17
|
+
ProjectPlanningService,
|
|
18
|
+
createProviderBackedKnowledgeSemanticLlm,
|
|
19
|
+
createWebKnowledgeGapRepairer,
|
|
20
|
+
projectPlanningProjectIdFromPath,
|
|
21
|
+
} from '@pellux/goodvibes-sdk/platform/knowledge/index';
|
|
13
22
|
import { MediaProviderRegistry, ensureBuiltinMediaProviders } from '@pellux/goodvibes-sdk/platform/media/index';
|
|
14
23
|
import { MultimodalService } from '@pellux/goodvibes-sdk/platform/multimodal/index';
|
|
15
24
|
import { AgentManager } from '@pellux/goodvibes-sdk/platform/tools/agent/index';
|
|
@@ -109,6 +118,8 @@ export interface RuntimeServices {
|
|
|
109
118
|
readonly artifactStore: ArtifactStore;
|
|
110
119
|
readonly knowledgeService: KnowledgeService;
|
|
111
120
|
readonly homeGraphService: HomeGraphService;
|
|
121
|
+
readonly projectPlanningService: ProjectPlanningService;
|
|
122
|
+
readonly projectPlanningProjectId: string;
|
|
112
123
|
readonly memoryStore: MemoryStore;
|
|
113
124
|
readonly memoryRegistry: MemoryRegistry;
|
|
114
125
|
readonly serviceRegistry: ServiceRegistry;
|
|
@@ -352,12 +363,26 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
352
363
|
},
|
|
353
364
|
});
|
|
354
365
|
const knowledgeStore = new KnowledgeStore({ configManager });
|
|
366
|
+
const knowledgeSemanticService = new KnowledgeSemanticService(knowledgeStore, {
|
|
367
|
+
llm: createProviderBackedKnowledgeSemanticLlm(providerRegistry, {
|
|
368
|
+
timeoutMs: 20_000,
|
|
369
|
+
maxConcurrent: 1,
|
|
370
|
+
}),
|
|
371
|
+
maxLlmSourcesPerReindex: 3,
|
|
372
|
+
});
|
|
355
373
|
const knowledgeService = new KnowledgeService(knowledgeStore, artifactStore, undefined, {
|
|
356
374
|
memoryRegistry,
|
|
357
375
|
runtimeBus: options.runtimeBus,
|
|
376
|
+
semanticService: knowledgeSemanticService,
|
|
358
377
|
});
|
|
359
378
|
knowledgeService.attachRuntimeBus(options.runtimeBus);
|
|
360
|
-
const homeGraphService = new HomeGraphService(knowledgeStore, artifactStore
|
|
379
|
+
const homeGraphService = new HomeGraphService(knowledgeStore, artifactStore, {
|
|
380
|
+
semanticService: knowledgeSemanticService,
|
|
381
|
+
});
|
|
382
|
+
const projectPlanningProjectId = projectPlanningProjectIdFromPath(workingDirectory);
|
|
383
|
+
const projectPlanningService = new ProjectPlanningService(knowledgeStore, {
|
|
384
|
+
defaultProjectId: projectPlanningProjectId,
|
|
385
|
+
});
|
|
361
386
|
const voiceProviders = new VoiceProviderRegistry();
|
|
362
387
|
ensureBuiltinVoiceProviders(voiceProviders);
|
|
363
388
|
const voiceService = new VoiceService(voiceProviders);
|
|
@@ -369,6 +394,10 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
369
394
|
serviceRegistry,
|
|
370
395
|
featureFlags,
|
|
371
396
|
});
|
|
397
|
+
knowledgeSemanticService.setGapRepairer(createWebKnowledgeGapRepairer({
|
|
398
|
+
searchService: webSearchService,
|
|
399
|
+
ingestService: knowledgeService,
|
|
400
|
+
}));
|
|
372
401
|
const mediaProviders = new MediaProviderRegistry();
|
|
373
402
|
ensureBuiltinMediaProviders(mediaProviders, artifactStore, providerRegistry);
|
|
374
403
|
const multimodalService = new MultimodalService(artifactStore, mediaProviders, voiceService, knowledgeService);
|
|
@@ -497,6 +526,8 @@ export function createRuntimeServices(options: RuntimeServicesOptions): RuntimeS
|
|
|
497
526
|
artifactStore,
|
|
498
527
|
knowledgeService,
|
|
499
528
|
homeGraphService,
|
|
529
|
+
projectPlanningService,
|
|
530
|
+
projectPlanningProjectId,
|
|
500
531
|
memoryStore,
|
|
501
532
|
memoryRegistry,
|
|
502
533
|
serviceRegistry,
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { format } from 'node:util';
|
|
2
|
+
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
3
|
+
|
|
4
|
+
type WritableStreamLike = {
|
|
5
|
+
write: {
|
|
6
|
+
(buffer: string | Uint8Array, cb?: (error?: Error | null) => void): boolean;
|
|
7
|
+
(buffer: string | Uint8Array, encoding?: BufferEncoding, cb?: (error?: Error | null) => void): boolean;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type TerminalOutputInterceptSource =
|
|
12
|
+
| 'stdout'
|
|
13
|
+
| 'stderr'
|
|
14
|
+
| 'console.debug'
|
|
15
|
+
| 'console.error'
|
|
16
|
+
| 'console.info'
|
|
17
|
+
| 'console.log'
|
|
18
|
+
| 'console.warn';
|
|
19
|
+
|
|
20
|
+
export type TerminalOutputIntercept = {
|
|
21
|
+
readonly source: TerminalOutputInterceptSource;
|
|
22
|
+
readonly text: string;
|
|
23
|
+
readonly preview: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type TerminalOutputGuard = {
|
|
27
|
+
setActive(active: boolean): void;
|
|
28
|
+
allowTerminalWrite<T>(fn: () => T): T;
|
|
29
|
+
dispose(): void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type TerminalOutputGuardOptions = {
|
|
33
|
+
readonly stdout: WritableStreamLike;
|
|
34
|
+
readonly stderr?: WritableStreamLike;
|
|
35
|
+
readonly active?: boolean;
|
|
36
|
+
readonly onIntercept?: (event: TerminalOutputIntercept) => void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type TuiTerminalOutputGuardOptions = {
|
|
40
|
+
readonly stdout: WritableStreamLike;
|
|
41
|
+
readonly stderr?: WritableStreamLike;
|
|
42
|
+
readonly active?: boolean;
|
|
43
|
+
readonly notify: (message: string) => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const MAX_LOG_TEXT = 4_000;
|
|
47
|
+
const MAX_PREVIEW_TEXT = 180;
|
|
48
|
+
const ANSI_RE = /\x1b\[[0-?]*[ -/]*[@-~]/g;
|
|
49
|
+
|
|
50
|
+
let currentGuard: TerminalOutputGuard | null = null;
|
|
51
|
+
|
|
52
|
+
function writeCallback(args: unknown[]): ((error?: Error | null) => void) | undefined {
|
|
53
|
+
const maybeCallback = args[args.length - 1];
|
|
54
|
+
return typeof maybeCallback === 'function'
|
|
55
|
+
? maybeCallback as (error?: Error | null) => void
|
|
56
|
+
: undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function chunkToText(chunk: unknown): string {
|
|
60
|
+
if (typeof chunk === 'string') return chunk;
|
|
61
|
+
if (chunk instanceof Uint8Array) return Buffer.from(chunk).toString('utf8');
|
|
62
|
+
return String(chunk);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function normalizeText(text: string): string {
|
|
66
|
+
return text.replace(ANSI_RE, '').replace(/\r/g, '').trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function previewText(text: string): string {
|
|
70
|
+
const singleLine = normalizeText(text).replace(/\s+/g, ' ');
|
|
71
|
+
if (singleLine.length <= MAX_PREVIEW_TEXT) return singleLine;
|
|
72
|
+
return `${singleLine.slice(0, MAX_PREVIEW_TEXT - 1)}...`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function truncateForLog(text: string): string {
|
|
76
|
+
if (text.length <= MAX_LOG_TEXT) return text;
|
|
77
|
+
return `${text.slice(0, MAX_LOG_TEXT)}\n[truncated ${text.length - MAX_LOG_TEXT} byte(s)]`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function invokeSuppressedCallback(args: unknown[]): void {
|
|
81
|
+
const callback = writeCallback(args);
|
|
82
|
+
if (callback) {
|
|
83
|
+
queueMicrotask(() => callback(null));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function allowTerminalWrite<T>(fn: () => T): T {
|
|
88
|
+
return currentGuard ? currentGuard.allowTerminalWrite(fn) : fn();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function installTerminalOutputGuard(options: TerminalOutputGuardOptions): TerminalOutputGuard {
|
|
92
|
+
const stdout = options.stdout;
|
|
93
|
+
const stderr = options.stderr ?? process.stderr;
|
|
94
|
+
const originalStdoutWriteMethod = stdout.write;
|
|
95
|
+
const originalStderrWriteMethod = stderr.write;
|
|
96
|
+
const originalStdoutWrite = (...args: unknown[]): boolean =>
|
|
97
|
+
Reflect.apply(originalStdoutWriteMethod, stdout, args) as boolean;
|
|
98
|
+
const originalStderrWrite = (...args: unknown[]): boolean =>
|
|
99
|
+
Reflect.apply(originalStderrWriteMethod, stderr, args) as boolean;
|
|
100
|
+
const originalConsole = {
|
|
101
|
+
debug: console.debug.bind(console),
|
|
102
|
+
error: console.error.bind(console),
|
|
103
|
+
info: console.info.bind(console),
|
|
104
|
+
log: console.log.bind(console),
|
|
105
|
+
warn: console.warn.bind(console),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
let active = options.active ?? true;
|
|
109
|
+
let disposed = false;
|
|
110
|
+
let allowDepth = 0;
|
|
111
|
+
let captureDepth = 0;
|
|
112
|
+
|
|
113
|
+
const record = (source: TerminalOutputInterceptSource, text: string): void => {
|
|
114
|
+
if (disposed || !active) return;
|
|
115
|
+
const normalized = normalizeText(text);
|
|
116
|
+
if (!normalized) return;
|
|
117
|
+
if (normalized.startsWith('[ActivityLogger]')) return;
|
|
118
|
+
if (captureDepth > 0) return;
|
|
119
|
+
|
|
120
|
+
captureDepth++;
|
|
121
|
+
try {
|
|
122
|
+
const event: TerminalOutputIntercept = {
|
|
123
|
+
source,
|
|
124
|
+
text: truncateForLog(normalized),
|
|
125
|
+
preview: previewText(normalized),
|
|
126
|
+
};
|
|
127
|
+
logger.warn('Intercepted terminal output while TUI renderer was active', {
|
|
128
|
+
source: event.source,
|
|
129
|
+
text: event.text,
|
|
130
|
+
});
|
|
131
|
+
options.onIntercept?.(event);
|
|
132
|
+
} finally {
|
|
133
|
+
captureDepth--;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const shouldPassThrough = (): boolean => !active || allowDepth > 0 || disposed;
|
|
138
|
+
|
|
139
|
+
stdout.write = ((...args: unknown[]) => {
|
|
140
|
+
if (shouldPassThrough()) {
|
|
141
|
+
return originalStdoutWrite(...args);
|
|
142
|
+
}
|
|
143
|
+
record('stdout', chunkToText(args[0]));
|
|
144
|
+
invokeSuppressedCallback(args);
|
|
145
|
+
return true;
|
|
146
|
+
}) as WritableStreamLike['write'];
|
|
147
|
+
|
|
148
|
+
stderr.write = ((...args: unknown[]) => {
|
|
149
|
+
if (shouldPassThrough()) {
|
|
150
|
+
return originalStderrWrite(...args);
|
|
151
|
+
}
|
|
152
|
+
record('stderr', chunkToText(args[0]));
|
|
153
|
+
invokeSuppressedCallback(args);
|
|
154
|
+
return true;
|
|
155
|
+
}) as WritableStreamLike['write'];
|
|
156
|
+
|
|
157
|
+
console.debug = (...args: unknown[]) => {
|
|
158
|
+
if (!active || disposed) return originalConsole.debug(...args);
|
|
159
|
+
record('console.debug', format(...args));
|
|
160
|
+
};
|
|
161
|
+
console.error = (...args: unknown[]) => {
|
|
162
|
+
if (!active || disposed) return originalConsole.error(...args);
|
|
163
|
+
record('console.error', format(...args));
|
|
164
|
+
};
|
|
165
|
+
console.info = (...args: unknown[]) => {
|
|
166
|
+
if (!active || disposed) return originalConsole.info(...args);
|
|
167
|
+
record('console.info', format(...args));
|
|
168
|
+
};
|
|
169
|
+
console.log = (...args: unknown[]) => {
|
|
170
|
+
if (!active || disposed) return originalConsole.log(...args);
|
|
171
|
+
record('console.log', format(...args));
|
|
172
|
+
};
|
|
173
|
+
console.warn = (...args: unknown[]) => {
|
|
174
|
+
if (!active || disposed) return originalConsole.warn(...args);
|
|
175
|
+
record('console.warn', format(...args));
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const guard: TerminalOutputGuard = {
|
|
179
|
+
setActive(nextActive) {
|
|
180
|
+
active = nextActive;
|
|
181
|
+
},
|
|
182
|
+
allowTerminalWrite<T>(fn: () => T): T {
|
|
183
|
+
allowDepth++;
|
|
184
|
+
try {
|
|
185
|
+
return fn();
|
|
186
|
+
} finally {
|
|
187
|
+
allowDepth--;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
dispose() {
|
|
191
|
+
if (disposed) return;
|
|
192
|
+
disposed = true;
|
|
193
|
+
stdout.write = originalStdoutWriteMethod;
|
|
194
|
+
stderr.write = originalStderrWriteMethod;
|
|
195
|
+
console.debug = originalConsole.debug;
|
|
196
|
+
console.error = originalConsole.error;
|
|
197
|
+
console.info = originalConsole.info;
|
|
198
|
+
console.log = originalConsole.log;
|
|
199
|
+
console.warn = originalConsole.warn;
|
|
200
|
+
if (currentGuard === guard) {
|
|
201
|
+
currentGuard = null;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
currentGuard = guard;
|
|
207
|
+
return guard;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function installTuiTerminalOutputGuard(options: TuiTerminalOutputGuardOptions): TerminalOutputGuard {
|
|
211
|
+
let capturedWriteCount = 0;
|
|
212
|
+
let lastNoticeAt = 0;
|
|
213
|
+
return installTerminalOutputGuard({
|
|
214
|
+
stdout: options.stdout,
|
|
215
|
+
stderr: options.stderr,
|
|
216
|
+
active: options.active,
|
|
217
|
+
onIntercept: (event) => {
|
|
218
|
+
capturedWriteCount++;
|
|
219
|
+
const now = Date.now();
|
|
220
|
+
if (now - lastNoticeAt < 5_000) return;
|
|
221
|
+
const count = capturedWriteCount;
|
|
222
|
+
capturedWriteCount = 0;
|
|
223
|
+
lastNoticeAt = now;
|
|
224
|
+
const plural = count === 1 ? '' : 's';
|
|
225
|
+
options.notify(`[Terminal] Captured ${count} direct ${event.source} write${plural} that would have corrupted the TUI: ${event.preview}`);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -79,6 +79,8 @@ export interface UiPlatformServices {
|
|
|
79
79
|
export interface UiPlanningServices {
|
|
80
80
|
readonly planManager: RuntimeServices['planManager'];
|
|
81
81
|
readonly adaptivePlanner: RuntimeServices['adaptivePlanner'];
|
|
82
|
+
readonly projectPlanningService: RuntimeServices['projectPlanningService'];
|
|
83
|
+
readonly projectPlanningProjectId: RuntimeServices['projectPlanningProjectId'];
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
export interface UiCoordinationServices {
|
|
@@ -169,6 +171,8 @@ export function createUiRuntimeServices(
|
|
|
169
171
|
planning: {
|
|
170
172
|
planManager: runtimeServices.planManager,
|
|
171
173
|
adaptivePlanner: runtimeServices.adaptivePlanner,
|
|
174
|
+
projectPlanningService: runtimeServices.projectPlanningService,
|
|
175
|
+
projectPlanningProjectId: runtimeServices.projectPlanningProjectId,
|
|
172
176
|
},
|
|
173
177
|
coordination: {
|
|
174
178
|
approvalBroker: runtimeServices.approvalBroker,
|
package/src/shell/ui-openers.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { McpRegistry } from '@pellux/goodvibes-sdk/platform/mcp/registry';
|
|
|
10
10
|
import type { SubscriptionManager } from '@pellux/goodvibes-sdk/platform/config/subscriptions';
|
|
11
11
|
import type { SecretsManager } from '@pellux/goodvibes-sdk/platform/config/secrets';
|
|
12
12
|
import type { ServiceInspectionQuery } from '../runtime/ui-service-queries.ts';
|
|
13
|
+
import type { ModelPickerTargetInfo } from '../input/model-picker.ts';
|
|
13
14
|
|
|
14
15
|
type WireShellUiOpenersOptions = {
|
|
15
16
|
commandContext: CommandContext;
|
|
@@ -112,7 +113,8 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
const getCurrentModelForPickerTarget = (): string => {
|
|
115
|
-
const
|
|
116
|
+
const selectedTarget = input.modelPicker.getSelectedTargetInfo();
|
|
117
|
+
const target = selectedTarget?.target ?? input.modelPicker.target;
|
|
116
118
|
if (target === 'helper') return String(configManager.get('helper.globalModel') || runtime.model);
|
|
117
119
|
if (target === 'tool') return String(configManager.get('tools.llmModel') || runtime.model);
|
|
118
120
|
if (target === 'tts') return String(configManager.get('tts.llmModel') || runtime.model);
|
|
@@ -120,13 +122,64 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
120
122
|
};
|
|
121
123
|
|
|
122
124
|
const getCurrentProviderForPickerTarget = (): string => {
|
|
123
|
-
const
|
|
125
|
+
const selectedTarget = input.modelPicker.getSelectedTargetInfo();
|
|
126
|
+
const target = selectedTarget?.target ?? input.modelPicker.target;
|
|
124
127
|
if (target === 'helper') return String(configManager.get('helper.globalProvider') || runtime.provider);
|
|
125
128
|
if (target === 'tool') return String(configManager.get('tools.llmProvider') || runtime.provider);
|
|
126
129
|
if (target === 'tts') return String(configManager.get('tts.llmProvider') || runtime.provider);
|
|
127
130
|
return runtime.provider;
|
|
128
131
|
};
|
|
129
132
|
|
|
133
|
+
const buildModelPickerTargets = (): ModelPickerTargetInfo[] => {
|
|
134
|
+
const mainProvider = String(configManager.get('provider.provider') || runtime.provider || '').trim();
|
|
135
|
+
const mainModel = String(configManager.get('provider.model') || runtime.model || '').trim();
|
|
136
|
+
const helperProvider = String(configManager.get('helper.globalProvider') ?? '').trim();
|
|
137
|
+
const helperModel = String(configManager.get('helper.globalModel') ?? '').trim();
|
|
138
|
+
const toolProvider = String(configManager.get('tools.llmProvider') ?? '').trim();
|
|
139
|
+
const toolModel = String(configManager.get('tools.llmModel') ?? '').trim();
|
|
140
|
+
const ttsProvider = String(configManager.get('tts.llmProvider') ?? '').trim();
|
|
141
|
+
const ttsModel = String(configManager.get('tts.llmModel') ?? '').trim();
|
|
142
|
+
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
target: 'main',
|
|
146
|
+
label: 'Main Chat',
|
|
147
|
+
description: 'Default provider and model for normal chat turns in this TUI session.',
|
|
148
|
+
provider: mainProvider,
|
|
149
|
+
model: mainModel,
|
|
150
|
+
enabled: true,
|
|
151
|
+
inherited: false,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
target: 'helper',
|
|
155
|
+
label: 'Helper Model',
|
|
156
|
+
description: 'Optional helper route used for supporting work. Empty provider/model values inherit Main Chat.',
|
|
157
|
+
provider: helperProvider || mainProvider,
|
|
158
|
+
model: helperModel || mainModel,
|
|
159
|
+
enabled: Boolean(configManager.get('helper.enabled')),
|
|
160
|
+
inherited: helperProvider.length === 0 && helperModel.length === 0,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
target: 'tool',
|
|
164
|
+
label: 'Tool LLM',
|
|
165
|
+
description: 'Optional LLM route for tool-specific reasoning. Selecting a model enables the tool LLM route.',
|
|
166
|
+
provider: toolProvider || mainProvider,
|
|
167
|
+
model: toolModel || mainModel,
|
|
168
|
+
enabled: Boolean(configManager.get('tools.llmEnabled')),
|
|
169
|
+
inherited: toolProvider.length === 0 && toolModel.length === 0,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
target: 'tts',
|
|
173
|
+
label: 'TTS LLM',
|
|
174
|
+
description: 'Optional LLM override for /tts response generation. Empty values use the current chat model.',
|
|
175
|
+
provider: ttsProvider || mainProvider,
|
|
176
|
+
model: ttsModel || mainModel,
|
|
177
|
+
enabled: true,
|
|
178
|
+
inherited: ttsProvider.length === 0 && ttsModel.length === 0,
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
};
|
|
182
|
+
|
|
130
183
|
commandContext.openModelPicker = () => {
|
|
131
184
|
void (async () => {
|
|
132
185
|
const models = providerRegistry.getSelectableModels();
|
|
@@ -140,6 +193,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
140
193
|
});
|
|
141
194
|
void input.modelPicker.loadRecentModels().catch(() => {}); // best-effort: prefetch for UI, failure is non-visible
|
|
142
195
|
input.modalOpened('modelPicker');
|
|
196
|
+
input.modelPicker.setTargetInfos(buildModelPickerTargets());
|
|
143
197
|
input.modelPicker.openAllModels(models, getCurrentModelForPickerTarget());
|
|
144
198
|
render();
|
|
145
199
|
})().catch((error: unknown) => {
|
|
@@ -159,6 +213,7 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
159
213
|
const secretProviderIds = await resolveSecretProviderIds();
|
|
160
214
|
input.modelPicker.configuredViaMap = buildConfiguredViaMap(providers, configuredIds, subscriptionManager, secretProviderIds);
|
|
161
215
|
input.modalOpened('modelPicker');
|
|
216
|
+
input.modelPicker.setTargetInfos(buildModelPickerTargets());
|
|
162
217
|
input.modelPicker.openProviders(providers, getCurrentProviderForPickerTarget());
|
|
163
218
|
render();
|
|
164
219
|
})().catch((error: unknown) => {
|
|
@@ -206,9 +261,10 @@ export function wireShellUiOpeners(options: WireShellUiOpenersOptions): void {
|
|
|
206
261
|
render();
|
|
207
262
|
};
|
|
208
263
|
|
|
209
|
-
commandContext.openSettingsModal = () => {
|
|
264
|
+
commandContext.openSettingsModal = (target?: string) => {
|
|
210
265
|
input.modalOpened('settings');
|
|
211
266
|
input.settingsModal.open(configManager, featureFlags, subscriptionManager, serviceRegistry, mcpRegistry, secretsManager);
|
|
267
|
+
input.settingsModal.selectTarget(target);
|
|
212
268
|
render();
|
|
213
269
|
};
|
|
214
270
|
|
package/src/utils/clipboard.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils/logger';
|
|
2
2
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
|
|
3
|
+
import { allowTerminalWrite } from '../runtime/terminal-output-guard.ts';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* copyToClipboard - Uses OSC 52 escape sequence to copy text to the terminal clipboard.
|
|
@@ -11,7 +12,7 @@ export function copyToClipboard(text: string) {
|
|
|
11
12
|
try {
|
|
12
13
|
const base64 = Buffer.from(text).toString('base64');
|
|
13
14
|
const sequence = `\x1b]52;c;${base64}\x07`;
|
|
14
|
-
process.stdout.write(sequence);
|
|
15
|
+
allowTerminalWrite(() => process.stdout.write(sequence));
|
|
15
16
|
logger.info('Clipboard: OSC 52 sequence written');
|
|
16
17
|
} catch (err: unknown) {
|
|
17
18
|
logger.error('Clipboard: OSC 52 copy failed', { error: summarizeError(err) });
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.19.
|
|
9
|
+
let _version = '0.19.55';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8'));
|
|
12
12
|
_version = pkg.version ?? _version;
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
-
import type { SelectionItem } from '../selection-modal.ts';
|
|
3
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils/error-display';
|
|
4
|
-
|
|
5
|
-
const VALID_MODES = ['allow-all', 'prompt', 'custom'] as const;
|
|
6
|
-
const VALID_ACTIONS = ['allow', 'prompt', 'deny'] as const;
|
|
7
|
-
const VALID_TOOLS = ['read', 'write', 'edit', 'exec', 'find', 'fetch', 'analyze', 'inspect', 'agent', 'state', 'workflow', 'registry', 'delegate', 'mcp'] as const;
|
|
8
|
-
type PermTool = typeof VALID_TOOLS[number];
|
|
9
|
-
|
|
10
|
-
export function registerPermissionsRuntimeCommands(registry: CommandRegistry): void {
|
|
11
|
-
registry.register({
|
|
12
|
-
name: 'permissions',
|
|
13
|
-
aliases: ['perms'],
|
|
14
|
-
description: 'Show or set permission mode and per-tool settings',
|
|
15
|
-
usage: '[allow-all|prompt|custom] | [tool <name> allow|prompt|deny]',
|
|
16
|
-
argsHint: '[allow-all|prompt|custom]',
|
|
17
|
-
handler(args, ctx) {
|
|
18
|
-
const cm = ctx.platform.configManager;
|
|
19
|
-
if (args.length === 0) {
|
|
20
|
-
if (ctx.openSelection) {
|
|
21
|
-
const items: SelectionItem[] = VALID_TOOLS.map((tool) => ({
|
|
22
|
-
id: tool,
|
|
23
|
-
label: tool,
|
|
24
|
-
detail: cm.get(`permissions.tools.${tool}` as Parameters<typeof cm.get>[0]) as string,
|
|
25
|
-
category: 'tools',
|
|
26
|
-
adjustable: true,
|
|
27
|
-
primaryAction: 'toggle',
|
|
28
|
-
actions: '[Space/Enter] cycle [←/→] adjust',
|
|
29
|
-
}));
|
|
30
|
-
items.unshift({
|
|
31
|
-
id: '__mode__',
|
|
32
|
-
label: 'permission mode',
|
|
33
|
-
detail: cm.get('permissions.mode') as string,
|
|
34
|
-
category: 'global',
|
|
35
|
-
adjustable: true,
|
|
36
|
-
primaryAction: 'toggle',
|
|
37
|
-
actions: '[Space/Enter] cycle [←/→] adjust',
|
|
38
|
-
});
|
|
39
|
-
ctx.openSelection('Permissions', items, { allowSearch: true }, (result) => {
|
|
40
|
-
if (!result) return;
|
|
41
|
-
if (result.item.id === '__mode__') {
|
|
42
|
-
const currentMode = cm.get('permissions.mode') as string;
|
|
43
|
-
const currentIndex = Math.max(0, VALID_MODES.indexOf(currentMode as typeof VALID_MODES[number]));
|
|
44
|
-
const nextMode = result.action === 'decrement'
|
|
45
|
-
? VALID_MODES[(currentIndex - 1 + VALID_MODES.length) % VALID_MODES.length]
|
|
46
|
-
: VALID_MODES[(currentIndex + 1) % VALID_MODES.length];
|
|
47
|
-
cm.setDynamic('permissions.mode', nextMode);
|
|
48
|
-
result.item.detail = nextMode;
|
|
49
|
-
} else {
|
|
50
|
-
const toolKey = `permissions.tools.${result.item.id}` as Parameters<typeof cm.get>[0];
|
|
51
|
-
const currentAction = cm.get(toolKey) as string;
|
|
52
|
-
const currentIndex = Math.max(0, VALID_ACTIONS.indexOf(currentAction as typeof VALID_ACTIONS[number]));
|
|
53
|
-
const nextAction = result.action === 'decrement'
|
|
54
|
-
? VALID_ACTIONS[(currentIndex - 1 + VALID_ACTIONS.length) % VALID_ACTIONS.length]
|
|
55
|
-
: VALID_ACTIONS[(currentIndex + 1) % VALID_ACTIONS.length];
|
|
56
|
-
cm.setDynamic(toolKey, nextAction);
|
|
57
|
-
result.item.detail = nextAction;
|
|
58
|
-
}
|
|
59
|
-
ctx.renderRequest();
|
|
60
|
-
});
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const lines = [`Permission mode: ${cm.get('permissions.mode')}`, ' Tool settings:'];
|
|
64
|
-
for (const tool of VALID_TOOLS) lines.push(` ${tool.padEnd(16)} ${cm.get(`permissions.tools.${tool}` as Parameters<typeof cm.get>[0])}`);
|
|
65
|
-
lines.push('', ' Modes: prompt (default), allow-all, custom', ' Usage: /permissions <mode> | /permissions tool <name> allow|prompt|deny');
|
|
66
|
-
ctx.print(lines.join('\n'));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
if (args[0] === 'tool') {
|
|
70
|
-
const toolName = args[1];
|
|
71
|
-
const action = args[2];
|
|
72
|
-
if (!toolName || !action) {
|
|
73
|
-
ctx.print('Usage: /permissions tool <name> allow|prompt|deny');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (!VALID_TOOLS.includes(toolName as PermTool)) {
|
|
77
|
-
ctx.print(`Unknown tool: ${toolName}\nValid tools: ${VALID_TOOLS.join(', ')}`);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (!VALID_ACTIONS.includes(action as typeof VALID_ACTIONS[number])) {
|
|
81
|
-
ctx.print(`Invalid action: ${action}\nValid actions: allow, prompt, deny`);
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
cm.setDynamic(`permissions.tools.${toolName}` as Parameters<typeof cm.set>[0], action);
|
|
86
|
-
ctx.print(`Permission for ${toolName} set to: ${action}`);
|
|
87
|
-
} catch (e) {
|
|
88
|
-
ctx.print(`Error: ${summarizeError(e)}`);
|
|
89
|
-
}
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (!VALID_MODES.includes(args[0] as typeof VALID_MODES[number])) {
|
|
93
|
-
ctx.print(`Invalid mode: ${args[0]}\nValid modes: ${VALID_MODES.join(', ')}`);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
cm.setDynamic('permissions.mode', args[0]);
|
|
98
|
-
ctx.print(`Permission mode set to: ${args[0]}`);
|
|
99
|
-
} catch (e) {
|
|
100
|
-
ctx.print(`Error: ${summarizeError(e)}`);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
}
|