@pellux/goodvibes-agent 0.1.50 → 0.1.52
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 +469 -10
- package/src/renderer/agent-workspace.ts +73 -8
- 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.52 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- e543fa5 Add selected local library actions
|
|
8
|
+
|
|
9
|
+
## 0.1.51 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- 6a8e8a6 Add local library workspace editors
|
|
12
|
+
|
|
5
13
|
## 0.1.50 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- bdb654a Improve local library workspaces
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
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,7 +20,38 @@ 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';
|
|
23
|
+
export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'local-selection' | 'local-operation';
|
|
24
|
+
|
|
25
|
+
export type AgentWorkspaceLocalEditorKind = 'persona' | 'skill' | 'routine';
|
|
26
|
+
|
|
27
|
+
export type AgentWorkspaceLocalOperation =
|
|
28
|
+
| 'persona-use'
|
|
29
|
+
| 'persona-review'
|
|
30
|
+
| 'persona-clear'
|
|
31
|
+
| 'skill-enable'
|
|
32
|
+
| 'skill-disable'
|
|
33
|
+
| 'skill-review'
|
|
34
|
+
| 'routine-start'
|
|
35
|
+
| 'routine-enable'
|
|
36
|
+
| 'routine-disable'
|
|
37
|
+
| 'routine-review';
|
|
38
|
+
|
|
39
|
+
export interface AgentWorkspaceEditorField {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly value: string;
|
|
43
|
+
readonly required: boolean;
|
|
44
|
+
readonly multiline: boolean;
|
|
45
|
+
readonly hint: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface AgentWorkspaceLocalEditor {
|
|
49
|
+
readonly kind: AgentWorkspaceLocalEditorKind;
|
|
50
|
+
readonly title: string;
|
|
51
|
+
readonly fields: readonly AgentWorkspaceEditorField[];
|
|
52
|
+
readonly selectedFieldIndex: number;
|
|
53
|
+
readonly message: string;
|
|
54
|
+
}
|
|
24
55
|
|
|
25
56
|
export interface AgentWorkspaceAction {
|
|
26
57
|
readonly id: string;
|
|
@@ -28,6 +59,10 @@ export interface AgentWorkspaceAction {
|
|
|
28
59
|
readonly detail: string;
|
|
29
60
|
readonly command?: string;
|
|
30
61
|
readonly targetCategoryId?: string;
|
|
62
|
+
readonly editorKind?: AgentWorkspaceLocalEditorKind;
|
|
63
|
+
readonly localKind?: AgentWorkspaceLocalEditorKind;
|
|
64
|
+
readonly selectionDelta?: number;
|
|
65
|
+
readonly localOperation?: AgentWorkspaceLocalOperation;
|
|
31
66
|
readonly kind: AgentWorkspaceActionKind;
|
|
32
67
|
readonly safety: 'safe' | 'read-only' | 'delegates' | 'blocked';
|
|
33
68
|
}
|
|
@@ -495,10 +530,12 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
495
530
|
actions: [
|
|
496
531
|
{ id: 'personas-list', label: 'List personas', detail: 'Print the full local persona library.', command: '/personas list', kind: 'command', safety: 'read-only' },
|
|
497
532
|
{ 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-
|
|
499
|
-
{ id: 'personas-
|
|
500
|
-
{ id: 'personas-
|
|
501
|
-
{ id: 'personas-
|
|
533
|
+
{ 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
|
+
{ 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
|
+
{ 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' },
|
|
536
|
+
{ 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
|
+
{ 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
|
+
{ 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' },
|
|
502
539
|
],
|
|
503
540
|
},
|
|
504
541
|
{
|
|
@@ -510,9 +547,12 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
510
547
|
actions: [
|
|
511
548
|
{ id: 'skills-list', label: 'List skills', detail: 'Print the full local Agent skill library.', command: '/agent-skills list', kind: 'command', safety: 'read-only' },
|
|
512
549
|
{ 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-
|
|
514
|
-
{ id: 'skills-
|
|
515
|
-
{ id: 'skills-
|
|
550
|
+
{ 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
|
+
{ 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
|
+
{ 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' },
|
|
553
|
+
{ 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
|
+
{ 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
|
+
{ 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' },
|
|
516
556
|
],
|
|
517
557
|
},
|
|
518
558
|
{
|
|
@@ -524,8 +564,13 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
524
564
|
actions: [
|
|
525
565
|
{ id: 'routines-list', label: 'List routines', detail: 'Print the full local Agent routine library.', command: '/routines list', kind: 'command', safety: 'read-only' },
|
|
526
566
|
{ id: 'routines-enabled', label: 'Enabled routines', detail: 'Show routines available for direct use.', command: '/routines enabled', kind: 'command', safety: 'read-only' },
|
|
527
|
-
{ id: 'routines-
|
|
528
|
-
{ id: 'routines-
|
|
567
|
+
{ 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
|
+
{ 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
|
+
{ 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' },
|
|
570
|
+
{ 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
|
+
{ 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
|
+
{ id: 'routines-disable', label: 'Disable selected', detail: 'Disable the selected routine without deleting it.', localKind: 'routine', localOperation: 'routine-disable', kind: 'local-operation', safety: 'safe' },
|
|
573
|
+
{ 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' },
|
|
529
574
|
{ 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
575
|
{ id: 'routines-receipts', label: 'Promotion receipts', detail: 'Inspect local redacted routine schedule promotion receipts.', command: '/routines receipts', kind: 'command', safety: 'read-only' },
|
|
531
576
|
],
|
|
@@ -578,6 +623,70 @@ function parseCommand(command: string): { readonly name: string; readonly args:
|
|
|
578
623
|
return { name: parts[0] ?? '', args: parts.slice(1) };
|
|
579
624
|
}
|
|
580
625
|
|
|
626
|
+
function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceLocalEditor {
|
|
627
|
+
if (kind === 'persona') {
|
|
628
|
+
return {
|
|
629
|
+
kind,
|
|
630
|
+
title: 'Create Persona',
|
|
631
|
+
selectedFieldIndex: 0,
|
|
632
|
+
message: 'Enter a local behavior profile for the serial main-conversation assistant.',
|
|
633
|
+
fields: [
|
|
634
|
+
{ id: 'name', label: 'Name', value: '', required: true, multiline: false, hint: 'Short persona name.' },
|
|
635
|
+
{ id: 'description', label: 'Description', value: '', required: true, multiline: false, hint: 'One-line summary of when to use it.' },
|
|
636
|
+
{ id: 'body', label: 'Instructions', value: '', required: true, multiline: true, hint: 'Operating guidance. Ctrl-J inserts a new line.' },
|
|
637
|
+
{ id: 'tags', label: 'Tags', value: '', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
638
|
+
{ id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this persona.' },
|
|
639
|
+
{ id: 'activate', label: 'Activate now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
|
|
640
|
+
],
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
if (kind === 'skill') {
|
|
644
|
+
return {
|
|
645
|
+
kind,
|
|
646
|
+
title: 'Create Skill',
|
|
647
|
+
selectedFieldIndex: 0,
|
|
648
|
+
message: 'Enter a reusable local procedure the assistant can apply from the main conversation.',
|
|
649
|
+
fields: [
|
|
650
|
+
{ id: 'name', label: 'Name', value: '', required: true, multiline: false, hint: 'Short skill name.' },
|
|
651
|
+
{ id: 'description', label: 'Description', value: '', required: true, multiline: false, hint: 'One-line summary of the procedure.' },
|
|
652
|
+
{ id: 'procedure', label: 'Procedure', value: '', required: true, multiline: true, hint: 'Reusable steps. Ctrl-J inserts a new line.' },
|
|
653
|
+
{ id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this skill.' },
|
|
654
|
+
{ id: 'tags', label: 'Tags', value: '', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
655
|
+
{ id: 'enabled', label: 'Enable now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
|
|
656
|
+
],
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
kind,
|
|
661
|
+
title: 'Create Routine',
|
|
662
|
+
selectedFieldIndex: 0,
|
|
663
|
+
message: 'Enter a repeatable workflow. It runs in the main conversation unless explicitly promoted to a daemon schedule.',
|
|
664
|
+
fields: [
|
|
665
|
+
{ id: 'name', label: 'Name', value: '', required: true, multiline: false, hint: 'Short routine name.' },
|
|
666
|
+
{ id: 'description', label: 'Description', value: '', required: true, multiline: false, hint: 'One-line summary of the workflow.' },
|
|
667
|
+
{ id: 'steps', label: 'Steps', value: '', required: true, multiline: true, hint: 'Workflow steps. Ctrl-J inserts a new line.' },
|
|
668
|
+
{ id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this routine.' },
|
|
669
|
+
{ id: 'tags', label: 'Tags', value: '', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
670
|
+
{ id: 'enabled', label: 'Enable now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
|
|
671
|
+
],
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function splitList(value: string): string[] {
|
|
676
|
+
return value.split(',').map((part) => part.trim()).filter(Boolean);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function isAffirmative(value: string): boolean {
|
|
680
|
+
const normalized = value.trim().toLowerCase();
|
|
681
|
+
return normalized === '' || normalized === 'yes' || normalized === 'y' || normalized === 'true' || normalized === 'enabled' || normalized === 'on';
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function editorCategoryId(kind: AgentWorkspaceLocalEditorKind): string {
|
|
685
|
+
if (kind === 'persona') return 'personas';
|
|
686
|
+
if (kind === 'skill') return 'skills';
|
|
687
|
+
return 'routines';
|
|
688
|
+
}
|
|
689
|
+
|
|
581
690
|
export class AgentWorkspace {
|
|
582
691
|
public active = false;
|
|
583
692
|
public focusPane: AgentWorkspaceFocusPane = 'actions';
|
|
@@ -586,6 +695,12 @@ export class AgentWorkspace {
|
|
|
586
695
|
public status = 'Ready. Choose an operator flow; ordinary assistant work stays in the main conversation.';
|
|
587
696
|
public runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null = null;
|
|
588
697
|
public lastActionResult: AgentWorkspaceActionResult | null = null;
|
|
698
|
+
public localEditor: AgentWorkspaceLocalEditor | null = null;
|
|
699
|
+
private readonly selectedLibraryItemIndexes: Record<AgentWorkspaceLocalEditorKind, number> = {
|
|
700
|
+
persona: 0,
|
|
701
|
+
skill: 0,
|
|
702
|
+
routine: 0,
|
|
703
|
+
};
|
|
589
704
|
private context: CommandContext | null = null;
|
|
590
705
|
private dispatchCommand: AgentWorkspaceCommandDispatcher | null = null;
|
|
591
706
|
|
|
@@ -597,6 +712,7 @@ export class AgentWorkspace {
|
|
|
597
712
|
this.focusPane = 'actions';
|
|
598
713
|
this.status = 'Ready. Choose an operator flow; ordinary assistant work stays in the main conversation.';
|
|
599
714
|
this.lastActionResult = null;
|
|
715
|
+
this.localEditor = null;
|
|
600
716
|
this.clampSelection();
|
|
601
717
|
}
|
|
602
718
|
|
|
@@ -607,6 +723,7 @@ export class AgentWorkspace {
|
|
|
607
723
|
|
|
608
724
|
close(): void {
|
|
609
725
|
this.active = false;
|
|
726
|
+
this.localEditor = null;
|
|
610
727
|
}
|
|
611
728
|
|
|
612
729
|
get categories(): readonly AgentWorkspaceCategory[] {
|
|
@@ -625,6 +742,13 @@ export class AgentWorkspace {
|
|
|
625
742
|
return this.actions[this.selectedActionIndex] ?? null;
|
|
626
743
|
}
|
|
627
744
|
|
|
745
|
+
selectedLocalLibraryItem(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceLocalLibraryItem | null {
|
|
746
|
+
const items = this.localLibraryItems(kind);
|
|
747
|
+
if (items.length === 0) return null;
|
|
748
|
+
const index = Math.max(0, Math.min(this.selectedLibraryItemIndexes[kind], items.length - 1));
|
|
749
|
+
return items[index] ?? null;
|
|
750
|
+
}
|
|
751
|
+
|
|
628
752
|
focusCategories(): void {
|
|
629
753
|
this.focusPane = 'categories';
|
|
630
754
|
}
|
|
@@ -688,13 +812,94 @@ export class AgentWorkspace {
|
|
|
688
812
|
};
|
|
689
813
|
}
|
|
690
814
|
|
|
815
|
+
cancelLocalEditor(): void {
|
|
816
|
+
if (!this.localEditor) return;
|
|
817
|
+
const title = this.localEditor.title;
|
|
818
|
+
this.localEditor = null;
|
|
819
|
+
this.status = `${title} cancelled.`;
|
|
820
|
+
this.lastActionResult = {
|
|
821
|
+
kind: 'guidance',
|
|
822
|
+
title: `${title} cancelled`,
|
|
823
|
+
detail: 'No local Agent registry changes were written.',
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
moveEditorField(delta: number): void {
|
|
828
|
+
const editor = this.localEditor;
|
|
829
|
+
if (!editor) return;
|
|
830
|
+
const nextIndex = Math.max(0, Math.min(editor.fields.length - 1, editor.selectedFieldIndex + delta));
|
|
831
|
+
this.localEditor = { ...editor, selectedFieldIndex: nextIndex };
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
appendEditorText(text: string): void {
|
|
835
|
+
const editor = this.localEditor;
|
|
836
|
+
if (!editor || text.length === 0) return;
|
|
837
|
+
const field = editor.fields[editor.selectedFieldIndex];
|
|
838
|
+
if (!field) return;
|
|
839
|
+
this.replaceEditorField(editor.selectedFieldIndex, `${field.value}${text}`, editor.message);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
appendEditorNewline(): void {
|
|
843
|
+
const editor = this.localEditor;
|
|
844
|
+
if (!editor) return;
|
|
845
|
+
const field = editor.fields[editor.selectedFieldIndex];
|
|
846
|
+
if (!field || !field.multiline) {
|
|
847
|
+
this.moveEditorField(1);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
this.replaceEditorField(editor.selectedFieldIndex, `${field.value}\n`, editor.message);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
editorBackspace(): void {
|
|
854
|
+
const editor = this.localEditor;
|
|
855
|
+
if (!editor) return;
|
|
856
|
+
const field = editor.fields[editor.selectedFieldIndex];
|
|
857
|
+
if (!field || field.value.length === 0) return;
|
|
858
|
+
const characters = Array.from(field.value);
|
|
859
|
+
characters.pop();
|
|
860
|
+
this.replaceEditorField(editor.selectedFieldIndex, characters.join(''), editor.message);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
submitEditorFieldOrForm(): void {
|
|
864
|
+
const editor = this.localEditor;
|
|
865
|
+
if (!editor) return;
|
|
866
|
+
if (editor.selectedFieldIndex < editor.fields.length - 1) {
|
|
867
|
+
this.moveEditorField(1);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
this.submitLocalEditor();
|
|
871
|
+
}
|
|
872
|
+
|
|
691
873
|
activateSelected(): void {
|
|
874
|
+
if (this.localEditor) {
|
|
875
|
+
this.submitEditorFieldOrForm();
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
692
878
|
if (this.focusPane === 'categories') {
|
|
693
879
|
this.focusActions();
|
|
694
880
|
return;
|
|
695
881
|
}
|
|
696
882
|
const action = this.selectedAction;
|
|
697
883
|
if (!action) return;
|
|
884
|
+
if (action.kind === 'editor' && action.editorKind) {
|
|
885
|
+
this.localEditor = createLocalEditor(action.editorKind);
|
|
886
|
+
this.status = `Editing ${this.localEditor.title}.`;
|
|
887
|
+
this.lastActionResult = {
|
|
888
|
+
kind: 'guidance',
|
|
889
|
+
title: this.localEditor.title,
|
|
890
|
+
detail: this.localEditor.message,
|
|
891
|
+
safety: action.safety,
|
|
892
|
+
};
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (action.kind === 'local-selection' && action.localKind) {
|
|
896
|
+
this.moveLocalLibraryItemSelection(action.localKind, action.selectionDelta ?? 0);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (action.kind === 'local-operation' && action.localOperation) {
|
|
900
|
+
this.applyLocalLibraryOperation(action.localOperation);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
698
903
|
if (action.kind === 'guidance' || !action.command) {
|
|
699
904
|
if (action.kind === 'workspace' && action.targetCategoryId) {
|
|
700
905
|
const targetIndex = this.categories.findIndex((category) => category.id === action.targetCategoryId);
|
|
@@ -788,6 +993,245 @@ export class AgentWorkspace {
|
|
|
788
993
|
private clampSelection(): void {
|
|
789
994
|
this.selectedCategoryIndex = Math.max(0, Math.min(this.selectedCategoryIndex, this.categories.length - 1));
|
|
790
995
|
this.selectedActionIndex = Math.max(0, Math.min(this.selectedActionIndex, this.actions.length - 1));
|
|
996
|
+
this.clampLocalLibrarySelection('persona');
|
|
997
|
+
this.clampLocalLibrarySelection('skill');
|
|
998
|
+
this.clampLocalLibrarySelection('routine');
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
private localLibraryItems(kind: AgentWorkspaceLocalEditorKind): readonly AgentWorkspaceLocalLibraryItem[] {
|
|
1002
|
+
if (kind === 'persona') return this.runtimeSnapshot?.localPersonas ?? [];
|
|
1003
|
+
if (kind === 'skill') return this.runtimeSnapshot?.localSkills ?? [];
|
|
1004
|
+
return this.runtimeSnapshot?.localRoutines ?? [];
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
private clampLocalLibrarySelection(kind: AgentWorkspaceLocalEditorKind): void {
|
|
1008
|
+
const length = this.localLibraryItems(kind).length;
|
|
1009
|
+
this.selectedLibraryItemIndexes[kind] = length === 0
|
|
1010
|
+
? 0
|
|
1011
|
+
: Math.max(0, Math.min(this.selectedLibraryItemIndexes[kind], length - 1));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private moveLocalLibraryItemSelection(kind: AgentWorkspaceLocalEditorKind, delta: number): void {
|
|
1015
|
+
const items = this.localLibraryItems(kind);
|
|
1016
|
+
if (items.length === 0) {
|
|
1017
|
+
this.status = `No local ${kind} records to select.`;
|
|
1018
|
+
this.lastActionResult = {
|
|
1019
|
+
kind: 'guidance',
|
|
1020
|
+
title: `No ${kind} records`,
|
|
1021
|
+
detail: `Create a local ${kind} before using selection actions.`,
|
|
1022
|
+
safety: 'safe',
|
|
1023
|
+
};
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
this.selectedLibraryItemIndexes[kind] = Math.max(0, Math.min(items.length - 1, this.selectedLibraryItemIndexes[kind] + delta));
|
|
1027
|
+
const selected = this.selectedLocalLibraryItem(kind);
|
|
1028
|
+
this.status = selected ? `Selected ${kind}: ${selected.name}.` : `Selected ${kind} updated.`;
|
|
1029
|
+
this.lastActionResult = {
|
|
1030
|
+
kind: 'guidance',
|
|
1031
|
+
title: selected ? `Selected ${selected.name}` : `Selected ${kind}`,
|
|
1032
|
+
detail: selected ? `${selected.name} (${selected.id}) is now the selected local ${kind}.` : `Selection changed for ${kind}.`,
|
|
1033
|
+
safety: 'safe',
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
private applyLocalLibraryOperation(operation: AgentWorkspaceLocalOperation): void {
|
|
1038
|
+
const shellPaths = this.context?.workspace?.shellPaths;
|
|
1039
|
+
if (!shellPaths) {
|
|
1040
|
+
this.status = 'Local Agent registry files are unavailable.';
|
|
1041
|
+
this.lastActionResult = {
|
|
1042
|
+
kind: 'error',
|
|
1043
|
+
title: 'Local registry unavailable',
|
|
1044
|
+
detail: 'The Agent workspace cannot locate the Agent-local registry files for this runtime.',
|
|
1045
|
+
};
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
try {
|
|
1049
|
+
if (operation === 'persona-clear') {
|
|
1050
|
+
AgentPersonaRegistry.fromShellPaths(shellPaths).clearActive();
|
|
1051
|
+
this.finishLocalOperation('persona', 'Cleared active persona', 'The default Agent policy will apply to future turns.');
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const selected = this.selectedItemForOperation(operation);
|
|
1055
|
+
if (!selected) {
|
|
1056
|
+
this.status = 'No selected local registry item.';
|
|
1057
|
+
this.lastActionResult = {
|
|
1058
|
+
kind: 'guidance',
|
|
1059
|
+
title: 'Nothing selected',
|
|
1060
|
+
detail: 'Create or select a local library item before running this action.',
|
|
1061
|
+
safety: 'safe',
|
|
1062
|
+
};
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (operation === 'persona-use') {
|
|
1066
|
+
AgentPersonaRegistry.fromShellPaths(shellPaths).setActive(selected.id);
|
|
1067
|
+
this.finishLocalOperation('persona', `Using persona ${selected.name}`, `${selected.name} will shape future main-conversation turns.`);
|
|
1068
|
+
} else if (operation === 'persona-review') {
|
|
1069
|
+
AgentPersonaRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
|
|
1070
|
+
this.finishLocalOperation('persona', `Reviewed persona ${selected.name}`, `${selected.name} is marked reviewed.`);
|
|
1071
|
+
} else if (operation === 'skill-enable') {
|
|
1072
|
+
AgentSkillRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, true);
|
|
1073
|
+
this.finishLocalOperation('skill', `Enabled skill ${selected.name}`, `${selected.name} can now inform main-conversation turns.`);
|
|
1074
|
+
} else if (operation === 'skill-disable') {
|
|
1075
|
+
AgentSkillRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, false);
|
|
1076
|
+
this.finishLocalOperation('skill', `Disabled skill ${selected.name}`, `${selected.name} remains saved but is no longer injected into guidance.`);
|
|
1077
|
+
} else if (operation === 'skill-review') {
|
|
1078
|
+
AgentSkillRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
|
|
1079
|
+
this.finishLocalOperation('skill', `Reviewed skill ${selected.name}`, `${selected.name} is marked reviewed.`);
|
|
1080
|
+
} else if (operation === 'routine-start') {
|
|
1081
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).markStarted(selected.id);
|
|
1082
|
+
this.finishLocalOperation('routine', `Started routine ${selected.name}`, `${selected.name} was marked started for this main-conversation workflow. No hidden job was created.`);
|
|
1083
|
+
} else if (operation === 'routine-enable') {
|
|
1084
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, true);
|
|
1085
|
+
this.finishLocalOperation('routine', `Enabled routine ${selected.name}`, `${selected.name} can now inform main-conversation turns.`);
|
|
1086
|
+
} else if (operation === 'routine-disable') {
|
|
1087
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).setEnabled(selected.id, false);
|
|
1088
|
+
this.finishLocalOperation('routine', `Disabled routine ${selected.name}`, `${selected.name} remains saved but is no longer injected into guidance.`);
|
|
1089
|
+
} else {
|
|
1090
|
+
AgentRoutineRegistry.fromShellPaths(shellPaths).markReviewed(selected.id);
|
|
1091
|
+
this.finishLocalOperation('routine', `Reviewed routine ${selected.name}`, `${selected.name} is marked reviewed.`);
|
|
1092
|
+
}
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1095
|
+
this.status = detail;
|
|
1096
|
+
this.lastActionResult = {
|
|
1097
|
+
kind: 'error',
|
|
1098
|
+
title: 'Local registry action failed',
|
|
1099
|
+
detail,
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private selectedItemForOperation(operation: AgentWorkspaceLocalOperation): AgentWorkspaceLocalLibraryItem | null {
|
|
1105
|
+
if (operation.startsWith('persona-')) return this.selectedLocalLibraryItem('persona');
|
|
1106
|
+
if (operation.startsWith('skill-')) return this.selectedLocalLibraryItem('skill');
|
|
1107
|
+
return this.selectedLocalLibraryItem('routine');
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
private finishLocalOperation(kind: AgentWorkspaceLocalEditorKind, title: string, detail: string): void {
|
|
1111
|
+
this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
|
|
1112
|
+
this.clampLocalLibrarySelection(kind);
|
|
1113
|
+
this.status = title;
|
|
1114
|
+
this.lastActionResult = {
|
|
1115
|
+
kind: 'refreshed',
|
|
1116
|
+
title,
|
|
1117
|
+
detail,
|
|
1118
|
+
safety: 'safe',
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
private replaceEditorField(index: number, value: string, message: string): void {
|
|
1123
|
+
const editor = this.localEditor;
|
|
1124
|
+
if (!editor) return;
|
|
1125
|
+
const fields = editor.fields.map((field, fieldIndex) => fieldIndex === index ? { ...field, value } : field);
|
|
1126
|
+
this.localEditor = { ...editor, fields, message };
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
private editorField(id: string): string {
|
|
1130
|
+
const editor = this.localEditor;
|
|
1131
|
+
return editor?.fields.find((field) => field.id === id)?.value.trim() ?? '';
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
private missingEditorField(): AgentWorkspaceEditorField | null {
|
|
1135
|
+
const editor = this.localEditor;
|
|
1136
|
+
if (!editor) return null;
|
|
1137
|
+
return editor.fields.find((field) => field.required && field.value.trim().length === 0) ?? null;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
private submitLocalEditor(): void {
|
|
1141
|
+
const editor = this.localEditor;
|
|
1142
|
+
if (!editor) return;
|
|
1143
|
+
const missing = this.missingEditorField();
|
|
1144
|
+
if (missing) {
|
|
1145
|
+
const missingIndex = editor.fields.findIndex((field) => field.id === missing.id);
|
|
1146
|
+
this.localEditor = {
|
|
1147
|
+
...editor,
|
|
1148
|
+
selectedFieldIndex: Math.max(0, missingIndex),
|
|
1149
|
+
message: `${missing.label} is required before saving.`,
|
|
1150
|
+
};
|
|
1151
|
+
this.status = `${missing.label} is required.`;
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
const shellPaths = this.context?.workspace?.shellPaths;
|
|
1155
|
+
if (!shellPaths) {
|
|
1156
|
+
this.localEditor = { ...editor, message: 'Cannot save because Agent shell paths are unavailable.' };
|
|
1157
|
+
this.status = 'Cannot save local Agent registry item without shell paths.';
|
|
1158
|
+
this.lastActionResult = {
|
|
1159
|
+
kind: 'error',
|
|
1160
|
+
title: 'Local registry unavailable',
|
|
1161
|
+
detail: 'The Agent workspace cannot locate the Agent-local registry files for this runtime.',
|
|
1162
|
+
};
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
if (editor.kind === 'persona') {
|
|
1167
|
+
const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
|
|
1168
|
+
const created = registry.create({
|
|
1169
|
+
name: this.editorField('name'),
|
|
1170
|
+
description: this.editorField('description'),
|
|
1171
|
+
body: this.editorField('body'),
|
|
1172
|
+
tags: splitList(this.editorField('tags')),
|
|
1173
|
+
triggers: splitList(this.editorField('triggers')),
|
|
1174
|
+
source: 'user',
|
|
1175
|
+
provenance: 'agent-workspace',
|
|
1176
|
+
});
|
|
1177
|
+
if (isAffirmative(this.editorField('activate'))) registry.setActive(created.id);
|
|
1178
|
+
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1179
|
+
} else if (editor.kind === 'skill') {
|
|
1180
|
+
const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
|
|
1181
|
+
const created = registry.create({
|
|
1182
|
+
name: this.editorField('name'),
|
|
1183
|
+
description: this.editorField('description'),
|
|
1184
|
+
procedure: this.editorField('procedure'),
|
|
1185
|
+
triggers: splitList(this.editorField('triggers')),
|
|
1186
|
+
tags: splitList(this.editorField('tags')),
|
|
1187
|
+
enabled: isAffirmative(this.editorField('enabled')),
|
|
1188
|
+
source: 'user',
|
|
1189
|
+
provenance: 'agent-workspace',
|
|
1190
|
+
});
|
|
1191
|
+
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1192
|
+
} else {
|
|
1193
|
+
const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
|
|
1194
|
+
const created = registry.create({
|
|
1195
|
+
name: this.editorField('name'),
|
|
1196
|
+
description: this.editorField('description'),
|
|
1197
|
+
steps: this.editorField('steps'),
|
|
1198
|
+
triggers: splitList(this.editorField('triggers')),
|
|
1199
|
+
tags: splitList(this.editorField('tags')),
|
|
1200
|
+
enabled: isAffirmative(this.editorField('enabled')),
|
|
1201
|
+
source: 'user',
|
|
1202
|
+
provenance: 'agent-workspace',
|
|
1203
|
+
});
|
|
1204
|
+
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1205
|
+
}
|
|
1206
|
+
} catch (error) {
|
|
1207
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1208
|
+
this.localEditor = { ...editor, message: detail };
|
|
1209
|
+
this.status = detail;
|
|
1210
|
+
this.lastActionResult = {
|
|
1211
|
+
kind: 'error',
|
|
1212
|
+
title: `${editor.title} failed`,
|
|
1213
|
+
detail,
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
private finishLocalEditor(kind: AgentWorkspaceLocalEditorKind, id: string, name: string): void {
|
|
1219
|
+
this.localEditor = null;
|
|
1220
|
+
const categoryId = editorCategoryId(kind);
|
|
1221
|
+
const categoryIndex = this.categories.findIndex((category) => category.id === categoryId);
|
|
1222
|
+
if (categoryIndex >= 0) {
|
|
1223
|
+
this.selectedCategoryIndex = categoryIndex;
|
|
1224
|
+
this.selectedActionIndex = 0;
|
|
1225
|
+
}
|
|
1226
|
+
this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
|
|
1227
|
+
this.status = `Created ${kind}: ${name}.`;
|
|
1228
|
+
this.lastActionResult = {
|
|
1229
|
+
kind: 'refreshed',
|
|
1230
|
+
title: `Created ${kind}`,
|
|
1231
|
+
detail: `${name} (${id}) was saved to the Agent-local ${categoryId} registry.`,
|
|
1232
|
+
safety: 'safe',
|
|
1233
|
+
};
|
|
1234
|
+
this.clampSelection();
|
|
791
1235
|
}
|
|
792
1236
|
}
|
|
793
1237
|
|
|
@@ -799,6 +1243,21 @@ export function handleAgentWorkspaceToken(
|
|
|
799
1243
|
): boolean {
|
|
800
1244
|
if (!workspace.active) return false;
|
|
801
1245
|
|
|
1246
|
+
if (workspace.localEditor) {
|
|
1247
|
+
if (token.type === 'text') {
|
|
1248
|
+
workspace.appendEditorText(token.value);
|
|
1249
|
+
} else if (token.type === 'key') {
|
|
1250
|
+
if (token.logicalName === 'escape') workspace.cancelLocalEditor();
|
|
1251
|
+
else if (token.logicalName === 'enter') workspace.submitEditorFieldOrForm();
|
|
1252
|
+
else if (token.logicalName === 'tab' || token.logicalName === 'down') workspace.moveEditorField(1);
|
|
1253
|
+
else if (token.logicalName === 'up') workspace.moveEditorField(-1);
|
|
1254
|
+
else if (token.logicalName === 'backspace' || token.logicalName === 'delete') workspace.editorBackspace();
|
|
1255
|
+
else if (token.logicalName === 'j' && token.ctrl === true) workspace.appendEditorNewline();
|
|
1256
|
+
}
|
|
1257
|
+
requestRender();
|
|
1258
|
+
return true;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
802
1261
|
if (token.type === 'key') {
|
|
803
1262
|
if (token.logicalName === 'escape') {
|
|
804
1263
|
handleEscape();
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
AgentWorkspaceAction,
|
|
4
4
|
AgentWorkspaceActionResult,
|
|
5
5
|
AgentWorkspaceCategory,
|
|
6
|
+
AgentWorkspaceLocalEditor,
|
|
6
7
|
AgentWorkspaceRuntimeSnapshot,
|
|
7
8
|
} from '../input/agent-workspace.ts';
|
|
8
9
|
import type { Line } from '../types/grid.ts';
|
|
@@ -68,6 +69,9 @@ function buildLeftRows(workspace: AgentWorkspace, height: number): WorkspaceRow[
|
|
|
68
69
|
|
|
69
70
|
function actionCommand(action: AgentWorkspaceAction): string {
|
|
70
71
|
if (action.kind === 'workspace') return action.targetCategoryId ? `open ${action.targetCategoryId}` : '(workspace)';
|
|
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)';
|
|
71
75
|
return action.command ?? '(guidance)';
|
|
72
76
|
}
|
|
73
77
|
|
|
@@ -104,6 +108,7 @@ function localLibraryLines(
|
|
|
104
108
|
title: string,
|
|
105
109
|
items: readonly AgentWorkspaceRuntimeSnapshot['localPersonas'][number][],
|
|
106
110
|
emptyText: string,
|
|
111
|
+
selectedId: string | null,
|
|
107
112
|
): ContextLine[] {
|
|
108
113
|
const lines: ContextLine[] = [
|
|
109
114
|
{ text: title, fg: PALETTE.title, bold: true },
|
|
@@ -113,7 +118,9 @@ function localLibraryLines(
|
|
|
113
118
|
return lines;
|
|
114
119
|
}
|
|
115
120
|
for (const item of items.slice(0, 8)) {
|
|
121
|
+
const selected = item.id === selectedId;
|
|
116
122
|
const status = [
|
|
123
|
+
selected ? 'selected' : '',
|
|
117
124
|
item.active ? 'active' : '',
|
|
118
125
|
item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
|
|
119
126
|
item.reviewState,
|
|
@@ -121,10 +128,11 @@ function localLibraryLines(
|
|
|
121
128
|
].filter(Boolean).join(' / ');
|
|
122
129
|
const tags = item.tags.length > 0 ? ` tags=${item.tags.join(',')}` : '';
|
|
123
130
|
const triggers = item.triggers.length > 0 ? ` triggers=${item.triggers.join(',')}` : '';
|
|
131
|
+
const marker = selected ? `${GLYPHS.navigation.selected} ` : '';
|
|
124
132
|
lines.push({
|
|
125
|
-
text: `${item.id}: ${item.name} (${status})`,
|
|
133
|
+
text: `${marker}${item.id}: ${item.name} (${status})`,
|
|
126
134
|
fg: item.reviewState === 'stale' ? PALETTE.warn : PALETTE.info,
|
|
127
|
-
bold: item.active === true,
|
|
135
|
+
bold: selected || item.active === true,
|
|
128
136
|
});
|
|
129
137
|
lines.push({ text: ` ${item.description}${tags}${triggers}`, fg: PALETTE.muted });
|
|
130
138
|
}
|
|
@@ -134,7 +142,7 @@ function localLibraryLines(
|
|
|
134
142
|
return lines;
|
|
135
143
|
}
|
|
136
144
|
|
|
137
|
-
function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
|
|
145
|
+
function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
|
|
138
146
|
if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
|
|
139
147
|
const base: ContextLine[] = [{ text: 'Live Agent Context', fg: PALETTE.title, bold: true }];
|
|
140
148
|
if (category.id === 'home') {
|
|
@@ -218,7 +226,7 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
218
226
|
{ text: 'Personas are local behavior profiles for the serial main-conversation assistant, not spawned agents.', fg: PALETTE.good },
|
|
219
227
|
{ text: 'Use them for tone, role, domain constraints, tool posture, and repeatable operating preferences.', fg: PALETTE.muted },
|
|
220
228
|
{ text: '' },
|
|
221
|
-
...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),
|
|
222
230
|
);
|
|
223
231
|
} else if (category.id === 'skills') {
|
|
224
232
|
base.push(
|
|
@@ -226,7 +234,7 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
226
234
|
{ text: 'Skills are reusable local procedures the assistant can apply from the main conversation.', fg: PALETTE.good },
|
|
227
235
|
{ text: 'Enabled skills are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
|
|
228
236
|
{ text: '' },
|
|
229
|
-
...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),
|
|
230
238
|
);
|
|
231
239
|
} else if (category.id === 'routines') {
|
|
232
240
|
base.push(
|
|
@@ -234,7 +242,7 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
234
242
|
{ text: 'Routines are repeatable main-conversation workflows. Starting one does not create hidden jobs.', fg: PALETTE.good },
|
|
235
243
|
{ text: 'Scheduling a reviewed routine is explicit and writes to the externally owned daemon only with --yes.', fg: PALETTE.warn },
|
|
236
244
|
{ text: '' },
|
|
237
|
-
...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),
|
|
238
246
|
);
|
|
239
247
|
} else if (category.id === 'work') {
|
|
240
248
|
base.push(
|
|
@@ -260,6 +268,23 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
260
268
|
return base;
|
|
261
269
|
}
|
|
262
270
|
|
|
271
|
+
function editorContextLines(editor: AgentWorkspaceLocalEditor): ContextLine[] {
|
|
272
|
+
const selected = editor.fields[editor.selectedFieldIndex];
|
|
273
|
+
const lines: ContextLine[] = [
|
|
274
|
+
{ text: editor.title, fg: PALETTE.title, bold: true },
|
|
275
|
+
{ text: editor.message, fg: editor.message.includes('required') || editor.message.includes('cannot') || editor.message.includes('Cannot') ? PALETTE.warn : PALETTE.info },
|
|
276
|
+
{ text: 'Enter advances fields and saves from the final field. Ctrl-J adds a line inside multiline fields. Esc cancels without writing.', fg: PALETTE.muted },
|
|
277
|
+
];
|
|
278
|
+
if (selected) {
|
|
279
|
+
lines.push(
|
|
280
|
+
{ text: '' },
|
|
281
|
+
{ text: `Editing: ${selected.label}${selected.required ? ' (required)' : ''}`, fg: PALETTE.title, bold: true },
|
|
282
|
+
{ text: selected.hint, fg: PALETTE.muted },
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return lines;
|
|
286
|
+
}
|
|
287
|
+
|
|
263
288
|
function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction | null, width: number): WorkspaceRow[] {
|
|
264
289
|
const lines: ContextLine[] = [
|
|
265
290
|
{ text: category.label, fg: PALETTE.title, bold: true },
|
|
@@ -267,7 +292,9 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
|
|
|
267
292
|
{ text: '' },
|
|
268
293
|
{ text: category.detail, fg: PALETTE.text },
|
|
269
294
|
{ text: '' },
|
|
270
|
-
...
|
|
295
|
+
...(workspace.localEditor ? editorContextLines(workspace.localEditor) : []),
|
|
296
|
+
...(workspace.localEditor ? [{ text: '' }] : []),
|
|
297
|
+
...snapshotLines(workspace, category, workspace.runtimeSnapshot),
|
|
271
298
|
];
|
|
272
299
|
|
|
273
300
|
if (action) {
|
|
@@ -303,7 +330,42 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
|
|
|
303
330
|
});
|
|
304
331
|
}
|
|
305
332
|
|
|
333
|
+
function buildEditorRows(editor: AgentWorkspaceLocalEditor, width: number, height: number): WorkspaceRow[] {
|
|
334
|
+
const rows: WorkspaceRow[] = [
|
|
335
|
+
{ text: editor.title, fg: PALETTE.title, bold: true },
|
|
336
|
+
{ text: editor.message, fg: PALETTE.info },
|
|
337
|
+
{ text: '' },
|
|
338
|
+
];
|
|
339
|
+
for (let index = 0; index < editor.fields.length; index += 1) {
|
|
340
|
+
const field = editor.fields[index]!;
|
|
341
|
+
const selected = index === editor.selectedFieldIndex;
|
|
342
|
+
const marker = selected ? GLYPHS.navigation.selected : ' ';
|
|
343
|
+
const required = field.required ? ' *' : '';
|
|
344
|
+
const value = field.value.length > 0 ? field.value : '(empty)';
|
|
345
|
+
const color = selected ? PALETTE.text : field.value.length > 0 ? PALETTE.info : PALETTE.muted;
|
|
346
|
+
rows.push({
|
|
347
|
+
text: `${marker} ${field.label}${required}`,
|
|
348
|
+
selected,
|
|
349
|
+
fg: color,
|
|
350
|
+
bold: selected,
|
|
351
|
+
});
|
|
352
|
+
const valueLines = value.split('\n');
|
|
353
|
+
for (const valueLine of valueLines.slice(0, 4)) {
|
|
354
|
+
for (const wrapped of wrapText(` ${valueLine}`, Math.max(1, width - 2))) {
|
|
355
|
+
rows.push({ text: wrapped, fg: field.value.length > 0 ? PALETTE.text : PALETTE.dim, dim: field.value.length === 0 });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (valueLines.length > 4) rows.push({ text: ` ${valueLines.length - 4} more line(s)`, fg: PALETTE.dim, dim: true });
|
|
359
|
+
rows.push({ text: ` ${field.hint}`, fg: PALETTE.dim, dim: true });
|
|
360
|
+
}
|
|
361
|
+
rows.push({ text: '' });
|
|
362
|
+
rows.push({ text: 'Enter next/save · Up/Down field · Backspace edit · Ctrl-J newline · Esc cancel', fg: PALETTE.muted });
|
|
363
|
+
while (rows.length < height) rows.push({ text: '', kind: 'empty' });
|
|
364
|
+
return rows.slice(0, height);
|
|
365
|
+
}
|
|
366
|
+
|
|
306
367
|
function buildActionRows(workspace: AgentWorkspace, width: number, height: number): WorkspaceRow[] {
|
|
368
|
+
if (workspace.localEditor) return buildEditorRows(workspace.localEditor, width, height);
|
|
307
369
|
const rows: WorkspaceRow[] = [];
|
|
308
370
|
const labelWidth = Math.min(28, Math.max(16, Math.floor(width * 0.30)));
|
|
309
371
|
const safetyWidth = 10;
|
|
@@ -350,6 +412,9 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
|
|
|
350
412
|
}
|
|
351
413
|
|
|
352
414
|
function footerText(workspace: AgentWorkspace): string {
|
|
415
|
+
if (workspace.localEditor) {
|
|
416
|
+
return `Agent workspace · editing ${workspace.localEditor.kind} · Enter next/save · Ctrl-J newline · Esc cancel`;
|
|
417
|
+
}
|
|
353
418
|
const focus = workspace.focusPane === 'categories' ? 'categories' : 'actions';
|
|
354
419
|
return `Agent workspace · focus ${focus} · Up/Down navigate · Left/Right pane · Enter open/action · R refresh · Esc close`;
|
|
355
420
|
}
|
|
@@ -371,7 +436,7 @@ export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, h
|
|
|
371
436
|
width,
|
|
372
437
|
height,
|
|
373
438
|
title: 'GoodVibes Agent / Operator Workspace',
|
|
374
|
-
stateLabel: workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
|
|
439
|
+
stateLabel: workspace.localEditor ? 'Editor' : workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
|
|
375
440
|
leftHeader: 'Operator Areas',
|
|
376
441
|
mainHeader: `${category.label} · ${category.actions.length} action(s)`,
|
|
377
442
|
leftRows: buildLeftRows(workspace, metrics.bodyRows),
|
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.52';
|
|
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 {
|