@pellux/goodvibes-agent 0.1.2 → 0.1.3

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +12 -1
  4. package/docs/README.md +2 -0
  5. package/docs/getting-started.md +19 -1
  6. package/docs/release-and-publishing.md +3 -1
  7. package/package.json +10 -1
  8. package/src/agent/persona-registry.ts +379 -0
  9. package/src/agent/skill-registry.ts +360 -0
  10. package/src/audio/spoken-turn-model-routing.ts +2 -1
  11. package/src/cli/management-commands.ts +3 -1
  12. package/src/config/surface.ts +1 -0
  13. package/src/input/agent-workspace.ts +32 -2
  14. package/src/input/command-registry.ts +4 -1
  15. package/src/input/commands/agent-skills-runtime.ts +216 -0
  16. package/src/input/commands/knowledge.ts +18 -18
  17. package/src/input/commands/personas-runtime.ts +219 -0
  18. package/src/input/commands/skills-runtime.ts +7 -2
  19. package/src/input/commands.ts +4 -0
  20. package/src/input/panel-integration-actions.ts +0 -52
  21. package/src/main.ts +2 -1
  22. package/src/panels/builtin/session.ts +4 -3
  23. package/src/panels/index.ts +0 -5
  24. package/src/panels/orchestration-panel.ts +4 -5
  25. package/src/panels/qr-panel.ts +3 -2
  26. package/src/panels/tasks-panel.ts +4 -4
  27. package/src/renderer/agent-workspace.ts +2 -0
  28. package/src/runtime/bootstrap-command-context.ts +3 -0
  29. package/src/runtime/bootstrap-command-parts.ts +6 -2
  30. package/src/runtime/bootstrap-core.ts +8 -4
  31. package/src/runtime/bootstrap-shell.ts +3 -1
  32. package/src/runtime/bootstrap.ts +10 -2
  33. package/src/runtime/cloudflare-control-plane.ts +2 -1
  34. package/src/version.ts +1 -1
  35. package/src/daemon/cli.ts +0 -55
  36. package/src/daemon/safe-serve.ts +0 -61
  37. package/src/panels/diff-panel.ts +0 -520
  38. package/src/panels/file-explorer-panel.ts +0 -584
  39. package/src/panels/file-preview-panel.ts +0 -434
  40. package/src/panels/git-panel.ts +0 -638
  41. package/src/panels/sandbox-panel.ts +0 -283
  42. package/src/panels/symbol-outline-panel.ts +0 -486
  43. package/src/panels/worktree-panel.ts +0 -182
  44. package/src/panels/wrfc-panel.ts +0 -609
