@phuetz/code-buddy 1.1.0 → 1.2.0
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/README.md +160 -165
- package/dist/agent/autonomous/agentic-coding-contract.d.ts +6 -6
- package/dist/agent/codebuddy-agent.d.ts +1 -1
- package/dist/agent/codebuddy-agent.js +69 -14
- package/dist/agent/execution/agent-executor.js +122 -12
- package/dist/agent/execution/tool-dependency-graph.js +7 -0
- package/dist/agent/execution/tool-hooks.d.ts +6 -0
- package/dist/agent/execution/tool-hooks.js +13 -5
- package/dist/agent/execution/tool-selection-strategy.d.ts +11 -2
- package/dist/agent/execution/tool-selection-strategy.js +67 -10
- package/dist/agent/extended-thinking.d.ts +8 -0
- package/dist/agent/extended-thinking.js +27 -0
- package/dist/agent/hermes-browser-backends.js +151 -11
- package/dist/agent/hermes-claw-migrate.d.ts +59 -0
- package/dist/agent/hermes-claw-migrate.js +427 -26
- package/dist/agent/hermes-parity-manifest.js +78 -71
- package/dist/agent/hermes-runtime-lifecycle.d.ts +67 -0
- package/dist/agent/hermes-runtime-lifecycle.js +468 -0
- package/dist/agent/middleware/index.d.ts +1 -0
- package/dist/agent/middleware/index.js +1 -0
- package/dist/agent/middleware/visual-validation-middleware.d.ts +17 -0
- package/dist/agent/middleware/visual-validation-middleware.js +67 -0
- package/dist/agent/model-benchmark.d.ts +77 -0
- package/dist/agent/model-benchmark.js +309 -0
- package/dist/agent/model-tier.d.ts +14 -0
- package/dist/agent/model-tier.js +71 -0
- package/dist/agent/multi-agent/agents/orchestrator-agent.js +17 -6
- package/dist/agent/multi-agent/multi-agent-system.js +3 -1
- package/dist/agent/multi-agent/session-fleet-bridge.d.ts +2 -0
- package/dist/agent/multi-agent/session-fleet-bridge.js +9 -1
- package/dist/agent/multi-agent/session-tools.js +22 -5
- package/dist/agent/reasoning/mcts.js +20 -6
- package/dist/agent/session-end-flush.d.ts +6 -1
- package/dist/agent/session-end-flush.js +20 -4
- package/dist/agent/streaming/streaming-handler.d.ts +7 -0
- package/dist/agent/streaming/streaming-handler.js +8 -1
- package/dist/agent/tool-executor.js +1 -0
- package/dist/agent/tool-handler.d.ts +5 -0
- package/dist/agent/tool-handler.js +130 -4
- package/dist/browser-automation/browser-operator-executor.js +15 -0
- package/dist/browser-automation/browser-use-runner.d.ts +96 -0
- package/dist/browser-automation/browser-use-runner.js +492 -0
- package/dist/browser-automation/camofox-runner.d.ts +107 -0
- package/dist/browser-automation/camofox-runner.js +287 -0
- package/dist/channels/dingtalk/index.js +3 -0
- package/dist/channels/discord/client.d.ts +9 -0
- package/dist/channels/discord/client.js +12 -0
- package/dist/channels/feishu/index.d.ts +167 -1
- package/dist/channels/feishu/index.js +432 -7
- package/dist/channels/gateway-lifecycle.d.ts +132 -0
- package/dist/channels/gateway-lifecycle.js +219 -0
- package/dist/channels/google-chat/index.d.ts +5 -1
- package/dist/channels/google-chat/index.js +5 -1
- package/dist/channels/imessage/index.d.ts +14 -0
- package/dist/channels/imessage/index.js +64 -17
- package/dist/channels/index.d.ts +4 -0
- package/dist/channels/index.js +4 -0
- package/dist/channels/irc/index.d.ts +73 -3
- package/dist/channels/irc/index.js +446 -11
- package/dist/channels/line/index.js +3 -0
- package/dist/channels/mattermost/index.d.ts +51 -4
- package/dist/channels/mattermost/index.js +303 -20
- package/dist/channels/nextcloud-talk/index.d.ts +89 -8
- package/dist/channels/nextcloud-talk/index.js +367 -16
- package/dist/channels/nostr/index.d.ts +121 -1
- package/dist/channels/nostr/index.js +396 -8
- package/dist/channels/ntfy/index.js +3 -0
- package/dist/channels/qq/index.js +3 -0
- package/dist/channels/slash-parity.d.ts +117 -0
- package/dist/channels/slash-parity.js +185 -0
- package/dist/channels/synology-chat/index.js +3 -0
- package/dist/channels/teams/index.d.ts +3 -0
- package/dist/channels/teams/index.js +3 -0
- package/dist/channels/telegram/client.d.ts +9 -0
- package/dist/channels/telegram/client.js +12 -0
- package/dist/channels/twilio-voice/index.js +3 -0
- package/dist/channels/webchat/index.d.ts +7 -1
- package/dist/channels/webchat/index.js +7 -1
- package/dist/channels/wecom/index.js +3 -0
- package/dist/channels/weixin/index.js +3 -0
- package/dist/channels/zalo/index.js +3 -0
- package/dist/cli/config-loader.js +1 -1
- package/dist/codebuddy/client.d.ts +30 -1
- package/dist/codebuddy/client.js +167 -20
- package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +5 -0
- package/dist/codebuddy/providers/provider-chatgpt-responses.js +1 -0
- package/dist/codebuddy/providers/provider-gemini-native.js +1 -1
- package/dist/codebuddy/providers/provider-openai-compat.d.ts +5 -0
- package/dist/codebuddy/providers/provider-openai-compat.js +82 -2
- package/dist/codebuddy/tool-definitions/computer-control-tools.js +32 -2
- package/dist/codebuddy/tool-definitions/core-tools.js +3 -3
- package/dist/codebuddy/tool-definitions/index.d.ts +2 -1
- package/dist/codebuddy/tool-definitions/index.js +2 -1
- package/dist/codebuddy/tool-definitions/multimodal-tools.d.ts +1 -0
- package/dist/codebuddy/tool-definitions/multimodal-tools.js +44 -1
- package/dist/codebuddy/tool-definitions/windows-tools.d.ts +2 -0
- package/dist/codebuddy/tool-definitions/windows-tools.js +34 -0
- package/dist/codebuddy/tools.d.ts +1 -1
- package/dist/codebuddy/tools.js +27 -24
- package/dist/commands/cli/hermes-commands.js +108 -0
- package/dist/commands/cli/native-engine-commands.js +102 -3
- package/dist/commands/cli/utility-commands.js +45 -0
- package/dist/commands/enhanced-command-handler.js +1 -1
- package/dist/commands/goal-cli.d.ts +30 -0
- package/dist/commands/goal-cli.js +210 -27
- package/dist/commands/handlers/goal-handler.d.ts +9 -2
- package/dist/commands/handlers/goal-handler.js +36 -9
- package/dist/commands/handlers/infra-handlers.js +36 -0
- package/dist/commands/handlers/memory-handlers.js +96 -1
- package/dist/commands/headless-slash.d.ts +2 -0
- package/dist/commands/headless-slash.js +12 -0
- package/dist/commands/llm-provider-resolution.d.ts +5 -3
- package/dist/commands/llm-provider-resolution.js +87 -33
- package/dist/commands/ollama.d.ts +25 -0
- package/dist/commands/ollama.js +100 -0
- package/dist/commands/provider.d.ts +5 -0
- package/dist/commands/provider.js +106 -55
- package/dist/commands/spec-next.js +2 -1
- package/dist/commands/spec-plan.js +5 -16
- package/dist/commands/tunnel.d.ts +2 -0
- package/dist/commands/tunnel.js +48 -0
- package/dist/config/config-resolver.d.ts +2 -1
- package/dist/config/config-resolver.js +54 -41
- package/dist/config/constants.d.ts +28 -0
- package/dist/config/constants.js +7 -0
- package/dist/config/env-schema.js +756 -0
- package/dist/config/model-tools.js +4 -4
- package/dist/config/toml-config.d.ts +16 -0
- package/dist/config/toml-config.js +3 -0
- package/dist/context/context-manager-v2.js +1 -0
- package/dist/daemon/autonomous-daemon.d.ts +1 -1
- package/dist/daemon/autonomous-daemon.js +3 -3
- package/dist/daemon/autonomous-loop.js +11 -2
- package/dist/daemon/colab-goal.js +9 -1
- package/dist/daemon/cron-agent-bridge.d.ts +12 -3
- package/dist/daemon/cron-agent-bridge.js +25 -9
- package/dist/desktop/codebuddy-engine-adapter.d.ts +32 -0
- package/dist/desktop/codebuddy-engine-adapter.js +257 -80
- package/dist/desktop/engine-adapter.d.ts +14 -0
- package/dist/desktop-automation/automation-manager.js +16 -0
- package/dist/desktop-automation/omniparser-runner.d.ts +63 -0
- package/dist/desktop-automation/omniparser-runner.js +115 -0
- package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
- package/dist/desktop-automation/smart-snapshot.js +72 -5
- package/dist/doctor/index.js +40 -23
- package/dist/events/types.d.ts +11 -0
- package/dist/fleet/colab-store.js +14 -2
- package/dist/fleet/peer-session-bridge.js +50 -12
- package/dist/fleet/peer-tool-bridge.js +14 -0
- package/dist/goals/goal-decomposer.d.ts +27 -0
- package/dist/goals/goal-decomposer.js +293 -0
- package/dist/goals/goal-judge-client.d.ts +15 -0
- package/dist/goals/goal-judge-client.js +45 -0
- package/dist/goals/goal-judge.d.ts +2 -0
- package/dist/goals/goal-judge.js +35 -6
- package/dist/goals/goal-loop.d.ts +14 -0
- package/dist/goals/goal-loop.js +47 -2
- package/dist/goals/goal-manager.d.ts +6 -0
- package/dist/goals/goal-manager.js +67 -14
- package/dist/goals/goal-state.d.ts +13 -0
- package/dist/goals/goal-state.js +83 -13
- package/dist/goals/goal-store.d.ts +3 -0
- package/dist/goals/goal-store.js +31 -15
- package/dist/hooks/user-hooks.js +17 -3
- package/dist/index.js +464 -25
- package/dist/input/text-to-speech.d.ts +2 -6
- package/dist/input/text-to-speech.js +2 -27
- package/dist/integrations/screenpipe/screenpipe-client.d.ts +10 -0
- package/dist/integrations/screenpipe/screenpipe-client.js +12 -2
- package/dist/integrations/tailscale.d.ts +13 -0
- package/dist/integrations/tailscale.js +87 -34
- package/dist/knowledge/workspace-indexer.js +53 -9
- package/dist/mcp/client.js +1 -0
- package/dist/mcp/mcp-desktop-tools.d.ts +23 -0
- package/dist/mcp/mcp-desktop-tools.js +158 -0
- package/dist/mcp/mcp-server.js +2 -0
- package/dist/memory/enhanced-memory.d.ts +10 -0
- package/dist/memory/enhanced-memory.js +33 -2
- package/dist/memory/index.d.ts +3 -1
- package/dist/memory/index.js +4 -1
- package/dist/memory/memory-auto-proposer.d.ts +23 -0
- package/dist/memory/memory-auto-proposer.js +308 -0
- package/dist/memory/memory-candidate-queue.d.ts +92 -0
- package/dist/memory/memory-candidate-queue.js +261 -0
- package/dist/memory/persistent-memory.d.ts +45 -2
- package/dist/memory/persistent-memory.js +236 -40
- package/dist/ml/bayesian-qualifier.d.ts +1 -1
- package/dist/ml/bayesian-qualifier.js +35 -2
- package/dist/openclaw/gateway-bridge.js +5 -0
- package/dist/plugins/marketplace.d.ts +1 -0
- package/dist/plugins/marketplace.js +7 -0
- package/dist/prompts/execution-discipline.d.ts +14 -0
- package/dist/prompts/execution-discipline.js +29 -0
- package/dist/prompts/variation-injector.js +9 -2
- package/dist/protocols/acp/acp-agentic-runner.d.ts +7 -6
- package/dist/protocols/acp/acp-agentic-runner.js +115 -18
- package/dist/protocols/acp/acp-session-store.d.ts +23 -0
- package/dist/protocols/acp/acp-session-store.js +77 -0
- package/dist/protocols/acp/acp-stdio-server.d.ts +7 -0
- package/dist/protocols/acp/acp-stdio-server.js +71 -16
- package/dist/providers/active-llm-registry.d.ts +37 -0
- package/dist/providers/active-llm-registry.js +186 -0
- package/dist/providers/auxiliary-provider.d.ts +25 -0
- package/dist/providers/auxiliary-provider.js +192 -0
- package/dist/providers/codex-oauth.d.ts +1 -1
- package/dist/providers/codex-oauth.js +27 -4
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/provider-catalog.d.ts +70 -0
- package/dist/providers/provider-catalog.js +738 -0
- package/dist/providers/provider-fallback.d.ts +35 -0
- package/dist/providers/provider-fallback.js +171 -0
- package/dist/providers/xai-oauth.d.ts +128 -0
- package/dist/providers/xai-oauth.js +735 -0
- package/dist/scheduler/cron-scheduler.d.ts +15 -3
- package/dist/scheduler/cron-scheduler.js +53 -7
- package/dist/scheduler/script-runner.d.ts +2 -0
- package/dist/scheduler/script-runner.js +19 -4
- package/dist/scheduler/watchdog-handlers.js +7 -20
- package/dist/search/usearch-index.js +7 -2
- package/dist/security/tool-policy/tool-groups.js +2 -0
- package/dist/server/index.js +17 -2
- package/dist/server/routes/mobile.d.ts +12 -4
- package/dist/server/routes/mobile.js +116 -1
- package/dist/server/tls-config.d.ts +35 -0
- package/dist/server/tls-config.js +142 -0
- package/dist/server/tunnel-manager.d.ts +20 -0
- package/dist/server/tunnel-manager.js +58 -0
- package/dist/server/websocket/fleet-bridge.js +5 -0
- package/dist/services/prompt-builder.d.ts +1 -0
- package/dist/services/prompt-builder.js +49 -8
- package/dist/shared/engine-types.d.ts +15 -1
- package/dist/sidecar/sidecar-bridge.d.ts +1 -0
- package/dist/sidecar/sidecar-bridge.js +21 -10
- package/dist/spec/spec-store.js +8 -1
- package/dist/talk-mode/providers/audioreader-tts.d.ts +1 -1
- package/dist/talk-mode/providers/audioreader-tts.js +8 -8
- package/dist/templates/project-scaffolding.js +3 -2
- package/dist/themes/theme-schema.d.ts +10 -10
- package/dist/tools/application-profiles.js +38 -0
- package/dist/tools/bash/bash-tool.d.ts +1 -0
- package/dist/tools/bash/bash-tool.js +16 -2
- package/dist/tools/bash/command-validator.js +3 -0
- package/dist/tools/bash/rtk-rewrite.d.ts +16 -0
- package/dist/tools/bash/rtk-rewrite.js +100 -0
- package/dist/tools/bash/streaming-executor.js +14 -1
- package/dist/tools/computer-control-tool.d.ts +16 -1
- package/dist/tools/computer-control-tool.js +317 -5
- package/dist/tools/document-generator.d.ts +14 -0
- package/dist/tools/document-generator.js +79 -0
- package/dist/tools/execute-code-rpc-invoker.d.ts +26 -2
- package/dist/tools/execute-code-rpc-invoker.js +46 -5
- package/dist/tools/metadata.js +28 -0
- package/dist/tools/office-macro-tool.d.ts +10 -0
- package/dist/tools/office-macro-tool.js +93 -0
- package/dist/tools/registry/index.d.ts +2 -1
- package/dist/tools/registry/index.js +2 -1
- package/dist/tools/registry/memory-tools.d.ts +20 -0
- package/dist/tools/registry/memory-tools.js +225 -3
- package/dist/tools/registry/multimodal-tools.js +2 -2
- package/dist/tools/registry/vision-tools.d.ts +33 -0
- package/dist/tools/registry/vision-tools.js +308 -6
- package/dist/tools/registry/windows-tools.d.ts +2 -0
- package/dist/tools/registry/windows-tools.js +47 -0
- package/dist/tools/route-peer-tool.js +14 -0
- package/dist/tools/screenshot-tool.js +14 -2
- package/dist/tools/text-to-speech-tool.d.ts +1 -1
- package/dist/tools/text-to-speech-tool.js +2 -12
- package/dist/tools/tool-selector.js +3 -59
- package/dist/utils/config-validation/schema.d.ts +40 -7
- package/dist/utils/config-validation/schema.js +130 -1
- package/dist/utils/cost-tracker.js +21 -1
- package/dist/utils/disk-guard.d.ts +180 -0
- package/dist/utils/disk-guard.js +404 -0
- package/dist/utils/interactive-setup.js +35 -2
- package/dist/utils/model-utils.js +11 -2
- package/dist/utils/output-sanitizer.js +11 -0
- package/dist/utils/provider-detector.d.ts +8 -7
- package/dist/utils/provider-detector.js +19 -77
- package/dist/utils/settings-manager.d.ts +7 -0
- package/dist/utils/token-display.d.ts +3 -3
- package/dist/utils/token-display.js +22 -4
- package/dist/wizard/onboarding.d.ts +39 -0
- package/dist/wizard/onboarding.js +433 -21
- package/dist/wizard/provider-onboarding.d.ts +8 -23
- package/dist/wizard/provider-onboarding.js +39 -158
- package/package.json +4 -2
- package/dist/plugins/provider-onboarding.d.ts +0 -23
- package/dist/plugins/provider-onboarding.js +0 -116
- package/dist/utils/kokoro-tts.d.ts +0 -8
- package/dist/utils/kokoro-tts.js +0 -67
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-gated long-term memory candidates.
|
|
3
|
+
*
|
|
4
|
+
* Declarative memories are more dangerous than procedural lessons because they
|
|
5
|
+
* are injected into future prompts as "facts". The safe loop is therefore:
|
|
6
|
+
* infer -> enqueue with evidence -> human accepts/rejects -> write bounded
|
|
7
|
+
* persistent memory. Proposing a candidate never mutates CODEBUDDY_MEMORY.md.
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { scanForSecrets, redactSecrets } from '../fleet/privacy-lint.js';
|
|
12
|
+
import { logger } from '../utils/logger.js';
|
|
13
|
+
import { getMemoryManager, } from './persistent-memory.js';
|
|
14
|
+
export const MEMORY_CANDIDATE_SCHEMA_VERSION = 1;
|
|
15
|
+
const VALID_CATEGORIES = [
|
|
16
|
+
'project',
|
|
17
|
+
'preferences',
|
|
18
|
+
'decisions',
|
|
19
|
+
'patterns',
|
|
20
|
+
'context',
|
|
21
|
+
'custom',
|
|
22
|
+
];
|
|
23
|
+
const registry = new Map();
|
|
24
|
+
export function getMemoryCandidateQueue(workDir = process.cwd()) {
|
|
25
|
+
const key = path.resolve(workDir);
|
|
26
|
+
if (!registry.has(key)) {
|
|
27
|
+
registry.set(key, new MemoryCandidateQueue(key));
|
|
28
|
+
if (registry.size > 20) {
|
|
29
|
+
const firstKey = registry.keys().next().value;
|
|
30
|
+
if (firstKey)
|
|
31
|
+
registry.delete(firstKey);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return registry.get(key);
|
|
35
|
+
}
|
|
36
|
+
export function resetMemoryCandidateQueues() {
|
|
37
|
+
registry.clear();
|
|
38
|
+
}
|
|
39
|
+
export class MemoryCandidateQueue {
|
|
40
|
+
workDir;
|
|
41
|
+
memoryManager;
|
|
42
|
+
filePath;
|
|
43
|
+
candidates = [];
|
|
44
|
+
loaded = false;
|
|
45
|
+
constructor(workDir = process.cwd(), memoryManager) {
|
|
46
|
+
this.workDir = workDir;
|
|
47
|
+
this.memoryManager = memoryManager;
|
|
48
|
+
this.filePath = path.join(workDir, '.codebuddy', 'memory-candidates.json');
|
|
49
|
+
}
|
|
50
|
+
propose(input) {
|
|
51
|
+
this.load();
|
|
52
|
+
const key = normalizeText(input.key);
|
|
53
|
+
const value = normalizeText(input.value);
|
|
54
|
+
const scope = normalizeScope(input.scope);
|
|
55
|
+
const category = normalizeCategory(input.category);
|
|
56
|
+
if (!key)
|
|
57
|
+
throw new Error('Memory candidate key is required.');
|
|
58
|
+
if (!value)
|
|
59
|
+
throw new Error('Memory candidate value is required.');
|
|
60
|
+
const citations = sanitizeCitations(input.citations);
|
|
61
|
+
const lintTarget = [
|
|
62
|
+
key,
|
|
63
|
+
value,
|
|
64
|
+
input.rationale ?? '',
|
|
65
|
+
...citations.map((citation) => citation.snippet),
|
|
66
|
+
].join('\n');
|
|
67
|
+
const lint = scanForSecrets(lintTarget);
|
|
68
|
+
if (lint.hasSecrets) {
|
|
69
|
+
throw new Error(`Memory candidate rejected before queueing: sensitive material detected (${lint.matches.map((m) => m.kind).join(', ')}).`);
|
|
70
|
+
}
|
|
71
|
+
const existing = this.candidates.find((candidate) => candidate.status === 'pending' &&
|
|
72
|
+
candidate.scope === scope &&
|
|
73
|
+
candidate.key.trim().toLowerCase() === key.toLowerCase() &&
|
|
74
|
+
candidate.value.trim().toLowerCase() === value.toLowerCase());
|
|
75
|
+
if (existing) {
|
|
76
|
+
return { candidate: existing, deduped: true };
|
|
77
|
+
}
|
|
78
|
+
const candidate = {
|
|
79
|
+
id: `mc-${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`,
|
|
80
|
+
key,
|
|
81
|
+
value,
|
|
82
|
+
scope,
|
|
83
|
+
category,
|
|
84
|
+
status: 'pending',
|
|
85
|
+
createdAt: Date.now(),
|
|
86
|
+
source: input.source ?? 'self_observed',
|
|
87
|
+
...(typeof input.confidence === 'number' ? { confidence: clampConfidence(input.confidence) } : {}),
|
|
88
|
+
...(input.rationale?.trim() ? { rationale: input.rationale.trim() } : {}),
|
|
89
|
+
...(citations.length > 0 ? { citations } : {}),
|
|
90
|
+
};
|
|
91
|
+
this.candidates.push(candidate);
|
|
92
|
+
this.save();
|
|
93
|
+
return { candidate, deduped: false };
|
|
94
|
+
}
|
|
95
|
+
list(status) {
|
|
96
|
+
this.load();
|
|
97
|
+
const items = status ? this.candidates.filter((candidate) => candidate.status === status) : this.candidates;
|
|
98
|
+
return [...items].sort((a, b) => b.createdAt - a.createdAt);
|
|
99
|
+
}
|
|
100
|
+
get(id) {
|
|
101
|
+
this.load();
|
|
102
|
+
return this.candidates.find((candidate) => candidate.id === id) ?? null;
|
|
103
|
+
}
|
|
104
|
+
async accept(id, input) {
|
|
105
|
+
this.load();
|
|
106
|
+
const reviewedBy = input.reviewedBy?.trim();
|
|
107
|
+
if (!reviewedBy) {
|
|
108
|
+
throw new Error('Human approval (reviewedBy) is required before a memory candidate can be written.');
|
|
109
|
+
}
|
|
110
|
+
const candidate = this.candidates.find((item) => item.id === id);
|
|
111
|
+
if (!candidate)
|
|
112
|
+
throw new Error(`Memory candidate not found: ${id}`);
|
|
113
|
+
if (candidate.status !== 'pending') {
|
|
114
|
+
throw new Error(`Memory candidate ${id} is already ${candidate.status}; only pending candidates can be accepted.`);
|
|
115
|
+
}
|
|
116
|
+
const key = input.key !== undefined ? normalizeText(input.key) : candidate.key;
|
|
117
|
+
const value = input.value !== undefined ? normalizeText(input.value) : candidate.value;
|
|
118
|
+
const scope = input.scope ? normalizeScope(input.scope) : candidate.scope;
|
|
119
|
+
const category = input.category ? normalizeCategory(input.category) : candidate.category;
|
|
120
|
+
if (!key)
|
|
121
|
+
throw new Error('Accepted memory key cannot be empty.');
|
|
122
|
+
if (!value)
|
|
123
|
+
throw new Error('Accepted memory value cannot be empty.');
|
|
124
|
+
const manager = this.memoryManager ?? getMemoryManager();
|
|
125
|
+
await manager.initialize();
|
|
126
|
+
const write = await manager.remember(key, value, {
|
|
127
|
+
scope,
|
|
128
|
+
category,
|
|
129
|
+
tags: ['memory-candidate', candidate.id],
|
|
130
|
+
});
|
|
131
|
+
candidate.key = key;
|
|
132
|
+
candidate.value = value;
|
|
133
|
+
candidate.scope = scope;
|
|
134
|
+
candidate.category = category;
|
|
135
|
+
candidate.status = 'accepted';
|
|
136
|
+
candidate.reviewedAt = Date.now();
|
|
137
|
+
candidate.reviewedBy = reviewedBy;
|
|
138
|
+
candidate.write = { status: write.status, usage: write.usage };
|
|
139
|
+
if (input.reviewNote?.trim()) {
|
|
140
|
+
candidate.reviewNote = input.reviewNote.trim();
|
|
141
|
+
}
|
|
142
|
+
this.save();
|
|
143
|
+
return { candidate, write };
|
|
144
|
+
}
|
|
145
|
+
reject(id, input = {}) {
|
|
146
|
+
this.load();
|
|
147
|
+
const candidate = this.candidates.find((item) => item.id === id);
|
|
148
|
+
if (!candidate)
|
|
149
|
+
throw new Error(`Memory candidate not found: ${id}`);
|
|
150
|
+
if (candidate.status === 'accepted') {
|
|
151
|
+
throw new Error(`Memory candidate ${id} was already accepted and cannot be rejected.`);
|
|
152
|
+
}
|
|
153
|
+
candidate.status = 'rejected';
|
|
154
|
+
candidate.reviewedAt = Date.now();
|
|
155
|
+
if (input.reviewedBy?.trim()) {
|
|
156
|
+
candidate.reviewedBy = input.reviewedBy.trim();
|
|
157
|
+
}
|
|
158
|
+
if (input.reason?.trim()) {
|
|
159
|
+
candidate.reviewNote = input.reason.trim();
|
|
160
|
+
}
|
|
161
|
+
this.save();
|
|
162
|
+
return candidate;
|
|
163
|
+
}
|
|
164
|
+
getStats() {
|
|
165
|
+
this.load();
|
|
166
|
+
const byStatus = {
|
|
167
|
+
pending: 0,
|
|
168
|
+
accepted: 0,
|
|
169
|
+
rejected: 0,
|
|
170
|
+
};
|
|
171
|
+
for (const candidate of this.candidates) {
|
|
172
|
+
byStatus[candidate.status] += 1;
|
|
173
|
+
}
|
|
174
|
+
return { total: this.candidates.length, byStatus };
|
|
175
|
+
}
|
|
176
|
+
load() {
|
|
177
|
+
if (this.loaded)
|
|
178
|
+
return;
|
|
179
|
+
this.loaded = true;
|
|
180
|
+
try {
|
|
181
|
+
if (!fs.existsSync(this.filePath))
|
|
182
|
+
return;
|
|
183
|
+
const parsed = JSON.parse(fs.readFileSync(this.filePath, 'utf-8'));
|
|
184
|
+
if (Array.isArray(parsed.candidates)) {
|
|
185
|
+
this.candidates = parsed.candidates.filter(isValidCandidate);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
logger.warn('[memory-candidates] failed to load queue', {
|
|
190
|
+
error: err instanceof Error ? err.message : String(err),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
save() {
|
|
195
|
+
try {
|
|
196
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
197
|
+
const file = {
|
|
198
|
+
schemaVersion: MEMORY_CANDIDATE_SCHEMA_VERSION,
|
|
199
|
+
candidates: this.candidates,
|
|
200
|
+
};
|
|
201
|
+
fs.writeFileSync(this.filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
logger.warn('[memory-candidates] failed to save queue', {
|
|
205
|
+
error: err instanceof Error ? err.message : String(err),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function normalizeText(value) {
|
|
211
|
+
return (value ?? '').replace(/\s+/g, ' ').trim();
|
|
212
|
+
}
|
|
213
|
+
function normalizeScope(scope) {
|
|
214
|
+
return scope === 'user' ? 'user' : 'project';
|
|
215
|
+
}
|
|
216
|
+
function normalizeCategory(category) {
|
|
217
|
+
const normalized = category ?? 'context';
|
|
218
|
+
if (!VALID_CATEGORIES.includes(normalized)) {
|
|
219
|
+
throw new Error(`Memory category must be one of: ${VALID_CATEGORIES.join(', ')}`);
|
|
220
|
+
}
|
|
221
|
+
return normalized;
|
|
222
|
+
}
|
|
223
|
+
function clampConfidence(value) {
|
|
224
|
+
if (!Number.isFinite(value))
|
|
225
|
+
return 0;
|
|
226
|
+
return Math.max(0, Math.min(1, value));
|
|
227
|
+
}
|
|
228
|
+
function sanitizeCitations(citations) {
|
|
229
|
+
if (!Array.isArray(citations))
|
|
230
|
+
return [];
|
|
231
|
+
return citations
|
|
232
|
+
.map((citation) => {
|
|
233
|
+
const snippet = redactSecrets(normalizeText(citation.snippet)).slice(0, 280);
|
|
234
|
+
if (!snippet)
|
|
235
|
+
return null;
|
|
236
|
+
return {
|
|
237
|
+
...(citation.sessionId ? { sessionId: citation.sessionId } : {}),
|
|
238
|
+
...(typeof citation.messageId === 'number' ? { messageId: citation.messageId } : {}),
|
|
239
|
+
...(typeof citation.messageIndex === 'number' ? { messageIndex: citation.messageIndex } : {}),
|
|
240
|
+
...(citation.role ? { role: citation.role } : {}),
|
|
241
|
+
snippet,
|
|
242
|
+
};
|
|
243
|
+
})
|
|
244
|
+
.filter((citation) => citation !== null)
|
|
245
|
+
.slice(0, 3);
|
|
246
|
+
}
|
|
247
|
+
function isValidCandidate(value) {
|
|
248
|
+
if (!value || typeof value !== 'object')
|
|
249
|
+
return false;
|
|
250
|
+
const candidate = value;
|
|
251
|
+
return (typeof candidate.id === 'string' &&
|
|
252
|
+
typeof candidate.key === 'string' &&
|
|
253
|
+
typeof candidate.value === 'string' &&
|
|
254
|
+
(candidate.scope === 'project' || candidate.scope === 'user') &&
|
|
255
|
+
typeof candidate.category === 'string' &&
|
|
256
|
+
VALID_CATEGORIES.includes(candidate.category) &&
|
|
257
|
+
(candidate.status === 'pending' || candidate.status === 'accepted' || candidate.status === 'rejected') &&
|
|
258
|
+
typeof candidate.createdAt === 'number' &&
|
|
259
|
+
typeof candidate.source === 'string');
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=memory-candidate-queue.js.map
|
|
@@ -15,6 +15,32 @@ export interface MemoryConfig {
|
|
|
15
15
|
autoCapture: boolean;
|
|
16
16
|
maxMemories: number;
|
|
17
17
|
relevanceThreshold: number;
|
|
18
|
+
enforceCharLimits: boolean;
|
|
19
|
+
projectCharLimit: number;
|
|
20
|
+
userCharLimit: number;
|
|
21
|
+
securityScan: boolean;
|
|
22
|
+
rejectExactDuplicates: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type MemoryScope = "project" | "user";
|
|
25
|
+
export interface MemoryUsage {
|
|
26
|
+
scope: MemoryScope;
|
|
27
|
+
used: number;
|
|
28
|
+
limit: number;
|
|
29
|
+
percent: number;
|
|
30
|
+
}
|
|
31
|
+
export type MemoryWriteStatus = 'stored' | 'updated' | 'duplicate' | 'replaced' | 'missing';
|
|
32
|
+
export interface MemoryWriteResult {
|
|
33
|
+
status: MemoryWriteStatus;
|
|
34
|
+
key: string;
|
|
35
|
+
scope: MemoryScope;
|
|
36
|
+
category?: MemoryCategory;
|
|
37
|
+
usage: MemoryUsage;
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
export declare class MemoryWriteRejectedError extends Error {
|
|
41
|
+
readonly code: 'memory_limit_exceeded' | 'memory_security_rejected';
|
|
42
|
+
readonly usage?: MemoryUsage | undefined;
|
|
43
|
+
constructor(message: string, code: 'memory_limit_exceeded' | 'memory_security_rejected', usage?: MemoryUsage | undefined);
|
|
18
44
|
}
|
|
19
45
|
/**
|
|
20
46
|
* Persistent Memory Manager - Inspired by Claude's CLAUDE.md memory system
|
|
@@ -50,10 +76,25 @@ export declare class PersistentMemoryManager extends EventEmitter {
|
|
|
50
76
|
* Remember something (store in memory)
|
|
51
77
|
*/
|
|
52
78
|
remember(key: string, value: string, options?: {
|
|
53
|
-
scope?:
|
|
79
|
+
scope?: MemoryScope;
|
|
54
80
|
category?: MemoryCategory;
|
|
55
81
|
tags?: string[];
|
|
56
|
-
}): Promise<
|
|
82
|
+
}): Promise<MemoryWriteResult>;
|
|
83
|
+
replace(key: string, value: string, options?: {
|
|
84
|
+
scope?: MemoryScope;
|
|
85
|
+
category?: MemoryCategory;
|
|
86
|
+
tags?: string[];
|
|
87
|
+
}): Promise<MemoryWriteResult>;
|
|
88
|
+
private setMemoryDirect;
|
|
89
|
+
private assertMemoryWriteSafe;
|
|
90
|
+
private assertScopeWithinLimit;
|
|
91
|
+
private getMemoryLimit;
|
|
92
|
+
private renderScopeEntries;
|
|
93
|
+
getMemoryUsage(scope: MemoryScope): MemoryUsage;
|
|
94
|
+
getMemoryUsages(): {
|
|
95
|
+
project: MemoryUsage;
|
|
96
|
+
user: MemoryUsage;
|
|
97
|
+
};
|
|
57
98
|
/**
|
|
58
99
|
* Recall something from memory
|
|
59
100
|
*/
|
|
@@ -82,6 +123,8 @@ export declare class PersistentMemoryManager extends EventEmitter {
|
|
|
82
123
|
* Get context string for system prompt
|
|
83
124
|
*/
|
|
84
125
|
getContextForPrompt(): string;
|
|
126
|
+
getHermesSnapshotForPrompt(): string;
|
|
127
|
+
private renderHermesMemorySection;
|
|
85
128
|
/**
|
|
86
129
|
* Auto-capture important information from conversation
|
|
87
130
|
*/
|
|
@@ -33,7 +33,39 @@ const DEFAULT_CONFIG = {
|
|
|
33
33
|
autoCapture: true,
|
|
34
34
|
maxMemories: 100,
|
|
35
35
|
relevanceThreshold: 0.5,
|
|
36
|
+
enforceCharLimits: process.env.CODEBUDDY_MEMORY_ENFORCE_LIMITS !== 'false',
|
|
37
|
+
projectCharLimit: parsePositiveInt(process.env.CODEBUDDY_MEMORY_PROJECT_CHAR_LIMIT, 2200),
|
|
38
|
+
userCharLimit: parsePositiveInt(process.env.CODEBUDDY_MEMORY_USER_CHAR_LIMIT, 1375),
|
|
39
|
+
securityScan: process.env.CODEBUDDY_MEMORY_SECURITY_SCAN !== 'false',
|
|
40
|
+
rejectExactDuplicates: process.env.CODEBUDDY_MEMORY_REJECT_DUPLICATES !== 'false',
|
|
36
41
|
};
|
|
42
|
+
export class MemoryWriteRejectedError extends Error {
|
|
43
|
+
code;
|
|
44
|
+
usage;
|
|
45
|
+
constructor(message, code, usage) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.code = code;
|
|
48
|
+
this.usage = usage;
|
|
49
|
+
this.name = 'MemoryWriteRejectedError';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function parsePositiveInt(value, fallback) {
|
|
53
|
+
if (!value)
|
|
54
|
+
return fallback;
|
|
55
|
+
const parsed = Number.parseInt(value, 10);
|
|
56
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
57
|
+
}
|
|
58
|
+
function cloneMemoryMap(memories) {
|
|
59
|
+
return new Map(Array.from(memories.entries()).map(([key, memory]) => [
|
|
60
|
+
key,
|
|
61
|
+
{
|
|
62
|
+
...memory,
|
|
63
|
+
createdAt: new Date(memory.createdAt),
|
|
64
|
+
updatedAt: new Date(memory.updatedAt),
|
|
65
|
+
tags: memory.tags ? [...memory.tags] : undefined,
|
|
66
|
+
},
|
|
67
|
+
]));
|
|
68
|
+
}
|
|
37
69
|
const MEMORY_TEMPLATE = `# Code Buddy Memory
|
|
38
70
|
|
|
39
71
|
This file stores persistent memory for the Code Buddy agent.
|
|
@@ -147,6 +179,7 @@ export class PersistentMemoryManager extends EventEmitter {
|
|
|
147
179
|
"User Preferences": "preferences",
|
|
148
180
|
"Decisions": "decisions",
|
|
149
181
|
"Patterns": "patterns",
|
|
182
|
+
"Context": "context",
|
|
150
183
|
"Custom": "custom",
|
|
151
184
|
};
|
|
152
185
|
let currentCategory = "custom";
|
|
@@ -212,13 +245,29 @@ export class PersistentMemoryManager extends EventEmitter {
|
|
|
212
245
|
async remember(key, value, options = {}) {
|
|
213
246
|
const { scope = "project", category = "context", tags } = options;
|
|
214
247
|
const memories = scope === "project" ? this.projectMemories : this.userMemories;
|
|
248
|
+
const normalizedKey = key.trim();
|
|
249
|
+
const normalizedValue = value.trim();
|
|
250
|
+
this.assertMemoryWriteSafe(normalizedKey, normalizedValue);
|
|
251
|
+
const existing = memories.get(normalizedKey);
|
|
252
|
+
if (this.config.rejectExactDuplicates && existing?.value === normalizedValue) {
|
|
253
|
+
return {
|
|
254
|
+
status: 'duplicate',
|
|
255
|
+
key: normalizedKey,
|
|
256
|
+
scope,
|
|
257
|
+
category: existing.category,
|
|
258
|
+
usage: this.getMemoryUsage(scope),
|
|
259
|
+
message: `No duplicate added for "${normalizedKey}".`,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const previousMemories = cloneMemoryMap(memories);
|
|
263
|
+
const status = existing ? 'updated' : 'stored';
|
|
215
264
|
try {
|
|
216
265
|
const { FactsMemoryService } = await import('./facts-memory.js');
|
|
217
266
|
const service = new FactsMemoryService();
|
|
218
267
|
if (await service.isAvailable()) {
|
|
219
268
|
const newFact = {
|
|
220
269
|
category: mapMemoryCategoryToFactCategory(category),
|
|
221
|
-
text: `${
|
|
270
|
+
text: `${normalizedKey}: ${normalizedValue}`,
|
|
222
271
|
source: tags?.join(', ') || 'manual',
|
|
223
272
|
updatedAt: new Date()
|
|
224
273
|
};
|
|
@@ -252,35 +301,166 @@ export class PersistentMemoryManager extends EventEmitter {
|
|
|
252
301
|
}
|
|
253
302
|
else {
|
|
254
303
|
// Fallback to default direct write
|
|
255
|
-
|
|
256
|
-
const memory = {
|
|
257
|
-
key,
|
|
258
|
-
value,
|
|
259
|
-
category,
|
|
260
|
-
createdAt: existing?.createdAt || new Date(),
|
|
261
|
-
updatedAt: new Date(),
|
|
262
|
-
accessCount: existing?.accessCount || 0,
|
|
263
|
-
tags,
|
|
264
|
-
};
|
|
265
|
-
memories.set(key, memory);
|
|
304
|
+
this.setMemoryDirect(memories, normalizedKey, normalizedValue, category, tags);
|
|
266
305
|
}
|
|
267
306
|
}
|
|
268
307
|
catch (err) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
308
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
309
|
+
logger.warn(`[FactsMemory] Failed to reconcile remember, falling back to default behavior: ${message}`);
|
|
310
|
+
memories.clear();
|
|
311
|
+
for (const [memoryKey, memory] of previousMemories) {
|
|
312
|
+
memories.set(memoryKey, memory);
|
|
313
|
+
}
|
|
314
|
+
this.setMemoryDirect(memories, normalizedKey, normalizedValue, category, tags);
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
this.assertScopeWithinLimit(scope);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
memories.clear();
|
|
321
|
+
for (const [memoryKey, memory] of previousMemories) {
|
|
322
|
+
memories.set(memoryKey, memory);
|
|
323
|
+
}
|
|
324
|
+
throw err;
|
|
325
|
+
}
|
|
326
|
+
await this.saveMemories(scope);
|
|
327
|
+
this.emit("memory:remembered", { key: normalizedKey, scope, category });
|
|
328
|
+
return {
|
|
329
|
+
status,
|
|
330
|
+
key: normalizedKey,
|
|
331
|
+
scope,
|
|
332
|
+
category,
|
|
333
|
+
usage: this.getMemoryUsage(scope),
|
|
334
|
+
message: status === 'updated'
|
|
335
|
+
? `Updated "${normalizedKey}" in ${scope} memory.`
|
|
336
|
+
: `Stored "${normalizedKey}" in ${scope} memory.`,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async replace(key, value, options = {}) {
|
|
340
|
+
const { scope = 'project', category, tags } = options;
|
|
341
|
+
const memories = scope === 'project' ? this.projectMemories : this.userMemories;
|
|
342
|
+
const normalizedKey = key.trim();
|
|
343
|
+
const normalizedValue = value.trim();
|
|
344
|
+
const existing = memories.get(normalizedKey);
|
|
345
|
+
if (!existing) {
|
|
346
|
+
return {
|
|
347
|
+
status: 'missing',
|
|
348
|
+
key: normalizedKey,
|
|
349
|
+
scope,
|
|
274
350
|
category,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
accessCount: existing?.accessCount || 0,
|
|
278
|
-
tags,
|
|
351
|
+
usage: this.getMemoryUsage(scope),
|
|
352
|
+
message: `No memory found for key "${normalizedKey}" in ${scope} scope.`,
|
|
279
353
|
};
|
|
280
|
-
|
|
354
|
+
}
|
|
355
|
+
this.assertMemoryWriteSafe(normalizedKey, normalizedValue);
|
|
356
|
+
const previousMemories = cloneMemoryMap(memories);
|
|
357
|
+
memories.set(normalizedKey, {
|
|
358
|
+
...existing,
|
|
359
|
+
value: normalizedValue,
|
|
360
|
+
category: category ?? existing.category,
|
|
361
|
+
updatedAt: new Date(),
|
|
362
|
+
tags: tags ?? existing.tags,
|
|
363
|
+
});
|
|
364
|
+
try {
|
|
365
|
+
this.assertScopeWithinLimit(scope);
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
memories.clear();
|
|
369
|
+
for (const [memoryKey, memory] of previousMemories) {
|
|
370
|
+
memories.set(memoryKey, memory);
|
|
371
|
+
}
|
|
372
|
+
throw err;
|
|
281
373
|
}
|
|
282
374
|
await this.saveMemories(scope);
|
|
283
|
-
this.emit("memory:
|
|
375
|
+
this.emit("memory:replaced", { key: normalizedKey, scope, category: category ?? existing.category });
|
|
376
|
+
return {
|
|
377
|
+
status: 'replaced',
|
|
378
|
+
key: normalizedKey,
|
|
379
|
+
scope,
|
|
380
|
+
category: category ?? existing.category,
|
|
381
|
+
usage: this.getMemoryUsage(scope),
|
|
382
|
+
message: `Replaced "${normalizedKey}" in ${scope} memory.`,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
setMemoryDirect(memories, key, value, category, tags) {
|
|
386
|
+
const existing = memories.get(key);
|
|
387
|
+
const memory = {
|
|
388
|
+
key,
|
|
389
|
+
value,
|
|
390
|
+
category,
|
|
391
|
+
createdAt: existing?.createdAt || new Date(),
|
|
392
|
+
updatedAt: new Date(),
|
|
393
|
+
accessCount: existing?.accessCount || 0,
|
|
394
|
+
tags,
|
|
395
|
+
};
|
|
396
|
+
memories.set(key, memory);
|
|
397
|
+
}
|
|
398
|
+
assertMemoryWriteSafe(key, value) {
|
|
399
|
+
if (!key || !value) {
|
|
400
|
+
throw new MemoryWriteRejectedError('Memory key and value must be non-empty.', 'memory_security_rejected');
|
|
401
|
+
}
|
|
402
|
+
if (!this.config.securityScan)
|
|
403
|
+
return;
|
|
404
|
+
const content = `${key}\n${value}`;
|
|
405
|
+
if (/[\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF]/u.test(content)) {
|
|
406
|
+
throw new MemoryWriteRejectedError('Memory write rejected: invisible Unicode control characters are not allowed in prompt-injected memory.', 'memory_security_rejected');
|
|
407
|
+
}
|
|
408
|
+
const threatPatterns = [
|
|
409
|
+
{
|
|
410
|
+
pattern: /\b(ignore|override|bypass|discard)\b.{0,80}\b(system|developer|previous|prior|above)\b.{0,80}\b(instructions?|prompt|rules?)\b/i,
|
|
411
|
+
reason: 'prompt injection instruction',
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
pattern: /\b(exfiltrate|steal|leak|send|upload|post)\b.{0,100}\b(api[-_ ]?key|token|secret|password|credential|private key)\b/i,
|
|
415
|
+
reason: 'credential exfiltration instruction',
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
pattern: /\b(authorized_keys|reverse shell|backdoor|ssh-rsa)\b/i,
|
|
419
|
+
reason: 'backdoor or SSH persistence pattern',
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
pattern: /-----BEGIN (?:OPENSSH|RSA|EC|DSA) PRIVATE KEY-----/i,
|
|
423
|
+
reason: 'private key material',
|
|
424
|
+
},
|
|
425
|
+
];
|
|
426
|
+
for (const { pattern, reason } of threatPatterns) {
|
|
427
|
+
if (pattern.test(content)) {
|
|
428
|
+
throw new MemoryWriteRejectedError(`Memory write rejected: ${reason} detected.`, 'memory_security_rejected');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
assertScopeWithinLimit(scope) {
|
|
433
|
+
if (!this.config.enforceCharLimits)
|
|
434
|
+
return;
|
|
435
|
+
const usage = this.getMemoryUsage(scope);
|
|
436
|
+
if (usage.limit > 0 && usage.used > usage.limit) {
|
|
437
|
+
throw new MemoryWriteRejectedError(`Memory ${scope} is full (${usage.used}/${usage.limit} chars). Consolidate or remove entries before retrying.`, 'memory_limit_exceeded', usage);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
getMemoryLimit(scope) {
|
|
441
|
+
return scope === 'project' ? this.config.projectCharLimit : this.config.userCharLimit;
|
|
442
|
+
}
|
|
443
|
+
renderScopeEntries(memories) {
|
|
444
|
+
return Array.from(memories.values())
|
|
445
|
+
.map((memory) => `${memory.key}: ${memory.value}`)
|
|
446
|
+
.join('\n§\n');
|
|
447
|
+
}
|
|
448
|
+
getMemoryUsage(scope) {
|
|
449
|
+
const memories = scope === 'project' ? this.projectMemories : this.userMemories;
|
|
450
|
+
const used = this.renderScopeEntries(memories).length;
|
|
451
|
+
const limit = this.getMemoryLimit(scope);
|
|
452
|
+
return {
|
|
453
|
+
scope,
|
|
454
|
+
used,
|
|
455
|
+
limit,
|
|
456
|
+
percent: limit > 0 ? Math.min(100, Math.round((used / limit) * 100)) : 0,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
getMemoryUsages() {
|
|
460
|
+
return {
|
|
461
|
+
project: this.getMemoryUsage('project'),
|
|
462
|
+
user: this.getMemoryUsage('user'),
|
|
463
|
+
};
|
|
284
464
|
}
|
|
285
465
|
/**
|
|
286
466
|
* Recall something from memory
|
|
@@ -463,21 +643,34 @@ export class PersistentMemoryManager extends EventEmitter {
|
|
|
463
643
|
* Get context string for system prompt
|
|
464
644
|
*/
|
|
465
645
|
getContextForPrompt() {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
...this.getByCategory("preferences", "user"),
|
|
469
|
-
...this.getByCategory("decisions"),
|
|
470
|
-
...this.getByCategory("patterns"),
|
|
471
|
-
].slice(0, 20);
|
|
472
|
-
if (relevant.length === 0) {
|
|
646
|
+
const snapshot = this.getHermesSnapshotForPrompt();
|
|
647
|
+
if (!snapshot)
|
|
473
648
|
return "";
|
|
649
|
+
return `--- PERSISTENT MEMORY ---\n${snapshot}\n--- END MEMORY ---\n`;
|
|
650
|
+
}
|
|
651
|
+
getHermesSnapshotForPrompt() {
|
|
652
|
+
const sections = [];
|
|
653
|
+
const projectEntries = this.renderScopeEntries(this.projectMemories);
|
|
654
|
+
const userEntries = this.renderScopeEntries(this.userMemories);
|
|
655
|
+
if (projectEntries) {
|
|
656
|
+
const usage = this.getMemoryUsage('project');
|
|
657
|
+
sections.push(this.renderHermesMemorySection('MEMORY (project notes)', usage, projectEntries));
|
|
474
658
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
659
|
+
if (userEntries) {
|
|
660
|
+
const usage = this.getMemoryUsage('user');
|
|
661
|
+
sections.push(this.renderHermesMemorySection('USER (profile and preferences)', usage, userEntries));
|
|
478
662
|
}
|
|
479
|
-
|
|
480
|
-
|
|
663
|
+
return sections.join('\n\n');
|
|
664
|
+
}
|
|
665
|
+
renderHermesMemorySection(title, usage, entries) {
|
|
666
|
+
const width = 46;
|
|
667
|
+
const bar = '═'.repeat(width);
|
|
668
|
+
return [
|
|
669
|
+
bar,
|
|
670
|
+
`${title} [${usage.percent}% - ${usage.used}/${usage.limit} chars]`,
|
|
671
|
+
bar,
|
|
672
|
+
entries,
|
|
673
|
+
].join('\n');
|
|
481
674
|
}
|
|
482
675
|
/**
|
|
483
676
|
* Auto-capture important information from conversation
|
|
@@ -527,7 +720,8 @@ export class PersistentMemoryManager extends EventEmitter {
|
|
|
527
720
|
await this.saveMemories("project");
|
|
528
721
|
}
|
|
529
722
|
catch (err) {
|
|
530
|
-
|
|
723
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
724
|
+
logger.warn(`[FactsMemory] Failed to autoCapture facts, falling back to pattern matching: ${message}`);
|
|
531
725
|
// Fallback: Detect project context
|
|
532
726
|
const projectPatterns = [
|
|
533
727
|
/this (?:is|project) (?:a|an) ([^.]+)/i,
|
|
@@ -605,8 +799,10 @@ export class PersistentMemoryManager extends EventEmitter {
|
|
|
605
799
|
*/
|
|
606
800
|
formatMemories(scope) {
|
|
607
801
|
let output = `\n🧠 Persistent Memory\n${"═".repeat(50)}\n\n`;
|
|
608
|
-
const formatScope = (name, memories) => {
|
|
802
|
+
const formatScope = (name, memories, memoryScope) => {
|
|
803
|
+
const usage = this.getMemoryUsage(memoryScope);
|
|
609
804
|
output += `📁 ${name}\n`;
|
|
805
|
+
output += ` Capacity: ${usage.used}/${usage.limit} chars (${usage.percent}%)\n`;
|
|
610
806
|
if (memories.size === 0) {
|
|
611
807
|
output += ` (empty)\n`;
|
|
612
808
|
}
|
|
@@ -619,13 +815,13 @@ export class PersistentMemoryManager extends EventEmitter {
|
|
|
619
815
|
output += `\n`;
|
|
620
816
|
};
|
|
621
817
|
if (!scope || scope === "project") {
|
|
622
|
-
formatScope("Project Memory", this.projectMemories);
|
|
818
|
+
formatScope("Project Memory", this.projectMemories, 'project');
|
|
623
819
|
}
|
|
624
820
|
if (!scope || scope === "user") {
|
|
625
|
-
formatScope("User Memory", this.userMemories);
|
|
821
|
+
formatScope("User Memory", this.userMemories, 'user');
|
|
626
822
|
}
|
|
627
823
|
output += `${"═".repeat(50)}\n`;
|
|
628
|
-
output += `💡 Commands: /remember <key> <value>, /recall <key>, /forget <key>\n`;
|
|
824
|
+
output += `💡 Commands: /remember <key> <value>, /memory replace <key> <value>, /recall <key>, /forget <key>\n`;
|
|
629
825
|
return output;
|
|
630
826
|
}
|
|
631
827
|
getStats() {
|
|
@@ -56,7 +56,7 @@ export declare class BayesianQualifier {
|
|
|
56
56
|
/**
|
|
57
57
|
* Load the qualifier state from a JSON string
|
|
58
58
|
*/
|
|
59
|
-
loadState(stateJson: string):
|
|
59
|
+
loadState(stateJson: string): boolean;
|
|
60
60
|
private rbfKernel;
|
|
61
61
|
/**
|
|
62
62
|
* Solve L * x = b for lower triangular matrix L
|