@kinqs/brainrouter-mcp-server 0.3.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/.env.example +144 -0
- package/README.md +56 -0
- package/agents/README.md +120 -0
- package/agents/code-reviewer.md +97 -0
- package/agents/security-auditor.md +101 -0
- package/agents/test-engineer.md +95 -0
- package/dist/__tests__/agent_mode.test.d.ts +1 -0
- package/dist/__tests__/api-routes.test.d.ts +1 -0
- package/dist/__tests__/api-routes.test.js +170 -0
- package/dist/__tests__/crypto.test.d.ts +1 -0
- package/dist/__tests__/crypto.test.js +28 -0
- package/dist/__tests__/host-integrations.test.d.ts +1 -0
- package/dist/__tests__/host-integrations.test.js +82 -0
- package/dist/__tests__/integration.test.d.ts +1 -0
- package/dist/__tests__/integration.test.js +50 -0
- package/dist/__tests__/loader.test.d.ts +1 -0
- package/dist/__tests__/loader.test.js +89 -0
- package/dist/__tests__/neural-spark.test.d.ts +1 -0
- package/dist/__tests__/neural-spark.test.js +112 -0
- package/dist/__tests__/pagination.test.d.ts +1 -0
- package/dist/__tests__/pagination.test.js +23 -0
- package/dist/__tests__/redaction.test.d.ts +1 -0
- package/dist/__tests__/redaction.test.js +17 -0
- package/dist/__tests__/registry.test.d.ts +1 -0
- package/dist/__tests__/registry.test.js +56 -0
- package/dist/__tests__/retry.test.d.ts +1 -0
- package/dist/__tests__/retry.test.js +30 -0
- package/dist/__tests__/skill-activation.test.d.ts +1 -0
- package/dist/__tests__/skill-activation.test.js +112 -0
- package/dist/__tests__/working-memory.test.d.ts +1 -0
- package/dist/__tests__/working-memory.test.js +200 -0
- package/dist/__tests__/workspace-paths.test.d.ts +1 -0
- package/dist/__tests__/workspace-paths.test.js +56 -0
- package/dist/__tests__/writer.test.d.ts +1 -0
- package/dist/__tests__/writer.test.js +94 -0
- package/dist/api/auth/crypto.d.ts +4 -0
- package/dist/api/auth/crypto.js +54 -0
- package/dist/api/middleware/auth.d.ts +12 -0
- package/dist/api/middleware/auth.js +90 -0
- package/dist/api/pagination.d.ts +18 -0
- package/dist/api/pagination.js +32 -0
- package/dist/api/routes/auth.d.ts +1 -0
- package/dist/api/routes/auth.js +130 -0
- package/dist/api/routes/chat-completions.d.ts +7 -0
- package/dist/api/routes/chat-completions.js +474 -0
- package/dist/api/routes/contradictions.d.ts +1 -0
- package/dist/api/routes/contradictions.js +28 -0
- package/dist/api/routes/evidence.d.ts +1 -0
- package/dist/api/routes/evidence.js +59 -0
- package/dist/api/routes/governance.d.ts +1 -0
- package/dist/api/routes/governance.js +95 -0
- package/dist/api/routes/graph.d.ts +1 -0
- package/dist/api/routes/graph.js +25 -0
- package/dist/api/routes/hooks.d.ts +1 -0
- package/dist/api/routes/hooks.js +88 -0
- package/dist/api/routes/memories.d.ts +1 -0
- package/dist/api/routes/memories.js +92 -0
- package/dist/api/routes/persona.d.ts +1 -0
- package/dist/api/routes/persona.js +9 -0
- package/dist/api/routes/scenes.d.ts +1 -0
- package/dist/api/routes/scenes.js +35 -0
- package/dist/api/routes/skills.d.ts +1 -0
- package/dist/api/routes/skills.js +14 -0
- package/dist/api/routes/stats.d.ts +1 -0
- package/dist/api/routes/stats.js +8 -0
- package/dist/api/routes/users.d.ts +1 -0
- package/dist/api/routes/users.js +82 -0
- package/dist/api/routes/working.d.ts +1 -0
- package/dist/api/routes/working.js +88 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +492 -0
- package/dist/integrations/claude-code.d.ts +12 -0
- package/dist/integrations/claude-code.js +35 -0
- package/dist/integrations/codex.d.ts +12 -0
- package/dist/integrations/codex.js +34 -0
- package/dist/integrations/generic-mcp.d.ts +52 -0
- package/dist/integrations/generic-mcp.js +118 -0
- package/dist/loader.d.ts +29 -0
- package/dist/loader.js +200 -0
- package/dist/memory/capture.d.ts +35 -0
- package/dist/memory/capture.js +230 -0
- package/dist/memory/config.d.ts +2 -0
- package/dist/memory/config.js +3 -0
- package/dist/memory/engine.d.ts +203 -0
- package/dist/memory/engine.js +626 -0
- package/dist/memory/llm-semaphore.d.ts +41 -0
- package/dist/memory/llm-semaphore.js +81 -0
- package/dist/memory/memory-type-config.d.ts +11 -0
- package/dist/memory/memory-type-config.js +65 -0
- package/dist/memory/pipeline/cognitive-contradiction.d.ts +7 -0
- package/dist/memory/pipeline/cognitive-contradiction.js +59 -0
- package/dist/memory/pipeline/cognitive-dedup.d.ts +23 -0
- package/dist/memory/pipeline/cognitive-dedup.js +38 -0
- package/dist/memory/pipeline/cognitive-extractor.d.ts +21 -0
- package/dist/memory/pipeline/cognitive-extractor.js +183 -0
- package/dist/memory/pipeline/contextual-focus-builder.d.ts +13 -0
- package/dist/memory/pipeline/contextual-focus-builder.js +135 -0
- package/dist/memory/pipeline/focus-direction-shift.d.ts +10 -0
- package/dist/memory/pipeline/focus-direction-shift.js +27 -0
- package/dist/memory/pipeline/graph-builder.d.ts +11 -0
- package/dist/memory/pipeline/graph-builder.js +88 -0
- package/dist/memory/pipeline/graph-recall.d.ts +13 -0
- package/dist/memory/pipeline/graph-recall.js +55 -0
- package/dist/memory/pipeline/identity-distiller.d.ts +15 -0
- package/dist/memory/pipeline/identity-distiller.js +40 -0
- package/dist/memory/pipeline/l1-contradiction.d.ts +7 -0
- package/dist/memory/pipeline/l1-contradiction.js +66 -0
- package/dist/memory/pipeline/l1-dedup.d.ts +23 -0
- package/dist/memory/pipeline/l1-dedup.js +39 -0
- package/dist/memory/pipeline/l1-extractor.d.ts +21 -0
- package/dist/memory/pipeline/l1-extractor.js +180 -0
- package/dist/memory/pipeline/l2-direction-shift.d.ts +10 -0
- package/dist/memory/pipeline/l2-direction-shift.js +27 -0
- package/dist/memory/pipeline/l2-scene.d.ts +15 -0
- package/dist/memory/pipeline/l2-scene.js +140 -0
- package/dist/memory/pipeline/l3-distiller.d.ts +15 -0
- package/dist/memory/pipeline/l3-distiller.js +40 -0
- package/dist/memory/pipeline/neural-spark.d.ts +27 -0
- package/dist/memory/pipeline/neural-spark.js +78 -0
- package/dist/memory/pipeline/skill-prewarm.d.ts +63 -0
- package/dist/memory/pipeline/skill-prewarm.js +127 -0
- package/dist/memory/pipeline/task-queue.d.ts +54 -0
- package/dist/memory/pipeline/task-queue.js +117 -0
- package/dist/memory/prompts/cognitive-contradiction.d.ts +1 -0
- package/dist/memory/prompts/cognitive-contradiction.js +25 -0
- package/dist/memory/prompts/cognitive-extraction.d.ts +10 -0
- package/dist/memory/prompts/cognitive-extraction.js +114 -0
- package/dist/memory/prompts/core-identity.d.ts +6 -0
- package/dist/memory/prompts/core-identity.js +60 -0
- package/dist/memory/prompts/focus-direction-shift.d.ts +5 -0
- package/dist/memory/prompts/focus-direction-shift.js +32 -0
- package/dist/memory/prompts/focus-scene-cluster.d.ts +2 -0
- package/dist/memory/prompts/focus-scene-cluster.js +33 -0
- package/dist/memory/prompts/focus-scene.d.ts +7 -0
- package/dist/memory/prompts/focus-scene.js +40 -0
- package/dist/memory/prompts/graph-extraction-batch.d.ts +14 -0
- package/dist/memory/prompts/graph-extraction-batch.js +54 -0
- package/dist/memory/prompts/graph-extraction.d.ts +2 -0
- package/dist/memory/prompts/graph-extraction.js +53 -0
- package/dist/memory/prompts/l1-contradiction-batch.d.ts +16 -0
- package/dist/memory/prompts/l1-contradiction-batch.js +47 -0
- package/dist/memory/prompts/l1-contradiction.d.ts +1 -0
- package/dist/memory/prompts/l1-contradiction.js +25 -0
- package/dist/memory/prompts/l1-extraction.d.ts +10 -0
- package/dist/memory/prompts/l1-extraction.js +114 -0
- package/dist/memory/prompts/l2-direction-shift.d.ts +5 -0
- package/dist/memory/prompts/l2-direction-shift.js +32 -0
- package/dist/memory/prompts/l2-scene-cluster.d.ts +2 -0
- package/dist/memory/prompts/l2-scene-cluster.js +33 -0
- package/dist/memory/prompts/l2-scene.d.ts +7 -0
- package/dist/memory/prompts/l2-scene.js +40 -0
- package/dist/memory/prompts/l3-persona.d.ts +6 -0
- package/dist/memory/prompts/l3-persona.js +60 -0
- package/dist/memory/recall.d.ts +47 -0
- package/dist/memory/recall.js +427 -0
- package/dist/memory/redaction.d.ts +1 -0
- package/dist/memory/redaction.js +24 -0
- package/dist/memory/retry.d.ts +13 -0
- package/dist/memory/retry.js +53 -0
- package/dist/memory/scheduler.d.ts +9 -0
- package/dist/memory/scheduler.js +16 -0
- package/dist/memory/skill-hints-loader.d.ts +30 -0
- package/dist/memory/skill-hints-loader.js +100 -0
- package/dist/memory/store/embedding.d.ts +16 -0
- package/dist/memory/store/embedding.js +68 -0
- package/dist/memory/store/reranker.d.ts +24 -0
- package/dist/memory/store/reranker.js +83 -0
- package/dist/memory/store/sqlite.d.ts +167 -0
- package/dist/memory/store/sqlite.js +1816 -0
- package/dist/memory/store/types.d.ts +101 -0
- package/dist/memory/store/types.js +1 -0
- package/dist/memory/types.d.ts +207 -0
- package/dist/memory/types.js +7 -0
- package/dist/memory/validation.d.ts +441 -0
- package/dist/memory/validation.js +129 -0
- package/dist/memory/working/canvas.d.ts +5 -0
- package/dist/memory/working/canvas.js +43 -0
- package/dist/memory/working/offload.d.ts +71 -0
- package/dist/memory/working/offload.js +211 -0
- package/dist/memory/working/step-log.d.ts +16 -0
- package/dist/memory/working/step-log.js +35 -0
- package/dist/registry.d.ts +34 -0
- package/dist/registry.js +305 -0
- package/dist/resolver.d.ts +17 -0
- package/dist/resolver.js +126 -0
- package/dist/scripts/validate-foreign-workspace-path.d.ts +1 -0
- package/dist/scripts/validate-foreign-workspace-path.js +39 -0
- package/dist/tools/agent_memory_tools.d.ts +485 -0
- package/dist/tools/agent_memory_tools.js +793 -0
- package/dist/tools/create_skill.d.ts +46 -0
- package/dist/tools/create_skill.js +46 -0
- package/dist/tools/get_doc.d.ts +21 -0
- package/dist/tools/get_doc.js +24 -0
- package/dist/tools/get_persona.d.ts +15 -0
- package/dist/tools/get_persona.js +20 -0
- package/dist/tools/get_reference.d.ts +15 -0
- package/dist/tools/get_reference.js +20 -0
- package/dist/tools/get_skill.d.ts +34 -0
- package/dist/tools/get_skill.js +65 -0
- package/dist/tools/get_template_doc.d.ts +21 -0
- package/dist/tools/get_template_doc.js +24 -0
- package/dist/tools/list_docs.d.ts +15 -0
- package/dist/tools/list_docs.js +16 -0
- package/dist/tools/list_skills.d.ts +18 -0
- package/dist/tools/list_skills.js +17 -0
- package/dist/tools/list_template_docs.d.ts +15 -0
- package/dist/tools/list_template_docs.js +16 -0
- package/dist/tools/memory-engineering.d.ts +225 -0
- package/dist/tools/memory-engineering.js +284 -0
- package/dist/tools/memory-explain.d.ts +34 -0
- package/dist/tools/memory-explain.js +109 -0
- package/dist/tools/memory-governance.d.ts +171 -0
- package/dist/tools/memory-governance.js +224 -0
- package/dist/tools/memory-hooks.d.ts +67 -0
- package/dist/tools/memory-hooks.js +102 -0
- package/dist/tools/memory-working.d.ts +98 -0
- package/dist/tools/memory-working.js +101 -0
- package/dist/tools/memory_capture_turn.d.ts +66 -0
- package/dist/tools/memory_capture_turn.js +85 -0
- package/dist/tools/memory_consolidate.d.ts +55 -0
- package/dist/tools/memory_consolidate.js +176 -0
- package/dist/tools/memory_contradictions.d.ts +53 -0
- package/dist/tools/memory_contradictions.js +52 -0
- package/dist/tools/memory_graph_query.d.ts +51 -0
- package/dist/tools/memory_graph_query.js +35 -0
- package/dist/tools/memory_mark_cited.d.ts +43 -0
- package/dist/tools/memory_mark_cited.js +63 -0
- package/dist/tools/memory_recall.d.ts +77 -0
- package/dist/tools/memory_recall.js +81 -0
- package/dist/tools/memory_register_skill_hints.d.ts +49 -0
- package/dist/tools/memory_register_skill_hints.js +55 -0
- package/dist/tools/memory_resolve_session.d.ts +24 -0
- package/dist/tools/memory_resolve_session.js +133 -0
- package/dist/tools/memory_search.d.ts +146 -0
- package/dist/tools/memory_search.js +84 -0
- package/dist/tools/search_skills.d.ts +18 -0
- package/dist/tools/search_skills.js +17 -0
- package/dist/tools/update_doc.d.ts +24 -0
- package/dist/tools/update_doc.js +35 -0
- package/dist/tools/update_skill.d.ts +30 -0
- package/dist/tools/update_skill.js +80 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.js +4 -0
- package/dist/writer.d.ts +30 -0
- package/dist/writer.js +220 -0
- package/docs/TEMPLATE ONLY +1 -0
- package/docs/api/API.md +64 -0
- package/docs/api/security/SECURITY.md +58 -0
- package/docs/deployment/DockerDeployment.md +30 -0
- package/docs/design/Design.md +59 -0
- package/docs/design/themes/apple.md +101 -0
- package/docs/design/themes/dieter-grid.md +100 -0
- package/docs/design/themes/gallery-white.md +100 -0
- package/docs/design/themes/pinterest.md +101 -0
- package/docs/design/themes/realty-open-house.md +101 -0
- package/docs/design/themes/vodafone.md +101 -0
- package/docs/hooks/Hooks.md +30 -0
- package/docs/schema/Schema.md +35 -0
- package/docs/strategy/ScalingStrategy.md +19 -0
- package/package.json +88 -0
- package/references/accessibility-checklist.md +160 -0
- package/references/orchestration-patterns.md +370 -0
- package/references/performance-checklist.md +153 -0
- package/references/security-checklist.md +134 -0
- package/references/testing-patterns.md +236 -0
- package/skills/agent/adr-skill/SKILL.md +299 -0
- package/skills/agent/agentic-engineering-workflow/SKILL.md +95 -0
- package/skills/agent/bootstrap-skill/SKILL.md +103 -0
- package/skills/agent/context-engineering/SKILL.md +307 -0
- package/skills/agent/debugging-and-error-recovery/SKILL.md +308 -0
- package/skills/agent/developer-growth-analysis/SKILL.md +328 -0
- package/skills/agent/doubt-driven-skill/SKILL.md +249 -0
- package/skills/agent/handover-skill/SKILL.md +112 -0
- package/skills/agent/idea-refine-skill/SKILL.md +185 -0
- package/skills/agent/idea-refine-skill/examples.md +238 -0
- package/skills/agent/idea-refine-skill/frameworks.md +99 -0
- package/skills/agent/idea-refine-skill/refinement-criteria.md +113 -0
- package/skills/agent/interview-skill/SKILL.md +226 -0
- package/skills/agent/planning-skill/SKILL.md +270 -0
- package/skills/agent/skill-authoring/SKILL.md +189 -0
- package/skills/agent/source-driven-skill/SKILL.md +197 -0
- package/skills/agent/spec-driven-skill/SKILL.md +221 -0
- package/skills/agent/sync-skill/SKILL.md +92 -0
- package/skills/agent/using-agent-skills/SKILL.md +189 -0
- package/skills/api/a11y-skill/SKILL.md +88 -0
- package/skills/api/api-skill/SKILL.md +123 -0
- package/skills/api/auth-skill/SKILL.md +80 -0
- package/skills/api/debug-skill/SKILL.md +535 -0
- package/skills/api/performance-skill/SKILL.md +100 -0
- package/skills/api/testing-skill/SKILL.md +100 -0
- package/skills/codebase/code-review-and-quality/SKILL.md +228 -0
- package/skills/codebase/code-simplification/SKILL.md +352 -0
- package/skills/codebase/code-structure-cleanup/SKILL.md +142 -0
- package/skills/codebase/concerns-skill/SKILL.md +89 -0
- package/skills/codebase/conventions-skill/SKILL.md +95 -0
- package/skills/codebase/doc-management-skill/SKILL.md +47 -0
- package/skills/codebase/git-workflow-skill/SKILL.md +312 -0
- package/skills/communication/1-3-1-rule/SKILL.md +120 -0
- package/skills/design/brutalist-skill/SKILL.md +131 -0
- package/skills/design/concept-diagrams/SKILL.md +387 -0
- package/skills/design/concept-diagrams/examples/apartment-floor-plan-conversion.md +244 -0
- package/skills/design/concept-diagrams/examples/automated-password-reset-flow.md +276 -0
- package/skills/design/concept-diagrams/examples/autonomous-llm-research-agent-flow.md +240 -0
- package/skills/design/concept-diagrams/examples/banana-journey-tree-to-smoothie.md +161 -0
- package/skills/design/concept-diagrams/examples/commercial-aircraft-structure.md +209 -0
- package/skills/design/concept-diagrams/examples/cpu-ooo-microarchitecture.md +236 -0
- package/skills/design/concept-diagrams/examples/electricity-grid-flow.md +182 -0
- package/skills/design/concept-diagrams/examples/feature-film-production-pipeline.md +172 -0
- package/skills/design/concept-diagrams/examples/hospital-emergency-department-flow.md +165 -0
- package/skills/design/concept-diagrams/examples/ml-benchmark-grouped-bar-chart.md +114 -0
- package/skills/design/concept-diagrams/examples/place-order-uml-sequence.md +325 -0
- package/skills/design/concept-diagrams/examples/smart-city-infrastructure.md +173 -0
- package/skills/design/concept-diagrams/examples/smartphone-layer-anatomy.md +154 -0
- package/skills/design/concept-diagrams/examples/sn2-reaction-mechanism.md +247 -0
- package/skills/design/concept-diagrams/examples/wind-turbine-structure.md +338 -0
- package/skills/design/concept-diagrams/references/dashboard-patterns.md +43 -0
- package/skills/design/concept-diagrams/references/infrastructure-patterns.md +144 -0
- package/skills/design/concept-diagrams/references/physical-shape-cookbook.md +42 -0
- package/skills/design/concept-diagrams/templates/template.html +174 -0
- package/skills/design/gpt-tasteskill/SKILL.md +114 -0
- package/skills/design/minimalist-skill/SKILL.md +116 -0
- package/skills/design/output-skill/SKILL.md +87 -0
- package/skills/design/redesign-skill/SKILL.md +213 -0
- package/skills/design/soft-skill/SKILL.md +132 -0
- package/skills/design/stitch-skill/EXAMPLE.md +121 -0
- package/skills/design/stitch-skill/SKILL.md +222 -0
- package/skills/design/taste-skill/SKILL.md +269 -0
- package/skills/devops/ci-cd-skill/SKILL.md +402 -0
- package/skills/devops/docker-skill/SKILL.md +297 -0
- package/skills/devops/domain-skill/SKILL.md +234 -0
- package/skills/lifecycle/changelog-generator/SKILL.md +135 -0
- package/skills/lifecycle/incremental-skill/SKILL.md +257 -0
- package/skills/lifecycle/migration-skill/SKILL.md +218 -0
- package/skills/lifecycle/shipping-skill/SKILL.md +321 -0
- package/skills/memory/agent-memory/SKILL.md +122 -0
- package/skills/qa/browser-testing-skill/SKILL.md +314 -0
- package/skills/ux/adversarial-ux-skill/SKILL.md +168 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FOCUS_DIRECTION_SHIFT_SYSTEM_PROMPT, formatFocusDirectionShiftPrompt } from "../prompts/focus-direction-shift.js";
|
|
2
|
+
export async function detectFocusShift(params) {
|
|
3
|
+
const { activeScene, newCognitiveRecords, llmRunner } = params;
|
|
4
|
+
try {
|
|
5
|
+
const prompt = formatFocusDirectionShiftPrompt(activeScene.sceneName, activeScene.summaryMd, newCognitiveRecords.map(r => ({ content: r.content, type: r.type })));
|
|
6
|
+
const response = await llmRunner.run({
|
|
7
|
+
prompt,
|
|
8
|
+
systemPrompt: FOCUS_DIRECTION_SHIFT_SYSTEM_PROMPT,
|
|
9
|
+
taskId: "focus-direction-shift",
|
|
10
|
+
timeoutMs: 30_000,
|
|
11
|
+
});
|
|
12
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
13
|
+
if (!jsonMatch) {
|
|
14
|
+
throw new Error("No JSON object found in LLM response");
|
|
15
|
+
}
|
|
16
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
17
|
+
return {
|
|
18
|
+
shift: Boolean(parsed.shift),
|
|
19
|
+
confidence: Number(parsed.confidence) || 0,
|
|
20
|
+
reason: String(parsed.reason) || "",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.error(`[BrainRouter] Focus direction shift detection failed:`, err.message);
|
|
25
|
+
return { shift: false, confidence: 0, reason: "Error" };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IMemoryStore } from "@kinqs/brainrouter-types";
|
|
2
|
+
import type { LLMRunner, CognitiveRecord } from "@kinqs/brainrouter-types";
|
|
3
|
+
/**
|
|
4
|
+
* Graph Construction Pipeline
|
|
5
|
+
* Extracts entities and relationships from Cognitive records to update the Knowledge Graph.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildGraphFromCognitive(params: {
|
|
8
|
+
record: CognitiveRecord;
|
|
9
|
+
store: IMemoryStore;
|
|
10
|
+
llmRunner: LLMRunner;
|
|
11
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { GRAPH_EXTRACTION_SYSTEM_PROMPT, formatGraphExtractionPrompt } from "../prompts/graph-extraction.js";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* Graph Construction Pipeline
|
|
5
|
+
* Extracts entities and relationships from Cognitive records to update the Knowledge Graph.
|
|
6
|
+
*/
|
|
7
|
+
export async function buildGraphFromCognitive(params) {
|
|
8
|
+
const { record, store, llmRunner } = params;
|
|
9
|
+
// Gate execution with env flag (default is true)
|
|
10
|
+
if (process.env.BRAINROUTER_GRAPH_ENABLED === "false") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// Skip graph extraction if memory is invalidated
|
|
14
|
+
if (record.invalidAt)
|
|
15
|
+
return;
|
|
16
|
+
const _parsedTimeout = parseInt(process.env.BRAINROUTER_GRAPH_TIMEOUT_MS || "", 10);
|
|
17
|
+
const timeoutMs = isNaN(_parsedTimeout) ? 120000 : _parsedTimeout;
|
|
18
|
+
try {
|
|
19
|
+
const rawExtraction = await llmRunner.run({
|
|
20
|
+
prompt: formatGraphExtractionPrompt(record.content),
|
|
21
|
+
systemPrompt: GRAPH_EXTRACTION_SYSTEM_PROMPT,
|
|
22
|
+
taskId: `graph-extraction-${record.id}`,
|
|
23
|
+
timeoutMs
|
|
24
|
+
});
|
|
25
|
+
const jsonMatch = rawExtraction.match(/\{[\s\S]*\}/);
|
|
26
|
+
if (!jsonMatch)
|
|
27
|
+
return;
|
|
28
|
+
const data = JSON.parse(jsonMatch[0]);
|
|
29
|
+
if (!data || !Array.isArray(data.entities))
|
|
30
|
+
return;
|
|
31
|
+
const entityMap = new Map(); // name -> node.id
|
|
32
|
+
// 1. Process and upsert nodes
|
|
33
|
+
for (const ent of data.entities) {
|
|
34
|
+
const entityName = String(ent.entity || "").trim();
|
|
35
|
+
const entityType = String(ent.type || "concept").trim();
|
|
36
|
+
const confidence = typeof ent.confidence === "number" ? ent.confidence : 1.0;
|
|
37
|
+
if (!entityName)
|
|
38
|
+
continue;
|
|
39
|
+
// Check if node already exists to get its ID, otherwise create a new one
|
|
40
|
+
const existingNode = store.getGraphNodeByEntity(record.userId, entityName);
|
|
41
|
+
const nodeId = existingNode?.id ?? `gn_${crypto.randomBytes(6).toString("hex")}`;
|
|
42
|
+
entityMap.set(entityName.toLowerCase(), nodeId);
|
|
43
|
+
const node = {
|
|
44
|
+
id: nodeId,
|
|
45
|
+
userId: record.userId,
|
|
46
|
+
entity: entityName,
|
|
47
|
+
entityType,
|
|
48
|
+
skillTag: record.skillTag || "",
|
|
49
|
+
confidence,
|
|
50
|
+
sourceRecordId: record.id,
|
|
51
|
+
createdTime: record.createdTime || new Date().toISOString()
|
|
52
|
+
};
|
|
53
|
+
store.upsertGraphNode(node);
|
|
54
|
+
}
|
|
55
|
+
// 2. Process and upsert edges
|
|
56
|
+
if (Array.isArray(data.relations)) {
|
|
57
|
+
for (const rel of data.relations) {
|
|
58
|
+
const fromName = String(rel.from || "").trim();
|
|
59
|
+
const toName = String(rel.to || "").trim();
|
|
60
|
+
const relation = String(rel.relation || "relates_to").trim();
|
|
61
|
+
const confidence = typeof rel.confidence === "number" ? rel.confidence : 1.0;
|
|
62
|
+
if (!fromName || !toName)
|
|
63
|
+
continue;
|
|
64
|
+
const fromNodeId = entityMap.get(fromName.toLowerCase());
|
|
65
|
+
const toNodeId = entityMap.get(toName.toLowerCase());
|
|
66
|
+
if (!fromNodeId || !toNodeId) {
|
|
67
|
+
// Skip edges where one endpoint wasn't extracted (LLM mentioned it in relations but not in entities — common with the 5-entity cap)
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const edge = {
|
|
71
|
+
id: `ge_${crypto.randomBytes(6).toString("hex")}`,
|
|
72
|
+
userId: record.userId,
|
|
73
|
+
fromNodeId,
|
|
74
|
+
toNodeId,
|
|
75
|
+
relation,
|
|
76
|
+
skillTag: record.skillTag || "",
|
|
77
|
+
confidence,
|
|
78
|
+
sourceRecordId: record.id,
|
|
79
|
+
createdTime: record.createdTime || new Date().toISOString()
|
|
80
|
+
};
|
|
81
|
+
store.upsertGraphEdge(edge);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error(`[BrainRouter] Graph extraction failed for record ${record.id}:`, err.message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { IMemoryStore } from "@kinqs/brainrouter-types";
|
|
2
|
+
/**
|
|
3
|
+
* Hybrid GraphRAG Recall Expansion
|
|
4
|
+
* Finds matching entities in the query and top Cognitive results, performs a 2-hop
|
|
5
|
+
* BFS traversal, and returns a formatted markdown block of the context.
|
|
6
|
+
*/
|
|
7
|
+
export declare function expandRecallWithGraph(params: {
|
|
8
|
+
topCognitiveResults: any[];
|
|
9
|
+
query: string;
|
|
10
|
+
userId: string;
|
|
11
|
+
activeSkill?: string;
|
|
12
|
+
store: IMemoryStore;
|
|
13
|
+
}): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid GraphRAG Recall Expansion
|
|
3
|
+
* Finds matching entities in the query and top Cognitive results, performs a 2-hop
|
|
4
|
+
* BFS traversal, and returns a formatted markdown block of the context.
|
|
5
|
+
*/
|
|
6
|
+
export function expandRecallWithGraph(params) {
|
|
7
|
+
const { topCognitiveResults, query, userId, activeSkill, store } = params;
|
|
8
|
+
try {
|
|
9
|
+
// 1. Fetch all graph nodes for this user to match entities
|
|
10
|
+
const allNodes = store.getAllGraphNodes(userId);
|
|
11
|
+
if (allNodes.length === 0)
|
|
12
|
+
return "";
|
|
13
|
+
const combinedText = `${query} ${topCognitiveResults.map(r => r.content || "").join(" ")}`.toLowerCase();
|
|
14
|
+
// 2. Find which nodes are mentioned in the query or top results
|
|
15
|
+
const matchingNodeIds = new Set();
|
|
16
|
+
for (const node of allNodes) {
|
|
17
|
+
if (combinedText.includes(node.entity.toLowerCase())) {
|
|
18
|
+
matchingNodeIds.add(node.id);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (matchingNodeIds.size === 0)
|
|
22
|
+
return "";
|
|
23
|
+
// 3. For each matching node, fetch neighbors up to 2 hops
|
|
24
|
+
const unionNodes = new Map();
|
|
25
|
+
const unionEdges = new Map();
|
|
26
|
+
for (const nodeId of matchingNodeIds) {
|
|
27
|
+
const { nodes, edges } = store.getGraphNeighbors(userId, nodeId, activeSkill, 2);
|
|
28
|
+
for (const n of nodes)
|
|
29
|
+
unionNodes.set(n.id, n);
|
|
30
|
+
for (const e of edges)
|
|
31
|
+
unionEdges.set(e.id, e);
|
|
32
|
+
}
|
|
33
|
+
if (unionNodes.size === 0)
|
|
34
|
+
return "";
|
|
35
|
+
// 4. Format into a beautiful Knowledge Graph Context
|
|
36
|
+
const nodeLines = Array.from(unionNodes.values()).map(n => `- **${n.entity}** (${n.entityType})`);
|
|
37
|
+
const edgeLines = Array.from(unionEdges.values()).map(e => {
|
|
38
|
+
const fromNode = unionNodes.get(e.fromNodeId);
|
|
39
|
+
const toNode = unionNodes.get(e.toNodeId);
|
|
40
|
+
if (!fromNode || !toNode)
|
|
41
|
+
return null;
|
|
42
|
+
return `- **${fromNode.entity}** --[${e.relation}]--> **${toNode.entity}** (confidence: ${e.confidence.toFixed(2)}${e.skillTag ? `, skill: ${e.skillTag}` : ""})`;
|
|
43
|
+
}).filter(Boolean);
|
|
44
|
+
let output = "\n==================================================\n";
|
|
45
|
+
output += "🕸️ KNOWLEDGE GRAPH CONTEXT (GraphRAG)\n";
|
|
46
|
+
output += "==================================================\n\n";
|
|
47
|
+
output += "### Graph Entities:\n" + nodeLines.join("\n") + "\n\n";
|
|
48
|
+
output += "### Graph Relationships:\n" + edgeLines.join("\n") + "\n";
|
|
49
|
+
return output;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error("[BrainRouter] Graph recall expansion failed:", err.message);
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IMemoryStore } from "@kinqs/brainrouter-types";
|
|
2
|
+
import type { LLMRunner } from "@kinqs/brainrouter-types";
|
|
3
|
+
/**
|
|
4
|
+
* Core Identity Distillation Pipeline
|
|
5
|
+
* Scans ALL persona + instruction CognitiveRecords across all sessions
|
|
6
|
+
* for a user and synthesizes a durable Narrative Profile.
|
|
7
|
+
*/
|
|
8
|
+
export declare function distillCoreIdentity(params: {
|
|
9
|
+
userId: string;
|
|
10
|
+
store: IMemoryStore;
|
|
11
|
+
llmRunner: LLMRunner;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
personaMd?: string;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { CORE_IDENTITY_SYSTEM_PROMPT, formatCoreIdentityPrompt } from "../prompts/core-identity.js";
|
|
2
|
+
/**
|
|
3
|
+
* Core Identity Distillation Pipeline
|
|
4
|
+
* Scans ALL persona + instruction CognitiveRecords across all sessions
|
|
5
|
+
* for a user and synthesizes a durable Narrative Profile.
|
|
6
|
+
*/
|
|
7
|
+
export async function distillCoreIdentity(params) {
|
|
8
|
+
const { userId, store, llmRunner } = params;
|
|
9
|
+
// Cross-session: fetch all persona + instruction cognitives for this user
|
|
10
|
+
const memories = store.getIdentityAndInstructionCognitives(userId, 100);
|
|
11
|
+
if (memories.length === 0) {
|
|
12
|
+
console.error(`[BrainRouter] Core Identity distillation skipped for "${userId}" — no persona/instruction memories yet.`);
|
|
13
|
+
return { success: false };
|
|
14
|
+
}
|
|
15
|
+
let personaMd;
|
|
16
|
+
try {
|
|
17
|
+
personaMd = await llmRunner.run({
|
|
18
|
+
prompt: formatCoreIdentityPrompt(memories),
|
|
19
|
+
systemPrompt: CORE_IDENTITY_SYSTEM_PROMPT,
|
|
20
|
+
taskId: "identity-distillation",
|
|
21
|
+
timeoutMs: 90_000,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error(`[BrainRouter] Core Identity distillation failed for "${userId}":`, err.message);
|
|
26
|
+
return { success: false };
|
|
27
|
+
}
|
|
28
|
+
const now = new Date().toISOString();
|
|
29
|
+
const existing = store.getCoreIdentity(userId);
|
|
30
|
+
const record = {
|
|
31
|
+
userId,
|
|
32
|
+
personaMd: personaMd.trim(),
|
|
33
|
+
cognitiveCountAtGeneration: memories.length,
|
|
34
|
+
createdTime: existing?.createdTime ?? now,
|
|
35
|
+
updatedTime: now,
|
|
36
|
+
};
|
|
37
|
+
store.upsertCoreIdentity(record);
|
|
38
|
+
console.error(`[BrainRouter] Core Identity updated for "${userId}" (${memories.length} cognitive records).`);
|
|
39
|
+
return { success: true, personaMd: personaMd.trim() };
|
|
40
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IMemoryStore } from "@brainrouter/types";
|
|
2
|
+
import type { LLMRunner, L1Record } from "@brainrouter/types";
|
|
3
|
+
export declare function detectContradictions(params: {
|
|
4
|
+
newRecord: L1Record;
|
|
5
|
+
store: IMemoryStore;
|
|
6
|
+
llmRunner: LLMRunner;
|
|
7
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { L1_CONTRADICTION_PROMPT } from "../prompts/l1-contradiction.js";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
export async function detectContradictions(params) {
|
|
4
|
+
const { newRecord, store, llmRunner } = params;
|
|
5
|
+
// 1. Search for potentially related memories
|
|
6
|
+
// We use keyword search on the content of the new record to find similar existing ones
|
|
7
|
+
const candidates = store.searchL1Fts(newRecord.userId, newRecord.content, 5);
|
|
8
|
+
const evaluations = [];
|
|
9
|
+
const _parsedContradictionTimeout = parseInt(process.env.BRAINROUTER_CONTRADICTION_TIMEOUT_MS || "", 10);
|
|
10
|
+
const contradictionTimeoutMs = isNaN(_parsedContradictionTimeout) ? 60000 : _parsedContradictionTimeout;
|
|
11
|
+
for (const candidate of candidates) {
|
|
12
|
+
// Don't compare with self
|
|
13
|
+
if (candidate.record_id === newRecord.id)
|
|
14
|
+
continue;
|
|
15
|
+
// Only compare if they are of the same type or both are episodic/persona
|
|
16
|
+
// (instructions don't usually contradict episodic facts)
|
|
17
|
+
const prompt = L1_CONTRADICTION_PROMPT
|
|
18
|
+
.replace("{{newContent}}", newRecord.content)
|
|
19
|
+
.replace("{{existingContent}}", candidate.content);
|
|
20
|
+
try {
|
|
21
|
+
const response = await llmRunner.run({
|
|
22
|
+
prompt,
|
|
23
|
+
taskId: `contradiction-check-${newRecord.id}-${candidate.record_id}`,
|
|
24
|
+
timeoutMs: contradictionTimeoutMs
|
|
25
|
+
});
|
|
26
|
+
// Simple JSON extraction (flexible for local models)
|
|
27
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
28
|
+
if (!jsonMatch)
|
|
29
|
+
continue;
|
|
30
|
+
const data = JSON.parse(jsonMatch[0]);
|
|
31
|
+
if (data.isContradiction && data.confidence > 0.7) {
|
|
32
|
+
evaluations.push({
|
|
33
|
+
candidate,
|
|
34
|
+
isContradiction: true,
|
|
35
|
+
confidence: data.confidence,
|
|
36
|
+
kind: data.kind || "genuine_conflict",
|
|
37
|
+
reason: data.reason
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
console.error(`[BrainRouter] Contradiction check failed for ${newRecord.id} vs ${candidate.record_id}:`, e.message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// If ANY evaluation is a temporal_update, then the entire batch of contradictions represents a temporal transition!
|
|
46
|
+
const hasTemporalUpdate = evaluations.some(ev => ev.kind === "temporal_update");
|
|
47
|
+
for (const ev of evaluations) {
|
|
48
|
+
if (hasTemporalUpdate) {
|
|
49
|
+
// Treat all conflicting old records as superseded by the new record
|
|
50
|
+
console.error(`[BrainRouter] TEMPORAL UPDATE DETECTED (transition): Superseding memory ${ev.candidate.record_id} with new memory ${newRecord.id}`);
|
|
51
|
+
store.invalidateL1Record(newRecord.userId, ev.candidate.record_id, newRecord.id);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Genuine conflict
|
|
55
|
+
console.error(`[BrainRouter] CONTRADICTION DETECTED: ${newRecord.id} vs ${ev.candidate.record_id}`);
|
|
56
|
+
store.upsertContradiction({
|
|
57
|
+
id: `conflict_${crypto.randomBytes(4).toString("hex")}`,
|
|
58
|
+
userId: newRecord.userId,
|
|
59
|
+
recordIdA: ev.candidate.record_id,
|
|
60
|
+
recordIdB: newRecord.id,
|
|
61
|
+
reason: ev.reason,
|
|
62
|
+
confidence: ev.confidence
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { L1Record } from "@brainrouter/types";
|
|
2
|
+
import type { IMemoryStore } from "@brainrouter/types";
|
|
3
|
+
/**
|
|
4
|
+
* Result of the deduplication process
|
|
5
|
+
*/
|
|
6
|
+
export interface DedupResult {
|
|
7
|
+
/** Memories that are unique and should be stored */
|
|
8
|
+
uniqueRecords: L1Record[];
|
|
9
|
+
/** Memories that were identified as exact duplicates and dropped */
|
|
10
|
+
droppedCount: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Proactively deduplicate extracted memories against the existing memory store
|
|
14
|
+
* before they are stored.
|
|
15
|
+
*
|
|
16
|
+
* Uses exact/near-exact string matching to prevent identical noisy facts
|
|
17
|
+
* from accumulating in the L1 store.
|
|
18
|
+
*/
|
|
19
|
+
export declare function deduplicateMemories(params: {
|
|
20
|
+
records: L1Record[];
|
|
21
|
+
store: IMemoryStore;
|
|
22
|
+
userId: string;
|
|
23
|
+
}): Promise<DedupResult>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proactively deduplicate extracted memories against the existing memory store
|
|
3
|
+
* before they are stored.
|
|
4
|
+
*
|
|
5
|
+
* Uses exact/near-exact string matching to prevent identical noisy facts
|
|
6
|
+
* from accumulating in the L1 store.
|
|
7
|
+
*/
|
|
8
|
+
export async function deduplicateMemories(params) {
|
|
9
|
+
const { records, store, userId } = params;
|
|
10
|
+
if (records.length === 0) {
|
|
11
|
+
return { uniqueRecords: [], droppedCount: 0 };
|
|
12
|
+
}
|
|
13
|
+
const uniqueRecords = [];
|
|
14
|
+
let droppedCount = 0;
|
|
15
|
+
for (const newRecord of records) {
|
|
16
|
+
// 1. Keyword search to find potentially identical memories
|
|
17
|
+
// We only need top 3 to see if there is an exact match
|
|
18
|
+
const candidates = store.searchL1Fts(userId, newRecord.content, 3);
|
|
19
|
+
let isDuplicate = false;
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
// Direct string comparison (case-insensitive, trimmed)
|
|
22
|
+
if (candidate.content.trim().toLowerCase() === newRecord.content.trim().toLowerCase()) {
|
|
23
|
+
isDuplicate = true;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (isDuplicate) {
|
|
28
|
+
console.log(`[BrainRouter] Dropped exact duplicate memory: "${newRecord.content}"`);
|
|
29
|
+
droppedCount++;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
uniqueRecords.push(newRecord);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
uniqueRecords,
|
|
37
|
+
droppedCount
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { L0Record, L1Record, LLMRunner } from "@brainrouter/types";
|
|
2
|
+
export interface L1ExtractionResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
extractedCount: number;
|
|
5
|
+
records: L1Record[];
|
|
6
|
+
sceneNames: string[];
|
|
7
|
+
errorMessage?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function extractL1Memories(params: {
|
|
10
|
+
messages: L0Record[];
|
|
11
|
+
userId: string;
|
|
12
|
+
sessionKey: string;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
llmRunner: LLMRunner;
|
|
15
|
+
maxMessagesPerExtraction?: number;
|
|
16
|
+
maxBackgroundMessages?: number;
|
|
17
|
+
previousSceneName?: string;
|
|
18
|
+
existingSceneNames?: string[];
|
|
19
|
+
activeSkill?: string;
|
|
20
|
+
skillHints?: string;
|
|
21
|
+
}): Promise<L1ExtractionResult>;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { EXTRACT_MEMORIES_SYSTEM_PROMPT, formatExtractionPrompt } from "../prompts/l1-extraction.js";
|
|
2
|
+
import { getMemoryTypeConfig } from "../memory-type-config.js";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
const ALLOWED_MEMORY_TYPES = new Set([
|
|
5
|
+
"persona", "episodic", "instruction", "skill_context", "tool_preference",
|
|
6
|
+
"codebase_fact", "api_contract", "data_model", "dependency_constraint",
|
|
7
|
+
"environment_constraint", "architecture_decision", "implementation_decision",
|
|
8
|
+
"design_constraint", "security_policy", "performance_baseline", "bug_finding",
|
|
9
|
+
"debug_trace", "fix_summary", "verification_result", "failed_attempt",
|
|
10
|
+
"regression_risk", "task_state", "handover_note", "blocked_reason",
|
|
11
|
+
"review_comment", "release_note", "source_evidence", "artifact_reference",
|
|
12
|
+
"file_history", "command_knowledge",
|
|
13
|
+
]);
|
|
14
|
+
const ALLOWED_SOURCE_KINDS = new Set([
|
|
15
|
+
"", "user_instruction", "source_file", "command_output", "test_result",
|
|
16
|
+
"model_inference", "prior_memory",
|
|
17
|
+
]);
|
|
18
|
+
const ALLOWED_VERIFICATION_STATUSES = new Set([
|
|
19
|
+
"", "verified", "unverified", "stale",
|
|
20
|
+
]);
|
|
21
|
+
// Ensure the message has actual words to extract from, not just symbols or single letters.
|
|
22
|
+
function shouldExtractL1(text) {
|
|
23
|
+
if (!text)
|
|
24
|
+
return false;
|
|
25
|
+
const clean = text.trim();
|
|
26
|
+
if (clean.length < 3)
|
|
27
|
+
return false;
|
|
28
|
+
// If it's pure symbols/numbers, ignore
|
|
29
|
+
if (/^[^a-zA-Z\u4e00-\u9fa5]+$/.test(clean))
|
|
30
|
+
return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
export async function extractL1Memories(params) {
|
|
34
|
+
const { messages, userId, sessionKey, sessionId, llmRunner, maxMessagesPerExtraction = 10, maxBackgroundMessages = 5, previousSceneName, existingSceneNames, activeSkill, skillHints } = params;
|
|
35
|
+
if (messages.length === 0) {
|
|
36
|
+
return { success: true, extractedCount: 0, records: [], sceneNames: [] };
|
|
37
|
+
}
|
|
38
|
+
const qualifiedMessages = messages.filter((m) => shouldExtractL1(m.messageText));
|
|
39
|
+
if (qualifiedMessages.length === 0) {
|
|
40
|
+
return { success: true, extractedCount: 0, records: [], sceneNames: [] };
|
|
41
|
+
}
|
|
42
|
+
const newMessages = qualifiedMessages.slice(-maxMessagesPerExtraction);
|
|
43
|
+
const bgEndIdx = qualifiedMessages.length - newMessages.length;
|
|
44
|
+
const backgroundMessages = bgEndIdx > 0
|
|
45
|
+
? qualifiedMessages.slice(Math.max(0, bgEndIdx - maxBackgroundMessages), bgEndIdx)
|
|
46
|
+
: [];
|
|
47
|
+
const userPrompt = formatExtractionPrompt({
|
|
48
|
+
newMessages,
|
|
49
|
+
backgroundMessages,
|
|
50
|
+
previousSceneName,
|
|
51
|
+
existingSceneNames,
|
|
52
|
+
activeSkill,
|
|
53
|
+
skillHints
|
|
54
|
+
});
|
|
55
|
+
let rawResult;
|
|
56
|
+
try {
|
|
57
|
+
rawResult = await llmRunner.run({
|
|
58
|
+
prompt: userPrompt,
|
|
59
|
+
systemPrompt: EXTRACT_MEMORIES_SYSTEM_PROMPT,
|
|
60
|
+
taskId: "l1-extraction",
|
|
61
|
+
timeoutMs: 120_000
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
66
|
+
console.error("[BrainRouter] LLM extraction failed:", err);
|
|
67
|
+
return { success: false, extractedCount: 0, records: [], sceneNames: [], errorMessage };
|
|
68
|
+
}
|
|
69
|
+
const parsedScenes = parseExtractionResult(rawResult);
|
|
70
|
+
const records = [];
|
|
71
|
+
const sceneNames = [];
|
|
72
|
+
const nowStr = new Date().toISOString();
|
|
73
|
+
for (const scene of parsedScenes) {
|
|
74
|
+
sceneNames.push(scene.scene_name);
|
|
75
|
+
for (const mem of scene.memories) {
|
|
76
|
+
const config = getMemoryTypeConfig(mem.type);
|
|
77
|
+
records.push({
|
|
78
|
+
id: `l1_${sessionKey}_${Date.now()}_${crypto.randomBytes(4).toString("hex")}`,
|
|
79
|
+
userId,
|
|
80
|
+
sessionKey,
|
|
81
|
+
sessionId,
|
|
82
|
+
content: mem.content,
|
|
83
|
+
type: mem.type,
|
|
84
|
+
priority: mem.priority,
|
|
85
|
+
sceneName: scene.scene_name,
|
|
86
|
+
skillTag: mem.skill_tag || activeSkill || "",
|
|
87
|
+
halfLifeDays: config.halfLifeDays,
|
|
88
|
+
supersededBy: null,
|
|
89
|
+
timestampStr: "", // Phase 1: Not strictly tracking time from LLM, just raw extraction
|
|
90
|
+
timestampStart: "",
|
|
91
|
+
timestampEnd: "",
|
|
92
|
+
createdTime: nowStr,
|
|
93
|
+
updatedTime: nowStr,
|
|
94
|
+
metadata: mem.metadata,
|
|
95
|
+
confidence: mem.confidence ?? config.defaultConfidence,
|
|
96
|
+
status: "active",
|
|
97
|
+
sourceKind: mem.sourceKind,
|
|
98
|
+
verificationStatus: mem.verificationStatus,
|
|
99
|
+
repoPaths: mem.repoPaths,
|
|
100
|
+
filePaths: mem.filePaths,
|
|
101
|
+
commands: mem.commands,
|
|
102
|
+
// ACE fields — zero on creation, updated by citation tracking
|
|
103
|
+
citationCount: 0,
|
|
104
|
+
lastCitedAt: null,
|
|
105
|
+
neverCitedCount: 0,
|
|
106
|
+
archived: false,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
extractedCount: records.length,
|
|
113
|
+
records,
|
|
114
|
+
sceneNames
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function parseExtractionResult(raw) {
|
|
118
|
+
try {
|
|
119
|
+
let cleaned = raw.trim();
|
|
120
|
+
if (cleaned.startsWith("\`\`\`")) {
|
|
121
|
+
cleaned = cleaned.replace(/^\`\`\`(?:json)?\s*\n?/, "").replace(/\n?\`\`\`\s*$/, "");
|
|
122
|
+
}
|
|
123
|
+
const match = cleaned.match(/\[[\s\S]*\]/);
|
|
124
|
+
if (!match)
|
|
125
|
+
return [];
|
|
126
|
+
const parsed = JSON.parse(match[0]);
|
|
127
|
+
if (!Array.isArray(parsed))
|
|
128
|
+
return [];
|
|
129
|
+
const scenes = [];
|
|
130
|
+
for (const item of parsed) {
|
|
131
|
+
if (!item || typeof item !== "object")
|
|
132
|
+
continue;
|
|
133
|
+
const s = item;
|
|
134
|
+
const memories = Array.isArray(s.memories) ? s.memories.map((m) => ({
|
|
135
|
+
content: String(m.content || ""),
|
|
136
|
+
type: parseMemoryType(m.type),
|
|
137
|
+
priority: clampNumber(m.priority, 0, 100, 50),
|
|
138
|
+
skill_tag: m.skill_tag ? String(m.skill_tag) : undefined,
|
|
139
|
+
confidence: typeof m.confidence === "number" ? clampNumber(m.confidence, 0, 1, 0.65) : undefined,
|
|
140
|
+
sourceKind: parseSourceKind(m.sourceKind ?? m.source_kind),
|
|
141
|
+
verificationStatus: parseVerificationStatus(m.verificationStatus ?? m.verification_status),
|
|
142
|
+
repoPaths: parseStringArray(m.repoPaths ?? m.repo_paths),
|
|
143
|
+
filePaths: parseStringArray(m.filePaths ?? m.file_paths),
|
|
144
|
+
commands: parseStringArray(m.commands),
|
|
145
|
+
metadata: m.metadata && typeof m.metadata === "object" ? m.metadata : {}
|
|
146
|
+
})).filter((m) => m.content.length > 0) : [];
|
|
147
|
+
scenes.push({
|
|
148
|
+
scene_name: String(s.scene_name || "Unknown Scene"),
|
|
149
|
+
memories
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return scenes;
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
console.error("[BrainRouter] Failed to parse extraction result", err);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function parseMemoryType(value) {
|
|
160
|
+
const candidate = String(value || "");
|
|
161
|
+
return ALLOWED_MEMORY_TYPES.has(candidate) ? candidate : "episodic";
|
|
162
|
+
}
|
|
163
|
+
function parseSourceKind(value) {
|
|
164
|
+
const candidate = String(value || "");
|
|
165
|
+
return ALLOWED_SOURCE_KINDS.has(candidate) ? candidate : "model_inference";
|
|
166
|
+
}
|
|
167
|
+
function parseVerificationStatus(value) {
|
|
168
|
+
const candidate = String(value || "");
|
|
169
|
+
return ALLOWED_VERIFICATION_STATUSES.has(candidate) ? candidate : "unverified";
|
|
170
|
+
}
|
|
171
|
+
function parseStringArray(value) {
|
|
172
|
+
if (!Array.isArray(value))
|
|
173
|
+
return [];
|
|
174
|
+
return [...new Set(value.map((item) => String(item).trim()).filter(Boolean))];
|
|
175
|
+
}
|
|
176
|
+
function clampNumber(value, min, max, fallback) {
|
|
177
|
+
return typeof value === "number" && Number.isFinite(value)
|
|
178
|
+
? Math.min(max, Math.max(min, value))
|
|
179
|
+
: fallback;
|
|
180
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { L1Record, L2SceneRecord, LLMRunner } from "@brainrouter/types";
|
|
2
|
+
export declare function detectDirectionShift(params: {
|
|
3
|
+
activeScene: L2SceneRecord;
|
|
4
|
+
newL1Records: L1Record[];
|
|
5
|
+
llmRunner: LLMRunner;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
shift: boolean;
|
|
8
|
+
confidence: number;
|
|
9
|
+
reason: string;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { L2_DIRECTION_SHIFT_SYSTEM_PROMPT, formatL2DirectionShiftPrompt } from "../prompts/l2-direction-shift.js";
|
|
2
|
+
export async function detectDirectionShift(params) {
|
|
3
|
+
const { activeScene, newL1Records, llmRunner } = params;
|
|
4
|
+
try {
|
|
5
|
+
const prompt = formatL2DirectionShiftPrompt(activeScene.sceneName, activeScene.summaryMd, newL1Records.map(r => ({ content: r.content, type: r.type })));
|
|
6
|
+
const response = await llmRunner.run({
|
|
7
|
+
prompt,
|
|
8
|
+
systemPrompt: L2_DIRECTION_SHIFT_SYSTEM_PROMPT,
|
|
9
|
+
taskId: "l2-direction-shift",
|
|
10
|
+
timeoutMs: 30_000,
|
|
11
|
+
});
|
|
12
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
13
|
+
if (!jsonMatch) {
|
|
14
|
+
throw new Error("No JSON object found in LLM response");
|
|
15
|
+
}
|
|
16
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
17
|
+
return {
|
|
18
|
+
shift: Boolean(parsed.shift),
|
|
19
|
+
confidence: Number(parsed.confidence) || 0,
|
|
20
|
+
reason: String(parsed.reason) || "",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
console.error(`[BrainRouter] L2 direction shift detection failed:`, err.message);
|
|
25
|
+
return { shift: false, confidence: 0, reason: "Error" };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IMemoryStore } from "@brainrouter/types";
|
|
2
|
+
import type { LLMRunner } from "@brainrouter/types";
|
|
3
|
+
/**
|
|
4
|
+
* L2 Scene Pipeline
|
|
5
|
+
* Groups L1 memories by scene_name and asks the LLM to produce
|
|
6
|
+
* a Markdown summary for each scene. Updates heat scores.
|
|
7
|
+
*/
|
|
8
|
+
export declare function distillScenes(params: {
|
|
9
|
+
userId: string;
|
|
10
|
+
store: IMemoryStore;
|
|
11
|
+
llmRunner: LLMRunner;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
scenesDistilled: number;
|
|
14
|
+
sceneNames: string[];
|
|
15
|
+
}>;
|