prism-mcp-server 19.2.7 → 19.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/scm/types.js +1 -4
- package/dist/storage/synalux.js +1 -1
- package/dist/tools/__tests__/ingestHandler.test.js +19 -13
- package/dist/tools/__tests__/layer1Integration.test.js +357 -0
- package/dist/tools/__tests__/ledgerHandlers.test.js +31 -28
- package/dist/tools/ingestHandler.js +2 -1
- package/dist/tools/ledgerHandlers.js +82 -15
- package/dist/tools/prismInferHandler.js +164 -22
- package/dist/tools/sessionDriftHandler.js +1 -1
- package/dist/tools/skillRouting.js +22 -9
- package/dist/utils/entitlements.js +9 -0
- package/dist/utils/inferenceMetrics.js +89 -21
- package/dist/utils/layer1.js +110 -0
- package/dist/utils/migration/utils.js +4 -4
- package/dist/utils/projectResolver.js +2 -4
- package/dist/utils/qualityGate.js +19 -3
- package/dist/utils/safetyGate.js +22 -0
- package/dist/vm/competitorImport.js +1 -1
- package/dist/vm/componentMarketplace.js +1 -1
- package/dist/vm/creativeStudio.js +1 -1
- package/dist/vm/ethicsEnforcement.js +2 -2
- package/dist/vm/gameEngine.js +1 -1
- package/dist/vm/projectTemplates.js +1 -1
- package/dist/vm/types.js +1 -2
- package/dist/vm/vmManager.js +1 -1
- package/dist/vm/workspaceLicensing.js +1 -1
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ vi.mock("../../../src/config.js", () => ({
|
|
|
54
54
|
PRISM_GRAPH_PRUNE_MAX_PROJECTS_PER_SWEEP: 25,
|
|
55
55
|
PRISM_ACTR_ENABLED: false,
|
|
56
56
|
PRISM_ACTR_ACCESS_LOG_RETENTION_DAYS: 90,
|
|
57
|
+
SYNALUX_CONFIGURED: false,
|
|
57
58
|
}));
|
|
58
59
|
vi.mock("../../../src/utils/logger.js", () => ({
|
|
59
60
|
debugLog: vi.fn(),
|
|
@@ -427,7 +428,7 @@ describe("ledgerHandlers", () => {
|
|
|
427
428
|
});
|
|
428
429
|
const text = result.content[0].text;
|
|
429
430
|
// With 100 tokens * 4 chars = 400 char budget, the 5000-char summary gets truncated
|
|
430
|
-
expect(text).toContain("
|
|
431
|
+
expect(text).toContain("Sections omitted to fit token budget");
|
|
431
432
|
});
|
|
432
433
|
it("includes recent sessions in formatted output", async () => {
|
|
433
434
|
storage.loadContext.mockResolvedValue({
|
|
@@ -677,36 +678,36 @@ describe("ledgerHandlers", () => {
|
|
|
677
678
|
describe("sessionForgetMemoryHandler", () => {
|
|
678
679
|
it("soft-deletes a memory entry by default", async () => {
|
|
679
680
|
const result = await sessionForgetMemoryHandler({
|
|
680
|
-
memory_id: "
|
|
681
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440123",
|
|
681
682
|
});
|
|
682
683
|
expect(result.isError).toBe(false);
|
|
683
684
|
expect(result.content[0].text).toContain("Soft Deleted");
|
|
684
|
-
expect(result.content[0].text).toContain("
|
|
685
|
-
expect(storage.softDeleteLedger).toHaveBeenCalledWith("
|
|
685
|
+
expect(result.content[0].text).toContain("550e8400-e29b-41d4-a716-446655440123");
|
|
686
|
+
expect(storage.softDeleteLedger).toHaveBeenCalledWith("550e8400-e29b-41d4-a716-446655440123", "test-user-id", undefined);
|
|
686
687
|
});
|
|
687
688
|
it("soft-deletes with reason for audit trail", async () => {
|
|
688
689
|
const result = await sessionForgetMemoryHandler({
|
|
689
|
-
memory_id: "
|
|
690
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440456",
|
|
690
691
|
reason: "GDPR Article 17 request",
|
|
691
692
|
});
|
|
692
693
|
expect(result.isError).toBe(false);
|
|
693
694
|
expect(result.content[0].text).toContain("Reason");
|
|
694
695
|
expect(result.content[0].text).toContain("GDPR Article 17 request");
|
|
695
|
-
expect(storage.softDeleteLedger).toHaveBeenCalledWith("
|
|
696
|
+
expect(storage.softDeleteLedger).toHaveBeenCalledWith("550e8400-e29b-41d4-a716-446655440456", "test-user-id", "GDPR Article 17 request");
|
|
696
697
|
});
|
|
697
698
|
it("hard-deletes when hard_delete is true", async () => {
|
|
698
699
|
const result = await sessionForgetMemoryHandler({
|
|
699
|
-
memory_id: "
|
|
700
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440789",
|
|
700
701
|
hard_delete: true,
|
|
701
702
|
});
|
|
702
703
|
expect(result.isError).toBe(false);
|
|
703
704
|
expect(result.content[0].text).toContain("Hard Deleted");
|
|
704
705
|
expect(result.content[0].text).toContain("permanently removed");
|
|
705
|
-
expect(storage.hardDeleteLedger).toHaveBeenCalledWith("
|
|
706
|
+
expect(storage.hardDeleteLedger).toHaveBeenCalledWith("550e8400-e29b-41d4-a716-446655440789", "test-user-id");
|
|
706
707
|
});
|
|
707
708
|
it("does not call hardDeleteLedger when hard_delete is false", async () => {
|
|
708
709
|
await sessionForgetMemoryHandler({
|
|
709
|
-
memory_id: "
|
|
710
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440aaa",
|
|
710
711
|
hard_delete: false,
|
|
711
712
|
});
|
|
712
713
|
expect(storage.hardDeleteLedger).not.toHaveBeenCalled();
|
|
@@ -731,7 +732,7 @@ describe("ledgerHandlers", () => {
|
|
|
731
732
|
it("catches storage errors and returns isError (never throws)", async () => {
|
|
732
733
|
storage.softDeleteLedger.mockRejectedValue(new Error("Entry not found"));
|
|
733
734
|
const result = await sessionForgetMemoryHandler({
|
|
734
|
-
memory_id: "
|
|
735
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440f99",
|
|
735
736
|
});
|
|
736
737
|
expect(result.isError).toBe(true);
|
|
737
738
|
expect(result.content[0].text).toContain("Entry not found");
|
|
@@ -739,7 +740,7 @@ describe("ledgerHandlers", () => {
|
|
|
739
740
|
it("catches hard_delete storage errors gracefully", async () => {
|
|
740
741
|
storage.hardDeleteLedger.mockRejectedValue(new Error("FK constraint"));
|
|
741
742
|
const result = await sessionForgetMemoryHandler({
|
|
742
|
-
memory_id: "
|
|
743
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440f11",
|
|
743
744
|
hard_delete: true,
|
|
744
745
|
});
|
|
745
746
|
expect(result.isError).toBe(true);
|
|
@@ -753,6 +754,7 @@ describe("ledgerHandlers", () => {
|
|
|
753
754
|
let tempDir;
|
|
754
755
|
beforeEach(async () => {
|
|
755
756
|
tempDir = await mkdtemp(join(tmpdir(), "prism-handler-export-"));
|
|
757
|
+
process.env.PRISM_EXPORT_ROOT = tempDir;
|
|
756
758
|
storage.listProjects.mockResolvedValue(["test-project"]);
|
|
757
759
|
storage.getLedgerEntries.mockResolvedValue([
|
|
758
760
|
{ id: "entry-1", summary: "Session 1", importance: 3 },
|
|
@@ -763,6 +765,7 @@ describe("ledgerHandlers", () => {
|
|
|
763
765
|
});
|
|
764
766
|
});
|
|
765
767
|
afterEach(async () => {
|
|
768
|
+
delete process.env.PRISM_EXPORT_ROOT;
|
|
766
769
|
await rm(tempDir, { recursive: true, force: true });
|
|
767
770
|
});
|
|
768
771
|
it("exports JSON file for a single project", async () => {
|
|
@@ -832,7 +835,7 @@ describe("ledgerHandlers", () => {
|
|
|
832
835
|
});
|
|
833
836
|
afterEach(async () => {
|
|
834
837
|
// Clean up vault directory if created
|
|
835
|
-
const vaultDir = join(os.homedir(), ".prism-mcp", "media", "
|
|
838
|
+
const vaultDir = join(os.homedir(), ".prism-mcp", "media", "__test-img__");
|
|
836
839
|
if (fs.existsSync(vaultDir)) {
|
|
837
840
|
await rm(vaultDir, { recursive: true, force: true });
|
|
838
841
|
}
|
|
@@ -840,7 +843,7 @@ describe("ledgerHandlers", () => {
|
|
|
840
843
|
});
|
|
841
844
|
it("saves an image and returns success with image ID", async () => {
|
|
842
845
|
const result = await sessionSaveImageHandler({
|
|
843
|
-
project: "
|
|
846
|
+
project: "__test-img__",
|
|
844
847
|
file_path: testImagePath,
|
|
845
848
|
description: "Dashboard screenshot",
|
|
846
849
|
});
|
|
@@ -851,7 +854,7 @@ describe("ledgerHandlers", () => {
|
|
|
851
854
|
});
|
|
852
855
|
it("updates handoff metadata with visual memory entry", async () => {
|
|
853
856
|
await sessionSaveImageHandler({
|
|
854
|
-
project: "
|
|
857
|
+
project: "__test-img__",
|
|
855
858
|
file_path: testImagePath,
|
|
856
859
|
description: "UI mockup",
|
|
857
860
|
});
|
|
@@ -862,8 +865,8 @@ describe("ledgerHandlers", () => {
|
|
|
862
865
|
});
|
|
863
866
|
it("returns error for non-existent file", async () => {
|
|
864
867
|
const result = await sessionSaveImageHandler({
|
|
865
|
-
project: "
|
|
866
|
-
file_path: "
|
|
868
|
+
project: "__test-img__",
|
|
869
|
+
file_path: join(tempDir, "does-not-exist.png"),
|
|
867
870
|
description: "Missing image",
|
|
868
871
|
});
|
|
869
872
|
expect(result.isError).toBe(true);
|
|
@@ -873,7 +876,7 @@ describe("ledgerHandlers", () => {
|
|
|
873
876
|
const bmpPath = join(tempDir, "test.bmp");
|
|
874
877
|
fs.writeFileSync(bmpPath, Buffer.from([0x42, 0x4d]));
|
|
875
878
|
const result = await sessionSaveImageHandler({
|
|
876
|
-
project: "
|
|
879
|
+
project: "__test-img__",
|
|
877
880
|
file_path: bmpPath,
|
|
878
881
|
description: "BMP image",
|
|
879
882
|
});
|
|
@@ -883,7 +886,7 @@ describe("ledgerHandlers", () => {
|
|
|
883
886
|
it("returns error when no active context exists", async () => {
|
|
884
887
|
storage.loadContext.mockResolvedValue(null);
|
|
885
888
|
const result = await sessionSaveImageHandler({
|
|
886
|
-
project: "
|
|
889
|
+
project: "__test-img__",
|
|
887
890
|
file_path: testImagePath,
|
|
888
891
|
description: "No context image",
|
|
889
892
|
});
|
|
@@ -893,7 +896,7 @@ describe("ledgerHandlers", () => {
|
|
|
893
896
|
// --- Input Validation ---
|
|
894
897
|
it("returns error for invalid args (missing required fields)", async () => {
|
|
895
898
|
const result = await sessionSaveImageHandler({
|
|
896
|
-
project: "
|
|
899
|
+
project: "__test-img__",
|
|
897
900
|
});
|
|
898
901
|
expect(result.isError).toBe(true);
|
|
899
902
|
expect(result.content[0].text).toContain("Invalid arguments");
|
|
@@ -912,7 +915,7 @@ describe("ledgerHandlers", () => {
|
|
|
912
915
|
let vaultImagePath;
|
|
913
916
|
beforeEach(async () => {
|
|
914
917
|
tempDir = await mkdtemp(join(tmpdir(), "prism-view-image-test-"));
|
|
915
|
-
vaultDir = join(os.homedir(), ".prism-mcp", "media", "
|
|
918
|
+
vaultDir = join(os.homedir(), ".prism-mcp", "media", "__test-img__");
|
|
916
919
|
fs.mkdirSync(vaultDir, { recursive: true });
|
|
917
920
|
// Create a test image in the vault
|
|
918
921
|
vaultImagePath = join(vaultDir, "abc12345.png");
|
|
@@ -941,7 +944,7 @@ describe("ledgerHandlers", () => {
|
|
|
941
944
|
},
|
|
942
945
|
});
|
|
943
946
|
const result = await sessionViewImageHandler({
|
|
944
|
-
project: "
|
|
947
|
+
project: "__test-img__",
|
|
945
948
|
image_id: "abc12345",
|
|
946
949
|
});
|
|
947
950
|
expect(result.isError).toBe(false);
|
|
@@ -964,7 +967,7 @@ describe("ledgerHandlers", () => {
|
|
|
964
967
|
},
|
|
965
968
|
});
|
|
966
969
|
const result = await sessionViewImageHandler({
|
|
967
|
-
project: "
|
|
970
|
+
project: "__test-img__",
|
|
968
971
|
image_id: "nonexistent",
|
|
969
972
|
});
|
|
970
973
|
expect(result.isError).toBe(true);
|
|
@@ -976,7 +979,7 @@ describe("ledgerHandlers", () => {
|
|
|
976
979
|
metadata: {},
|
|
977
980
|
});
|
|
978
981
|
const result = await sessionViewImageHandler({
|
|
979
|
-
project: "
|
|
982
|
+
project: "__test-img__",
|
|
980
983
|
image_id: "any-id",
|
|
981
984
|
});
|
|
982
985
|
expect(result.isError).toBe(true);
|
|
@@ -997,7 +1000,7 @@ describe("ledgerHandlers", () => {
|
|
|
997
1000
|
},
|
|
998
1001
|
});
|
|
999
1002
|
const result = await sessionViewImageHandler({
|
|
1000
|
-
project: "
|
|
1003
|
+
project: "__test-img__",
|
|
1001
1004
|
image_id: "abc12345",
|
|
1002
1005
|
});
|
|
1003
1006
|
expect(result.isError).toBe(true);
|
|
@@ -1018,7 +1021,7 @@ describe("ledgerHandlers", () => {
|
|
|
1018
1021
|
},
|
|
1019
1022
|
});
|
|
1020
1023
|
const result = await sessionViewImageHandler({
|
|
1021
|
-
project: "
|
|
1024
|
+
project: "__test-img__",
|
|
1022
1025
|
image_id: "abc12345",
|
|
1023
1026
|
});
|
|
1024
1027
|
expect(result.isError).toBe(false);
|
|
@@ -1028,7 +1031,7 @@ describe("ledgerHandlers", () => {
|
|
|
1028
1031
|
// --- Input Validation ---
|
|
1029
1032
|
it("returns error for invalid args (missing image_id)", async () => {
|
|
1030
1033
|
const result = await sessionViewImageHandler({
|
|
1031
|
-
project: "
|
|
1034
|
+
project: "__test-img__",
|
|
1032
1035
|
});
|
|
1033
1036
|
expect(result.isError).toBe(true);
|
|
1034
1037
|
});
|
|
@@ -1154,13 +1157,13 @@ describe("ledgerHandlers", () => {
|
|
|
1154
1157
|
});
|
|
1155
1158
|
it("sessionForgetMemoryHandler includes 'tombstoned' in soft-delete response", async () => {
|
|
1156
1159
|
const result = await sessionForgetMemoryHandler({
|
|
1157
|
-
memory_id: "
|
|
1160
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440001",
|
|
1158
1161
|
});
|
|
1159
1162
|
expect(result.content[0].text).toContain("tombstoned");
|
|
1160
1163
|
});
|
|
1161
1164
|
it("sessionForgetMemoryHandler mentions hard_delete option in soft-delete response", async () => {
|
|
1162
1165
|
const result = await sessionForgetMemoryHandler({
|
|
1163
|
-
memory_id: "
|
|
1166
|
+
memory_id: "550e8400-e29b-41d4-a716-446655440001",
|
|
1164
1167
|
});
|
|
1165
1168
|
expect(result.content[0].text).toContain("hard_delete: true");
|
|
1166
1169
|
});
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* The handler is storage-agnostic — works with SQLite (local) or Supabase (remote).
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync, existsSync } from "fs";
|
|
15
|
+
import { resolve as resolvePath } from "path";
|
|
15
16
|
import { basename } from "path";
|
|
16
17
|
import { PRISM_USER_ID } from "../config.js";
|
|
17
18
|
import { getStorage } from "../storage/index.js";
|
|
@@ -103,7 +104,7 @@ export async function ingestKnowledge(args) {
|
|
|
103
104
|
const { project, source_label, chunk_size = 4000, } = args;
|
|
104
105
|
let content = args.content || "";
|
|
105
106
|
if (args.file_path) {
|
|
106
|
-
const resolved =
|
|
107
|
+
const resolved = resolvePath(args.file_path);
|
|
107
108
|
const blocked = ["/etc", "/var", "/usr", "/sys", "/proc", "/dev", "/root",
|
|
108
109
|
"/.ssh", "/.env", "/.git/config", "/private/etc"].some(p => resolved.startsWith(p) || resolved.includes("/."));
|
|
109
110
|
if (blocked) {
|
|
@@ -89,7 +89,7 @@ const MEMORY_BOUNDARY_SUFFIX = '\n</prism_memory>';
|
|
|
89
89
|
* After saving, generates an embedding vector for the entry via fire-and-forget.
|
|
90
90
|
*/
|
|
91
91
|
import { computeEffectiveImportance, recordMemoryAccess } from "../utils/cognitiveMemory.js";
|
|
92
|
-
import { formatInferenceMetrics, resetInferenceMetrics } from "../utils/inferenceMetrics.js";
|
|
92
|
+
import { formatInferenceMetrics, resetInferenceMetrics, getInferenceSnapshot } from "../utils/inferenceMetrics.js";
|
|
93
93
|
export async function sessionSaveLedgerHandler(args) {
|
|
94
94
|
if (!isSessionSaveLedgerArgs(args)) {
|
|
95
95
|
throw new Error("Invalid arguments for session_save_ledger");
|
|
@@ -581,10 +581,17 @@ export async function sessionLoadContextHandler(args) {
|
|
|
581
581
|
if (!isSessionLoadContextArgs(args)) {
|
|
582
582
|
throw new Error("Invalid arguments for session_load_context");
|
|
583
583
|
}
|
|
584
|
-
|
|
584
|
+
// T3 fix: only reset metrics at true session start (totalCalls==0), not on GATE 5 mid-session
|
|
585
|
+
// reloads. Resetting on every call wipes accumulated delegation metrics right before
|
|
586
|
+
// session_save_ledger renders them, making the 📊 block show near-zero after any reload.
|
|
587
|
+
if (getInferenceSnapshot().totalCalls === 0) {
|
|
588
|
+
resetInferenceMetrics();
|
|
589
|
+
}
|
|
585
590
|
const { project, level = "standard", role } = args;
|
|
586
|
-
|
|
587
|
-
|
|
591
|
+
// T6 fix: explicit Number() coercion prevents string "2000" from later concatenating instead of adding
|
|
592
|
+
const _maxTokensArg = Number(args.max_tokens);
|
|
593
|
+
const _maxTokensSetting = parseInt(await getSetting("max_tokens", "0"), 10);
|
|
594
|
+
const maxTokens = (_maxTokensArg > 0 ? _maxTokensArg : undefined) ?? (_maxTokensSetting > 0 ? _maxTokensSetting : undefined);
|
|
588
595
|
const agentName = await getSetting("agent_name", "");
|
|
589
596
|
const validLevels = ["quick", "standard", "deep"];
|
|
590
597
|
if (!validLevels.includes(level)) {
|
|
@@ -600,12 +607,30 @@ export async function sessionLoadContextHandler(args) {
|
|
|
600
607
|
const storage = await getStorage();
|
|
601
608
|
const effectiveRole = role || await getSetting("default_role", "") || undefined;
|
|
602
609
|
const data = await storage.loadContext(project, level, PRISM_USER_ID, effectiveRole); // v3.0: role with dashboard fallback
|
|
610
|
+
// F4 fix: inject protected skills even for fresh projects.
|
|
611
|
+
// Previously this returned before skill injection, leaving new projects with zero
|
|
612
|
+
// behavioral guardrails for the entire session. Now protected skills always load.
|
|
603
613
|
if (!data) {
|
|
614
|
+
let freshSkillBlock = "";
|
|
615
|
+
try {
|
|
616
|
+
const { resolveSkillsForProject: resolveForFresh } = await import("./skillRouting.js");
|
|
617
|
+
const freshResolved = await resolveForFresh(project);
|
|
618
|
+
for (const entry of freshResolved.skills.filter(e => e.protected)) {
|
|
619
|
+
const content = await getSetting(`skill:${entry.name}`, "");
|
|
620
|
+
if (!content?.trim())
|
|
621
|
+
continue;
|
|
622
|
+
freshSkillBlock += `\n\n[📜 SKILL: ${entry.name}]\n${content.trim()}`;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
debugLog(`[session_load_context] Fresh project skill injection failed — continuing without`);
|
|
627
|
+
}
|
|
604
628
|
return {
|
|
605
629
|
content: [{
|
|
606
630
|
type: "text",
|
|
607
631
|
text: `No session context found for project "${project}" at level ${level}.\n` +
|
|
608
|
-
`This project has no previous session history. Starting fresh
|
|
632
|
+
`This project has no previous session history. Starting fresh.` +
|
|
633
|
+
freshSkillBlock,
|
|
609
634
|
}],
|
|
610
635
|
isError: false,
|
|
611
636
|
};
|
|
@@ -855,6 +880,10 @@ export async function sessionLoadContextHandler(args) {
|
|
|
855
880
|
const resolved = await resolveSkillsForProject(project);
|
|
856
881
|
const sortedSkills = resolved.skills;
|
|
857
882
|
const userLocalPolicy = resolved.user_local;
|
|
883
|
+
// F5: surface offline routing to the agent so it knows project/keyword skills may be missing
|
|
884
|
+
if (resolved.isOffline) {
|
|
885
|
+
skillBlock += `\n\n[⚠️ OFFLINE ROUTING: synalux.ai unreachable — using stale or fallback routing table. Project-specific and keyword-triggered skills may be missing. Universal protected skills still load.]`;
|
|
886
|
+
}
|
|
858
887
|
let synaluxContent = {};
|
|
859
888
|
if (SYNALUX_CONFIGURED && storage && typeof storage.fetchSkillContent === "function") {
|
|
860
889
|
const missing = sortedSkills.map(s => s.name).filter(n => !loadedSkills.includes(n));
|
|
@@ -994,12 +1023,8 @@ export async function sessionLoadContextHandler(args) {
|
|
|
994
1023
|
behavWarnings.map(w => `- ${w.summary} (importance: ${w.importance})`).join("\n");
|
|
995
1024
|
behavBlock = [...rawBlock].slice(0, 2000).join('');
|
|
996
1025
|
}
|
|
997
|
-
let responseText = `${MEMORY_BOUNDARY_PREFIX}📋 Session context for "${project}" (${level}):\n\n${formattedContext.trim()}${splitBrainWarning}${driftReport}${briefingBlock}${sdmRecallBlock}${greetingBlock}${visualMemoryBlock}${behavBlock}${skillBlock}${versionNote}`;
|
|
998
1026
|
// ─── v9.4.7: ABA Precision Protocol (foundational) ────────
|
|
999
|
-
|
|
1000
|
-
// under these behavioral rules. Never truncated (placed before
|
|
1001
|
-
// token budget check).
|
|
1002
|
-
responseText += `\n\n[🧠 ABA PRECISION PROTOCOL]\n` +
|
|
1027
|
+
const abaProtocol = `\n\n[🧠 ABA PRECISION PROTOCOL]\n` +
|
|
1003
1028
|
`Rule 1 — Observable Goals: Every task must have a measurable, verifiable outcome. State the specific result.\n` +
|
|
1004
1029
|
`Rule 2 — Precise Execution: One step at a time. Verify each step. If it fails → STOP → fix → verify → then continue.\n` +
|
|
1005
1030
|
`Rule 3 — No Reinforcement of Errors: Never repeat the same mistake twice. When the user says something is wrong, read the actual code/data FIRST before forming an opinion.\n` +
|
|
@@ -1007,15 +1032,57 @@ export async function sessionLoadContextHandler(args) {
|
|
|
1007
1032
|
`Rule 5 — Fix Without Asking: When you see a bug, fix it immediately. Do NOT ask "would you like me to fix that?" — just fix it.\n` +
|
|
1008
1033
|
`Rule 6 — Action Intent: When user says "fix/run/open/deploy", they want ACTION not a tutorial. Ask for specific info needed in 1-2 sentences, or act directly.\n` +
|
|
1009
1034
|
`Rule 7 — Tool Redirect: When user asks to "open browser"/"run terminal"/"git push" — output ONLY the URL or command. No follow-up. No explanations. Example: "open browser" → "https://synalux.ai/dashboard"`;
|
|
1010
|
-
//
|
|
1011
|
-
//
|
|
1035
|
+
// T5 structural truncation: assemble sections in priority order so budget truncation
|
|
1036
|
+
// drops the LEAST critical sections first (session history), never skills.
|
|
1037
|
+
// Priority high→low: ABA | skills | behavioral warnings | version | drift | briefing | SDM | history
|
|
1038
|
+
// The header + critical rules are always at the top; history is appended last.
|
|
1039
|
+
const criticalPrefix = `${MEMORY_BOUNDARY_PREFIX}📋 Session context for "${project}" (${level}):\n\n` +
|
|
1040
|
+
abaProtocol +
|
|
1041
|
+
behavBlock +
|
|
1042
|
+
skillBlock +
|
|
1043
|
+
versionNote +
|
|
1044
|
+
greetingBlock +
|
|
1045
|
+
splitBrainWarning;
|
|
1046
|
+
const lowerPriority = driftReport +
|
|
1047
|
+
briefingBlock +
|
|
1048
|
+
sdmRecallBlock +
|
|
1049
|
+
visualMemoryBlock;
|
|
1050
|
+
const historySection = formattedContext.trim()
|
|
1051
|
+
? `\n\n---\n\n📋 Historical Context:\n${formattedContext.trim()}`
|
|
1052
|
+
: "";
|
|
1053
|
+
// ─── v4.0: Token Budget Truncation (T5 structural version) ──
|
|
1054
|
+
// Drops low-priority sections whole (no mid-slice) before falling back
|
|
1055
|
+
// to trimming the history block. Protected sections (skills, ABA) always survive.
|
|
1056
|
+
// T1: use ~3.5 chars/token (better for emoji+code payload) instead of flat 4.
|
|
1012
1057
|
if (maxTokens && maxTokens > 0) {
|
|
1013
|
-
const maxChars = maxTokens *
|
|
1058
|
+
const maxChars = Math.floor(maxTokens * 3.5);
|
|
1059
|
+
const droppedSections = [];
|
|
1060
|
+
let responseText = criticalPrefix + lowerPriority + historySection;
|
|
1014
1061
|
if (responseText.length > maxChars) {
|
|
1015
|
-
|
|
1016
|
-
|
|
1062
|
+
// Drop low-priority sections first (SDM recall, briefing, drift)
|
|
1063
|
+
responseText = criticalPrefix + historySection;
|
|
1064
|
+
droppedSections.push("briefing", "drift-report", "sdm-recall");
|
|
1065
|
+
debugLog(`[session_load_context] T5: dropped low-priority sections (${droppedSections.join(", ")})`);
|
|
1066
|
+
if (responseText.length > maxChars) {
|
|
1067
|
+
// History too long — trim it, preserving all critical sections
|
|
1068
|
+
const histAllowed = Math.max(0, maxChars - criticalPrefix.length - 200);
|
|
1069
|
+
const trimmedHistory = histAllowed > 0
|
|
1070
|
+
? `\n\n---\n\n📋 Historical Context (trimmed):\n${formattedContext.trim().slice(0, histAllowed)}…`
|
|
1071
|
+
: "";
|
|
1072
|
+
droppedSections.push("partial-history");
|
|
1073
|
+
responseText = criticalPrefix + trimmedHistory;
|
|
1074
|
+
debugLog(`[session_load_context] T5: trimmed history to ${histAllowed} chars`);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
if (droppedSections.length > 0) {
|
|
1078
|
+
responseText += `\n\n[ℹ️ Sections omitted to fit token budget (${maxTokens} tokens): ${droppedSections.join(", ")}. Skills and behavioral rules were preserved.]`;
|
|
1017
1079
|
}
|
|
1080
|
+
return {
|
|
1081
|
+
content: [{ type: "text", text: responseText + MEMORY_BOUNDARY_SUFFIX }],
|
|
1082
|
+
isError: false,
|
|
1083
|
+
};
|
|
1018
1084
|
}
|
|
1085
|
+
let responseText = criticalPrefix + lowerPriority + historySection;
|
|
1019
1086
|
return {
|
|
1020
1087
|
content: [{ type: "text", text: responseText + MEMORY_BOUNDARY_SUFFIX }],
|
|
1021
1088
|
isError: false,
|