@pellux/goodvibes-agent 0.1.52 → 0.1.53

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.53 - 2026-05-31
6
+
7
+ - 77ad0cf Add local library edit workspace flow
8
+
5
9
  ## 0.1.52 - 2026-05-31
6
10
 
7
11
  - e543fa5 Add selected local library actions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
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",
@@ -25,12 +25,15 @@ export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'e
25
25
  export type AgentWorkspaceLocalEditorKind = 'persona' | 'skill' | 'routine';
26
26
 
27
27
  export type AgentWorkspaceLocalOperation =
28
+ | 'persona-edit'
28
29
  | 'persona-use'
29
30
  | 'persona-review'
30
31
  | 'persona-clear'
32
+ | 'skill-edit'
31
33
  | 'skill-enable'
32
34
  | 'skill-disable'
33
35
  | 'skill-review'
36
+ | 'routine-edit'
34
37
  | 'routine-start'
35
38
  | 'routine-enable'
36
39
  | 'routine-disable'
@@ -47,6 +50,8 @@ export interface AgentWorkspaceEditorField {
47
50
 
48
51
  export interface AgentWorkspaceLocalEditor {
49
52
  readonly kind: AgentWorkspaceLocalEditorKind;
53
+ readonly mode: 'create' | 'update';
54
+ readonly recordId?: string;
50
55
  readonly title: string;
51
56
  readonly fields: readonly AgentWorkspaceEditorField[];
52
57
  readonly selectedFieldIndex: number;
@@ -533,6 +538,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
533
538
  { id: 'personas-prev', label: 'Previous persona', detail: 'Move the local persona selection up without changing active state.', localKind: 'persona', selectionDelta: -1, kind: 'local-selection', safety: 'safe' },
534
539
  { id: 'personas-next', label: 'Next persona', detail: 'Move the local persona selection down without changing active state.', localKind: 'persona', selectionDelta: 1, kind: 'local-selection', safety: 'safe' },
535
540
  { id: 'personas-create', label: 'Create persona', detail: 'Open an in-workspace form for a local persona. No placeholder command is dispatched.', editorKind: 'persona', kind: 'editor', safety: 'safe' },
541
+ { id: 'personas-edit', label: 'Edit selected', detail: 'Open the selected local persona in an in-workspace editor.', localKind: 'persona', localOperation: 'persona-edit', kind: 'local-operation', safety: 'safe' },
536
542
  { id: 'personas-use', label: 'Use selected', detail: 'Activate the selected local persona for future main-conversation turns.', localKind: 'persona', localOperation: 'persona-use', kind: 'local-operation', safety: 'safe' },
537
543
  { id: 'personas-review', label: 'Review selected', detail: 'Mark the selected local persona reviewed after inspecting it.', localKind: 'persona', localOperation: 'persona-review', kind: 'local-operation', safety: 'safe' },
538
544
  { id: 'personas-clear', label: 'Clear active persona', detail: 'Return to the default Agent policy without deleting any persona.', localKind: 'persona', localOperation: 'persona-clear', kind: 'local-operation', safety: 'safe' },
@@ -550,6 +556,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
550
556
  { id: 'skills-prev', label: 'Previous skill', detail: 'Move the local skill selection up without changing enabled state.', localKind: 'skill', selectionDelta: -1, kind: 'local-selection', safety: 'safe' },
551
557
  { id: 'skills-next', label: 'Next skill', detail: 'Move the local skill selection down without changing enabled state.', localKind: 'skill', selectionDelta: 1, kind: 'local-selection', safety: 'safe' },
552
558
  { id: 'skills-create', label: 'Create skill', detail: 'Open an in-workspace form for a reusable local procedure. No placeholder command is dispatched.', editorKind: 'skill', kind: 'editor', safety: 'safe' },
559
+ { id: 'skills-edit', label: 'Edit selected', detail: 'Open the selected local Agent skill in an in-workspace editor.', localKind: 'skill', localOperation: 'skill-edit', kind: 'local-operation', safety: 'safe' },
553
560
  { id: 'skills-enable', label: 'Enable selected', detail: 'Enable the selected local Agent skill for future main-conversation guidance.', localKind: 'skill', localOperation: 'skill-enable', kind: 'local-operation', safety: 'safe' },
554
561
  { id: 'skills-disable', label: 'Disable selected', detail: 'Disable the selected local Agent skill without deleting it.', localKind: 'skill', localOperation: 'skill-disable', kind: 'local-operation', safety: 'safe' },
555
562
  { id: 'skills-review', label: 'Review selected', detail: 'Mark the selected local skill reviewed after inspecting it.', localKind: 'skill', localOperation: 'skill-review', kind: 'local-operation', safety: 'safe' },
@@ -567,6 +574,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
567
574
  { id: 'routines-prev', label: 'Previous routine', detail: 'Move the local routine selection up without changing enabled state.', localKind: 'routine', selectionDelta: -1, kind: 'local-selection', safety: 'safe' },
568
575
  { id: 'routines-next', label: 'Next routine', detail: 'Move the local routine selection down without changing enabled state.', localKind: 'routine', selectionDelta: 1, kind: 'local-selection', safety: 'safe' },
569
576
  { id: 'routines-create', label: 'Create routine', detail: 'Open an in-workspace form for a repeatable local workflow. No placeholder command is dispatched.', editorKind: 'routine', kind: 'editor', safety: 'safe' },
577
+ { id: 'routines-edit', label: 'Edit selected', detail: 'Open the selected local Agent routine in an in-workspace editor.', localKind: 'routine', localOperation: 'routine-edit', kind: 'local-operation', safety: 'safe' },
570
578
  { id: 'routines-start', label: 'Start selected', detail: 'Mark the selected routine started and show it as a main-conversation workflow. This creates no hidden job.', localKind: 'routine', localOperation: 'routine-start', kind: 'local-operation', safety: 'safe' },
571
579
  { id: 'routines-enable', label: 'Enable selected', detail: 'Enable the selected routine for future main-conversation guidance.', localKind: 'routine', localOperation: 'routine-enable', kind: 'local-operation', safety: 'safe' },
572
580
  { id: 'routines-disable', label: 'Disable selected', detail: 'Disable the selected routine without deleting it.', localKind: 'routine', localOperation: 'routine-disable', kind: 'local-operation', safety: 'safe' },
@@ -627,6 +635,7 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
627
635
  if (kind === 'persona') {
628
636
  return {
629
637
  kind,
638
+ mode: 'create',
630
639
  title: 'Create Persona',
631
640
  selectedFieldIndex: 0,
632
641
  message: 'Enter a local behavior profile for the serial main-conversation assistant.',
@@ -643,6 +652,7 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
643
652
  if (kind === 'skill') {
644
653
  return {
645
654
  kind,
655
+ mode: 'create',
646
656
  title: 'Create Skill',
647
657
  selectedFieldIndex: 0,
648
658
  message: 'Enter a reusable local procedure the assistant can apply from the main conversation.',
@@ -658,6 +668,7 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
658
668
  }
659
669
  return {
660
670
  kind,
671
+ mode: 'create',
661
672
  title: 'Create Routine',
662
673
  selectedFieldIndex: 0,
663
674
  message: 'Enter a repeatable workflow. It runs in the main conversation unless explicitly promoted to a daemon schedule.',
@@ -672,6 +683,63 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
672
683
  };
673
684
  }
674
685
 
686
+ function createPersonaUpdateEditor(record: AgentPersonaRecord, active: boolean): AgentWorkspaceLocalEditor {
687
+ return {
688
+ kind: 'persona',
689
+ mode: 'update',
690
+ recordId: record.id,
691
+ title: 'Edit Persona',
692
+ selectedFieldIndex: 0,
693
+ message: `Editing ${record.name}. Saving marks it fresh for review.`,
694
+ fields: [
695
+ { id: 'name', label: 'Name', value: record.name, required: true, multiline: false, hint: 'Short persona name.' },
696
+ { id: 'description', label: 'Description', value: record.description, required: true, multiline: false, hint: 'One-line summary of when to use it.' },
697
+ { id: 'body', label: 'Instructions', value: record.body, required: true, multiline: true, hint: 'Operating guidance. Ctrl-J inserts a new line.' },
698
+ { id: 'tags', label: 'Tags', value: record.tags.join(', '), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
699
+ { id: 'triggers', label: 'Triggers', value: record.triggers.join(', '), required: false, multiline: false, hint: 'Comma-separated words that suggest this persona.' },
700
+ { id: 'activate', label: 'Active', value: active ? 'yes' : 'no', required: false, multiline: false, hint: 'yes/no. Setting no clears this persona only if it is currently active.' },
701
+ ],
702
+ };
703
+ }
704
+
705
+ function createSkillUpdateEditor(record: AgentSkillRecord): AgentWorkspaceLocalEditor {
706
+ return {
707
+ kind: 'skill',
708
+ mode: 'update',
709
+ recordId: record.id,
710
+ title: 'Edit Skill',
711
+ selectedFieldIndex: 0,
712
+ message: `Editing ${record.name}. Saving marks it fresh for review.`,
713
+ fields: [
714
+ { id: 'name', label: 'Name', value: record.name, required: true, multiline: false, hint: 'Short skill name.' },
715
+ { id: 'description', label: 'Description', value: record.description, required: true, multiline: false, hint: 'One-line summary of the procedure.' },
716
+ { id: 'procedure', label: 'Procedure', value: record.procedure, required: true, multiline: true, hint: 'Reusable steps. Ctrl-J inserts a new line.' },
717
+ { id: 'triggers', label: 'Triggers', value: record.triggers.join(', '), required: false, multiline: false, hint: 'Comma-separated words that suggest this skill.' },
718
+ { id: 'tags', label: 'Tags', value: record.tags.join(', '), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
719
+ { id: 'enabled', label: 'Enabled', value: record.enabled ? 'yes' : 'no', required: false, multiline: false, hint: 'yes/no.' },
720
+ ],
721
+ };
722
+ }
723
+
724
+ function createRoutineUpdateEditor(record: AgentRoutineRecord): AgentWorkspaceLocalEditor {
725
+ return {
726
+ kind: 'routine',
727
+ mode: 'update',
728
+ recordId: record.id,
729
+ title: 'Edit Routine',
730
+ selectedFieldIndex: 0,
731
+ message: `Editing ${record.name}. Saving marks it fresh for review.`,
732
+ fields: [
733
+ { id: 'name', label: 'Name', value: record.name, required: true, multiline: false, hint: 'Short routine name.' },
734
+ { id: 'description', label: 'Description', value: record.description, required: true, multiline: false, hint: 'One-line summary of the workflow.' },
735
+ { id: 'steps', label: 'Steps', value: record.steps, required: true, multiline: true, hint: 'Workflow steps. Ctrl-J inserts a new line.' },
736
+ { id: 'triggers', label: 'Triggers', value: record.triggers.join(', '), required: false, multiline: false, hint: 'Comma-separated words that suggest this routine.' },
737
+ { id: 'tags', label: 'Tags', value: record.tags.join(', '), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
738
+ { id: 'enabled', label: 'Enabled', value: record.enabled ? 'yes' : 'no', required: false, multiline: false, hint: 'yes/no.' },
739
+ ],
740
+ };
741
+ }
742
+
675
743
  function splitList(value: string): string[] {
676
744
  return value.split(',').map((part) => part.trim()).filter(Boolean);
677
745
  }
@@ -1062,12 +1130,35 @@ export class AgentWorkspace {
1062
1130
  };
1063
1131
  return;
1064
1132
  }
1065
- if (operation === 'persona-use') {
1133
+ if (operation === 'persona-edit') {
1134
+ const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
1135
+ const persona = registry.get(selected.id);
1136
+ if (!persona) throw new Error(`Unknown persona: ${selected.id}`);
1137
+ this.localEditor = createPersonaUpdateEditor(persona, registry.snapshot().activePersonaId === persona.id);
1138
+ this.status = `Editing persona: ${persona.name}.`;
1139
+ this.lastActionResult = {
1140
+ kind: 'guidance',
1141
+ title: this.localEditor.title,
1142
+ detail: this.localEditor.message,
1143
+ safety: 'safe',
1144
+ };
1145
+ } else if (operation === 'persona-use') {
1066
1146
  AgentPersonaRegistry.fromShellPaths(shellPaths).setActive(selected.id);
1067
1147
  this.finishLocalOperation('persona', `Using persona ${selected.name}`, `${selected.name} will shape future main-conversation turns.`);
1068
1148
  } else if (operation === 'persona-review') {
1069
1149
  AgentPersonaRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
1070
1150
  this.finishLocalOperation('persona', `Reviewed persona ${selected.name}`, `${selected.name} is marked reviewed.`);
1151
+ } else if (operation === 'skill-edit') {
1152
+ const skill = AgentSkillRegistry.fromShellPaths(shellPaths).get(selected.id);
1153
+ if (!skill) throw new Error(`Unknown skill: ${selected.id}`);
1154
+ this.localEditor = createSkillUpdateEditor(skill);
1155
+ this.status = `Editing skill: ${skill.name}.`;
1156
+ this.lastActionResult = {
1157
+ kind: 'guidance',
1158
+ title: this.localEditor.title,
1159
+ detail: this.localEditor.message,
1160
+ safety: 'safe',
1161
+ };
1071
1162
  } else if (operation === 'skill-enable') {
1072
1163
  AgentSkillRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, true);
1073
1164
  this.finishLocalOperation('skill', `Enabled skill ${selected.name}`, `${selected.name} can now inform main-conversation turns.`);
@@ -1077,6 +1168,17 @@ export class AgentWorkspace {
1077
1168
  } else if (operation === 'skill-review') {
1078
1169
  AgentSkillRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
1079
1170
  this.finishLocalOperation('skill', `Reviewed skill ${selected.name}`, `${selected.name} is marked reviewed.`);
1171
+ } else if (operation === 'routine-edit') {
1172
+ const routine = AgentRoutineRegistry.fromShellPaths(shellPaths).get(selected.id);
1173
+ if (!routine) throw new Error(`Unknown routine: ${selected.id}`);
1174
+ this.localEditor = createRoutineUpdateEditor(routine);
1175
+ this.status = `Editing routine: ${routine.name}.`;
1176
+ this.lastActionResult = {
1177
+ kind: 'guidance',
1178
+ title: this.localEditor.title,
1179
+ detail: this.localEditor.message,
1180
+ safety: 'safe',
1181
+ };
1080
1182
  } else if (operation === 'routine-start') {
1081
1183
  AgentRoutineRegistry.fromShellPaths(shellPaths).markStarted(selected.id);
1082
1184
  this.finishLocalOperation('routine', `Started routine ${selected.name}`, `${selected.name} was marked started for this main-conversation workflow. No hidden job was created.`);
@@ -1165,6 +1267,21 @@ export class AgentWorkspace {
1165
1267
  try {
1166
1268
  if (editor.kind === 'persona') {
1167
1269
  const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
1270
+ if (editor.mode === 'update' && editor.recordId) {
1271
+ const wasActive = registry.snapshot().activePersonaId === editor.recordId;
1272
+ const updated = registry.update(editor.recordId, {
1273
+ name: this.editorField('name'),
1274
+ description: this.editorField('description'),
1275
+ body: this.editorField('body'),
1276
+ tags: splitList(this.editorField('tags')),
1277
+ triggers: splitList(this.editorField('triggers')),
1278
+ provenance: 'agent-workspace',
1279
+ });
1280
+ if (isAffirmative(this.editorField('activate'))) registry.setActive(updated.id);
1281
+ else if (wasActive) registry.clearActive();
1282
+ this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
1283
+ return;
1284
+ }
1168
1285
  const created = registry.create({
1169
1286
  name: this.editorField('name'),
1170
1287
  description: this.editorField('description'),
@@ -1175,9 +1292,22 @@ export class AgentWorkspace {
1175
1292
  provenance: 'agent-workspace',
1176
1293
  });
1177
1294
  if (isAffirmative(this.editorField('activate'))) registry.setActive(created.id);
1178
- this.finishLocalEditor(editor.kind, created.id, created.name);
1295
+ this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
1179
1296
  } else if (editor.kind === 'skill') {
1180
1297
  const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
1298
+ if (editor.mode === 'update' && editor.recordId) {
1299
+ const updated = registry.update(editor.recordId, {
1300
+ name: this.editorField('name'),
1301
+ description: this.editorField('description'),
1302
+ procedure: this.editorField('procedure'),
1303
+ triggers: splitList(this.editorField('triggers')),
1304
+ tags: splitList(this.editorField('tags')),
1305
+ provenance: 'agent-workspace',
1306
+ });
1307
+ registry.setEnabled(updated.id, isAffirmative(this.editorField('enabled')));
1308
+ this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
1309
+ return;
1310
+ }
1181
1311
  const created = registry.create({
1182
1312
  name: this.editorField('name'),
1183
1313
  description: this.editorField('description'),
@@ -1188,9 +1318,22 @@ export class AgentWorkspace {
1188
1318
  source: 'user',
1189
1319
  provenance: 'agent-workspace',
1190
1320
  });
1191
- this.finishLocalEditor(editor.kind, created.id, created.name);
1321
+ this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
1192
1322
  } else {
1193
1323
  const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
1324
+ if (editor.mode === 'update' && editor.recordId) {
1325
+ const updated = registry.update(editor.recordId, {
1326
+ name: this.editorField('name'),
1327
+ description: this.editorField('description'),
1328
+ steps: this.editorField('steps'),
1329
+ triggers: splitList(this.editorField('triggers')),
1330
+ tags: splitList(this.editorField('tags')),
1331
+ provenance: 'agent-workspace',
1332
+ });
1333
+ registry.setEnabled(updated.id, isAffirmative(this.editorField('enabled')));
1334
+ this.finishLocalEditor(editor.kind, updated.id, updated.name, 'Updated');
1335
+ return;
1336
+ }
1194
1337
  const created = registry.create({
1195
1338
  name: this.editorField('name'),
1196
1339
  description: this.editorField('description'),
@@ -1201,7 +1344,7 @@ export class AgentWorkspace {
1201
1344
  source: 'user',
1202
1345
  provenance: 'agent-workspace',
1203
1346
  });
1204
- this.finishLocalEditor(editor.kind, created.id, created.name);
1347
+ this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
1205
1348
  }
1206
1349
  } catch (error) {
1207
1350
  const detail = error instanceof Error ? error.message : String(error);
@@ -1215,7 +1358,7 @@ export class AgentWorkspace {
1215
1358
  }
1216
1359
  }
1217
1360
 
1218
- private finishLocalEditor(kind: AgentWorkspaceLocalEditorKind, id: string, name: string): void {
1361
+ private finishLocalEditor(kind: AgentWorkspaceLocalEditorKind, id: string, name: string, verb: 'Created' | 'Updated'): void {
1219
1362
  this.localEditor = null;
1220
1363
  const categoryId = editorCategoryId(kind);
1221
1364
  const categoryIndex = this.categories.findIndex((category) => category.id === categoryId);
@@ -1224,10 +1367,10 @@ export class AgentWorkspace {
1224
1367
  this.selectedActionIndex = 0;
1225
1368
  }
1226
1369
  this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
1227
- this.status = `Created ${kind}: ${name}.`;
1370
+ this.status = `${verb} ${kind}: ${name}.`;
1228
1371
  this.lastActionResult = {
1229
1372
  kind: 'refreshed',
1230
- title: `Created ${kind}`,
1373
+ title: `${verb} ${kind}`,
1231
1374
  detail: `${name} (${id}) was saved to the Agent-local ${categoryId} registry.`,
1232
1375
  safety: 'safe',
1233
1376
  };
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.52';
9
+ let _version = '0.1.53';
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 {