@pellux/goodvibes-agent 0.1.51 → 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 +8 -0
- package/package.json +1 -1
- package/src/input/agent-workspace.ts +325 -13
- package/src/renderer/agent-workspace.ts +13 -7
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.1.52 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- e543fa5 Add selected local library actions
|
|
12
|
+
|
|
5
13
|
## 0.1.51 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- 6a8e8a6 Add local library workspace editors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
|
@@ -20,10 +20,25 @@ export const AGENT_WORKSPACE_MODAL_NAME = 'agentWorkspace';
|
|
|
20
20
|
|
|
21
21
|
export type AgentWorkspaceFocusPane = 'categories' | 'actions';
|
|
22
22
|
|
|
23
|
-
export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor';
|
|
23
|
+
export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'local-selection' | 'local-operation';
|
|
24
24
|
|
|
25
25
|
export type AgentWorkspaceLocalEditorKind = 'persona' | 'skill' | 'routine';
|
|
26
26
|
|
|
27
|
+
export type AgentWorkspaceLocalOperation =
|
|
28
|
+
| 'persona-edit'
|
|
29
|
+
| 'persona-use'
|
|
30
|
+
| 'persona-review'
|
|
31
|
+
| 'persona-clear'
|
|
32
|
+
| 'skill-edit'
|
|
33
|
+
| 'skill-enable'
|
|
34
|
+
| 'skill-disable'
|
|
35
|
+
| 'skill-review'
|
|
36
|
+
| 'routine-edit'
|
|
37
|
+
| 'routine-start'
|
|
38
|
+
| 'routine-enable'
|
|
39
|
+
| 'routine-disable'
|
|
40
|
+
| 'routine-review';
|
|
41
|
+
|
|
27
42
|
export interface AgentWorkspaceEditorField {
|
|
28
43
|
readonly id: string;
|
|
29
44
|
readonly label: string;
|
|
@@ -35,6 +50,8 @@ export interface AgentWorkspaceEditorField {
|
|
|
35
50
|
|
|
36
51
|
export interface AgentWorkspaceLocalEditor {
|
|
37
52
|
readonly kind: AgentWorkspaceLocalEditorKind;
|
|
53
|
+
readonly mode: 'create' | 'update';
|
|
54
|
+
readonly recordId?: string;
|
|
38
55
|
readonly title: string;
|
|
39
56
|
readonly fields: readonly AgentWorkspaceEditorField[];
|
|
40
57
|
readonly selectedFieldIndex: number;
|
|
@@ -48,6 +65,9 @@ export interface AgentWorkspaceAction {
|
|
|
48
65
|
readonly command?: string;
|
|
49
66
|
readonly targetCategoryId?: string;
|
|
50
67
|
readonly editorKind?: AgentWorkspaceLocalEditorKind;
|
|
68
|
+
readonly localKind?: AgentWorkspaceLocalEditorKind;
|
|
69
|
+
readonly selectionDelta?: number;
|
|
70
|
+
readonly localOperation?: AgentWorkspaceLocalOperation;
|
|
51
71
|
readonly kind: AgentWorkspaceActionKind;
|
|
52
72
|
readonly safety: 'safe' | 'read-only' | 'delegates' | 'blocked';
|
|
53
73
|
}
|
|
@@ -515,10 +535,13 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
515
535
|
actions: [
|
|
516
536
|
{ id: 'personas-list', label: 'List personas', detail: 'Print the full local persona library.', command: '/personas list', kind: 'command', safety: 'read-only' },
|
|
517
537
|
{ 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' },
|
|
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' },
|
|
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' },
|
|
518
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' },
|
|
519
|
-
{ id: 'personas-
|
|
520
|
-
{ id: 'personas-
|
|
521
|
-
{ id: 'personas-
|
|
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' },
|
|
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' },
|
|
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' },
|
|
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' },
|
|
522
545
|
],
|
|
523
546
|
},
|
|
524
547
|
{
|
|
@@ -530,9 +553,13 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
530
553
|
actions: [
|
|
531
554
|
{ id: 'skills-list', label: 'List skills', detail: 'Print the full local Agent skill library.', command: '/agent-skills list', kind: 'command', safety: 'read-only' },
|
|
532
555
|
{ id: 'skills-enabled', label: 'Enabled skills', detail: 'Show only skills currently injected into Agent guidance.', command: '/agent-skills enabled', kind: 'command', safety: 'read-only' },
|
|
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' },
|
|
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' },
|
|
533
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' },
|
|
534
|
-
{ id: 'skills-
|
|
535
|
-
{ id: 'skills-
|
|
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' },
|
|
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' },
|
|
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' },
|
|
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' },
|
|
536
563
|
],
|
|
537
564
|
},
|
|
538
565
|
{
|
|
@@ -544,8 +571,14 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
544
571
|
actions: [
|
|
545
572
|
{ id: 'routines-list', label: 'List routines', detail: 'Print the full local Agent routine library.', command: '/routines list', kind: 'command', safety: 'read-only' },
|
|
546
573
|
{ id: 'routines-enabled', label: 'Enabled routines', detail: 'Show routines available for direct use.', command: '/routines enabled', kind: 'command', safety: 'read-only' },
|
|
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' },
|
|
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' },
|
|
547
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' },
|
|
548
|
-
{ id: 'routines-
|
|
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' },
|
|
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' },
|
|
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' },
|
|
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' },
|
|
581
|
+
{ id: 'routines-review', label: 'Review selected', detail: 'Mark the selected local routine reviewed after inspecting it.', localKind: 'routine', localOperation: 'routine-review', kind: 'local-operation', safety: 'safe' },
|
|
549
582
|
{ 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' },
|
|
550
583
|
{ id: 'routines-receipts', label: 'Promotion receipts', detail: 'Inspect local redacted routine schedule promotion receipts.', command: '/routines receipts', kind: 'command', safety: 'read-only' },
|
|
551
584
|
],
|
|
@@ -602,6 +635,7 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
|
|
|
602
635
|
if (kind === 'persona') {
|
|
603
636
|
return {
|
|
604
637
|
kind,
|
|
638
|
+
mode: 'create',
|
|
605
639
|
title: 'Create Persona',
|
|
606
640
|
selectedFieldIndex: 0,
|
|
607
641
|
message: 'Enter a local behavior profile for the serial main-conversation assistant.',
|
|
@@ -618,6 +652,7 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
|
|
|
618
652
|
if (kind === 'skill') {
|
|
619
653
|
return {
|
|
620
654
|
kind,
|
|
655
|
+
mode: 'create',
|
|
621
656
|
title: 'Create Skill',
|
|
622
657
|
selectedFieldIndex: 0,
|
|
623
658
|
message: 'Enter a reusable local procedure the assistant can apply from the main conversation.',
|
|
@@ -633,6 +668,7 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
|
|
|
633
668
|
}
|
|
634
669
|
return {
|
|
635
670
|
kind,
|
|
671
|
+
mode: 'create',
|
|
636
672
|
title: 'Create Routine',
|
|
637
673
|
selectedFieldIndex: 0,
|
|
638
674
|
message: 'Enter a repeatable workflow. It runs in the main conversation unless explicitly promoted to a daemon schedule.',
|
|
@@ -647,6 +683,63 @@ function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceL
|
|
|
647
683
|
};
|
|
648
684
|
}
|
|
649
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
|
+
|
|
650
743
|
function splitList(value: string): string[] {
|
|
651
744
|
return value.split(',').map((part) => part.trim()).filter(Boolean);
|
|
652
745
|
}
|
|
@@ -671,6 +764,11 @@ export class AgentWorkspace {
|
|
|
671
764
|
public runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null = null;
|
|
672
765
|
public lastActionResult: AgentWorkspaceActionResult | null = null;
|
|
673
766
|
public localEditor: AgentWorkspaceLocalEditor | null = null;
|
|
767
|
+
private readonly selectedLibraryItemIndexes: Record<AgentWorkspaceLocalEditorKind, number> = {
|
|
768
|
+
persona: 0,
|
|
769
|
+
skill: 0,
|
|
770
|
+
routine: 0,
|
|
771
|
+
};
|
|
674
772
|
private context: CommandContext | null = null;
|
|
675
773
|
private dispatchCommand: AgentWorkspaceCommandDispatcher | null = null;
|
|
676
774
|
|
|
@@ -712,6 +810,13 @@ export class AgentWorkspace {
|
|
|
712
810
|
return this.actions[this.selectedActionIndex] ?? null;
|
|
713
811
|
}
|
|
714
812
|
|
|
813
|
+
selectedLocalLibraryItem(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceLocalLibraryItem | null {
|
|
814
|
+
const items = this.localLibraryItems(kind);
|
|
815
|
+
if (items.length === 0) return null;
|
|
816
|
+
const index = Math.max(0, Math.min(this.selectedLibraryItemIndexes[kind], items.length - 1));
|
|
817
|
+
return items[index] ?? null;
|
|
818
|
+
}
|
|
819
|
+
|
|
715
820
|
focusCategories(): void {
|
|
716
821
|
this.focusPane = 'categories';
|
|
717
822
|
}
|
|
@@ -855,6 +960,14 @@ export class AgentWorkspace {
|
|
|
855
960
|
};
|
|
856
961
|
return;
|
|
857
962
|
}
|
|
963
|
+
if (action.kind === 'local-selection' && action.localKind) {
|
|
964
|
+
this.moveLocalLibraryItemSelection(action.localKind, action.selectionDelta ?? 0);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (action.kind === 'local-operation' && action.localOperation) {
|
|
968
|
+
this.applyLocalLibraryOperation(action.localOperation);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
858
971
|
if (action.kind === 'guidance' || !action.command) {
|
|
859
972
|
if (action.kind === 'workspace' && action.targetCategoryId) {
|
|
860
973
|
const targetIndex = this.categories.findIndex((category) => category.id === action.targetCategoryId);
|
|
@@ -948,6 +1061,164 @@ export class AgentWorkspace {
|
|
|
948
1061
|
private clampSelection(): void {
|
|
949
1062
|
this.selectedCategoryIndex = Math.max(0, Math.min(this.selectedCategoryIndex, this.categories.length - 1));
|
|
950
1063
|
this.selectedActionIndex = Math.max(0, Math.min(this.selectedActionIndex, this.actions.length - 1));
|
|
1064
|
+
this.clampLocalLibrarySelection('persona');
|
|
1065
|
+
this.clampLocalLibrarySelection('skill');
|
|
1066
|
+
this.clampLocalLibrarySelection('routine');
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
private localLibraryItems(kind: AgentWorkspaceLocalEditorKind): readonly AgentWorkspaceLocalLibraryItem[] {
|
|
1070
|
+
if (kind === 'persona') return this.runtimeSnapshot?.localPersonas ?? [];
|
|
1071
|
+
if (kind === 'skill') return this.runtimeSnapshot?.localSkills ?? [];
|
|
1072
|
+
return this.runtimeSnapshot?.localRoutines ?? [];
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
private clampLocalLibrarySelection(kind: AgentWorkspaceLocalEditorKind): void {
|
|
1076
|
+
const length = this.localLibraryItems(kind).length;
|
|
1077
|
+
this.selectedLibraryItemIndexes[kind] = length === 0
|
|
1078
|
+
? 0
|
|
1079
|
+
: Math.max(0, Math.min(this.selectedLibraryItemIndexes[kind], length - 1));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
private moveLocalLibraryItemSelection(kind: AgentWorkspaceLocalEditorKind, delta: number): void {
|
|
1083
|
+
const items = this.localLibraryItems(kind);
|
|
1084
|
+
if (items.length === 0) {
|
|
1085
|
+
this.status = `No local ${kind} records to select.`;
|
|
1086
|
+
this.lastActionResult = {
|
|
1087
|
+
kind: 'guidance',
|
|
1088
|
+
title: `No ${kind} records`,
|
|
1089
|
+
detail: `Create a local ${kind} before using selection actions.`,
|
|
1090
|
+
safety: 'safe',
|
|
1091
|
+
};
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
this.selectedLibraryItemIndexes[kind] = Math.max(0, Math.min(items.length - 1, this.selectedLibraryItemIndexes[kind] + delta));
|
|
1095
|
+
const selected = this.selectedLocalLibraryItem(kind);
|
|
1096
|
+
this.status = selected ? `Selected ${kind}: ${selected.name}.` : `Selected ${kind} updated.`;
|
|
1097
|
+
this.lastActionResult = {
|
|
1098
|
+
kind: 'guidance',
|
|
1099
|
+
title: selected ? `Selected ${selected.name}` : `Selected ${kind}`,
|
|
1100
|
+
detail: selected ? `${selected.name} (${selected.id}) is now the selected local ${kind}.` : `Selection changed for ${kind}.`,
|
|
1101
|
+
safety: 'safe',
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
private applyLocalLibraryOperation(operation: AgentWorkspaceLocalOperation): void {
|
|
1106
|
+
const shellPaths = this.context?.workspace?.shellPaths;
|
|
1107
|
+
if (!shellPaths) {
|
|
1108
|
+
this.status = 'Local Agent registry files are unavailable.';
|
|
1109
|
+
this.lastActionResult = {
|
|
1110
|
+
kind: 'error',
|
|
1111
|
+
title: 'Local registry unavailable',
|
|
1112
|
+
detail: 'The Agent workspace cannot locate the Agent-local registry files for this runtime.',
|
|
1113
|
+
};
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
try {
|
|
1117
|
+
if (operation === 'persona-clear') {
|
|
1118
|
+
AgentPersonaRegistry.fromShellPaths(shellPaths).clearActive();
|
|
1119
|
+
this.finishLocalOperation('persona', 'Cleared active persona', 'The default Agent policy will apply to future turns.');
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const selected = this.selectedItemForOperation(operation);
|
|
1123
|
+
if (!selected) {
|
|
1124
|
+
this.status = 'No selected local registry item.';
|
|
1125
|
+
this.lastActionResult = {
|
|
1126
|
+
kind: 'guidance',
|
|
1127
|
+
title: 'Nothing selected',
|
|
1128
|
+
detail: 'Create or select a local library item before running this action.',
|
|
1129
|
+
safety: 'safe',
|
|
1130
|
+
};
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
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') {
|
|
1146
|
+
AgentPersonaRegistry.fromShellPaths(shellPaths).setActive(selected.id);
|
|
1147
|
+
this.finishLocalOperation('persona', `Using persona ${selected.name}`, `${selected.name} will shape future main-conversation turns.`);
|
|
1148
|
+
} else if (operation === 'persona-review') {
|
|
1149
|
+
AgentPersonaRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
|
|
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
|
+
};
|
|
1162
|
+
} else if (operation === 'skill-enable') {
|
|
1163
|
+
AgentSkillRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, true);
|
|
1164
|
+
this.finishLocalOperation('skill', `Enabled skill ${selected.name}`, `${selected.name} can now inform main-conversation turns.`);
|
|
1165
|
+
} else if (operation === 'skill-disable') {
|
|
1166
|
+
AgentSkillRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, false);
|
|
1167
|
+
this.finishLocalOperation('skill', `Disabled skill ${selected.name}`, `${selected.name} remains saved but is no longer injected into guidance.`);
|
|
1168
|
+
} else if (operation === 'skill-review') {
|
|
1169
|
+
AgentSkillRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
|
|
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
|
+
};
|
|
1182
|
+
} else if (operation === 'routine-start') {
|
|
1183
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).markStarted(selected.id);
|
|
1184
|
+
this.finishLocalOperation('routine', `Started routine ${selected.name}`, `${selected.name} was marked started for this main-conversation workflow. No hidden job was created.`);
|
|
1185
|
+
} else if (operation === 'routine-enable') {
|
|
1186
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, true);
|
|
1187
|
+
this.finishLocalOperation('routine', `Enabled routine ${selected.name}`, `${selected.name} can now inform main-conversation turns.`);
|
|
1188
|
+
} else if (operation === 'routine-disable') {
|
|
1189
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, false);
|
|
1190
|
+
this.finishLocalOperation('routine', `Disabled routine ${selected.name}`, `${selected.name} remains saved but is no longer injected into guidance.`);
|
|
1191
|
+
} else {
|
|
1192
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
|
|
1193
|
+
this.finishLocalOperation('routine', `Reviewed routine ${selected.name}`, `${selected.name} is marked reviewed.`);
|
|
1194
|
+
}
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1197
|
+
this.status = detail;
|
|
1198
|
+
this.lastActionResult = {
|
|
1199
|
+
kind: 'error',
|
|
1200
|
+
title: 'Local registry action failed',
|
|
1201
|
+
detail,
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
private selectedItemForOperation(operation: AgentWorkspaceLocalOperation): AgentWorkspaceLocalLibraryItem | null {
|
|
1207
|
+
if (operation.startsWith('persona-')) return this.selectedLocalLibraryItem('persona');
|
|
1208
|
+
if (operation.startsWith('skill-')) return this.selectedLocalLibraryItem('skill');
|
|
1209
|
+
return this.selectedLocalLibraryItem('routine');
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
private finishLocalOperation(kind: AgentWorkspaceLocalEditorKind, title: string, detail: string): void {
|
|
1213
|
+
this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
|
|
1214
|
+
this.clampLocalLibrarySelection(kind);
|
|
1215
|
+
this.status = title;
|
|
1216
|
+
this.lastActionResult = {
|
|
1217
|
+
kind: 'refreshed',
|
|
1218
|
+
title,
|
|
1219
|
+
detail,
|
|
1220
|
+
safety: 'safe',
|
|
1221
|
+
};
|
|
951
1222
|
}
|
|
952
1223
|
|
|
953
1224
|
private replaceEditorField(index: number, value: string, message: string): void {
|
|
@@ -996,6 +1267,21 @@ export class AgentWorkspace {
|
|
|
996
1267
|
try {
|
|
997
1268
|
if (editor.kind === 'persona') {
|
|
998
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
|
+
}
|
|
999
1285
|
const created = registry.create({
|
|
1000
1286
|
name: this.editorField('name'),
|
|
1001
1287
|
description: this.editorField('description'),
|
|
@@ -1006,9 +1292,22 @@ export class AgentWorkspace {
|
|
|
1006
1292
|
provenance: 'agent-workspace',
|
|
1007
1293
|
});
|
|
1008
1294
|
if (isAffirmative(this.editorField('activate'))) registry.setActive(created.id);
|
|
1009
|
-
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1295
|
+
this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
|
|
1010
1296
|
} else if (editor.kind === 'skill') {
|
|
1011
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
|
+
}
|
|
1012
1311
|
const created = registry.create({
|
|
1013
1312
|
name: this.editorField('name'),
|
|
1014
1313
|
description: this.editorField('description'),
|
|
@@ -1019,9 +1318,22 @@ export class AgentWorkspace {
|
|
|
1019
1318
|
source: 'user',
|
|
1020
1319
|
provenance: 'agent-workspace',
|
|
1021
1320
|
});
|
|
1022
|
-
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1321
|
+
this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
|
|
1023
1322
|
} else {
|
|
1024
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
|
+
}
|
|
1025
1337
|
const created = registry.create({
|
|
1026
1338
|
name: this.editorField('name'),
|
|
1027
1339
|
description: this.editorField('description'),
|
|
@@ -1032,7 +1344,7 @@ export class AgentWorkspace {
|
|
|
1032
1344
|
source: 'user',
|
|
1033
1345
|
provenance: 'agent-workspace',
|
|
1034
1346
|
});
|
|
1035
|
-
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1347
|
+
this.finishLocalEditor(editor.kind, created.id, created.name, 'Created');
|
|
1036
1348
|
}
|
|
1037
1349
|
} catch (error) {
|
|
1038
1350
|
const detail = error instanceof Error ? error.message : String(error);
|
|
@@ -1046,7 +1358,7 @@ export class AgentWorkspace {
|
|
|
1046
1358
|
}
|
|
1047
1359
|
}
|
|
1048
1360
|
|
|
1049
|
-
private finishLocalEditor(kind: AgentWorkspaceLocalEditorKind, id: string, name: string): void {
|
|
1361
|
+
private finishLocalEditor(kind: AgentWorkspaceLocalEditorKind, id: string, name: string, verb: 'Created' | 'Updated'): void {
|
|
1050
1362
|
this.localEditor = null;
|
|
1051
1363
|
const categoryId = editorCategoryId(kind);
|
|
1052
1364
|
const categoryIndex = this.categories.findIndex((category) => category.id === categoryId);
|
|
@@ -1055,10 +1367,10 @@ export class AgentWorkspace {
|
|
|
1055
1367
|
this.selectedActionIndex = 0;
|
|
1056
1368
|
}
|
|
1057
1369
|
this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
|
|
1058
|
-
this.status =
|
|
1370
|
+
this.status = `${verb} ${kind}: ${name}.`;
|
|
1059
1371
|
this.lastActionResult = {
|
|
1060
1372
|
kind: 'refreshed',
|
|
1061
|
-
title:
|
|
1373
|
+
title: `${verb} ${kind}`,
|
|
1062
1374
|
detail: `${name} (${id}) was saved to the Agent-local ${categoryId} registry.`,
|
|
1063
1375
|
safety: 'safe',
|
|
1064
1376
|
};
|
|
@@ -70,6 +70,8 @@ function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[
|
|
|
70
70
|
function actionCommand(action: AgentWorkspaceAction): string {
|
|
71
71
|
if (action.kind === 'workspace') return action.targetCategoryId ? `open ${action.targetCategoryId}` : '(workspace)';
|
|
72
72
|
if (action.kind === 'editor') return action.editorKind ? `edit ${action.editorKind}` : '(editor)';
|
|
73
|
+
if (action.kind === 'local-selection') return action.selectionDelta && action.selectionDelta < 0 ? 'select previous' : 'select next';
|
|
74
|
+
if (action.kind === 'local-operation') return action.localOperation ?? '(local action)';
|
|
73
75
|
return action.command ?? '(guidance)';
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -106,6 +108,7 @@ function localLibraryLines(
|
|
|
106
108
|
title: string,
|
|
107
109
|
items: readonly AgentWorkspaceRuntimeSnapshot['localPersonas'][number][],
|
|
108
110
|
emptyText: string,
|
|
111
|
+
selectedId: string | null,
|
|
109
112
|
): ContextLine[] {
|
|
110
113
|
const lines: ContextLine[] = [
|
|
111
114
|
{ text: title, fg: PALETTE.title, bold: true },
|
|
@@ -115,7 +118,9 @@ function localLibraryLines(
|
|
|
115
118
|
return lines;
|
|
116
119
|
}
|
|
117
120
|
for (const item of items.slice(0, 8)) {
|
|
121
|
+
const selected = item.id === selectedId;
|
|
118
122
|
const status = [
|
|
123
|
+
selected ? 'selected' : '',
|
|
119
124
|
item.active ? 'active' : '',
|
|
120
125
|
item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
|
|
121
126
|
item.reviewState,
|
|
@@ -123,10 +128,11 @@ function localLibraryLines(
|
|
|
123
128
|
].filter(Boolean).join(' / ');
|
|
124
129
|
const tags = item.tags.length > 0 ? ` tags=${item.tags.join(',')}` : '';
|
|
125
130
|
const triggers = item.triggers.length > 0 ? ` triggers=${item.triggers.join(',')}` : '';
|
|
131
|
+
const marker = selected ? `${GLYPHS.navigation.selected} ` : '';
|
|
126
132
|
lines.push({
|
|
127
|
-
text: `${item.id}: ${item.name} (${status})`,
|
|
133
|
+
text: `${marker}${item.id}: ${item.name} (${status})`,
|
|
128
134
|
fg: item.reviewState === 'stale' ? PALETTE.warn : PALETTE.info,
|
|
129
|
-
bold: item.active === true,
|
|
135
|
+
bold: selected || item.active === true,
|
|
130
136
|
});
|
|
131
137
|
lines.push({ text: ` ${item.description}${tags}${triggers}`, fg: PALETTE.muted });
|
|
132
138
|
}
|
|
@@ -136,7 +142,7 @@ function localLibraryLines(
|
|
|
136
142
|
return lines;
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
|
|
145
|
+
function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
|
|
140
146
|
if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
|
|
141
147
|
const base: ContextLine[] = [{ text: 'Live Agent Context', fg: PALETTE.title, bold: true }];
|
|
142
148
|
if (category.id === 'home') {
|
|
@@ -220,7 +226,7 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
220
226
|
{ text: 'Personas are local behavior profiles for the serial main-conversation assistant, not spawned agents.', fg: PALETTE.good },
|
|
221
227
|
{ text: 'Use them for tone, role, domain constraints, tool posture, and repeatable operating preferences.', fg: PALETTE.muted },
|
|
222
228
|
{ text: '' },
|
|
223
|
-
...localLibraryLines('Persona Library', snapshot.localPersonas, 'No local personas yet. Create one with
|
|
229
|
+
...localLibraryLines('Persona Library', snapshot.localPersonas, 'No local personas yet. Create one here with Create persona.', workspace.selectedLocalLibraryItem('persona')?.id ?? null),
|
|
224
230
|
);
|
|
225
231
|
} else if (category.id === 'skills') {
|
|
226
232
|
base.push(
|
|
@@ -228,7 +234,7 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
228
234
|
{ text: 'Skills are reusable local procedures the assistant can apply from the main conversation.', fg: PALETTE.good },
|
|
229
235
|
{ text: 'Enabled skills are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
|
|
230
236
|
{ text: '' },
|
|
231
|
-
...localLibraryLines('Skill Library', snapshot.localSkills, 'No local skills yet. Create one with
|
|
237
|
+
...localLibraryLines('Skill Library', snapshot.localSkills, 'No local skills yet. Create one here with Create skill.', workspace.selectedLocalLibraryItem('skill')?.id ?? null),
|
|
232
238
|
);
|
|
233
239
|
} else if (category.id === 'routines') {
|
|
234
240
|
base.push(
|
|
@@ -236,7 +242,7 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
236
242
|
{ text: 'Routines are repeatable main-conversation workflows. Starting one does not create hidden jobs.', fg: PALETTE.good },
|
|
237
243
|
{ text: 'Scheduling a reviewed routine is explicit and writes to the externally owned daemon only with --yes.', fg: PALETTE.warn },
|
|
238
244
|
{ text: '' },
|
|
239
|
-
...localLibraryLines('Routine Library', snapshot.localRoutines, 'No local routines yet. Create one with
|
|
245
|
+
...localLibraryLines('Routine Library', snapshot.localRoutines, 'No local routines yet. Create one here with Create routine.', workspace.selectedLocalLibraryItem('routine')?.id ?? null),
|
|
240
246
|
);
|
|
241
247
|
} else if (category.id === 'work') {
|
|
242
248
|
base.push(
|
|
@@ -288,7 +294,7 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
|
|
|
288
294
|
{ text: '' },
|
|
289
295
|
...(workspace.localEditor ? editorContextLines(workspace.localEditor) : []),
|
|
290
296
|
...(workspace.localEditor ? [{ text: '' }] : []),
|
|
291
|
-
...snapshotLines(category, workspace.runtimeSnapshot),
|
|
297
|
+
...snapshotLines(workspace, category, workspace.runtimeSnapshot),
|
|
292
298
|
];
|
|
293
299
|
|
|
294
300
|
if (action) {
|
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.
|
|
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 {
|