@theihtisham/agent-shadow-brain 7.1.0 → 7.1.4
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 +42 -0
- package/README.md +125 -450
- package/dist/activity-cli.d.ts +3 -0
- package/dist/activity-cli.d.ts.map +1 -0
- package/dist/activity-cli.js +78 -0
- package/dist/activity-cli.js.map +1 -0
- package/dist/adapters/index.d.ts +2 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +3 -2
- package/dist/adapters/index.js.map +1 -1
- package/dist/brain/activity-journal.d.ts +82 -0
- package/dist/brain/activity-journal.d.ts.map +1 -0
- package/dist/brain/activity-journal.js +315 -0
- package/dist/brain/activity-journal.js.map +1 -0
- package/dist/brain/activity-reporting.d.ts +14 -0
- package/dist/brain/activity-reporting.d.ts.map +1 -0
- package/dist/brain/activity-reporting.js +17 -0
- package/dist/brain/activity-reporting.js.map +1 -0
- package/dist/brain/auto-update.js +1 -1
- package/dist/brain/brain-portability.js +1 -1
- package/dist/brain/client-config.d.ts +19 -0
- package/dist/brain/client-config.d.ts.map +1 -0
- package/dist/brain/client-config.js +40 -0
- package/dist/brain/client-config.js.map +1 -0
- package/dist/brain/client-integrations.d.ts +63 -0
- package/dist/brain/client-integrations.d.ts.map +1 -0
- package/dist/brain/client-integrations.js +79 -0
- package/dist/brain/client-integrations.js.map +1 -0
- package/dist/brain/context-assembly.d.ts +84 -0
- package/dist/brain/context-assembly.d.ts.map +1 -0
- package/dist/brain/context-assembly.js +529 -0
- package/dist/brain/context-assembly.js.map +1 -0
- package/dist/brain/context-request.d.ts +4 -0
- package/dist/brain/context-request.d.ts.map +1 -0
- package/dist/brain/context-request.js +22 -0
- package/dist/brain/context-request.js.map +1 -0
- package/dist/brain/context-retrieval.d.ts +88 -0
- package/dist/brain/context-retrieval.d.ts.map +1 -0
- package/dist/brain/context-retrieval.js +300 -0
- package/dist/brain/context-retrieval.js.map +1 -0
- package/dist/brain/feature-settings.d.ts +58 -0
- package/dist/brain/feature-settings.d.ts.map +1 -0
- package/dist/brain/feature-settings.js +242 -0
- package/dist/brain/feature-settings.js.map +1 -0
- package/dist/brain/file-identity.d.ts +4 -0
- package/dist/brain/file-identity.d.ts.map +1 -0
- package/dist/brain/file-identity.js +27 -0
- package/dist/brain/file-identity.js.map +1 -0
- package/dist/brain/global-brain.d.ts +18 -1
- package/dist/brain/global-brain.d.ts.map +1 -1
- package/dist/brain/global-brain.js +77 -10
- package/dist/brain/global-brain.js.map +1 -1
- package/dist/brain/mcp-server.d.ts +30 -1
- package/dist/brain/mcp-server.d.ts.map +1 -1
- package/dist/brain/mcp-server.js +378 -28
- package/dist/brain/mcp-server.js.map +1 -1
- package/dist/brain/mcp-stdio.d.ts +12 -0
- package/dist/brain/mcp-stdio.d.ts.map +1 -0
- package/dist/brain/mcp-stdio.js +140 -0
- package/dist/brain/mcp-stdio.js.map +1 -0
- package/dist/brain/memory-annotations.d.ts +97 -0
- package/dist/brain/memory-annotations.d.ts.map +1 -0
- package/dist/brain/memory-annotations.js +389 -0
- package/dist/brain/memory-annotations.js.map +1 -0
- package/dist/brain/memory-graph.d.ts.map +1 -1
- package/dist/brain/memory-graph.js +12 -23
- package/dist/brain/memory-graph.js.map +1 -1
- package/dist/brain/memory-inspection.d.ts +72 -0
- package/dist/brain/memory-inspection.d.ts.map +1 -0
- package/dist/brain/memory-inspection.js +131 -0
- package/dist/brain/memory-inspection.js.map +1 -0
- package/dist/brain/memory-mcp-content.d.ts +44 -0
- package/dist/brain/memory-mcp-content.d.ts.map +1 -0
- package/dist/brain/memory-mcp-content.js +91 -0
- package/dist/brain/memory-mcp-content.js.map +1 -0
- package/dist/brain/memory-sources.d.ts +8 -0
- package/dist/brain/memory-sources.d.ts.map +1 -0
- package/dist/brain/memory-sources.js +27 -0
- package/dist/brain/memory-sources.js.map +1 -0
- package/dist/brain/orchestrator.js +1 -1
- package/dist/brain/project-feedback.d.ts +63 -0
- package/dist/brain/project-feedback.d.ts.map +1 -0
- package/dist/brain/project-feedback.js +289 -0
- package/dist/brain/project-feedback.js.map +1 -0
- package/dist/brain/project-hub-contract.d.ts +326 -0
- package/dist/brain/project-hub-contract.d.ts.map +1 -0
- package/dist/brain/project-hub-contract.js +56 -0
- package/dist/brain/project-hub-contract.js.map +1 -0
- package/dist/brain/project-hub-mcp.d.ts +168 -0
- package/dist/brain/project-hub-mcp.d.ts.map +1 -0
- package/dist/brain/project-hub-mcp.js +41 -0
- package/dist/brain/project-hub-mcp.js.map +1 -0
- package/dist/brain/project-hub-pdf-worker.d.ts +13 -0
- package/dist/brain/project-hub-pdf-worker.d.ts.map +1 -0
- package/dist/brain/project-hub-pdf-worker.js +84 -0
- package/dist/brain/project-hub-pdf-worker.js.map +1 -0
- package/dist/brain/project-hub-store.d.ts +38 -0
- package/dist/brain/project-hub-store.d.ts.map +1 -0
- package/dist/brain/project-hub-store.js +189 -0
- package/dist/brain/project-hub-store.js.map +1 -0
- package/dist/brain/project-hub.d.ts +127 -0
- package/dist/brain/project-hub.d.ts.map +1 -0
- package/dist/brain/project-hub.js +381 -0
- package/dist/brain/project-hub.js.map +1 -0
- package/dist/brain/project-kit-inspection.d.ts +28 -0
- package/dist/brain/project-kit-inspection.d.ts.map +1 -0
- package/dist/brain/project-kit-inspection.js +200 -0
- package/dist/brain/project-kit-inspection.js.map +1 -0
- package/dist/brain/project-kit-paths.d.ts +13 -0
- package/dist/brain/project-kit-paths.d.ts.map +1 -0
- package/dist/brain/project-kit-paths.js +141 -0
- package/dist/brain/project-kit-paths.js.map +1 -0
- package/dist/brain/project-kit.d.ts +41 -0
- package/dist/brain/project-kit.d.ts.map +1 -0
- package/dist/brain/project-kit.js +255 -0
- package/dist/brain/project-kit.js.map +1 -0
- package/dist/brain/project-memory.d.ts +38 -0
- package/dist/brain/project-memory.d.ts.map +1 -0
- package/dist/brain/project-memory.js +76 -0
- package/dist/brain/project-memory.js.map +1 -0
- package/dist/brain/subconscious.d.ts +21 -24
- package/dist/brain/subconscious.d.ts.map +1 -1
- package/dist/brain/subconscious.js +82 -218
- package/dist/brain/subconscious.js.map +1 -1
- package/dist/brain/workflow-skills.d.ts +5 -5
- package/dist/brain/workflow-skills.d.ts.map +1 -1
- package/dist/brain/workflow-skills.js +3 -2
- package/dist/brain/workflow-skills.js.map +1 -1
- package/dist/cli.js +40 -4
- package/dist/cli.js.map +1 -1
- package/dist/context-cli.d.ts +3 -0
- package/dist/context-cli.d.ts.map +1 -0
- package/dist/context-cli.js +90 -0
- package/dist/context-cli.js.map +1 -0
- package/dist/curation-cli.d.ts +4 -0
- package/dist/curation-cli.d.ts.map +1 -0
- package/dist/curation-cli.js +118 -0
- package/dist/curation-cli.js.map +1 -0
- package/dist/dashboard/brain-hub-actions.d.ts +2 -0
- package/dist/dashboard/brain-hub-actions.d.ts.map +1 -0
- package/dist/dashboard/brain-hub-actions.js +137 -0
- package/dist/dashboard/brain-hub-actions.js.map +1 -0
- package/dist/dashboard/brain-hub-client.d.ts +2 -0
- package/dist/dashboard/brain-hub-client.d.ts.map +1 -0
- package/dist/dashboard/brain-hub-client.js +335 -0
- package/dist/dashboard/brain-hub-client.js.map +1 -0
- package/dist/dashboard/brain-hub-graph.d.ts +2 -0
- package/dist/dashboard/brain-hub-graph.d.ts.map +1 -0
- package/dist/dashboard/brain-hub-graph.js +176 -0
- package/dist/dashboard/brain-hub-graph.js.map +1 -0
- package/dist/dashboard/brain-hub-html.d.ts +2 -0
- package/dist/dashboard/brain-hub-html.d.ts.map +1 -0
- package/dist/dashboard/brain-hub-html.js +30 -0
- package/dist/dashboard/brain-hub-html.js.map +1 -0
- package/dist/dashboard/brain-hub-project-views.d.ts +2 -0
- package/dist/dashboard/brain-hub-project-views.d.ts.map +1 -0
- package/dist/dashboard/brain-hub-project-views.js +79 -0
- package/dist/dashboard/brain-hub-project-views.js.map +1 -0
- package/dist/dashboard/brain-hub-views.d.ts +2 -0
- package/dist/dashboard/brain-hub-views.d.ts.map +1 -0
- package/dist/dashboard/brain-hub-views.js +150 -0
- package/dist/dashboard/brain-hub-views.js.map +1 -0
- package/dist/dashboard/demo-workspace.d.ts +11 -0
- package/dist/dashboard/demo-workspace.d.ts.map +1 -0
- package/dist/dashboard/demo-workspace.js +178 -0
- package/dist/dashboard/demo-workspace.js.map +1 -0
- package/dist/dashboard/live-workspace-html.d.ts +2 -0
- package/dist/dashboard/live-workspace-html.d.ts.map +1 -0
- package/dist/dashboard/live-workspace-html.js +1042 -0
- package/dist/dashboard/live-workspace-html.js.map +1 -0
- package/dist/dashboard/project-feedback-html.d.ts +5 -0
- package/dist/dashboard/project-feedback-html.d.ts.map +1 -0
- package/dist/dashboard/project-feedback-html.js +216 -0
- package/dist/dashboard/project-feedback-html.js.map +1 -0
- package/dist/dashboard/project-hub-api.d.ts +5 -0
- package/dist/dashboard/project-hub-api.d.ts.map +1 -0
- package/dist/dashboard/project-hub-api.js +121 -0
- package/dist/dashboard/project-hub-api.js.map +1 -0
- package/dist/dashboard/project-kit-html.d.ts +4 -0
- package/dist/dashboard/project-kit-html.d.ts.map +1 -0
- package/dist/dashboard/project-kit-html.js +359 -0
- package/dist/dashboard/project-kit-html.js.map +1 -0
- package/dist/dashboard/project-workspace.d.ts +16 -0
- package/dist/dashboard/project-workspace.d.ts.map +1 -0
- package/dist/dashboard/project-workspace.js +106 -0
- package/dist/dashboard/project-workspace.js.map +1 -0
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +65 -19
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
- package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
- package/dist/dashboard/v6-dashboard-html.js +34 -56
- package/dist/dashboard/v6-dashboard-html.js.map +1 -1
- package/dist/dashboard/workspace-api.d.ts +22 -0
- package/dist/dashboard/workspace-api.d.ts.map +1 -0
- package/dist/dashboard/workspace-api.js +429 -0
- package/dist/dashboard/workspace-api.js.map +1 -0
- package/dist/dashboard/workspace-html.d.ts +2 -0
- package/dist/dashboard/workspace-html.d.ts.map +1 -0
- package/dist/dashboard/workspace-html.js +2538 -0
- package/dist/dashboard/workspace-html.js.map +1 -0
- package/dist/dashboard/workspace-status.d.ts +50 -0
- package/dist/dashboard/workspace-status.d.ts.map +1 -0
- package/dist/dashboard/workspace-status.js +22 -0
- package/dist/dashboard/workspace-status.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/integration-cli.d.ts +3 -0
- package/dist/integration-cli.d.ts.map +1 -0
- package/dist/integration-cli.js +16 -0
- package/dist/integration-cli.js.map +1 -0
- package/dist/project-cli.d.ts +3 -0
- package/dist/project-cli.d.ts.map +1 -0
- package/dist/project-cli.js +100 -0
- package/dist/project-cli.js.map +1 -0
- package/dist/types.d.ts +12 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/COMPARISON.md +22 -0
- package/docs/COMPONENTS.md +66 -0
- package/docs/CONTEXT_LAB.md +79 -0
- package/docs/FEATURES_AND_LIMITS.md +24 -0
- package/docs/INTEGRATIONS.md +68 -0
- package/docs/LEARNING.md +46 -0
- package/docs/PROJECT_KIT.md +93 -0
- package/docs/QUICKSTART.md +44 -0
- package/docs/README.md +33 -0
- package/docs/ROADMAP-v7.md +147 -0
- package/docs/SHARING.md +13 -0
- package/docs/SHOWCASE.md +87 -0
- package/docs/STATS.md +21 -0
- package/docs/TROUBLESHOOTING.md +42 -0
- package/docs/UPGRADE_PLAN_v7.0.0.md +203 -0
- package/docs/VIRAL-PLAYBOOK.md +259 -0
- package/docs/VISUAL_GUIDE_v7.1.4.md +70 -0
- package/docs/WORKFLOW_INTEGRATION.md +19 -1
- package/docs/WORKSPACE.md +37 -0
- package/docs/assets/7.1.4-collaboration-design-preview.png +0 -0
- package/docs/assets/7.1.4-comparison.png +0 -0
- package/docs/assets/7.1.4-context-lab.png +0 -0
- package/docs/assets/7.1.4-control-room-dense-preview.png +0 -0
- package/docs/assets/7.1.4-control-room-focus-preview.png +0 -0
- package/docs/assets/7.1.4-features.png +0 -0
- package/docs/assets/7.1.4-graph.png +0 -0
- package/docs/assets/7.1.4-hub-control-room.png +0 -0
- package/docs/assets/7.1.4-hub-empty.png +0 -0
- package/docs/assets/7.1.4-hub-knowledge.png +0 -0
- package/docs/assets/7.1.4-hub-learning.png +0 -0
- package/docs/assets/7.1.4-hub-mobile.png +0 -0
- package/docs/assets/7.1.4-hub-public-docs-source.png +0 -0
- package/docs/assets/7.1.4-hub-public-docs.gif +0 -0
- package/docs/assets/7.1.4-hub-public-docs.png +0 -0
- package/docs/assets/7.1.4-hub-setup.png +0 -0
- package/docs/assets/7.1.4-hub-walkthrough.gif +0 -0
- package/docs/assets/7.1.4-integrations.png +0 -0
- package/docs/assets/7.1.4-knowledge-design-preview.png +0 -0
- package/docs/assets/7.1.4-learning-design-preview.png +0 -0
- package/docs/assets/7.1.4-learning.png +0 -0
- package/docs/assets/7.1.4-live.png +0 -0
- package/docs/assets/7.1.4-memory-review.png +0 -0
- package/docs/assets/7.1.4-mobile.png +0 -0
- package/docs/assets/7.1.4-models-design-preview.png +0 -0
- package/docs/assets/7.1.4-outcome-ranking.png +0 -0
- package/docs/assets/7.1.4-overview.png +0 -0
- package/docs/assets/7.1.4-project-kit.png +0 -0
- package/docs/assets/7.1.4-project-skill-preview.png +0 -0
- package/docs/assets/7.1.4-replay.png +0 -0
- package/docs/assets/7.1.4-setup-design-preview.png +0 -0
- package/docs/assets/7.1.4-usage-design-preview.png +0 -0
- package/docs/assets/context-flow-v7.2.png +0 -0
- package/docs/assets/context-v7.1.3.png +0 -0
- package/docs/assets/features-v7.1.3.png +0 -0
- package/docs/assets/graph-v7.1.3.png +0 -0
- package/docs/assets/integrations-v7.1.3.png +0 -0
- package/docs/assets/live-v7.1.3.png +0 -0
- package/docs/assets/live-v7.1.4.gif +0 -0
- package/docs/assets/memory-detail-v7.1.3.png +0 -0
- package/docs/assets/overview-v7.1.3.png +0 -0
- package/docs/assets/workspace-graph-v7.2.png +0 -0
- package/docs/assets/workspace-overview-v7.2.png +0 -0
- package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.json +1818 -0
- package/docs/evaluation/CONTEXT_EVALUATION_v7.1.4.md +36 -0
- package/docs/launch/COMPONENT_MAP_v7.1.4.md +307 -0
- package/docs/launch/PROJECT_KIT_PLAN_v7.1.4.md +110 -0
- package/docs/launch/PROJECT_LEARNING_PLAN_v7.1.4.md +63 -0
- package/docs/launch/RELEASE_NOTES_v7.1.3.md +40 -0
- package/docs/launch/RELEASE_NOTES_v7.1.4.md +30 -0
- package/docs/launch/RELEASE_NOTES_v7.2.0.md +24 -0
- package/docs/launch/RESEARCH_AND_PLAN_v7.1.4.md +255 -0
- package/package.json +43 -126
- package/skills/shadow-research/SKILL.md +2 -0
- package/skills/shadow-workflow/SKILL.md +6 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
|
+
import { matchesFileIdentity } from './file-identity.js';
|
|
7
|
+
export const FEATURE_DEFINITIONS = [
|
|
8
|
+
{
|
|
9
|
+
id: 'contextAssembly',
|
|
10
|
+
label: 'Context packs',
|
|
11
|
+
description: 'Assemble project memories into a bounded, explained context pack.',
|
|
12
|
+
defaultEnabled: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'crossProjectContext',
|
|
16
|
+
label: 'Cross-project context',
|
|
17
|
+
description: 'Allow context requests to include memories from other projects when explicitly selected.',
|
|
18
|
+
defaultEnabled: false,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'automaticBriefing',
|
|
22
|
+
label: 'Session briefings',
|
|
23
|
+
description: 'Generate the automatic project briefing at session start.',
|
|
24
|
+
defaultEnabled: true,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'liveActivity',
|
|
28
|
+
label: 'Live dashboard activity',
|
|
29
|
+
description: 'Show activity updates and the recent activity feed in the dashboard.',
|
|
30
|
+
defaultEnabled: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'activityRecording',
|
|
34
|
+
label: 'Record agent activity',
|
|
35
|
+
description: 'Record bounded tool timing and explicitly reported session phases. Arguments, memory text, and credentials are not captured.',
|
|
36
|
+
defaultEnabled: true,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
export const DEFAULT_FEATURE_FLAGS = Object.freeze({
|
|
40
|
+
contextAssembly: true,
|
|
41
|
+
crossProjectContext: false,
|
|
42
|
+
automaticBriefing: true,
|
|
43
|
+
liveActivity: true,
|
|
44
|
+
activityRecording: true,
|
|
45
|
+
});
|
|
46
|
+
export const FEATURE_SETTINGS_FILENAME = 'feature-settings.json';
|
|
47
|
+
const FEATURE_IDS = new Set(FEATURE_DEFINITIONS.map(({ id }) => id));
|
|
48
|
+
const ORIGINAL_FEATURE_IDS = ['contextAssembly', 'crossProjectContext', 'automaticBriefing', 'liveActivity'];
|
|
49
|
+
const LOCK_TIMEOUT_MS = 3000;
|
|
50
|
+
const LOCK_POLL_MS = 20;
|
|
51
|
+
export class FeatureSettingsError extends Error {
|
|
52
|
+
constructor(code, message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.code = code;
|
|
55
|
+
this.name = 'FeatureSettingsError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function errorCode(error) {
|
|
59
|
+
return error?.code;
|
|
60
|
+
}
|
|
61
|
+
function isRecord(value) {
|
|
62
|
+
if (!value || typeof value !== 'object')
|
|
63
|
+
return false;
|
|
64
|
+
const prototype = Object.getPrototypeOf(value);
|
|
65
|
+
return prototype === Object.prototype || prototype === null;
|
|
66
|
+
}
|
|
67
|
+
function isFeatureMap(value) {
|
|
68
|
+
return isRecord(value) && Object.entries(value).every(([key, enabled]) => FEATURE_IDS.has(key) && typeof enabled === 'boolean');
|
|
69
|
+
}
|
|
70
|
+
function defaultSnapshot() {
|
|
71
|
+
return { schemaVersion: 1, revision: 0, updatedAt: null, features: { ...DEFAULT_FEATURE_FLAGS } };
|
|
72
|
+
}
|
|
73
|
+
function parseSnapshot(raw) {
|
|
74
|
+
try {
|
|
75
|
+
const data = JSON.parse(raw);
|
|
76
|
+
if (isRecord(data) && data.schemaVersion === 1 && typeof data.revision === 'number' &&
|
|
77
|
+
Number.isSafeInteger(data.revision) && data.revision >= 1 &&
|
|
78
|
+
typeof data.updatedAt === 'string' && Number.isFinite(Date.parse(data.updatedAt)) &&
|
|
79
|
+
isFeatureMap(data.features) && ORIGINAL_FEATURE_IDS.every(id => Object.hasOwn(data.features, id)) &&
|
|
80
|
+
[FEATURE_IDS.size - 1, FEATURE_IDS.size].includes(Object.keys(data.features).length)) {
|
|
81
|
+
return {
|
|
82
|
+
schemaVersion: 1, revision: data.revision, updatedAt: data.updatedAt,
|
|
83
|
+
features: { ...DEFAULT_FEATURE_FLAGS, ...data.features },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch { /* Use a safe diagnostic without stored contents. */ }
|
|
88
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_STORE', 'Feature settings are malformed or use an unsupported schema.');
|
|
89
|
+
}
|
|
90
|
+
/** Read and patch the local feature settings file without accessing provider configuration. */
|
|
91
|
+
export class FeatureSettingsStore {
|
|
92
|
+
constructor(options = {}) {
|
|
93
|
+
this.filePath = path.resolve(options.filePath ?? path.join(os.homedir(), '.shadow-brain', FEATURE_SETTINGS_FILENAME));
|
|
94
|
+
this.lockTimeoutMs = typeof options.lockTimeoutMs === 'number' && Number.isFinite(options.lockTimeoutMs)
|
|
95
|
+
? Math.max(0, Math.min(options.lockTimeoutMs, 60000))
|
|
96
|
+
: LOCK_TIMEOUT_MS;
|
|
97
|
+
}
|
|
98
|
+
get() {
|
|
99
|
+
let raw;
|
|
100
|
+
try {
|
|
101
|
+
const info = fs.lstatSync(this.filePath, { throwIfNoEntry: false });
|
|
102
|
+
if (!info)
|
|
103
|
+
return defaultSnapshot();
|
|
104
|
+
if (!info.isFile() || info.isSymbolicLink() || info.nlink !== 1) {
|
|
105
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_STORE', 'Feature settings must be a regular local file.');
|
|
106
|
+
}
|
|
107
|
+
raw = fs.readFileSync(this.filePath, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (error instanceof FeatureSettingsError)
|
|
111
|
+
throw error;
|
|
112
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_READ_FAILED', 'Could not read feature settings.');
|
|
113
|
+
}
|
|
114
|
+
return parseSnapshot(raw);
|
|
115
|
+
}
|
|
116
|
+
isEnabled(id) {
|
|
117
|
+
if (!FEATURE_IDS.has(id)) {
|
|
118
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_PATCH', 'Unknown feature setting.');
|
|
119
|
+
}
|
|
120
|
+
return this.get().features[id];
|
|
121
|
+
}
|
|
122
|
+
async update(patch, expectedRevision) {
|
|
123
|
+
if (!isFeatureMap(patch) || (expectedRevision !== undefined &&
|
|
124
|
+
(!Number.isSafeInteger(expectedRevision) || expectedRevision < 0))) {
|
|
125
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_PATCH', 'Feature settings accept only known feature IDs and boolean values.');
|
|
126
|
+
}
|
|
127
|
+
const changes = { ...patch };
|
|
128
|
+
if (!Object.keys(changes).length)
|
|
129
|
+
return this.checkRevision(this.get(), expectedRevision);
|
|
130
|
+
try {
|
|
131
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_WRITE_FAILED', 'Could not open the feature settings directory.');
|
|
135
|
+
}
|
|
136
|
+
const release = await this.acquireLock();
|
|
137
|
+
try {
|
|
138
|
+
const current = this.checkRevision(this.get(), expectedRevision);
|
|
139
|
+
if (Object.keys(changes).every(id => changes[id] === current.features[id]))
|
|
140
|
+
return current;
|
|
141
|
+
if (current.revision === Number.MAX_SAFE_INTEGER) {
|
|
142
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_INVALID_STORE', 'Feature settings revision has reached its limit.');
|
|
143
|
+
}
|
|
144
|
+
const next = {
|
|
145
|
+
schemaVersion: 1,
|
|
146
|
+
revision: current.revision + 1,
|
|
147
|
+
updatedAt: new Date().toISOString(),
|
|
148
|
+
features: { ...current.features, ...changes },
|
|
149
|
+
};
|
|
150
|
+
this.replace(next);
|
|
151
|
+
return next;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
release();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
checkRevision(snapshot, expectedRevision) {
|
|
158
|
+
if (expectedRevision !== undefined && expectedRevision !== snapshot.revision) {
|
|
159
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_CONFLICT', 'Feature settings changed. Refresh them and retry the update.');
|
|
160
|
+
}
|
|
161
|
+
return snapshot;
|
|
162
|
+
}
|
|
163
|
+
async acquireLock() {
|
|
164
|
+
const lock = this.filePath + '.lock';
|
|
165
|
+
const deadline = performance.now() + this.lockTimeoutMs;
|
|
166
|
+
for (;;) {
|
|
167
|
+
let fd;
|
|
168
|
+
try {
|
|
169
|
+
fd = fs.openSync(lock, 'wx', 0o600);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (errorCode(error) !== 'EEXIST') {
|
|
173
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_FAILED', 'Could not acquire the feature settings lock.');
|
|
174
|
+
}
|
|
175
|
+
const remaining = deadline - performance.now();
|
|
176
|
+
if (remaining <= 0)
|
|
177
|
+
break;
|
|
178
|
+
await delay(Math.min(remaining, LOCK_POLL_MS));
|
|
179
|
+
if (performance.now() >= deadline)
|
|
180
|
+
break;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
let owner;
|
|
184
|
+
try {
|
|
185
|
+
owner = fs.fstatSync(fd, { bigint: true });
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
try {
|
|
189
|
+
fs.closeSync(fd);
|
|
190
|
+
}
|
|
191
|
+
catch { /* Preserve the original lock error. */ }
|
|
192
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_FAILED', 'Could not identify the feature settings lock.');
|
|
193
|
+
}
|
|
194
|
+
return () => {
|
|
195
|
+
try {
|
|
196
|
+
fs.closeSync(fd);
|
|
197
|
+
const current = fs.lstatSync(lock, { bigint: true, throwIfNoEntry: false });
|
|
198
|
+
if (!matchesFileIdentity(lock, owner, current)) {
|
|
199
|
+
throw new Error('Lock ownership changed.');
|
|
200
|
+
}
|
|
201
|
+
fs.unlinkSync(lock);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_FAILED', 'Could not release the feature settings lock.');
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_LOCK_TIMEOUT', 'Timed out waiting for the feature settings lock.');
|
|
209
|
+
}
|
|
210
|
+
replace(snapshot) {
|
|
211
|
+
const temporary = this.filePath + '.' + randomUUID() + '.tmp';
|
|
212
|
+
let created = false;
|
|
213
|
+
try {
|
|
214
|
+
const fd = fs.openSync(temporary, 'wx', 0o600);
|
|
215
|
+
created = true;
|
|
216
|
+
try {
|
|
217
|
+
fs.writeFileSync(fd, JSON.stringify(snapshot, null, 2) + '\n');
|
|
218
|
+
fs.fsyncSync(fd);
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
fs.closeSync(fd);
|
|
222
|
+
}
|
|
223
|
+
fs.renameSync(temporary, this.filePath);
|
|
224
|
+
created = false;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
throw new FeatureSettingsError('FEATURE_SETTINGS_WRITE_FAILED', 'Could not save feature settings.');
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
if (created) {
|
|
231
|
+
try {
|
|
232
|
+
fs.unlinkSync(temporary);
|
|
233
|
+
}
|
|
234
|
+
catch { /* Preserve the original write error. */ }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
export function getFeatureSettings() {
|
|
240
|
+
return new FeatureSettingsStore();
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=feature-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-settings.js","sourceRoot":"","sources":["../../src/brain/feature-settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
/** Match a regular opened file to its current project or lock path. */
|
|
3
|
+
export declare function matchesFileIdentity(filename: string, handle: fs.BigIntStats, named: fs.BigIntStats | undefined): boolean;
|
|
4
|
+
//# sourceMappingURL=file-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-identity.d.ts","sourceRoot":"","sources":["../../src/brain/file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,WAAW,GAAG,SAAS,GAAG,OAAO,CAiBxH"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
/** Match a regular opened file to its current project or lock path. */
|
|
3
|
+
export function matchesFileIdentity(filename, handle, named) {
|
|
4
|
+
if (!named?.isFile() || !handle.isFile() || handle.nlink !== 1n || named.nlink !== 1n || handle.ino === 0n || named.ino !== handle.ino)
|
|
5
|
+
return false;
|
|
6
|
+
if (named.dev === handle.dev)
|
|
7
|
+
return true;
|
|
8
|
+
// Some Windows Node releases report device zero for path stats, but expose the volume through an open handle.
|
|
9
|
+
if (process.platform !== 'win32' || named.dev !== 0n || handle.dev === 0n)
|
|
10
|
+
return false;
|
|
11
|
+
let fd;
|
|
12
|
+
try {
|
|
13
|
+
fd = fs.openSync(filename, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
14
|
+
const reopened = fs.fstatSync(fd, { bigint: true });
|
|
15
|
+
const current = fs.lstatSync(filename, { bigint: true, throwIfNoEntry: false });
|
|
16
|
+
return reopened.isFile() && reopened.nlink === 1n && reopened.dev === handle.dev && reopened.ino === handle.ino &&
|
|
17
|
+
current?.isFile() === true && current.nlink === 1n && current.dev === named.dev && current.ino === named.ino;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
if (fd !== undefined)
|
|
24
|
+
fs.closeSync(fd);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=file-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-identity.js","sourceRoot":"","sources":["../../src/brain/file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,uEAAuE;AACvE,MAAM,UAAU,mBAAmB,CAAC,QAAgB,EAAE,MAAsB,EAAE,KAAiC;IAC7G,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC,KAAK,KAAK,EAAE,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACrJ,IAAI,KAAK,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAC1C,8GAA8G;IAC9G,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,KAAK,CAAC,GAAG,KAAK,EAAE,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACxF,IAAI,EAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC;QACnF,MAAM,QAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;QAChF,OAAO,QAAQ,CAAC,MAAM,EAAE,IAAI,QAAQ,CAAC,KAAK,KAAK,EAAE,IAAI,QAAQ,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG;YAC7G,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,KAAK,EAAE,IAAI,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,KAAK,KAAK,CAAC,GAAG,CAAC;IACjH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,SAAS;YAAE,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;AACH,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentTool, BrainTimelineEvent, GlobalBrainConfig, GlobalBrainStats, GlobalBrainStorageHealth, GlobalEntry, ScoredGlobalEntry } from '../types.js';
|
|
2
|
+
import { type ContextSearchMemory } from './context-retrieval.js';
|
|
2
3
|
type EntryInput = Omit<GlobalEntry, 'id' | 'createdAt' | 'lastAccessed' | 'accessCount'>;
|
|
3
4
|
interface RecallOptions {
|
|
4
5
|
projectId?: string;
|
|
@@ -7,7 +8,10 @@ interface RecallOptions {
|
|
|
7
8
|
keywords?: string[];
|
|
8
9
|
limit?: number;
|
|
9
10
|
minImportance?: number;
|
|
11
|
+
/** Exclude curated memories before matching, corroboration, and candidate limits. */
|
|
12
|
+
excludeIds?: ReadonlySet<string>;
|
|
10
13
|
}
|
|
14
|
+
export declare function normalizeRecallKeywords(keywords: unknown): string[];
|
|
11
15
|
/** Local JSON store shared by cooperating Global Brain processes. */
|
|
12
16
|
export declare class GlobalBrain {
|
|
13
17
|
private static instance;
|
|
@@ -24,6 +28,10 @@ export declare class GlobalBrain {
|
|
|
24
28
|
private lastError;
|
|
25
29
|
private constructor();
|
|
26
30
|
static getInstance(config?: Partial<GlobalBrainConfig>): GlobalBrain;
|
|
31
|
+
/** Explicit independent store. Does not replace or consult the global singleton. */
|
|
32
|
+
static createIsolated(config: Partial<GlobalBrainConfig> & {
|
|
33
|
+
dbPath: string;
|
|
34
|
+
}): GlobalBrain;
|
|
27
35
|
init(): Promise<void>;
|
|
28
36
|
private initialize;
|
|
29
37
|
/** Generate a stable project ID from absolute path. */
|
|
@@ -33,9 +41,10 @@ export declare class GlobalBrain {
|
|
|
33
41
|
/** Queue a write. A failed attempt resolves false and retains the entry for a later sync. */
|
|
34
42
|
write(entry: EntryInput): Promise<boolean>;
|
|
35
43
|
/** Stage an entry for later sync and return its ID immediately. */
|
|
36
|
-
writeSync(entry: EntryInput): string;
|
|
44
|
+
writeSync(entry: EntryInput, id?: string): string;
|
|
37
45
|
private requireWritable;
|
|
38
46
|
private prepareEntry;
|
|
47
|
+
private requireSameExplicitEntry;
|
|
39
48
|
private publishWrite;
|
|
40
49
|
/** Recall matching entries across projects and agents. */
|
|
41
50
|
recall(opts?: RecallOptions): GlobalEntry[];
|
|
@@ -43,6 +52,8 @@ export declare class GlobalBrain {
|
|
|
43
52
|
recallScored(opts?: RecallOptions & {
|
|
44
53
|
minConfidence?: number;
|
|
45
54
|
}): ScoredGlobalEntry[];
|
|
55
|
+
/** Read the scoped fields used by context ranking without exposing mutable memory records. */
|
|
56
|
+
contextEntries(opts: Pick<RecallOptions, 'projectId' | 'minImportance' | 'excludeIds'>): IterableIterator<ContextSearchMemory>;
|
|
46
57
|
private findMatches;
|
|
47
58
|
private compareEntries;
|
|
48
59
|
private recordRecall;
|
|
@@ -50,6 +61,12 @@ export declare class GlobalBrain {
|
|
|
50
61
|
/** Bump an entry's access count without replacing its persisted snapshot. */
|
|
51
62
|
touch(id: string): void;
|
|
52
63
|
recallByIds(ids: string[]): GlobalEntry[];
|
|
64
|
+
/** Read one memory without exposing mutable in-memory state or recording a recall. */
|
|
65
|
+
getEntry(id: string): GlobalEntry | undefined;
|
|
66
|
+
/** Hash the actual read view, including staged entries, without committing or recording access. */
|
|
67
|
+
readFingerprint(projectId?: string): string;
|
|
68
|
+
/** Refresh the local read view without acknowledging or committing pending operations. */
|
|
69
|
+
refresh(): void;
|
|
53
70
|
/** Chronological memory stream for dashboards and handoffs. */
|
|
54
71
|
timeline(opts?: Pick<RecallOptions, 'projectId' | 'agentTool' | 'category' | 'limit'>): BrainTimelineEvent[];
|
|
55
72
|
getStats(): GlobalBrainStats;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global-brain.d.ts","sourceRoot":"","sources":["../../src/brain/global-brain.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"global-brain.d.ts","sourceRoot":"","sources":["../../src/brain/global-brain.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,8 +2,11 @@ import * as fs from 'fs';
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import * as crypto from 'crypto';
|
|
5
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
5
6
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
7
|
import { getBrainBus } from './brain-bus.js';
|
|
8
|
+
import { contextSearchMemory } from './context-retrieval.js';
|
|
9
|
+
import { matchesFileIdentity } from './file-identity.js';
|
|
7
10
|
const DEFAULT_DB_PATH = path.join(os.homedir(), '.shadow-brain', 'global.json');
|
|
8
11
|
const LOCK_TIMEOUT_MS = 5000;
|
|
9
12
|
const LOCK_POLL_MS = 20;
|
|
@@ -89,7 +92,7 @@ function hydrateEntry(entry) {
|
|
|
89
92
|
function normalizeText(value) {
|
|
90
93
|
return value.trim().replace(/\s+/g, ' ').toLowerCase();
|
|
91
94
|
}
|
|
92
|
-
function
|
|
95
|
+
export function normalizeRecallKeywords(keywords) {
|
|
93
96
|
if (!Array.isArray(keywords))
|
|
94
97
|
return [];
|
|
95
98
|
const unique = new Set();
|
|
@@ -148,6 +151,12 @@ export class GlobalBrain {
|
|
|
148
151
|
GlobalBrain.instance ?? (GlobalBrain.instance = new GlobalBrain(config));
|
|
149
152
|
return GlobalBrain.instance;
|
|
150
153
|
}
|
|
154
|
+
/** Explicit independent store. Does not replace or consult the global singleton. */
|
|
155
|
+
static createIsolated(config) {
|
|
156
|
+
if (typeof config.dbPath !== 'string' || !config.dbPath.trim())
|
|
157
|
+
throw new TypeError('An explicit memory-store path is required.');
|
|
158
|
+
return new GlobalBrain(config);
|
|
159
|
+
}
|
|
151
160
|
async init() {
|
|
152
161
|
this.initialize();
|
|
153
162
|
}
|
|
@@ -211,9 +220,17 @@ export class GlobalBrain {
|
|
|
211
220
|
});
|
|
212
221
|
}
|
|
213
222
|
/** Stage an entry for later sync and return its ID immediately. */
|
|
214
|
-
writeSync(entry) {
|
|
223
|
+
writeSync(entry, id) {
|
|
215
224
|
this.requireWritable();
|
|
216
|
-
|
|
225
|
+
if (id !== undefined && (typeof id !== 'string' || !/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/u.test(id))) {
|
|
226
|
+
throw new TypeError('An explicit memory ID must be a UUID.');
|
|
227
|
+
}
|
|
228
|
+
const stored = this.prepareEntry(entry, id);
|
|
229
|
+
const existing = this.entries.get(stored.id);
|
|
230
|
+
if (existing) {
|
|
231
|
+
this.requireSameExplicitEntry(existing, stored);
|
|
232
|
+
return stored.id;
|
|
233
|
+
}
|
|
217
234
|
this.pendingOperations.push({ kind: 'add', entry: stored, queued: false });
|
|
218
235
|
this.entries.set(stored.id, hydrateEntry(structuredClone(stored)));
|
|
219
236
|
return stored.id;
|
|
@@ -224,12 +241,12 @@ export class GlobalBrain {
|
|
|
224
241
|
if (this.shuttingDown)
|
|
225
242
|
throw new Error('GlobalBrain is shutting down');
|
|
226
243
|
}
|
|
227
|
-
prepareEntry(entry) {
|
|
244
|
+
prepareEntry(entry, id = crypto.randomUUID()) {
|
|
228
245
|
const now = new Date();
|
|
229
246
|
// Snapshot caller input now so later mutations or serialization errors cannot block a commit.
|
|
230
247
|
try {
|
|
231
248
|
const data = JSON.parse(JSON.stringify({
|
|
232
|
-
...entry, id
|
|
249
|
+
...entry, id, createdAt: now, lastAccessed: now, accessCount: 0,
|
|
233
250
|
}));
|
|
234
251
|
if (hasUniqueKeys([data], 'id', ['projectId', 'projectName', 'agentTool', 'category', 'content'])) {
|
|
235
252
|
return data;
|
|
@@ -238,6 +255,14 @@ export class GlobalBrain {
|
|
|
238
255
|
catch { /* Report an invalid input without exposing its contents. */ }
|
|
239
256
|
throw new StorageError('STORE_INVALID_ENTRY', 'The memory entry cannot be stored as JSON.');
|
|
240
257
|
}
|
|
258
|
+
requireSameExplicitEntry(previous, next) {
|
|
259
|
+
const stable = ({ createdAt, lastAccessed, accessCount, ...entry }) => ({
|
|
260
|
+
...entry, importance: boundedSignal(entry.importance), metadata: isRecord(entry.metadata) ? entry.metadata : {},
|
|
261
|
+
});
|
|
262
|
+
if (!isDeepStrictEqual(stable(previous), stable(next))) {
|
|
263
|
+
throw new StorageError('STORE_INVALID_ENTRY', 'This memory ID is already assigned to a different record.');
|
|
264
|
+
}
|
|
265
|
+
}
|
|
241
266
|
publishWrite(entry) {
|
|
242
267
|
try {
|
|
243
268
|
getBrainBus().publish({
|
|
@@ -260,7 +285,7 @@ export class GlobalBrain {
|
|
|
260
285
|
recall(opts = {}) {
|
|
261
286
|
if (!this.initialized)
|
|
262
287
|
return [];
|
|
263
|
-
const keywords =
|
|
288
|
+
const keywords = normalizeRecallKeywords(opts.keywords);
|
|
264
289
|
const result = this.findMatches(opts, keywords)
|
|
265
290
|
.sort((a, b) => b.relevance - a.relevance || this.compareEntries(a.entry, b.entry))
|
|
266
291
|
.slice(0, nonNegativeInt(opts.limit, 20))
|
|
@@ -272,7 +297,7 @@ export class GlobalBrain {
|
|
|
272
297
|
recallScored(opts = {}) {
|
|
273
298
|
if (!this.initialized)
|
|
274
299
|
return [];
|
|
275
|
-
const keywords =
|
|
300
|
+
const keywords = normalizeRecallKeywords(opts.keywords);
|
|
276
301
|
const candidates = this.findMatches(opts, keywords);
|
|
277
302
|
const evidence = new Map();
|
|
278
303
|
for (const { entry } of candidates) {
|
|
@@ -280,6 +305,8 @@ export class GlobalBrain {
|
|
|
280
305
|
evidence.set(evidenceKey(entry), new Set());
|
|
281
306
|
}
|
|
282
307
|
for (const entry of this.entries.values()) {
|
|
308
|
+
if (opts.excludeIds?.has(entry.id))
|
|
309
|
+
continue;
|
|
283
310
|
evidence.get(evidenceKey(entry))?.add(entry.agentTool);
|
|
284
311
|
}
|
|
285
312
|
const now = Date.now();
|
|
@@ -298,10 +325,25 @@ export class GlobalBrain {
|
|
|
298
325
|
this.recordRecall(result.length);
|
|
299
326
|
return result;
|
|
300
327
|
}
|
|
328
|
+
/** Read the scoped fields used by context ranking without exposing mutable memory records. */
|
|
329
|
+
*contextEntries(opts) {
|
|
330
|
+
if (!this.initialized)
|
|
331
|
+
return;
|
|
332
|
+
const minImportance = boundedSignal(opts.minImportance);
|
|
333
|
+
for (const entry of this.entries.values()) {
|
|
334
|
+
if (opts.projectId && entry.projectId !== opts.projectId)
|
|
335
|
+
continue;
|
|
336
|
+
if (opts.excludeIds?.has(entry.id) || entry.importance < minImportance)
|
|
337
|
+
continue;
|
|
338
|
+
yield contextSearchMemory(entry);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
301
341
|
findMatches(opts, keywords) {
|
|
302
342
|
const minImportance = boundedSignal(opts.minImportance);
|
|
303
343
|
const matches = [];
|
|
304
344
|
for (const entry of this.entries.values()) {
|
|
345
|
+
if (opts.excludeIds?.has(entry.id))
|
|
346
|
+
continue;
|
|
305
347
|
if (opts.projectId && entry.projectId !== opts.projectId)
|
|
306
348
|
continue;
|
|
307
349
|
if (opts.agentTool && entry.agentTool !== opts.agentTool)
|
|
@@ -392,6 +434,27 @@ export class GlobalBrain {
|
|
|
392
434
|
}
|
|
393
435
|
return out;
|
|
394
436
|
}
|
|
437
|
+
/** Read one memory without exposing mutable in-memory state or recording a recall. */
|
|
438
|
+
getEntry(id) {
|
|
439
|
+
const entry = this.entries.get(id);
|
|
440
|
+
return entry ? structuredClone(entry) : undefined;
|
|
441
|
+
}
|
|
442
|
+
/** Hash the actual read view, including staged entries, without committing or recording access. */
|
|
443
|
+
readFingerprint(projectId) {
|
|
444
|
+
const hash = crypto.createHash('sha256');
|
|
445
|
+
for (const entry of this.entries.values()) {
|
|
446
|
+
if (projectId !== undefined && entry.projectId !== projectId)
|
|
447
|
+
continue;
|
|
448
|
+
hash.update(JSON.stringify(entry));
|
|
449
|
+
hash.update('\n');
|
|
450
|
+
}
|
|
451
|
+
return hash.digest('hex');
|
|
452
|
+
}
|
|
453
|
+
/** Refresh the local read view without acknowledging or committing pending operations. */
|
|
454
|
+
refresh() {
|
|
455
|
+
this.initialize();
|
|
456
|
+
this.refreshSnapshot();
|
|
457
|
+
}
|
|
395
458
|
/** Chronological memory stream for dashboards and handoffs. */
|
|
396
459
|
timeline(opts = {}) {
|
|
397
460
|
if (!this.initialized)
|
|
@@ -541,7 +604,8 @@ export class GlobalBrain {
|
|
|
541
604
|
refreshSnapshot() {
|
|
542
605
|
try {
|
|
543
606
|
const { data, sizeMB } = this.readLatestSnapshot();
|
|
544
|
-
this.
|
|
607
|
+
const view = this.pendingOperations.length ? this.mergeOperations(data, this.pendingOperations) : data;
|
|
608
|
+
this.adoptSnapshot(view, sizeMB, this.hydrateEntries(view));
|
|
545
609
|
if (this.lastError?.code !== 'STORE_LOCK_FAILED')
|
|
546
610
|
this.lastError = null;
|
|
547
611
|
}
|
|
@@ -556,7 +620,10 @@ export class GlobalBrain {
|
|
|
556
620
|
for (const operation of operations) {
|
|
557
621
|
switch (operation.kind) {
|
|
558
622
|
case 'add': {
|
|
559
|
-
|
|
623
|
+
const previous = entries.get(operation.entry.id);
|
|
624
|
+
if (previous)
|
|
625
|
+
this.requireSameExplicitEntry(previous, operation.entry);
|
|
626
|
+
else
|
|
560
627
|
entries.set(operation.entry.id, operation.entry);
|
|
561
628
|
break;
|
|
562
629
|
}
|
|
@@ -650,7 +717,7 @@ export class GlobalBrain {
|
|
|
650
717
|
// A failed token write can leave partial bytes. Check the exclusive file's identity.
|
|
651
718
|
try {
|
|
652
719
|
const current = fs.lstatSync(lockPath, { bigint: true });
|
|
653
|
-
if (identity &&
|
|
720
|
+
if (identity && matchesFileIdentity(lockPath, identity, current))
|
|
654
721
|
fs.unlinkSync(lockPath);
|
|
655
722
|
}
|
|
656
723
|
catch { /* report the lock initialization error */ }
|