@@ -0,0 +1,360 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import type { ShellPathService } from '@/runtime/index.ts';
4
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
5
+ import { assertNoSecretLikeText } from './persona-registry.ts';
6
+
7
+ export type AgentSkillSource = 'user' | 'agent' | 'imported' | 'system';
8
+ export type AgentSkillReviewState = 'fresh' | 'reviewed' | 'stale';
9
+
10
+ export interface AgentSkillRecord {
11
+ readonly id: string;
12
+ readonly name: string;
13
+ readonly description: string;
14
+ readonly procedure: string;
15
+ readonly triggers: readonly string[];
16
+ readonly tags: readonly string[];
17
+ readonly enabled: boolean;
18
+ readonly source: AgentSkillSource;
19
+ readonly provenance: string;
20
+ readonly reviewState: AgentSkillReviewState;
21
+ readonly staleReason?: string;
22
+ readonly createdAt: string;
23
+ readonly updatedAt: string;
24
+ readonly reviewedAt?: string;
25
+ }
26
+
27
+ export interface AgentSkillCreateInput {
28
+ readonly name: string;
29
+ readonly description: string;
30
+ readonly procedure: string;
31
+ readonly triggers?: readonly string[];
32
+ readonly tags?: readonly string[];
33
+ readonly enabled?: boolean;
34
+ readonly source?: AgentSkillSource;
35
+ readonly provenance?: string;
36
+ }
37
+
38
+ export interface AgentSkillUpdateInput {
39
+ readonly name?: string;
40
+ readonly description?: string;
41
+ readonly procedure?: string;
42
+ readonly triggers?: readonly string[];
43
+ readonly tags?: readonly string[];
44
+ readonly provenance?: string;
45
+ }
46
+
47
+ export interface AgentSkillSnapshot {
48
+ readonly path: string;
49
+ readonly skills: readonly AgentSkillRecord[];
50
+ readonly enabledSkills: readonly AgentSkillRecord[];
51
+ }
52
+
53
+ interface SkillStoreFile {
54
+ readonly version: 1;
55
+ readonly skills: readonly AgentSkillRecord[];
56
+ }
57
+
58
+ const STORE_VERSION = 1;
59
+
60
+ function isRecord(value: unknown): value is Record<string, unknown> {
61
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
62
+ }
63
+
64
+ function readString(value: unknown, fallback = ''): string {
65
+ return typeof value === 'string' ? value : fallback;
66
+ }
67
+
68
+ function readStringArray(value: unknown): string[] {
69
+ if (!Array.isArray(value)) return [];
70
+ return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
71
+ }
72
+
73
+ function normalizeName(name: string): string {
74
+ return name.trim().replace(/\s+/g, ' ');
75
+ }
76
+
77
+ function normalizeList(values: readonly string[] | undefined): string[] {
78
+ const seen = new Set<string>();
79
+ const result: string[] = [];
80
+ for (const value of values ?? []) {
81
+ const trimmed = value.trim();
82
+ if (!trimmed) continue;
83
+ const key = trimmed.toLowerCase();
84
+ if (seen.has(key)) continue;
85
+ seen.add(key);
86
+ result.push(trimmed);
87
+ }
88
+ return result;
89
+ }
90
+
91
+ function slugify(value: string): string {
92
+ const slug = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
93
+ return slug || 'skill';
94
+ }
95
+
96
+ function nowIso(): string {
97
+ return new Date().toISOString();
98
+ }
99
+
100
+ function parseSkill(value: unknown): AgentSkillRecord | null {
101
+ if (!isRecord(value)) return null;
102
+ const id = readString(value.id).trim();
103
+ const name = normalizeName(readString(value.name));
104
+ const description = readString(value.description).trim();
105
+ const procedure = readString(value.procedure).trim();
106
+ if (!id || !name || !description || !procedure) return null;
107
+ const reviewState = value.reviewState === 'reviewed' || value.reviewState === 'stale' ? value.reviewState : 'fresh';
108
+ const source = value.source === 'agent' || value.source === 'imported' || value.source === 'system' ? value.source : 'user';
109
+ const staleReason = readString(value.staleReason).trim();
110
+ const reviewedAt = readString(value.reviewedAt).trim();
111
+ return {
112
+ id,
113
+ name,
114
+ description,
115
+ procedure,
116
+ triggers: readStringArray(value.triggers),
117
+ tags: readStringArray(value.tags),
118
+ enabled: value.enabled === true,
119
+ source,
120
+ provenance: readString(value.provenance, source).trim() || source,
121
+ reviewState,
122
+ staleReason: staleReason || undefined,
123
+ createdAt: readString(value.createdAt, nowIso()),
124
+ updatedAt: readString(value.updatedAt, nowIso()),
125
+ reviewedAt: reviewedAt || undefined,
126
+ };
127
+ }
128
+
129
+ function parseStore(raw: string): SkillStoreFile {
130
+ const parsed: unknown = JSON.parse(raw);
131
+ if (!isRecord(parsed)) return { version: STORE_VERSION, skills: [] };
132
+ return {
133
+ version: STORE_VERSION,
134
+ skills: Array.isArray(parsed.skills)
135
+ ? parsed.skills.map(parseSkill).filter((entry): entry is AgentSkillRecord => entry !== null)
136
+ : [],
137
+ };
138
+ }
139
+
140
+ function formatStore(store: SkillStoreFile): string {
141
+ return `${JSON.stringify(store, null, 2)}\n`;
142
+ }
143
+
144
+ export function skillStorePath(shellPaths: ShellPathService): string {
145
+ return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'skills', 'skills.json');
146
+ }
147
+
148
+ export class AgentSkillRegistry {
149
+ public constructor(private readonly storePath: string) {}
150
+
151
+ public static fromShellPaths(shellPaths: ShellPathService): AgentSkillRegistry {
152
+ return new AgentSkillRegistry(skillStorePath(shellPaths));
153
+ }
154
+
155
+ public snapshot(): AgentSkillSnapshot {
156
+ const store = this.readStore();
157
+ return {
158
+ path: this.storePath,
159
+ skills: [...store.skills],
160
+ enabledSkills: store.skills.filter((skill) => skill.enabled),
161
+ };
162
+ }
163
+
164
+ public list(): readonly AgentSkillRecord[] {
165
+ return this.snapshot().skills;
166
+ }
167
+
168
+ public search(query: string): readonly AgentSkillRecord[] {
169
+ const normalized = query.trim().toLowerCase();
170
+ if (!normalized) return this.list();
171
+ return this.list().filter((skill) => [
172
+ skill.id,
173
+ skill.name,
174
+ skill.description,
175
+ skill.procedure,
176
+ ...skill.tags,
177
+ ...skill.triggers,
178
+ ].some((field) => field.toLowerCase().includes(normalized)));
179
+ }
180
+
181
+ public get(idOrName: string): AgentSkillRecord | null {
182
+ const lookup = idOrName.trim().toLowerCase();
183
+ if (!lookup) return null;
184
+ return this.list().find((skill) => skill.id.toLowerCase() === lookup || skill.name.toLowerCase() === lookup) ?? null;
185
+ }
186
+
187
+ public create(input: AgentSkillCreateInput): AgentSkillRecord {
188
+ const store = this.readStore();
189
+ const name = normalizeName(input.name);
190
+ const description = input.description.trim();
191
+ const procedure = input.procedure.trim();
192
+ this.validateRequired(name, description, procedure);
193
+ assertNoSecretLikeText([name, description, procedure, ...(input.tags ?? []), ...(input.triggers ?? [])]);
194
+ const duplicate = store.skills.find((skill) => skill.name.toLowerCase() === name.toLowerCase());
195
+ if (duplicate) throw new Error(`Skill already exists: ${duplicate.id}`);
196
+ const timestamp = nowIso();
197
+ const skill: AgentSkillRecord = {
198
+ id: this.nextId(name, store.skills),
199
+ name,
200
+ description,
201
+ procedure,
202
+ triggers: normalizeList(input.triggers),
203
+ tags: normalizeList(input.tags),
204
+ enabled: input.enabled === true,
205
+ source: input.source ?? 'user',
206
+ provenance: input.provenance?.trim() || input.source || 'user',
207
+ reviewState: 'fresh',
208
+ createdAt: timestamp,
209
+ updatedAt: timestamp,
210
+ };
211
+ this.writeStore({ ...store, skills: [...store.skills, skill] });
212
+ return skill;
213
+ }
214
+
215
+ public update(idOrName: string, input: AgentSkillUpdateInput): AgentSkillRecord {
216
+ const store = this.readStore();
217
+ const existing = this.findInStore(store, idOrName);
218
+ if (!existing) throw new Error(`Unknown skill: ${idOrName}`);
219
+ const name = input.name === undefined ? existing.name : normalizeName(input.name);
220
+ const description = input.description === undefined ? existing.description : input.description.trim();
221
+ const procedure = input.procedure === undefined ? existing.procedure : input.procedure.trim();
222
+ this.validateRequired(name, description, procedure);
223
+ assertNoSecretLikeText([name, description, procedure, ...(input.tags ?? []), ...(input.triggers ?? [])]);
224
+ const duplicate = store.skills.find((skill) => skill.id !== existing.id && skill.name.toLowerCase() === name.toLowerCase());
225
+ if (duplicate) throw new Error(`Skill already exists: ${duplicate.id}`);
226
+ const updated: AgentSkillRecord = {
227
+ ...existing,
228
+ name,
229
+ description,
230
+ procedure,
231
+ triggers: input.triggers === undefined ? existing.triggers : normalizeList(input.triggers),
232
+ tags: input.tags === undefined ? existing.tags : normalizeList(input.tags),
233
+ provenance: input.provenance === undefined ? existing.provenance : input.provenance.trim() || existing.provenance,
234
+ reviewState: 'fresh',
235
+ staleReason: undefined,
236
+ reviewedAt: undefined,
237
+ updatedAt: nowIso(),
238
+ };
239
+ this.writeStore({
240
+ ...store,
241
+ skills: store.skills.map((skill) => skill.id === existing.id ? updated : skill),
242
+ });
243
+ return updated;
244
+ }
245
+
246
+ public setEnabled(idOrName: string, enabled: boolean): AgentSkillRecord {
247
+ const store = this.readStore();
248
+ const existing = this.findInStore(store, idOrName);
249
+ if (!existing) throw new Error(`Unknown skill: ${idOrName}`);
250
+ const updated: AgentSkillRecord = { ...existing, enabled, updatedAt: nowIso() };
251
+ this.writeStore({
252
+ ...store,
253
+ skills: store.skills.map((skill) => skill.id === existing.id ? updated : skill),
254
+ });
255
+ return updated;
256
+ }
257
+
258
+ public markReviewed(idOrName: string): AgentSkillRecord {
259
+ const store = this.readStore();
260
+ const existing = this.findInStore(store, idOrName);
261
+ if (!existing) throw new Error(`Unknown skill: ${idOrName}`);
262
+ const updated: AgentSkillRecord = {
263
+ ...existing,
264
+ reviewState: 'reviewed',
265
+ staleReason: undefined,
266
+ reviewedAt: nowIso(),
267
+ updatedAt: nowIso(),
268
+ };
269
+ this.writeStore({
270
+ ...store,
271
+ skills: store.skills.map((skill) => skill.id === existing.id ? updated : skill),
272
+ });
273
+ return updated;
274
+ }
275
+
276
+ public markStale(idOrName: string, reason: string): AgentSkillRecord {
277
+ const store = this.readStore();
278
+ const existing = this.findInStore(store, idOrName);
279
+ if (!existing) throw new Error(`Unknown skill: ${idOrName}`);
280
+ const updated: AgentSkillRecord = {
281
+ ...existing,
282
+ reviewState: 'stale',
283
+ staleReason: reason.trim() || 'Marked stale by user.',
284
+ updatedAt: nowIso(),
285
+ };
286
+ this.writeStore({
287
+ ...store,
288
+ skills: store.skills.map((skill) => skill.id === existing.id ? updated : skill),
289
+ });
290
+ return updated;
291
+ }
292
+
293
+ public deleteSkill(idOrName: string): AgentSkillRecord {
294
+ const store = this.readStore();
295
+ const existing = this.findInStore(store, idOrName);
296
+ if (!existing) throw new Error(`Unknown skill: ${idOrName}`);
297
+ this.writeStore({
298
+ ...store,
299
+ skills: store.skills.filter((skill) => skill.id !== existing.id),
300
+ });
301
+ return existing;
302
+ }
303
+
304
+ private validateRequired(name: string, description: string, procedure: string): void {
305
+ if (!name) throw new Error('Skill name is required.');
306
+ if (!description) throw new Error('Skill description is required.');
307
+ if (!procedure) throw new Error('Skill procedure is required.');
308
+ }
309
+
310
+ private nextId(name: string, skills: readonly AgentSkillRecord[]): string {
311
+ const base = slugify(name);
312
+ const ids = new Set(skills.map((skill) => skill.id));
313
+ if (!ids.has(base)) return base;
314
+ for (let index = 2; index < 1000; index += 1) {
315
+ const candidate = `${base}-${index}`;
316
+ if (!ids.has(candidate)) return candidate;
317
+ }
318
+ throw new Error(`Could not allocate skill id for ${name}.`);
319
+ }
320
+
321
+ private findInStore(store: SkillStoreFile, idOrName: string): AgentSkillRecord | null {
322
+ const lookup = idOrName.trim().toLowerCase();
323
+ if (!lookup) return null;
324
+ return store.skills.find((skill) => skill.id.toLowerCase() === lookup || skill.name.toLowerCase() === lookup) ?? null;
325
+ }
326
+
327
+ private readStore(): SkillStoreFile {
328
+ if (!existsSync(this.storePath)) return { version: STORE_VERSION, skills: [] };
329
+ try {
330
+ return parseStore(readFileSync(this.storePath, 'utf-8'));
331
+ } catch (error) {
332
+ throw new Error(`Could not read Agent skill store: ${error instanceof Error ? error.message : String(error)}`);
333
+ }
334
+ }
335
+
336
+ private writeStore(store: SkillStoreFile): void {
337
+ mkdirSync(dirname(this.storePath), { recursive: true });
338
+ const tmpPath = `${this.storePath}.tmp`;
339
+ writeFileSync(tmpPath, formatStore(store), 'utf-8');
340
+ renameSync(tmpPath, this.storePath);
341
+ }
342
+ }
343
+
344
+ export function buildEnabledSkillsPrompt(shellPaths: ShellPathService): string | null {
345
+ const enabled = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot().enabledSkills;
346
+ if (enabled.length === 0) return null;
347
+ return [
348
+ '## Enabled GoodVibes Agent Skills',
349
+ 'Use these local reusable procedures inside the same serial assistant conversation when they fit the user request.',
350
+ '',
351
+ ...enabled.slice(0, 8).flatMap((skill) => [
352
+ `### ${skill.name}`,
353
+ `Description: ${skill.description}`,
354
+ `Review state: ${skill.reviewState}`,
355
+ `Triggers: ${skill.triggers.join(', ') || '(manual)'}`,
356
+ skill.procedure,
357
+ '',
358
+ ]),
359
+ ].join('\n').trim();
360
+ }
@@ -2,6 +2,7 @@ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
2
2
  import type { ModelDefinition, ProviderRegistry } from '@pellux/goodvibes-sdk/platform/providers';
