@pellux/goodvibes-agent 0.1.102 → 0.1.104
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 +14 -0
- package/README.md +10 -0
- package/docs/README.md +1 -1
- package/docs/getting-started.md +17 -3
- package/package.json +1 -1
- package/src/agent/memory-safety.ts +16 -0
- package/src/cli/help.ts +86 -0
- package/src/cli/local-library-command.ts +516 -0
- package/src/cli/management.ts +17 -0
- package/src/cli/memory-command.ts +630 -0
- package/src/cli/package-verification.ts +10 -0
- package/src/cli/parser.ts +8 -0
- package/src/cli/types.ts +3 -0
- 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 +7 -5
- package/src/input/agent-workspace-snapshot.ts +40 -4
- package/src/input/agent-workspace-token.ts +51 -0
- package/src/input/agent-workspace-types.ts +13 -3
- package/src/input/agent-workspace.ts +130 -185
- package/src/input/feed-context-factory.ts +1 -3
- package/src/input/handler-feed.ts +1 -4
- package/src/input/handler-interactions.ts +0 -1
- package/src/input/handler-modal-stack.ts +0 -1
- package/src/input/handler-modal-token-routes.ts +0 -11
- package/src/input/handler-picker-routes.ts +11 -20
- package/src/input/handler-ui-state.ts +0 -6
- package/src/input/handler.ts +1 -17
- package/src/main.ts +0 -6
- package/src/panels/builtin/agent.ts +0 -17
- package/src/panels/index.ts +0 -2
- package/src/renderer/agent-workspace.ts +8 -3
- package/src/renderer/conversation-overlays.ts +0 -6
- package/src/renderer/live-tail-modal.ts +10 -69
- package/src/renderer/process-modal.ts +28 -530
- package/src/runtime/bootstrap-core.ts +1 -1
- package/src/runtime/services.ts +3 -4
- package/src/tools/{wrfc-agent-guard.ts → agent-tool-policy-guard.ts} +0 -6
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -521
- package/src/panels/agent-inspector-shared.ts +0 -94
- package/src/panels/agent-logs-panel.ts +0 -559
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/renderer/agent-detail-modal.ts +0 -331
- package/src/renderer/process-summary.ts +0 -67
|
@@ -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
|
+
}
|
|
@@ -11,8 +11,10 @@ export interface AgentWorkspaceSetupChecklistItem {
|
|
|
11
11
|
export interface AgentWorkspaceSetupChecklistInput {
|
|
12
12
|
readonly provider: string;
|
|
13
13
|
readonly model: string;
|
|
14
|
-
readonly
|
|
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;
|
|
@@ -39,7 +41,7 @@ export function buildAgentWorkspaceSetupChecklist(input: AgentWorkspaceSetupChec
|
|
|
39
41
|
id: 'runtime',
|
|
40
42
|
label: 'GoodVibes runtime',
|
|
41
43
|
status: 'ready',
|
|
42
|
-
detail: `Agent will connect to ${input.
|
|
44
|
+
detail: `Agent will connect to ${input.runtimeBaseUrl}; runtime ownership stays outside this product.`,
|
|
43
45
|
command: '/health',
|
|
44
46
|
},
|
|
45
47
|
{
|
|
@@ -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;
|
|
@@ -252,7 +283,7 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
252
283
|
const ttsVoice = readConfigString(context, 'tts.voice', '(voice default)');
|
|
253
284
|
const ttsLlmProvider = readConfigString(context, 'tts.llmProvider', '');
|
|
254
285
|
const ttsLlmModel = readConfigString(context, 'tts.llmModel', '');
|
|
255
|
-
const
|
|
286
|
+
const runtimeBaseUrl = `http://${host}:${port}`;
|
|
256
287
|
const channels = buildAgentWorkspaceChannels(context);
|
|
257
288
|
const voiceMediaReadiness = buildAgentWorkspaceVoiceMediaReadiness({
|
|
258
289
|
context,
|
|
@@ -262,8 +293,10 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
262
293
|
const setupChecklist = buildAgentWorkspaceSetupChecklist({
|
|
263
294
|
provider,
|
|
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,
|
|
@@ -285,9 +318,12 @@ export function buildAgentWorkspaceRuntimeSnapshot(context: CommandContext): Age
|
|
|
285
318
|
sessionId: context.session?.runtime?.sessionId ?? 'unknown',
|
|
286
319
|
workingDirectory: context.workspace?.shellPaths?.workingDirectory ?? 'unavailable',
|
|
287
320
|
homeDirectory: context.workspace?.shellPaths?.homeDirectory ?? 'unavailable',
|
|
288
|
-
|
|
289
|
-
|
|
321
|
+
runtimeBaseUrl,
|
|
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;
|
|
@@ -120,9 +127,12 @@ export interface AgentWorkspaceRuntimeSnapshot {
|
|
|
120
127
|
readonly sessionId: string;
|
|
121
128
|
readonly workingDirectory: string;
|
|
122
129
|
readonly homeDirectory: string;
|
|
123
|
-
readonly
|
|
124
|
-
readonly
|
|
130
|
+
readonly runtimeBaseUrl: string;
|
|
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[];
|