@pellux/goodvibes-agent 0.1.103 → 0.1.105
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/agent/memory-safety.ts +16 -0
- package/src/cli/memory-command.ts +3 -19
- package/src/input/agent-workspace-activation.ts +170 -0
- package/src/input/agent-workspace-categories.ts +8 -1
- package/src/input/agent-workspace-editors.ts +36 -0
- package/src/input/agent-workspace-memory-editor.ts +88 -0
- package/src/input/agent-workspace-setup.ts +5 -3
- package/src/input/agent-workspace-snapshot.ts +36 -0
- package/src/input/agent-workspace-token.ts +51 -0
- package/src/input/agent-workspace-types.ts +11 -1
- package/src/input/agent-workspace.ts +130 -185
- package/src/panels/builtin/usage.ts +21 -0
- package/src/panels/builtin-panels.ts +2 -2
- package/src/panels/panel-list-panel.ts +1 -2
- package/src/panels/panel-picker.ts +1 -2
- package/src/panels/types.ts +1 -1
- package/src/renderer/agent-workspace.ts +5 -0
- package/src/version.ts +1 -1
- package/src/panels/builtin/development.ts +0 -31
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.105 - 2026-06-01
|
|
6
|
+
|
|
7
|
+
- 7ea88c8 Remove development panel surface
|
|
8
|
+
|
|
9
|
+
## 0.1.104 - 2026-06-01
|
|
10
|
+
|
|
11
|
+
- 37fe253 Add Agent memory workspace controls
|
|
12
|
+
|
|
5
13
|
## 0.1.103 - 2026-06-01
|
|
6
14
|
|
|
7
15
|
- ed9895a Add CLI controls for Agent memory
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.105",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const SECRET_PATTERNS: readonly RegExp[] = [
|
|
2
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,
|
|
3
|
+
/\bsk-[A-Za-z0-9_-]{16,}\b/,
|
|
4
|
+
/\bgh[pousr]_[A-Za-z0-9_]{16,}\b/i,
|
|
5
|
+
/\b(?:password|passwd|api[_-]?key|token|secret)\s*[:=]\s*\S{6,}/i,
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
export function containsSecretLikeText(text: string): boolean {
|
|
9
|
+
return SECRET_PATTERNS.some((pattern) => pattern.test(text));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function assertNoSecretLikeMemoryText(fields: readonly string[]): void {
|
|
13
|
+
if (fields.some((field) => containsSecretLikeText(field))) {
|
|
14
|
+
throw new Error('Agent memory cannot store secret-looking values. Store a secret reference or remove the sensitive text.');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type ProvenanceLink,
|
|
17
17
|
type ProvenanceLinkKind,
|
|
18
18
|
} from '@pellux/goodvibes-sdk/platform/state';
|
|
19
|
+
import { assertNoSecretLikeMemoryText } from '../agent/memory-safety.ts';
|
|
19
20
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
20
21
|
import type { CliCommandOutput } from './types.ts';
|
|
21
22
|
import type { CliCommandRuntime } from './management.ts';
|
|
@@ -40,13 +41,6 @@ const VALUE_OPTIONS = new Set([
|
|
|
40
41
|
'task',
|
|
41
42
|
'turn',
|
|
42
43
|
]);
|
|
43
|
-
const SECRET_PATTERNS: readonly RegExp[] = [
|
|
44
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----/i,
|
|
45
|
-
/\bsk-[A-Za-z0-9_-]{16,}\b/,
|
|
46
|
-
/\bgh[pousr]_[A-Za-z0-9_]{16,}\b/i,
|
|
47
|
-
/\b(?:password|passwd|api[_-]?key|token|secret)\s*[:=]\s*\S{6,}/i,
|
|
48
|
-
];
|
|
49
|
-
|
|
50
44
|
interface CommandSuccess<TData> {
|
|
51
45
|
readonly ok: true;
|
|
52
46
|
readonly kind: string;
|
|
@@ -197,16 +191,6 @@ function optionalClass(value: string | undefined): MemoryClass | undefined {
|
|
|
197
191
|
return requireClass(value);
|
|
198
192
|
}
|
|
199
193
|
|
|
200
|
-
function containsSecretLikeText(text: string): boolean {
|
|
201
|
-
return SECRET_PATTERNS.some((pattern) => pattern.test(text));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function assertNoSecretLikeText(fields: readonly string[]): void {
|
|
205
|
-
if (fields.some((field) => containsSecretLikeText(field))) {
|
|
206
|
-
throw new Error('Agent memory cannot store secret-looking values. Store a secret reference or remove the sensitive text.');
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
194
|
function timestamp(value: number): string {
|
|
211
195
|
return new Date(value).toISOString().slice(0, 19).replace('T', ' ');
|
|
212
196
|
}
|
|
@@ -385,7 +369,7 @@ function readBundle(path: string): MemoryBundle {
|
|
|
385
369
|
const parsed: unknown = JSON.parse(readFileSync(path, 'utf-8'));
|
|
386
370
|
if (!isMemoryBundle(parsed)) throw new Error('Invalid Agent memory bundle.');
|
|
387
371
|
for (const record of parsed.records) {
|
|
388
|
-
|
|
372
|
+
assertNoSecretLikeMemoryText([
|
|
389
373
|
record.summary,
|
|
390
374
|
record.detail ?? '',
|
|
391
375
|
...record.tags,
|
|
@@ -454,7 +438,7 @@ async function handleAdd(runtime: CliCommandRuntime, context: MemoryContext, arg
|
|
|
454
438
|
if (!summary) return failure(runtime, 'invalid_memory_command', 'Usage: goodvibes-agent memory add <class> <summary> [--scope <scope>] [--detail <text>] [--tags a,b]', 2);
|
|
455
439
|
const detail = optionValue(options, 'detail');
|
|
456
440
|
const tags = csvOption(options, 'tags');
|
|
457
|
-
|
|
441
|
+
assertNoSecretLikeMemoryText([summary, detail ?? '', ...(tags ?? [])]);
|
|
458
442
|
const reviewState = optionValue(options, 'review-state');
|
|
459
443
|
if (reviewState !== undefined && !isReviewState(reviewState)) {
|
|
460
444
|
return failure(runtime, 'invalid_memory_command', `Invalid review state "${reviewState}". Valid: ${VALID_REVIEW_STATES.join(', ')}`, 2);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createLocalEditor, createProfileEditor } from './agent-workspace-editors.ts';
|
|
2
|
+
import type {
|
|
3
|
+
AgentWorkspaceActionResult,
|
|
4
|
+
AgentWorkspaceCategory,
|
|
5
|
+
AgentWorkspaceCommandDispatcher,
|
|
6
|
+
AgentWorkspaceFocusPane,
|
|
7
|
+
AgentWorkspaceLocalEditor,
|
|
8
|
+
AgentWorkspaceLocalEditorKind,
|
|
9
|
+
AgentWorkspaceLocalOperation,
|
|
10
|
+
AgentWorkspaceRuntimeSnapshot,
|
|
11
|
+
} from './agent-workspace-types.ts';
|
|
12
|
+
|
|
13
|
+
interface AgentWorkspaceActivationHost {
|
|
14
|
+
readonly categories: readonly AgentWorkspaceCategory[];
|
|
15
|
+
readonly selectedCategory: AgentWorkspaceCategory;
|
|
16
|
+
readonly selectedAction: AgentWorkspaceCategory['actions'][number] | null;
|
|
17
|
+
readonly runtimeSnapshot: AgentWorkspaceRuntimeSnapshot | null;
|
|
18
|
+
localEditor: AgentWorkspaceLocalEditor | null;
|
|
19
|
+
focusPane: AgentWorkspaceFocusPane;
|
|
20
|
+
selectedCategoryIndex: number;
|
|
21
|
+
selectedActionIndex: number;
|
|
22
|
+
status: string;
|
|
23
|
+
lastActionResult: AgentWorkspaceActionResult | null;
|
|
24
|
+
submitEditorFieldOrForm(requestRender?: () => void): void;
|
|
25
|
+
focusActions(): void;
|
|
26
|
+
clampSelection(): void;
|
|
27
|
+
moveLocalLibraryItemSelection(kind: AgentWorkspaceLocalEditorKind, delta: number): void;
|
|
28
|
+
applyLocalLibraryOperation(operation: AgentWorkspaceLocalOperation): void;
|
|
29
|
+
hasCommandDispatch(): boolean;
|
|
30
|
+
dispatchWorkspaceCommand: AgentWorkspaceCommandDispatcher;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseCommand(command: string): { readonly name: string; readonly args: readonly string[] } {
|
|
34
|
+
const trimmed = command.trim().replace(/^\//, '');
|
|
35
|
+
if (!trimmed) return { name: '', args: [] };
|
|
36
|
+
const parts = trimmed.split(/\s+/);
|
|
37
|
+
return { name: parts[0] ?? '', args: parts.slice(1) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function activateAgentWorkspaceSelection(
|
|
41
|
+
workspace: AgentWorkspaceActivationHost,
|
|
42
|
+
requestRender?: () => void,
|
|
43
|
+
): void {
|
|
44
|
+
if (workspace.localEditor) {
|
|
45
|
+
workspace.submitEditorFieldOrForm(requestRender);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (workspace.focusPane === 'categories') {
|
|
49
|
+
workspace.focusActions();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const action = workspace.selectedAction;
|
|
53
|
+
if (!action) return;
|
|
54
|
+
if (action.kind === 'editor' && action.editorKind) {
|
|
55
|
+
workspace.localEditor = action.editorKind === 'profile'
|
|
56
|
+
? createProfileEditor(workspace.runtimeSnapshot?.runtimeStarterTemplates ?? [])
|
|
57
|
+
: createLocalEditor(action.editorKind);
|
|
58
|
+
workspace.status = `Editing ${workspace.localEditor.title}.`;
|
|
59
|
+
workspace.lastActionResult = {
|
|
60
|
+
kind: 'guidance',
|
|
61
|
+
title: workspace.localEditor.title,
|
|
62
|
+
detail: workspace.localEditor.message,
|
|
63
|
+
safety: action.safety,
|
|
64
|
+
};
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (action.kind === 'local-selection' && action.localKind) {
|
|
68
|
+
workspace.moveLocalLibraryItemSelection(action.localKind, action.selectionDelta ?? 0);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (action.kind === 'local-operation' && action.localOperation) {
|
|
72
|
+
workspace.applyLocalLibraryOperation(action.localOperation);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (action.kind === 'guidance' || !action.command) {
|
|
76
|
+
handleGuidanceOrWorkspaceAction(workspace, action);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (action.safety === 'blocked') {
|
|
80
|
+
workspace.status = `Blocked here: ${action.label}.`;
|
|
81
|
+
workspace.lastActionResult = {
|
|
82
|
+
kind: 'blocked',
|
|
83
|
+
title: `${action.label} is blocked in Agent`,
|
|
84
|
+
detail: action.detail,
|
|
85
|
+
command: action.command,
|
|
86
|
+
safety: action.safety,
|
|
87
|
+
};
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const parsed = parseCommand(action.command);
|
|
91
|
+
if (!parsed.name) {
|
|
92
|
+
workspace.status = `No command is configured for ${action.label}.`;
|
|
93
|
+
workspace.lastActionResult = {
|
|
94
|
+
kind: 'error',
|
|
95
|
+
title: 'Command unavailable',
|
|
96
|
+
detail: `No command is configured for ${action.label}.`,
|
|
97
|
+
safety: action.safety,
|
|
98
|
+
};
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (/<[^>\s]+(?:\s+[^>]*)?>/.test(action.command)) {
|
|
102
|
+
workspace.status = `Placeholder command not dispatched: ${action.command}.`;
|
|
103
|
+
workspace.lastActionResult = {
|
|
104
|
+
kind: 'guidance',
|
|
105
|
+
title: `${action.label} needs details`,
|
|
106
|
+
detail: 'This action is a command template. Close the workspace and run it with real task text instead of placeholder values.',
|
|
107
|
+
command: action.command,
|
|
108
|
+
safety: action.safety,
|
|
109
|
+
};
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!workspace.hasCommandDispatch()) {
|
|
113
|
+
workspace.status = `Command dispatch is not available for ${action.command}.`;
|
|
114
|
+
workspace.lastActionResult = {
|
|
115
|
+
kind: 'error',
|
|
116
|
+
title: 'Command dispatch unavailable',
|
|
117
|
+
detail: `The command ${action.command} cannot be opened from this runtime.`,
|
|
118
|
+
command: action.command,
|
|
119
|
+
safety: action.safety,
|
|
120
|
+
};
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
workspace.status = `Opening ${action.command}.`;
|
|
124
|
+
workspace.lastActionResult = {
|
|
125
|
+
kind: 'dispatched',
|
|
126
|
+
title: `Opening ${action.label}`,
|
|
127
|
+
detail: 'The workspace handed this safe or read-only command to the shell-owned command router.',
|
|
128
|
+
command: action.command,
|
|
129
|
+
safety: action.safety,
|
|
130
|
+
};
|
|
131
|
+
workspace.dispatchWorkspaceCommand(action.command);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function handleGuidanceOrWorkspaceAction(
|
|
135
|
+
workspace: AgentWorkspaceActivationHost,
|
|
136
|
+
action: AgentWorkspaceCategory['actions'][number],
|
|
137
|
+
): void {
|
|
138
|
+
if (action.kind === 'workspace' && action.targetCategoryId) {
|
|
139
|
+
const targetIndex = workspace.categories.findIndex((category) => category.id === action.targetCategoryId);
|
|
140
|
+
if (targetIndex >= 0) {
|
|
141
|
+
workspace.selectedCategoryIndex = targetIndex;
|
|
142
|
+
workspace.selectedActionIndex = 0;
|
|
143
|
+
workspace.focusActions();
|
|
144
|
+
workspace.status = `Opened ${workspace.selectedCategory.label}.`;
|
|
145
|
+
workspace.lastActionResult = {
|
|
146
|
+
kind: 'refreshed',
|
|
147
|
+
title: `Opened ${workspace.selectedCategory.label}`,
|
|
148
|
+
detail: action.detail,
|
|
149
|
+
safety: action.safety,
|
|
150
|
+
};
|
|
151
|
+
workspace.clampSelection();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
workspace.status = `Workspace area unavailable: ${action.targetCategoryId}.`;
|
|
155
|
+
workspace.lastActionResult = {
|
|
156
|
+
kind: 'error',
|
|
157
|
+
title: 'Workspace area unavailable',
|
|
158
|
+
detail: `No Agent workspace category exists for ${action.targetCategoryId}.`,
|
|
159
|
+
safety: action.safety,
|
|
160
|
+
};
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
workspace.status = action.detail;
|
|
164
|
+
workspace.lastActionResult = {
|
|
165
|
+
kind: 'guidance',
|
|
166
|
+
title: action.label,
|
|
167
|
+
detail: action.detail,
|
|
168
|
+
safety: action.safety,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -110,7 +110,14 @@ export const AGENT_WORKSPACE_CATEGORIES: readonly AgentWorkspaceCategory[] = [
|
|
|
110
110
|
summary: 'Local assistant memory, routines, skills, and reusable behavior.',
|
|
111
111
|
detail: 'Memory, routines, skills, and personas stay Agent-local until stable shared registry contracts exist. Secrets must not be stored as memory.',
|
|
112
112
|
actions: [
|
|
113
|
-
{ id: 'memory', label: '
|
|
113
|
+
{ id: 'memory-list', label: 'List memory', detail: 'Print the full Agent-owned memory list.', command: '/memory list', kind: 'command', safety: 'read-only' },
|
|
114
|
+
{ id: 'memory-prev', label: 'Previous memory', detail: 'Move the local memory selection up without changing review state.', localKind: 'memory', selectionDelta: -1, kind: 'local-selection', safety: 'safe' },
|
|
115
|
+
{ id: 'memory-next', label: 'Next memory', detail: 'Move the local memory selection down without changing review state.', localKind: 'memory', selectionDelta: 1, kind: 'local-selection', safety: 'safe' },
|
|
116
|
+
{ id: 'memory-create', label: 'Create memory', detail: 'Open an in-workspace form for a durable, non-secret Agent memory. No default wiki fallback is used.', editorKind: 'memory', kind: 'editor', safety: 'safe' },
|
|
117
|
+
{ id: 'memory-edit', label: 'Edit selected memory', detail: 'Open the selected Agent memory in an in-workspace editor.', localKind: 'memory', localOperation: 'memory-edit', kind: 'local-operation', safety: 'safe' },
|
|
118
|
+
{ id: 'memory-review', label: 'Review selected', detail: 'Mark the selected Agent memory reviewed after inspecting it.', localKind: 'memory', localOperation: 'memory-review', kind: 'local-operation', safety: 'safe' },
|
|
119
|
+
{ id: 'memory-stale', label: 'Mark selected stale', detail: 'Mark the selected Agent memory stale so it stops being trusted until reviewed.', localKind: 'memory', localOperation: 'memory-stale', kind: 'local-operation', safety: 'safe' },
|
|
120
|
+
{ id: 'memory-delete', label: 'Delete selected memory', detail: 'Open a confirmation form before deleting the selected Agent memory.', localKind: 'memory', localOperation: 'memory-delete', kind: 'local-operation', safety: 'safe' },
|
|
114
121
|
{ id: 'personas', label: 'Persona library', detail: 'Open the local persona workspace for active role selection and review.', targetCategoryId: 'personas', kind: 'workspace', safety: 'safe' },
|
|
115
122
|
{ id: 'skills', label: 'Local skill library', detail: 'Open the local skill workspace for reusable procedures and review.', targetCategoryId: 'skills', kind: 'workspace', safety: 'safe' },
|
|
116
123
|
{ id: 'routines', label: 'Routine library', detail: 'Open the local routine workspace for repeatable workflows and schedule promotion review.', targetCategoryId: 'routines', kind: 'workspace', safety: 'safe' },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentPersonaRecord } from '../agent/persona-registry.ts';
|
|
2
2
|
import type { AgentRoutineRecord } from '../agent/routine-registry.ts';
|
|
3
3
|
import type { AgentSkillRecord } from '../agent/skill-registry.ts';
|
|
4
|
+
import type { MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
|
|
4
5
|
import type {
|
|
5
6
|
AgentWorkspaceLocalEditor,
|
|
6
7
|
AgentWorkspaceLocalEditorKind,
|
|
@@ -31,6 +32,23 @@ export function createProfileEditor(templates: readonly AgentWorkspaceRuntimeSta
|
|
|
31
32
|
|
|
32
33
|
export function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWorkspaceLocalEditor {
|
|
33
34
|
if (kind === 'profile') return createProfileEditor([]);
|
|
35
|
+
if (kind === 'memory') {
|
|
36
|
+
return {
|
|
37
|
+
kind,
|
|
38
|
+
mode: 'create',
|
|
39
|
+
title: 'Create Memory',
|
|
40
|
+
selectedFieldIndex: 0,
|
|
41
|
+
message: 'Record a durable, non-secret Agent memory. This stays in the Agent-owned memory store and never writes to default Knowledge/Wiki.',
|
|
42
|
+
fields: [
|
|
43
|
+
{ id: 'cls', label: 'Class', value: 'fact', required: true, multiline: false, hint: 'fact, decision, constraint, incident, pattern, risk, runbook, architecture, or ownership.' },
|
|
44
|
+
{ id: 'scope', label: 'Scope', value: 'project', required: true, multiline: false, hint: 'session, project, or team.' },
|
|
45
|
+
{ id: 'summary', label: 'Summary', value: '', required: true, multiline: false, hint: 'One durable sentence. Do not store secrets.' },
|
|
46
|
+
{ id: 'detail', label: 'Detail', value: '', required: false, multiline: true, hint: 'Optional supporting detail. Ctrl-J inserts a new line.' },
|
|
47
|
+
{ id: 'tags', label: 'Tags', value: '', required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
48
|
+
{ id: 'confidence', label: 'Confidence', value: '80', required: false, multiline: false, hint: '0-100 confidence score.' },
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
34
52
|
if (kind === 'persona') {
|
|
35
53
|
return {
|
|
36
54
|
kind,
|
|
@@ -82,6 +100,23 @@ export function createLocalEditor(kind: AgentWorkspaceLocalEditorKind): AgentWor
|
|
|
82
100
|
};
|
|
83
101
|
}
|
|
84
102
|
|
|
103
|
+
export function createMemoryUpdateEditor(record: MemoryRecord): AgentWorkspaceLocalEditor {
|
|
104
|
+
return {
|
|
105
|
+
kind: 'memory',
|
|
106
|
+
mode: 'update',
|
|
107
|
+
recordId: record.id,
|
|
108
|
+
title: 'Edit Memory',
|
|
109
|
+
selectedFieldIndex: 0,
|
|
110
|
+
message: `Editing ${record.id}. Saving updates only the Agent-owned memory record.`,
|
|
111
|
+
fields: [
|
|
112
|
+
{ id: 'scope', label: 'Scope', value: record.scope, required: true, multiline: false, hint: 'session, project, or team.' },
|
|
113
|
+
{ id: 'summary', label: 'Summary', value: record.summary, required: true, multiline: false, hint: 'One durable sentence. Do not store secrets.' },
|
|
114
|
+
{ id: 'detail', label: 'Detail', value: record.detail ?? '', required: false, multiline: true, hint: 'Optional supporting detail. Ctrl-J inserts a new line.' },
|
|
115
|
+
{ id: 'tags', label: 'Tags', value: record.tags.join(', '), required: false, multiline: false, hint: 'Comma-separated optional tags.' },
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
85
120
|
export function createPersonaUpdateEditor(record: AgentPersonaRecord, active: boolean): AgentWorkspaceLocalEditor {
|
|
86
121
|
return {
|
|
87
122
|
kind: 'persona',
|
|
@@ -164,6 +199,7 @@ export function isAffirmative(value: string): boolean {
|
|
|
164
199
|
}
|
|
165
200
|
|
|
166
201
|
export function editorCategoryId(kind: AgentWorkspaceLocalEditorKind): string {
|
|
202
|
+
if (kind === 'memory') return 'memory';
|
|
167
203
|
if (kind === 'profile') return 'profiles';
|
|
168
204
|
if (kind === 'persona') return 'personas';
|
|
169
205
|
if (kind === 'skill') return 'skills';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { MemoryApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
2
|
+
import type { MemoryClass, MemoryRecord, MemoryScope } from '@pellux/goodvibes-sdk/platform/state';
|
|
3
|
+
import { assertNoSecretLikeMemoryText } from '../agent/memory-safety.ts';
|
|
4
|
+
import type { AgentWorkspaceLocalEditor } from './agent-workspace-types.ts';
|
|
5
|
+
import { splitList } from './agent-workspace-editors.ts';
|
|
6
|
+
import { isValidClass, isValidScope } from './commands/recall-shared.ts';
|
|
7
|
+
|
|
8
|
+
export type AgentWorkspaceEditorFieldReader = (id: string) => string;
|
|
9
|
+
|
|
10
|
+
export interface AgentWorkspaceMemoryEditorResult {
|
|
11
|
+
readonly record: MemoryRecord;
|
|
12
|
+
readonly verb: 'Created' | 'Updated';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AgentWorkspaceMemoryDeleteResult {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function submitAgentWorkspaceMemoryEditor(
|
|
21
|
+
editor: AgentWorkspaceLocalEditor,
|
|
22
|
+
memory: MemoryApi,
|
|
23
|
+
readField: AgentWorkspaceEditorFieldReader,
|
|
24
|
+
): Promise<AgentWorkspaceMemoryEditorResult> {
|
|
25
|
+
if (editor.mode === 'update' && editor.recordId) {
|
|
26
|
+
const scope = parseMemoryScope(readField('scope'));
|
|
27
|
+
const summary = readField('summary');
|
|
28
|
+
const detail = readField('detail');
|
|
29
|
+
const tags = splitList(readField('tags'));
|
|
30
|
+
assertNoSecretLikeMemoryText([summary, detail, ...tags]);
|
|
31
|
+
const updated = memory.update(editor.recordId, {
|
|
32
|
+
scope,
|
|
33
|
+
summary,
|
|
34
|
+
detail: detail.length > 0 ? detail : undefined,
|
|
35
|
+
tags,
|
|
36
|
+
});
|
|
37
|
+
if (!updated) throw new Error(`Unknown Agent memory: ${editor.recordId}`);
|
|
38
|
+
return { record: updated, verb: 'Updated' };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const cls = parseMemoryClass(readField('cls'));
|
|
42
|
+
const scope = parseMemoryScope(readField('scope'));
|
|
43
|
+
const summary = readField('summary');
|
|
44
|
+
const detail = readField('detail');
|
|
45
|
+
const tags = splitList(readField('tags'));
|
|
46
|
+
const confidence = parseMemoryConfidence(readField('confidence'));
|
|
47
|
+
assertNoSecretLikeMemoryText([summary, detail, ...tags]);
|
|
48
|
+
const record = await memory.add({
|
|
49
|
+
cls,
|
|
50
|
+
scope,
|
|
51
|
+
summary,
|
|
52
|
+
detail: detail.length > 0 ? detail : undefined,
|
|
53
|
+
tags,
|
|
54
|
+
review: {
|
|
55
|
+
state: 'fresh',
|
|
56
|
+
confidence,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return { record, verb: 'Created' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function deleteAgentWorkspaceMemoryEditor(
|
|
63
|
+
editor: AgentWorkspaceLocalEditor,
|
|
64
|
+
confirmedId: string,
|
|
65
|
+
memory: MemoryApi,
|
|
66
|
+
): AgentWorkspaceMemoryDeleteResult | null {
|
|
67
|
+
const expectedId = editor.recordId ?? '';
|
|
68
|
+
if (!expectedId || confirmedId !== expectedId) return null;
|
|
69
|
+
const removed = memory.delete(expectedId);
|
|
70
|
+
if (!removed) throw new Error(`Unknown Agent memory: ${expectedId}`);
|
|
71
|
+
return { id: expectedId, name: expectedId };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseMemoryClass(value: string): MemoryClass {
|
|
75
|
+
if (!isValidClass(value)) throw new Error(`Invalid memory class "${value}".`);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseMemoryScope(value: string): MemoryScope {
|
|
80
|
+
if (!isValidScope(value)) throw new Error(`Invalid memory scope "${value}".`);
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseMemoryConfidence(value: string): number {
|
|
85
|
+
const parsed = value.trim().length === 0 ? 80 : Number.parseInt(value, 10);
|
|
86
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 100) throw new Error('Memory confidence must be an integer from 0 to 100.');
|
|
87
|
+
return parsed;
|
|
88
|
+
}
|
|
@@ -13,6 +13,8 @@ export interface AgentWorkspaceSetupChecklistInput {
|
|
|
13
13
|
readonly model: string;
|
|
14
14
|
readonly runtimeBaseUrl: string;
|
|
15
15
|
readonly sessionMemoryCount: number;
|
|
16
|
+
readonly localMemoryCount: number;
|
|
17
|
+
readonly localMemoryReviewQueueCount: number;
|
|
16
18
|
readonly routineCount: number;
|
|
17
19
|
readonly enabledRoutineCount: number;
|
|
18
20
|
readonly skillCount: number;
|
|
@@ -97,9 +99,9 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
|
|
|
97
99
|
{
|
|
98
100
|
id: 'memory',
|
|
99
101
|
label: 'Local memory',
|
|
100
|
-
status: setupStatusForCount(input.
|
|
101
|
-
detail: input.
|
|
102
|
-
? `${input.
|
|
102
|
+
status: setupStatusForCount(input.localMemoryCount, 'ready', 'optional'),
|
|
103
|
+
detail: input.localMemoryCount > 0
|
|
104
|
+
? `${input.localMemoryCount} Agent memory record(s) are available; ${input.localMemoryReviewQueueCount} need review.`
|
|
103
105
|
: 'Memory starts empty; durable facts should be stored deliberately and never include secrets.',
|
|
104
106
|
command: '/memory',
|
|
105
107
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { basename, sep } from 'node:path';
|
|
2
|
+
import type { MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
|
|
2
3
|
import type { CommandContext } from './command-registry.ts';
|
|
3
4
|
import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
|
|
4
5
|
import { AgentRoutineRegistry, type AgentRoutineRecord } from '../agent/routine-registry.ts';
|
|
@@ -113,6 +114,22 @@ function summarizeRoutineItem(routine: AgentRoutineRecord): AgentWorkspaceLocalL
|
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
|
|
117
|
+
function summarizeMemoryItem(record: MemoryRecord): AgentWorkspaceLocalLibraryItem {
|
|
118
|
+
const detail = record.detail?.trim();
|
|
119
|
+
return {
|
|
120
|
+
id: record.id,
|
|
121
|
+
name: record.summary,
|
|
122
|
+
description: detail && detail.length > 0 ? detail : `${record.scope}/${record.cls}`,
|
|
123
|
+
reviewState: record.reviewState,
|
|
124
|
+
source: 'agent-memory',
|
|
125
|
+
tags: record.tags,
|
|
126
|
+
triggers: [],
|
|
127
|
+
scope: record.scope,
|
|
128
|
+
cls: record.cls,
|
|
129
|
+
confidence: record.confidence,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
116
133
|
function summarizeRuntimeProfile(profile: ReturnType<typeof listAgentRuntimeProfiles>[number]): AgentWorkspaceRuntimeProfileItem {
|
|
117
134
|
return {
|
|
118
135
|
id: profile.id,
|
|
@@ -154,6 +171,20 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
154
171
|
return 0;
|
|
155
172
|
}
|
|
156
173
|
})();
|
|
174
|
+
const memorySnapshot = (() => {
|
|
175
|
+
try {
|
|
176
|
+
const memory = context.clients?.agentKnowledgeApi?.memory;
|
|
177
|
+
if (!memory) return { count: 0, reviewQueueCount: 0, items: [] };
|
|
178
|
+
const records = [...memory.getAll()].sort((left, right) => right.updatedAt - left.updatedAt);
|
|
179
|
+
return {
|
|
180
|
+
count: records.length,
|
|
181
|
+
reviewQueueCount: memory.reviewQueue(100).length,
|
|
182
|
+
items: records.map(summarizeMemoryItem),
|
|
183
|
+
};
|
|
184
|
+
} catch {
|
|
185
|
+
return { count: 0, reviewQueueCount: 0, items: [] };
|
|
186
|
+
}
|
|
187
|
+
})();
|
|
157
188
|
const personaSnapshot = (() => {
|
|
158
189
|
try {
|
|
159
190
|
const shellPaths = context.workspace?.shellPaths;
|
|
@@ -264,6 +295,8 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
264
295
|
model,
|
|
265
296
|
runtimeBaseUrl,
|
|
266
297
|
sessionMemoryCount,
|
|
298
|
+
localMemoryCount: memorySnapshot.count,
|
|
299
|
+
localMemoryReviewQueueCount: memorySnapshot.reviewQueueCount,
|
|
267
300
|
routineCount: routineSnapshot.count,
|
|
268
301
|
enabledRoutineCount: routineSnapshot.enabled,
|
|
269
302
|
skillCount: skillSnapshot.count,
|
|
@@ -288,6 +321,9 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
288
321
|
runtimeBaseUrl,
|
|
289
322
|
runtimeOwnership: 'external',
|
|
290
323
|
sessionMemoryCount,
|
|
324
|
+
localMemoryCount: memorySnapshot.count,
|
|
325
|
+
localMemoryReviewQueueCount: memorySnapshot.reviewQueueCount,
|
|
326
|
+
localMemories: memorySnapshot.items,
|
|
291
327
|
localRoutineCount: routineSnapshot.count,
|
|
292
328
|
enabledRoutineCount: routineSnapshot.enabled,
|
|
293
329
|
localRoutines: routineSnapshot.items,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { InputToken } from '@pellux/goodvibes-sdk/platform/core';
|
|
2
|
+
import type { AgentWorkspace } from './agent-workspace.ts';
|
|
3
|
+
|
|
4
|
+
export function handleAgentWorkspaceToken(
|
|
5
|
+
workspace: AgentWorkspace,
|
|
6
|
+
token: InputToken,
|
|
7
|
+
handleEscape: () => void,
|
|
8
|
+
requestRender: () => void,
|
|
9
|
+
): boolean {
|
|
10
|
+
if (!workspace.active) return false;
|
|
11
|
+
|
|
12
|
+
if (workspace.localEditor) {
|
|
13
|
+
if (token.type === 'text') {
|
|
14
|
+
workspace.appendEditorText(token.value);
|
|
15
|
+
} else if (token.type === 'key') {
|
|
16
|
+
if (token.logicalName === 'escape') workspace.cancelLocalEditor();
|
|
17
|
+
else if (token.logicalName === 'enter') workspace.submitEditorFieldOrForm(requestRender);
|
|
18
|
+
else if (token.logicalName === 'tab' || token.logicalName === 'down') workspace.moveEditorField(1);
|
|
19
|
+
else if (token.logicalName === 'up') workspace.moveEditorField(-1);
|
|
20
|
+
else if (token.logicalName === 'backspace' || token.logicalName === 'delete') workspace.editorBackspace();
|
|
21
|
+
else if (token.logicalName === 'j' && token.ctrl === true) workspace.appendEditorNewline();
|
|
22
|
+
}
|
|
23
|
+
requestRender();
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (token.type === 'key') {
|
|
28
|
+
if (token.logicalName === 'escape') {
|
|
29
|
+
handleEscape();
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (token.logicalName === 'enter' || token.logicalName === 'space') workspace.activateSelected(requestRender);
|
|
33
|
+
else if (token.logicalName === 'left') workspace.focusCategories();
|
|
34
|
+
else if (token.logicalName === 'right') workspace.focusActions();
|
|
35
|
+
else if (token.logicalName === 'up') workspace.moveUp();
|
|
36
|
+
else if (token.logicalName === 'down') workspace.moveDown();
|
|
37
|
+
else if (token.logicalName === 'tab') workspace.toggleFocusPane();
|
|
38
|
+
else if (token.logicalName === 'home') workspace.jumpHome();
|
|
39
|
+
else if (token.logicalName === 'end') workspace.jumpEnd();
|
|
40
|
+
} else if (token.type === 'text') {
|
|
41
|
+
if (token.value === 'h') workspace.focusCategories();
|
|
42
|
+
else if (token.value === 'l') workspace.focusActions();
|
|
43
|
+
else if (token.value === 'j') workspace.moveDown();
|
|
44
|
+
else if (token.value === 'k') workspace.moveUp();
|
|
45
|
+
else if (token.value === 'r' || token.value === 'R') workspace.refreshRuntimeSnapshot();
|
|
46
|
+
else if (token.value === ' ') workspace.activateSelected(requestRender);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
requestRender();
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
@@ -8,9 +8,13 @@ export type AgentWorkspaceFocusPane = 'categories' | 'actions';
|
|
|
8
8
|
|
|
9
9
|
export type AgentWorkspaceActionKind = 'command' | 'guidance' | 'workspace' | 'editor' | 'local-selection' | 'local-operation';
|
|
10
10
|
|
|
11
|
-
export type AgentWorkspaceLocalEditorKind = 'persona' | 'skill' | 'routine' | 'profile';
|
|
11
|
+
export type AgentWorkspaceLocalEditorKind = 'memory' | 'persona' | 'skill' | 'routine' | 'profile';
|
|
12
12
|
|
|
13
13
|
export type AgentWorkspaceLocalOperation =
|
|
14
|
+
| 'memory-edit'
|
|
15
|
+
| 'memory-review'
|
|
16
|
+
| 'memory-stale'
|
|
17
|
+
| 'memory-delete'
|
|
14
18
|
| 'persona-edit'
|
|
15
19
|
| 'persona-use'
|
|
16
20
|
| 'persona-review'
|
|
@@ -90,6 +94,9 @@ export interface AgentWorkspaceLocalLibraryItem {
|
|
|
90
94
|
readonly source: string;
|
|
91
95
|
readonly tags: readonly string[];
|
|
92
96
|
readonly triggers: readonly string[];
|
|
97
|
+
readonly scope?: string;
|
|
98
|
+
readonly cls?: string;
|
|
99
|
+
readonly confidence?: number;
|
|
93
100
|
readonly active?: boolean;
|
|
94
101
|
readonly enabled?: boolean;
|
|
95
102
|
readonly startCount?: number;
|
|
@@ -123,6 +130,9 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
123
130
|
readonly runtimeBaseUrl: string;
|
|
124
131
|
readonly runtimeOwnership: 'external';
|
|
125
132
|
readonly sessionMemoryCount: number;
|
|
133
|
+
readonly localMemoryCount: number;
|
|
134
|
+
readonly localMemoryReviewQueueCount: number;
|
|
135
|
+
readonly localMemories: readonly AgentWorkspaceLocalLibraryItem[];
|
|
126
136
|
readonly localRoutineCount: number;
|
|
127
137
|
readonly enabledRoutineCount: number;
|
|
128
138
|
readonly localRoutines: readonly AgentWorkspaceLocalLibraryItem[];
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MemoryApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
2
|
+
import type { MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
|
|
2
3
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
3
4
|
import type { CommandContext } from './command-registry.ts';
|
|
4
5
|
import { AgentPersonaRegistry } from '../agent/persona-registry.ts';
|
|
5
6
|
import { AgentRoutineRegistry } from '../agent/routine-registry.ts';
|
|
6
7
|
import { createAgentRuntimeProfile, type AgentRuntimeProfileInfo } from '../agent/runtime-profile.ts';
|
|
7
8
|
import { AgentSkillRegistry } from '../agent/skill-registry.ts';
|
|
9
|
+
import { activateAgentWorkspaceSelection } from './agent-workspace-activation.ts';
|
|
8
10
|
import { AGENT_WORKSPACE_CATEGORIES } from './agent-workspace-categories.ts';
|
|
9
|
-
import { createDeleteEditor,
|
|
11
|
+
import { createDeleteEditor, createMemoryUpdateEditor, createPersonaUpdateEditor, createRoutineUpdateEditor, createSkillUpdateEditor, editorCategoryId, isAffirmative, splitList } from './agent-workspace-editors.ts';
|
|
12
|
+
import { deleteAgentWorkspaceMemoryEditor, submitAgentWorkspaceMemoryEditor } from './agent-workspace-memory-editor.ts';
|
|
10
13
|
import { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
11
14
|
import type { AgentWorkspaceAction, AgentWorkspaceActionResult, AgentWorkspaceCategory, AgentWorkspaceCommandDispatcher, AgentWorkspaceEditorField, AgentWorkspaceFocusPane, AgentWorkspaceLocalEditor, AgentWorkspaceLocalEditorKind, AgentWorkspaceLocalLibraryItem, AgentWorkspaceLocalOperation, AgentWorkspaceRuntimeSnapshot } from './agent-workspace-types.ts';
|
|
12
15
|
|
|
@@ -26,12 +29,7 @@ export type {
|
|
|
26
29
|
} from './agent-workspace-types.ts';
|
|
27
30
|
export { AGENT_WORKSPACE_MODAL_NAME } from './agent-workspace-types.ts';
|
|
28
31
|
export { buildAgentWorkspaceRuntimeSnapshot } from './agent-workspace-snapshot.ts';
|
|
29
|
-
|
|
30
|
-
const trimmed = command.trim().replace(/^\//, '');
|
|
31
|
-
if (!trimmed) return { name: '', args: [] };
|
|
32
|
-
const parts = trimmed.split(/\s+/);
|
|
33
|
-
return { name: parts[0] ?? '', args: parts.slice(1) };
|
|
34
|
-
}
|
|
32
|
+
export { handleAgentWorkspaceToken } from './agent-workspace-token.ts';
|
|
35
33
|
|
|
36
34
|
export class AgentWorkspace {
|
|
37
35
|
public active = false;
|
|
@@ -43,6 +41,7 @@ export class AgentWorkspace {
|
|
|
43
41
|
public lastActionResult: AgentWorkspaceActionResult | null = null;
|
|
44
42
|
public localEditor: AgentWorkspaceLocalEditor | null = null;
|
|
45
43
|
private readonly selectedLibraryItemIndexes: Record<AgentWorkspaceLocalEditorKind, number> = {
|
|
44
|
+
memory: 0,
|
|
46
45
|
persona: 0,
|
|
47
46
|
skill: 0,
|
|
48
47
|
routine: 0,
|
|
@@ -207,147 +206,39 @@ export class AgentWorkspace {
|
|
|
207
206
|
this.replaceEditorField(editor.selectedFieldIndex, characters.join(''), editor.message);
|
|
208
207
|
}
|
|
209
208
|
|
|
210
|
-
submitEditorFieldOrForm(): void {
|
|
209
|
+
submitEditorFieldOrForm(requestRender?: () => void): void {
|
|
211
210
|
const editor = this.localEditor;
|
|
212
211
|
if (!editor) return;
|
|
213
212
|
if (editor.selectedFieldIndex < editor.fields.length - 1) {
|
|
214
213
|
this.moveEditorField(1);
|
|
215
214
|
return;
|
|
216
215
|
}
|
|
217
|
-
this.submitLocalEditor();
|
|
216
|
+
this.submitLocalEditor(requestRender);
|
|
218
217
|
}
|
|
219
218
|
|
|
220
|
-
activateSelected(): void {
|
|
221
|
-
|
|
222
|
-
this.submitEditorFieldOrForm();
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (this.focusPane === 'categories') {
|
|
226
|
-
this.focusActions();
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const action = this.selectedAction;
|
|
230
|
-
if (!action) return;
|
|
231
|
-
if (action.kind === 'editor' && action.editorKind) {
|
|
232
|
-
this.localEditor = action.editorKind === 'profile'
|
|
233
|
-
? createProfileEditor(this.runtimeSnapshot?.runtimeStarterTemplates ?? [])
|
|
234
|
-
: createLocalEditor(action.editorKind);
|
|
235
|
-
this.status = `Editing ${this.localEditor.title}.`;
|
|
236
|
-
this.lastActionResult = {
|
|
237
|
-
kind: 'guidance',
|
|
238
|
-
title: this.localEditor.title,
|
|
239
|
-
detail: this.localEditor.message,
|
|
240
|
-
safety: action.safety,
|
|
241
|
-
};
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
if (action.kind === 'local-selection' && action.localKind) {
|
|
245
|
-
this.moveLocalLibraryItemSelection(action.localKind, action.selectionDelta ?? 0);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
if (action.kind === 'local-operation' && action.localOperation) {
|
|
249
|
-
this.applyLocalLibraryOperation(action.localOperation);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
if (action.kind === 'guidance' || !action.command) {
|
|
253
|
-
if (action.kind === 'workspace' && action.targetCategoryId) {
|
|
254
|
-
const targetIndex = this.categories.findIndex((category) => category.id === action.targetCategoryId);
|
|
255
|
-
if (targetIndex >= 0) {
|
|
256
|
-
this.selectedCategoryIndex = targetIndex;
|
|
257
|
-
this.selectedActionIndex = 0;
|
|
258
|
-
this.focusActions();
|
|
259
|
-
this.status = `Opened ${this.selectedCategory.label}.`;
|
|
260
|
-
this.lastActionResult = {
|
|
261
|
-
kind: 'refreshed',
|
|
262
|
-
title: `Opened ${this.selectedCategory.label}`,
|
|
263
|
-
detail: action.detail,
|
|
264
|
-
safety: action.safety,
|
|
265
|
-
};
|
|
266
|
-
this.clampSelection();
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
this.status = `Workspace area unavailable: ${action.targetCategoryId}.`;
|
|
270
|
-
this.lastActionResult = {
|
|
271
|
-
kind: 'error',
|
|
272
|
-
title: 'Workspace area unavailable',
|
|
273
|
-
detail: `No Agent workspace category exists for ${action.targetCategoryId}.`,
|
|
274
|
-
safety: action.safety,
|
|
275
|
-
};
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
this.status = action.detail;
|
|
279
|
-
this.lastActionResult = {
|
|
280
|
-
kind: 'guidance',
|
|
281
|
-
title: action.label,
|
|
282
|
-
detail: action.detail,
|
|
283
|
-
safety: action.safety,
|
|
284
|
-
};
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (action.safety === 'blocked') {
|
|
288
|
-
this.status = `Blocked here: ${action.label}.`;
|
|
289
|
-
this.lastActionResult = {
|
|
290
|
-
kind: 'blocked',
|
|
291
|
-
title: `${action.label} is blocked in Agent`,
|
|
292
|
-
detail: action.detail,
|
|
293
|
-
command: action.command,
|
|
294
|
-
safety: action.safety,
|
|
295
|
-
};
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const parsed = parseCommand(action.command);
|
|
299
|
-
if (!parsed.name) {
|
|
300
|
-
this.status = `No command is configured for ${action.label}.`;
|
|
301
|
-
this.lastActionResult = {
|
|
302
|
-
kind: 'error',
|
|
303
|
-
title: 'Command unavailable',
|
|
304
|
-
detail: `No command is configured for ${action.label}.`,
|
|
305
|
-
safety: action.safety,
|
|
306
|
-
};
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
if (/<[^>\s]+(?:\s+[^>]*)?>/.test(action.command)) {
|
|
310
|
-
this.status = `Placeholder command not dispatched: ${action.command}.`;
|
|
311
|
-
this.lastActionResult = {
|
|
312
|
-
kind: 'guidance',
|
|
313
|
-
title: `${action.label} needs details`,
|
|
314
|
-
detail: 'This action is a command template. Close the workspace and run it with real task text instead of placeholder values.',
|
|
315
|
-
command: action.command,
|
|
316
|
-
safety: action.safety,
|
|
317
|
-
};
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (!this.context?.executeCommand || !this.dispatchCommand) {
|
|
321
|
-
this.status = `Command dispatch is not available for ${action.command}.`;
|
|
322
|
-
this.lastActionResult = {
|
|
323
|
-
kind: 'error',
|
|
324
|
-
title: 'Command dispatch unavailable',
|
|
325
|
-
detail: `The command ${action.command} cannot be opened from this runtime.`,
|
|
326
|
-
command: action.command,
|
|
327
|
-
safety: action.safety,
|
|
328
|
-
};
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
this.status = `Opening ${action.command}.`;
|
|
332
|
-
this.lastActionResult = {
|
|
333
|
-
kind: 'dispatched',
|
|
334
|
-
title: `Opening ${action.label}`,
|
|
335
|
-
detail: 'The workspace handed this safe or read-only command to the shell-owned command router.',
|
|
336
|
-
command: action.command,
|
|
337
|
-
safety: action.safety,
|
|
338
|
-
};
|
|
339
|
-
this.dispatchCommand(action.command);
|
|
219
|
+
activateSelected(requestRender?: () => void): void {
|
|
220
|
+
activateAgentWorkspaceSelection(this, requestRender);
|
|
340
221
|
}
|
|
341
222
|
|
|
342
|
-
|
|
223
|
+
hasCommandDispatch(): boolean {
|
|
224
|
+
return Boolean(this.context?.executeCommand && this.dispatchCommand);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
dispatchWorkspaceCommand(command: string): void {
|
|
228
|
+
this.dispatchCommand?.(command);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
clampSelection(): void {
|
|
343
232
|
this.selectedCategoryIndex = Math.max(0, Math.min(this.selectedCategoryIndex, this.categories.length - 1));
|
|
344
233
|
this.selectedActionIndex = Math.max(0, Math.min(this.selectedActionIndex, this.actions.length - 1));
|
|
234
|
+
this.clampLocalLibrarySelection('memory');
|
|
345
235
|
this.clampLocalLibrarySelection('persona');
|
|
346
236
|
this.clampLocalLibrarySelection('skill');
|
|
347
237
|
this.clampLocalLibrarySelection('routine');
|
|
348
238
|
}
|
|
349
239
|
|
|
350
240
|
private localLibraryItems(kind: AgentWorkspaceLocalEditorKind): readonly AgentWorkspaceLocalLibraryItem[] {
|
|
241
|
+
if (kind === 'memory') return this.runtimeSnapshot?.localMemories ?? [];
|
|
351
242
|
if (kind === 'persona') return this.runtimeSnapshot?.localPersonas ?? [];
|
|
352
243
|
if (kind === 'skill') return this.runtimeSnapshot?.localSkills ?? [];
|
|
353
244
|
if (kind === 'profile') return [];
|
|
@@ -361,7 +252,7 @@ export class AgentWorkspace {
|
|
|
361
252
|
: Math.max(0, Math.min(this.selectedLibraryItemIndexes[kind], length - 1));
|
|
362
253
|
}
|
|
363
254
|
|
|
364
|
-
|
|
255
|
+
moveLocalLibraryItemSelection(kind: AgentWorkspaceLocalEditorKind, delta: number): void {
|
|
365
256
|
const items = this.localLibraryItems(kind);
|
|
366
257
|
if (items.length === 0) {
|
|
367
258
|
this.status = `No local ${kind} records to select.`;
|
|
@@ -384,7 +275,7 @@ export class AgentWorkspace {
|
|
|
384
275
|
};
|
|
385
276
|
}
|
|
386
277
|
|
|
387
|
-
|
|
278
|
+
applyLocalLibraryOperation(operation: AgentWorkspaceLocalOperation): void {
|
|
388
279
|
const shellPaths = this.context?.workspace?.shellPaths;
|
|
389
280
|
if (!shellPaths) {
|
|
390
281
|
this.status = 'Local Agent registry files are unavailable.';
|
|
@@ -412,7 +303,29 @@ export class AgentWorkspace {
|
|
|
412
303
|
};
|
|
413
304
|
return;
|
|
414
305
|
}
|
|
415
|
-
if (operation === '
|
|
306
|
+
if (operation === 'memory-edit') {
|
|
307
|
+
const memory = this.memoryApi();
|
|
308
|
+
const record = memory.get(selected.id);
|
|
309
|
+
if (!record) throw new Error(`Unknown Agent memory: ${selected.id}`);
|
|
310
|
+
this.localEditor = createMemoryUpdateEditor(record);
|
|
311
|
+
this.status = `Editing memory: ${record.id}.`;
|
|
312
|
+
this.lastActionResult = {
|
|
313
|
+
kind: 'guidance',
|
|
314
|
+
title: this.localEditor.title,
|
|
315
|
+
detail: this.localEditor.message,
|
|
316
|
+
safety: 'safe',
|
|
317
|
+
};
|
|
318
|
+
} else if (operation === 'memory-review') {
|
|
319
|
+
const record = this.memoryApi().review(selected.id, { state: 'reviewed', confidence: selected.confidence ?? 100, reviewedBy: 'operator' });
|
|
320
|
+
if (!record) throw new Error(`Unknown Agent memory: ${selected.id}`);
|
|
321
|
+
this.finishLocalOperation('memory', `Reviewed memory ${record.id}`, `${record.summary} is marked reviewed.`);
|
|
322
|
+
} else if (operation === 'memory-stale') {
|
|
323
|
+
const record = this.memoryApi().review(selected.id, { state: 'stale', staleReason: 'Marked stale from Agent workspace', reviewedBy: 'operator' });
|
|
324
|
+
if (!record) throw new Error(`Unknown Agent memory: ${selected.id}`);
|
|
325
|
+
this.finishLocalOperation('memory', `Marked memory stale ${record.id}`, `${record.summary} needs review before reuse.`);
|
|
326
|
+
} else if (operation === 'memory-delete') {
|
|
327
|
+
this.openDeleteEditor('memory', selected);
|
|
328
|
+
} else if (operation === 'persona-edit') {
|
|
416
329
|
const registry = AgentPersonaRegistry.fromShellPaths(shellPaths);
|
|
417
330
|
const persona = registry.get(selected.id);
|
|
418
331
|
if (!persona) throw new Error(`Unknown persona: ${selected.id}`);
|
|
@@ -492,11 +405,18 @@ export class AgentWorkspace {
|
|
|
492
405
|
}
|
|
493
406
|
|
|
494
407
|
private selectedItemForOperation(operation: AgentWorkspaceLocalOperation): AgentWorkspaceLocalLibraryItem | null {
|
|
408
|
+
if (operation.startsWith('memory-')) return this.selectedLocalLibraryItem('memory');
|
|
495
409
|
if (operation.startsWith('persona-')) return this.selectedLocalLibraryItem('persona');
|
|
496
410
|
if (operation.startsWith('skill-')) return this.selectedLocalLibraryItem('skill');
|
|
497
411
|
return this.selectedLocalLibraryItem('routine');
|
|
498
412
|
}
|
|
499
413
|
|
|
414
|
+
private memoryApi(): MemoryApi {
|
|
415
|
+
const memory = this.context?.clients?.agentKnowledgeApi?.memory;
|
|
416
|
+
if (!memory) throw new Error('Agent Memory API is unavailable; refusing default Knowledge/Wiki or non-Agent fallback.');
|
|
417
|
+
return memory;
|
|
418
|
+
}
|
|
419
|
+
|
|
500
420
|
private finishLocalOperation(kind: AgentWorkspaceLocalEditorKind, title: string, detail: string): void {
|
|
501
421
|
this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
|
|
502
422
|
this.clampLocalLibrarySelection(kind);
|
|
@@ -538,7 +458,7 @@ export class AgentWorkspace {
|
|
|
538
458
|
return editor.fields.find((field) => field.required && field.value.trim().length === 0) ?? null;
|
|
539
459
|
}
|
|
540
460
|
|
|
541
|
-
private submitLocalEditor(): void {
|
|
461
|
+
private submitLocalEditor(requestRender?: () => void): void {
|
|
542
462
|
const editor = this.localEditor;
|
|
543
463
|
if (!editor) return;
|
|
544
464
|
const missing = this.missingEditorField();
|
|
@@ -552,6 +472,26 @@ export class AgentWorkspace {
|
|
|
552
472
|
this.status = `${missing.label} is required.`;
|
|
553
473
|
return;
|
|
554
474
|
}
|
|
475
|
+
if (editor.kind === 'memory') {
|
|
476
|
+
if (editor.mode === 'delete') {
|
|
477
|
+
try {
|
|
478
|
+
this.submitMemoryDeleteEditor(editor);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
481
|
+
this.localEditor = { ...editor, message: detail };
|
|
482
|
+
this.status = detail;
|
|
483
|
+
this.lastActionResult = {
|
|
484
|
+
kind: 'error',
|
|
485
|
+
title: `${editor.title} failed`,
|
|
486
|
+
detail,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
requestRender?.();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
void this.submitMemoryEditor(editor).finally(() => requestRender?.());
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
555
495
|
const shellPaths = this.context?.workspace?.shellPaths;
|
|
556
496
|
if (!shellPaths) {
|
|
557
497
|
this.localEditor = { ...editor, message: 'Cannot save because Agent shell paths are unavailable.' };
|
|
@@ -679,7 +619,11 @@ export class AgentWorkspace {
|
|
|
679
619
|
this.status = 'Deletion not confirmed.';
|
|
680
620
|
return;
|
|
681
621
|
}
|
|
682
|
-
if (editor.kind === '
|
|
622
|
+
if (editor.kind === 'memory') {
|
|
623
|
+
const removed = this.memoryApi().delete(expectedId);
|
|
624
|
+
if (!removed) throw new Error(`Unknown Agent memory: ${expectedId}`);
|
|
625
|
+
this.finishLocalDelete(editor.kind, expectedId, expectedId);
|
|
626
|
+
} else if (editor.kind === 'persona') {
|
|
683
627
|
const removed = AgentPersonaRegistry.fromShellPaths(shellPaths).deletePersona(expectedId);
|
|
684
628
|
this.finishLocalDelete(editor.kind, removed.id, removed.name);
|
|
685
629
|
} else if (editor.kind === 'skill') {
|
|
@@ -691,6 +635,56 @@ export class AgentWorkspace {
|
|
|
691
635
|
}
|
|
692
636
|
}
|
|
693
637
|
|
|
638
|
+
private submitMemoryDeleteEditor(editor: AgentWorkspaceLocalEditor): void {
|
|
639
|
+
const expectedId = editor.recordId ?? '';
|
|
640
|
+
const confirmedId = this.editorField('confirm');
|
|
641
|
+
const removed = deleteAgentWorkspaceMemoryEditor(editor, confirmedId, this.memoryApi());
|
|
642
|
+
if (!removed) {
|
|
643
|
+
this.localEditor = {
|
|
644
|
+
...editor,
|
|
645
|
+
message: `Deletion not confirmed. Type ${expectedId} exactly, then press Enter.`,
|
|
646
|
+
};
|
|
647
|
+
this.status = 'Deletion not confirmed.';
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
this.finishLocalDelete(editor.kind, removed.id, removed.name);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
private async submitMemoryEditor(editor: AgentWorkspaceLocalEditor): Promise<void> {
|
|
654
|
+
try {
|
|
655
|
+
this.status = 'Saving Agent memory...';
|
|
656
|
+
const result = await submitAgentWorkspaceMemoryEditor(editor, this.memoryApi(), (id) => this.editorField(id));
|
|
657
|
+
this.finishMemoryEditor(result.record, result.verb);
|
|
658
|
+
} catch (error) {
|
|
659
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
660
|
+
this.localEditor = { ...editor, message: detail };
|
|
661
|
+
this.status = detail;
|
|
662
|
+
this.lastActionResult = {
|
|
663
|
+
kind: 'error',
|
|
664
|
+
title: `${editor.title} failed`,
|
|
665
|
+
detail,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
private finishMemoryEditor(record: MemoryRecord, verb: 'Created' | 'Updated'): void {
|
|
671
|
+
this.localEditor = null;
|
|
672
|
+
this.runtimeSnapshot = this.context ? buildAgentWorkspaceRuntimeSnapshot(this.context) : this.runtimeSnapshot;
|
|
673
|
+
const categoryIndex = this.categories.findIndex((category) => category.id === 'memory');
|
|
674
|
+
if (categoryIndex >= 0) {
|
|
675
|
+
this.selectedCategoryIndex = categoryIndex;
|
|
676
|
+
this.selectedActionIndex = 0;
|
|
677
|
+
}
|
|
678
|
+
this.status = `${verb} memory: ${record.summary}.`;
|
|
679
|
+
this.lastActionResult = {
|
|
680
|
+
kind: 'refreshed',
|
|
681
|
+
title: `${verb} memory`,
|
|
682
|
+
detail: `${record.summary} (${record.id}) was saved to Agent-owned memory only.`,
|
|
683
|
+
safety: 'safe',
|
|
684
|
+
};
|
|
685
|
+
this.clampSelection();
|
|
686
|
+
}
|
|
687
|
+
|
|
694
688
|
private finishLocalEditor(kind: AgentWorkspaceLocalEditorKind, id: string, name: string, verb: 'Created' | 'Updated'): void {
|
|
695
689
|
this.localEditor = null;
|
|
696
690
|
const categoryId = editorCategoryId(kind);
|
|
@@ -749,52 +743,3 @@ export class AgentWorkspace {
|
|
|
749
743
|
this.clampSelection();
|
|
750
744
|
}
|
|
751
745
|
}
|
|
752
|
-
|
|
753
|
-
export function handleAgentWorkspaceToken(
|
|
754
|
-
workspace: AgentWorkspace,
|
|
755
|
-
token: InputToken,
|
|
756
|
-
handleEscape: () => void,
|
|
757
|
-
requestRender: () => void,
|
|
758
|
-
): boolean {
|
|
759
|
-
if (!workspace.active) return false;
|
|
760
|
-
|
|
761
|
-
if (workspace.localEditor) {
|
|
762
|
-
if (token.type === 'text') {
|
|
763
|
-
workspace.appendEditorText(token.value);
|
|
764
|
-
} else if (token.type === 'key') {
|
|
765
|
-
if (token.logicalName === 'escape') workspace.cancelLocalEditor();
|
|
766
|
-
else if (token.logicalName === 'enter') workspace.submitEditorFieldOrForm();
|
|
767
|
-
else if (token.logicalName === 'tab' || token.logicalName === 'down') workspace.moveEditorField(1);
|
|
768
|
-
else if (token.logicalName === 'up') workspace.moveEditorField(-1);
|
|
769
|
-
else if (token.logicalName === 'backspace' || token.logicalName === 'delete') workspace.editorBackspace();
|
|
770
|
-
else if (token.logicalName === 'j' && token.ctrl === true) workspace.appendEditorNewline();
|
|
771
|
-
}
|
|
772
|
-
requestRender();
|
|
773
|
-
return true;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
if (token.type === 'key') {
|
|
777
|
-
if (token.logicalName === 'escape') {
|
|
778
|
-
handleEscape();
|
|
779
|
-
return true;
|
|
780
|
-
}
|
|
781
|
-
if (token.logicalName === 'enter' || token.logicalName === 'space') workspace.activateSelected();
|
|
782
|
-
else if (token.logicalName === 'left') workspace.focusCategories();
|
|
783
|
-
else if (token.logicalName === 'right') workspace.focusActions();
|
|
784
|
-
else if (token.logicalName === 'up') workspace.moveUp();
|
|
785
|
-
else if (token.logicalName === 'down') workspace.moveDown();
|
|
786
|
-
else if (token.logicalName === 'tab') workspace.toggleFocusPane();
|
|
787
|
-
else if (token.logicalName === 'home') workspace.jumpHome();
|
|
788
|
-
else if (token.logicalName === 'end') workspace.jumpEnd();
|
|
789
|
-
} else if (token.type === 'text') {
|
|
790
|
-
if (token.value === 'h') workspace.focusCategories();
|
|
791
|
-
else if (token.value === 'l') workspace.focusActions();
|
|
792
|
-
else if (token.value === 'j') workspace.moveDown();
|
|
793
|
-
else if (token.value === 'k') workspace.moveUp();
|
|
794
|
-
else if (token.value === 'r' || token.value === 'R') workspace.refreshRuntimeSnapshot();
|
|
795
|
-
else if (token.value === ' ') workspace.activateSelected();
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
requestRender();
|
|
799
|
-
return true;
|
|
800
|
-
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PanelManager } from '../panel-manager.ts';
|
|
2
|
+
import { CostTrackerPanel } from '../cost-tracker-panel.ts';
|
|
3
|
+
import type { ResolvedBuiltinPanelDeps } from './shared.ts';
|
|
4
|
+
import { requireUiServices } from './shared.ts';
|
|
5
|
+
|
|
6
|
+
export function registerUsagePanels(manager: PanelManager, deps: ResolvedBuiltinPanelDeps): void {
|
|
7
|
+
if (!deps.getOrchestratorUsage) return;
|
|
8
|
+
|
|
9
|
+
const { getOrchestratorUsage, budgetThreshold } = deps;
|
|
10
|
+
manager.registerType({
|
|
11
|
+
id: 'cost',
|
|
12
|
+
name: 'Cost',
|
|
13
|
+
icon: '$',
|
|
14
|
+
category: 'monitoring',
|
|
15
|
+
description: 'Estimated assistant usage costs for this session and explicit delegated work, with budget alerts',
|
|
16
|
+
factory: () => {
|
|
17
|
+
const ui = requireUiServices(deps);
|
|
18
|
+
return new CostTrackerPanel(ui.events.turns, ui.events.agents, getOrchestratorUsage, { budgetThreshold });
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PanelManager } from './panel-manager.ts';
|
|
2
2
|
import { resolveBuiltinPanelDeps, type BuiltinPanelDeps } from './builtin/shared.ts';
|
|
3
|
-
import {
|
|
3
|
+
import { registerUsagePanels } from './builtin/usage.ts';
|
|
4
4
|
import { registerOperationsPanels } from './builtin/operations.ts';
|
|
5
5
|
import { registerAgentPanels } from './builtin/agent.ts';
|
|
6
6
|
import { registerSessionPanels } from './builtin/session.ts';
|
|
@@ -13,7 +13,7 @@ import { registerKnowledgePanels } from './builtin/knowledge.ts';
|
|
|
13
13
|
*/
|
|
14
14
|
export function registerBuiltinPanels(manager: PanelManager, deps: BuiltinPanelDeps): void {
|
|
15
15
|
const resolved = resolveBuiltinPanelDeps(deps);
|
|
16
|
-
|
|
16
|
+
registerUsagePanels(manager, resolved);
|
|
17
17
|
registerOperationsPanels(manager, resolved);
|
|
18
18
|
registerAgentPanels(manager, resolved);
|
|
19
19
|
registerSessionPanels(manager, resolved);
|
|
@@ -65,9 +65,8 @@ const C = {
|
|
|
65
65
|
intro: '#94a3b8',
|
|
66
66
|
} as const;
|
|
67
67
|
|
|
68
|
-
const CATEGORY_ORDER: PanelCategory[] = ['
|
|
68
|
+
const CATEGORY_ORDER: PanelCategory[] = ['agent', 'monitoring', 'session', 'ai'];
|
|
69
69
|
const CATEGORY_LABELS: Record<PanelCategory, string> = {
|
|
70
|
-
development: 'Development',
|
|
71
70
|
agent: 'Agent',
|
|
72
71
|
monitoring: 'Monitoring',
|
|
73
72
|
session: 'Session',
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { PanelRegistration, PanelCategory } from './types.ts';
|
|
2
2
|
|
|
3
3
|
/** Display order for panel categories. */
|
|
4
|
-
const CATEGORY_ORDER: PanelCategory[] = ['
|
|
4
|
+
const CATEGORY_ORDER: PanelCategory[] = ['agent', 'monitoring', 'session', 'ai'];
|
|
5
5
|
|
|
6
6
|
/** Human-readable labels for each category. */
|
|
7
7
|
const CATEGORY_LABELS: Record<PanelCategory, string> = {
|
|
8
|
-
development: 'Development',
|
|
9
8
|
agent: 'Agent',
|
|
10
9
|
monitoring: 'Monitoring',
|
|
11
10
|
session: 'Session',
|
package/src/panels/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import type { ComponentResourceContract, ComponentHealthState } from '../runtime/perf/panel-contracts.ts';
|
|
3
3
|
|
|
4
|
-
export type PanelCategory = '
|
|
4
|
+
export type PanelCategory = 'agent' | 'monitoring' | 'session' | 'ai';
|
|
5
5
|
|
|
6
6
|
export interface Panel {
|
|
7
7
|
id: string;
|
|
@@ -123,6 +123,8 @@ function localLibraryLines(
|
|
|
123
123
|
selected ? 'selected' : '',
|
|
124
124
|
item.active ? 'active' : '',
|
|
125
125
|
item.enabled === true ? 'enabled' : item.enabled === false ? 'disabled' : '',
|
|
126
|
+
item.scope && item.cls ? `${item.scope}/${item.cls}` : '',
|
|
127
|
+
item.confidence !== undefined ? `${item.confidence}%` : '',
|
|
126
128
|
item.reviewState,
|
|
127
129
|
item.startCount !== undefined ? `starts ${item.startCount}` : '',
|
|
128
130
|
].filter(Boolean).join(' / ');
|
|
@@ -301,11 +303,14 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
301
303
|
} else if (category.id === 'memory') {
|
|
302
304
|
base.push(
|
|
303
305
|
{ text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
|
|
306
|
+
{ text: `Agent memory: ${snapshot.localMemoryCount}; review queue: ${snapshot.localMemoryReviewQueueCount}`, fg: PALETTE.info },
|
|
304
307
|
{ text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
|
|
305
308
|
{ text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
|
|
306
309
|
{ text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
|
|
307
310
|
{ text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
308
311
|
{ text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
|
|
312
|
+
{ text: '' },
|
|
313
|
+
...localLibraryLines('Agent Memory', snapshot.localMemories, 'No Agent memory yet. Create one here with Create memory.', workspace.selectedLocalLibraryItem('memory')?.id ?? null),
|
|
309
314
|
);
|
|
310
315
|
} else if (category.id === 'personas') {
|
|
311
316
|
base.push(
|
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.105';
|
|
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 {
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { PanelManager } from '../panel-manager.ts';
|
|
2
|
-
import { PlanDashboardPanel } from '../plan-dashboard-panel.ts';
|
|
3
|
-
import { CostTrackerPanel } from '../cost-tracker-panel.ts';
|
|
4
|
-
import type { ResolvedBuiltinPanelDeps } from './shared.ts';
|
|
5
|
-
import { requireUiServices } from './shared.ts';
|
|
6
|
-
|
|
7
|
-
export function registerDevelopmentPanels(manager: PanelManager, deps: ResolvedBuiltinPanelDeps): void {
|
|
8
|
-
manager.registerType({
|
|
9
|
-
id: 'plan',
|
|
10
|
-
name: 'Plan',
|
|
11
|
-
icon: 'P',
|
|
12
|
-
category: 'agent',
|
|
13
|
-
description: 'Active execution plan with phase progress and item status',
|
|
14
|
-
factory: () => new PlanDashboardPanel(deps.planManager),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
if (deps.getOrchestratorUsage) {
|
|
18
|
-
const { getOrchestratorUsage, budgetThreshold } = deps;
|
|
19
|
-
manager.registerType({
|
|
20
|
-
id: 'cost',
|
|
21
|
-
name: 'Cost',
|
|
22
|
-
icon: '$',
|
|
23
|
-
category: 'monitoring',
|
|
24
|
-
description: 'Estimated costs per session, agent, and plan with budget alerts',
|
|
25
|
-
factory: () => {
|
|
26
|
-
const ui = requireUiServices(deps);
|
|
27
|
-
return new CostTrackerPanel(ui.events.turns, ui.events.agents, getOrchestratorUsage, { budgetThreshold });
|
|
28
|
-
},
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|