prism-mcp-server 8.0.2 → 9.0.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/README.md +80 -9
- package/dist/cli.js +0 -0
- package/dist/config.js +42 -5
- package/dist/dashboard/server.js +118 -0
- package/dist/dashboard/ui.js +211 -1
- package/dist/memory/cognitiveBudget.js +224 -0
- package/dist/memory/surprisalGate.js +119 -0
- package/dist/memory/synapseEngine.js +28 -2
- package/dist/memory/valenceEngine.js +234 -0
- package/dist/scholar/webScholar.js +7 -6
- package/dist/server.js +60 -19
- package/dist/storage/index.js +53 -9
- package/dist/storage/sqlite.js +103 -11
- package/dist/storage/supabase.js +74 -5
- package/dist/storage/supabaseMigrations.js +30 -0
- package/dist/sync/factory.js +5 -1
- package/dist/tools/graphHandlers.js +24 -2
- package/dist/tools/ledgerHandlers.js +122 -4
- package/dist/utils/universalImporter.js +0 -0
- package/package.json +13 -3
- package/dist/dashboard/ui.tmp.js +0 -3475
- package/dist/test-cli.js +0 -18
- package/dist/tools/sessionMemoryHandlers.js +0 -2633
- package/dist/utils/embeddingApi.js +0 -104
- package/dist/utils/googleAi.js +0 -88
- package/dist/utils/testUniversalImporter.js +0 -10
- package/dist/verification/renameDetector.js +0 -170
package/dist/storage/sqlite.js
CHANGED
|
@@ -21,7 +21,7 @@ import * as path from "path";
|
|
|
21
21
|
import * as os from "os";
|
|
22
22
|
import { randomUUID } from "crypto";
|
|
23
23
|
import { AccessLogBuffer } from "../utils/accessLogBuffer.js";
|
|
24
|
-
import { PRISM_ACTR_BUFFER_FLUSH_MS, PRISM_SYNAPSE_ENABLED, PRISM_SYNAPSE_ITERATIONS, PRISM_SYNAPSE_SPREAD_FACTOR, PRISM_SYNAPSE_LATERAL_INHIBITION, PRISM_SYNAPSE_SOFT_CAP, } from "../config.js";
|
|
24
|
+
import { PRISM_ACTR_BUFFER_FLUSH_MS, PRISM_SYNAPSE_ENABLED, PRISM_SYNAPSE_ITERATIONS, PRISM_SYNAPSE_SPREAD_FACTOR, PRISM_SYNAPSE_LATERAL_INHIBITION, PRISM_SYNAPSE_SOFT_CAP, PRISM_VALENCE_ENABLED, } from "../config.js";
|
|
25
25
|
import { getSetting as cfgGet, setSetting as cfgSet, getAllSettings as cfgGetAll } from "./configStorage.js";
|
|
26
26
|
import { debugLog } from "../utils/logger.js";
|
|
27
27
|
import { SafetyController } from "../darkfactory/safetyController.js";
|
|
@@ -108,7 +108,8 @@ export class SqliteStorage {
|
|
|
108
108
|
rollup_count INTEGER DEFAULT 0,
|
|
109
109
|
archived_at TEXT DEFAULT NULL,
|
|
110
110
|
session_date TEXT DEFAULT NULL,
|
|
111
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
111
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
112
|
+
valence REAL DEFAULT NULL
|
|
112
113
|
);
|
|
113
114
|
|
|
114
115
|
-- ─── Session Handoffs (live project state, OCC-controlled) ───
|
|
@@ -123,6 +124,7 @@ export class SqliteStorage {
|
|
|
123
124
|
key_context TEXT DEFAULT NULL,
|
|
124
125
|
active_branch TEXT DEFAULT NULL,
|
|
125
126
|
version INTEGER NOT NULL DEFAULT 1,
|
|
127
|
+
cognitive_budget REAL DEFAULT NULL,
|
|
126
128
|
metadata TEXT DEFAULT '{}',
|
|
127
129
|
created_at TEXT DEFAULT (datetime('now')),
|
|
128
130
|
updated_at TEXT DEFAULT (datetime('now')),
|
|
@@ -166,7 +168,7 @@ export class SqliteStorage {
|
|
|
166
168
|
VALUES ('delete', old.rowid, old.project, old.summary, old.decisions, old.keywords);
|
|
167
169
|
END;
|
|
168
170
|
|
|
169
|
-
CREATE TRIGGER IF NOT EXISTS ledger_fts_update AFTER UPDATE ON session_ledger BEGIN
|
|
171
|
+
CREATE TRIGGER IF NOT EXISTS ledger_fts_update AFTER UPDATE OF project, summary, decisions, keywords ON session_ledger BEGIN
|
|
170
172
|
INSERT INTO ledger_fts(ledger_fts, rowid, project, summary, decisions, keywords)
|
|
171
173
|
VALUES ('delete', old.rowid, old.project, old.summary, old.decisions, old.keywords);
|
|
172
174
|
INSERT INTO ledger_fts(rowid, project, summary, decisions, keywords)
|
|
@@ -725,6 +727,36 @@ export class SqliteStorage {
|
|
|
725
727
|
// Non-fatal: some older libSQL versions may not support all integrity_check modes.
|
|
726
728
|
debugLog(`[SqliteStorage] v6.1: integrity_check skipped (${e.message})`);
|
|
727
729
|
}
|
|
730
|
+
// ─── v9.0 Migration: Affect-Tagged Memory (Valence) ───────────
|
|
731
|
+
//
|
|
732
|
+
// Adds a REAL valence column to session_ledger for affect-tagged memory.
|
|
733
|
+
// Valence is auto-derived from event_type at save time.
|
|
734
|
+
// Uses Affective Salience: |valence| boosts retrieval, sign drives UX warnings.
|
|
735
|
+
// For fresh DBs, valence is already in the CREATE TABLE. This ALTER TABLE
|
|
736
|
+
// is purely for existing production databases upgrading from v8.x → v9.0.
|
|
737
|
+
try {
|
|
738
|
+
await this.db.execute(`ALTER TABLE session_ledger ADD COLUMN valence REAL DEFAULT NULL`);
|
|
739
|
+
debugLog("[SqliteStorage] v9.0 migration: added valence column");
|
|
740
|
+
}
|
|
741
|
+
catch (e) {
|
|
742
|
+
if (!e.message?.includes("duplicate column name"))
|
|
743
|
+
throw e;
|
|
744
|
+
}
|
|
745
|
+
// Partial index for valence-aware queries (schema parity with Supabase)
|
|
746
|
+
await this.db.execute(`CREATE INDEX IF NOT EXISTS idx_ledger_valence ON session_ledger(valence) WHERE valence IS NOT NULL`);
|
|
747
|
+
// ─── v9.0 Migration: Persistent Cognitive Budget ──────────────
|
|
748
|
+
//
|
|
749
|
+
// Budget belongs to the PROJECT (stored in session_handoffs), not
|
|
750
|
+
// the ephemeral session, to prevent the "Reset Exploit" where an
|
|
751
|
+
// agent escapes budget exhaustion by simply starting a new session.
|
|
752
|
+
try {
|
|
753
|
+
await this.db.execute(`ALTER TABLE session_handoffs ADD COLUMN cognitive_budget REAL DEFAULT NULL`);
|
|
754
|
+
debugLog("[SqliteStorage] v9.0 migration: added cognitive_budget column");
|
|
755
|
+
}
|
|
756
|
+
catch (e) {
|
|
757
|
+
if (!e.message?.includes("duplicate column name"))
|
|
758
|
+
throw e;
|
|
759
|
+
}
|
|
728
760
|
}
|
|
729
761
|
// ─── PostgREST Filter Parser ───────────────────────────────
|
|
730
762
|
//
|
|
@@ -896,10 +928,10 @@ export class SqliteStorage {
|
|
|
896
928
|
sql: `INSERT INTO session_ledger
|
|
897
929
|
(id, project, conversation_id, user_id, role, summary, todos, files_changed,
|
|
898
930
|
decisions, keywords, is_rollup, rollup_count, title, agent_name,
|
|
899
|
-
event_type, confidence_score, importance,
|
|
931
|
+
event_type, confidence_score, importance, valence,
|
|
900
932
|
embedding_compressed, embedding_format, embedding_turbo_radius,
|
|
901
933
|
created_at, session_date)
|
|
902
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
934
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
903
935
|
args: [
|
|
904
936
|
id,
|
|
905
937
|
entry.project,
|
|
@@ -918,6 +950,7 @@ export class SqliteStorage {
|
|
|
918
950
|
entry.event_type || "session", // v4.0: default to 'session'
|
|
919
951
|
entry.confidence_score ?? null, // v4.0: nullable
|
|
920
952
|
entry.importance || 0, // v4.0: default to 0
|
|
953
|
+
entry.valence ?? null, // v9.0: affect-tagged memory
|
|
921
954
|
entry.embedding_compressed || null, // v5.0: TurboQuant
|
|
922
955
|
entry.embedding_format || null, // v5.0: turbo3/turbo4/float32
|
|
923
956
|
entry.embedding_turbo_radius ?? null, // v5.0: original vector magnitude
|
|
@@ -950,7 +983,7 @@ export class SqliteStorage {
|
|
|
950
983
|
'embedding', 'embedding_compressed', 'embedding_format', 'embedding_turbo_radius',
|
|
951
984
|
'archived_at', 'deleted_at', 'deleted_reason', 'is_rollup', 'rollup_count',
|
|
952
985
|
'importance', 'last_accessed_at', 'keywords', 'todos', 'files_changed', 'decisions',
|
|
953
|
-
'summary', 'confidence_score', 'event_type', 'role',
|
|
986
|
+
'summary', 'confidence_score', 'event_type', 'role', 'valence',
|
|
954
987
|
]);
|
|
955
988
|
const sets = [];
|
|
956
989
|
const args = [];
|
|
@@ -978,6 +1011,18 @@ export class SqliteStorage {
|
|
|
978
1011
|
args,
|
|
979
1012
|
});
|
|
980
1013
|
}
|
|
1014
|
+
// ─── v9.0: Atomic delta-based budget persistence ────────────────
|
|
1015
|
+
// Uses COALESCE + delta to prevent concurrency race conditions:
|
|
1016
|
+
// Agent A loads budget=2000, spends 100 → delta=-100
|
|
1017
|
+
// Agent B loads budget=2000, spends 50 → delta=-50
|
|
1018
|
+
// With absolute writes: Agent B overwrites Agent A's spend (budget=1950)
|
|
1019
|
+
// With delta: Both apply correctly → budget=2000 + (-100) + (-50) = 1850
|
|
1020
|
+
async patchHandoffBudgetDelta(project, userId, budgetDelta) {
|
|
1021
|
+
await this.db.execute({
|
|
1022
|
+
sql: `UPDATE session_handoffs SET cognitive_budget = MAX(0, COALESCE(cognitive_budget, 2000) + ?) WHERE project = ? AND user_id = ?`,
|
|
1023
|
+
args: [budgetDelta, project, userId],
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
981
1026
|
async getLedgerEntries(params) {
|
|
982
1027
|
const { ids, ...restParams } = params;
|
|
983
1028
|
const { where, args, select, order, limit } = this.parsePostgRESTFilters(restParams);
|
|
@@ -1492,6 +1537,7 @@ export class SqliteStorage {
|
|
|
1492
1537
|
const sql = `
|
|
1493
1538
|
SELECT l.id, l.project, l.summary, l.decisions, l.files_changed,
|
|
1494
1539
|
l.session_date, l.created_at, l.is_rollup, l.importance, l.last_accessed_at,
|
|
1540
|
+
l.valence,
|
|
1495
1541
|
(1 - vector_distance_cos(l.embedding, vector(?))) AS similarity
|
|
1496
1542
|
FROM session_ledger l
|
|
1497
1543
|
WHERE ${conditions.join(" AND ")}
|
|
@@ -1513,6 +1559,7 @@ export class SqliteStorage {
|
|
|
1513
1559
|
is_rollup: Boolean(r.is_rollup),
|
|
1514
1560
|
importance: r.importance ?? 0,
|
|
1515
1561
|
last_accessed_at: r.last_accessed_at || null,
|
|
1562
|
+
valence: r.valence ?? null, // v9.0: affect-tagged memory
|
|
1516
1563
|
}));
|
|
1517
1564
|
if (params.activation?.enabled) {
|
|
1518
1565
|
return this.applySynapse(baseResults, params.activation, params.userId);
|
|
@@ -1634,7 +1681,7 @@ export class SqliteStorage {
|
|
|
1634
1681
|
const anchorMap = new Map();
|
|
1635
1682
|
for (const a of anchors)
|
|
1636
1683
|
anchorMap.set(a.id, a.similarity ?? 1.0);
|
|
1637
|
-
const { results, telemetry } = await propagateActivation(anchorMap, async (nodeIds) => this.getLinksForNodes(nodeIds, userId), {
|
|
1684
|
+
const { results, telemetry, flowWeights } = await propagateActivation(anchorMap, async (nodeIds) => this.getLinksForNodes(nodeIds, userId), {
|
|
1638
1685
|
iterations: options.iterations ?? PRISM_SYNAPSE_ITERATIONS,
|
|
1639
1686
|
spreadFactor: options.spreadFactor ?? PRISM_SYNAPSE_SPREAD_FACTOR,
|
|
1640
1687
|
lateralInhibition: options.lateralInhibition ?? PRISM_SYNAPSE_LATERAL_INHIBITION,
|
|
@@ -1649,7 +1696,7 @@ export class SqliteStorage {
|
|
|
1649
1696
|
if (missingIds.length > 0) {
|
|
1650
1697
|
const placeholders = missingIds.map(() => '?').join(',');
|
|
1651
1698
|
const missingQuery = `
|
|
1652
|
-
SELECT id, project, summary, session_date, decisions, files_changed, keywords, is_rollup, importance, last_accessed_at
|
|
1699
|
+
SELECT id, project, summary, session_date, decisions, files_changed, keywords, is_rollup, importance, last_accessed_at, valence
|
|
1653
1700
|
FROM session_ledger
|
|
1654
1701
|
WHERE id IN (${placeholders}) AND deleted_at IS NULL AND user_id = ?
|
|
1655
1702
|
`;
|
|
@@ -1666,9 +1713,43 @@ export class SqliteStorage {
|
|
|
1666
1713
|
importance: Number(row.importance) || 0,
|
|
1667
1714
|
last_accessed_at: row.last_accessed_at || null,
|
|
1668
1715
|
similarity: 0.0,
|
|
1716
|
+
valence: row.valence ?? null,
|
|
1669
1717
|
});
|
|
1670
1718
|
}
|
|
1671
1719
|
}
|
|
1720
|
+
// ── v9.0: Valence Propagation ────────────────────────────────
|
|
1721
|
+
// After activation propagation, compute propagated valence for
|
|
1722
|
+
// discovered nodes using energy-weighted averaging from source flows.
|
|
1723
|
+
let propagatedValenceMap = null;
|
|
1724
|
+
if (PRISM_VALENCE_ENABLED) {
|
|
1725
|
+
try {
|
|
1726
|
+
const { propagateValence } = await import("../memory/valenceEngine.js");
|
|
1727
|
+
// Build valence lookup from all known nodes
|
|
1728
|
+
const valenceLookup = new Map();
|
|
1729
|
+
for (const [id, node] of fullNodeMap) {
|
|
1730
|
+
if (node.valence != null)
|
|
1731
|
+
valenceLookup.set(id, node.valence);
|
|
1732
|
+
}
|
|
1733
|
+
// For missing valence values, bulk-fetch from DB
|
|
1734
|
+
const missingValenceIds = finalIds.filter(id => !valenceLookup.has(id));
|
|
1735
|
+
if (missingValenceIds.length > 0) {
|
|
1736
|
+
const vPlaceholders = missingValenceIds.map(() => '?').join(',');
|
|
1737
|
+
const vQuery = `SELECT id, valence FROM session_ledger WHERE id IN (${vPlaceholders}) AND valence IS NOT NULL`;
|
|
1738
|
+
const vRes = await this.db.execute({ sql: vQuery, args: missingValenceIds });
|
|
1739
|
+
for (const row of vRes.rows) {
|
|
1740
|
+
valenceLookup.set(row.id, row.valence);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
propagatedValenceMap = propagateValence(results, valenceLookup, flowWeights);
|
|
1744
|
+
debugLog(`[SqliteStorage] v9.0 valence propagation: ${propagatedValenceMap.size} nodes processed`);
|
|
1745
|
+
}
|
|
1746
|
+
catch (valErr) {
|
|
1747
|
+
debugLog(`[SqliteStorage] v9.0 valence propagation failed (non-fatal): ${valErr instanceof Error ? valErr.message : String(valErr)}`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const { computeHybridScoreWithValence } = PRISM_VALENCE_ENABLED
|
|
1751
|
+
? await import("../memory/valenceEngine.js")
|
|
1752
|
+
: { computeHybridScoreWithValence: null };
|
|
1672
1753
|
const finalResults = [];
|
|
1673
1754
|
for (const r of results) {
|
|
1674
1755
|
if (fullNodeMap.has(r.id)) {
|
|
@@ -1677,8 +1758,19 @@ export class SqliteStorage {
|
|
|
1677
1758
|
node.activationScore = normEnergy;
|
|
1678
1759
|
node.rawActivationEnergy = r.activationEnergy;
|
|
1679
1760
|
node.isDiscovered = r.isDiscovered;
|
|
1680
|
-
//
|
|
1681
|
-
|
|
1761
|
+
// v9.0: Attach propagated valence (overrides raw for discovered nodes)
|
|
1762
|
+
if (propagatedValenceMap?.has(r.id)) {
|
|
1763
|
+
node.valence = propagatedValenceMap.get(r.id);
|
|
1764
|
+
}
|
|
1765
|
+
// v9.0: Hybrid blend with valence salience:
|
|
1766
|
+
// 0.65 × similarity + 0.25 × activation + 0.10 × |valence|
|
|
1767
|
+
// Falls back to 70/30 if valence is disabled.
|
|
1768
|
+
if (computeHybridScoreWithValence) {
|
|
1769
|
+
node.hybridScore = computeHybridScoreWithValence(node.similarity, normEnergy, node.valence ?? null);
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
node.hybridScore = (node.similarity * 0.7) + (normEnergy * 0.3);
|
|
1773
|
+
}
|
|
1682
1774
|
finalResults.push(node);
|
|
1683
1775
|
}
|
|
1684
1776
|
}
|
|
@@ -2847,7 +2939,7 @@ export class SqliteStorage {
|
|
|
2847
2939
|
WITH input_kw(kw) AS (VALUES ${placeholders})
|
|
2848
2940
|
SELECT sl.id, COUNT(DISTINCT ik.kw) AS shared_count
|
|
2849
2941
|
FROM session_ledger sl,
|
|
2850
|
-
json_each(sl.keywords) AS je,
|
|
2942
|
+
json_each(COALESCE(sl.keywords, '[]')) AS je,
|
|
2851
2943
|
input_kw ik
|
|
2852
2944
|
WHERE sl.user_id = ?
|
|
2853
2945
|
AND sl.project = ?
|
package/dist/storage/supabase.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import { supabasePost, supabaseGet, supabaseRpc, supabasePatch, supabaseDelete, } from "../utils/supabaseApi.js";
|
|
16
16
|
import { gzipSync, gunzipSync } from "node:zlib";
|
|
17
17
|
import { debugLog } from "../utils/logger.js";
|
|
18
|
-
import { PRISM_USER_ID, PRISM_SYNAPSE_ENABLED, PRISM_SYNAPSE_ITERATIONS, PRISM_SYNAPSE_SPREAD_FACTOR, PRISM_SYNAPSE_LATERAL_INHIBITION, PRISM_SYNAPSE_SOFT_CAP, } from "../config.js";
|
|
18
|
+
import { PRISM_USER_ID, PRISM_SYNAPSE_ENABLED, PRISM_SYNAPSE_ITERATIONS, PRISM_SYNAPSE_SPREAD_FACTOR, PRISM_SYNAPSE_LATERAL_INHIBITION, PRISM_SYNAPSE_SOFT_CAP, PRISM_VALENCE_ENABLED, } from "../config.js";
|
|
19
19
|
import { getSetting as cfgGet, setSetting as cfgSet, getAllSettings as cfgGetAll } from "./configStorage.js";
|
|
20
20
|
import { runAutoMigrations } from "./supabaseMigrations.js";
|
|
21
21
|
import { SafetyController } from "../darkfactory/safetyController.js";
|
|
@@ -60,12 +60,48 @@ export class SupabaseStorage {
|
|
|
60
60
|
...(entry.embedding_compressed !== undefined && { embedding_compressed: entry.embedding_compressed }),
|
|
61
61
|
...(entry.embedding_format !== undefined && { embedding_format: entry.embedding_format }),
|
|
62
62
|
...(entry.embedding_turbo_radius !== undefined && { embedding_turbo_radius: entry.embedding_turbo_radius }),
|
|
63
|
+
// v9.0: Affect-Tagged Memory
|
|
64
|
+
...(entry.valence !== undefined && entry.valence !== null && { valence: entry.valence }),
|
|
63
65
|
};
|
|
64
66
|
return supabasePost("session_ledger", record);
|
|
65
67
|
}
|
|
66
68
|
async patchLedger(id, data) {
|
|
67
69
|
await supabasePatch("session_ledger", data, { id: `eq.${id}` });
|
|
68
70
|
}
|
|
71
|
+
// v9.0: Atomic delta-based budget persistence
|
|
72
|
+
// Supabase REST PATCH can't do arithmetic, so we use an RPC function.
|
|
73
|
+
// Falls back to read-modify-write if the RPC doesn't exist.
|
|
74
|
+
async patchHandoffBudgetDelta(project, userId, budgetDelta) {
|
|
75
|
+
try {
|
|
76
|
+
// Preferred: atomic delta via SQL RPC
|
|
77
|
+
await supabaseRpc("patch_budget_delta", {
|
|
78
|
+
p_project: project,
|
|
79
|
+
p_user_id: userId,
|
|
80
|
+
p_delta: budgetDelta,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (rpcErr) {
|
|
84
|
+
// Fallback: read-modify-write (non-atomic, acceptable for Supabase)
|
|
85
|
+
debugLog(`[SupabaseStorage] patch_budget_delta RPC unavailable, using fallback: ${rpcErr instanceof Error ? rpcErr.message : String(rpcErr)}`);
|
|
86
|
+
try {
|
|
87
|
+
const data = await supabaseGet("session_handoffs", {
|
|
88
|
+
project: `eq.${project}`,
|
|
89
|
+
user_id: `eq.${userId}`,
|
|
90
|
+
select: "cognitive_budget",
|
|
91
|
+
limit: "1",
|
|
92
|
+
});
|
|
93
|
+
const rows = Array.isArray(data) ? data : [];
|
|
94
|
+
const currentBudget = rows.length > 0 ? rows[0].cognitive_budget ?? 2000 : 2000;
|
|
95
|
+
await supabasePatch("session_handoffs", { cognitive_budget: Math.max(0, currentBudget + budgetDelta) }, {
|
|
96
|
+
project: `eq.${project}`,
|
|
97
|
+
user_id: `eq.${userId}`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (fallbackErr) {
|
|
101
|
+
debugLog(`[SupabaseStorage] Budget delta fallback also failed: ${fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr)}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
69
105
|
async getLedgerEntries(params) {
|
|
70
106
|
const { ids, ...restParams } = params;
|
|
71
107
|
// Construct PostgREST 'in.' payload for array of ids if present
|
|
@@ -354,7 +390,7 @@ export class SupabaseStorage {
|
|
|
354
390
|
const anchorMap = new Map();
|
|
355
391
|
for (const a of anchors)
|
|
356
392
|
anchorMap.set(a.id, a.similarity ?? 1.0);
|
|
357
|
-
const { results, telemetry } = await propagateActivation(anchorMap, async (nodeIds) => this.getLinksForNodes(nodeIds, userId), {
|
|
393
|
+
const { results, telemetry, flowWeights } = await propagateActivation(anchorMap, async (nodeIds) => this.getLinksForNodes(nodeIds, userId), {
|
|
358
394
|
iterations: options.iterations ?? PRISM_SYNAPSE_ITERATIONS,
|
|
359
395
|
spreadFactor: options.spreadFactor ?? PRISM_SYNAPSE_SPREAD_FACTOR,
|
|
360
396
|
lateralInhibition: options.lateralInhibition ?? PRISM_SYNAPSE_LATERAL_INHIBITION,
|
|
@@ -374,7 +410,7 @@ export class SupabaseStorage {
|
|
|
374
410
|
id: `in.(${missingIds.join(",")})`,
|
|
375
411
|
user_id: `eq.${userId}`,
|
|
376
412
|
deleted_at: "is.null",
|
|
377
|
-
select: "id,project,summary,session_date,decisions,files_changed,is_rollup,importance,last_accessed_at",
|
|
413
|
+
select: "id,project,summary,session_date,decisions,files_changed,is_rollup,importance,last_accessed_at,valence",
|
|
378
414
|
});
|
|
379
415
|
for (const row of (Array.isArray(rows) ? rows : [])) {
|
|
380
416
|
fullNodeMap.set(row.id, {
|
|
@@ -387,6 +423,7 @@ export class SupabaseStorage {
|
|
|
387
423
|
is_rollup: Boolean(row.is_rollup),
|
|
388
424
|
importance: Number(row.importance) || 0,
|
|
389
425
|
last_accessed_at: row.last_accessed_at || null,
|
|
426
|
+
valence: row.valence != null ? Number(row.valence) : undefined,
|
|
390
427
|
similarity: 0.0,
|
|
391
428
|
});
|
|
392
429
|
}
|
|
@@ -395,6 +432,31 @@ export class SupabaseStorage {
|
|
|
395
432
|
debugLog(`[SupabaseStorage] applySynapse: failed to fetch missing nodes: ${e instanceof Error ? e.message : String(e)}`);
|
|
396
433
|
}
|
|
397
434
|
}
|
|
435
|
+
// ─── v9.0: Valence Propagation for discovered nodes ──────────
|
|
436
|
+
// Mirrors the SQLite implementation: batch propagateValence() call
|
|
437
|
+
// over the full results array with a valence lookup map.
|
|
438
|
+
let propagatedValenceMap = null;
|
|
439
|
+
if (PRISM_VALENCE_ENABLED) {
|
|
440
|
+
try {
|
|
441
|
+
const { propagateValence } = await import("../memory/valenceEngine.js");
|
|
442
|
+
// Build valence lookup from all known nodes
|
|
443
|
+
const valenceLookup = new Map();
|
|
444
|
+
for (const [id, node] of fullNodeMap) {
|
|
445
|
+
if (node.valence != null && Number.isFinite(node.valence)) {
|
|
446
|
+
valenceLookup.set(id, node.valence);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
propagatedValenceMap = propagateValence(results, valenceLookup, flowWeights);
|
|
450
|
+
debugLog(`[SupabaseStorage] v9.0 valence propagation: ${propagatedValenceMap.size} nodes processed`);
|
|
451
|
+
}
|
|
452
|
+
catch (valErr) {
|
|
453
|
+
debugLog(`[SupabaseStorage] applySynapse: valence propagation failed (non-fatal): ${valErr instanceof Error ? valErr.message : String(valErr)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// Import hybrid scoring if valence is enabled
|
|
457
|
+
const { computeHybridScoreWithValence } = PRISM_VALENCE_ENABLED
|
|
458
|
+
? await import("../memory/valenceEngine.js")
|
|
459
|
+
: { computeHybridScoreWithValence: null };
|
|
398
460
|
// Compute hybrid scores and build final result set
|
|
399
461
|
const finalResults = [];
|
|
400
462
|
for (const r of results) {
|
|
@@ -404,8 +466,15 @@ export class SupabaseStorage {
|
|
|
404
466
|
node.activationScore = normEnergy;
|
|
405
467
|
node.rawActivationEnergy = r.activationEnergy;
|
|
406
468
|
node.isDiscovered = r.isDiscovered;
|
|
407
|
-
//
|
|
408
|
-
|
|
469
|
+
// v9.0: Three-component hybrid blend with valence salience
|
|
470
|
+
const nodeValence = propagatedValenceMap?.get(r.id) ?? node.valence;
|
|
471
|
+
if (computeHybridScoreWithValence && nodeValence != null && Number.isFinite(nodeValence)) {
|
|
472
|
+
node.valence = nodeValence;
|
|
473
|
+
node.hybridScore = computeHybridScoreWithValence(node.similarity, normEnergy, nodeValence);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
node.hybridScore = (node.similarity * 0.7) + (normEnergy * 0.3);
|
|
477
|
+
}
|
|
409
478
|
finalResults.push(node);
|
|
410
479
|
}
|
|
411
480
|
}
|
|
@@ -773,6 +773,36 @@ export const MIGRATIONS = [
|
|
|
773
773
|
CHECK (status IN ('PENDING', 'RUNNING', 'PAUSED', 'ABORTED', 'COMPLETED', 'FAILED'));
|
|
774
774
|
`
|
|
775
775
|
},
|
|
776
|
+
{
|
|
777
|
+
// ─── v9.0: Affect-Tagged Memory + Token-Economic Budget ──────────
|
|
778
|
+
//
|
|
779
|
+
// Two new columns:
|
|
780
|
+
// 1. session_ledger.valence — REAL [-1.0, +1.0], nullable
|
|
781
|
+
// Stores the affective "gut feeling" score for each memory entry.
|
|
782
|
+
// Derived deterministically from event_type at write time.
|
|
783
|
+
// Legacy entries remain NULL (neutral).
|
|
784
|
+
//
|
|
785
|
+
// 2. session_handoffs.cognitive_budget — REAL, nullable
|
|
786
|
+
// Persists the agent's current token-economic budget balance
|
|
787
|
+
// across sessions. Initialized on first spend; NULL before first use.
|
|
788
|
+
//
|
|
789
|
+
// Both are idempotent (ADD COLUMN IF NOT EXISTS) and non-breaking
|
|
790
|
+
// (nullable with no NOT NULL constraint). Existing data is untouched.
|
|
791
|
+
version: 42,
|
|
792
|
+
name: "v9_affect_tagged_memory_and_cognitive_budget",
|
|
793
|
+
sql: `
|
|
794
|
+
-- v9.0: Affect-Tagged Memory — valence column on session_ledger
|
|
795
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS valence REAL DEFAULT NULL;
|
|
796
|
+
|
|
797
|
+
-- Partial index for valence-aware retrieval (non-null valence entries)
|
|
798
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_valence
|
|
799
|
+
ON session_ledger(valence)
|
|
800
|
+
WHERE valence IS NOT NULL;
|
|
801
|
+
|
|
802
|
+
-- v9.0: Token-Economic Cognitive Budget — budget column on session_handoffs
|
|
803
|
+
ALTER TABLE session_handoffs ADD COLUMN IF NOT EXISTS cognitive_budget REAL DEFAULT NULL;
|
|
804
|
+
`,
|
|
805
|
+
},
|
|
776
806
|
];
|
|
777
807
|
/**
|
|
778
808
|
* Current schema version — derived from the MIGRATIONS array.
|
package/dist/sync/factory.js
CHANGED
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { PRISM_STORAGE } from "../config.js";
|
|
10
10
|
import { debugLog } from "../utils/logger.js";
|
|
11
|
+
import { getSetting } from "../storage/configStorage.js";
|
|
11
12
|
let _bus = null;
|
|
12
13
|
export async function getSyncBus() {
|
|
13
14
|
if (_bus)
|
|
14
15
|
return _bus;
|
|
15
|
-
|
|
16
|
+
// DB-first, then env, then config.ts default (same priority as storage/index.ts)
|
|
17
|
+
const dbStorage = await getSetting("PRISM_STORAGE", "");
|
|
18
|
+
const resolvedStorage = dbStorage || process.env.PRISM_STORAGE || PRISM_STORAGE;
|
|
19
|
+
if (resolvedStorage === "local") {
|
|
16
20
|
const { SqliteSyncBus } = await import("./sqliteSync.js");
|
|
17
21
|
_bus = new SqliteSyncBus();
|
|
18
22
|
}
|
|
@@ -57,6 +57,9 @@ import { HdcStateMachine } from "../sdm/stateMachine.js";
|
|
|
57
57
|
import { ConceptDictionary } from "../sdm/conceptDictionary.js";
|
|
58
58
|
import { PolicyGateway } from "../sdm/policyGateway.js";
|
|
59
59
|
import { getSdmEngine } from "../sdm/sdmEngine.js";
|
|
60
|
+
// v9.0: Affect-Tagged Memory — valence-aware retrieval
|
|
61
|
+
import { formatValenceTag, generateValenceWarning, } from "../memory/valenceEngine.js";
|
|
62
|
+
import { PRISM_VALENCE_ENABLED } from "../config.js";
|
|
60
63
|
import { PRISM_HDC_ENABLED, PRISM_HDC_EXPLAINABILITY_ENABLED, PRISM_HDC_POLICY_FALLBACK_THRESHOLD, PRISM_HDC_POLICY_CLARIFY_THRESHOLD, } from "../config.js";
|
|
61
64
|
export async function knowledgeSearchHandler(args) {
|
|
62
65
|
if (!isKnowledgeSearchArgs(args)) {
|
|
@@ -457,7 +460,11 @@ export async function sessionSearchMemoryHandler(args) {
|
|
|
457
460
|
: "";
|
|
458
461
|
// v8.0: Tag nodes discovered via Synapse multi-hop traversal
|
|
459
462
|
const synapseTag = r.isDiscovered ? " [🌐 Synapse]" : "";
|
|
460
|
-
|
|
463
|
+
// v9.0: Valence tag — affect-tagged memory indicator
|
|
464
|
+
const valTag = PRISM_VALENCE_ENABLED && r.valence != null
|
|
465
|
+
? ` ${formatValenceTag(r.valence)}`
|
|
466
|
+
: "";
|
|
467
|
+
return `[${i + 1}] ${simScore} similar${synapseTag}${valTag} — ${r.session_date || "unknown date"}\n` +
|
|
461
468
|
` Project: ${r.project}\n` +
|
|
462
469
|
` Summary: ${r.summary}\n` +
|
|
463
470
|
importanceStr +
|
|
@@ -465,10 +472,25 @@ export async function sessionSearchMemoryHandler(args) {
|
|
|
465
472
|
(r.decisions?.length ? ` Decisions: ${r.decisions.join("; ")}\n` : "") +
|
|
466
473
|
(r.files_changed?.length ? ` Files: ${r.files_changed.join(", ")}\n` : "");
|
|
467
474
|
}).join("\n");
|
|
475
|
+
// v9.0: Valence Warning — inject contextual warning when top results
|
|
476
|
+
// have historically negative affect (failures, corrections).
|
|
477
|
+
let valenceWarning = "";
|
|
478
|
+
if (PRISM_VALENCE_ENABLED && results.length > 0) {
|
|
479
|
+
const valenceValues = results
|
|
480
|
+
.map((r) => r.valence)
|
|
481
|
+
.filter((v) => v != null && Number.isFinite(v));
|
|
482
|
+
if (valenceValues.length > 0) {
|
|
483
|
+
const avgValence = valenceValues.reduce((a, b) => a + b, 0) / valenceValues.length;
|
|
484
|
+
const warning = generateValenceWarning(avgValence);
|
|
485
|
+
if (warning) {
|
|
486
|
+
valenceWarning = `\n\n${warning}`;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
468
490
|
// Phase 1: content[0] = human-readable results (unchanged from pre-Phase 1)
|
|
469
491
|
const contentBlocks = [{
|
|
470
492
|
type: "text",
|
|
471
|
-
text: `🧠 Found ${results.length} semantically similar sessions:\n\n${formatted}`,
|
|
493
|
+
text: `🧠 Found ${results.length} semantically similar sessions:\n\n${formatted}${valenceWarning}`,
|
|
472
494
|
}];
|
|
473
495
|
// Phase 1: content[1] = machine-readable MemoryTrace (only when enable_trace=true)
|
|
474
496
|
// topScore is read from results[0].similarity — this is the cosine distance
|
|
@@ -29,9 +29,13 @@ import { getLLMProvider } from "../utils/llm/factory.js";
|
|
|
29
29
|
import { getCurrentGitState, getGitDrift } from "../utils/git.js";
|
|
30
30
|
import { getSetting, getAllSettings } from "../storage/configStorage.js";
|
|
31
31
|
import { mergeHandoff, dbToHandoffSchema, sanitizeForMerge } from "../utils/crdtMerge.js";
|
|
32
|
-
import { GOOGLE_API_KEY, PRISM_USER_ID, PRISM_AUTO_CAPTURE, PRISM_CAPTURE_PORTS } from "../config.js";
|
|
32
|
+
import { GOOGLE_API_KEY, PRISM_USER_ID, PRISM_AUTO_CAPTURE, PRISM_CAPTURE_PORTS, PRISM_VALENCE_ENABLED, PRISM_VALENCE_WARNING_THRESHOLD, PRISM_COGNITIVE_BUDGET_ENABLED, } from "../config.js";
|
|
33
33
|
import { captureLocalEnvironment } from "../utils/autoCapture.js";
|
|
34
34
|
import { fireCaptionAsync } from "../utils/imageCaptioner.js";
|
|
35
|
+
// ─── v9.0: Affect-Tagged Memory + Token-Economic RL ──────────
|
|
36
|
+
import { deriveValence } from "../memory/valenceEngine.js";
|
|
37
|
+
import { estimateTokens, spendBudget, applyEarnings, formatBudgetDiagnostics, DEFAULT_BUDGET_SIZE, } from "../memory/cognitiveBudget.js";
|
|
38
|
+
import { computeVectorSurprisal } from "../memory/surprisalGate.js";
|
|
35
39
|
import { isSessionSaveLedgerArgs, isSessionSaveHandoffArgs, isSessionLoadContextArgs, isMemoryHistoryArgs, isMemoryCheckoutArgs, // v2.2.0: health check type guard
|
|
36
40
|
isSessionForgetMemoryArgs, // v3.1: TTL retention policy type guard
|
|
37
41
|
// v4.0: Active Behavioral Memory type guards
|
|
@@ -83,6 +87,73 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
83
87
|
const combinedText = [summary, ...(decisions || [])].join(" ");
|
|
84
88
|
const keywords = toKeywordArray(combinedText);
|
|
85
89
|
debugLog(`[session_save_ledger] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
|
|
90
|
+
// ── v9.0: Auto-derive valence from event_type ──────────────────
|
|
91
|
+
// Valence is a [-1, +1] real representing the affective charge of a memory.
|
|
92
|
+
// It's auto-derived at create-time from the event_type field so the agent
|
|
93
|
+
// doesn't need to manually classify emotional context.
|
|
94
|
+
let valence = null;
|
|
95
|
+
let valenceWarning = "";
|
|
96
|
+
if (PRISM_VALENCE_ENABLED) {
|
|
97
|
+
const eventType = args.event_type || "session";
|
|
98
|
+
valence = deriveValence(eventType);
|
|
99
|
+
if (valence !== null && valence < PRISM_VALENCE_WARNING_THRESHOLD) {
|
|
100
|
+
valenceWarning = `\n\n⚠️ **Negative Valence (${valence.toFixed(2)}):** This entry is tagged as a negative experience. ` +
|
|
101
|
+
`It will be prioritized in future retrievals to prevent repeating past mistakes.`;
|
|
102
|
+
}
|
|
103
|
+
debugLog(`[session_save_ledger] v9.0 valence derived: ${valence} (event_type=${eventType})`);
|
|
104
|
+
}
|
|
105
|
+
// ── v9.0: Token-Economic Budget ────────────────────────────────
|
|
106
|
+
// Charge the project's cognitive budget for this write operation.
|
|
107
|
+
// Budget exhaustion triggers warnings but NEVER blocks writes (graceful degradation).
|
|
108
|
+
let budgetDiagnostics = "";
|
|
109
|
+
let queryEmbedding = null;
|
|
110
|
+
if (PRISM_COGNITIVE_BUDGET_ENABLED) {
|
|
111
|
+
try {
|
|
112
|
+
// Load current budget from the project's handoff state
|
|
113
|
+
const handoff = await storage.loadContext(project, "quick", PRISM_USER_ID);
|
|
114
|
+
const currentBudget = handoff?.cognitive_budget ?? DEFAULT_BUDGET_SIZE;
|
|
115
|
+
// Apply UBI earnings before spending.
|
|
116
|
+
// NOTE: event_type is intentionally NOT passed to applyEarnings().
|
|
117
|
+
// The "infinite money glitch" — LLMs self-declare event_type: "success"
|
|
118
|
+
// to mint free tokens. Budget bonuses should only come from the
|
|
119
|
+
// Dark Factory adversarial evaluator. Valence derivation still uses
|
|
120
|
+
// event_type correctly for affect tagging.
|
|
121
|
+
const lastCreated = handoff?.updated_at ?? null;
|
|
122
|
+
const earnings = applyEarnings(currentBudget, lastCreated, undefined);
|
|
123
|
+
// v9.0: Compute real surprisal via vector similarity search.
|
|
124
|
+
// Uses the existing embedding pipeline — generates the embedding
|
|
125
|
+
// early so we can reuse it for the post-save embedding patch.
|
|
126
|
+
let surprisal = 0.5; // Fallback: neutral surprisal
|
|
127
|
+
if (GOOGLE_API_KEY) {
|
|
128
|
+
try {
|
|
129
|
+
const embeddingText = [summary, ...(decisions || [])].join("\n");
|
|
130
|
+
queryEmbedding = await getLLMProvider().generateEmbedding(embeddingText);
|
|
131
|
+
const surprisalResult = await computeVectorSurprisal(storage.searchMemory.bind(storage), JSON.stringify(queryEmbedding), project, PRISM_USER_ID);
|
|
132
|
+
surprisal = surprisalResult.surprisal;
|
|
133
|
+
debugLog(`[session_save_ledger] v9.0 surprisal: ${surprisal.toFixed(3)} (${surprisalResult.isBoilerplate ? 'boilerplate' : surprisalResult.isNovel ? 'novel' : 'standard'})`);
|
|
134
|
+
}
|
|
135
|
+
catch (surprErr) {
|
|
136
|
+
debugLog(`[session_save_ledger] Surprisal computation failed (using 0.5 fallback): ${surprErr instanceof Error ? surprErr.message : String(surprErr)}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const rawTokenCost = estimateTokens(summary);
|
|
140
|
+
const result = spendBudget(earnings.newBalance, rawTokenCost, surprisal);
|
|
141
|
+
// Format diagnostics for MCP response
|
|
142
|
+
budgetDiagnostics = "\n\n" + formatBudgetDiagnostics(result, DEFAULT_BUDGET_SIZE, earnings.ubiEarned, earnings.bonusEarned);
|
|
143
|
+
// v9.0: Persist budget using delta-based update to prevent concurrency race.
|
|
144
|
+
// If Agent A and Agent B both load budget=2000 concurrently, absolute writes
|
|
145
|
+
// cause Agent A's spend to be overwritten by Agent B's stale value.
|
|
146
|
+
// Delta update: UPDATE SET cognitive_budget = COALESCE(cognitive_budget, 2000) + delta
|
|
147
|
+
const budgetDelta = (earnings.ubiEarned + earnings.bonusEarned) - result.spent;
|
|
148
|
+
storage.patchHandoffBudgetDelta(project, PRISM_USER_ID, budgetDelta).catch((err) => {
|
|
149
|
+
debugLog(`[session_save_ledger] Budget persist failed (non-fatal): ${err.message}`);
|
|
150
|
+
});
|
|
151
|
+
debugLog(`[session_save_ledger] v9.0 budget: cost=${result.spent}, balance=${result.remaining}, delta=${budgetDelta}`);
|
|
152
|
+
}
|
|
153
|
+
catch (budgetErr) {
|
|
154
|
+
debugLog(`[session_save_ledger] Budget tracking failed (non-fatal): ${budgetErr instanceof Error ? budgetErr.message : String(budgetErr)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
86
157
|
// Save via storage backend
|
|
87
158
|
const effectiveRole = role || await getSetting("default_role", "global");
|
|
88
159
|
const result = await storage.saveLedger({
|
|
@@ -95,14 +166,19 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
95
166
|
decisions: decisions || [],
|
|
96
167
|
keywords,
|
|
97
168
|
role: effectiveRole, // v3.0: Hivemind role scoping (dashboard fallback)
|
|
169
|
+
valence, // v9.0: Affect-tagged memory
|
|
98
170
|
});
|
|
99
171
|
// ─── Fire-and-forget embedding generation ───
|
|
100
172
|
if (GOOGLE_API_KEY && result) {
|
|
101
|
-
const embeddingText = [summary, ...(decisions || [])].join("\n");
|
|
102
173
|
const savedEntry = Array.isArray(result) ? result[0] : result;
|
|
103
174
|
const entryId = savedEntry?.id;
|
|
104
175
|
if (entryId) {
|
|
105
|
-
|
|
176
|
+
// If embedding was already generated during surprisal computation, reuse it.
|
|
177
|
+
// Otherwise, generate it now (fire-and-forget).
|
|
178
|
+
const embeddingPromise = queryEmbedding
|
|
179
|
+
? Promise.resolve(queryEmbedding)
|
|
180
|
+
: getLLMProvider().generateEmbedding([summary, ...(decisions || [])].join("\n"));
|
|
181
|
+
embeddingPromise
|
|
106
182
|
.then(async (embedding) => {
|
|
107
183
|
// Build atomic patch — float32 + TurboQuant in ONE DB update
|
|
108
184
|
const patchData = {
|
|
@@ -194,7 +270,10 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
194
270
|
(files_changed?.length ? `Files changed: ${files_changed.length}\n` : "") +
|
|
195
271
|
(decisions?.length ? `Decisions: ${decisions.length}\n` : "") +
|
|
196
272
|
(GOOGLE_API_KEY ? `📊 Embedding generation queued for semantic search.\n` : "") +
|
|
273
|
+
(valence !== null ? `🎭 Valence: ${valence.toFixed(2)}\n` : "") +
|
|
197
274
|
repoPathWarning +
|
|
275
|
+
valenceWarning +
|
|
276
|
+
budgetDiagnostics +
|
|
198
277
|
`\nRaw response: ${JSON.stringify(result)}`,
|
|
199
278
|
}],
|
|
200
279
|
isError: false,
|
|
@@ -720,8 +799,38 @@ export async function sessionLoadContextHandler(args) {
|
|
|
720
799
|
debugLog(`[session_load_context] SDM Recall failed (non-fatal): ${err instanceof Error ? err.message : err}`);
|
|
721
800
|
}
|
|
722
801
|
}
|
|
802
|
+
// ─── v9.0: Cognitive Budget Diagnostics ──────────────────────
|
|
803
|
+
// Show the agent its current token-economic budget status at session start.
|
|
804
|
+
// This gives real-time feedback on spending capacity and health.
|
|
805
|
+
let budgetDiagBlock = "";
|
|
806
|
+
if (PRISM_COGNITIVE_BUDGET_ENABLED && level !== "quick") {
|
|
807
|
+
try {
|
|
808
|
+
const currentBudget = d.cognitive_budget ?? DEFAULT_BUDGET_SIZE;
|
|
809
|
+
const budgetSize = DEFAULT_BUDGET_SIZE;
|
|
810
|
+
const ratio = Math.max(0, Math.min(1, currentBudget / budgetSize));
|
|
811
|
+
const barLength = 20;
|
|
812
|
+
const fillLength = Math.round(ratio * barLength);
|
|
813
|
+
const bar = '█'.repeat(Math.max(0, fillLength)) + '░'.repeat(Math.max(0, barLength - fillLength));
|
|
814
|
+
let healthLabel;
|
|
815
|
+
if (ratio > 0.6)
|
|
816
|
+
healthLabel = "🟢 Healthy";
|
|
817
|
+
else if (ratio > 0.3)
|
|
818
|
+
healthLabel = "🟡 Moderate";
|
|
819
|
+
else if (ratio > 0.1)
|
|
820
|
+
healthLabel = "🟠 Low";
|
|
821
|
+
else
|
|
822
|
+
healthLabel = "🔴 Critical";
|
|
823
|
+
budgetDiagBlock = `\n\n[💰 COGNITIVE BUDGET]\n` +
|
|
824
|
+
`${bar} ${currentBudget}/${budgetSize} tokens — ${healthLabel}\n` +
|
|
825
|
+
`Budget replenishes via UBI (+5 tokens/hour) and event bonuses (success: +20, learning: +10).`;
|
|
826
|
+
debugLog(`[session_load_context] v9.0 budget diagnostics: ${currentBudget}/${budgetSize} (${(ratio * 100).toFixed(0)}%)`);
|
|
827
|
+
}
|
|
828
|
+
catch (budgetErr) {
|
|
829
|
+
debugLog(`[session_load_context] Budget diagnostics failed (non-fatal): ${budgetErr instanceof Error ? budgetErr.message : String(budgetErr)}`);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
723
832
|
// Build the response object before v4.0 augmentations
|
|
724
|
-
let responseText = `📋 Session context for "${project}" (${level}):\n\n${formattedContext.trim()}${driftReport}${briefingBlock}${sdmRecallBlock}${greetingBlock}${visualMemoryBlock}${skillBlock}${versionNote}`;
|
|
833
|
+
let responseText = `📋 Session context for "${project}" (${level}):\n\n${formattedContext.trim()}${driftReport}${briefingBlock}${sdmRecallBlock}${greetingBlock}${visualMemoryBlock}${skillBlock}${budgetDiagBlock}${versionNote}`;
|
|
725
834
|
// ─── v4.0: Behavioral Warnings Injection ───────────────────
|
|
726
835
|
// If loadContext returned behavioral_warnings, add them to the
|
|
727
836
|
// formatted output so the agent sees them prominently.
|
|
@@ -1090,6 +1199,14 @@ export async function sessionSaveExperienceHandler(args) {
|
|
|
1090
1199
|
const keywords = toKeywordArray(summary);
|
|
1091
1200
|
debugLog(`[session_save_experience] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
|
|
1092
1201
|
const effectiveRole = role || await getSetting("default_role", "global");
|
|
1202
|
+
// v9.0: Experience events are the PRIMARY source of valence.
|
|
1203
|
+
// A failure event without valence = -0.8 is invisible to affective routing.
|
|
1204
|
+
// This was Bug #7 — the feature was wired in sessionSaveLedgerHandler but
|
|
1205
|
+
// missing in the handler that matters most for typed events.
|
|
1206
|
+
const valence = PRISM_VALENCE_ENABLED ? deriveValence(event_type, outcome) : null;
|
|
1207
|
+
if (valence !== null) {
|
|
1208
|
+
debugLog(`[session_save_experience] v9.0 valence derived: ${valence} (event_type=${event_type})`);
|
|
1209
|
+
}
|
|
1093
1210
|
const result = await storage.saveLedger({
|
|
1094
1211
|
project,
|
|
1095
1212
|
conversation_id: "experience-event",
|
|
@@ -1107,6 +1224,7 @@ export async function sessionSaveExperienceHandler(args) {
|
|
|
1107
1224
|
confidence_score: typeof confidence_score === "number" ? confidence_score : undefined,
|
|
1108
1225
|
// Corrections start with importance 1 to jumpstart visibility
|
|
1109
1226
|
importance: event_type === "correction" ? 1 : 0,
|
|
1227
|
+
valence, // v9.0: Affect-tagged memory — derived from event_type
|
|
1110
1228
|
});
|
|
1111
1229
|
// Fire-and-forget embedding generation
|
|
1112
1230
|
if (GOOGLE_API_KEY && result) {
|