3
3
  import type { ContentPart } from '@pellux/goodvibes-sdk/platform/providers';
4
4
  import type { Orchestrator, OrchestratorUserInputOptions } from '../core/orchestrator.ts';
5
+ import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
5
6
 
6
7
  const SPOKEN_TURN_SOURCE = 'tts';
7
8
 
@@ -27,7 +28,7 @@ export function createSpokenTurnInputOptions(): OrchestratorUserInputOptions {
27
28
  return {
28
29
  origin: {
29
30
  source: SPOKEN_TURN_SOURCE,
30
- surface: 'tui',
31
+ surface: GOODVIBES_AGENT_PAIRING_SURFACE,
31
32
  metadata: { spokenOutput: true },
32
33
  },
33
34
  };
@@ -11,6 +11,7 @@ import { resolveRuntimeEndpointBinding } from './endpoints.ts';
11
11
  import { classifyBindPosture, isNetworkFacing } from './network-posture.ts';
12
12
  import type { CliCommandRuntime } from './management.ts';
13
13
  import { extractAuthorizationCode, formatJsonOrText, hasCommandFlag, openBrowser, probeTcp, readAuthPaths, runNonInteractiveAgent, urlHostForBindHost, withRuntimeServices, yesNo } from './management.ts';
14
+ import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
14
15
 
15
16
  export async function renderSubscriptions(runtime: CliCommandRuntime): Promise<string> {
16
17
  return await withRuntimeServices(runtime, async (services) => {
@@ -370,13 +371,14 @@ export async function renderControlPlaneStatus(runtime: CliCommandRuntime): Prom
370
371
 
371
372
  export async function renderPairing(runtime: CliCommandRuntime): Promise<string> {
372
373
  const daemonHomeDir = join(runtime.homeDirectory, '.goodvibes', 'daemon');
373
- const tokenRecord = getOrCreateCompanionToken('tui', { daemonHomeDir });
374
+ const tokenRecord = getOrCreateCompanionToken(GOODVIBES_AGENT_PAIRING_SURFACE, { daemonHomeDir });
374
375
  const binding = resolveRuntimeEndpointBinding(runtime.configManager, 'controlPlane');
375
376
  const daemonUrl = `http://${urlHostForBindHost(binding.host)}:${binding.port}`;
376
377
  const info = buildCompanionConnectionInfo({
377
378
  daemonUrl,
378
379
  token: tokenRecord.token,
379
380
  username: 'admin',
381
+ surface: GOODVIBES_AGENT_PAIRING_SURFACE,
380
382
  });
381
383
  const payload = encodeConnectionPayload(info);
382
384
  const qr = renderQrToString(generateQrMatrix(payload));
@@ -1 +1,2 @@
1
1
  export const GOODVIBES_AGENT_SURFACE_ROOT = 'agent';
2
+ export const GOODVIBES_AGENT_PAIRING_SURFACE = 'goodvibes-agent';
@@ -1,5 +1,7 @@
1
1
  import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
2
2
  import type { CommandContext } from './command-registry.ts';
3
+ import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
4
+ import { AgentSkillRegistry } from '../agent/skill-registry.ts';
3
5
 
4
6
  export const AGENT_WORKSPACE_MODAL_NAME = 'agentWorkspace';
5
7
 
@@ -51,6 +53,10 @@ export interface AgentWorkspaceRuntimeSnapshot {
51
53
  readonly daemonBaseUrl: string;
52
54
  readonly daemonOwnership: 'external';
53
55
  readonly sessionMemoryCount: number;
56
+ readonly localSkillCount: number;
57
+ readonly enabledSkillCount: number;
58
+ readonly localPersonaCount: number;
59
+ readonly activePersonaName: string;
54
60
  readonly knowledgeRoute: '/api/goodvibes-agent/knowledge';
55
61
  readonly knowledgeIsolation: 'agent-only';
56
62
  readonly executionPolicy: 'serial-proactive';
@@ -98,6 +104,26 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
98
104
  return 0;
99
105
  }
100
106
  })();
107
+ const personaSnapshot = (() => {
108
+ try {
109
+ const shellPaths = context.workspace?.shellPaths;
110
+ if (!shellPaths) return { count: 0, activeName: '(none)' };
111
+ const snapshot = AgentPersonaRegistry.fromShellPaths(shellPaths).snapshot();
112
+ return { count: snapshot.personas.length, activeName: snapshot.activePersona?.name ?? '(none)' };
113
+ } catch {
114
+ return { count: 0, activeName: '(unavailable)' };
115
+ }
116
+ })();
117
+ const skillSnapshot = (() => {
118
+ try {
119
+ const shellPaths = context.workspace?.shellPaths;
120
+ if (!shellPaths) return { count: 0, enabled: 0 };
121
+ const snapshot = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot();
122
+ return { count: snapshot.skills.length, enabled: snapshot.enabledSkills.length };
123
+ } catch {
124
+ return { count: 0, enabled: 0 };
125
+ }
126
+ })();
101
127
  const warnings: string[] = [];
102
128
  if (provider === 'unknown' || model === 'unknown') warnings.push('Provider/model unavailable in this runtime context.');
103
129
  if (!context.executeCommand) warnings.push('Command dispatch is unavailable; workspace actions will show guidance only.');
@@ -112,6 +138,10 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
112
138
  daemonBaseUrl: `http://${host}:${port}`,
113
139
  daemonOwnership: 'external',
114
140
  sessionMemoryCount,
141
+ localSkillCount: skillSnapshot.count,
142
+ enabledSkillCount: skillSnapshot.enabled,
143
+ localPersonaCount: personaSnapshot.count,
144
+ activePersonaName: personaSnapshot.activeName,
115
145
  knowledgeRoute: '/api/goodvibes-agent/knowledge',
116
146
  knowledgeIsolation: 'agent-only',
117
147
  executionPolicy: 'serial-proactive',
@@ -167,8 +197,8 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
167
197
  detail: 'Memory, skills, and personas stay Agent-local until stable shared daemon registry contracts exist. Secrets must not be stored as memory.',
168
198
  actions: [
169
199
  { id: 'memory', label: 'Open memory', detail: 'Inspect local/session memory commands and surfaces.', command: '/memory', kind: 'command', safety: 'read-only' },
170
- { id: 'skills', label: 'Open skills', detail: 'Inspect discovered skills and skill catalog state.', command: '/skills open', kind: 'command', safety: 'read-only' },
171
- { id: 'personas', label: 'Persona library', detail: 'Use local Agent personas to shape serial assistant behavior without spawning background agents.', kind: 'guidance', safety: 'safe' },
200
+ { id: 'skills', label: 'Local skill library', detail: 'Create, review, and enable local Agent reusable procedures.', command: '/agent-skills', kind: 'command', safety: 'safe' },
201
+ { id: 'personas', label: 'Persona library', detail: 'Use local Agent personas to shape serial assistant behavior without spawning background agents.', command: '/personas', kind: 'command', safety: 'safe' },
172
202
  ],
173
203
  },
174
204
  {
@@ -180,7 +180,9 @@ export interface CommandExtensionRegistryServices {
180
180
 
181
181
  export interface CommandExtensionServices
182
182
  extends CommandExtensionRegistryServices,
183
- CommandExtensionShellServices {}
183
+ CommandExtensionShellServices {
184
+ readonly agentKnowledgeService?: import('@pellux/goodvibes-sdk/platform/knowledge').KnowledgeService;
185
+ }
184
186
 
185
187
  /**
186
188
  * CommandContext - Passed to every slash command handler so commands can
@@ -200,6 +202,7 @@ export interface CommandContext
200
202
  readonly operator?: OperatorClient;
201
203
  readonly peer?: PeerClient;
202
204
  readonly providerApi?: ProviderApi;
205
+ readonly agentKnowledgeApi?: KnowledgeApi;
203
206
  readonly knowledgeApi?: KnowledgeApi;
204
207
  readonly hookApi?: HookApi;
205
208
  readonly mcpApi?: McpApi;