@pellux/goodvibes-agent 1.1.3 → 1.1.5

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.
@@ -1,4 +1,5 @@
1
1
  import { basename, sep } from 'node:path';
2
+ import { listAvailableSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
2
3
  import type { MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
3
4
  import type { CommandContext } from './command-registry.ts';
4
5
  import { AgentNoteRegistry, type AgentNoteRecord } from '../agent/note-registry.ts';
@@ -398,6 +399,41 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
398
399
  const ttsVoice = readConfigString(context, 'tts.voice', '(voice default)');
399
400
  const ttsLlmProvider = readConfigString(context, 'tts.llmProvider', '');
400
401
  const ttsLlmModel = readConfigString(context, 'tts.llmModel', '');
402
+ const embeddingProvider = readConfigString(context, 'provider.embeddingProvider', '(provider default)');
403
+ const reasoningEffort = readConfigString(context, 'provider.reasoningEffort', '(default)');
404
+ const helperEnabled = readConfigBoolean(context, 'helper.enabled', false);
405
+ const toolLlmEnabled = readConfigBoolean(context, 'tools.llmEnabled', false);
406
+ const providerFailureHints = readConfigBoolean(context, 'behavior.suggestAlternativeOnProviderFail', false);
407
+ const cacheEnabled = readConfigBoolean(context, 'cache.enabled', true);
408
+ const cacheStableTtl = readConfigString(context, 'cache.stableTtl', '(default)');
409
+ const cacheMonitorHitRate = readConfigBoolean(context, 'cache.monitorHitRate', true);
410
+ const cacheHitRateWarningThreshold = readConfigNumber(context, 'cache.hitRateWarningThreshold', 0.3);
411
+ const hitlMode = readConfigString(context, 'behavior.hitlMode', '(default)');
412
+ const guidanceMode = readConfigString(context, 'behavior.guidanceMode', '(default)');
413
+ const saveHistory = readConfigBoolean(context, 'behavior.saveHistory', true);
414
+ const autoApprove = readConfigBoolean(context, 'behavior.autoApprove', false);
415
+ const autoCompactThreshold = readConfigNumber(context, 'behavior.autoCompactThreshold', 0);
416
+ const staleContextWarnings = readConfigBoolean(context, 'behavior.staleContextWarnings', false);
417
+ const showThinking = readConfigBoolean(context, 'display.showThinking', false);
418
+ const showReasoningSummary = readConfigBoolean(context, 'display.showReasoningSummary', false);
419
+ const theme = readConfigString(context, 'display.theme', '(default)');
420
+ const stream = readConfigBoolean(context, 'display.stream', true);
421
+ const lineNumbers = readConfigString(context, 'display.lineNumbers', '(default)');
422
+ const operationalMessages = readConfigString(context, 'ui.operationalMessages', '(default)');
423
+ const systemMessages = readConfigString(context, 'ui.systemMessages', '(default)');
424
+ const releaseChannel = readConfigString(context, 'release.channel', '(default)');
425
+ const permissionMode = readConfigString(context, 'permissions.mode', '(default)');
426
+ const toolAutoHeal = readConfigBoolean(context, 'tools.autoHeal', false);
427
+ const toolsDefaultTokenBudget = readConfigNumber(context, 'tools.defaultTokenBudget', 5000);
428
+ const artifactMaxBytes = readConfigNumber(context, 'storage.artifacts.maxBytes', 512 * 1024 * 1024);
429
+ const rawPromptTelemetry = readConfigBoolean(context, 'telemetry.includeRawPrompts', false);
430
+ const automationEnabled = readConfigBoolean(context, 'automation.enabled', false);
431
+ const automationMaxConcurrentRuns = readConfigNumber(context, 'automation.maxConcurrentRuns', 4);
432
+ const automationRunHistoryLimit = readConfigNumber(context, 'automation.runHistoryLimit', 100);
433
+ const automationDefaultTimeoutMs = readConfigNumber(context, 'automation.defaultTimeoutMs', 15 * 60 * 1000);
434
+ const automationCatchUpWindowMinutes = readConfigNumber(context, 'automation.catchUpWindowMinutes', 30);
435
+ const automationFailureCooldownMs = readConfigNumber(context, 'automation.failureCooldownMs', 5 * 60 * 1000);
436
+ const automationDeleteAfterRun = readConfigBoolean(context, 'automation.deleteAfterRun', false);
401
437
  const runtimeBaseUrl = `http://${host}:${port}`;
402
438
  const companionAccess = (() => {
403
439
  const homeDirectory = context.workspace?.shellPaths?.homeDirectory ?? '';
@@ -425,6 +461,18 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
425
461
  nextStep,
426
462
  } as const;
427
463
  })();
464
+ const subscriptionSnapshot = (() => {
465
+ try {
466
+ const manager = context.platform?.subscriptionManager;
467
+ const services = context.platform?.serviceRegistry;
468
+ const active = manager?.list?.().length ?? 0;
469
+ const pending = manager?.listPending?.().length ?? 0;
470
+ const available = services ? listAvailableSubscriptionProviders(services.getAll()).length : 0;
471
+ return { active, pending, available };
472
+ } catch {
473
+ return { active: 0, pending: 0, available: 0 };
474
+ }
475
+ })();
428
476
  const channels = buildAgentWorkspaceChannels(context);
429
477
  const voiceMediaReadiness = buildAgentWorkspaceVoiceMediaReadiness({
430
478
  context,
@@ -435,6 +483,9 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
435
483
  provider,
436
484
  model,
437
485
  runtimeBaseUrl,
486
+ activeSubscriptionCount: subscriptionSnapshot.active,
487
+ pendingSubscriptionCount: subscriptionSnapshot.pending,
488
+ availableSubscriptionProviderCount: subscriptionSnapshot.available,
438
489
  sessionMemoryCount,
439
490
  localMemoryCount: memorySnapshot.count,
440
491
  localMemoryReviewQueueCount: memorySnapshot.reviewQueueCount,
@@ -463,11 +514,49 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
463
514
  provider,
464
515
  model,
465
516
  modelDisplayName: currentModel?.displayName ?? model,
517
+ embeddingProvider,
518
+ reasoningEffort,
519
+ helperEnabled,
520
+ toolLlmEnabled,
521
+ providerFailureHints,
522
+ cacheEnabled,
523
+ cacheStableTtl,
524
+ cacheMonitorHitRate,
525
+ cacheHitRateWarningThreshold,
526
+ hitlMode,
527
+ guidanceMode,
528
+ saveHistory,
529
+ autoApprove,
530
+ autoCompactThreshold,
531
+ staleContextWarnings,
532
+ showThinking,
533
+ showReasoningSummary,
534
+ theme,
535
+ stream,
536
+ lineNumbers,
537
+ operationalMessages,
538
+ systemMessages,
539
+ releaseChannel,
540
+ permissionMode,
541
+ toolAutoHeal,
542
+ toolsDefaultTokenBudget,
543
+ artifactMaxBytes,
544
+ rawPromptTelemetry,
545
+ automationEnabled,
546
+ automationMaxConcurrentRuns,
547
+ automationRunHistoryLimit,
548
+ automationDefaultTimeoutMs,
549
+ automationCatchUpWindowMinutes,
550
+ automationFailureCooldownMs,
551
+ automationDeleteAfterRun,
466
552
  sessionId: context.session?.runtime?.sessionId ?? 'unknown',
467
553
  workingDirectory: context.workspace?.shellPaths?.workingDirectory ?? 'unavailable',
468
554
  homeDirectory: context.workspace?.shellPaths?.homeDirectory ?? 'unavailable',
469
555
  runtimeBaseUrl,
470
556
  runtimeOwnership: 'external',
557
+ activeSubscriptionCount: subscriptionSnapshot.active,
558
+ pendingSubscriptionCount: subscriptionSnapshot.pending,
559
+ availableSubscriptionProviderCount: subscriptionSnapshot.available,
471
560
  sessionMemoryCount,
472
561
  localMemoryCount: memorySnapshot.count,
473
562
  localMemoryReviewQueueCount: memorySnapshot.reviewQueueCount,
@@ -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';
@@ -10,6 +11,14 @@ export type AgentWorkspaceFocusPane = 'categories' | 'actions';
10
11
  export const AGENT_WORKSPACE_CATEGORY_IDS = [
11
12
  'home',
12
13
  'setup',
14
+ 'account-model',
15
+ 'assistant-behavior',
16
+ 'tools-permissions',
17
+ 'onboarding-display',
18
+ 'onboarding-channels',
19
+ 'onboarding-voice-media',
20
+ 'onboarding-context',
21
+ 'onboarding-automation',
13
22
  'research',
14
23
  'artifacts',
15
24
  'conversation',
@@ -32,7 +41,7 @@ export const AGENT_WORKSPACE_CATEGORY_IDS = [
32
41
 
33
42
  export type AgentWorkspaceCategoryId = (typeof AGENT_WORKSPACE_CATEGORY_IDS)[number];
34
43
 
35
- export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | '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';
36
45
 
37
46
  export type AgentWorkspaceLocalEditorKind = 'memory' | 'note' | 'persona' | 'skill' | 'routine' | 'profile';
38
47
 
@@ -178,6 +187,7 @@ export type AgentWorkspaceEditorKind =
178
187
  | 'schedule-receipt'
179
188
  | 'mode-preset'
180
189
  | 'mode-domain'
190
+ | 'setting-set'
181
191
  | 'model-pin'
182
192
  | 'model-unpin'
183
193
  | 'delegate-task'
@@ -247,6 +257,13 @@ export interface AgentWorkspaceAction {
247
257
  readonly command?: string;
248
258
  readonly targetCategoryId?: AgentWorkspaceCategoryId;
249
259
  readonly editorKind?: AgentWorkspaceEditorKind;
260
+ readonly settingKey?: string;
261
+ readonly settingValueHint?: string;
262
+ readonly modelPickerTarget?: ModelPickerTarget;
263
+ readonly modelPickerFlow?: 'providerModel' | 'model';
264
+ readonly settingsTarget?: string;
265
+ readonly visibleWhenSettingKey?: string;
266
+ readonly visibleWhenSettingValue?: string | boolean | number;
250
267
  readonly localKind?: AgentWorkspaceLocalEditorKind;
251
268
  readonly selectionDelta?: number;
252
269
  readonly localOperation?: AgentWorkspaceLocalOperation;
@@ -349,11 +366,49 @@ export interface AgentWorkspaceRuntimeSnapshot {
349
366
  readonly provider: string;
350
367
  readonly model: string;
351
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;
352
404
  readonly sessionId: string;
353
405
  readonly workingDirectory: string;
354
406
  readonly homeDirectory: string;
355
407
  readonly runtimeBaseUrl: string;
356
408
  readonly runtimeOwnership: 'external';
409
+ readonly activeSubscriptionCount: number;
410
+ readonly pendingSubscriptionCount: number;
411
+ readonly availableSubscriptionProviderCount: number;
357
412
  readonly sessionMemoryCount: number;
358
413
  readonly localMemoryCount: number;
359
414
  readonly localMemoryReviewQueueCount: number;