@pellux/goodvibes-agent 1.5.5 → 1.5.6
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 +9 -0
- package/README.md +1 -1
- package/dist/package/main.js +94130 -79335
- package/docs/tools-and-commands.md +2 -1
- package/package.json +6 -3
- package/src/agent/autonomy-schedule-format.ts +1 -1
- package/src/agent/autonomy-schedule.ts +3 -3
- package/src/agent/calendar/calendar-oauth-service.ts +324 -0
- package/src/agent/calendar-subscription-registry.ts +497 -0
- package/src/agent/email/email-service.ts +57 -0
- package/src/agent/email/smtp-client.ts +30 -10
- package/src/agent/memory-prompt.ts +143 -12
- package/src/agent/operator-actions.ts +26 -22
- package/src/agent/prompt-context-receipts.ts +45 -18
- package/src/agent/reminder-schedule-format.ts +1 -1
- package/src/agent/reminder-schedule.ts +3 -3
- package/src/agent/routine-schedule-format.ts +2 -2
- package/src/agent/routine-schedule-promotion.ts +5 -5
- package/src/agent/routine-schedule-receipts.ts +2 -2
- package/src/agent/schedule-edit-format.ts +1 -1
- package/src/agent/schedule-edit.ts +5 -5
- package/src/agent/session-registration.ts +281 -0
- package/src/agent/vibe-file.ts +50 -0
- package/src/cli/local-library-command-shared.ts +146 -0
- package/src/cli/management.ts +2 -1
- package/src/cli/personas-command.ts +252 -0
- package/src/cli/resume-relaunch-notice.ts +151 -0
- package/src/cli/routines-command.ts +25 -2
- package/src/cli/service-posture.ts +3 -2
- package/src/cli/skill-bundle-command.ts +175 -0
- package/src/cli/skills-command.ts +309 -0
- package/src/cli/status.ts +43 -7
- package/src/cli/tui-startup.ts +24 -1
- package/src/config/agent-settings-policy.ts +0 -1
- package/src/config/secret-config.ts +4 -0
- package/src/core/conversation-rendering.ts +20 -11
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/system-message-noise.ts +108 -0
- package/src/core/system-message-router.ts +54 -1
- package/src/core/thinking-overlay.ts +83 -0
- package/src/input/agent-workspace-access-command-editor-submission.ts +75 -108
- package/src/input/agent-workspace-access-command-editors.ts +108 -128
- package/src/input/agent-workspace-activation.ts +15 -0
- package/src/input/agent-workspace-basic-command-editor-submission.ts +292 -513
- package/src/input/agent-workspace-basic-command-editors.ts +433 -564
- package/src/input/agent-workspace-calendar-connect-editor.ts +116 -0
- package/src/input/agent-workspace-calendar-oauth-editor.ts +152 -0
- package/src/input/agent-workspace-calendar-subscribe-editor.ts +128 -0
- package/src/input/agent-workspace-categories.ts +7 -2
- package/src/input/agent-workspace-channel-command-editor-submission.ts +38 -52
- package/src/input/agent-workspace-channel-command-editors.ts +42 -46
- package/src/input/agent-workspace-command-editor-engine.ts +133 -0
- package/src/input/agent-workspace-command-editor.ts +4 -0
- package/src/input/agent-workspace-direct-editor-submission.ts +59 -0
- package/src/input/agent-workspace-email-connect-editor.ts +162 -0
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +52 -81
- package/src/input/agent-workspace-knowledge-command-editors.ts +71 -71
- package/src/input/agent-workspace-library-command-editor-submission.ts +10 -28
- package/src/input/agent-workspace-library-command-editors.ts +16 -2
- package/src/input/agent-workspace-live-counters.ts +55 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +60 -82
- package/src/input/agent-workspace-mcp-command-editors.ts +52 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +12 -39
- package/src/input/agent-workspace-media-command-editors.ts +10 -8
- package/src/input/agent-workspace-memory-command-editor-submission.ts +76 -151
- package/src/input/agent-workspace-memory-command-editors.ts +116 -141
- package/src/input/agent-workspace-operations-command-editor-submission.ts +131 -184
- package/src/input/agent-workspace-operations-command-editors.ts +150 -162
- package/src/input/agent-workspace-provider-command-editor-submission.ts +60 -106
- package/src/input/agent-workspace-provider-command-editors.ts +58 -68
- package/src/input/agent-workspace-search.ts +8 -1
- package/src/input/agent-workspace-session-command-editor-submission.ts +104 -132
- package/src/input/agent-workspace-session-command-editors.ts +160 -195
- package/src/input/agent-workspace-settings.ts +40 -1
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +55 -71
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +65 -69
- package/src/input/agent-workspace-snapshot-builders.ts +431 -0
- package/src/input/agent-workspace-snapshot-config.ts +43 -0
- package/src/input/agent-workspace-snapshot.ts +198 -432
- package/src/input/agent-workspace-task-command-editor-submission.ts +34 -44
- package/src/input/agent-workspace-task-command-editors.ts +27 -28
- package/src/input/agent-workspace-types.ts +35 -1
- package/src/input/agent-workspace.ts +5 -14
- package/src/input/command-registry.ts +23 -1
- package/src/input/commands/calendar-connect-runtime.ts +226 -0
- package/src/input/commands/calendar-runtime.ts +110 -7
- package/src/input/commands/calendar-subscription-runtime.ts +225 -0
- package/src/input/commands/email-runtime.ts +100 -40
- package/src/input/commands/knowledge.ts +1 -1
- package/src/input/commands/network-scan-runtime.ts +75 -0
- package/src/input/commands/operator-actions-runtime.ts +9 -6
- package/src/input/commands/personas-runtime.ts +1 -1
- package/src/input/commands/schedule-runtime.ts +4 -4
- package/src/input/commands/session-content.ts +13 -1
- package/src/input/commands/session-workflow.ts +15 -20
- package/src/input/commands/session.ts +3 -1
- package/src/input/commands/shell-core.ts +20 -6
- package/src/input/commands.ts +2 -2
- package/src/input/delete-key-policy.ts +46 -0
- package/src/input/feed-context-factory.ts +10 -0
- package/src/input/handler-feed.ts +87 -0
- package/src/input/handler-modal-routes.ts +6 -1
- package/src/input/handler.ts +5 -0
- package/src/input/panel-paste-flood-guard.ts +94 -0
- package/src/input/settings-modal-types.ts +5 -3
- package/src/input/settings-modal.ts +21 -0
- package/src/main.ts +36 -36
- package/src/permissions/approval-posture.ts +141 -0
- package/src/renderer/agent-workspace-context-lines.ts +10 -2
- package/src/renderer/compositor.ts +27 -4
- package/src/renderer/diff.ts +61 -18
- package/src/renderer/fullscreen-primitives.ts +37 -18
- package/src/renderer/markdown.ts +20 -10
- package/src/renderer/modal-factory.ts +25 -15
- package/src/renderer/overlay-box.ts +23 -12
- package/src/renderer/process-indicator.ts +8 -3
- package/src/renderer/prompt-content-width.ts +16 -0
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +2 -0
- package/src/renderer/startup-theme-probe.ts +35 -0
- package/src/renderer/status-glyphs.ts +11 -15
- package/src/renderer/system-message.ts +17 -2
- package/src/renderer/term-caps.ts +318 -0
- package/src/renderer/terminal-bg-probe.ts +373 -0
- package/src/renderer/terminal-escapes.ts +24 -0
- package/src/renderer/theme-mode-config.ts +87 -0
- package/src/renderer/theme.ts +241 -0
- package/src/renderer/thinking.ts +12 -3
- package/src/renderer/tool-call.ts +7 -3
- package/src/renderer/ui-factory.ts +92 -36
- package/src/renderer/ui-primitives.ts +33 -93
- package/src/runtime/bootstrap-core.ts +15 -0
- package/src/runtime/bootstrap-hook-bridge.ts +6 -0
- package/src/runtime/bootstrap-shell.ts +2 -0
- package/src/runtime/bootstrap.ts +68 -5
- package/src/runtime/calendar-boot-refresh.ts +105 -0
- package/src/runtime/lan-scan-consent.ts +253 -0
- package/src/runtime/services.ts +127 -2
- package/src/runtime/session-spine-rest-transport.ts +160 -0
- package/src/runtime/terminal-output-guard.ts +6 -1
- package/src/runtime/ui-services.ts +3 -0
- package/src/shell/agent-workspace-fullscreen.ts +5 -0
- package/src/shell/terminal-focus-mode.ts +120 -0
- package/src/shell/ui-openers.ts +13 -4
- package/src/tools/agent-harness-autonomy-intake.ts +5 -5
- package/src/tools/agent-harness-autonomy-queue.ts +1 -1
- package/src/tools/agent-harness-background-processes.ts +2 -1
- package/src/tools/agent-harness-metadata.ts +16 -8
- package/src/tools/agent-harness-operator-methods.ts +44 -8
- package/src/tools/agent-harness-personal-ops-discovery.ts +57 -10
- package/src/tools/agent-harness-personal-ops-lanes.ts +17 -5
- package/src/tools/agent-harness-personal-ops-operations.ts +7 -7
- package/src/tools/agent-harness-personal-ops-types.ts +6 -0
- package/src/tools/agent-harness-prompt-context.ts +26 -12
- package/src/tools/agent-harness-workspace-actions.ts +4 -0
- package/src/tools/agent-local-registry-args.ts +117 -0
- package/src/tools/agent-local-registry-memory.ts +227 -0
- package/src/tools/agent-local-registry-tool.ts +19 -237
- package/src/tools/agent-operator-briefing-tool.ts +2 -2
- package/src/tools/agent-operator-method-tool.ts +13 -0
- package/src/tools/agent-policy-explanation.ts +39 -4
- package/src/tools/agent-schedule-tool.ts +5 -5
- package/src/utils/terminal-width.ts +98 -1
- package/src/version.ts +1 -1
- package/src/cli/local-library-command.ts +0 -825
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MemoryApi } from '@pellux/goodvibes-sdk/platform/knowledge';
|
|
2
2
|
import { getTierForContextWindow, getTierPromptSupplement } from '@pellux/goodvibes-sdk/platform/providers';
|
|
3
3
|
import type { MemoryRecord } from '@pellux/goodvibes-sdk/platform/state';
|
|
4
|
-
import { isPromptActiveMemory, MIN_PROMPT_MEMORY_CONFIDENCE } from '../agent/memory-prompt.ts';
|
|
4
|
+
import { describeMemoryPromptEligibility, isPromptActiveMemory, MIN_PROMPT_MEMORY_CONFIDENCE } from '../agent/memory-prompt.ts';
|
|
5
5
|
import { AgentPersonaRegistry, buildActivePersonaPrompt } from '../agent/persona-registry.ts';
|
|
6
6
|
import { buildProjectContextPrompt, discoverProjectContextFiles } from '../agent/project-context-files.ts';
|
|
7
7
|
import { AgentRoutineRegistry, buildEnabledRoutinesPrompt, evaluateAgentRoutineReadiness } from '../agent/routine-registry.ts';
|
|
@@ -229,6 +229,14 @@ function memorySegment(memory: PromptMemoryApi | undefined, includeParameters: b
|
|
|
229
229
|
suppressedCount: suppressed.length,
|
|
230
230
|
promptChars: promptText.length,
|
|
231
231
|
promptText,
|
|
232
|
+
// F7c: this segment orders the eligible set by stored confidence/recency
|
|
233
|
+
// (memorySort), NOT by relevance to the current turn. The `context
|
|
234
|
+
// action:"receipt"` surface (prompt-context-receipts.ts) additionally
|
|
235
|
+
// ranks the same eligible set by per-turn semantic relevance when that
|
|
236
|
+
// scoring succeeds. Stating the ordering basis here up front keeps the
|
|
237
|
+
// two memory surfaces from reading as contradictory when their orders
|
|
238
|
+
// differ for the same eligible records.
|
|
239
|
+
note: 'Ordered by stored confidence/recency, not by relevance to the current turn — see context action:"receipt" for per-turn relevance scoring.',
|
|
232
240
|
route: 'memory action:"status"',
|
|
233
241
|
selected: active.map((record) => ({
|
|
234
242
|
id: record.id,
|
|
@@ -237,17 +245,23 @@ function memorySegment(memory: PromptMemoryApi | undefined, includeParameters: b
|
|
|
237
245
|
confidence: record.confidence,
|
|
238
246
|
inspectRoute: `agent_local_registry domain:"memory" action:"get" recordId:"${record.id}"`,
|
|
239
247
|
})),
|
|
240
|
-
suppressed: suppressed.slice(0, 12).map((record) =>
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
suppressed: suppressed.slice(0, 12).map((record) => {
|
|
249
|
+
// Honest, per-record reason straight from describeMemoryPromptEligibility (Wave-4
|
|
250
|
+
// W4-A1B) — the same wording source prompt-context-receipts.ts uses, never a
|
|
251
|
+
// locally invented "not reviewed"/"outside prompt limit" guess. A record here
|
|
252
|
+
// either genuinely failed eligibility, or passed it but was cut by the top-10
|
|
253
|
+
// prompt slice — those read differently.
|
|
254
|
+
const eligibility = describeMemoryPromptEligibility(record);
|
|
255
|
+
return {
|
|
256
|
+
id: record.id,
|
|
257
|
+
reviewState: record.reviewState,
|
|
258
|
+
confidence: record.confidence,
|
|
259
|
+
reason: eligibility.eligible
|
|
260
|
+
? `eligible (${eligibility.reason}) but outside the top-10 prompt slice — budget-limited, not a trust problem`
|
|
261
|
+
: eligibility.reason,
|
|
262
|
+
inspectRoute: `agent_local_registry domain:"memory" action:"get" recordId:"${record.id}"`,
|
|
263
|
+
};
|
|
264
|
+
}),
|
|
251
265
|
}, includeParameters);
|
|
252
266
|
}
|
|
253
267
|
|
|
@@ -29,6 +29,7 @@ export interface WorkspaceEditorContext {
|
|
|
29
29
|
readonly selectedRoutine: AgentWorkspaceLocalLibraryItem | null;
|
|
30
30
|
readonly recentReviewerHandoffArtifacts: AgentWorkspaceRuntimeSnapshot['recentReviewerHandoffArtifacts'];
|
|
31
31
|
readonly reviewPacketDefaults: AgentWorkspaceRuntimeSnapshot['reviewPacketDefaults'] | null;
|
|
32
|
+
readonly calendarOAuthConfigStatus: AgentWorkspaceRuntimeSnapshot['calendarOAuthConfigStatus'] | null;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface WorkspaceActionLookup {
|
|
@@ -247,6 +248,7 @@ export function buildWorkspaceEditorContext(context: CommandContext, args: Agent
|
|
|
247
248
|
selectedRoutine: selectedRoutineFromArgs(snapshot, args),
|
|
248
249
|
recentReviewerHandoffArtifacts: snapshot.recentReviewerHandoffArtifacts,
|
|
249
250
|
reviewPacketDefaults: snapshot.reviewPacketDefaults,
|
|
251
|
+
calendarOAuthConfigStatus: snapshot.calendarOAuthConfigStatus,
|
|
250
252
|
};
|
|
251
253
|
} catch {
|
|
252
254
|
return {
|
|
@@ -254,6 +256,7 @@ export function buildWorkspaceEditorContext(context: CommandContext, args: Agent
|
|
|
254
256
|
selectedRoutine: null,
|
|
255
257
|
recentReviewerHandoffArtifacts: [],
|
|
256
258
|
reviewPacketDefaults: null,
|
|
259
|
+
calendarOAuthConfigStatus: null,
|
|
257
260
|
};
|
|
258
261
|
}
|
|
259
262
|
}
|
|
@@ -267,6 +270,7 @@ export function createWorkspaceEditor(
|
|
|
267
270
|
selectedRoutine: editorKind === 'routine-schedule' ? editorContext?.selectedRoutine ?? null : null,
|
|
268
271
|
recentReviewerHandoffArtifacts: editorContext?.recentReviewerHandoffArtifacts ?? [],
|
|
269
272
|
reviewPacketDefaults: editorContext?.reviewPacketDefaults ?? null,
|
|
273
|
+
calendarOAuthConfigStatus: editorContext?.calendarOAuthConfigStatus ?? null,
|
|
270
274
|
});
|
|
271
275
|
}
|
|
272
276
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared arg-reading/validation helpers for the agent_local_registry tool.
|
|
3
|
+
*
|
|
4
|
+
* Split out of agent-local-registry-tool.ts to stay under the 800-line
|
|
5
|
+
* architecture cap. These are generic across every domain (memory, note,
|
|
6
|
+
* persona, skill, skill_bundle, routine) — no domain-specific logic lives
|
|
7
|
+
* here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface AgentLocalRegistryToolArgs {
|
|
11
|
+
readonly domain?: unknown;
|
|
12
|
+
readonly action?: unknown;
|
|
13
|
+
readonly id?: unknown;
|
|
14
|
+
readonly query?: unknown;
|
|
15
|
+
readonly semantic?: unknown;
|
|
16
|
+
readonly cls?: unknown;
|
|
17
|
+
readonly scope?: unknown;
|
|
18
|
+
readonly summary?: unknown;
|
|
19
|
+
readonly detail?: unknown;
|
|
20
|
+
readonly confidence?: unknown;
|
|
21
|
+
readonly title?: unknown;
|
|
22
|
+
readonly name?: unknown;
|
|
23
|
+
readonly description?: unknown;
|
|
24
|
+
readonly body?: unknown;
|
|
25
|
+
readonly sourceUrl?: unknown;
|
|
26
|
+
readonly procedure?: unknown;
|
|
27
|
+
readonly steps?: unknown;
|
|
28
|
+
readonly skills?: unknown;
|
|
29
|
+
readonly skillIds?: unknown;
|
|
30
|
+
readonly requiresEnv?: unknown;
|
|
31
|
+
readonly requiresCommands?: unknown;
|
|
32
|
+
readonly triggers?: unknown;
|
|
33
|
+
readonly tags?: unknown;
|
|
34
|
+
readonly reason?: unknown;
|
|
35
|
+
readonly enabled?: unknown;
|
|
36
|
+
readonly activate?: unknown;
|
|
37
|
+
readonly provenance?: unknown;
|
|
38
|
+
readonly confirm?: unknown;
|
|
39
|
+
readonly explicitUserRequest?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const AGENT_TOOL_PROVENANCE = 'agent-local-registry-tool';
|
|
43
|
+
|
|
44
|
+
export function readString(value: unknown): string {
|
|
45
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function readAffirmative(value: unknown): boolean {
|
|
49
|
+
const normalized = readString(value).toLowerCase();
|
|
50
|
+
return value === true || (typeof value === 'string' && (normalized === '' || normalized === 'yes' || normalized === 'y' || normalized === 'true' || normalized === 'on'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function readStringList(value: unknown): readonly string[] {
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
return value.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
if (!Array.isArray(value)) return [];
|
|
58
|
+
return value.filter((entry): entry is string => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function readOptionalNumber(value: unknown): number | undefined {
|
|
62
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
63
|
+
if (typeof value !== 'string' || !value.trim()) return undefined;
|
|
64
|
+
const parsed = Number(value);
|
|
65
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function readOptionalConfidence(value: unknown): number | undefined {
|
|
69
|
+
const confidence = readOptionalNumber(value);
|
|
70
|
+
if (confidence === undefined) return undefined;
|
|
71
|
+
if (confidence < 0 || confidence > 100) throw new Error('confidence must be between 0 and 100.');
|
|
72
|
+
return confidence;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function registryError(message: string): { readonly success: false; readonly error: string } {
|
|
76
|
+
return { success: false, error: message };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function registryOutput(output: string): { readonly success: true; readonly output: string } {
|
|
80
|
+
return { success: true, output };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function requireId(args: AgentLocalRegistryToolArgs): string {
|
|
84
|
+
const id = readString(args.id);
|
|
85
|
+
if (!id) throw new Error('id is required.');
|
|
86
|
+
return id;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function requireConfirmedDelete(args: AgentLocalRegistryToolArgs, label: string): void {
|
|
90
|
+
const explicitUserRequest = readString(args.explicitUserRequest);
|
|
91
|
+
if (!explicitUserRequest) throw new Error(`${label} deletion requires explicitUserRequest with the user's exact request or a short faithful summary.`);
|
|
92
|
+
if (args.confirm !== true) throw new Error(`${label} deletion requires confirm:true after an explicit user request.`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function requireName(args: AgentLocalRegistryToolArgs): string {
|
|
96
|
+
const name = readString(args.name);
|
|
97
|
+
if (!name) throw new Error('name is required.');
|
|
98
|
+
return name;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function requireDescription(args: AgentLocalRegistryToolArgs): string {
|
|
102
|
+
const description = readString(args.description);
|
|
103
|
+
if (!description) throw new Error('description is required.');
|
|
104
|
+
return description;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function requireTextField(value: unknown, fieldName: string): string {
|
|
108
|
+
const text = readString(value);
|
|
109
|
+
if (!text) throw new Error(`${fieldName} is required.`);
|
|
110
|
+
return text;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function requireSummary(args: AgentLocalRegistryToolArgs): string {
|
|
114
|
+
const summary = readString(args.summary || args.description);
|
|
115
|
+
if (!summary) throw new Error('summary is required.');
|
|
116
|
+
return summary;
|
|
117
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-local memory domain handler (W4-A1: memory honesty).
|
|
3
|
+
*
|
|
4
|
+
* Split out of agent-local-registry-tool.ts to stay under the 800-line
|
|
5
|
+
* architecture cap. Owns the `memory` domain of the agent_local_registry
|
|
6
|
+
* tool: list/search/get/create/update/review/stale/delete.
|
|
7
|
+
*
|
|
8
|
+
* The `search` action defaults to semantic recall (registry.searchSemantic)
|
|
9
|
+
* instead of the old literal LIKE-scan default — natural-language recall must
|
|
10
|
+
* not depend on the query text appearing as a literal substring in the
|
|
11
|
+
* stored summary/detail. Index health is checked before every semantic
|
|
12
|
+
* search so an unavailable index degrades honestly (stated reason, literal
|
|
13
|
+
* fallback) instead of silently returning zero matches that read as "nothing
|
|
14
|
+
* was ever stored."
|
|
15
|
+
*/
|
|
16
|
+
import {
|
|
17
|
+
HASHED_MEMORY_EMBEDDING_PROVIDER,
|
|
18
|
+
MemoryRegistry,
|
|
19
|
+
type MemoryClass,
|
|
20
|
+
type MemoryRecord,
|
|
21
|
+
type MemoryScope,
|
|
22
|
+
type MemorySemanticSearchResult,
|
|
23
|
+
type MemoryVectorStats,
|
|
24
|
+
} from '@pellux/goodvibes-sdk/platform/state';
|
|
25
|
+
import { assertNoSecretLikeMemoryText } from '../agent/memory-safety.ts';
|
|
26
|
+
import { formatAgentRecordReference, formatAgentRecordReviewState } from '../agent/record-labels.ts';
|
|
27
|
+
import {
|
|
28
|
+
AGENT_TOOL_PROVENANCE,
|
|
29
|
+
type AgentLocalRegistryToolArgs,
|
|
30
|
+
readOptionalConfidence,
|
|
31
|
+
readString,
|
|
32
|
+
readStringList,
|
|
33
|
+
requireConfirmedDelete,
|
|
34
|
+
requireId,
|
|
35
|
+
requireSummary,
|
|
36
|
+
} from './agent-local-registry-args.ts';
|
|
37
|
+
// `import type` is erased at compile time, so this does not create a runtime
|
|
38
|
+
// circular dependency even though agent-local-registry-tool.ts imports
|
|
39
|
+
// handleMemory (below) from this module.
|
|
40
|
+
import type { AgentLocalRegistryAction } from './agent-local-registry-tool.ts';
|
|
41
|
+
|
|
42
|
+
export const MEMORY_CLASSES: readonly MemoryClass[] = ['decision', 'constraint', 'incident', 'pattern', 'fact', 'risk', 'runbook', 'architecture', 'ownership'];
|
|
43
|
+
export const MEMORY_SCOPES: readonly MemoryScope[] = ['session', 'project', 'team'];
|
|
44
|
+
|
|
45
|
+
function isMemoryClass(value: unknown): value is MemoryClass {
|
|
46
|
+
return typeof value === 'string' && MEMORY_CLASSES.includes(value as MemoryClass);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isMemoryScope(value: unknown): value is MemoryScope {
|
|
50
|
+
return typeof value === 'string' && MEMORY_SCOPES.includes(value as MemoryScope);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requireMemoryClass(args: AgentLocalRegistryToolArgs): MemoryClass {
|
|
54
|
+
const cls = args.cls || 'fact';
|
|
55
|
+
if (!isMemoryClass(cls)) throw new Error(`Invalid memory class. Valid values ${MEMORY_CLASSES.join(', ')}.`);
|
|
56
|
+
return cls;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readMemoryScope(args: AgentLocalRegistryToolArgs): MemoryScope {
|
|
60
|
+
const scope = args.scope || 'project';
|
|
61
|
+
if (!isMemoryScope(scope)) throw new Error(`Invalid memory scope. Valid values ${MEMORY_SCOPES.join(', ')}.`);
|
|
62
|
+
return scope;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatMemory(record: MemoryRecord): string {
|
|
66
|
+
const tags = record.tags.length > 0 ? ` tags ${record.tags.join(', ')}` : '';
|
|
67
|
+
return `${record.id} ${record.scope}/${record.cls} ${formatAgentRecordReviewState(record.reviewState)} ${record.confidence}%${tags} ${record.summary}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function formatMemoryMatch(entry: MemorySemanticSearchResult): string {
|
|
71
|
+
const matchLabel = entry.similarity > 0 ? ` match ${Math.round(entry.similarity * 100)}% (score ${Math.round(entry.score)})` : '';
|
|
72
|
+
return `${formatMemory(entry.record)}${matchLabel}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A HARD unavailable reason: the semantic index cannot be consulted at all, so the
|
|
77
|
+
* search action must fall back to a literal LIKE-scan and say so — never a silent
|
|
78
|
+
* empty result that reads as "no memory matches" when the index was never asked.
|
|
79
|
+
*/
|
|
80
|
+
function describeMemoryIndexUnavailable(stats: MemoryVectorStats): string | null {
|
|
81
|
+
if (!stats.enabled) return 'the semantic memory index is disabled for this store';
|
|
82
|
+
if (!stats.available) return `the semantic memory index is unavailable${stats.error ? `: ${stats.error}` : ''}`;
|
|
83
|
+
if (stats.indexedRecords === 0) return 'the semantic memory index has no indexed records yet';
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* A SOFT caveat: the index is up and consulted, but it is running on the built-in
|
|
89
|
+
* hashed-only fallback embedding provider (bag-of-words/token hashing) rather than a
|
|
90
|
+
* real embedding model. That still ranks real matches meaningfully better than a
|
|
91
|
+
* literal substring scan, but it is not a modeled semantic understanding — say so
|
|
92
|
+
* rather than presenting it as equivalent to a configured semantic provider.
|
|
93
|
+
*/
|
|
94
|
+
function describeMemoryIndexCaveat(stats: MemoryVectorStats): string | null {
|
|
95
|
+
if (stats.embeddingProviderId === HASHED_MEMORY_EMBEDDING_PROVIDER.id) {
|
|
96
|
+
return 'running on the hashed-only fallback embedding provider (no dedicated semantic model configured) — ranking is approximate, not modeled semantic understanding';
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function renderMemorySearch(mode: string, query: string, lines: readonly string[]): string {
|
|
102
|
+
if (lines.length === 0) {
|
|
103
|
+
return `Agent-local memory search (${mode})\nNo Agent-local memory records matched "${query}".`;
|
|
104
|
+
}
|
|
105
|
+
return [`Agent-local memory search (${mode})`, `query ${query || '(all)'}`, ...lines].join('\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function handleMemory(registry: MemoryRegistry, action: AgentLocalRegistryAction, args: AgentLocalRegistryToolArgs): Promise<string> {
|
|
109
|
+
if (action === 'list') {
|
|
110
|
+
const records = registry.getAll();
|
|
111
|
+
return records.length === 0
|
|
112
|
+
? 'Agent-local memory\nNo Agent-local memory records.'
|
|
113
|
+
: ['Agent-local memory', ...records.map(formatMemory)].join('\n');
|
|
114
|
+
}
|
|
115
|
+
if (action === 'search') {
|
|
116
|
+
const query = readString(args.query);
|
|
117
|
+
const wantsLiteral = args.semantic === false;
|
|
118
|
+
if (wantsLiteral) {
|
|
119
|
+
const records = registry.search({ query, limit: 10 });
|
|
120
|
+
return renderMemorySearch('literal — explicitly requested', query, records.map(formatMemory));
|
|
121
|
+
}
|
|
122
|
+
// SEMANTIC BY DEFAULT: natural-language recall must not depend on the query text
|
|
123
|
+
// appearing as a literal substring in the stored summary/detail. Check index
|
|
124
|
+
// health FIRST so an unavailable index degrades honestly instead of silently
|
|
125
|
+
// returning zero matches that read as "nothing was ever stored."
|
|
126
|
+
const stats = registry.vectorStats();
|
|
127
|
+
const unavailable = describeMemoryIndexUnavailable(stats);
|
|
128
|
+
if (unavailable) {
|
|
129
|
+
const records = registry.search({ query, limit: 10 });
|
|
130
|
+
return renderMemorySearch(`literal fallback — ${unavailable}`, query, records.map(formatMemory));
|
|
131
|
+
}
|
|
132
|
+
const results = registry.searchSemantic({ query, limit: 10 });
|
|
133
|
+
const consultedSemanticIndex = query.length > 0 && results.some((entry) => entry.similarity > 0);
|
|
134
|
+
const caveat = describeMemoryIndexCaveat(stats);
|
|
135
|
+
const mode = !consultedSemanticIndex && query.length > 0
|
|
136
|
+
? 'semantic — no vector match for this query, ranked by stored confidence instead'
|
|
137
|
+
: caveat
|
|
138
|
+
? `semantic — ${caveat}`
|
|
139
|
+
: 'semantic';
|
|
140
|
+
return renderMemorySearch(mode, query, results.map(formatMemoryMatch));
|
|
141
|
+
}
|
|
142
|
+
if (action === 'get') {
|
|
143
|
+
const record = registry.get(requireId(args));
|
|
144
|
+
if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
|
|
145
|
+
return [
|
|
146
|
+
formatMemory(record),
|
|
147
|
+
`created ${new Date(record.createdAt).toISOString()}`,
|
|
148
|
+
`updated ${new Date(record.updatedAt).toISOString()}`,
|
|
149
|
+
`origin ${record.provenance.map((entry) => formatAgentRecordReference({ kind: String(entry.kind), ref: String(entry.ref) })).join(', ') || '(none)'}`,
|
|
150
|
+
`provenance: ${record.provenance.map((entry) => `${String(entry.kind)}:${String(entry.ref)}`).join(',') || '(none)'}`,
|
|
151
|
+
'',
|
|
152
|
+
record.detail || '(no detail)',
|
|
153
|
+
].join('\n');
|
|
154
|
+
}
|
|
155
|
+
if (action === 'create') {
|
|
156
|
+
const summary = requireSummary(args);
|
|
157
|
+
const detail = readString(args.detail || args.body);
|
|
158
|
+
const tags = readStringList(args.tags);
|
|
159
|
+
const confidence = readOptionalConfidence(args.confidence);
|
|
160
|
+
assertNoSecretLikeMemoryText([summary, detail, ...tags]);
|
|
161
|
+
const record = await registry.add({
|
|
162
|
+
scope: readMemoryScope(args),
|
|
163
|
+
cls: requireMemoryClass(args),
|
|
164
|
+
summary,
|
|
165
|
+
detail,
|
|
166
|
+
tags: [...tags],
|
|
167
|
+
...(confidence === undefined ? {} : { review: { state: 'fresh' as const, confidence } }),
|
|
168
|
+
provenance: [{ kind: 'event', ref: readString(args.provenance) || AGENT_TOOL_PROVENANCE }],
|
|
169
|
+
});
|
|
170
|
+
return [
|
|
171
|
+
'Created Agent-local memory',
|
|
172
|
+
` id ${record.id}`,
|
|
173
|
+
` summary ${record.summary}`,
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
if (action === 'update') {
|
|
177
|
+
const summary = readString(args.summary || args.description);
|
|
178
|
+
const detail = readString(args.detail || args.body);
|
|
179
|
+
const tags = args.tags === undefined ? undefined : [...readStringList(args.tags)];
|
|
180
|
+
assertNoSecretLikeMemoryText([summary, detail, ...(tags ?? [])]);
|
|
181
|
+
const record = registry.update(requireId(args), {
|
|
182
|
+
summary: summary || undefined,
|
|
183
|
+
detail: detail || undefined,
|
|
184
|
+
tags,
|
|
185
|
+
scope: args.scope === undefined ? undefined : readMemoryScope(args),
|
|
186
|
+
});
|
|
187
|
+
if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
|
|
188
|
+
return [
|
|
189
|
+
'Updated Agent-local memory',
|
|
190
|
+
` id ${record.id}`,
|
|
191
|
+
` summary ${record.summary}`,
|
|
192
|
+
].join('\n');
|
|
193
|
+
}
|
|
194
|
+
if (action === 'review') {
|
|
195
|
+
const record = registry.review(requireId(args), {
|
|
196
|
+
state: 'reviewed',
|
|
197
|
+
confidence: readOptionalConfidence(args.confidence),
|
|
198
|
+
reviewedBy: 'agent',
|
|
199
|
+
});
|
|
200
|
+
if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
|
|
201
|
+
return [
|
|
202
|
+
'Reviewed Agent-local memory',
|
|
203
|
+
` id ${record.id}`,
|
|
204
|
+
].join('\n');
|
|
205
|
+
}
|
|
206
|
+
if (action === 'stale') {
|
|
207
|
+
const record = registry.review(requireId(args), {
|
|
208
|
+
state: 'stale',
|
|
209
|
+
staleReason: readString(args.reason) || 'Marked stale by Agent.',
|
|
210
|
+
});
|
|
211
|
+
if (!record) return `Unknown Agent-local memory ${readString(args.id)}`;
|
|
212
|
+
return [
|
|
213
|
+
'Marked Agent-local memory stale',
|
|
214
|
+
` id ${record.id}`,
|
|
215
|
+
].join('\n');
|
|
216
|
+
}
|
|
217
|
+
if (action === 'delete') {
|
|
218
|
+
const id = requireId(args);
|
|
219
|
+
requireConfirmedDelete(args, 'Agent-local memory');
|
|
220
|
+
if (!registry.delete(id)) return `Unknown Agent-local memory ${id}`;
|
|
221
|
+
return [
|
|
222
|
+
'Deleted Agent-local memory',
|
|
223
|
+
` id ${id}`,
|
|
224
|
+
].join('\n');
|
|
225
|
+
}
|
|
226
|
+
throw new Error(`Action ${action} is not valid for memory.`);
|
|
227
|
+
}
|