@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,118 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { redactSensitiveMemoryText } from "../memory/redaction.js";
|
|
3
|
+
import { resetWorkingMemory } from "../memory/working/offload.js";
|
|
4
|
+
const registeredHooks = new Map();
|
|
5
|
+
const SECRET_KEY_PATTERN = /(api[_-]?key|authorization|bearer|password|secret|token)/i;
|
|
6
|
+
const MAX_STRING_LENGTH = 4_000;
|
|
7
|
+
const MAX_ARRAY_ITEMS = 25;
|
|
8
|
+
const MAX_OBJECT_KEYS = 50;
|
|
9
|
+
const SESSION_END_EVENTS = new Set(["Stop", "SubagentStop", "session_end"]);
|
|
10
|
+
function truncate(value) {
|
|
11
|
+
return value.length > MAX_STRING_LENGTH
|
|
12
|
+
? `${value.slice(0, MAX_STRING_LENGTH)}...[truncated]`
|
|
13
|
+
: value;
|
|
14
|
+
}
|
|
15
|
+
export function sanitizeHookPayload(value, depth = 0) {
|
|
16
|
+
if (depth > 6)
|
|
17
|
+
return "[TRUNCATED_DEPTH]";
|
|
18
|
+
if (value === null || typeof value === "number" || typeof value === "boolean")
|
|
19
|
+
return value;
|
|
20
|
+
if (typeof value === "string")
|
|
21
|
+
return redactSensitiveMemoryText(truncate(value));
|
|
22
|
+
if (typeof value === "undefined")
|
|
23
|
+
return undefined;
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return value.slice(0, MAX_ARRAY_ITEMS).map((item) => sanitizeHookPayload(item, depth + 1));
|
|
26
|
+
}
|
|
27
|
+
if (typeof value === "object") {
|
|
28
|
+
const output = {};
|
|
29
|
+
for (const [key, item] of Object.entries(value).slice(0, MAX_OBJECT_KEYS)) {
|
|
30
|
+
output[key] = SECRET_KEY_PATTERN.test(key)
|
|
31
|
+
? "[REDACTED]"
|
|
32
|
+
: sanitizeHookPayload(item, depth + 1);
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
return String(value);
|
|
37
|
+
}
|
|
38
|
+
export function registerHostHook(params) {
|
|
39
|
+
const now = new Date().toISOString();
|
|
40
|
+
const id = `${params.userId}:${params.source}:${params.sessionKey ?? "global"}`;
|
|
41
|
+
const existing = registeredHooks.get(id);
|
|
42
|
+
const hook = {
|
|
43
|
+
id,
|
|
44
|
+
userId: params.userId,
|
|
45
|
+
source: params.source,
|
|
46
|
+
events: params.events ?? existing?.events ?? [],
|
|
47
|
+
sessionKey: params.sessionKey ?? existing?.sessionKey,
|
|
48
|
+
workspacePath: params.workspacePath ?? existing?.workspacePath,
|
|
49
|
+
registeredAt: existing?.registeredAt ?? now,
|
|
50
|
+
lastSeenAt: existing?.lastSeenAt ?? null,
|
|
51
|
+
lastEvent: existing?.lastEvent ?? null,
|
|
52
|
+
metadata: params.metadata ?? existing?.metadata ?? {},
|
|
53
|
+
};
|
|
54
|
+
registeredHooks.set(id, hook);
|
|
55
|
+
return hook;
|
|
56
|
+
}
|
|
57
|
+
export function listHostHooks(userId) {
|
|
58
|
+
return [...registeredHooks.values()]
|
|
59
|
+
.filter((hook) => hook.userId === userId)
|
|
60
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
61
|
+
}
|
|
62
|
+
function touchHook(event) {
|
|
63
|
+
const hook = registerHostHook({
|
|
64
|
+
userId: event.userId,
|
|
65
|
+
source: event.source,
|
|
66
|
+
events: [event.event],
|
|
67
|
+
sessionKey: event.sessionKey,
|
|
68
|
+
workspacePath: event.workspacePath,
|
|
69
|
+
});
|
|
70
|
+
hook.lastSeenAt = new Date().toISOString();
|
|
71
|
+
hook.lastEvent = event.event;
|
|
72
|
+
if (!hook.events.includes(event.event)) {
|
|
73
|
+
hook.events = [...hook.events, event.event];
|
|
74
|
+
}
|
|
75
|
+
registeredHooks.set(hook.id, hook);
|
|
76
|
+
return hook;
|
|
77
|
+
}
|
|
78
|
+
function buildCaptureContent(event) {
|
|
79
|
+
return JSON.stringify({
|
|
80
|
+
source: event.source,
|
|
81
|
+
event: event.event,
|
|
82
|
+
toolName: event.toolName,
|
|
83
|
+
args: sanitizeHookPayload(event.args),
|
|
84
|
+
result: sanitizeHookPayload(event.result),
|
|
85
|
+
prompt: sanitizeHookPayload(event.prompt),
|
|
86
|
+
content: sanitizeHookPayload(event.content),
|
|
87
|
+
metadata: sanitizeHookPayload(event.metadata ?? {}),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export async function processGenericMcpHook(engine, event) {
|
|
91
|
+
const hook = touchHook(event);
|
|
92
|
+
const capture = engine.capturePassiveL0({
|
|
93
|
+
userId: event.userId,
|
|
94
|
+
sessionKey: event.sessionKey,
|
|
95
|
+
sessionId: event.sessionId,
|
|
96
|
+
role: "tool",
|
|
97
|
+
content: buildCaptureContent(event),
|
|
98
|
+
timestamp: event.timestamp ?? Date.now(),
|
|
99
|
+
skillTag: `host:${event.source}`,
|
|
100
|
+
});
|
|
101
|
+
const workspacePath = event.workspacePath ?? hook.workspacePath;
|
|
102
|
+
const flushedWorkingMemory = SESSION_END_EVENTS.has(event.event) && Boolean(workspacePath);
|
|
103
|
+
if (flushedWorkingMemory && workspacePath) {
|
|
104
|
+
resetWorkingMemory(workspacePath, event.userId, event.sessionKey);
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
hookId: hook.id,
|
|
108
|
+
l0RecordedCount: 1,
|
|
109
|
+
l0RecordId: capture.id,
|
|
110
|
+
flushedWorkingMemory,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export function buildHookResult(value) {
|
|
114
|
+
return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
|
|
115
|
+
}
|
|
116
|
+
export function fallbackSessionKey(source) {
|
|
117
|
+
return `${source}-${randomUUID()}`;
|
|
118
|
+
}
|
package/dist/loader.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Fragment, SkillSection, SkillScope } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load the entire file verbatim (minus frontmatter, which is prepended cleanly).
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadFull(filePath: string, scope?: SkillScope): Fragment;
|
|
6
|
+
/**
|
|
7
|
+
* Load only the frontmatter description field.
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadDescription(filePath: string, scope?: SkillScope): Fragment;
|
|
10
|
+
/**
|
|
11
|
+
* Load a named ## section from a markdown file.
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadSection(filePath: string, sectionKey: Exclude<SkillSection, 'description' | 'full' | 'phases' | 'checklist'>, scope?: SkillScope): Fragment;
|
|
14
|
+
/**
|
|
15
|
+
* Load all ### Phase N sub-blocks.
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadPhases(filePath: string, scope?: SkillScope): Fragment;
|
|
18
|
+
/**
|
|
19
|
+
* Load all checklist items from the file.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadChecklist(filePath: string, scope?: SkillScope): Fragment;
|
|
22
|
+
/**
|
|
23
|
+
* Master dispatcher: load the correct fragment based on SkillSection.
|
|
24
|
+
*/
|
|
25
|
+
export declare function loadSkillSection(filePath: string, section: SkillSection, scope?: SkillScope): Fragment;
|
|
26
|
+
/**
|
|
27
|
+
* Load a named ## heading from any markdown doc file (used for get_template_doc).
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadDocSection(filePath: string, sectionHeading?: string): Fragment;
|
package/dist/loader.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────
|
|
2
|
+
// BrainRouter MCP Server — Markdown Loader
|
|
3
|
+
// Reads and extracts sections from any markdown file on demand.
|
|
4
|
+
// ─────────────────────────────────────────────
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import matter from 'gray-matter';
|
|
7
|
+
/** Estimate token count: ~4 chars per token */
|
|
8
|
+
function estimateTokens(text) {
|
|
9
|
+
return Math.ceil(text.length / 4);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Heading aliases: maps SkillSection keys → possible ## heading strings.
|
|
13
|
+
* We match case-insensitively. First match wins.
|
|
14
|
+
*/
|
|
15
|
+
const SECTION_HEADINGS = {
|
|
16
|
+
overview: ['overview'],
|
|
17
|
+
when_to_use: ['when to use', 'when to invoke', 'triggers'],
|
|
18
|
+
workflow: ['workflow', 'core process', 'the workflow', 'steps', 'process', 'checklist', 'methodology', 'routine', 'guidelines', 'patterns', 'template', 'structure', 'lifecycle'],
|
|
19
|
+
usage: ['usage', 'example usage', 'examples'],
|
|
20
|
+
detailed_instructions: ['detailed instructions', 'instructions', 'detailed guide'],
|
|
21
|
+
red_flags: ['red flags', 'warning signs'],
|
|
22
|
+
rationalizations: ['common rationalizations', 'rationalizations'],
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Split a markdown body (after frontmatter) into { heading, content } blocks.
|
|
26
|
+
* Only splits on ## level headings.
|
|
27
|
+
*/
|
|
28
|
+
function splitSections(body) {
|
|
29
|
+
const lines = body.split('\n');
|
|
30
|
+
const sections = [];
|
|
31
|
+
let currentHeading = '__preamble__';
|
|
32
|
+
let currentLines = [];
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const h2Match = line.match(/^##\s+(.+)/);
|
|
35
|
+
if (h2Match) {
|
|
36
|
+
sections.push({ heading: currentHeading, content: currentLines.join('\n').trim() });
|
|
37
|
+
currentHeading = h2Match[1].trim();
|
|
38
|
+
currentLines = [];
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
currentLines.push(line);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
sections.push({ heading: currentHeading, content: currentLines.join('\n').trim() });
|
|
45
|
+
return sections;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract all ### Phase N sub-blocks from a body string.
|
|
49
|
+
*/
|
|
50
|
+
function extractPhases(body) {
|
|
51
|
+
const lines = body.split('\n');
|
|
52
|
+
const phaseLines = [];
|
|
53
|
+
let inPhase = false;
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
if (/^###\s+phase/i.test(line)) {
|
|
56
|
+
inPhase = true;
|
|
57
|
+
}
|
|
58
|
+
else if (/^##\s/.test(line)) {
|
|
59
|
+
inPhase = false;
|
|
60
|
+
}
|
|
61
|
+
if (inPhase)
|
|
62
|
+
phaseLines.push(line);
|
|
63
|
+
}
|
|
64
|
+
return phaseLines.join('\n').trim();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract all checklist items (- [ ] or - [x]) from a body string.
|
|
68
|
+
*/
|
|
69
|
+
function extractChecklist(body) {
|
|
70
|
+
return body
|
|
71
|
+
.split('\n')
|
|
72
|
+
.filter((l) => /^- \[[ x]\]/.test(l))
|
|
73
|
+
.join('\n')
|
|
74
|
+
.trim();
|
|
75
|
+
}
|
|
76
|
+
// ─── Public API ─────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* Load the entire file verbatim (minus frontmatter, which is prepended cleanly).
|
|
79
|
+
*/
|
|
80
|
+
export function loadFull(filePath, scope) {
|
|
81
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
82
|
+
const { content } = matter(raw);
|
|
83
|
+
return {
|
|
84
|
+
content: content.trim(),
|
|
85
|
+
source: filePath,
|
|
86
|
+
scope,
|
|
87
|
+
tokenEstimate: estimateTokens(content),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Load only the frontmatter description field.
|
|
92
|
+
*/
|
|
93
|
+
export function loadDescription(filePath, scope) {
|
|
94
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
95
|
+
const { data } = matter(raw);
|
|
96
|
+
const content = data.description ?? '';
|
|
97
|
+
return {
|
|
98
|
+
content,
|
|
99
|
+
source: filePath,
|
|
100
|
+
scope,
|
|
101
|
+
tokenEstimate: estimateTokens(content),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Load a named ## section from a markdown file.
|
|
106
|
+
*/
|
|
107
|
+
export function loadSection(filePath, sectionKey, scope) {
|
|
108
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
109
|
+
const { content } = matter(raw);
|
|
110
|
+
const sections = splitSections(content);
|
|
111
|
+
const aliases = SECTION_HEADINGS[sectionKey];
|
|
112
|
+
let match = sections.find((s) => aliases.some((alias) => s.heading.toLowerCase().includes(alias)));
|
|
113
|
+
// FALLBACK for Overview: If ## Overview is missing, use the preamble (text before first ##)
|
|
114
|
+
if (!match && sectionKey === 'overview') {
|
|
115
|
+
const preamble = sections.find(s => s.heading === '__preamble__');
|
|
116
|
+
if (preamble && preamble.content.length > 20) {
|
|
117
|
+
match = { heading: 'Overview', content: preamble.content };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const extracted = match
|
|
121
|
+
? `## ${match.heading}\n\n${match.content}`
|
|
122
|
+
: `<!-- Section "${sectionKey}" not found in ${filePath} -->`;
|
|
123
|
+
return {
|
|
124
|
+
content: extracted.trim(),
|
|
125
|
+
source: filePath,
|
|
126
|
+
scope,
|
|
127
|
+
tokenEstimate: estimateTokens(extracted),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Load all ### Phase N sub-blocks.
|
|
132
|
+
*/
|
|
133
|
+
export function loadPhases(filePath, scope) {
|
|
134
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
135
|
+
const { content } = matter(raw);
|
|
136
|
+
const phases = extractPhases(content);
|
|
137
|
+
const result = phases || `<!-- No phase blocks found in ${filePath} -->`;
|
|
138
|
+
return {
|
|
139
|
+
content: result,
|
|
140
|
+
source: filePath,
|
|
141
|
+
scope,
|
|
142
|
+
tokenEstimate: estimateTokens(result),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Load all checklist items from the file.
|
|
147
|
+
*/
|
|
148
|
+
export function loadChecklist(filePath, scope) {
|
|
149
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
150
|
+
const { content } = matter(raw);
|
|
151
|
+
const checklist = extractChecklist(content);
|
|
152
|
+
const result = checklist || `<!-- No checklist items found in ${filePath} -->`;
|
|
153
|
+
return {
|
|
154
|
+
content: result,
|
|
155
|
+
source: filePath,
|
|
156
|
+
scope,
|
|
157
|
+
tokenEstimate: estimateTokens(result),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Master dispatcher: load the correct fragment based on SkillSection.
|
|
162
|
+
*/
|
|
163
|
+
export function loadSkillSection(filePath, section, scope) {
|
|
164
|
+
switch (section) {
|
|
165
|
+
case 'description':
|
|
166
|
+
return loadDescription(filePath, scope);
|
|
167
|
+
case 'full':
|
|
168
|
+
return loadFull(filePath, scope);
|
|
169
|
+
case 'phases':
|
|
170
|
+
return loadPhases(filePath, scope);
|
|
171
|
+
case 'checklist':
|
|
172
|
+
return loadChecklist(filePath, scope);
|
|
173
|
+
default:
|
|
174
|
+
return loadSection(filePath, section, scope);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Load a named ## heading from any markdown doc file (used for get_template_doc).
|
|
179
|
+
*/
|
|
180
|
+
export function loadDocSection(filePath, sectionHeading) {
|
|
181
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
182
|
+
const { content } = matter(raw);
|
|
183
|
+
if (!sectionHeading) {
|
|
184
|
+
return {
|
|
185
|
+
content: content.trim(),
|
|
186
|
+
source: filePath,
|
|
187
|
+
tokenEstimate: estimateTokens(content),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
const sections = splitSections(content);
|
|
191
|
+
const match = sections.find((s) => s.heading.toLowerCase().includes(sectionHeading.toLowerCase()));
|
|
192
|
+
const extracted = match
|
|
193
|
+
? `## ${match.heading}\n\n${match.content}`
|
|
194
|
+
: `<!-- Section "${sectionHeading}" not found in ${filePath} -->`;
|
|
195
|
+
return {
|
|
196
|
+
content: extracted.trim(),
|
|
197
|
+
source: filePath,
|
|
198
|
+
tokenEstimate: estimateTokens(extracted),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { IMemoryStore } from "@kinqs/brainrouter-types";
|
|
2
|
+
import type { CaptureResult, LLMRunner, CognitiveExtractionStatus } from "@kinqs/brainrouter-types";
|
|
3
|
+
import type { EmbeddingService } from "./store/embedding.js";
|
|
4
|
+
export declare class MemoryCapturePipeline {
|
|
5
|
+
private store;
|
|
6
|
+
private llmRunner;
|
|
7
|
+
private embeddingService;
|
|
8
|
+
private extractEveryNTurns;
|
|
9
|
+
constructor(store: IMemoryStore, llmRunner: LLMRunner, embeddingService: EmbeddingService, extractEveryNTurns?: number);
|
|
10
|
+
captureTurn(params: {
|
|
11
|
+
userId: string;
|
|
12
|
+
sessionKey: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
messages: {
|
|
15
|
+
role: string;
|
|
16
|
+
content: string;
|
|
17
|
+
timestamp: number;
|
|
18
|
+
}[];
|
|
19
|
+
activeSkill?: string;
|
|
20
|
+
skillHints?: string;
|
|
21
|
+
}): Promise<CaptureResult>;
|
|
22
|
+
processBacklog(params: {
|
|
23
|
+
userId: string;
|
|
24
|
+
sessionKey: string;
|
|
25
|
+
sessionId?: string;
|
|
26
|
+
activeSkill?: string;
|
|
27
|
+
skillHints?: string;
|
|
28
|
+
}): Promise<{
|
|
29
|
+
triggered: boolean;
|
|
30
|
+
extractedCount: number;
|
|
31
|
+
status: CognitiveExtractionStatus;
|
|
32
|
+
errorMessage?: string;
|
|
33
|
+
}>;
|
|
34
|
+
private extractPendingSensory;
|
|
35
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { extractCognitiveMemories } from "./pipeline/cognitive-extractor.js";
|
|
2
|
+
import { deduplicateMemories } from "./pipeline/cognitive-dedup.js";
|
|
3
|
+
import { detectContradictions } from "./pipeline/cognitive-contradiction.js";
|
|
4
|
+
import { buildGraphFromCognitive } from "./pipeline/graph-builder.js";
|
|
5
|
+
import { distillFocusScenes } from "./pipeline/contextual-focus-builder.js";
|
|
6
|
+
import { distillCoreIdentity } from "./pipeline/identity-distiller.js";
|
|
7
|
+
import { detectFocusShift } from "./pipeline/focus-direction-shift.js";
|
|
8
|
+
import { shouldRunFocusDistill, shouldRunIdentityDistill } from "./scheduler.js";
|
|
9
|
+
import { NeuralSparkEngine } from "./pipeline/neural-spark.js";
|
|
10
|
+
import { redactSensitiveMemoryText } from "./redaction.js";
|
|
11
|
+
import crypto from "node:crypto";
|
|
12
|
+
export class MemoryCapturePipeline {
|
|
13
|
+
store;
|
|
14
|
+
llmRunner;
|
|
15
|
+
embeddingService;
|
|
16
|
+
extractEveryNTurns;
|
|
17
|
+
constructor(store, llmRunner, embeddingService, extractEveryNTurns = 3) {
|
|
18
|
+
this.store = store;
|
|
19
|
+
this.llmRunner = llmRunner;
|
|
20
|
+
this.embeddingService = embeddingService;
|
|
21
|
+
this.extractEveryNTurns = extractEveryNTurns;
|
|
22
|
+
}
|
|
23
|
+
async captureTurn(params) {
|
|
24
|
+
const { userId, sessionKey, sessionId = "", messages, activeSkill, skillHints } = params;
|
|
25
|
+
const nowStr = new Date().toISOString();
|
|
26
|
+
const sensoryRecords = [];
|
|
27
|
+
// 1. Write Sensory Records atomically
|
|
28
|
+
for (let i = 0; i < messages.length; i++) {
|
|
29
|
+
const msg = messages[i];
|
|
30
|
+
const record = {
|
|
31
|
+
id: `sensory_${sessionKey}_${msg.timestamp}_${i}_${crypto.randomBytes(3).toString("hex")}`,
|
|
32
|
+
userId,
|
|
33
|
+
sessionKey,
|
|
34
|
+
sessionId,
|
|
35
|
+
role: msg.role,
|
|
36
|
+
messageText: redactSensitiveMemoryText(msg.content),
|
|
37
|
+
recordedAt: nowStr,
|
|
38
|
+
timestamp: msg.timestamp,
|
|
39
|
+
skillTag: activeSkill || "",
|
|
40
|
+
};
|
|
41
|
+
this.store.upsertSensory(record);
|
|
42
|
+
sensoryRecords.push(record);
|
|
43
|
+
}
|
|
44
|
+
// 2. Decide if we should trigger Cognitive extraction
|
|
45
|
+
const unextractedCount = this.store.getUnextractedSensoryCount(userId, sessionKey);
|
|
46
|
+
let cognitiveExtractionTriggered = false;
|
|
47
|
+
let cognitiveExtractedCount = 0;
|
|
48
|
+
let cognitiveExtractionStatus = "skipped";
|
|
49
|
+
let cognitiveExtractionError;
|
|
50
|
+
if (unextractedCount >= this.extractEveryNTurns) {
|
|
51
|
+
const result = await this.extractPendingSensory({ userId, sessionKey, sessionId, activeSkill, skillHints });
|
|
52
|
+
cognitiveExtractionTriggered = result.triggered;
|
|
53
|
+
cognitiveExtractedCount = result.extractedCount;
|
|
54
|
+
cognitiveExtractionStatus = result.status;
|
|
55
|
+
cognitiveExtractionError = result.errorMessage;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
sensoryRecordedCount: sensoryRecords.length,
|
|
59
|
+
cognitiveExtractionTriggered,
|
|
60
|
+
cognitiveExtractedCount,
|
|
61
|
+
cognitiveExtractionStatus,
|
|
62
|
+
cognitiveExtractionError,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async processBacklog(params) {
|
|
66
|
+
return this.extractPendingSensory(params);
|
|
67
|
+
}
|
|
68
|
+
async extractPendingSensory(params) {
|
|
69
|
+
const { userId, sessionKey, sessionId = "", activeSkill, skillHints } = params;
|
|
70
|
+
const recentSensory = this.store.getRecentSensoryMessages(userId, sessionKey, 20);
|
|
71
|
+
if (recentSensory.length === 0) {
|
|
72
|
+
return { triggered: false, extractedCount: 0, status: "skipped" };
|
|
73
|
+
}
|
|
74
|
+
const extractionResult = await extractCognitiveMemories({
|
|
75
|
+
messages: recentSensory,
|
|
76
|
+
userId,
|
|
77
|
+
sessionKey,
|
|
78
|
+
sessionId,
|
|
79
|
+
llmRunner: this.llmRunner,
|
|
80
|
+
activeSkill,
|
|
81
|
+
existingSceneNames: this.store.getTopContextualFocus(userId, 20).map(s => s.sceneName),
|
|
82
|
+
skillHints: skillHints ?? (activeSkill ? this.store.getSkillHints(activeSkill) ?? undefined : undefined)
|
|
83
|
+
});
|
|
84
|
+
if (!extractionResult.success) {
|
|
85
|
+
this.store.recordExtractionFailure(userId, extractionResult.errorMessage ?? "Cognitive extraction failed");
|
|
86
|
+
return {
|
|
87
|
+
triggered: true,
|
|
88
|
+
extractedCount: 0,
|
|
89
|
+
status: "failed",
|
|
90
|
+
errorMessage: extractionResult.errorMessage ?? "Cognitive extraction failed",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
this.store.markSensoryExtracted(userId, sessionKey, recentSensory.map((record) => record.id));
|
|
94
|
+
this.store.resetExtractionFailures(userId);
|
|
95
|
+
if (extractionResult.records.length === 0) {
|
|
96
|
+
// LLM returned an empty list — legitimate "nothing notable" outcome
|
|
97
|
+
// (e.g. a greeting or trivial exchange). Status is "ok" so the CLI
|
|
98
|
+
// doesn't surface a misleading "extractor broke" warning.
|
|
99
|
+
return { triggered: true, extractedCount: 0, status: "ok" };
|
|
100
|
+
}
|
|
101
|
+
// Run active deduplication BEFORE storing
|
|
102
|
+
const { uniqueRecords, droppedCount } = await deduplicateMemories({
|
|
103
|
+
records: extractionResult.records,
|
|
104
|
+
store: this.store,
|
|
105
|
+
userId
|
|
106
|
+
});
|
|
107
|
+
if (droppedCount > 0) {
|
|
108
|
+
console.log(`[BrainRouter] Dropped ${droppedCount} duplicate cognitive memories.`);
|
|
109
|
+
}
|
|
110
|
+
// Write to store
|
|
111
|
+
for (const record of uniqueRecords) {
|
|
112
|
+
this.store.upsertCognitive(record);
|
|
113
|
+
// Non-blocking background embedding (Slice A)
|
|
114
|
+
if (this.embeddingService.isReady()) {
|
|
115
|
+
this.embeddingService.embed(record.content)
|
|
116
|
+
.then((vec) => {
|
|
117
|
+
this.store.upsertCognitiveVec(record.id, vec);
|
|
118
|
+
})
|
|
119
|
+
.catch((err) => {
|
|
120
|
+
console.error(`[BrainRouter] Background embedding failed for ${record.id}:`, err.message);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Non-blocking contradiction check (Slice C)
|
|
124
|
+
detectContradictions({
|
|
125
|
+
newRecord: record,
|
|
126
|
+
store: this.store,
|
|
127
|
+
llmRunner: this.llmRunner
|
|
128
|
+
}).catch((err) => {
|
|
129
|
+
console.error(`[BrainRouter] Background contradiction check failed for ${record.id}:`, err.message);
|
|
130
|
+
});
|
|
131
|
+
// Non-blocking graph extraction (GraphRAG Slice)
|
|
132
|
+
buildGraphFromCognitive({
|
|
133
|
+
record,
|
|
134
|
+
store: this.store,
|
|
135
|
+
llmRunner: this.llmRunner
|
|
136
|
+
}).catch((err) => {
|
|
137
|
+
console.error(`[BrainRouter] Background graph extraction failed for ${record.id}:`, err.message);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
// --- Seeding Dendritic Spine Connections ---
|
|
141
|
+
for (let i = 0; i < uniqueRecords.length; i++) {
|
|
142
|
+
const recA = uniqueRecords[i];
|
|
143
|
+
// 1. Connect with other records extracted in this same batch/turn
|
|
144
|
+
for (let j = i + 1; j < uniqueRecords.length; j++) {
|
|
145
|
+
const recB = uniqueRecords[j];
|
|
146
|
+
this.store.upsertConnection(userId, recA.id, recB.id, 0.5);
|
|
147
|
+
this.store.upsertConnection(userId, recB.id, recA.id, 0.5);
|
|
148
|
+
}
|
|
149
|
+
// 2. Connect with existing active records sharing the same focus scene name
|
|
150
|
+
if (recA.sceneName) {
|
|
151
|
+
const matchingRecords = this.store.getCognitivesByFocus(userId, recA.sceneName, 10);
|
|
152
|
+
for (const match of matchingRecords) {
|
|
153
|
+
if (match.record_id !== recA.id) {
|
|
154
|
+
this.store.upsertConnection(userId, recA.id, match.record_id, 0.5);
|
|
155
|
+
this.store.upsertConnection(userId, match.record_id, recA.id, 0.5);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const cognitiveExtractedCount = uniqueRecords.length;
|
|
161
|
+
if (cognitiveExtractedCount === 0) {
|
|
162
|
+
// All extracted records were duplicates of existing memories — the
|
|
163
|
+
// LLM ran fine, dedup just dropped everything. Still "ok".
|
|
164
|
+
return { triggered: true, extractedCount: 0, status: "ok" };
|
|
165
|
+
}
|
|
166
|
+
// Update scheduler counters
|
|
167
|
+
this.store.incrementSchedulerCognitiveCount(userId, cognitiveExtractedCount);
|
|
168
|
+
// Check if Focus distillation should fire
|
|
169
|
+
const topScenes = this.store.getTopContextualFocus(userId, 1);
|
|
170
|
+
if (topScenes.length > 0) {
|
|
171
|
+
detectFocusShift({
|
|
172
|
+
activeScene: topScenes[0],
|
|
173
|
+
newCognitiveRecords: uniqueRecords,
|
|
174
|
+
llmRunner: this.llmRunner,
|
|
175
|
+
}).then(shiftResult => {
|
|
176
|
+
if (shiftResult.shift && shiftResult.confidence >= 0.75) {
|
|
177
|
+
console.error(`[BrainRouter] Focus shift detected (confidence=${shiftResult.confidence.toFixed(2)}): ${shiftResult.reason}. Triggering focus distillation.`);
|
|
178
|
+
this.store.resetSchedulerFocusCount(userId);
|
|
179
|
+
try {
|
|
180
|
+
const sparkEngine = new NeuralSparkEngine(this.store);
|
|
181
|
+
sparkEngine.decayAndPrune(userId);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
console.error("[BrainRouter] LTD decay and prune failed:", err.message);
|
|
185
|
+
}
|
|
186
|
+
distillFocusScenes({ userId, store: this.store, llmRunner: this.llmRunner })
|
|
187
|
+
.catch(err => console.error("[BrainRouter] Background focus distillation failed:", err.message));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const countState = this.store.getSchedulerState(userId);
|
|
191
|
+
if (shouldRunFocusDistill(countState)) {
|
|
192
|
+
this.store.resetSchedulerFocusCount(userId);
|
|
193
|
+
try {
|
|
194
|
+
const sparkEngine = new NeuralSparkEngine(this.store);
|
|
195
|
+
sparkEngine.decayAndPrune(userId);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
console.error("[BrainRouter] LTD decay and prune failed:", err.message);
|
|
199
|
+
}
|
|
200
|
+
distillFocusScenes({ userId, store: this.store, llmRunner: this.llmRunner })
|
|
201
|
+
.catch(err => console.error("[BrainRouter] Background focus distillation failed:", err.message));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}).catch(err => console.error("[BrainRouter] Background focus shift detection failed:", err.message));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const countState = this.store.getSchedulerState(userId);
|
|
208
|
+
if (shouldRunFocusDistill(countState)) {
|
|
209
|
+
this.store.resetSchedulerFocusCount(userId);
|
|
210
|
+
try {
|
|
211
|
+
const sparkEngine = new NeuralSparkEngine(this.store);
|
|
212
|
+
sparkEngine.decayAndPrune(userId);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
console.error("[BrainRouter] LTD decay and prune failed:", err.message);
|
|
216
|
+
}
|
|
217
|
+
distillFocusScenes({ userId, store: this.store, llmRunner: this.llmRunner })
|
|
218
|
+
.catch(err => console.error("[BrainRouter] Background focus distillation failed:", err.message));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Check if Core Identity distillation should fire
|
|
222
|
+
const identityState = this.store.getSchedulerState(userId);
|
|
223
|
+
if (shouldRunIdentityDistill(identityState)) {
|
|
224
|
+
this.store.resetSchedulerIdentityCount(userId);
|
|
225
|
+
distillCoreIdentity({ userId, store: this.store, llmRunner: this.llmRunner })
|
|
226
|
+
.catch(err => console.error("[BrainRouter] Background core identity distillation failed:", err.message));
|
|
227
|
+
}
|
|
228
|
+
return { triggered: true, extractedCount: cognitiveExtractedCount, status: "ok" };
|
|
229
|
+
}
|
|
230
|
+
}
|