@pellux/goodvibes-agent 0.1.49 → 0.1.50

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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to GoodVibes Agent will be recorded here.
4
4
 
5
+ ## 0.1.50 - 2026-05-31
6
+
7
+ - bdb654a Improve local library workspaces
8
+
5
9
  ## 0.1.49 - 2026-05-31
6
10
 
7
11
  - 445e694 Show isolated Agent Knowledge in TUI panel
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "private": false,
5
5
  "description": "Near-fork GoodVibes operator assistant with the GoodVibes TUI shell, renderer, input, fullscreen workspace, and daemon-connected Agent product brain.",
6
6
  "type": "module",
@@ -1,9 +1,9 @@
1
1
  import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
2
2
  import { basename, sep } from 'node:path';
3
3
  import type { CommandContext } from './command-registry.ts';
4
- import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
5
- import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
6
- import { AgentSkillRegistry } from '../agent/skill-registry.ts';
4
+ import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
5
+ import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
6
+ import { AgentSkillRegistry, type AgentSkillRecord } from '../agent/skill-registry.ts';
7
7
  import { getAgentRuntimeProfilesRoot, listAgentRuntimeProfiles, listAgentRuntimeProfileTemplates } from '../agent/runtime-profile.ts';
8
8
  import {
9
9
  buildAgentWorkspaceChannels,
@@ -53,6 +53,19 @@ export interface AgentWorkspaceActionResult {
53
53
  readonly safety?: AgentWorkspaceAction['safety'];
54
54
  }
55
55
 
56
+ export interface AgentWorkspaceLocalLibraryItem {
57
+ readonly id: string;
58
+ readonly name: string;
59
+ readonly description: string;
60
+ readonly reviewState: string;
61
+ readonly source: string;
62
+ readonly tags: readonly string[];
63
+ readonly triggers: readonly string[];
64
+ readonly active?: boolean;
65
+ readonly enabled?: boolean;
66
+ readonly startCount?: number;
67
+ }
68
+
56
69
  type AgentWorkspaceConfigReader = {
57
70
  get(key: string): unknown;
58
71
  };
@@ -69,10 +82,13 @@ export interface AgentWorkspaceRuntimeSnapshot {
69
82
  readonly sessionMemoryCount: number;
70
83
  readonly localRoutineCount: number;
71
84
  readonly enabledRoutineCount: number;
85
+ readonly localRoutines: readonly AgentWorkspaceLocalLibraryItem[];
72
86
  readonly localSkillCount: number;
73
87
  readonly enabledSkillCount: number;
88
+ readonly localSkills: readonly AgentWorkspaceLocalLibraryItem[];
74
89
  readonly localPersonaCount: number;
75
90
  readonly activePersonaName: string;
91
+ readonly localPersonas: readonly AgentWorkspaceLocalLibraryItem[];
76
92
  readonly knowledgeRoute: '/api/goodvibes-agent/knowledge';
77
93
  readonly knowledgeIsolation: 'agent-only';
78
94
  readonly executionPolicy: 'serial-proactive';
@@ -143,6 +159,46 @@ function inferActiveRuntimeProfile(homeDirectory: string): string {
143
159
  return homeDirectory.includes(marker) ? basename(homeDirectory) : '(default home)';
144
160
  }
145
161
 
162
+ function summarizePersonaItem(persona: AgentPersonaRecord, activePersonaId: string | null): AgentWorkspaceLocalLibraryItem {
163
+ return {
164
+ id: persona.id,
165
+ name: persona.name,
166
+ description: persona.description,
167
+ reviewState: persona.reviewState,
168
+ source: persona.source,
169
+ tags: persona.tags,
170
+ triggers: persona.triggers,
171
+ active: persona.id === activePersonaId,
172
+ };
173
+ }
174
+
175
+ function summarizeSkillItem(skill: AgentSkillRecord): AgentWorkspaceLocalLibraryItem {
176
+ return {
177
+ id: skill.id,
178
+ name: skill.name,
179
+ description: skill.description,
180
+ reviewState: skill.reviewState,
181
+ source: skill.source,
182
+ tags: skill.tags,
183
+ triggers: skill.triggers,
184
+ enabled: skill.enabled,
185
+ };
186
+ }
187
+
188
+ function summarizeRoutineItem(routine: AgentRoutineRecord): AgentWorkspaceLocalLibraryItem {
189
+ return {
190
+ id: routine.id,
191
+ name: routine.name,
192
+ description: routine.description,
193
+ reviewState: routine.reviewState,
194
+ source: routine.source,
195
+ tags: routine.tags,
196
+ triggers: routine.triggers,
197
+ enabled: routine.enabled,
198
+ startCount: routine.startCount,
199
+ };
200
+ }
201
+
146
202
  export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): AgentWorkspaceRuntimeSnapshot {
147
203
  const host = readConfigString(context, 'controlPlane.host', '127.0.0.1');
148
204
  const port = readConfigNumber(context, 'controlPlane.port', 3421);
@@ -165,31 +221,43 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
165
221
  const personaSnapshot = (() => {
166
222
  try {
167
223
  const shellPaths = context.workspace?.shellPaths;
168
- if (!shellPaths) return { count: 0, activeName: '(none)' };
224
+ if (!shellPaths) return { count: 0, activeName: '(none)', items: [] };
169
225
  const snapshot = AgentPersonaRegistry.fromShellPaths(shellPaths).snapshot();
170
- return { count: snapshot.personas.length, activeName: snapshot.activePersona?.name ?? '(none)' };
226
+ return {
227
+ count: snapshot.personas.length,
228
+ activeName: snapshot.activePersona?.name ?? '(none)',
229
+ items: snapshot.personas.map((persona) => summarizePersonaItem(persona, snapshot.activePersonaId)),
230
+ };
171
231
  } catch {
172
- return { count: 0, activeName: '(unavailable)' };
232
+ return { count: 0, activeName: '(unavailable)', items: [] };
173
233
  }
174
234
  })();
175
235
  const skillSnapshot = (() => {
176
236
  try {
177
237
  const shellPaths = context.workspace?.shellPaths;
178
- if (!shellPaths) return { count: 0, enabled: 0 };
238
+ if (!shellPaths) return { count: 0, enabled: 0, items: [] };
179
239
  const snapshot = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot();
180
- return { count: snapshot.skills.length, enabled: snapshot.enabledSkills.length };
240
+ return {
241
+ count: snapshot.skills.length,
242
+ enabled: snapshot.enabledSkills.length,
243
+ items: snapshot.skills.map(summarizeSkillItem),
244
+ };
181
245
  } catch {
182
- return { count: 0, enabled: 0 };
246
+ return { count: 0, enabled: 0, items: [] };
183
247
  }
184
248
  })();
185
249
  const routineSnapshot = (() => {
186
250
  try {
187
251
  const shellPaths = context.workspace?.shellPaths;
188
- if (!shellPaths) return { count: 0, enabled: 0 };
252
+ if (!shellPaths) return { count: 0, enabled: 0, items: [] };
189
253
  const snapshot = AgentRoutineRegistry.fromShellPaths(shellPaths).snapshot();
190
- return { count: snapshot.routines.length, enabled: snapshot.enabledRoutines.length };
254
+ return {
255
+ count: snapshot.routines.length,
256
+ enabled: snapshot.enabledRoutines.length,
257
+ items: snapshot.routines.map(summarizeRoutineItem),
258
+ };
191
259
  } catch {
192
- return { count: 0, enabled: 0 };
260
+ return { count: 0, enabled: 0, items: [] };
193
261
  }
194
262
  })();
195
263
  const runtimeProfiles = (() => {
@@ -265,10 +333,13 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
265
333
  sessionMemoryCount,
266
334
  localRoutineCount: routineSnapshot.count,
267
335
  enabledRoutineCount: routineSnapshot.enabled,
336
+ localRoutines: routineSnapshot.items,
268
337
  localSkillCount: skillSnapshot.count,
269
338
  enabledSkillCount: skillSnapshot.enabled,
339
+ localSkills: skillSnapshot.items,
270
340
  localPersonaCount: personaSnapshot.count,
271
341
  activePersonaName: personaSnapshot.activeName,
342
+ localPersonas: personaSnapshot.items,
272
343
  knowledgeRoute: '/api/goodvibes-agent/knowledge',
273
344
  knowledgeIsolation: 'agent-only',
274
345
  executionPolicy: 'serial-proactive',
@@ -329,9 +400,9 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
329
400
  { id: 'setup-provider-model', label: 'Provider and model', detail: 'Choose the provider/model route for normal assistant chat.', command: '/model', kind: 'command', safety: 'safe' },
330
401
  { id: 'setup-agent-knowledge', label: 'Agent Knowledge', detail: 'Inspect the isolated Agent Knowledge store before ingesting source-backed material.', command: '/knowledge status', kind: 'command', safety: 'read-only' },
331
402
  { id: 'setup-runtime-profiles', label: 'Runtime profiles', detail: 'Browse starter templates for isolated Agent homes and operator identities.', command: '/agent-profile templates', kind: 'command', safety: 'read-only' },
332
- { id: 'setup-personas', label: 'Personas', detail: 'Create or select the active local Agent persona.', command: '/personas', kind: 'command', safety: 'safe' },
333
- { id: 'setup-skills', label: 'Skills', detail: 'Create, review, and enable reusable local Agent skills.', command: '/agent-skills', kind: 'command', safety: 'safe' },
334
- { id: 'setup-routines', label: 'Routines', detail: 'Create, review, and enable local Agent routines before any explicit schedule promotion.', command: '/routines', kind: 'command', safety: 'safe' },
403
+ { id: 'setup-personas', label: 'Personas', detail: 'Create or select the active local Agent persona.', targetCategoryId: 'personas', kind: 'workspace', safety: 'safe' },
404
+ { id: 'setup-skills', label: 'Skills', detail: 'Create, review, and enable reusable local Agent skills.', targetCategoryId: 'skills', kind: 'workspace', safety: 'safe' },
405
+ { id: 'setup-routines', label: 'Routines', detail: 'Create, review, and enable local Agent routines before any explicit schedule promotion.', targetCategoryId: 'routines', kind: 'workspace', safety: 'safe' },
335
406
  { id: 'setup-memory', label: 'Local memory', detail: 'Inspect local/session memory; secrets stay rejected or redacted.', command: '/memory', kind: 'command', safety: 'read-only' },
336
407
  { id: 'setup-channels', label: 'Channels', detail: 'Open companion pairing and channel readiness setup.', command: '/pair', kind: 'command', safety: 'safe' },
337
408
  { id: 'setup-voice-media', label: 'Voice and media', detail: 'Open TTS/media settings for voice and image-capable Agent flows.', command: '/config tts', kind: 'command', safety: 'safe' },
@@ -410,9 +481,53 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
410
481
  detail: 'Memory, routines, skills, and personas stay Agent-local until stable shared daemon registry contracts exist. Secrets must not be stored as memory.',
411
482
  actions: [
412
483
  { id: 'memory', label: 'Open memory', detail: 'Inspect local/session memory commands and surfaces.', command: '/memory', kind: 'command', safety: 'read-only' },
413
- { id: 'routines', label: 'Routine library', detail: 'Create, review, enable, and start local Agent routines in the main conversation.', command: '/routines', kind: 'command', safety: 'safe' },
414
- { id: 'skills', label: 'Local skill library', detail: 'Create, review, and enable local Agent reusable procedures.', command: '/agent-skills', kind: 'command', safety: 'safe' },
415
- { 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' },
484
+ { id: 'personas', label: 'Persona library', detail: 'Open the local persona workspace for active role selection and review.', targetCategoryId: 'personas', kind: 'workspace', safety: 'safe' },
485
+ { id: 'skills', label: 'Local skill library', detail: 'Open the local skill workspace for reusable procedures and review.', targetCategoryId: 'skills', kind: 'workspace', safety: 'safe' },
486
+ { id: 'routines', label: 'Routine library', detail: 'Open the local routine workspace for repeatable workflows and schedule promotion review.', targetCategoryId: 'routines', kind: 'workspace', safety: 'safe' },
487
+ ],
488
+ },
489
+ {
490
+ id: 'personas',
491
+ group: 'LEARN',
492
+ label: 'Personas',
493
+ summary: 'Local behavior profiles for the main assistant.',
494
+ detail: 'Personas shape the serial Agent in the main conversation. They are not background agents and they never spawn specialist roots.',
495
+ actions: [
496
+ { id: 'personas-list', label: 'List personas', detail: 'Print the full local persona library.', command: '/personas list', kind: 'command', safety: 'read-only' },
497
+ { id: 'personas-active', label: 'Show active persona', detail: 'Inspect the active local persona applied to new turns.', command: '/personas active', kind: 'command', safety: 'read-only' },
498
+ { id: 'personas-create', label: 'Create persona', detail: 'Create a local persona with real name, summary, and instructions. Placeholder commands are never dispatched.', command: '/personas create --name <name> --description <summary> --body <instructions>', kind: 'command', safety: 'safe' },
499
+ { id: 'personas-use', label: 'Use persona', detail: 'Activate a local persona by id or name.', command: '/personas use <id>', kind: 'command', safety: 'safe' },
500
+ { id: 'personas-review', label: 'Review persona', detail: 'Mark a local persona reviewed after inspecting it.', command: '/personas review <id>', kind: 'command', safety: 'safe' },
501
+ { id: 'personas-clear', label: 'Clear active persona', detail: 'Return to the default Agent policy without deleting any persona.', command: '/personas clear', kind: 'command', safety: 'safe' },
502
+ ],
503
+ },
504
+ {
505
+ id: 'skills',
506
+ group: 'LEARN',
507
+ label: 'Skills',
508
+ summary: 'Reusable local procedures the assistant can apply on demand.',
509
+ detail: 'Skills are local, reviewable procedures. Enabled skills inform the main conversation; secret-looking content is rejected.',
510
+ actions: [
511
+ { id: 'skills-list', label: 'List skills', detail: 'Print the full local Agent skill library.', command: '/agent-skills list', kind: 'command', safety: 'read-only' },
512
+ { id: 'skills-enabled', label: 'Enabled skills', detail: 'Show only skills currently injected into Agent guidance.', command: '/agent-skills enabled', kind: 'command', safety: 'read-only' },
513
+ { id: 'skills-create', label: 'Create skill', detail: 'Create a reusable local procedure with real details. Placeholder commands are never dispatched.', command: '/agent-skills create --name <name> --description <summary> --procedure <steps>', kind: 'command', safety: 'safe' },
514
+ { id: 'skills-enable', label: 'Enable skill', detail: 'Enable a local Agent skill by id or name.', command: '/agent-skills enable <id>', kind: 'command', safety: 'safe' },
515
+ { id: 'skills-review', label: 'Review skill', detail: 'Mark a local skill reviewed after inspecting it.', command: '/agent-skills review <id>', kind: 'command', safety: 'safe' },
516
+ ],
517
+ },
518
+ {
519
+ id: 'routines',
520
+ group: 'LEARN',
521
+ label: 'Routines',
522
+ summary: 'Repeatable workflows for the main conversation.',
523
+ detail: 'Routines run in the main conversation by default. Promotion to an external daemon schedule requires a real schedule command and --yes.',
524
+ actions: [
525
+ { id: 'routines-list', label: 'List routines', detail: 'Print the full local Agent routine library.', command: '/routines list', kind: 'command', safety: 'read-only' },
526
+ { id: 'routines-enabled', label: 'Enabled routines', detail: 'Show routines available for direct use.', command: '/routines enabled', kind: 'command', safety: 'read-only' },
527
+ { id: 'routines-create', label: 'Create routine', detail: 'Create a repeatable workflow with real steps. Placeholder commands are never dispatched.', command: '/routines create --name <name> --description <summary> --steps <steps>', kind: 'command', safety: 'safe' },
528
+ { id: 'routines-start', label: 'Start routine', detail: 'Start a local routine in the main conversation without creating a hidden job.', command: '/routines start <id>', kind: 'command', safety: 'safe' },
529
+ { id: 'routines-promote', label: 'Promote to schedule', detail: 'Create an external daemon schedule from a reviewed routine only with real timing and --yes.', command: '/routines promote <id> --cron <expr> --yes', kind: 'command', safety: 'safe' },
530
+ { id: 'routines-receipts', label: 'Promotion receipts', detail: 'Inspect local redacted routine schedule promotion receipts.', command: '/routines receipts', kind: 'command', safety: 'read-only' },
416
531
  ],
417
532
  },
418
533
  {
@@ -100,6 +100,40 @@ function setupChecklistLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLi
100
100
  return lines;
101
101
  }
102
102
 
103
+ function localLibraryLines(
104
+ title: string,
105
+ items: readonly AgentWorkspaceRuntimeSnapshot['localPersonas'][number][],
106
+ emptyText: string,
107
+ ): ContextLine[] {
108
+ const lines: ContextLine[] = [
109
+ { text: title, fg: PALETTE.title, bold: true },
110
+ ];
111
+ if (items.length === 0) {
112
+ lines.push({ text: emptyText, fg: PALETTE.warn });
113
+ return lines;
114
+ }
115
+ for (const item of items.slice(0, 8)) {
116
+ const status = [
117
+ item.active ? 'active' : '',
118
+ item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
119
+ item.reviewState,
120
+ item.startCount !== undefined ? `starts ${item.startCount}` : '',
121
+ ].filter(Boolean).join(' / ');
122
+ const tags = item.tags.length > 0 ? ` tags=${item.tags.join(',')}` : '';
123
+ const triggers = item.triggers.length > 0 ? ` triggers=${item.triggers.join(',')}` : '';
124
+ lines.push({
125
+ text: `${item.id}: ${item.name} (${status})`,
126
+ fg: item.reviewState === 'stale' ? PALETTE.warn : PALETTE.info,
127
+ bold: item.active === true,
128
+ });
129
+ lines.push({ text: ` ${item.description}${tags}${triggers}`, fg: PALETTE.muted });
130
+ }
131
+ if (items.length > 8) {
132
+ lines.push({ text: `${items.length - 8} more item(s). Open the library command for the full list.`, fg: PALETTE.dim });
133
+ }
134
+ return lines;
135
+ }
136
+
103
137
  function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
104
138
  if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
105
139
  const base: ContextLine[] = [{ text: 'Live Agent Context', fg: PALETTE.title, bold: true }];
@@ -178,6 +212,30 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
178
212
  { text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
179
213
  { text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
180
214
  );
215
+ } else if (category.id === 'personas') {
216
+ base.push(
217
+ { text: `Personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
218
+ { text: 'Personas are local behavior profiles for the serial main-conversation assistant, not spawned agents.', fg: PALETTE.good },
219
+ { text: 'Use them for tone, role, domain constraints, tool posture, and repeatable operating preferences.', fg: PALETTE.muted },
220
+ { text: '' },
221
+ ...localLibraryLines('Persona Library', snapshot.localPersonas, 'No local personas yet. Create one with /personas create ...'),
222
+ );
223
+ } else if (category.id === 'skills') {
224
+ base.push(
225
+ { text: `Skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
226
+ { text: 'Skills are reusable local procedures the assistant can apply from the main conversation.', fg: PALETTE.good },
227
+ { text: 'Enabled skills are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
228
+ { text: '' },
229
+ ...localLibraryLines('Skill Library', snapshot.localSkills, 'No local skills yet. Create one with /agent-skills create ...'),
230
+ );
231
+ } else if (category.id === 'routines') {
232
+ base.push(
233
+ { text: `Routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
234
+ { text: 'Routines are repeatable main-conversation workflows. Starting one does not create hidden jobs.', fg: PALETTE.good },
235
+ { text: 'Scheduling a reviewed routine is explicit and writes to the externally owned daemon only with --yes.', fg: PALETTE.warn },
236
+ { text: '' },
237
+ ...localLibraryLines('Routine Library', snapshot.localRoutines, 'No local routines yet. Create one with /routines create ...'),
238
+ );
181
239
  } else if (category.id === 'work') {
182
240
  base.push(
183
241
  { text: 'Work plan and approvals are read or explicitly confirmed through public operator routes.', fg: PALETTE.info },
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '0.1.49';
9
+ let _version = '0.1.50';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {