@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +10 -0
  3. package/docs/README.md +1 -1
  4. package/docs/getting-started.md +17 -3
  5. package/package.json +1 -1
  6. package/src/agent/memory-safety.ts +16 -0
  7. package/src/cli/help.ts +86 -0
  8. package/src/cli/local-library-command.ts +516 -0
  9. package/src/cli/management.ts +17 -0
  10. package/src/cli/memory-command.ts +630 -0
  11. package/src/cli/package-verification.ts +10 -0
  12. package/src/cli/parser.ts +8 -0
  13. package/src/cli/types.ts +3 -0
  14. package/src/input/agent-workspace-activation.ts +170 -0
  15. package/src/input/agent-workspace-categories.ts +8 -1
  16. package/src/input/agent-workspace-editors.ts +36 -0
  17. package/src/input/agent-workspace-memory-editor.ts +88 -0
  18. package/src/input/agent-workspace-setup.ts +7 -5
  19. package/src/input/agent-workspace-snapshot.ts +40 -4
  20. package/src/input/agent-workspace-token.ts +51 -0
  21. package/src/input/agent-workspace-types.ts +13 -3
  22. package/src/input/agent-workspace.ts +130 -185
  23. package/src/input/feed-context-factory.ts +1 -3
  24. package/src/input/handler-feed.ts +1 -4
  25. package/src/input/handler-interactions.ts +0 -1
  26. package/src/input/handler-modal-stack.ts +0 -1
  27. package/src/input/handler-modal-token-routes.ts +0 -11
  28. package/src/input/handler-picker-routes.ts +11 -20
  29. package/src/input/handler-ui-state.ts +0 -6
  30. package/src/input/handler.ts +1 -17
  31. package/src/main.ts +0 -6
  32. package/src/panels/builtin/agent.ts +0 -17
  33. package/src/panels/index.ts +0 -2
  34. package/src/renderer/agent-workspace.ts +8 -3
  35. package/src/renderer/conversation-overlays.ts +0 -6
  36. package/src/renderer/live-tail-modal.ts +10 -69
  37. package/src/renderer/process-modal.ts +28 -530
  38. package/src/runtime/bootstrap-core.ts +1 -1
  39. package/src/runtime/services.ts +3 -4
  40. package/src/tools/{wrfc-agent-guard.ts → agent-tool-policy-guard.ts} +0 -6
  41. package/src/version.ts +1 -1
  42. package/src/panels/agent-inspector-panel.ts +0 -521
  43. package/src/panels/agent-inspector-shared.ts +0 -94
  44. package/src/panels/agent-logs-panel.ts +0 -559
  45. package/src/panels/agent-logs-shared.ts +0 -129
  46. package/src/renderer/agent-detail-modal.ts +0 -331
  47. 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: 'Open memory', detail: 'Inspect local/session memory records and commands.', command: '/memory', kind: 'command', safety: 'read-only' },
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 daemonBaseUrl: string;
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.daemonBaseUrl}; runtime ownership stays outside this product.`,
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.sessionMemoryCount, 'ready', 'optional'),
101
- detail: input.sessionMemoryCount > 0
102
- ? `${input.sessionMemoryCount} session memory record(s) are available.`
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 daemonBaseUrl = `http://${host}:${port}`;
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
- daemonBaseUrl,
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
- daemonBaseUrl,
289
- daemonOwnership: 'external',
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 daemonBaseUrl: string;
124
- readonly daemonOwnership: 'external';
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[];