@pellux/goodvibes-agent 0.1.49 → 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 +8 -0
- package/package.json +1 -1
- package/src/input/agent-workspace.ts +424 -19
- package/src/renderer/agent-workspace.ts +118 -1
- 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.51 - 2026-05-31
|
|
6
|
+
|
|
7
|
+
- 6a8e8a6 Add local library workspace editors
|
|
8
|
+
|
|
9
|
+
## 0.1.50 - 2026-05-31
|
|
10
|
+
|
|
11
|
+
- bdb654a Improve local library workspaces
|
|
12
|
+
|
|
5
13
|
## 0.1.49 - 2026-05-31
|
|
6
14
|
|
|
7
15
|
- 445e694 Show isolated Agent Knowledge in TUI panel
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
|
|
2
2
|
import { basename, sep } from 'node:path';
|
|
3
3
|
import type { CommandContext } from './command-registry.ts';
|
|
4
|
-
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
5
|
-
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
6
|
-
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
4
|
+
import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
|
|
5
|
+
import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
|
|
6
|
+
import { AgentSkillRegistry, type AgentSkillRecord } from '../agent/skill-registry.ts';
|
|
7
7
|
import { getAgentRuntimeProfilesRoot, listAgentRuntimeProfiles, listAgentRuntimeProfileTemplates } from '../agent/runtime-profile.ts';
|
|
8
8
|
import {
|
|
9
9
|
buildAgentWorkspaceChannels,
|
|
@@ -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
|
}
|
|
@@ -53,6 +73,19 @@ export interface AgentWorkspaceActionResult {
|
|
|
53
73
|
readonly safety?: AgentWorkspaceAction['safety'];
|
|
54
74
|
}
|
|
55
75
|
|
|
76
|
+
export interface AgentWorkspaceLocalLibraryItem {
|
|
77
|
+
readonly id: string;
|
|
78
|
+
readonly name: string;
|
|
79
|
+
readonly description: string;
|
|
80
|
+
readonly reviewState: string;
|
|
81
|
+
readonly source: string;
|
|
82
|
+
readonly tags: readonly string[];
|
|
83
|
+
readonly triggers: readonly string[];
|
|
84
|
+
readonly active?: boolean;
|
|
85
|
+
readonly enabled?: boolean;
|
|
86
|
+
readonly startCount?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
type AgentWorkspaceConfigReader = {
|
|
57
90
|
get(key: string): unknown;
|
|
58
91
|
};
|
|
@@ -69,10 +102,13 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
69
102
|
readonly sessionMemoryCount: number;
|
|
70
103
|
readonly localRoutineCount: number;
|
|
71
104
|
readonly enabledRoutineCount: number;
|
|
105
|
+
readonly localRoutines: readonly AgentWorkspaceLocalLibraryItem[];
|
|
72
106
|
readonly localSkillCount: number;
|
|
73
107
|
readonly enabledSkillCount: number;
|
|
108
|
+
readonly localSkills: readonly AgentWorkspaceLocalLibraryItem[];
|
|
74
109
|
readonly localPersonaCount: number;
|
|
75
110
|
readonly activePersonaName: string;
|
|
111
|
+
readonly localPersonas: readonly AgentWorkspaceLocalLibraryItem[];
|
|
76
112
|
readonly knowledgeRoute: '/api/goodvibes-agent/knowledge';
|
|
77
113
|
readonly knowledgeIsolation: 'agent-only';
|
|
78
114
|
readonly executionPolicy: 'serial-proactive';
|
|
@@ -143,6 +179,46 @@ function inferActiveRuntimeProfile(homeDirectory: string): string {
|
|
|
143
179
|
return homeDirectory.includes(marker) ? basename(homeDirectory) : '(default home)';
|
|
144
180
|
}
|
|
145
181
|
|
|
182
|
+
function summarizePersonaItem(persona: AgentPersonaRecord, activePersonaId: string | null): AgentWorkspaceLocalLibraryItem {
|
|
183
|
+
return {
|
|
184
|
+
id: persona.id,
|
|
185
|
+
name: persona.name,
|
|
186
|
+
description: persona.description,
|
|
187
|
+
reviewState: persona.reviewState,
|
|
188
|
+
source: persona.source,
|
|
189
|
+
tags: persona.tags,
|
|
190
|
+
triggers: persona.triggers,
|
|
191
|
+
active: persona.id === activePersonaId,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function summarizeSkillItem(skill: AgentSkillRecord): AgentWorkspaceLocalLibraryItem {
|
|
196
|
+
return {
|
|
197
|
+
id: skill.id,
|
|
198
|
+
name: skill.name,
|
|
199
|
+
description: skill.description,
|
|
200
|
+
reviewState: skill.reviewState,
|
|
201
|
+
source: skill.source,
|
|
202
|
+
tags: skill.tags,
|
|
203
|
+
triggers: skill.triggers,
|
|
204
|
+
enabled: skill.enabled,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function summarizeRoutineItem(routine: AgentRoutineRecord): AgentWorkspaceLocalLibraryItem {
|
|
209
|
+
return {
|
|
210
|
+
id: routine.id,
|
|
211
|
+
name: routine.name,
|
|
212
|
+
description: routine.description,
|
|
213
|
+
reviewState: routine.reviewState,
|
|
214
|
+
source: routine.source,
|
|
215
|
+
tags: routine.tags,
|
|
216
|
+
triggers: routine.triggers,
|
|
217
|
+
enabled: routine.enabled,
|
|
218
|
+
startCount: routine.startCount,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
146
222
|
export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): AgentWorkspaceRuntimeSnapshot {
|
|
147
223
|
const host = readConfigString(context, 'controlPlane.host', '127.0.0.1');
|
|
148
224
|
const port = readConfigNumber(context, 'controlPlane.port', 3421);
|
|
@@ -165,31 +241,43 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
165
241
|
const personaSnapshot = (() => {
|
|
166
242
|
try {
|
|
167
243
|
const shellPaths = context.workspace?.shellPaths;
|
|
168
|
-
if (!shellPaths) return { count: 0, activeName: '(none)' };
|
|
244
|
+
if (!shellPaths) return { count: 0, activeName: '(none)', items: [] };
|
|
169
245
|
const snapshot = AgentPersonaRegistry.fromShellPaths(shellPaths).snapshot();
|
|
170
|
-
return {
|
|
246
|
+
return {
|
|
247
|
+
count: snapshot.personas.length,
|
|
248
|
+
activeName: snapshot.activePersona?.name ?? '(none)',
|
|
249
|
+
items: snapshot.personas.map((persona) => summarizePersonaItem(persona, snapshot.activePersonaId)),
|
|
250
|
+
};
|
|
171
251
|
} catch {
|
|
172
|
-
return { count: 0, activeName: '(unavailable)' };
|
|
252
|
+
return { count: 0, activeName: '(unavailable)', items: [] };
|
|
173
253
|
}
|
|
174
254
|
})();
|
|
175
255
|
const skillSnapshot = (() => {
|
|
176
256
|
try {
|
|
177
257
|
const shellPaths = context.workspace?.shellPaths;
|
|
178
|
-
if (!shellPaths) return { count: 0, enabled: 0 };
|
|
258
|
+
if (!shellPaths) return { count: 0, enabled: 0, items: [] };
|
|
179
259
|
const snapshot = AgentSkillRegistry.fromShellPaths(shellPaths).snapshot();
|
|
180
|
-
return {
|
|
260
|
+
return {
|
|
261
|
+
count: snapshot.skills.length,
|
|
262
|
+
enabled: snapshot.enabledSkills.length,
|
|
263
|
+
items: snapshot.skills.map(summarizeSkillItem),
|
|
264
|
+
};
|
|
181
265
|
} catch {
|
|
182
|
-
return { count: 0, enabled: 0 };
|
|
266
|
+
return { count: 0, enabled: 0, items: [] };
|
|
183
267
|
}
|
|
184
268
|
})();
|
|
185
269
|
const routineSnapshot = (() => {
|
|
186
270
|
try {
|
|
187
271
|
const shellPaths = context.workspace?.shellPaths;
|
|
188
|
-
if (!shellPaths) return { count: 0, enabled: 0 };
|
|
272
|
+
if (!shellPaths) return { count: 0, enabled: 0, items: [] };
|
|
189
273
|
const snapshot = AgentRoutineRegistry.fromShellPaths(shellPaths).snapshot();
|
|
190
|
-
return {
|
|
274
|
+
return {
|
|
275
|
+
count: snapshot.routines.length,
|
|
276
|
+
enabled: snapshot.enabledRoutines.length,
|
|
277
|
+
items: snapshot.routines.map(summarizeRoutineItem),
|
|
278
|
+
};
|
|
191
279
|
} catch {
|
|
192
|
-
return { count: 0, enabled: 0 };
|
|
280
|
+
return { count: 0, enabled: 0, items: [] };
|
|
193
281
|
}
|
|
194
282
|
})();
|
|
195
283
|
const runtimeProfiles = (() => {
|
|
@@ -265,10 +353,13 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
265
353
|
sessionMemoryCount,
|
|
266
354
|
localRoutineCount: routineSnapshot.count,
|
|
267
355
|
enabledRoutineCount: routineSnapshot.enabled,
|
|
356
|
+
localRoutines: routineSnapshot.items,
|
|
268
357
|
localSkillCount: skillSnapshot.count,
|
|
269
358
|
enabledSkillCount: skillSnapshot.enabled,
|
|
359
|
+
localSkills: skillSnapshot.items,
|
|
270
360
|
localPersonaCount: personaSnapshot.count,
|
|
271
361
|
activePersonaName: personaSnapshot.activeName,
|
|
362
|
+
localPersonas: personaSnapshot.items,
|
|
272
363
|
knowledgeRoute: '/api/goodvibes-agent/knowledge',
|
|
273
364
|
knowledgeIsolation: 'agent-only',
|
|
274
365
|
executionPolicy: 'serial-proactive',
|
|
@@ -329,9 +420,9 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
329
420
|
{ id: 'setup-provider-model', label: 'Provider and model', detail: 'Choose the provider/model route for normal assistant chat.', command: '/model', kind: 'command', safety: 'safe' },
|
|
330
421
|
{ id: 'setup-agent-knowledge', label: 'Agent Knowledge', detail: 'Inspect the isolated Agent Knowledge store before ingesting source-backed material.', command: '/knowledge status', kind: 'command', safety: 'read-only' },
|
|
331
422
|
{ id: 'setup-runtime-profiles', label: 'Runtime profiles', detail: 'Browse starter templates for isolated Agent homes and operator identities.', command: '/agent-profile templates', kind: 'command', safety: 'read-only' },
|
|
332
|
-
{ id: 'setup-personas', label: 'Personas', detail: 'Create or select the active local Agent persona.',
|
|
333
|
-
{ id: 'setup-skills', label: 'Skills', detail: 'Create, review, and enable reusable local Agent skills.',
|
|
334
|
-
{ id: 'setup-routines', label: 'Routines', detail: 'Create, review, and enable local Agent routines before any explicit schedule promotion.',
|
|
423
|
+
{ id: 'setup-personas', label: 'Personas', detail: 'Create or select the active local Agent persona.', targetCategoryId: 'personas', kind: 'workspace', safety: 'safe' },
|
|
424
|
+
{ id: 'setup-skills', label: 'Skills', detail: 'Create, review, and enable reusable local Agent skills.', targetCategoryId: 'skills', kind: 'workspace', safety: 'safe' },
|
|
425
|
+
{ id: 'setup-routines', label: 'Routines', detail: 'Create, review, and enable local Agent routines before any explicit schedule promotion.', targetCategoryId: 'routines', kind: 'workspace', safety: 'safe' },
|
|
335
426
|
{ id: 'setup-memory', label: 'Local memory', detail: 'Inspect local/session memory; secrets stay rejected or redacted.', command: '/memory', kind: 'command', safety: 'read-only' },
|
|
336
427
|
{ id: 'setup-channels', label: 'Channels', detail: 'Open companion pairing and channel readiness setup.', command: '/pair', kind: 'command', safety: 'safe' },
|
|
337
428
|
{ id: 'setup-voice-media', label: 'Voice and media', detail: 'Open TTS/media settings for voice and image-capable Agent flows.', command: '/config tts', kind: 'command', safety: 'safe' },
|
|
@@ -410,9 +501,53 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
410
501
|
detail: 'Memory, routines, skills, and personas stay Agent-local until stable shared daemon registry contracts exist. Secrets must not be stored as memory.',
|
|
411
502
|
actions: [
|
|
412
503
|
{ id: 'memory', label: 'Open memory', detail: 'Inspect local/session memory commands and surfaces.', command: '/memory', kind: 'command', safety: 'read-only' },
|
|
413
|
-
{ id: '
|
|
414
|
-
{ id: 'skills', label: 'Local skill library', detail: '
|
|
415
|
-
{ id: '
|
|
504
|
+
{ id: 'personas', label: 'Persona library', detail: 'Open the local persona workspace for active role selection and review.', targetCategoryId: 'personas', kind: 'workspace', safety: 'safe' },
|
|
505
|
+
{ id: 'skills', label: 'Local skill library', detail: 'Open the local skill workspace for reusable procedures and review.', targetCategoryId: 'skills', kind: 'workspace', safety: 'safe' },
|
|
506
|
+
{ id: 'routines', label: 'Routine library', detail: 'Open the local routine workspace for repeatable workflows and schedule promotion review.', targetCategoryId: 'routines', kind: 'workspace', safety: 'safe' },
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
id: 'personas',
|
|
511
|
+
group: 'LEARN',
|
|
512
|
+
label: 'Personas',
|
|
513
|
+
summary: 'Local behavior profiles for the main assistant.',
|
|
514
|
+
detail: 'Personas shape the serial Agent in the main conversation. They are not background agents and they never spawn specialist roots.',
|
|
515
|
+
actions: [
|
|
516
|
+
{ id: 'personas-list', label: 'List personas', detail: 'Print the full local persona library.', command: '/personas list', kind: 'command', safety: 'read-only' },
|
|
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' },
|
|
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' },
|
|
519
|
+
{ id: 'personas-use', label: 'Use persona', detail: 'Activate a local persona by id or name.', command: '/personas use <id>', kind: 'command', safety: 'safe' },
|
|
520
|
+
{ id: 'personas-review', label: 'Review persona', detail: 'Mark a local persona reviewed after inspecting it.', command: '/personas review <id>', kind: 'command', safety: 'safe' },
|
|
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' },
|
|
522
|
+
],
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
id: 'skills',
|
|
526
|
+
group: 'LEARN',
|
|
527
|
+
label: 'Skills',
|
|
528
|
+
summary: 'Reusable local procedures the assistant can apply on demand.',
|
|
529
|
+
detail: 'Skills are local, reviewable procedures. Enabled skills inform the main conversation; secret-looking content is rejected.',
|
|
530
|
+
actions: [
|
|
531
|
+
{ id: 'skills-list', label: 'List skills', detail: 'Print the full local Agent skill library.', command: '/agent-skills list', kind: 'command', safety: 'read-only' },
|
|
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' },
|
|
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' },
|
|
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' },
|
|
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' },
|
|
536
|
+
],
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
id: 'routines',
|
|
540
|
+
group: 'LEARN',
|
|
541
|
+
label: 'Routines',
|
|
542
|
+
summary: 'Repeatable workflows for the main conversation.',
|
|
543
|
+
detail: 'Routines run in the main conversation by default. Promotion to an external daemon schedule requires a real schedule command and --yes.',
|
|
544
|
+
actions: [
|
|
545
|
+
{ id: 'routines-list', label: 'List routines', detail: 'Print the full local Agent routine library.', command: '/routines list', kind: 'command', safety: 'read-only' },
|
|
546
|
+
{ id: 'routines-enabled', label: 'Enabled routines', detail: 'Show routines available for direct use.', command: '/routines enabled', kind: 'command', safety: 'read-only' },
|
|
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' },
|
|
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' },
|
|
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' },
|
|
550
|
+
{ id: 'routines-receipts', label: 'Promotion receipts', detail: 'Inspect local redacted routine schedule promotion receipts.', command: '/routines receipts', kind: 'command', safety: 'read-only' },
|
|
416
551
|
],
|
|
417
552
|
},
|
|
418
553
|
{
|
|
@@ -463,6 +598,70 @@ function parseCommand(command: string): { readonly name: string; readonly args:
|
|
|
463
598
|
return { name: parts[0] ?? '', args: parts.slice(1) };
|
|
464
599
|
}
|
|
465
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
|
+
|
|
466
665
|
export class AgentWorkspace {
|
|
467
666
|
public active = false;
|
|
468
667
|
public focusPane: AgentWorkspaceFocusPane = 'actions';
|
|
@@ -471,6 +670,7 @@ export class AgentWorkspace {
|
|
|
471
670
|
public status = 'Ready. Choose an operator flow; ordinary assistant work stays in the main conversation.';
|
|
472
671
|
public runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null = null;
|
|
473
672
|
public lastActionResult: AgentWorkspaceActionResult | null = null;
|
|
673
|
+
public localEditor: AgentWorkspaceLocalEditor | null = null;
|
|
474
674
|
private context: CommandContext | null = null;
|
|
475
675
|
private dispatchCommand: AgentWorkspaceCommandDispatcher | null = null;
|
|
476
676
|
|
|
@@ -482,6 +682,7 @@ export class AgentWorkspace {
|
|
|
482
682
|
this.focusPane = 'actions';
|
|
483
683
|
this.status = 'Ready. Choose an operator flow; ordinary assistant work stays in the main conversation.';
|
|
484
684
|
this.lastActionResult = null;
|
|
685
|
+
this.localEditor = null;
|
|
485
686
|
this.clampSelection();
|
|
486
687
|
}
|
|
487
688
|
|
|
@@ -492,6 +693,7 @@ export class AgentWorkspace {
|
|
|
492
693
|
|
|
493
694
|
close(): void {
|
|
494
695
|
this.active = false;
|
|
696
|
+
this.localEditor = null;
|
|
495
697
|
}
|
|
496
698
|
|
|
497
699
|
get categories(): readonly AgentWorkspaceCategory[] {
|
|
@@ -573,13 +775,86 @@ export class AgentWorkspace {
|
|
|
573
775
|
};
|
|
574
776
|
}
|
|
575
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
|
+
|
|
576
836
|
activateSelected(): void {
|
|
837
|
+
if (this.localEditor) {
|
|
838
|
+
this.submitEditorFieldOrForm();
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
577
841
|
if (this.focusPane === 'categories') {
|
|
578
842
|
this.focusActions();
|
|
579
843
|
return;
|
|
580
844
|
}
|
|
581
845
|
const action = this.selectedAction;
|
|
582
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
|
+
}
|
|
583
858
|
if (action.kind === 'guidance' || !action.command) {
|
|
584
859
|
if (action.kind === 'workspace' && action.targetCategoryId) {
|
|
585
860
|
const targetIndex = this.categories.findIndex((category) => category.id === action.targetCategoryId);
|
|
@@ -674,6 +949,121 @@ export class AgentWorkspace {
|
|
|
674
949
|
this.selectedCategoryIndex = Math.max(0, Math.min(this.selectedCategoryIndex, this.categories.length - 1));
|
|
675
950
|
this.selectedActionIndex = Math.max(0, Math.min(this.selectedActionIndex, this.actions.length - 1));
|
|
676
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
|
+
}
|
|
677
1067
|
}
|
|
678
1068
|
|
|
679
1069
|
export function handleAgentWorkspaceToken(
|
|
@@ -684,6 +1074,21 @@ export function handleAgentWorkspaceToken(
|
|
|
684
1074
|
): boolean {
|
|
685
1075
|
if (!workspace.active) return false;
|
|
686
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
|
+
|
|
687
1092
|
if (token.type === 'key') {
|
|
688
1093
|
if (token.logicalName === 'escape') {
|
|
689
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
|
|
|
@@ -100,6 +102,40 @@ function setupChecklistLines(snapshot: AgentWorkspaceRuntimeSnapshot): ContextLi
|
|
|
100
102
|
return lines;
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
function localLibraryLines(
|
|
106
|
+
title: string,
|
|
107
|
+
items: readonly AgentWorkspaceRuntimeSnapshot['localPersonas'][number][],
|
|
108
|
+
emptyText: string,
|
|
109
|
+
): ContextLine[] {
|
|
110
|
+
const lines: ContextLine[] = [
|
|
111
|
+
{ text: title, fg: PALETTE.title, bold: true },
|
|
112
|
+
];
|
|
113
|
+
if (items.length === 0) {
|
|
114
|
+
lines.push({ text: emptyText, fg: PALETTE.warn });
|
|
115
|
+
return lines;
|
|
116
|
+
}
|
|
117
|
+
for (const item of items.slice(0, 8)) {
|
|
118
|
+
const status = [
|
|
119
|
+
item.active ? 'active' : '',
|
|
120
|
+
item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
|
|
121
|
+
item.reviewState,
|
|
122
|
+
item.startCount !== undefined ? `starts ${item.startCount}` : '',
|
|
123
|
+
].filter(Boolean).join(' / ');
|
|
124
|
+
const tags = item.tags.length > 0 ? ` tags=${item.tags.join(',')}` : '';
|
|
125
|
+
const triggers = item.triggers.length > 0 ? ` triggers=${item.triggers.join(',')}` : '';
|
|
126
|
+
lines.push({
|
|
127
|
+
text: `${item.id}: ${item.name} (${status})`,
|
|
128
|
+
fg: item.reviewState === 'stale' ? PALETTE.warn : PALETTE.info,
|
|
129
|
+
bold: item.active === true,
|
|
130
|
+
});
|
|
131
|
+
lines.push({ text: ` ${item.description}${tags}${triggers}`, fg: PALETTE.muted });
|
|
132
|
+
}
|
|
133
|
+
if (items.length > 8) {
|
|
134
|
+
lines.push({ text: `${items.length - 8} more item(s). Open the library command for the full list.`, fg: PALETTE.dim });
|
|
135
|
+
}
|
|
136
|
+
return lines;
|
|
137
|
+
}
|
|
138
|
+
|
|
103
139
|
function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspaceRuntimeSnapshot | null): ContextLine[] {
|
|
104
140
|
if (!snapshot) return [{ text: 'Runtime context is not loaded yet.', fg: PALETTE.warn }];
|
|
105
141
|
const base: ContextLine[] = [{ text: 'Live Agent Context', fg: PALETTE.title, bold: true }];
|
|
@@ -178,6 +214,30 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
178
214
|
{ text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
179
215
|
{ text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
|
|
180
216
|
);
|
|
217
|
+
} else if (category.id === 'personas') {
|
|
218
|
+
base.push(
|
|
219
|
+
{ text: `Personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
|
|
220
|
+
{ text: 'Personas are local behavior profiles for the serial main-conversation assistant, not spawned agents.', fg: PALETTE.good },
|
|
221
|
+
{ text: 'Use them for tone, role, domain constraints, tool posture, and repeatable operating preferences.', fg: PALETTE.muted },
|
|
222
|
+
{ text: '' },
|
|
223
|
+
...localLibraryLines('Persona Library', snapshot.localPersonas, 'No local personas yet. Create one with /personas create ...'),
|
|
224
|
+
);
|
|
225
|
+
} else if (category.id === 'skills') {
|
|
226
|
+
base.push(
|
|
227
|
+
{ text: `Skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
|
|
228
|
+
{ text: 'Skills are reusable local procedures the assistant can apply from the main conversation.', fg: PALETTE.good },
|
|
229
|
+
{ text: 'Enabled skills are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
|
|
230
|
+
{ text: '' },
|
|
231
|
+
...localLibraryLines('Skill Library', snapshot.localSkills, 'No local skills yet. Create one with /agent-skills create ...'),
|
|
232
|
+
);
|
|
233
|
+
} else if (category.id === 'routines') {
|
|
234
|
+
base.push(
|
|
235
|
+
{ text: `Routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
|
|
236
|
+
{ text: 'Routines are repeatable main-conversation workflows. Starting one does not create hidden jobs.', fg: PALETTE.good },
|
|
237
|
+
{ text: 'Scheduling a reviewed routine is explicit and writes to the externally owned daemon only with --yes.', fg: PALETTE.warn },
|
|
238
|
+
{ text: '' },
|
|
239
|
+
...localLibraryLines('Routine Library', snapshot.localRoutines, 'No local routines yet. Create one with /routines create ...'),
|
|
240
|
+
);
|
|
181
241
|
} else if (category.id === 'work') {
|
|
182
242
|
base.push(
|
|
183
243
|
{ text: 'Work plan and approvals are read or explicitly confirmed through public operator routes.', fg: PALETTE.info },
|
|
@@ -202,6 +262,23 @@ function snapshotLines(category: AgentWorkspaceCategory, snapshot: AgentWorkspac
|
|
|
202
262
|
return base;
|
|
203
263
|
}
|
|
204
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
|
+
|
|
205
282
|
function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCategory, action: AgentWorkspaceAction | null, width: number): WorkspaceRow[] {
|
|
206
283
|
const lines: ContextLine[] = [
|
|
207
284
|
{ text: category.label, fg: PALETTE.title, bold: true },
|
|
@@ -209,6 +286,8 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
|
|
|
209
286
|
{ text: '' },
|
|
210
287
|
{ text: category.detail, fg: PALETTE.text },
|
|
211
288
|
{ text: '' },
|
|
289
|
+
...(workspace.localEditor ? editorContextLines(workspace.localEditor) : []),
|
|
290
|
+
...(workspace.localEditor ? [{ text: '' }] : []),
|
|
212
291
|
...snapshotLines(category, workspace.runtimeSnapshot),
|
|
213
292
|
];
|
|
214
293
|
|
|
@@ -245,7 +324,42 @@ function buildContextRows(workspace: AgentWorkspace, category: AgentWorkspaceCat
|
|
|
245
324
|
});
|
|
246
325
|
}
|
|
247
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
|
+
|
|
248
361
|
function buildActionRows(workspace: AgentWorkspace, width: number, height: number): WorkspaceRow[] {
|
|
362
|
+
if (workspace.localEditor) return buildEditorRows(workspace.localEditor, width, height);
|
|
249
363
|
const rows: WorkspaceRow[] = [];
|
|
250
364
|
const labelWidth = Math.min(28, Math.max(16, Math.floor(width * 0.30)));
|
|
251
365
|
const safetyWidth = 10;
|
|
@@ -292,6 +406,9 @@ function buildActionRows(workspace: AgentWorkspace, width: number, height: numbe
|
|
|
292
406
|
}
|
|
293
407
|
|
|
294
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
|
+
}
|
|
295
412
|
const focus = workspace.focusPane === 'categories' ? 'categories' : 'actions';
|
|
296
413
|
return `Agent workspace · focus ${focus} · Up/Down navigate · Left/Right pane · Enter open/action · R refresh · Esc close`;
|
|
297
414
|
}
|
|
@@ -313,7 +430,7 @@ export function renderAgentWorkspace(workspace: AgentWorkspace, width: number, h
|
|
|
313
430
|
width,
|
|
314
431
|
height,
|
|
315
432
|
title: 'GoodVibes Agent / Operator Workspace',
|
|
316
|
-
stateLabel: workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
|
|
433
|
+
stateLabel: workspace.localEditor ? 'Editor' : workspace.focusPane === 'categories' ? 'Categories' : 'Actions',
|
|
317
434
|
leftHeader: 'Operator Areas',
|
|
318
435
|
mainHeader: `${category.label} · ${category.actions.length} action(s)`,
|
|
319
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 {
|