@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,57 @@
|
|
|
1
|
+
import type { AgentWorkspaceCategory } from './agent-workspace-types.ts';
|
|
2
|
+
|
|
3
|
+
export const AGENT_WORKSPACE_HOST_CATEGORY: AgentWorkspaceCategory = {
|
|
4
|
+
id: 'host',
|
|
5
|
+
group: 'OPERATIONS',
|
|
6
|
+
label: 'Connected Host',
|
|
7
|
+
summary: 'Connected-host health, tasks, sessions, channels, and automation.',
|
|
8
|
+
detail: 'Use this workspace to inspect the GoodVibes host surfaces that Agent can see: system health, remote routes, host tasks, sessions, channels, schedules, knowledge, media, MCP, provider auth, support bundles, and telemetry/config posture.',
|
|
9
|
+
actions: [
|
|
10
|
+
{ id: 'host-overview', label: 'Host overview', detail: 'Review connected-host, provider, settings, continuity, and Agent health without starting or mutating host work.', command: '/health review', kind: 'command', safety: 'read-only' },
|
|
11
|
+
{ id: 'host-services', label: 'Host services', detail: 'Inspect connected-host service readiness, credentials, and integration issues without changing them.', command: '/health host', kind: 'command', safety: 'read-only' },
|
|
12
|
+
{ id: 'host-setup', label: 'Setup review', detail: 'Inspect setup issues that affect connected-host and Agent runtime readiness.', command: '/health setup', kind: 'command', safety: 'read-only' },
|
|
13
|
+
{ id: 'host-remote', label: 'Remote routes', detail: 'Inspect remote build-host route health and recovery signals without starting build work.', command: '/health remote', kind: 'command', safety: 'read-only' },
|
|
14
|
+
{ id: 'host-maintenance', label: 'Session maintenance', detail: 'Review continuity, compaction, and current-session maintenance posture.', command: '/health maintenance', kind: 'command', safety: 'read-only' },
|
|
15
|
+
{ id: 'host-continuity', label: 'Continuity', detail: 'Inspect last-session pointer, recovery-file posture, and return-context state.', command: '/health continuity', kind: 'command', safety: 'read-only' },
|
|
16
|
+
{ id: 'host-tasks', label: 'Host tasks', detail: 'Inspect connected-host task state without creating, retrying, or mutating tasks.', command: '/tasks list', kind: 'command', safety: 'read-only' },
|
|
17
|
+
{ id: 'host-task-filter', label: 'Filter host tasks', detail: 'Open a status/type form for read-only connected-host task filtering.', editorKind: 'task-list-filter', kind: 'editor', safety: 'read-only' },
|
|
18
|
+
{ id: 'host-task-show', label: 'Inspect host task', detail: 'Open a task-id form for read-only connected-host task metadata.', editorKind: 'task-show', kind: 'editor', safety: 'read-only' },
|
|
19
|
+
{ id: 'host-task-output', label: 'Show task output', detail: 'Open a task-id form for read-only connected-host task output.', editorKind: 'task-output', kind: 'editor', safety: 'read-only' },
|
|
20
|
+
{ id: 'host-sessions', label: 'Host session routes', detail: 'Browse saved Agent sessions and return-context posture exposed through the runtime session surface.', command: '/session list', kind: 'command', safety: 'read-only' },
|
|
21
|
+
{ id: 'host-session-graph', label: 'Session graph', detail: 'Open a read-only form for cross-session graph inspection; graph mutation remains blocked in Agent.', editorKind: 'session-graph', kind: 'editor', safety: 'read-only' },
|
|
22
|
+
{ id: 'host-channels-status', label: 'Channel status', detail: 'Inspect connected channel runtime status from the host without mutating delivery state.', command: '/channels status', kind: 'command', safety: 'read-only' },
|
|
23
|
+
{ id: 'host-channel-accounts', label: 'Channel accounts', detail: 'Inspect connected channel accounts without printing secret values or sending messages.', command: '/channels accounts', kind: 'command', safety: 'read-only' },
|
|
24
|
+
{ id: 'host-channel-policies', label: 'Channel policies', detail: 'Inspect channel delivery policy posture without changing routing.', command: '/channels policies', kind: 'command', safety: 'read-only' },
|
|
25
|
+
{ id: 'host-channels', label: 'Open Channels', detail: 'Jump to the channel setup and delivery workspace.', targetCategoryId: 'channels', kind: 'workspace', safety: 'safe' },
|
|
26
|
+
{ id: 'host-automation', label: 'Open Automation', detail: 'Jump to connected schedules, reminders, receipts, and explicit run controls.', targetCategoryId: 'automation', kind: 'workspace', safety: 'safe' },
|
|
27
|
+
{ id: 'host-schedules', label: 'Schedule status', detail: 'Inspect schedules and run history without running or mutating them.', command: '/schedule list', kind: 'command', safety: 'read-only' },
|
|
28
|
+
{ id: 'host-schedule-reconcile', label: 'Reconcile schedules', detail: 'Compare local promotion receipts with live connected schedules.', command: '/schedule reconcile', kind: 'command', safety: 'read-only' },
|
|
29
|
+
{ id: 'host-knowledge', label: 'Knowledge route', detail: 'Inspect isolated Agent Knowledge route readiness and source counts.', command: '/knowledge status', kind: 'command', safety: 'read-only' },
|
|
30
|
+
{ id: 'host-knowledge-open', label: 'Open Knowledge', detail: 'Jump to Agent Knowledge source, graph, review, and ingest controls.', targetCategoryId: 'knowledge', kind: 'workspace', safety: 'safe' },
|
|
31
|
+
{ id: 'host-media', label: 'Media providers', detail: 'Inspect configured media providers before generating media with confirmation.', command: '/media providers', kind: 'command', safety: 'read-only' },
|
|
32
|
+
{ id: 'host-media-open', label: 'Open Voice and Media', detail: 'Jump to voice, TTS, image input, media provider, and browser-tool setup actions.', targetCategoryId: 'voice-media', kind: 'workspace', safety: 'safe' },
|
|
33
|
+
{ id: 'host-mcp-health', label: 'MCP health', detail: 'Inspect MCP lifecycle issues without changing trust posture or approving tool-definition review.', command: '/health mcp', kind: 'command', safety: 'read-only' },
|
|
34
|
+
{ id: 'host-mcp-review', label: 'MCP review', detail: 'Inspect MCP server connection, trust, role, and review posture.', command: '/mcp review', kind: 'command', safety: 'read-only' },
|
|
35
|
+
{ id: 'host-tools-open', label: 'Open Tools and MCP', detail: 'Jump to MCP server setup, tool inventory, secrets, and security review.', targetCategoryId: 'tools', kind: 'workspace', safety: 'safe' },
|
|
36
|
+
{ id: 'host-provider-accounts', label: 'Provider accounts', detail: 'Review provider account routes, subscription windows, and billing-path safety.', command: '/accounts review', kind: 'command', safety: 'read-only' },
|
|
37
|
+
{ id: 'host-provider-detail', label: 'Provider detail', detail: 'Open a provider-id form for account and provider configuration review.', editorKind: 'provider-inspect', kind: 'editor', safety: 'read-only' },
|
|
38
|
+
{ id: 'host-provider-routes', label: 'Provider routes', detail: 'Open a provider-id form for account, subscription, and route inspection.', editorKind: 'provider-routes', kind: 'editor', safety: 'read-only' },
|
|
39
|
+
{ id: 'host-provider-repair', label: 'Provider repair guidance', detail: 'Open a provider-id form for read-only provider repair guidance.', editorKind: 'provider-account-repair', kind: 'editor', safety: 'read-only' },
|
|
40
|
+
{ id: 'host-auth-owner', label: 'Connected-host auth owner', detail: 'Show that connected-host auth administration belongs to the owning GoodVibes host, not Agent.', command: '/auth local', kind: 'command', safety: 'read-only' },
|
|
41
|
+
{ id: 'host-auth-review', label: 'Provider auth review', detail: 'Review provider auth posture without printing token values.', command: '/auth review', kind: 'command', safety: 'read-only' },
|
|
42
|
+
{ id: 'host-auth-detail', label: 'Provider auth detail', detail: 'Open a provider-id form for read-only provider auth inspection.', editorKind: 'auth-show', kind: 'editor', safety: 'read-only' },
|
|
43
|
+
{ id: 'host-auth-repair', label: 'Provider auth repair', detail: 'Open a provider-id form for provider auth repair guidance.', editorKind: 'auth-repair', kind: 'editor', safety: 'read-only' },
|
|
44
|
+
{ id: 'host-auth-bundle-export', label: 'Export auth bundle', detail: 'Open a confirmed form that exports a redacted provider auth review bundle.', editorKind: 'auth-bundle-export', kind: 'editor', safety: 'safe' },
|
|
45
|
+
{ id: 'host-auth-bundle-inspect', label: 'Inspect auth bundle', detail: 'Open a form that inspects a provider auth review bundle before sharing or import.', editorKind: 'auth-bundle-inspect', kind: 'editor', safety: 'read-only' },
|
|
46
|
+
{ id: 'host-subscription-bundle-export', label: 'Export subscription bundle', detail: 'Open a confirmed form that exports redacted provider subscription state for review.', editorKind: 'subscription-bundle-export', kind: 'editor', safety: 'safe' },
|
|
47
|
+
{ id: 'host-subscription-bundle-inspect', label: 'Inspect subscription bundle', detail: 'Open a form that inspects provider subscription state before sharing.', editorKind: 'subscription-bundle-inspect', kind: 'editor', safety: 'read-only' },
|
|
48
|
+
{ id: 'host-security', label: 'Security review', detail: 'Inspect token posture, MCP attack paths, policy lint, plugin risk, and incident pressure.', command: '/security review', kind: 'command', safety: 'read-only' },
|
|
49
|
+
{ id: 'host-trust', label: 'Trust review', detail: 'Review permission, secret, plugin, and MCP trust posture without exporting bundles or changing trust.', command: '/trust review', kind: 'command', safety: 'read-only' },
|
|
50
|
+
{ id: 'host-support-bundle-export', label: 'Export support bundle', detail: 'Export a redacted Agent support bundle from the Host page.', editorKind: 'support-bundle-export', kind: 'editor', safety: 'safe' },
|
|
51
|
+
{ id: 'host-support-bundle-inspect', label: 'Inspect support bundle', detail: 'Inspect a support bundle before import or sharing.', editorKind: 'support-bundle-inspect', kind: 'editor', safety: 'read-only' },
|
|
52
|
+
{ id: 'host-support-bundle-import', label: 'Import support bundle', detail: 'Import reviewed, non-redacted config values from a support bundle.', editorKind: 'support-bundle-import', kind: 'editor', safety: 'safe' },
|
|
53
|
+
{ id: 'host-telemetry', label: 'Telemetry settings', detail: 'Open telemetry payload policy settings for connected-host and Agent observability review.', command: '/config telemetry', kind: 'command', safety: 'safe' },
|
|
54
|
+
{ id: 'host-config', label: 'Control-plane settings', detail: 'Open configuration controls for service, channels, tools, automation, provider, and storage posture.', command: '/config', kind: 'command', safety: 'safe' },
|
|
55
|
+
{ id: 'host-safety', label: 'Host mutation policy', detail: 'Agent may inspect connected-host surfaces and run explicit confirmed actions, but does not silently create tasks, send channel messages, mutate auth, or start automation.', kind: 'guidance', safety: 'blocked' },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
2
|
+
import { AgentNoteRegistry } from '../agent/note-registry.ts';
|
|
3
|
+
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
4
|
+
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
5
|
+
import { createAgentRuntimeProfile, type AgentRuntimeProfileInfo } from '../agent/runtime-profile.ts';
|
|
6
|
+
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
7
|
+
import { createAgentWorkspaceLearnedBehavior } from './agent-workspace-learned-behavior.ts';
|
|
8
|
+
import { buildAgentWorkspaceRequirements } from './agent-workspace-requirements.ts';
|
|
9
|
+
import { isAffirmative, splitList } from './agent-workspace-editors.ts';
|
|
10
|
+
import type { AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind } from './agent-workspace-types.ts';
|
|
11
|
+
|
|
12
|
+
type LearnedBehaviorTarget = Exclude<AgentWorkspaceLocalEditorKind, 'memory' | 'note' | 'profile'>;
|
|
13
|
+
|
|
14
|
+
export interface AgentWorkspaceLocalEditorSubmissionCallbacks {
|
|
15
|
+
readonly readField: (id: string) => string;
|
|
16
|
+
readonly learnedBehaviorTarget: () => LearnedBehaviorTarget;
|
|
17
|
+
readonly submitDeleteEditor: () => void;
|
|
18
|
+
readonly finishLocalEditor: (kind: AgentWorkspaceLocalEditorKind, id: string, name: string, verb: 'Created' | 'Updated') => void;
|
|
19
|
+
readonly finishProfileEditor: (profile: AgentRuntimeProfileInfo) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function submitAgentWorkspaceLocalRegistryEditor(
|
|
23
|
+
shellPaths: ShellPathService,
|
|
24
|
+
editor: AgentWorkspaceLocalEditor,
|
|
25
|
+
callbacks: AgentWorkspaceLocalEditorSubmissionCallbacks,
|
|
26
|
+
): void {
|
|
27
|
+
const field = callbacks.readField;
|
|
28
|
+
if (editor.mode === 'delete') {
|
|
29
|
+
callbacks.submitDeleteEditor();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (editor.kind === 'learned-behavior') {
|
|
33
|
+
const created = createAgentWorkspaceLearnedBehavior(shellPaths, {
|
|
34
|
+
target: callbacks.learnedBehaviorTarget(),
|
|
35
|
+
name: field('name'),
|
|
36
|
+
description: field('description'),
|
|
37
|
+
notes: field('notes'),
|
|
38
|
+
tags: splitList(field('tags')),
|
|
39
|
+
triggers: splitList(field('triggers')),
|
|
40
|
+
enable: isAffirmative(field('enable')),
|
|
41
|
+
});
|
|
42
|
+
callbacks.finishLocalEditor(created.kind, created.id, created.name, 'Created');
|
|
43
|
+
} else if (editor.kind === 'profile') {
|
|
44
|
+
const template = field('template');
|
|
45
|
+
const templateId = template && template.toLowerCase() !== 'none' ? template : undefined;
|
|
46
|
+
const profile = createAgentRuntimeProfile(shellPaths.homeDirectory, field('name'), {
|
|
47
|
+
...(templateId ? { templateId } : {}),
|
|
48
|
+
});
|
|
49
|
+
callbacks.finishProfileEditor(profile);
|
|
50
|
+
} else if (editor.kind === 'note') {
|
|
51
|
+
submitNoteEditor(shellPaths, editor, field, callbacks.finishLocalEditor);
|
|
52
|
+
} else if (editor.kind === 'persona') {
|
|
53
|
+
submitPersonaEditor(shellPaths, editor, field, callbacks.finishLocalEditor);
|
|
54
|
+
} else if (editor.kind === 'skill') {
|
|
55
|
+
submitSkillEditor(shellPaths, editor, field, callbacks.finishLocalEditor);
|
|
56
|
+
} else {
|
|
57
|
+
submitRoutineEditor(shellPaths, editor, field, callbacks.finishLocalEditor);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function submitNoteEditor(
|
|
62
|
+
shellPaths: ShellPathService,
|
|
63
|
+
editor: AgentWorkspaceLocalEditor,
|
|
64
|
+
field: (id: string) => string,
|
|
65
|
+
finish: AgentWorkspaceLocalEditorSubmissionCallbacks['finishLocalEditor'],
|
|
66
|
+
): void {
|
|
67
|
+
const registry = AgentNoteRegistry.fromShellPaths(shellPaths);
|
|
68
|
+
if (editor.mode === 'update' && editor.recordId) {
|
|
69
|
+
const updated = registry.update(editor.recordId, {
|
|
70
|
+
title: field('title'),
|
|
71
|
+
body: field('body'),
|
|
72
|
+
sourceUrl: field('sourceUrl'),
|
|
73
|
+
tags: splitList(field('tags')),
|
|
74
|
+
provenance: 'Workspace',
|
|
75
|
+
});
|
|
76
|
+
finish('note', updated.id, updated.title, 'Updated');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const created = registry.create({
|
|
80
|
+
title: field('title'),
|
|
81
|
+
body: field('body'),
|
|
82
|
+
sourceUrl: field('sourceUrl'),
|
|
83
|
+
tags: splitList(field('tags')),
|
|
84
|
+
source: 'user',
|
|
85
|
+
provenance: 'Workspace',
|
|
86
|
+
});
|
|
87
|
+
finish('note', created.id, created.title, 'Created');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function submitPersonaEditor(
|
|
91
|
+
shellPaths: ShellPathService,
|
|
92
|
+
editor: AgentWorkspaceLocalEditor,
|
|
93
|
+
field: (id: string) => string,
|
|
94
|
+
finish: AgentWorkspaceLocalEditorSubmissionCallbacks['finishLocalEditor'],
|
|
95
|
+
): void {
|
|
96
|
+
const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
|
|
97
|
+
if (editor.mode === 'update' && editor.recordId) {
|
|
98
|
+
const wasActive = registry.snapshot().activePersonaId === editor.recordId;
|
|
99
|
+
const updated = registry.update(editor.recordId, {
|
|
100
|
+
name: field('name'),
|
|
101
|
+
description: field('description'),
|
|
102
|
+
body: field('body'),
|
|
103
|
+
tags: splitList(field('tags')),
|
|
104
|
+
triggers: splitList(field('triggers')),
|
|
105
|
+
provenance: 'Workspace',
|
|
106
|
+
});
|
|
107
|
+
if (isAffirmative(field('activate'))) registry.setActive(updated.id);
|
|
108
|
+
else if (wasActive) registry.clearActive();
|
|
109
|
+
finish('persona', updated.id, updated.name, 'Updated');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const created = registry.create({
|
|
113
|
+
name: field('name'),
|
|
114
|
+
description: field('description'),
|
|
115
|
+
body: field('body'),
|
|
116
|
+
tags: splitList(field('tags')),
|
|
117
|
+
triggers: splitList(field('triggers')),
|
|
118
|
+
source: 'user',
|
|
119
|
+
provenance: 'Workspace',
|
|
120
|
+
});
|
|
121
|
+
if (isAffirmative(field('activate'))) registry.setActive(created.id);
|
|
122
|
+
finish('persona', created.id, created.name, 'Created');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function submitSkillEditor(
|
|
126
|
+
shellPaths: ShellPathService,
|
|
127
|
+
editor: AgentWorkspaceLocalEditor,
|
|
128
|
+
field: (id: string) => string,
|
|
129
|
+
finish: AgentWorkspaceLocalEditorSubmissionCallbacks['finishLocalEditor'],
|
|
130
|
+
): void {
|
|
131
|
+
const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
|
|
132
|
+
if (editor.mode === 'update' && editor.recordId) {
|
|
133
|
+
const updated = registry.update(editor.recordId, {
|
|
134
|
+
name: field('name'),
|
|
135
|
+
description: field('description'),
|
|
136
|
+
procedure: field('procedure'),
|
|
137
|
+
triggers: splitList(field('triggers')),
|
|
138
|
+
tags: splitList(field('tags')),
|
|
139
|
+
requirements: buildAgentWorkspaceRequirements(field),
|
|
140
|
+
provenance: 'Workspace',
|
|
141
|
+
});
|
|
142
|
+
registry.setEnabled(updated.id, isAffirmative(field('enabled')));
|
|
143
|
+
finish('skill', updated.id, updated.name, 'Updated');
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const created = registry.create({
|
|
147
|
+
name: field('name'),
|
|
148
|
+
description: field('description'),
|
|
149
|
+
procedure: field('procedure'),
|
|
150
|
+
triggers: splitList(field('triggers')),
|
|
151
|
+
tags: splitList(field('tags')),
|
|
152
|
+
requirements: buildAgentWorkspaceRequirements(field),
|
|
153
|
+
enabled: isAffirmative(field('enabled')),
|
|
154
|
+
source: 'user',
|
|
155
|
+
provenance: 'Workspace',
|
|
156
|
+
});
|
|
157
|
+
finish('skill', created.id, created.name, 'Created');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function submitRoutineEditor(
|
|
161
|
+
shellPaths: ShellPathService,
|
|
162
|
+
editor: AgentWorkspaceLocalEditor,
|
|
163
|
+
field: (id: string) => string,
|
|
164
|
+
finish: AgentWorkspaceLocalEditorSubmissionCallbacks['finishLocalEditor'],
|
|
165
|
+
): void {
|
|
166
|
+
const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
|
|
167
|
+
if (editor.mode === 'update' && editor.recordId) {
|
|
168
|
+
const updated = registry.update(editor.recordId, {
|
|
169
|
+
name: field('name'),
|
|
170
|
+
description: field('description'),
|
|
171
|
+
steps: field('steps'),
|
|
172
|
+
triggers: splitList(field('triggers')),
|
|
173
|
+
tags: splitList(field('tags')),
|
|
174
|
+
requirements: buildAgentWorkspaceRequirements(field),
|
|
175
|
+
provenance: 'Workspace',
|
|
176
|
+
});
|
|
177
|
+
registry.setEnabled(updated.id, isAffirmative(field('enabled')));
|
|
178
|
+
finish('routine', updated.id, updated.name, 'Updated');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const created = registry.create({
|
|
182
|
+
name: field('name'),
|
|
183
|
+
description: field('description'),
|
|
184
|
+
steps: field('steps'),
|
|
185
|
+
triggers: splitList(field('triggers')),
|
|
186
|
+
tags: splitList(field('tags')),
|
|
187
|
+
requirements: buildAgentWorkspaceRequirements(field),
|
|
188
|
+
enabled: isAffirmative(field('enabled')),
|
|
189
|
+
source: 'user',
|
|
190
|
+
provenance: 'Workspace',
|
|
191
|
+
});
|
|
192
|
+
finish('routine', created.id, created.name, 'Created');
|
|
193
|
+
}
|
|
@@ -40,6 +40,9 @@ export function searchAgentWorkspaceActions(
|
|
|
40
40
|
action.command ?? '',
|
|
41
41
|
action.editorKind ?? '',
|
|
42
42
|
action.targetCategoryId ?? '',
|
|
43
|
+
action.modelPickerFlow ?? '',
|
|
44
|
+
action.modelPickerTarget ?? '',
|
|
45
|
+
action.settingsTarget ?? '',
|
|
43
46
|
action.localOperation ?? '',
|
|
44
47
|
action.safety,
|
|
45
48
|
].join(' ').toLowerCase();
|
|
@@ -80,6 +83,9 @@ function scoreActionSearchResult(
|
|
|
80
83
|
score += scoreField(action.editorKind, terms, exactQuery, 85, 26);
|
|
81
84
|
score += scoreField(action.command, terms, exactQuery, 75, 22);
|
|
82
85
|
score += scoreField(action.label, terms, exactQuery, 65, 18);
|
|
86
|
+
score += scoreField(action.modelPickerTarget, terms, exactQuery, 58, 18);
|
|
87
|
+
score += scoreField(action.settingsTarget, terms, exactQuery, 58, 18);
|
|
88
|
+
score += scoreField(action.modelPickerFlow, terms, exactQuery, 54, 16);
|
|
83
89
|
score += scoreField(action.localOperation, terms, exactQuery, 50, 16);
|
|
84
90
|
score += scoreField(action.targetCategoryId, terms, exactQuery, 40, 12);
|
|
85
91
|
score += scoreField(action.detail, terms, exactQuery, 24, 6);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import type { ConfigKey, ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
|
+
import type { PendingSubscriptionLogin, ProviderSubscription } from '@pellux/goodvibes-sdk/platform/config';
|
|
3
4
|
import { setHarnessSetting } from '../agent/harness-control.ts';
|
|
4
5
|
import { isExternalHostOwnedSettingKey } from '../config/agent-settings-policy.ts';
|
|
5
6
|
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
@@ -83,6 +84,83 @@ function valuesMatch(left: unknown, right: unknown): boolean {
|
|
|
83
84
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
function readRecordMap(record: Record<string, unknown>, key: string): readonly unknown[] {
|
|
88
|
+
const value = record[key];
|
|
89
|
+
return isRecord(value) ? Object.values(value) : [];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isProviderSubscription(value: unknown): value is ProviderSubscription {
|
|
93
|
+
return isRecord(value)
|
|
94
|
+
&& typeof value.provider === 'string'
|
|
95
|
+
&& typeof value.accessToken === 'string'
|
|
96
|
+
&& typeof value.tokenType === 'string'
|
|
97
|
+
&& value.authMode === 'oauth'
|
|
98
|
+
&& typeof value.overrideAmbientApiKeys === 'boolean'
|
|
99
|
+
&& typeof value.createdAt === 'number'
|
|
100
|
+
&& typeof value.updatedAt === 'number';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isPendingSubscriptionLogin(value: unknown): value is PendingSubscriptionLogin {
|
|
104
|
+
return isRecord(value)
|
|
105
|
+
&& typeof value.provider === 'string'
|
|
106
|
+
&& typeof value.state === 'string'
|
|
107
|
+
&& typeof value.verifier === 'string'
|
|
108
|
+
&& typeof value.redirectUri === 'string'
|
|
109
|
+
&& typeof value.createdAt === 'number';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function importTuiSubscriptions(context: CommandContext, parseErrors: string[]): {
|
|
113
|
+
readonly importedActive: string[];
|
|
114
|
+
readonly importedPending: string[];
|
|
115
|
+
readonly unchangedActive: string[];
|
|
116
|
+
readonly unchangedPending: string[];
|
|
117
|
+
readonly skipped: string[];
|
|
118
|
+
} {
|
|
119
|
+
const shellPaths = context.workspace.shellPaths;
|
|
120
|
+
const manager = context.platform.subscriptionManager;
|
|
121
|
+
const result = {
|
|
122
|
+
importedActive: [] as string[],
|
|
123
|
+
importedPending: [] as string[],
|
|
124
|
+
unchangedActive: [] as string[],
|
|
125
|
+
unchangedPending: [] as string[],
|
|
126
|
+
skipped: [] as string[],
|
|
127
|
+
};
|
|
128
|
+
if (!shellPaths || !manager) return result;
|
|
129
|
+
|
|
130
|
+
const sourcePath = shellPaths.resolveUserPath(GOODVIBES_TUI_SURFACE_ROOT, 'subscriptions.json');
|
|
131
|
+
try {
|
|
132
|
+
const store = readJsonRecord(sourcePath);
|
|
133
|
+
if (!store) return result;
|
|
134
|
+
for (const entry of readRecordMap(store, 'subscriptions')) {
|
|
135
|
+
if (!isProviderSubscription(entry)) {
|
|
136
|
+
result.skipped.push('active subscription with invalid shape');
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (valuesMatch(manager.get(entry.provider), entry)) {
|
|
140
|
+
result.unchangedActive.push(entry.provider);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
manager.saveSubscription(entry);
|
|
144
|
+
result.importedActive.push(entry.provider);
|
|
145
|
+
}
|
|
146
|
+
for (const entry of readRecordMap(store, 'pending')) {
|
|
147
|
+
if (!isPendingSubscriptionLogin(entry)) {
|
|
148
|
+
result.skipped.push('pending subscription with invalid shape');
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (valuesMatch(manager.getPending(entry.provider), entry)) {
|
|
152
|
+
result.unchangedPending.push(entry.provider);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
manager.savePending(entry);
|
|
156
|
+
result.importedPending.push(entry.provider);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
parseErrors.push(`subscriptions: ${error instanceof Error ? error.message : String(error)}`);
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
86
164
|
export function agentWorkspaceSettingSchema(context: CommandContext | null, key: string): ConfigSetting | null {
|
|
87
165
|
return context?.platform?.configManager
|
|
88
166
|
?.getSchema()
|
|
@@ -200,7 +278,7 @@ export async function applyAgentWorkspaceSettingValue(
|
|
|
200
278
|
export async function importAgentWorkspaceTuiSettings(context: CommandContext | null): Promise<TuiSettingsImportOutcome> {
|
|
201
279
|
const shellPaths = context?.workspace?.shellPaths;
|
|
202
280
|
const configManager = context?.platform?.configManager;
|
|
203
|
-
if (!shellPaths || !configManager) {
|
|
281
|
+
if (!context || !shellPaths || !configManager) {
|
|
204
282
|
return {
|
|
205
283
|
status: 'GoodVibes TUI settings import is unavailable in this runtime.',
|
|
206
284
|
runtimeSnapshot: null,
|
|
@@ -232,11 +310,14 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
|
|
|
232
310
|
parseErrors.push(`${source.label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
233
311
|
}
|
|
234
312
|
}
|
|
313
|
+
const subscriptions = importTuiSubscriptions(context, parseErrors);
|
|
314
|
+
const subscriptionImports = subscriptions.importedActive.length + subscriptions.importedPending.length;
|
|
315
|
+
const subscriptionUnchanged = subscriptions.unchangedActive.length + subscriptions.unchangedPending.length;
|
|
235
316
|
|
|
236
|
-
if (values.size === 0) {
|
|
317
|
+
if (values.size === 0 && subscriptionImports === 0 && subscriptionUnchanged === 0 && subscriptions.skipped.length === 0) {
|
|
237
318
|
const detail = parseErrors.length > 0
|
|
238
|
-
? `No importable settings found. ${parseErrors.join('; ')}`
|
|
239
|
-
: 'No GoodVibes TUI settings
|
|
319
|
+
? `No importable settings or subscriptions found. ${parseErrors.join('; ')}`
|
|
320
|
+
: 'No GoodVibes TUI settings or subscription store with importable Agent-owned state was found.';
|
|
240
321
|
return {
|
|
241
322
|
status: 'No GoodVibes TUI settings imported.',
|
|
242
323
|
runtimeSnapshot: null,
|
|
@@ -266,16 +347,21 @@ export async function importAgentWorkspaceTuiSettings(context: CommandContext |
|
|
|
266
347
|
skipped.push(`${setting.key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
267
348
|
}
|
|
268
349
|
}
|
|
350
|
+
skipped.push(...subscriptions.skipped);
|
|
351
|
+
const changedCount = imported.length + subscriptionImports;
|
|
352
|
+
const unchangedCount = unchanged.length + subscriptionUnchanged;
|
|
269
353
|
|
|
270
354
|
return {
|
|
271
|
-
status:
|
|
355
|
+
status: changedCount > 0 ? `Imported ${changedCount} GoodVibes TUI item(s).` : 'No GoodVibes TUI settings changed.',
|
|
272
356
|
runtimeSnapshot: context ? buildAgentWorkspaceRuntimeSnapshot(context) : null,
|
|
273
357
|
result: {
|
|
274
|
-
kind: skipped.length > 0 &&
|
|
275
|
-
title:
|
|
358
|
+
kind: skipped.length > 0 && changedCount === 0 ? 'error' : changedCount > 0 ? 'refreshed' : 'guidance',
|
|
359
|
+
title: changedCount > 0 ? 'GoodVibes TUI settings imported' : 'No settings changed',
|
|
276
360
|
detail: [
|
|
277
361
|
imported.length > 0 ? `Imported: ${imported.slice(0, 10).join(', ')}${imported.length > 10 ? `, +${imported.length - 10} more` : ''}.` : '',
|
|
278
|
-
|
|
362
|
+
subscriptions.importedActive.length > 0 ? `Imported active subscription(s): ${subscriptions.importedActive.join(', ')}.` : '',
|
|
363
|
+
subscriptions.importedPending.length > 0 ? `Imported pending subscription(s): ${subscriptions.importedPending.join(', ')}.` : '',
|
|
364
|
+
unchangedCount > 0 ? `${unchangedCount} item(s) already matched.` : '',
|
|
279
365
|
skipped.length > 0 ? `Skipped: ${skipped.slice(0, 5).join('; ')}${skipped.length > 5 ? `; +${skipped.length - 5} more` : ''}.` : '',
|
|
280
366
|
parseErrors.length > 0 ? `Parse issues: ${parseErrors.join('; ')}.` : '',
|
|
281
367
|
].filter((line) => line.length > 0).join(' '),
|
|
@@ -14,6 +14,9 @@ export interface AgentWorkspaceSetupChecklistInput {
|
|
|
14
14
|
readonly provider: string;
|
|
15
15
|
readonly model: string;
|
|
16
16
|
readonly runtimeBaseUrl: string;
|
|
17
|
+
readonly activeSubscriptionCount: number;
|
|
18
|
+
readonly pendingSubscriptionCount: number;
|
|
19
|
+
readonly availableSubscriptionProviderCount: number;
|
|
17
20
|
readonly sessionMemoryCount: number;
|
|
18
21
|
readonly localMemoryCount: number;
|
|
19
22
|
readonly localMemoryReviewQueueCount: number;
|
|
@@ -69,6 +72,23 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
|
|
|
69
72
|
: 'Choose a provider and model before relying on assistant turns.',
|
|
70
73
|
command: 'Setup -> Provider and model',
|
|
71
74
|
},
|
|
75
|
+
{
|
|
76
|
+
id: 'subscriptions',
|
|
77
|
+
label: 'Provider subscriptions',
|
|
78
|
+
status: input.activeSubscriptionCount > 0
|
|
79
|
+
? 'ready'
|
|
80
|
+
: input.pendingSubscriptionCount > 0
|
|
81
|
+
? 'recommended'
|
|
82
|
+
: input.availableSubscriptionProviderCount > 0 ? 'recommended' : 'optional',
|
|
83
|
+
detail: input.activeSubscriptionCount > 0
|
|
84
|
+
? `${input.activeSubscriptionCount} provider subscription session(s) are active.`
|
|
85
|
+
: input.pendingSubscriptionCount > 0
|
|
86
|
+
? `${input.pendingSubscriptionCount} provider subscription login(s) are pending completion.`
|
|
87
|
+
: input.availableSubscriptionProviderCount > 0
|
|
88
|
+
? `${input.availableSubscriptionProviderCount} subscription-capable provider(s) are available. Start login if you want subscription routing.`
|
|
89
|
+
: 'No subscription-capable providers are available yet. Use API keys or add an OAuth provider service.',
|
|
90
|
+
command: 'Start -> Start subscription login',
|
|
91
|
+
},
|
|
72
92
|
{
|
|
73
93
|
id: 'agent-knowledge',
|
|
74
94
|
label: 'Agent Knowledge',
|
|
@@ -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,
|