prism-mcp-server 20.2.6 → 20.2.7
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 +82 -104
- package/dist/session/sessionContext.js +113 -15
- package/dist/storage/configStorage.js +100 -0
- package/dist/tools/graphHandlers.js +5 -4
- package/dist/tools/handlers.js +2 -2
- package/dist/tools/ledgerHandlers.js +12 -16
- package/dist/tools/prismInferHandler.js +81 -7
- package/dist/tools/queryMemoryNaturalHandler.js +514 -0
- package/dist/tools/sessionMemoryDefinitions.js +23 -5
- package/dist/tools/v12Handlers.js +2 -46
- package/dist/utils/braveApi.js +31 -48
- package/dist/utils/codingQualityPolicy.js +400 -0
- package/dist/utils/layer1.js +63 -4
- package/dist/utils/nlQuery.js +6 -33
- package/dist/utils/qualityGate.js +16 -2
- package/dist/utils/synaluxSearch.js +12 -8
- package/package.json +3 -2
- package/dist/boundaries/__tests__/boundaries.test.js +0 -46
- package/dist/session/__tests__/sessionContext.test.js +0 -134
- package/dist/tools/__tests__/ingestHandler.test.js +0 -323
- package/dist/tools/__tests__/layer1Integration.test.js +0 -590
- package/dist/tools/__tests__/ledgerHandlers.test.js +0 -1281
|
@@ -275,8 +275,8 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
275
275
|
// silently wrong write. Does NOT gate safety — prism_infer handles that.
|
|
276
276
|
let _saveLedgerGateWarning;
|
|
277
277
|
{
|
|
278
|
-
const {
|
|
279
|
-
const gate =
|
|
278
|
+
const { requireContextLoadedForProject } = await import("../session/sessionContext.js");
|
|
279
|
+
const gate = await requireContextLoadedForProject(args.conversation_id, args.project);
|
|
280
280
|
if (gate !== null && gate.blocked) {
|
|
281
281
|
return { content: [{ type: "text", text: gate.error }], isError: true };
|
|
282
282
|
}
|
|
@@ -452,8 +452,8 @@ export async function sessionSaveHandoffHandler(args, server) {
|
|
|
452
452
|
}
|
|
453
453
|
let _saveHandoffGateWarning;
|
|
454
454
|
{
|
|
455
|
-
const {
|
|
456
|
-
const gate =
|
|
455
|
+
const { requireContextLoadedForProject } = await import("../session/sessionContext.js");
|
|
456
|
+
const gate = await requireContextLoadedForProject(args.conversation_id, args.project);
|
|
457
457
|
if (gate !== null && gate.blocked) {
|
|
458
458
|
return { content: [{ type: "text", text: gate.error }], isError: true };
|
|
459
459
|
}
|
|
@@ -885,10 +885,9 @@ export async function sessionLoadContextHandler(args, options = {}) {
|
|
|
885
885
|
}
|
|
886
886
|
}
|
|
887
887
|
if (convId) {
|
|
888
|
-
const {
|
|
888
|
+
const { registerContextLoaded } = await import("../session/sessionContext.js");
|
|
889
889
|
const { BOUNDARIES_VERSION } = await import("../boundaries/boundaries.js");
|
|
890
|
-
|
|
891
|
-
noteDriftSessionStart(convId);
|
|
890
|
+
await registerContextLoaded(convId, project, BOUNDARIES_VERSION);
|
|
892
891
|
}
|
|
893
892
|
const freshText = `No session context found for project "${project}" at level ${level}.\n` +
|
|
894
893
|
`This project has no previous session history. Starting fresh.` +
|
|
@@ -1163,10 +1162,9 @@ export async function sessionLoadContextHandler(args, options = {}) {
|
|
|
1163
1162
|
}
|
|
1164
1163
|
nativeContext += `\n**Session Version:** ${version === null || version === undefined ? "None" : compact(version, 40)}\n`;
|
|
1165
1164
|
if (convId) {
|
|
1166
|
-
const {
|
|
1165
|
+
const { registerContextLoaded } = await import("../session/sessionContext.js");
|
|
1167
1166
|
const { BOUNDARIES_VERSION } = await import("../boundaries/boundaries.js");
|
|
1168
|
-
|
|
1169
|
-
noteDriftSessionStart(convId);
|
|
1167
|
+
await registerContextLoaded(convId, project, BOUNDARIES_VERSION);
|
|
1170
1168
|
}
|
|
1171
1169
|
return {
|
|
1172
1170
|
content: [{
|
|
@@ -1407,10 +1405,9 @@ export async function sessionLoadContextHandler(args, options = {}) {
|
|
|
1407
1405
|
responseText += `\n\n[ℹ️ Sections omitted to fit token budget (${maxTokens} tokens): ${droppedSections.join(", ")}. Skills and behavioral rules were preserved.]`;
|
|
1408
1406
|
}
|
|
1409
1407
|
if (convId) {
|
|
1410
|
-
const {
|
|
1408
|
+
const { registerContextLoaded } = await import("../session/sessionContext.js");
|
|
1411
1409
|
const { BOUNDARIES_VERSION } = await import("../boundaries/boundaries.js");
|
|
1412
|
-
|
|
1413
|
-
noteDriftSessionStart(convId);
|
|
1410
|
+
await registerContextLoaded(convId, project, BOUNDARIES_VERSION);
|
|
1414
1411
|
}
|
|
1415
1412
|
return {
|
|
1416
1413
|
content: [{ type: "text", text: responseText + MEMORY_BOUNDARY_SUFFIX }],
|
|
@@ -1419,10 +1416,9 @@ export async function sessionLoadContextHandler(args, options = {}) {
|
|
|
1419
1416
|
}
|
|
1420
1417
|
let responseText = criticalPrefix + lowerPriority + historySection;
|
|
1421
1418
|
if (convId) {
|
|
1422
|
-
const {
|
|
1419
|
+
const { registerContextLoaded } = await import("../session/sessionContext.js");
|
|
1423
1420
|
const { BOUNDARIES_VERSION } = await import("../boundaries/boundaries.js");
|
|
1424
|
-
|
|
1425
|
-
noteDriftSessionStart(convId);
|
|
1421
|
+
await registerContextLoaded(convId, project, BOUNDARIES_VERSION);
|
|
1426
1422
|
}
|
|
1427
1423
|
return {
|
|
1428
1424
|
content: [{ type: "text", text: responseText + MEMORY_BOUNDARY_SUFFIX }],
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 4. On local fail, if cloud_fallback=true:
|
|
13
13
|
* - exchange synalux_sk_ → JWT (cached)
|
|
14
14
|
* - POST synalux portal /api/v1/prism/inference
|
|
15
|
-
* - portal
|
|
15
|
+
* - portal serves Gemini 3.6 Flash according to the user's tier
|
|
16
16
|
* 5. Return { output, backend, model_picked, ram_free_mb, latency_ms, used_cloud }
|
|
17
17
|
*
|
|
18
18
|
* `prism_infer` is a thin client. It never calls Anthropic / OpenRouter
|
|
@@ -28,6 +28,7 @@ import { getEntitlements, clampCeiling } from "../utils/entitlements.js";
|
|
|
28
28
|
import { ddLog } from "../utils/ddLogger.js";
|
|
29
29
|
import { stripThink } from "../utils/thinkStrip.js";
|
|
30
30
|
import { passesQualityGate } from "../utils/qualityGate.js";
|
|
31
|
+
import { applyDeterministicCodingRepairs, buildCodingRepairPrompt, passesCodingQualityGate, } from "../utils/codingQualityPolicy.js";
|
|
31
32
|
import { checkInputSafety, checkOutputSafety } from "../utils/safetyGate.js";
|
|
32
33
|
import { callLayer1 as defaultCallLayer1, keywordBackstop } from "../utils/layer1.js";
|
|
33
34
|
import { recordInference, recordThinkOnlyRetry, formatInferenceMetrics, estimateTokens } from "../utils/inferenceMetrics.js";
|
|
@@ -61,6 +62,7 @@ const MEMORY_HISTORY_LIMITS = {
|
|
|
61
62
|
};
|
|
62
63
|
const FAST_TASK_COMPLEXITY_MAX = 3;
|
|
63
64
|
const BALANCED_TASK_COMPLEXITY_MAX = 6;
|
|
65
|
+
const MAX_CODING_REPAIR_ATTEMPTS = 2;
|
|
64
66
|
// ─── Tool Definition ────────────────────────────────────────────
|
|
65
67
|
export const PRISM_INFER_TOOL = {
|
|
66
68
|
name: "prism_infer",
|
|
@@ -68,7 +70,7 @@ export const PRISM_INFER_TOOL = {
|
|
|
68
70
|
"Owns model selection across 27B / 9B / 4B / 2B using an explicit `model_ceiling` or " +
|
|
69
71
|
"the caller's `task_complexity`, then validates loaded memory size, model context, " +
|
|
70
72
|
"entitlements, installed models, and free RAM at call time. " +
|
|
71
|
-
"Falls through to the
|
|
73
|
+
"Falls through to the Synalux portal Gemini 3.6 Flash cloud fallback " +
|
|
72
74
|
"only when local is unviable AND `cloud_fallback=true`. " +
|
|
73
75
|
"When `project` is provided, loads the dashboard-configured quick/standard/deep handoff and bounded history " +
|
|
74
76
|
"as untrusted historical context for a memory-aware local worker. " +
|
|
@@ -448,8 +450,8 @@ async function callSynaluxInference(prompt, maxTokens, timeoutMs, opts) {
|
|
|
448
450
|
return { ok: false, reason: "jwt_exchange_failed" };
|
|
449
451
|
const url = `${PRISM_SYNALUX_BASE_URL}/api/v1/prism/inference`;
|
|
450
452
|
// reserved=true tells the portal this prompt was refused by local Layer-1
|
|
451
|
-
// as reserved clinical content: it must be served by
|
|
452
|
-
//
|
|
453
|
+
// as reserved clinical content: it must be served by the portal's
|
|
454
|
+
// reserved-capable cloud backend or refused — never by a local model.
|
|
453
455
|
const reqBody = JSON.stringify({ prompt, max_tokens: maxTokens, ...(opts?.reserved ? { reserved: true } : {}) });
|
|
454
456
|
try {
|
|
455
457
|
let res = await fetch(url, {
|
|
@@ -874,10 +876,82 @@ export async function runInfer(args, deps) {
|
|
|
874
876
|
result = await deps.callLocal(deps.ollamaUrl, ollamaName, args.prompt, args.system, maxTokens, temperature, timeout, false);
|
|
875
877
|
}
|
|
876
878
|
if (result.ok) {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
+
let { stripped, thinkOnly } = stripThink(result.text);
|
|
880
|
+
let output = stripped;
|
|
879
881
|
// Quality gate — all modes. Route uses mode-aware empty floor (length===0).
|
|
880
|
-
|
|
882
|
+
let gate = passesQualityGate(output, thinkOnly, result.doneReason, mode);
|
|
883
|
+
if (gate.pass && mode === "code") {
|
|
884
|
+
gate = passesCodingQualityGate(args.prompt, output);
|
|
885
|
+
}
|
|
886
|
+
// High-precision coding failures get bounded same-tier repair
|
|
887
|
+
// attempts before cloud escalation. Multiple attempts matter
|
|
888
|
+
// when syntax repair exposes a second structural defect.
|
|
889
|
+
for (let repairAttempt = 0; repairAttempt < MAX_CODING_REPAIR_ATTEMPTS; repairAttempt++) {
|
|
890
|
+
const codingGateFailure = !gate.pass &&
|
|
891
|
+
mode === "code" &&
|
|
892
|
+
(gate.reason?.startsWith("code_") === true ||
|
|
893
|
+
gate.reason?.startsWith("python_") === true);
|
|
894
|
+
if (!codingGateFailure)
|
|
895
|
+
break;
|
|
896
|
+
const failedReason = gate.reason ?? "code_quality";
|
|
897
|
+
const deterministicRepair = applyDeterministicCodingRepairs(output, failedReason);
|
|
898
|
+
if (deterministicRepair.changes.length > 0) {
|
|
899
|
+
output = deterministicRepair.output;
|
|
900
|
+
gate = passesQualityGate(output, false, result.doneReason, mode);
|
|
901
|
+
if (gate.pass) {
|
|
902
|
+
gate = passesCodingQualityGate(args.prompt, output);
|
|
903
|
+
}
|
|
904
|
+
attempts.push({
|
|
905
|
+
tier: tier.tag,
|
|
906
|
+
reason: `code_repair_deterministic:${deterministicRepair.changes.join(",")}`,
|
|
907
|
+
});
|
|
908
|
+
if (gate.pass)
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
const repair = buildCodingRepairPrompt(args.prompt, output, failedReason);
|
|
912
|
+
const repairSystem = args.system
|
|
913
|
+
? `${args.system}\n\n${repair.system}`
|
|
914
|
+
: repair.system;
|
|
915
|
+
const repairPromptTokens = estimateTokens(repair.prompt) +
|
|
916
|
+
estimateTokens(repairSystem) +
|
|
917
|
+
CTX_TEMPLATE_MARGIN;
|
|
918
|
+
if (repairPromptTokens <= tier.ctxTokens) {
|
|
919
|
+
attempts.push({ tier: tier.tag, reason: `code_repair:${failedReason}` });
|
|
920
|
+
const repaired = await deps.callLocal(deps.ollamaUrl, ollamaName, repair.prompt, repairSystem, maxTokens, 0, timeout, false);
|
|
921
|
+
if (repaired.ok) {
|
|
922
|
+
const repairedStripped = stripThink(repaired.text);
|
|
923
|
+
const repairedGenericGate = passesQualityGate(repairedStripped.stripped, repairedStripped.thinkOnly, repaired.doneReason, mode);
|
|
924
|
+
const repairedGate = repairedGenericGate.pass
|
|
925
|
+
? passesCodingQualityGate(args.prompt, repairedStripped.stripped)
|
|
926
|
+
: repairedGenericGate;
|
|
927
|
+
result = repaired;
|
|
928
|
+
stripped = repairedStripped.stripped;
|
|
929
|
+
thinkOnly = repairedStripped.thinkOnly;
|
|
930
|
+
output = stripped;
|
|
931
|
+
gate = repairedGate;
|
|
932
|
+
if (!gate.pass) {
|
|
933
|
+
attempts.push({
|
|
934
|
+
tier: tier.tag,
|
|
935
|
+
reason: `code_repair_failed:${gate.reason ?? "quality_gate"}`,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
else {
|
|
940
|
+
attempts.push({
|
|
941
|
+
tier: tier.tag,
|
|
942
|
+
reason: `code_repair_error:${repaired.reason}`,
|
|
943
|
+
});
|
|
944
|
+
break;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
attempts.push({
|
|
949
|
+
tier: tier.tag,
|
|
950
|
+
reason: "code_repair_skipped:ctx_insufficient",
|
|
951
|
+
});
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
881
955
|
if (!gate.pass && allowCloud) {
|
|
882
956
|
debugLog(`[prism_infer] quality gate FAIL (${gate.reason}) — escalating to cloud`);
|
|
883
957
|
attempts.push({ tier: tier.tag, reason: `quality_gate:${gate.reason}` });
|