@pellux/goodvibes-agent 0.1.50 → 0.1.51
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 +4 -0
- package/package.json +1 -1
- package/src/input/agent-workspace.ts +294 -4
- package/src/renderer/agent-workspace.ts +60 -1
- package/src/version.ts +1 -1
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
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,26 @@ 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';
|
|
24
|
+
|
|
25
|
+
export type AgentWorkspaceLocalEditorKind = 'persona' | 'skill' | 'routine';
|
|
26
|
+
|
|
27
|
+
export interface AgentWorkspaceEditorField {
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly label: string;
|
|
30
|
+
readonly value: string;
|
|
31
|
+
readonly required: boolean;
|
|
32
|
+
readonly multiline: boolean;
|
|
33
|
+
readonly hint: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface AgentWorkspaceLocalEditor {
|
|
37
|
+
readonly kind: AgentWorkspaceLocalEditorKind;
|
|
38
|
+
readonly title: string;
|
|
39
|
+
readonly fields: readonly AgentWorkspaceEditorField[];
|
|
40
|
+
readonly selectedFieldIndex: number;
|
|
41
|
+
readonly message: string;
|
|
42
|
+
}
|
|
24
43
|
|
|
25
44
|
export interface AgentWorkspaceAction {
|
|
26
45
|
readonly id: string;
|
|
@@ -28,6 +47,7 @@ export interface AgentWorkspaceAction {
|
|
|
28
47
|
readonly detail: string;
|
|
29
48
|
readonly command?: string;
|
|
30
49
|
readonly targetCategoryId?: string;
|
|
50
|
+
readonly editorKind?: AgentWorkspaceLocalEditorKind;
|
|
31
51
|
readonly kind: AgentWorkspaceActionKind;
|
|
32
52
|
readonly safety: 'safe' | 'read-only' | 'delegates' | 'blocked';
|
|
33
53
|
}
|
|
@@ -495,7 +515,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
495
515
|
actions: [
|
|
496
516
|
{ id: 'personas-list', label: 'List personas', detail: 'Print the full local persona library.', command: '/personas list', kind: 'command', safety: 'read-only' },
|
|
497
517
|
{ id: 'personas-active', label: 'Show active persona', detail: 'Inspect the active local persona applied to new turns.', command: '/personas active', kind: 'command', safety: 'read-only' },
|
|
498
|
-
{ id: 'personas-create', label: 'Create persona', detail: '
|
|
518
|
+
{ 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' },
|
|
499
519
|
{ id: 'personas-use', label: 'Use persona', detail: 'Activate a local persona by id or name.', command: '/personas use <id>', kind: 'command', safety: 'safe' },
|
|
500
520
|
{ id: 'personas-review', label: 'Review persona', detail: 'Mark a local persona reviewed after inspecting it.', command: '/personas review <id>', kind: 'command', safety: 'safe' },
|
|
501
521
|
{ id: 'personas-clear', label: 'Clear active persona', detail: 'Return to the default Agent policy without deleting any persona.', command: '/personas clear', kind: 'command', safety: 'safe' },
|
|
@@ -510,7 +530,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
510
530
|
actions: [
|
|
511
531
|
{ id: 'skills-list', label: 'List skills', detail: 'Print the full local Agent skill library.', command: '/agent-skills list', kind: 'command', safety: 'read-only' },
|
|
512
532
|
{ id: 'skills-enabled', label: 'Enabled skills', detail: 'Show only skills currently injected into Agent guidance.', command: '/agent-skills enabled', kind: 'command', safety: 'read-only' },
|
|
513
|
-
{ id: 'skills-create', label: 'Create skill', detail: '
|
|
533
|
+
{ 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' },
|
|
514
534
|
{ id: 'skills-enable', label: 'Enable skill', detail: 'Enable a local Agent skill by id or name.', command: '/agent-skills enable <id>', kind: 'command', safety: 'safe' },
|
|
515
535
|
{ id: 'skills-review', label: 'Review skill', detail: 'Mark a local skill reviewed after inspecting it.', command: '/agent-skills review <id>', kind: 'command', safety: 'safe' },
|
|
516
536
|
],
|
|
@@ -524,7 +544,7 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
524
544
|
actions: [
|
|
525
545
|
{ id: 'routines-list', label: 'List routines', detail: 'Print the full local Agent routine library.', command: '/routines list', kind: 'command', safety: 'read-only' },
|
|
526
546
|
{ id: 'routines-enabled', label: 'Enabled routines', detail: 'Show routines available for direct use.', command: '/routines enabled', kind: 'command', safety: 'read-only' },
|
|
527
|
-
{ id: 'routines-create', label: 'Create routine', detail: '
|
|
547
|
+
{ 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' },
|
|
528
548
|
{ id: 'routines-start', label: 'Start routine', detail: 'Start a local routine in the main conversation without creating a hidden job.', command: '/routines start <id>', kind: 'command', safety: 'safe' },
|
|
529
549
|
{ 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
550
|
{ id: 'routines-receipts', label: 'Promotion receipts', detail: 'Inspect local redacted routine schedule promotion receipts.', command: '/routines receipts', kind: 'command', safety: 'read-only' },
|
|
@@ -578,6 +598,70 @@ function parseCommand(command: string): { readonly name: string; readonly args:
|
|
|
578
598
|
return { name: parts[0] ?? '', args: parts.slice(1) };
|
|
579
599
|
}
|
|
580
600
|
|
|
601
|
+
function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceLocalEditor {
|
|
602
|
+
if (kind === 'persona') {
|
|
603
|
+
return {
|
|
604
|
+
kind,
|
|
605
|
+
title: 'Create Persona',
|
|
606
|
+
selectedFieldIndex: 0,
|
|
607
|
+
message: 'Enter a local behavior profile for the serial main-conversation assistant.',
|
|
608
|
+
fields: [
|
|
609
|
+
{ id: 'name', label: 'Name', value: '', required: true, multiline: false, hint: 'Short persona name.' },
|
|
610
|
+
{ id: 'description', label: 'Description', value: '', required: true, multiline: false, hint: 'One-line summary of when to use it.' },
|
|
611
|
+
{ id: 'body', label: 'Instructions', value: '', required: true, multiline: true, hint: 'Operating guidance. Ctrl-J inserts a new line.' },
|
|
612
|
+
{ id: 'tags', label: 'Tags', value: '', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
613
|
+
{ id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this persona.' },
|
|
614
|
+
{ id: 'activate', label: 'Activate now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
|
|
615
|
+
],
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
if (kind === 'skill') {
|
|
619
|
+
return {
|
|
620
|
+
kind,
|
|
621
|
+
title: 'Create Skill',
|
|
622
|
+
selectedFieldIndex: 0,
|
|
623
|
+
message: 'Enter a reusable local procedure the assistant can apply from the main conversation.',
|
|
624
|
+
fields: [
|
|
625
|
+
{ id: 'name', label: 'Name', value: '', required: true, multiline: false, hint: 'Short skill name.' },
|
|
626
|
+
{ id: 'description', label: 'Description', value: '', required: true, multiline: false, hint: 'One-line summary of the procedure.' },
|
|
627
|
+
{ id: 'procedure', label: 'Procedure', value: '', required: true, multiline: true, hint: 'Reusable steps. Ctrl-J inserts a new line.' },
|
|
628
|
+
{ id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this skill.' },
|
|
629
|
+
{ id: 'tags', label: 'Tags', value: '', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
630
|
+
{ id: 'enabled', label: 'Enable now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
|
|
631
|
+
],
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return {
|
|
635
|
+
kind,
|
|
636
|
+
title: 'Create Routine',
|
|
637
|
+
selectedFieldIndex: 0,
|
|
638
|
+
message: 'Enter a repeatable workflow. It runs in the main conversation unless explicitly promoted to a daemon schedule.',
|
|
639
|
+
fields: [
|
|
640
|
+
{ id: 'name', label: 'Name', value: '', required: true, multiline: false, hint: 'Short routine name.' },
|
|
641
|
+
{ id: 'description', label: 'Description', value: '', required: true, multiline: false, hint: 'One-line summary of the workflow.' },
|
|
642
|
+
{ id: 'steps', label: 'Steps', value: '', required: true, multiline: true, hint: 'Workflow steps. Ctrl-J inserts a new line.' },
|
|
643
|
+
{ id: 'triggers', label: 'Triggers', value: '', required: false, multiline: false, hint: 'Comma-separated words that suggest this routine.' },
|
|
644
|
+
{ id: 'tags', label: 'Tags', value: '', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
645
|
+
{ id: 'enabled', label: 'Enable now', value: 'yes', required: false, multiline: false, hint: 'yes/no.' },
|
|
646
|
+
],
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function splitList(value: string): string[] {
|
|
651
|
+
return value.split(',').map((part) => part.trim()).filter(Boolean);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function isAffirmative(value: string): boolean {
|
|
655
|
+
const normalized = value.trim().toLowerCase();
|
|
656
|
+
return normalized === '' || normalized === 'yes' || normalized === 'y' || normalized === 'true' || normalized === 'enabled' || normalized === 'on';
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function editorCategoryId(kind: AgentWorkspaceLocalEditorKind): string {
|
|
660
|
+
if (kind === 'persona') return 'personas';
|
|
661
|
+
if (kind === 'skill') return 'skills';
|
|
662
|
+
return 'routines';
|
|
663
|
+
}
|
|
664
|
+
|
|
581
665
|
export class AgentWorkspace {
|
|
582
666
|
public active = false;
|
|
583
667
|
public focusPane: AgentWorkspaceFocusPane = 'actions';
|
|
@@ -586,6 +670,7 @@ export class AgentWorkspace {
|
|
|
586
670
|
public status = 'Ready. Choose an operator flow; ordinary assistant work stays in the main conversation.';
|
|
587
671
|
public runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null = null;
|
|
588
672
|
public lastActionResult: AgentWorkspaceActionResult | null = null;
|
|
673
|
+
public localEditor: AgentWorkspaceLocalEditor | null = null;
|
|
589
674
|
private context: CommandContext | null = null;
|
|
590
675
|
private dispatchCommand: AgentWorkspaceCommandDispatcher | null = null;
|
|
591
676
|
|
|
@@ -597,6 +682,7 @@ export class AgentWorkspace {
|
|
|
597
682
|
this.focusPane = 'actions';
|
|
598
683
|
this.status = 'Ready. Choose an operator flow; ordinary assistant work stays in the main conversation.';
|
|
599
684
|
this.lastActionResult = null;
|
|
685
|
+
this.localEditor = null;
|
|
600
686
|
this.clampSelection();
|
|
601
687
|
}
|
|
602
688
|
|
|
@@ -607,6 +693,7 @@ export class AgentWorkspace {
|
|
|
607
693
|
|
|
608
694
|
close(): void {
|
|
609
695
|
this.active = false;
|
|
696
|
+
this.localEditor = null;
|
|
610
697
|
}
|
|
611
698
|
|
|
612
699
|
get categories(): readonly AgentWorkspaceCategory[] {
|
|
@@ -688,13 +775,86 @@ export class AgentWorkspace {
|
|
|
688
775
|
};
|
|
689
776
|
}
|
|
690
777
|
|
|
778
|
+
cancelLocalEditor(): void {
|
|
779
|
+
if (!this.localEditor) return;
|
|
780
|
+
const title = this.localEditor.title;
|
|
781
|
+
this.localEditor = null;
|
|
782
|
+
this.status = `${title} cancelled.`;
|
|
783
|
+
this.lastActionResult = {
|
|
784
|
+
kind: 'guidance',
|
|
785
|
+
title: `${title} cancelled`,
|
|
786
|
+
detail: 'No local Agent registry changes were written.',
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
moveEditorField(delta: number): void {
|
|
791
|
+
const editor = this.localEditor;
|
|
792
|
+
if (!editor) return;
|
|
793
|
+
const nextIndex = Math.max(0, Math.min(editor.fields.length - 1, editor.selectedFieldIndex + delta));
|
|
794
|
+
this.localEditor = { ...editor, selectedFieldIndex: nextIndex };
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
appendEditorText(text: string): void {
|
|
798
|
+
const editor = this.localEditor;
|
|
799
|
+
if (!editor || text.length === 0) return;
|
|
800
|
+
const field = editor.fields[editor.selectedFieldIndex];
|
|
801
|
+
if (!field) return;
|
|
802
|
+
this.replaceEditorField(editor.selectedFieldIndex, `${field.value}${text}`, editor.message);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
appendEditorNewline(): void {
|
|
806
|
+
const editor = this.localEditor;
|
|
807
|
+
if (!editor) return;
|
|
808
|
+
const field = editor.fields[editor.selectedFieldIndex];
|
|
809
|
+
if (!field || !field.multiline) {
|
|
810
|
+
this.moveEditorField(1);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
this.replaceEditorField(editor.selectedFieldIndex, `${field.value}\n`, editor.message);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
editorBackspace(): void {
|
|
817
|
+
const editor = this.localEditor;
|
|
818
|
+
if (!editor) return;
|
|
819
|
+
const field = editor.fields[editor.selectedFieldIndex];
|
|
820
|
+
if (!field || field.value.length === 0) return;
|
|
821
|
+
const characters = Array.from(field.value);
|
|
822
|
+
characters.pop();
|
|
823
|
+
this.replaceEditorField(editor.selectedFieldIndex, characters.join(''), editor.message);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
submitEditorFieldOrForm(): void {
|
|
827
|
+
const editor = this.localEditor;
|
|
828
|
+
if (!editor) return;
|
|
829
|
+
if (editor.selectedFieldIndex < editor.fields.length - 1) {
|
|
830
|
+
this.moveEditorField(1);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
this.submitLocalEditor();
|
|
834
|
+
}
|
|
835
|
+
|
|
691
836
|
activateSelected(): void {
|
|
837
|
+
if (this.localEditor) {
|
|
838
|
+
this.submitEditorFieldOrForm();
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
692
841
|
if (this.focusPane === 'categories') {
|
|
693
842
|
this.focusActions();
|
|
694
843
|
return;
|
|
695
844
|
}
|
|
696
845
|
const action = this.selectedAction;
|
|
697
846
|
if (!action) return;
|
|
847
|
+
if (action.kind === 'editor' && action.editorKind) {
|
|
848
|
+
this.localEditor = createLocalEditor(action.editorKind);
|
|
849
|
+
this.status = `Editing ${this.localEditor.title}.`;
|
|
850
|
+
this.lastActionResult = {
|
|
851
|
+
kind: 'guidance',
|
|
852
|
+
title: this.localEditor.title,
|
|
853
|
+
detail: this.localEditor.message,
|
|
854
|
+
safety: action.safety,
|
|
855
|
+
};
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
698
858
|
if (action.kind === 'guidance' || !action.command) {
|
|
699
859
|
if (action.kind === 'workspace' && action.targetCategoryId) {
|
|
700
860
|
const targetIndex = this.categories.findIndex((category) => category.id === action.targetCategoryId);
|
|
@@ -789,6 +949,121 @@ export class AgentWorkspace {
|
|
|
789
949
|
this.selectedCategoryIndex = Math.max(0, Math.min(this.selectedCategoryIndex, this.categories.length - 1));
|
|
790
950
|
this.selectedActionIndex = Math.max(0, Math.min(this.selectedActionIndex, this.actions.length - 1));
|
|
791
951
|
}
|
|
952
|
+
|
|
953
|
+
private replaceEditorField(index: number, value: string, message: string): void {
|
|
954
|
+
const editor = this.localEditor;
|
|
955
|
+
if (!editor) return;
|
|
956
|
+
const fields = editor.fields.map((field, fieldIndex) => fieldIndex === index ? { ...field, value } : field);
|
|
957
|
+
this.localEditor = { ...editor, fields, message };
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
private editorField(id: string): string {
|
|
961
|
+
const editor = this.localEditor;
|
|
962
|
+
return editor?.fields.find((field) => field.id === id)?.value.trim() ?? '';
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
private missingEditorField(): AgentWorkspaceEditorField | null {
|
|
966
|
+
const editor = this.localEditor;
|
|
967
|
+
if (!editor) return null;
|
|
968
|
+
return editor.fields.find((field) => field.required && field.value.trim().length === 0) ?? null;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private submitLocalEditor(): void {
|
|
972
|
+
const editor = this.localEditor;
|
|
973
|
+
if (!editor) return;
|
|
974
|
+
const missing = this.missingEditorField();
|
|
975
|
+
if (missing) {
|
|
976
|
+
const missingIndex = editor.fields.findIndex((field) => field.id === missing.id);
|
|
977
|
+
this.localEditor = {
|
|
978
|
+
...editor,
|
|
979
|
+
selectedFieldIndex: Math.max(0, missingIndex),
|
|
980
|
+
message: `${missing.label} is required before saving.`,
|
|
981
|
+
};
|
|
982
|
+
this.status = `${missing.label} is required.`;
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const shellPaths = this.context?.workspace?.shellPaths;
|
|
986
|
+
if (!shellPaths) {
|
|
987
|
+
this.localEditor = { ...editor, message: 'Cannot save because Agent shell paths are unavailable.' };
|
|
988
|
+
this.status = 'Cannot save local Agent registry item without shell paths.';
|
|
989
|
+
this.lastActionResult = {
|
|
990
|
+
kind: 'error',
|
|
991
|
+
title: 'Local registry unavailable',
|
|
992
|
+
detail: 'The Agent workspace cannot locate the Agent-local registry files for this runtime.',
|
|
993
|
+
};
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
try {
|
|
997
|
+
if (editor.kind === 'persona') {
|
|
998
|
+
const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
|
|
999
|
+
const created = registry.create({
|
|
1000
|
+
name: this.editorField('name'),
|
|
1001
|
+
description: this.editorField('description'),
|
|
1002
|
+
body: this.editorField('body'),
|
|
1003
|
+
tags: splitList(this.editorField('tags')),
|
|
1004
|
+
triggers: splitList(this.editorField('triggers')),
|
|
1005
|
+
source: 'user',
|
|
1006
|
+
provenance: 'agent-workspace',
|
|
1007
|
+
});
|
|
1008
|
+
if (isAffirmative(this.editorField('activate'))) registry.setActive(created.id);
|
|
1009
|
+
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1010
|
+
} else if (editor.kind === 'skill') {
|
|
1011
|
+
const registry = AgentSkillRegistry.fromShellPaths(shellPaths);
|
|
1012
|
+
const created = registry.create({
|
|
1013
|
+
name: this.editorField('name'),
|
|
1014
|
+
description: this.editorField('description'),
|
|
1015
|
+
procedure: this.editorField('procedure'),
|
|
1016
|
+
triggers: splitList(this.editorField('triggers')),
|
|
1017
|
+
tags: splitList(this.editorField('tags')),
|
|
1018
|
+
enabled: isAffirmative(this.editorField('enabled')),
|
|
1019
|
+
source: 'user',
|
|
1020
|
+
provenance: 'agent-workspace',
|
|
1021
|
+
});
|
|
1022
|
+
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1023
|
+
} else {
|
|
1024
|
+
const registry = AgentRoutineRegistry.fromShellPaths(shellPaths);
|
|
1025
|
+
const created = registry.create({
|
|
1026
|
+
name: this.editorField('name'),
|
|
1027
|
+
description: this.editorField('description'),
|
|
1028
|
+
steps: this.editorField('steps'),
|
|
1029
|
+
triggers: splitList(this.editorField('triggers')),
|
|
1030
|
+
tags: splitList(this.editorField('tags')),
|
|
1031
|
+
enabled: isAffirmative(this.editorField('enabled')),
|
|
1032
|
+
source: 'user',
|
|
1033
|
+
provenance: 'agent-workspace',
|
|
1034
|
+
});
|
|
1035
|
+
this.finishLocalEditor(editor.kind, created.id, created.name);
|
|
1036
|
+
}
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1039
|
+
this.localEditor = { ...editor, message: detail };
|
|
1040
|
+
this.status = detail;
|
|
1041
|
+
this.lastActionResult = {
|
|
1042
|
+
kind: 'error',
|
|
1043
|
+
title: `${editor.title} failed`,
|
|
1044
|
+
detail,
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
private finishLocalEditor(kind: AgentWorkspaceLocalEditorKind, id: string, name: string): void {
|
|
1050
|
+
this.localEditor = null;
|
|
1051
|
+
const categoryId = editorCategoryId(kind);
|
|
1052
|
+
const categoryIndex = this.categories.findIndex((category) => category.id === categoryId);
|
|
1053
|
+
if (categoryIndex >= 0) {
|
|
1054
|
+
this.selectedCategoryIndex = categoryIndex;
|
|
1055
|
+
this.selectedActionIndex = 0;
|
|
1056
|
+
}
|
|
1057
|
+
this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
|
|
1058
|
+
this.status = `Created ${kind}: ${name}.`;
|
|
1059
|
+
this.lastActionResult = {
|
|
1060
|
+
kind: 'refreshed',
|
|
1061
|
+
title: `Created ${kind}`,
|
|
1062
|
+
detail: `${name} (${id}) was saved to the Agent-local ${categoryId} registry.`,
|
|
1063
|
+
safety: 'safe',
|
|
1064
|
+
};
|
|
1065
|
+
this.clampSelection();
|
|
1066
|
+
}
|
|
792
1067
|
}
|
|
793
1068
|
|
|
794
1069
|
export function handleAgentWorkspaceToken(
|
|
@@ -799,6 +1074,21 @@ export function handleAgentWorkspaceToken(
|
|
|
799
1074
|
): boolean {
|
|
800
1075
|
if (!workspace.active) return false;
|
|
801
1076
|
|
|
1077
|
+
if (workspace.localEditor) {
|
|
1078
|
+
if (token.type === 'text') {
|
|
1079
|
+
workspace.appendEditorText(token.value);
|
|
1080
|
+
} else if (token.type === 'key') {
|
|
1081
|
+
if (token.logicalName === 'escape') workspace.cancelLocalEditor();
|
|
1082
|
+
else if (token.logicalName === 'enter') workspace.submitEditorFieldOrForm();
|
|
1083
|
+
else if (token.logicalName === 'tab' || token.logicalName === 'down') workspace.moveEditorField(1);
|
|
1084
|
+
else if (token.logicalName === 'up') workspace.moveEditorField(-1);
|
|
1085
|
+
else if (token.logicalName === 'backspace' || token.logicalName === 'delete') workspace.editorBackspace();
|
|
1086
|
+
else if (token.logicalName === 'j' && token.ctrl === true) workspace.appendEditorNewline();
|
|
1087
|
+
}
|
|
1088
|
+
requestRender();
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
802
1092
|
if (token.type === 'key') {
|
|
803
1093
|
if (token.logicalName === 'escape') {
|
|
804
1094
|
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,7 @@ 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)';
|
|
71
73
|
return action.command ?? '(guidance)';
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -260,6 +262,23 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
260
262
|
return base;
|
|
261
263
|
}
|
|
262
264
|
|
|
265
|
+
function editorContextLines(editor: AgentWorkspaceLocalEditor): ContextLine[] {
|
|
266
|
+
const selected = editor.fields[editor.selectedFieldIndex];
|
|
267
|
+
const lines: ContextLine[] = [
|
|
268
|
+
{ text: editor.title, fg: PALETTE.title, bold: true },
|
|
269
|
+
{ text: editor.message, fg: editor.message.includes('required') || editor.message.includes('cannot') || editor.message.includes('Cannot') ? PALETTE.warn : PALETTE.info },
|
|
270
|
+
{ 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 },
|
|
271
|
+
];
|
|
272
|
+
if (selected) {
|
|
273
|
+
lines.push(
|
|
274
|
+
{ text: '' },
|
|
275
|
+
{ text: `Editing: ${selected.label}${selected.required ? ' (required)' : ''}`, fg: PALETTE.title, bold: true },
|
|
276
|
+
{ text: selected.hint, fg: PALETTE.muted },
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return lines;
|
|
280
|
+
}
|
|
281
|
+
|
|
263
282
|
function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction | null, width: number): WorkspaceRow[] {
|
|
264
283
|
const lines: ContextLine[] = [
|
|
265
284
|
{ text: category.label, fg: PALETTE.title, bold: true },
|
|
@@ -267,6 +286,8 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
|
|
|
267
286
|
{ text: '' },
|
|
268
287
|
{ text: category.detail, fg: PALETTE.text },
|
|
269
288
|
{ text: '' },
|
|
289
|
+
...(workspace.localEditor ? editorContextLines(workspace.localEditor) : []),
|
|
290
|
+
...(workspace.localEditor ? [{ text: '' }] : []),
|
|
270
291
|
...snapshotLines(category, workspace.runtimeSnapshot),
|
|
271
292
|
];
|
|
272
293
|
|
|
@@ -303,7 +324,42 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
|
|
|
303
324
|
});
|
|
304
325
|
}
|
|
305
326
|
|
|
327
|
+
function buildEditorRows(editor: AgentWorkspaceLocalEditor, width: number, height: number): WorkspaceRow[] {
|
|
328
|
+
const rows: WorkspaceRow[] = [
|
|
329
|
+
{ text: editor.title, fg: PALETTE.title, bold: true },
|
|
330
|
+
{ text: editor.message, fg: PALETTE.info },
|
|
331
|
+
{ text: '' },
|
|
332
|
+
];
|
|
333
|
+
for (let index = 0; index < editor.fields.length; index += 1) {
|
|
334
|
+
const field = editor.fields[index]!;
|
|
335
|
+
const selected = index === editor.selectedFieldIndex;
|
|
336
|
+
const marker = selected ? GLYPHS.navigation.selected : ' ';
|
|
337
|
+
const required = field.required ? ' *' : '';
|
|
338
|
+
const value = field.value.length > 0 ? field.value : '(empty)';
|
|
339
|
+
const color = selected ? PALETTE.text : field.value.length > 0 ? PALETTE.info : PALETTE.muted;
|
|
340
|
+
rows.push({
|
|
341
|
+
text: `${marker} ${field.label}${required}`,
|
|
342
|
+
selected,
|
|
343
|
+
fg: color,
|
|
344
|
+
bold: selected,
|
|
345
|
+
});
|
|
346
|
+
const valueLines = value.split('\n');
|
|
347
|
+
for (const valueLine of valueLines.slice(0, 4)) {
|
|
348
|
+
for (const wrapped of wrapText(` ${valueLine}`, Math.max(1, width - 2))) {
|
|
349
|
+
rows.push({ text: wrapped, fg: field.value.length > 0 ? PALETTE.text : PALETTE.dim, dim: field.value.length === 0 });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (valueLines.length > 4) rows.push({ text: ` ${valueLines.length - 4} more line(s)`, fg: PALETTE.dim, dim: true });
|
|
353
|
+
rows.push({ text: ` ${field.hint}`, fg: PALETTE.dim, dim: true });
|
|
354
|
+
}
|
|
355
|
+
rows.push({ text: '' });
|
|
356
|
+
rows.push({ text: 'Enter next/save · Up/Down field · Backspace edit · Ctrl-J newline · Esc cancel', fg: PALETTE.muted });
|
|
357
|
+
while (rows.length < height) rows.push({ text: '', kind: 'empty' });
|
|
358
|
+
return rows.slice(0, height);
|
|
359
|
+
}
|
|
360
|
+
|
|
306
361
|
function buildActionRows(workspace: AgentWorkspace, width: number, height: number): WorkspaceRow[] {
|
|
362
|
+
if (workspace.localEditor) return buildEditorRows(workspace.localEditor, width, height);
|
|
307
363
|
const rows: WorkspaceRow[] = [];
|
|
308
364
|
const labelWidth = Math.min(28, Math.max(16, Math.floor(width * 0.30)));
|
|
309
365
|
const safetyWidth = 10;
|
|
@@ -350,6 +406,9 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
|
|
|
350
406
|
}
|
|
351
407
|
|
|
352
408
|
function footerText(workspace: AgentWorkspace): string {
|
|
409
|
+
if (workspace.localEditor) {
|
|
410
|
+
return `Agent workspace · editing ${workspace.localEditor.kind} · Enter next/save · Ctrl-J newline · Esc cancel`;
|
|
411
|
+
}
|
|
353
412
|
const focus = workspace.focusPane === 'categories' ? 'categories' : 'actions';
|
|
354
413
|
return `Agent workspace · focus ${focus} · Up/Down navigate · Left/Right pane · Enter open/action · R refresh · Esc close`;
|
|
355
414
|
}
|
|
@@ -371,7 +430,7 @@ export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, h
|
|
|
371
430
|
width,
|
|
372
431
|
height,
|
|
373
432
|
title: 'GoodVibes Agent / Operator Workspace',
|
|
374
|
-
stateLabel: workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
|
|
433
|
+
stateLabel: workspace.localEditor ? 'Editor' : workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
|
|
375
434
|
leftHeader: 'Operator Areas',
|
|
376
435
|
mainHeader: `${category.label} · ${category.actions.length} action(s)`,
|
|
377
436
|
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.51';
|
|
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 {
|