@pellux/goodvibes-agent 1.1.4 → 1.1.6
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 +12 -0
- package/dist/package/main.js +1321 -825
- package/package.json +1 -1
- package/src/input/agent-workspace-activation.ts +10 -0
- package/src/input/agent-workspace-basic-command-editors.ts +8 -9
- package/src/input/agent-workspace-categories.ts +109 -117
- package/src/input/agent-workspace-host-category.ts +57 -0
- package/src/input/agent-workspace-local-editor-submission.ts +193 -0
- package/src/input/agent-workspace-search.ts +6 -0
- package/src/input/agent-workspace-settings.ts +94 -8
- package/src/input/agent-workspace-setup.ts +20 -0
- package/src/input/agent-workspace-snapshot.ts +89 -0
- package/src/input/agent-workspace-subscription-editor.ts +214 -0
- package/src/input/agent-workspace-types.ts +44 -2
- package/src/input/agent-workspace.ts +73 -133
- package/src/input/handler-modal-token-routes.ts +12 -12
- package/src/input/handler.ts +2 -1
- package/src/main.ts +11 -7
- package/src/renderer/agent-workspace.ts +68 -3
- package/src/shell/agent-workspace-fullscreen.ts +11 -3
- package/src/version.ts +1 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { beginOpenAICodexLogin, exchangeOpenAICodexCode, getSubscriptionProviderConfig } from '@pellux/goodvibes-sdk/platform/config';
|
|
2
|
+
import type { OAuthProviderConfig, ProviderSubscription } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
|
+
import { openExternalUrl } from '@pellux/goodvibes-sdk/platform/utils';
|
|
4
|
+
import type { CommandContext } from './command-registry.ts';
|
|
5
|
+
import { isAffirmative } from './agent-workspace-editors.ts';
|
|
6
|
+
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
7
|
+
import type { AgentWorkspaceActionResult, AgentWorkspaceLocalEditor, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
|
|
8
|
+
|
|
9
|
+
export interface AgentWorkspaceSubscriptionEditorHost {
|
|
10
|
+
localEditor: AgentWorkspaceLocalEditor | null;
|
|
11
|
+
runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null;
|
|
12
|
+
status: string;
|
|
13
|
+
lastActionResult: AgentWorkspaceActionResult | null;
|
|
14
|
+
clampSelection(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type AgentWorkspaceSubscriptionFieldReader = (id: string) => string;
|
|
18
|
+
|
|
19
|
+
export async function submitAgentWorkspaceSubscriptionLoginStartEditor(
|
|
20
|
+
host: AgentWorkspaceSubscriptionEditorHost,
|
|
21
|
+
editor: AgentWorkspaceLocalEditor,
|
|
22
|
+
context: CommandContext | null,
|
|
23
|
+
readField: AgentWorkspaceSubscriptionFieldReader,
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
if (!isAffirmative(readField('confirm'))) {
|
|
26
|
+
host.localEditor = { ...editor, message: 'Subscription login start not confirmed. Type yes, then press Enter.' };
|
|
27
|
+
host.status = 'Subscription login start not confirmed.';
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const provider = readField('provider') || 'openai';
|
|
32
|
+
const openBrowser = isAffirmative(readField('openBrowser'));
|
|
33
|
+
const { manager, services } = subscriptionServices(context);
|
|
34
|
+
const resolved = getSubscriptionProviderConfig(provider, services.get(provider));
|
|
35
|
+
if (!resolved) {
|
|
36
|
+
throw new Error(`OAuth is not configured for ${provider}. Add an OAuth provider service or choose a built-in subscription provider.`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const started = provider === 'openai' && resolved.source === 'builtin'
|
|
40
|
+
? await beginOpenAICodexLogin()
|
|
41
|
+
: null;
|
|
42
|
+
const authorizationUrl = started
|
|
43
|
+
? started.authorizationUrl
|
|
44
|
+
: (await manager.beginOAuthLogin(provider, resolveManualLoginConfig(resolved.oauth))).authorizationUrl;
|
|
45
|
+
|
|
46
|
+
if (started) {
|
|
47
|
+
manager.savePending({
|
|
48
|
+
provider,
|
|
49
|
+
state: started.state,
|
|
50
|
+
verifier: started.verifier,
|
|
51
|
+
redirectUri: started.redirectUri,
|
|
52
|
+
createdAt: Date.now(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const browserOpened = openBrowser ? await openExternalUrl(authorizationUrl) : false;
|
|
57
|
+
host.localEditor = null;
|
|
58
|
+
host.runtimeSnapshot = context ? buildAgentWorkspaceRuntimeSnapshot(context) : host.runtimeSnapshot;
|
|
59
|
+
host.status = `Subscription login started for ${provider}.`;
|
|
60
|
+
host.lastActionResult = {
|
|
61
|
+
kind: 'refreshed',
|
|
62
|
+
title: 'Subscription login started',
|
|
63
|
+
detail: [
|
|
64
|
+
`Provider: ${provider}.`,
|
|
65
|
+
`Browser: ${openBrowser ? (browserOpened ? 'opened' : 'open failed') : 'skipped'}.`,
|
|
66
|
+
'Use Finish subscription login with the callback code or redirect URL.',
|
|
67
|
+
`Authorization URL: ${authorizationUrl}`,
|
|
68
|
+
].join(' '),
|
|
69
|
+
safety: 'safe',
|
|
70
|
+
};
|
|
71
|
+
host.clampSelection();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
74
|
+
host.localEditor = { ...editor, message: detail };
|
|
75
|
+
host.status = detail;
|
|
76
|
+
host.lastActionResult = { kind: 'error', title: 'Subscription login start failed', detail, safety: 'safe' };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function submitAgentWorkspaceSubscriptionLoginFinishEditor(
|
|
81
|
+
host: AgentWorkspaceSubscriptionEditorHost,
|
|
82
|
+
editor: AgentWorkspaceLocalEditor,
|
|
83
|
+
context: CommandContext | null,
|
|
84
|
+
readField: AgentWorkspaceSubscriptionFieldReader,
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
if (!isAffirmative(readField('confirm'))) {
|
|
87
|
+
host.localEditor = { ...editor, message: 'Subscription login finish not confirmed. Type yes, then press Enter.' };
|
|
88
|
+
host.status = 'Subscription login finish not confirmed.';
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const provider = readField('provider') || 'openai';
|
|
93
|
+
const codeInput = readField('code');
|
|
94
|
+
const code = extractAuthorizationCode(codeInput) ?? codeInput;
|
|
95
|
+
const { manager, services } = subscriptionServices(context);
|
|
96
|
+
const resolved = getSubscriptionProviderConfig(provider, services.get(provider));
|
|
97
|
+
if (!resolved) {
|
|
98
|
+
throw new Error(`OAuth is not configured for ${provider}. Start with a configured or built-in subscription provider.`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const record = provider === 'openai' && resolved.source === 'builtin'
|
|
102
|
+
? (() => {
|
|
103
|
+
const pending = manager.getPending(provider);
|
|
104
|
+
if (!pending) throw new Error(`No pending OAuth login for ${provider}. Start subscription login first.`);
|
|
105
|
+
return exchangeOpenAICodexCode(code, pending.verifier).then((token) => {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
return manager.saveSubscription({
|
|
108
|
+
provider,
|
|
109
|
+
accessToken: token.accessToken,
|
|
110
|
+
tokenType: token.tokenType,
|
|
111
|
+
...(typeof token.refreshToken === 'string' && token.refreshToken.length > 0
|
|
112
|
+
? { refreshToken: token.refreshToken }
|
|
113
|
+
: {}),
|
|
114
|
+
...(typeof token.expiresAt === 'number' && Number.isFinite(token.expiresAt)
|
|
115
|
+
? { expiresAt: token.expiresAt }
|
|
116
|
+
: {}),
|
|
117
|
+
...(token.scopes ? { scopes: token.scopes } : {}),
|
|
118
|
+
authMode: 'oauth',
|
|
119
|
+
overrideAmbientApiKeys: false,
|
|
120
|
+
createdAt: manager.get(provider)?.createdAt ?? now,
|
|
121
|
+
updatedAt: now,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
})()
|
|
125
|
+
: manager.completeOAuthLogin(provider, resolveManualLoginConfig(resolved.oauth), code);
|
|
126
|
+
|
|
127
|
+
const saved = await record;
|
|
128
|
+
host.localEditor = null;
|
|
129
|
+
host.runtimeSnapshot = context ? buildAgentWorkspaceRuntimeSnapshot(context) : host.runtimeSnapshot;
|
|
130
|
+
host.status = `Subscription session saved for ${provider}.`;
|
|
131
|
+
host.lastActionResult = {
|
|
132
|
+
kind: 'refreshed',
|
|
133
|
+
title: 'Subscription session saved',
|
|
134
|
+
detail: [
|
|
135
|
+
`Provider: ${provider}.`,
|
|
136
|
+
`Token type: ${saved.tokenType}.`,
|
|
137
|
+
`Expires: ${saved.expiresAt ? new Date(saved.expiresAt).toISOString() : 'n/a'}.`,
|
|
138
|
+
describeSubscriptionPrecedence(saved),
|
|
139
|
+
].join(' '),
|
|
140
|
+
safety: 'safe',
|
|
141
|
+
};
|
|
142
|
+
host.clampSelection();
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
145
|
+
host.localEditor = { ...editor, message: detail };
|
|
146
|
+
host.status = detail;
|
|
147
|
+
host.lastActionResult = { kind: 'error', title: 'Subscription login finish failed', detail, safety: 'safe' };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function submitAgentWorkspaceSubscriptionLogoutEditor(
|
|
152
|
+
host: AgentWorkspaceSubscriptionEditorHost,
|
|
153
|
+
editor: AgentWorkspaceLocalEditor,
|
|
154
|
+
context: CommandContext | null,
|
|
155
|
+
readField: AgentWorkspaceSubscriptionFieldReader,
|
|
156
|
+
): void {
|
|
157
|
+
if (!isAffirmative(readField('confirm'))) {
|
|
158
|
+
host.localEditor = { ...editor, message: 'Subscription logout not confirmed. Type yes, then press Enter.' };
|
|
159
|
+
host.status = 'Subscription logout not confirmed.';
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const provider = readField('provider') || 'openai';
|
|
164
|
+
const { manager } = subscriptionServices(context);
|
|
165
|
+
const removed = manager.logout(provider);
|
|
166
|
+
host.localEditor = null;
|
|
167
|
+
host.runtimeSnapshot = context ? buildAgentWorkspaceRuntimeSnapshot(context) : host.runtimeSnapshot;
|
|
168
|
+
host.status = removed ? `Logged out of ${provider}.` : `No subscription session existed for ${provider}.`;
|
|
169
|
+
host.lastActionResult = {
|
|
170
|
+
kind: removed ? 'refreshed' : 'guidance',
|
|
171
|
+
title: removed ? 'Subscription session removed' : 'No subscription session found',
|
|
172
|
+
detail: removed
|
|
173
|
+
? `Removed active or pending subscription state for ${provider}. Ambient API key resolution applies if configured.`
|
|
174
|
+
: `No active or pending subscription state existed for ${provider}.`,
|
|
175
|
+
safety: 'safe',
|
|
176
|
+
};
|
|
177
|
+
host.clampSelection();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
180
|
+
host.localEditor = { ...editor, message: detail };
|
|
181
|
+
host.status = detail;
|
|
182
|
+
host.lastActionResult = { kind: 'error', title: 'Subscription logout failed', detail, safety: 'safe' };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function subscriptionServices(context: CommandContext | null) {
|
|
187
|
+
const manager = context?.platform.subscriptionManager;
|
|
188
|
+
const services = context?.platform.serviceRegistry;
|
|
189
|
+
if (!manager || !services) throw new Error('Subscription services are unavailable in this runtime.');
|
|
190
|
+
return { manager, services };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function extractAuthorizationCode(input: string): string | null {
|
|
194
|
+
const trimmed = input.trim();
|
|
195
|
+
if (!trimmed) return null;
|
|
196
|
+
try {
|
|
197
|
+
const url = new URL(trimmed);
|
|
198
|
+
return url.searchParams.get('code');
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveManualLoginConfig(config: OAuthProviderConfig): OAuthProviderConfig {
|
|
205
|
+
return config.manualRedirectUri
|
|
206
|
+
? { ...config, redirectUri: config.manualRedirectUri }
|
|
207
|
+
: config;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function describeSubscriptionPrecedence(record: Pick<ProviderSubscription, 'overrideAmbientApiKeys'>): string {
|
|
211
|
+
return record.overrideAmbientApiKeys
|
|
212
|
+
? 'Subscription routing now overrides ambient API keys for this provider.'
|
|
213
|
+
: 'Subscription session is stored for subscription-backed flows; ambient API keys are unchanged.';
|
|
214
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ModelPickerTarget } from './model-picker.ts';
|
|
1
2
|
import type { AgentWorkspaceChannelStatus } from './agent-workspace-channels.ts';
|
|
2
3
|
import type { AgentWorkspaceSetupChecklistItem } from './agent-workspace-setup.ts';
|
|
3
4
|
import type { AgentWorkspaceVoiceMediaReadiness } from './agent-workspace-voice-media.ts';
|
|
@@ -17,7 +18,7 @@ export const AGENT_WORKSPACE_CATEGORY_IDS = [
|
|
|
17
18
|
'onboarding-channels',
|
|
18
19
|
'onboarding-voice-media',
|
|
19
20
|
'onboarding-context',
|
|
20
|
-
'onboarding-
|
|
21
|
+
'onboarding-automation',
|
|
21
22
|
'research',
|
|
22
23
|
'artifacts',
|
|
23
24
|
'conversation',
|
|
@@ -40,7 +41,7 @@ export const AGENT_WORKSPACE_CATEGORY_IDS = [
|
|
|
40
41
|
|
|
41
42
|
export type AgentWorkspaceCategoryId = (typeof AGENT_WORKSPACE_CATEGORY_IDS)[number];
|
|
42
43
|
|
|
43
|
-
export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'setting' | 'settings-import' | 'local-selection' | 'local-operation' | 'onboarding-complete';
|
|
44
|
+
export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'setting' | 'settings-import' | 'model-picker' | 'settings-modal' | 'local-selection' | 'local-operation' | 'onboarding-complete';
|
|
44
45
|
|
|
45
46
|
export type AgentWorkspaceLocalEditorKind = 'memory' | 'note' | 'persona' | 'skill' | 'routine' | 'profile';
|
|
46
47
|
|
|
@@ -258,6 +259,9 @@ export interface AgentWorkspaceAction {
|
|
|
258
259
|
readonly editorKind?: AgentWorkspaceEditorKind;
|
|
259
260
|
readonly settingKey?: string;
|
|
260
261
|
readonly settingValueHint?: string;
|
|
262
|
+
readonly modelPickerTarget?: ModelPickerTarget;
|
|
263
|
+
readonly modelPickerFlow?: 'providerModel' | 'model';
|
|
264
|
+
readonly settingsTarget?: string;
|
|
261
265
|
readonly visibleWhenSettingKey?: string;
|
|
262
266
|
readonly visibleWhenSettingValue?: string | boolean | number;
|
|
263
267
|
readonly localKind?: AgentWorkspaceLocalEditorKind;
|
|
@@ -362,11 +366,49 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
362
366
|
readonly provider: string;
|
|
363
367
|
readonly model: string;
|
|
364
368
|
readonly modelDisplayName: string;
|
|
369
|
+
readonly embeddingProvider: string;
|
|
370
|
+
readonly reasoningEffort: string;
|
|
371
|
+
readonly helperEnabled: boolean;
|
|
372
|
+
readonly toolLlmEnabled: boolean;
|
|
373
|
+
readonly providerFailureHints: boolean;
|
|
374
|
+
readonly cacheEnabled: boolean;
|
|
375
|
+
readonly cacheStableTtl: string;
|
|
376
|
+
readonly cacheMonitorHitRate: boolean;
|
|
377
|
+
readonly cacheHitRateWarningThreshold: number;
|
|
378
|
+
readonly hitlMode: string;
|
|
379
|
+
readonly guidanceMode: string;
|
|
380
|
+
readonly saveHistory: boolean;
|
|
381
|
+
readonly autoApprove: boolean;
|
|
382
|
+
readonly autoCompactThreshold: number;
|
|
383
|
+
readonly staleContextWarnings: boolean;
|
|
384
|
+
readonly showThinking: boolean;
|
|
385
|
+
readonly showReasoningSummary: boolean;
|
|
386
|
+
readonly theme: string;
|
|
387
|
+
readonly stream: boolean;
|
|
388
|
+
readonly lineNumbers: string;
|
|
389
|
+
readonly operationalMessages: string;
|
|
390
|
+
readonly systemMessages: string;
|
|
391
|
+
readonly releaseChannel: string;
|
|
392
|
+
readonly permissionMode: string;
|
|
393
|
+
readonly toolAutoHeal: boolean;
|
|
394
|
+
readonly toolsDefaultTokenBudget: number;
|
|
395
|
+
readonly artifactMaxBytes: number;
|
|
396
|
+
readonly rawPromptTelemetry: boolean;
|
|
397
|
+
readonly automationEnabled: boolean;
|
|
398
|
+
readonly automationMaxConcurrentRuns: number;
|
|
399
|
+
readonly automationRunHistoryLimit: number;
|
|
400
|
+
readonly automationDefaultTimeoutMs: number;
|
|
401
|
+
readonly automationCatchUpWindowMinutes: number;
|
|
402
|
+
readonly automationFailureCooldownMs: number;
|
|
403
|
+
readonly automationDeleteAfterRun: boolean;
|
|
365
404
|
readonly sessionId: string;
|
|
366
405
|
readonly workingDirectory: string;
|
|
367
406
|
readonly homeDirectory: string;
|
|
368
407
|
readonly runtimeBaseUrl: string;
|
|
369
408
|
readonly runtimeOwnership: 'external';
|
|
409
|
+
readonly activeSubscriptionCount: number;
|
|
410
|
+
readonly pendingSubscriptionCount: number;
|
|
411
|
+
readonly availableSubscriptionProviderCount: number;
|
|
370
412
|
readonly sessionMemoryCount: number;
|
|
371
413
|
readonly localMemoryCount: number;
|
|
372
414
|
readonly localMemoryReviewQueueCount: number;
|
|
@@ -6,21 +6,21 @@ import type { CommandContext } from './command-registry.ts';
|
|
|
6
6
|
import { AgentNoteRegistry } from '../agent/note-registry.ts';
|
|
7
7
|
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
8
8
|
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
9
|
-
import {
|
|
9
|
+
import type { AgentRuntimeProfileInfo } from '../agent/runtime-profile.ts';
|
|
10
10
|
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
11
11
|
import { activateAgentWorkspaceSelection } from './agent-workspace-activation.ts';
|
|
12
12
|
import { AGENT_WORKSPACE_CATEGORIES } from './agent-workspace-categories.ts';
|
|
13
13
|
import { buildAgentWorkspaceCommandEditorSubmission, isAgentWorkspaceCommandEditorKind } from './agent-workspace-command-editor.ts';
|
|
14
|
-
import { createDeleteEditor, editorCategoryId
|
|
15
|
-
import {
|
|
14
|
+
import { createDeleteEditor, editorCategoryId } from './agent-workspace-editors.ts';
|
|
15
|
+
import { submitAgentWorkspaceLocalRegistryEditor } from './agent-workspace-local-editor-submission.ts';
|
|
16
16
|
import { clampAgentWorkspaceLocalLibrarySelection, moveAgentWorkspaceLocalLibraryItemSelection, selectedAgentWorkspaceLocalLibraryItem, type AgentWorkspaceLocalSelectionIndexes } from './agent-workspace-local-selection.ts';
|
|
17
17
|
import { applyAgentWorkspaceLocalLibraryOperation } from './agent-workspace-local-operations.ts';
|
|
18
18
|
import { deleteAgentWorkspaceMemoryEditor, submitAgentWorkspaceMemoryEditor } from './agent-workspace-memory-editor.ts';
|
|
19
19
|
import { jumpAgentWorkspaceSelection, moveAgentWorkspaceSelection, selectAgentWorkspaceCategory } from './agent-workspace-navigation.ts';
|
|
20
|
-
import { buildAgentWorkspaceRequirements } from './agent-workspace-requirements.ts';
|
|
21
20
|
import { appendAgentWorkspaceActionSearchText, backspaceAgentWorkspaceActionSearch, beginAgentWorkspaceActionSearch, clearAgentWorkspaceActionSearch, commitAgentWorkspaceActionSearchSelection, searchAgentWorkspaceActions } from './agent-workspace-search.ts';
|
|
22
21
|
import { agentWorkspaceSettingSchema, applyAgentWorkspaceSettingValue, buildAgentWorkspaceSettingActionEffect, importAgentWorkspaceTuiSettings, isAgentWorkspaceActionVisible } from './agent-workspace-settings.ts';
|
|
23
22
|
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
23
|
+
import { submitAgentWorkspaceSubscriptionLoginFinishEditor, submitAgentWorkspaceSubscriptionLoginStartEditor, submitAgentWorkspaceSubscriptionLogoutEditor } from './agent-workspace-subscription-editor.ts';
|
|
24
24
|
import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceActionSearchResult, AgentWorkspaceCategory, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspacePromptDispatcher, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
|
|
25
25
|
import { writeOnboardingCheckMarker, writeOnboardingCompletionMarker } from '../runtime/onboarding/index.ts';
|
|
26
26
|
|
|
@@ -333,6 +333,55 @@ export class AgentWorkspace {
|
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
+
openModelPickerAction(action: AgentWorkspaceAction, requestRender?: () => void): void {
|
|
337
|
+
const target = action.modelPickerTarget ?? 'main';
|
|
338
|
+
const opened = action.modelPickerFlow === 'model'
|
|
339
|
+
? this.context?.openModelPickerWithTarget?.(target)
|
|
340
|
+
: this.context?.openProviderModelPickerWithTarget?.(target);
|
|
341
|
+
if (!opened) {
|
|
342
|
+
this.status = 'Model picker is unavailable.';
|
|
343
|
+
this.lastActionResult = {
|
|
344
|
+
kind: 'error',
|
|
345
|
+
title: 'Model picker unavailable',
|
|
346
|
+
detail: 'This runtime cannot open the model picker from Agent workspace.',
|
|
347
|
+
safety: action.safety,
|
|
348
|
+
};
|
|
349
|
+
requestRender?.();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this.status = `Opening ${action.label}.`;
|
|
353
|
+
this.lastActionResult = {
|
|
354
|
+
kind: 'dispatched',
|
|
355
|
+
title: `Opening ${action.label}`,
|
|
356
|
+
detail: 'Opened the shared provider/model picker for this setup target.',
|
|
357
|
+
safety: action.safety,
|
|
358
|
+
};
|
|
359
|
+
requestRender?.();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
openSettingsModalAction(action: AgentWorkspaceAction, requestRender?: () => void): void {
|
|
363
|
+
if (!this.context?.openSettingsModal) {
|
|
364
|
+
this.status = 'Settings are unavailable.';
|
|
365
|
+
this.lastActionResult = {
|
|
366
|
+
kind: 'error',
|
|
367
|
+
title: 'Settings unavailable',
|
|
368
|
+
detail: 'This runtime cannot open settings from Agent workspace.',
|
|
369
|
+
safety: action.safety,
|
|
370
|
+
};
|
|
371
|
+
requestRender?.();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
this.context.openSettingsModal(action.settingsTarget);
|
|
375
|
+
this.status = `Opening ${action.label}.`;
|
|
376
|
+
this.lastActionResult = {
|
|
377
|
+
kind: 'dispatched',
|
|
378
|
+
title: `Opening ${action.label}`,
|
|
379
|
+
detail: 'Opened the shared settings surface for this setup area.',
|
|
380
|
+
safety: action.safety,
|
|
381
|
+
};
|
|
382
|
+
requestRender?.();
|
|
383
|
+
}
|
|
384
|
+
|
|
336
385
|
applySettingAction(action: AgentWorkspaceAction, requestRender?: () => void): void {
|
|
337
386
|
const effect = buildAgentWorkspaceSettingActionEffect(this.context, action);
|
|
338
387
|
if (effect.kind === 'result') {
|
|
@@ -446,6 +495,19 @@ export class AgentWorkspace {
|
|
|
446
495
|
this.status = `${missing.label} is required.`;
|
|
447
496
|
return;
|
|
448
497
|
}
|
|
498
|
+
if (editor.kind === 'subscription-login-start') {
|
|
499
|
+
void submitAgentWorkspaceSubscriptionLoginStartEditor(this, editor, this.context, (id) => this.editorField(id)).finally(() => requestRender?.());
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (editor.kind === 'subscription-login-finish') {
|
|
503
|
+
void submitAgentWorkspaceSubscriptionLoginFinishEditor(this, editor, this.context, (id) => this.editorField(id)).finally(() => requestRender?.());
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (editor.kind === 'subscription-logout') {
|
|
507
|
+
submitAgentWorkspaceSubscriptionLogoutEditor(this, editor, this.context, (id) => this.editorField(id));
|
|
508
|
+
requestRender?.();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
449
511
|
if (isAgentWorkspaceCommandEditorKind(editor.kind)) {
|
|
450
512
|
this.submitCommandEditor(editor);
|
|
451
513
|
requestRender?.();
|
|
@@ -496,135 +558,13 @@ export class AgentWorkspace {
|
|
|
496
558
|
return;
|
|
497
559
|
}
|
|
498
560
|
try {
|
|
499
|
-
|
|
500
|
-
this.
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
name: this.editorField('name'),
|
|
507
|
-
description: this.editorField('description'),
|
|
508
|
-
notes: this.editorField('notes'),
|
|
509
|
-
tags: splitList(this.editorField('tags')),
|
|
510
|
-
triggers: splitList(this.editorField('triggers')),
|
|
511
|
-
enable: isAffirmative(this.editorField('enable')),
|
|
512
|
-
});
|
|
513
|
-
this.finishLocalEditor(created.kind, created.id, created.name, 'Created');
|
|
514
|
-
} else if (editor.kind === 'profile') {
|
|
515
|
-
const template = this.editorField('template');
|
|
516
|
-
const templateId = template && template.toLowerCase() !== 'none' ? template : undefined;
|
|
517
|
-
const profile = createAgentRuntimeProfile(shellPaths.homeDirectory, this.editorField('name'), {
|
|
518
|
-
...(templateId ? { templateId } : {}),
|
|
519
|
-
});
|
|
520
|
-
this.finishProfileEditor(profile);
|
|
521
|
-
} else if (editor.kind === 'note') {
|
|
522
|
-
const registry = AgentNoteRegistry.fromShellPaths(shellPaths);
|
|
523
|
-
if (editor.mode === 'update' && editor.recordId) {
|
|
524
|
-
const updated = registry.update(editor.recordId, {
|
|
525
|
-
title: this.editorField('title'),
|
|
526
|
-
body: this.editorField('body'),
|
|
527
|
-
sourceUrl: this.editorField('sourceUrl'),
|
|
528
|
-
tags: splitList(this.editorField('tags')),
|
|
529
|
-
provenance: 'Workspace',
|
|
530
|
-
});
|
|
531
|
-
this.finishLocalEditor(editor.kind, updated.id, updated.title, 'Updated');
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
const created = registry.create({
|
|
535
|
-
title: this.editorField('title'),
|
|
536
|
-
body: this.editorField('body'),
|
|
537
|
-
sourceUrl: this.editorField('sourceUrl'),
|
|
538
|
-
tags: splitList(this.editorField('tags')),
|
|
539
|
-
source: 'user',
|
|
540
|
-
provenance: 'Workspace',
|
|
541
|
-
});
|
|
542
|
-
this.finishLocalEditor(editor.kind, created.id, created.title, 'Created');
|
|
543
|
-
} else if (editor.kind === 'persona') {
|
|
544
|
-
const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
|
|
545
|
-
if (editor.mode === 'update' && editor.recordId) {
|
|
546
|
-
const wasActive = registry.snapshot().activePersonaId === editor.recordId;
|
|
547
|
-
const updated = registry.update(editor.recordId, {
|
|
548
|
-
name: this.editorField('name'),
|
|
549
|
-
description: this.editorField('description'),
|
|
550
|
-
body: this.editorField('body'),
|
|
551
|
-
tags: splitList(this.editorField('tags')),
|
|
552
|
-
triggers: splitList(this.editorField('triggers')),
|
|
553
|
-
provenance: 'Workspace',
|
|
554
|
-
});
|
|
555
|
-
if (isAffirmative(this.editorField('activate'))) registry.setActive(updated.id);
|
|
556
|
-
else if (wasActive) registry.clearActive();
|
|
557
|
-
this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
const created = registry.create({
|
|
561
|
-
name: this.editorField('name'),
|
|
562
|
-
description: this.editorField('description'),
|
|
563
|
-
body: this.editorField('body'),
|
|
564
|
-
tags: splitList(this.editorField('tags')),
|
|
565
|
-
triggers: splitList(this.editorField('triggers')),
|
|
566
|
-
source: 'user',
|
|
567
|
-
provenance: 'Workspace',
|
|
568
|
-
});
|
|
569
|
-
if (isAffirmative(this.editorField('activate'))) registry.setActive(created.id);
|
|
570
|
-
this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
|
|
571
|
-
} else if (editor.kind === 'skill') {
|
|
572
|
-
const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
|
|
573
|
-
if (editor.mode === 'update' && editor.recordId) {
|
|
574
|
-
const updated = registry.update(editor.recordId, {
|
|
575
|
-
name: this.editorField('name'),
|
|
576
|
-
description: this.editorField('description'),
|
|
577
|
-
procedure: this.editorField('procedure'),
|
|
578
|
-
triggers: splitList(this.editorField('triggers')),
|
|
579
|
-
tags: splitList(this.editorField('tags')),
|
|
580
|
-
requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
|
|
581
|
-
provenance: 'Workspace',
|
|
582
|
-
});
|
|
583
|
-
registry.setEnabled(updated.id, isAffirmative(this.editorField('enabled')));
|
|
584
|
-
this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
const created = registry.create({
|
|
588
|
-
name: this.editorField('name'),
|
|
589
|
-
description: this.editorField('description'),
|
|
590
|
-
procedure: this.editorField('procedure'),
|
|
591
|
-
triggers: splitList(this.editorField('triggers')),
|
|
592
|
-
tags: splitList(this.editorField('tags')),
|
|
593
|
-
requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
|
|
594
|
-
enabled: isAffirmative(this.editorField('enabled')),
|
|
595
|
-
source: 'user',
|
|
596
|
-
provenance: 'Workspace',
|
|
597
|
-
});
|
|
598
|
-
this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
|
|
599
|
-
} else {
|
|
600
|
-
const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
|
|
601
|
-
if (editor.mode === 'update' && editor.recordId) {
|
|
602
|
-
const updated = registry.update(editor.recordId, {
|
|
603
|
-
name: this.editorField('name'),
|
|
604
|
-
description: this.editorField('description'),
|
|
605
|
-
steps: this.editorField('steps'),
|
|
606
|
-
triggers: splitList(this.editorField('triggers')),
|
|
607
|
-
tags: splitList(this.editorField('tags')),
|
|
608
|
-
requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
|
|
609
|
-
provenance: 'Workspace',
|
|
610
|
-
});
|
|
611
|
-
registry.setEnabled(updated.id, isAffirmative(this.editorField('enabled')));
|
|
612
|
-
this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
const created = registry.create({
|
|
616
|
-
name: this.editorField('name'),
|
|
617
|
-
description: this.editorField('description'),
|
|
618
|
-
steps: this.editorField('steps'),
|
|
619
|
-
triggers: splitList(this.editorField('triggers')),
|
|
620
|
-
tags: splitList(this.editorField('tags')),
|
|
621
|
-
requirements: buildAgentWorkspaceRequirements((id) => this.editorField(id)),
|
|
622
|
-
enabled: isAffirmative(this.editorField('enabled')),
|
|
623
|
-
source: 'user',
|
|
624
|
-
provenance: 'Workspace',
|
|
625
|
-
});
|
|
626
|
-
this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
|
|
627
|
-
}
|
|
561
|
+
submitAgentWorkspaceLocalRegistryEditor(shellPaths, editor, {
|
|
562
|
+
readField: (id) => this.editorField(id),
|
|
563
|
+
learnedBehaviorTarget: () => this.learnedBehaviorTarget(),
|
|
564
|
+
submitDeleteEditor: () => this.submitLocalDeleteEditor(shellPaths, editor),
|
|
565
|
+
finishLocalEditor: (kind, id, name, verb) => this.finishLocalEditor(kind, id, name, verb),
|
|
566
|
+
finishProfileEditor: (profile) => this.finishProfileEditor(profile),
|
|
567
|
+
});
|
|
628
568
|
} catch (error) {
|
|
629
569
|
const detail = error instanceof Error ? error.message : String(error);
|
|
630
570
|
this.localEditor = { ...editor, message: detail };
|
|
@@ -131,6 +131,18 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
131
131
|
return withState(state, true);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
if (handleModelPickerToken({
|
|
135
|
+
modelPicker: state.modelPicker,
|
|
136
|
+
modalStack: state.modalStack,
|
|
137
|
+
commandContext: state.commandContext,
|
|
138
|
+
getViewportHeight: state.getViewportHeight,
|
|
139
|
+
requestRender: state.requestRender,
|
|
140
|
+
handleEscape: state.handleEscape,
|
|
141
|
+
onModelPickerCommit: state.onModelPickerCommit,
|
|
142
|
+
}, token)) {
|
|
143
|
+
return withState(state, true);
|
|
144
|
+
}
|
|
145
|
+
|
|
134
146
|
if (handleSettingsModalToken({
|
|
135
147
|
settingsModal: state.settingsModal,
|
|
136
148
|
commandContext: state.commandContext,
|
|
@@ -200,18 +212,6 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
200
212
|
return withState(state, true, historyState);
|
|
201
213
|
}
|
|
202
214
|
|
|
203
|
-
if (handleModelPickerToken({
|
|
204
|
-
modelPicker: state.modelPicker,
|
|
205
|
-
modalStack: state.modalStack,
|
|
206
|
-
commandContext: state.commandContext,
|
|
207
|
-
getViewportHeight: state.getViewportHeight,
|
|
208
|
-
requestRender: state.requestRender,
|
|
209
|
-
handleEscape: state.handleEscape,
|
|
210
|
-
onModelPickerCommit: state.onModelPickerCommit,
|
|
211
|
-
}, token)) {
|
|
212
|
-
return withState(state, true);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
215
|
if (handleLiveTailToken({
|
|
216
216
|
liveTailModal: state.liveTailModal,
|
|
217
217
|
processModal: state.processModal,
|
package/src/input/handler.ts
CHANGED
|
@@ -336,7 +336,8 @@ export class InputHandler {
|
|
|
336
336
|
public handleBlockToggle(): void { handleBlockToggleForHandler(this); }
|
|
337
337
|
public handleCtrlC(): void { handleCtrlCForHandler(this); }
|
|
338
338
|
public modalOpened(name: string): void {
|
|
339
|
-
|
|
339
|
+
const keepAgentWorkspaceUnderlay = name === 'modelPicker' || name === 'settings';
|
|
340
|
+
if (name !== 'agentWorkspace' && !keepAgentWorkspaceUnderlay && this.agentWorkspace.active) {
|
|
340
341
|
this.closeAgentWorkspaceModal();
|
|
341
342
|
}
|
|
342
343
|
modalOpenedForHandler(this, name);
|
package/src/main.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { PermissionPromptUI } from './permissions/prompt.ts';
|
|
|
15
15
|
import { CommandRegistry } from './input/command-registry.ts';
|
|
16
16
|
import type { CommandContext } from './input/command-registry.ts';
|
|
17
17
|
import { renderProcessIndicator } from './renderer/process-indicator.ts';
|
|
18
|
+
import { renderModelWorkspace } from './renderer/model-workspace.ts';
|
|
19
|
+
import { renderSettingsModal } from './renderer/settings-modal.ts';
|
|
18
20
|
import { registerBuiltinCommands } from './input/commands.ts';
|
|
19
21
|
import { ScheduleManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
20
22
|
import { InputHistory } from './input/input-history.ts';
|
|
@@ -40,7 +42,7 @@ import {
|
|
|
40
42
|
} from '@/runtime/index.ts';
|
|
41
43
|
import type { SessionSnapshot } from '@/runtime/index.ts';
|
|
42
44
|
import { handleBlockingShellInput, type PendingPermissionState } from './shell/blocking-input.ts';
|
|
43
|
-
import { createAgentWorkspaceFullscreenComposite } from './shell/agent-workspace-fullscreen.ts';
|
|
45
|
+
import { createAgentWorkspaceFullscreenComposite, createFullscreenCompositeFromLines } from './shell/agent-workspace-fullscreen.ts';
|
|
44
46
|
import { getTerminalSize } from './shell/terminal-size.ts';
|
|
45
47
|
import { wireShellUiOpeners } from './shell/ui-openers.ts';
|
|
46
48
|
import { deriveComposerState } from './core/composer-state.ts';
|
|
@@ -477,6 +479,14 @@ async function main() {
|
|
|
477
479
|
input.setPanelMouseLayout(null);
|
|
478
480
|
activeConversationWidth = width;
|
|
479
481
|
conversation.setSplashSuppressed(true);
|
|
482
|
+
if (input.modelPicker.active) {
|
|
483
|
+
compositor.composite(createFullscreenCompositeFromLines(renderModelWorkspace(input.modelPicker, width, height), width, height));
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (input.settingsModal.active) {
|
|
487
|
+
compositor.composite(createFullscreenCompositeFromLines(renderSettingsModal(input.settingsModal, width, height), width, height));
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
480
490
|
compositor.composite(createAgentWorkspaceFullscreenComposite(input.agentWorkspace, width, height));
|
|
481
491
|
return;
|
|
482
492
|
}
|
|
@@ -709,7 +719,6 @@ async function main() {
|
|
|
709
719
|
streamTokenSpeed = elapsed > 0 ? streamDeltaCount / elapsed : 0;
|
|
710
720
|
}));
|
|
711
721
|
|
|
712
|
-
// --- Terminal setup ---
|
|
713
722
|
stdin.setRawMode(true);
|
|
714
723
|
stdin.resume();
|
|
715
724
|
stdin.setEncoding('utf8');
|
|
@@ -748,11 +757,9 @@ async function main() {
|
|
|
748
757
|
process.on('unhandledRejection', unhandledRejectionHandler);
|
|
749
758
|
stdout.on('resize', resizeHandler);
|
|
750
759
|
|
|
751
|
-
// Initial render
|
|
752
760
|
conversation.rebuildHistory();
|
|
753
761
|
render();
|
|
754
762
|
|
|
755
|
-
// --- Crash recovery check ---
|
|
756
763
|
const recoveryInfo = checkRecoveryFile({ workingDirectory: workingDir, homeDirectory });
|
|
757
764
|
if (recoveryInfo) {
|
|
758
765
|
systemMessageRouter.high(`[Recovery] Found unsaved session from ${new Date(recoveryInfo.timestamp).toLocaleString()}. Title: "${recoveryInfo.title}". Press Ctrl+R to restore, Esc to discard, or start typing to ignore it.`);
|
|
@@ -763,7 +770,6 @@ async function main() {
|
|
|
763
770
|
recoveryPending = true;
|
|
764
771
|
}
|
|
765
772
|
|
|
766
|
-
// --- Auto-save to recovery file every 60s ---
|
|
767
773
|
recoveryInterval = setInterval(() => {
|
|
768
774
|
const snapshot = buildCurrentSessionSnapshot();
|
|
769
775
|
writeRecoveryFile(
|
|
@@ -773,9 +779,7 @@ async function main() {
|
|
|
773
779
|
{ workingDirectory: workingDir, homeDirectory },
|
|
774
780
|
);
|
|
775
781
|
}, 60_000);
|
|
776
|
-
|
|
777
782
|
}
|
|
778
|
-
|
|
779
783
|
main().catch((err: unknown) => {
|
|
780
784
|
const detail = formatFatalStartupErrorForLog(err);
|
|
781
785
|
try {
|