@jungjaehoon/mama-core 1.4.2 → 1.6.0
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 +48 -10
- package/db/migrations/026-create-canonical-entity-tables.sql +99 -0
- package/db/migrations/027-create-entity-review-support.sql +44 -0
- package/db/migrations/028-create-entity-audit-tables.sql +45 -0
- package/db/migrations/029-extend-entity-observations-observation-type.sql +60 -0
- package/db/migrations/030-case-first-memory-substrate.sql +901 -0
- package/db/migrations/031-wiki-page-index-provenance-columns.sql +2 -0
- package/db/migrations/032-add-memory-provenance-columns.sql +21 -0
- package/db/migrations/033-create-model-runs-and-tool-traces.sql +53 -0
- package/db/migrations/034-add-connector-event-scope-columns.sql +14 -0
- package/db/migrations/035-create-twin-edges.sql +63 -0
- package/db/migrations/036-create-agent-situation-packets.sql +61 -0
- package/dist/agent-graph/alias-write.d.ts +3 -0
- package/dist/agent-graph/alias-write.js +231 -0
- package/dist/agent-graph/entity-resolve.d.ts +3 -0
- package/dist/agent-graph/entity-resolve.js +275 -0
- package/dist/agent-graph/errors.d.ts +4 -0
- package/dist/agent-graph/errors.js +11 -0
- package/dist/agent-graph/graph-query.d.ts +5 -0
- package/dist/agent-graph/graph-query.js +359 -0
- package/dist/agent-graph/index.d.ts +6 -0
- package/dist/agent-graph/index.js +22 -0
- package/dist/agent-graph/types.d.ts +162 -0
- package/dist/agent-graph/types.js +3 -0
- package/dist/agent-situation/builder.d.ts +8 -0
- package/dist/agent-situation/builder.js +239 -0
- package/dist/agent-situation/cache-key.d.ts +4 -0
- package/dist/agent-situation/cache-key.js +64 -0
- package/dist/agent-situation/index.d.ts +7 -0
- package/dist/agent-situation/index.js +28 -0
- package/dist/agent-situation/packet-store.d.ts +27 -0
- package/dist/agent-situation/packet-store.js +228 -0
- package/dist/agent-situation/ranking-policy.d.ts +18 -0
- package/dist/agent-situation/ranking-policy.js +75 -0
- package/dist/agent-situation/source-readers.d.ts +52 -0
- package/dist/agent-situation/source-readers.js +300 -0
- package/dist/agent-situation/types.d.ts +195 -0
- package/dist/agent-situation/types.js +3 -0
- package/dist/canonicalize.d.ts +38 -0
- package/dist/canonicalize.js +106 -0
- package/dist/cases/case-links.d.ts +116 -0
- package/dist/cases/case-links.js +794 -0
- package/dist/cases/composition-overrides.d.ts +90 -0
- package/dist/cases/composition-overrides.js +724 -0
- package/dist/cases/corrections.d.ts +156 -0
- package/dist/cases/corrections.js +1045 -0
- package/dist/cases/freshness.d.ts +57 -0
- package/dist/cases/freshness.js +283 -0
- package/dist/cases/live-state.d.ts +48 -0
- package/dist/cases/live-state.js +256 -0
- package/dist/cases/membership-explain.d.ts +56 -0
- package/dist/cases/membership-explain.js +140 -0
- package/dist/cases/membership-matcher.d.ts +23 -0
- package/dist/cases/membership-matcher.js +193 -0
- package/dist/cases/merge-split.d.ts +61 -0
- package/dist/cases/merge-split.js +368 -0
- package/dist/cases/role-inference.d.ts +10 -0
- package/dist/cases/role-inference.js +85 -0
- package/dist/cases/search-rollup.d.ts +27 -0
- package/dist/cases/search-rollup.js +309 -0
- package/dist/cases/sqlite-transaction.d.ts +10 -0
- package/dist/cases/sqlite-transaction.js +62 -0
- package/dist/cases/store.d.ts +74 -0
- package/dist/cases/store.js +816 -0
- package/dist/cases/target-ref.d.ts +25 -0
- package/dist/cases/target-ref.js +22 -0
- package/dist/cases/timeline-range.d.ts +6 -0
- package/dist/cases/timeline-range.js +322 -0
- package/dist/cases/tombstone-sweeper.d.ts +13 -0
- package/dist/cases/tombstone-sweeper.js +128 -0
- package/dist/cases/types.d.ts +244 -0
- package/dist/cases/types.js +47 -0
- package/dist/cases/wiki-page-index.d.ts +50 -0
- package/dist/cases/wiki-page-index.js +350 -0
- package/dist/connectors/event-index.d.ts +45 -0
- package/dist/connectors/event-index.js +381 -0
- package/dist/connectors/raw-query.d.ts +18 -0
- package/dist/connectors/raw-query.js +305 -0
- package/dist/connectors/types.d.ts +109 -0
- package/dist/connectors/types.js +3 -0
- package/dist/db-adapter/node-sqlite-adapter.d.ts +6 -0
- package/dist/db-adapter/node-sqlite-adapter.js +131 -0
- package/dist/db-manager.d.ts +12 -1
- package/dist/db-manager.js +60 -5
- package/dist/decision-formatter.d.ts +2 -0
- package/dist/decision-formatter.js +11 -8
- package/dist/edges/ref-validation.d.ts +8 -0
- package/dist/edges/ref-validation.js +262 -0
- package/dist/edges/store.d.ts +10 -0
- package/dist/edges/store.js +343 -0
- package/dist/edges/types.d.ts +86 -0
- package/dist/edges/types.js +18 -0
- package/dist/embedding-server/mobile/daemon.d.ts +1 -0
- package/dist/embedding-server/mobile/daemon.js +11 -1
- package/dist/embeddings.js +13 -0
- package/dist/entities/audit-metrics.d.ts +73 -0
- package/dist/entities/audit-metrics.js +128 -0
- package/dist/entities/candidate-generator.d.ts +9 -0
- package/dist/entities/candidate-generator.js +436 -0
- package/dist/entities/entity-impact.d.ts +41 -0
- package/dist/entities/entity-impact.js +140 -0
- package/dist/entities/entity-linked-decision-counts.d.ts +2 -0
- package/dist/entities/entity-linked-decision-counts.js +29 -0
- package/dist/entities/entity-list.d.ts +23 -0
- package/dist/entities/entity-list.js +99 -0
- package/dist/entities/entity-orphan-list.d.ts +26 -0
- package/dist/entities/entity-orphan-list.js +91 -0
- package/dist/entities/entity-search.d.ts +21 -0
- package/dist/entities/entity-search.js +150 -0
- package/dist/entities/errors.d.ts +65 -0
- package/dist/entities/errors.js +124 -0
- package/dist/entities/exact-merge-backfill.d.ts +11 -0
- package/dist/entities/exact-merge-backfill.js +161 -0
- package/dist/entities/lineage-backfill.d.ts +12 -0
- package/dist/entities/lineage-backfill.js +101 -0
- package/dist/entities/lineage-store.d.ts +51 -0
- package/dist/entities/lineage-store.js +308 -0
- package/dist/entities/normalization.d.ts +14 -0
- package/dist/entities/normalization.js +63 -0
- package/dist/entities/policy-store.d.ts +17 -0
- package/dist/entities/policy-store.js +263 -0
- package/dist/entities/policy-types.d.ts +72 -0
- package/dist/entities/policy-types.js +59 -0
- package/dist/entities/projection.d.ts +8 -0
- package/dist/entities/projection.js +82 -0
- package/dist/entities/provenance-query.d.ts +33 -0
- package/dist/entities/provenance-query.js +167 -0
- package/dist/entities/read-identity.d.ts +31 -0
- package/dist/entities/read-identity.js +264 -0
- package/dist/entities/recall-bridge.d.ts +5 -0
- package/dist/entities/recall-bridge.js +171 -0
- package/dist/entities/resolution-engine.d.ts +15 -0
- package/dist/entities/resolution-engine.js +69 -0
- package/dist/entities/rollback-preview.d.ts +39 -0
- package/dist/entities/rollback-preview.js +235 -0
- package/dist/entities/source-locator.d.ts +3 -0
- package/dist/entities/source-locator.js +32 -0
- package/dist/entities/store.d.ts +75 -0
- package/dist/entities/store.js +465 -0
- package/dist/entities/types.d.ts +162 -0
- package/dist/entities/types.js +30 -0
- package/dist/index.d.ts +62 -3
- package/dist/index.js +116 -26
- package/dist/mama-api.d.ts +53 -35
- package/dist/mama-api.js +437 -78
- package/dist/memory/api.d.ts +26 -38
- package/dist/memory/api.js +772 -68
- package/dist/memory/event-store.d.ts +3 -0
- package/dist/memory/event-store.js +21 -2
- package/dist/memory/provenance-audit.d.ts +22 -0
- package/dist/memory/provenance-audit.js +112 -0
- package/dist/memory/provenance-query.d.ts +11 -0
- package/dist/memory/provenance-query.js +193 -0
- package/dist/memory/provenance.d.ts +29 -0
- package/dist/memory/provenance.js +136 -0
- package/dist/memory/scope-backfill.d.ts +17 -0
- package/dist/memory/scope-backfill.js +77 -0
- package/dist/memory/types.d.ts +97 -16
- package/dist/model-runs/store.d.ts +13 -0
- package/dist/model-runs/store.js +457 -0
- package/dist/model-runs/tool-trace-store.d.ts +4 -0
- package/dist/model-runs/tool-trace-store.js +114 -0
- package/dist/model-runs/types.d.ts +69 -0
- package/dist/model-runs/types.js +5 -0
- package/dist/runtime/trusted-provenance.d.ts +3 -0
- package/dist/runtime/trusted-provenance.js +6 -0
- package/dist/search/feedback-store.d.ts +59 -0
- package/dist/search/feedback-store.js +211 -0
- package/dist/search/question-type.d.ts +5 -0
- package/dist/search/question-type.js +41 -0
- package/dist/search/ranker-features.d.ts +23 -0
- package/dist/search/ranker-features.js +104 -0
- package/dist/search/ranker-rescore.d.ts +31 -0
- package/dist/search/ranker-rescore.js +226 -0
- package/dist/search/ranker-trainer.d.ts +94 -0
- package/dist/search/ranker-trainer.js +860 -0
- package/dist/search/search-quality.d.ts +36 -0
- package/dist/search/search-quality.js +29 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +52 -0
- package/package.json +28 -3
package/dist/db-manager.js
CHANGED
|
@@ -23,6 +23,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.initDB = initDB;
|
|
26
|
+
exports.assertTestProcessIsNotUsingRealDb = assertTestProcessIsNotUsingRealDb;
|
|
26
27
|
exports.getDB = getDB;
|
|
27
28
|
exports.getAdapter = getAdapter;
|
|
28
29
|
exports.closeDB = closeDB;
|
|
@@ -43,6 +44,7 @@ exports.isTestMode = isTestMode;
|
|
|
43
44
|
exports.fts5Search = fts5Search;
|
|
44
45
|
exports.reindexEmbeddings = reindexEmbeddings;
|
|
45
46
|
const path_1 = __importDefault(require("path"));
|
|
47
|
+
const os_1 = __importDefault(require("os"));
|
|
46
48
|
const debug_logger_js_1 = require("./debug-logger.js");
|
|
47
49
|
const progress_indicator_js_1 = require("./progress-indicator.js");
|
|
48
50
|
const index_js_1 = require("./db-adapter/index.js");
|
|
@@ -53,6 +55,7 @@ let isInitialized = false;
|
|
|
53
55
|
let initializingPromise = null; // Single-flight guard for concurrent callers
|
|
54
56
|
// Migration directory (moved to src/db/migrations for M1.2)
|
|
55
57
|
const MIGRATIONS_DIR = path_1.default.join(__dirname, '..', 'db', 'migrations');
|
|
58
|
+
const REAL_USER_DB_PATH = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-memory.db');
|
|
56
59
|
/**
|
|
57
60
|
* Initialize SQLite database adapter and connect
|
|
58
61
|
*
|
|
@@ -65,6 +68,7 @@ const MIGRATIONS_DIR = path_1.default.join(__dirname, '..', 'db', 'migrations');
|
|
|
65
68
|
* @returns SQLite database connection
|
|
66
69
|
*/
|
|
67
70
|
async function initDB() {
|
|
71
|
+
assertTestProcessIsNotUsingRealDb();
|
|
68
72
|
// Already initialized - return immediately
|
|
69
73
|
if (isInitialized) {
|
|
70
74
|
return dbConnection;
|
|
@@ -79,6 +83,7 @@ async function initDB() {
|
|
|
79
83
|
(0, progress_indicator_js_1.logSearching)('Initializing database...');
|
|
80
84
|
// Create SQLite adapter
|
|
81
85
|
dbAdapter = (0, index_js_1.createAdapter)();
|
|
86
|
+
assertTestProcessIsNotUsingRealDb(resolveAdapterDbPath(dbAdapter), 'adapter.getDbPath()');
|
|
82
87
|
// Connect to database
|
|
83
88
|
dbConnection = await dbAdapter.connect();
|
|
84
89
|
// Run migrations (includes 012-create-checkpoints-table.sql)
|
|
@@ -104,6 +109,48 @@ async function initDB() {
|
|
|
104
109
|
})();
|
|
105
110
|
return initializingPromise;
|
|
106
111
|
}
|
|
112
|
+
function assertTestProcessIsNotUsingRealDb(effectivePath, effectivePathSource = 'adapter') {
|
|
113
|
+
if (!isDatabaseBoundaryTestMode()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const configuredPaths = [
|
|
117
|
+
{ name: 'MAMA_DB_PATH', value: process.env.MAMA_DB_PATH },
|
|
118
|
+
{ name: 'MAMA_DATABASE_PATH', value: process.env.MAMA_DATABASE_PATH },
|
|
119
|
+
];
|
|
120
|
+
if (effectivePath) {
|
|
121
|
+
configuredPaths.push({ name: effectivePathSource, value: effectivePath });
|
|
122
|
+
}
|
|
123
|
+
for (const configuredPath of configuredPaths) {
|
|
124
|
+
if (!configuredPath.value) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const resolvedPath = path_1.default.resolve(expandHomePath(configuredPath.value));
|
|
128
|
+
if (resolvedPath === path_1.default.resolve(REAL_USER_DB_PATH)) {
|
|
129
|
+
throw new Error(`[db-boundary] Refusing to initDB against real DB ${REAL_USER_DB_PATH} ` +
|
|
130
|
+
`from a test process (${configuredPath.name}=${configuredPath.value}). ` +
|
|
131
|
+
'Set MAMA_DB_PATH to a temporary path before initDB.');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function resolveAdapterDbPath(adapter) {
|
|
136
|
+
if (typeof adapter.getDbPath === 'function') {
|
|
137
|
+
return adapter.getDbPath();
|
|
138
|
+
}
|
|
139
|
+
return adapter.dbPath;
|
|
140
|
+
}
|
|
141
|
+
function isDatabaseBoundaryTestMode() {
|
|
142
|
+
return isTestMode();
|
|
143
|
+
}
|
|
144
|
+
function expandHomePath(value) {
|
|
145
|
+
const home = os_1.default.homedir();
|
|
146
|
+
if (value === '~') {
|
|
147
|
+
return home;
|
|
148
|
+
}
|
|
149
|
+
if (value.startsWith('~/')) {
|
|
150
|
+
return path_1.default.join(home, value.slice(2));
|
|
151
|
+
}
|
|
152
|
+
return value.replaceAll('${HOME}', home);
|
|
153
|
+
}
|
|
107
154
|
/**
|
|
108
155
|
* Get database connection (singleton pattern)
|
|
109
156
|
*
|
|
@@ -267,6 +314,13 @@ async function insertDecisionWithEmbedding(decision) {
|
|
|
267
314
|
throw new Error(`Invalid event_date: must be ISO 8601 YYYY-MM-DD (got: ${d})`);
|
|
268
315
|
}
|
|
269
316
|
}
|
|
317
|
+
if (decision.event_datetime !== null && decision.event_datetime !== undefined) {
|
|
318
|
+
if (typeof decision.event_datetime !== 'number' ||
|
|
319
|
+
!Number.isFinite(decision.event_datetime) ||
|
|
320
|
+
decision.event_datetime <= 0) {
|
|
321
|
+
throw new Error(`Invalid event_datetime: must be a positive millisecond timestamp (got: ${decision.event_datetime})`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
270
324
|
try {
|
|
271
325
|
// Generate embedding BEFORE transaction (required for SQLite's sync transaction)
|
|
272
326
|
// Note: Redact topic for privacy - only log length
|
|
@@ -299,14 +353,15 @@ async function insertDecisionWithEmbedding(decision) {
|
|
|
299
353
|
confidence, created_at, updated_at,
|
|
300
354
|
needs_validation, validation_attempts, last_validated_at, usage_count,
|
|
301
355
|
trust_context, usage_success, usage_failure, time_saved,
|
|
302
|
-
evidence, alternatives, risks, event_date
|
|
303
|
-
|
|
356
|
+
evidence, alternatives, risks, event_date, event_datetime,
|
|
357
|
+
agent_id, model_run_id, envelope_hash, gateway_call_id, source_refs_json, provenance_json
|
|
358
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
304
359
|
`);
|
|
305
360
|
const insertResult = stmt.run(decision.id, decision.topic, decision.decision, decision.reasoning || null, decision.outcome || null, decision.failure_reason || null, decision.limitation || null, decision.user_involvement || null, decision.session_id || null, decision.supersedes || null, decision.superseded_by || null, decision.refined_from ? JSON.stringify(decision.refined_from) : null, decision.confidence !== undefined ? decision.confidence : 0.5,
|
|
306
361
|
// IMPORTANT: All timestamps are stored in milliseconds (Date.now()).
|
|
307
362
|
// The schema DEFAULT uses unixepoch() (seconds) but is never used
|
|
308
363
|
// since all inserts go through this function which always provides ms.
|
|
309
|
-
decision.created_at || Date.now(), decision.updated_at || Date.now(), decision.needs_validation !== undefined ? decision.needs_validation : 0, decision.validation_attempts || 0, decision.last_validated_at || null, decision.usage_count || 0, decision.trust_context || null, decision.usage_success || 0, decision.usage_failure || 0, decision.time_saved || 0, decision.evidence || null, decision.alternatives || null, decision.risks || null, decision.event_date || null);
|
|
364
|
+
decision.created_at || Date.now(), decision.updated_at || Date.now(), decision.needs_validation !== undefined ? decision.needs_validation : 0, decision.validation_attempts || 0, decision.last_validated_at || null, decision.usage_count || 0, decision.trust_context || null, decision.usage_success || 0, decision.usage_failure || 0, decision.time_saved || 0, decision.evidence || null, decision.alternatives || null, decision.risks || null, decision.event_date || null, decision.event_datetime ?? null, decision.agent_id ?? null, decision.model_run_id ?? null, decision.envelope_hash ?? null, decision.gateway_call_id ?? null, decision.source_refs_json ?? null, decision.provenance_json ?? null);
|
|
310
365
|
const rowid = Number(insertResult.lastInsertRowid);
|
|
311
366
|
// Insert embedding in same transaction to ensure rowid matching
|
|
312
367
|
(0, debug_logger_js_1.info)(`[db-manager] Vector search enabled: ${adapter.vectorSearchEnabled}`);
|
|
@@ -668,10 +723,10 @@ function resetDBState(options = {}) {
|
|
|
668
723
|
/**
|
|
669
724
|
* Check if running in test mode
|
|
670
725
|
*
|
|
671
|
-
* Returns true if MAMA_TEST_MODE or
|
|
726
|
+
* Returns true if MAMA_TEST_MODE, VITEST, or NODE_ENV=test env vars are set
|
|
672
727
|
*/
|
|
673
728
|
function isTestMode() {
|
|
674
|
-
return
|
|
729
|
+
return Boolean(process.env.MAMA_TEST_MODE || process.env.VITEST || process.env.NODE_ENV === 'test');
|
|
675
730
|
}
|
|
676
731
|
/**
|
|
677
732
|
* FTS5 keyword search on decisions table.
|
|
@@ -38,6 +38,8 @@ export interface DecisionForFormat {
|
|
|
38
38
|
risks?: string;
|
|
39
39
|
/** ISO 8601 date when the event actually occurred. Null if not set. */
|
|
40
40
|
event_date?: string | null;
|
|
41
|
+
/** Source event timestamp in milliseconds when known. Null if not set. */
|
|
42
|
+
event_datetime?: number | null;
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
45
|
* Trust context object
|
|
@@ -140,7 +140,7 @@ Top ${full.length} Most Relevant Decisions:
|
|
|
140
140
|
`.trim();
|
|
141
141
|
for (let i = 0; i < full.length; i++) {
|
|
142
142
|
const d = full[i];
|
|
143
|
-
const duration = calculateDuration(d
|
|
143
|
+
const duration = calculateDuration(getDecisionTimestamp(d));
|
|
144
144
|
const outcomeEmoji = getOutcomeEmoji(d.outcome ?? null);
|
|
145
145
|
const relevancePercent = Math.round((d.relevanceScore || 0) * 100);
|
|
146
146
|
context += `\n\n${i + 1}. ${d.decision} (${duration}, relevance: ${relevancePercent}%) ${outcomeEmoji}`;
|
|
@@ -166,7 +166,7 @@ Top ${full.length} Most Relevant Decisions:
|
|
|
166
166
|
* Format small decision history (3 or fewer)
|
|
167
167
|
*/
|
|
168
168
|
function formatSmallHistory(current, history) {
|
|
169
|
-
const duration = calculateDuration(current
|
|
169
|
+
const duration = calculateDuration(getDecisionTimestamp(current));
|
|
170
170
|
let context = `
|
|
171
171
|
🧠 DECISION HISTORY: ${current.topic}
|
|
172
172
|
|
|
@@ -284,6 +284,9 @@ function calculateDuration(timestamp) {
|
|
|
284
284
|
}
|
|
285
285
|
return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`;
|
|
286
286
|
}
|
|
287
|
+
function getDecisionTimestamp(decision) {
|
|
288
|
+
return decision.event_datetime ?? decision.created_at;
|
|
289
|
+
}
|
|
287
290
|
/**
|
|
288
291
|
* Calculate duration between two timestamps in days
|
|
289
292
|
*/
|
|
@@ -560,7 +563,7 @@ function formatTeaserList(decisions, topN = 3) {
|
|
|
560
563
|
output += `\n "${preview}"`;
|
|
561
564
|
// Recency metadata (NEW - Gaussian Decay)
|
|
562
565
|
if (d.recency_age_days !== undefined && d.created_at) {
|
|
563
|
-
const timeAgo = calculateDuration(d
|
|
566
|
+
const timeAgo = calculateDuration(getDecisionTimestamp(d));
|
|
564
567
|
const recencyScore = d.recency_score ? Math.round(d.recency_score * 100) : null;
|
|
565
568
|
const finalScore = d.final_score ? Math.round(d.final_score * 100) : null;
|
|
566
569
|
output += `\n ⏰ ${timeAgo}`;
|
|
@@ -582,7 +585,7 @@ function formatTeaser(decision) {
|
|
|
582
585
|
if (!decision) {
|
|
583
586
|
return null;
|
|
584
587
|
}
|
|
585
|
-
const timeAgo = calculateDuration(decision
|
|
588
|
+
const timeAgo = calculateDuration(getDecisionTimestamp(decision));
|
|
586
589
|
// Extract preview (first 60 chars)
|
|
587
590
|
const preview = decision.decision.length > 60 ? decision.decision.substring(0, 60) + '...' : decision.decision;
|
|
588
591
|
// Extract files from trust_context or show generic
|
|
@@ -633,7 +636,7 @@ function formatRecall(decisions, semanticEdges = null) {
|
|
|
633
636
|
* Format single decision with full detail
|
|
634
637
|
*/
|
|
635
638
|
function formatSingleDecision(decision) {
|
|
636
|
-
const timeAgo = calculateDuration(decision
|
|
639
|
+
const timeAgo = calculateDuration(getDecisionTimestamp(decision));
|
|
637
640
|
const confidencePercent = Math.round((decision.confidence || 0) * 100);
|
|
638
641
|
const outcomeEmoji = getOutcomeEmoji(decision.outcome ?? null);
|
|
639
642
|
const outcomeText = decision.outcome || 'Not yet tracked';
|
|
@@ -725,7 +728,7 @@ function formatDecisionHistory(decisions, semanticEdges = null) {
|
|
|
725
728
|
📋 Decision History: ${topic}
|
|
726
729
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
727
730
|
|
|
728
|
-
Latest Decision (${calculateDuration(latest
|
|
731
|
+
Latest Decision (${calculateDuration(getDecisionTimestamp(latest))}):
|
|
729
732
|
${latest.decision}
|
|
730
733
|
`.trim();
|
|
731
734
|
// Show brief reasoning if available
|
|
@@ -777,7 +780,7 @@ ${latest.decision}
|
|
|
777
780
|
output += `\nPrevious Decisions (${older.length}):\n`;
|
|
778
781
|
for (let i = 0; i < Math.min(older.length, 5); i++) {
|
|
779
782
|
const d = older[i];
|
|
780
|
-
const timeAgo = calculateDuration(d
|
|
783
|
+
const timeAgo = calculateDuration(getDecisionTimestamp(d));
|
|
781
784
|
const emoji = getOutcomeEmoji(d.outcome ?? null);
|
|
782
785
|
output += `\n${i + 2}. ${d.decision} (${timeAgo}) ${emoji}`;
|
|
783
786
|
if (d.outcome === 'FAILED' && d.failure_reason) {
|
|
@@ -876,7 +879,7 @@ function formatList(decisions, options = {}) {
|
|
|
876
879
|
output += '━'.repeat(60) + '\n';
|
|
877
880
|
for (let i = 0; i < items.length; i++) {
|
|
878
881
|
const d = items[i];
|
|
879
|
-
const timeAgo = calculateDuration(d
|
|
882
|
+
const timeAgo = calculateDuration(getDecisionTimestamp(d));
|
|
880
883
|
const type = d.user_involvement === 'approved' ? '👤 User' : '🤖 Assistant';
|
|
881
884
|
const status = d.outcome ? getOutcomeEmoji(d.outcome) + ' ' + d.outcome : '⏳ Pending';
|
|
882
885
|
const confidence = Math.round((d.confidence || 0) * 100);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
import type { ListVisibleTwinEdgesOptions, TwinEdgeRecord, TwinRef, TwinScopeRef, TwinVisibility } from './types.js';
|
|
3
|
+
type TwinRefVisibilityAdapter = Pick<DatabaseAdapter, 'prepare'>;
|
|
4
|
+
export declare function assertTwinRefsVisible(adapter: TwinRefVisibilityAdapter, refs: readonly TwinRef[], visibility?: TwinVisibility): void;
|
|
5
|
+
export declare function assertTwinRefsVisibleToScopes(adapter: TwinRefVisibilityAdapter, refs: readonly TwinRef[], scopes?: TwinScopeRef[]): void;
|
|
6
|
+
export declare function listVisibleTwinEdgesForRefs(adapter: TwinRefVisibilityAdapter, refs: readonly TwinRef[], options?: ListVisibleTwinEdgesOptions): TwinEdgeRecord[];
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=ref-validation.d.ts.map
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertTwinRefsVisible = assertTwinRefsVisible;
|
|
4
|
+
exports.assertTwinRefsVisibleToScopes = assertTwinRefsVisibleToScopes;
|
|
5
|
+
exports.listVisibleTwinEdgesForRefs = listVisibleTwinEdgesForRefs;
|
|
6
|
+
const store_js_1 = require("./store.js");
|
|
7
|
+
const TABLE_COLUMN_PRAGMAS = {
|
|
8
|
+
decisions: 'PRAGMA table_info(decisions)',
|
|
9
|
+
case_truth: 'PRAGMA table_info(case_truth)',
|
|
10
|
+
};
|
|
11
|
+
const tableColumnCache = new WeakMap();
|
|
12
|
+
function scopeKey(scope) {
|
|
13
|
+
return `${scope.kind}\0${scope.id}`;
|
|
14
|
+
}
|
|
15
|
+
function hasScopes(scopes) {
|
|
16
|
+
return Array.isArray(scopes) && scopes.length > 0;
|
|
17
|
+
}
|
|
18
|
+
function hasConnectors(connectors) {
|
|
19
|
+
return Array.isArray(connectors) && connectors.length > 0;
|
|
20
|
+
}
|
|
21
|
+
function hasProjectRefs(projectRefs) {
|
|
22
|
+
return Array.isArray(projectRefs) && projectRefs.length > 0;
|
|
23
|
+
}
|
|
24
|
+
function visibilityFromScopes(scopes) {
|
|
25
|
+
return { scopes: scopes ? [...scopes] : undefined };
|
|
26
|
+
}
|
|
27
|
+
function asOfMs(visibility) {
|
|
28
|
+
return typeof visibility.asOfMs === 'number' ? visibility.asOfMs : null;
|
|
29
|
+
}
|
|
30
|
+
function isAtOrBeforeAsOf(value, visibility) {
|
|
31
|
+
const asOf = asOfMs(visibility);
|
|
32
|
+
return asOf === null || (typeof value === 'number' && value <= asOf);
|
|
33
|
+
}
|
|
34
|
+
function parseTimestamp(value) {
|
|
35
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
36
|
+
return Math.floor(value);
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === 'string') {
|
|
39
|
+
const parsed = Date.parse(value);
|
|
40
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function tableColumns(adapter, table) {
|
|
45
|
+
const pragma = TABLE_COLUMN_PRAGMAS[table];
|
|
46
|
+
if (!pragma) {
|
|
47
|
+
throw new Error(`Unsupported table for column introspection: ${table}`);
|
|
48
|
+
}
|
|
49
|
+
let adapterCache = tableColumnCache.get(adapter);
|
|
50
|
+
if (!adapterCache) {
|
|
51
|
+
adapterCache = new Map();
|
|
52
|
+
tableColumnCache.set(adapter, adapterCache);
|
|
53
|
+
}
|
|
54
|
+
const cached = adapterCache.get(table);
|
|
55
|
+
if (cached) {
|
|
56
|
+
return cached;
|
|
57
|
+
}
|
|
58
|
+
const rows = adapter.prepare(pragma).all();
|
|
59
|
+
const columns = new Set(rows.map((row) => row.name));
|
|
60
|
+
adapterCache.set(table, columns);
|
|
61
|
+
return columns;
|
|
62
|
+
}
|
|
63
|
+
function isMemoryVisible(adapter, id, visibility) {
|
|
64
|
+
const row = adapter
|
|
65
|
+
.prepare(`
|
|
66
|
+
SELECT created_at, event_datetime
|
|
67
|
+
FROM decisions
|
|
68
|
+
WHERE id = ?
|
|
69
|
+
LIMIT 1
|
|
70
|
+
`)
|
|
71
|
+
.get(id);
|
|
72
|
+
if (!row) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (!isAtOrBeforeAsOf(row.event_datetime ?? row.created_at, visibility)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const scopes = visibility.scopes;
|
|
79
|
+
if (!hasScopes(scopes)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
const scopeClauses = scopes.map(() => '(ms.kind = ? AND ms.external_id = ?)').join(' OR ');
|
|
83
|
+
const params = scopes.flatMap((scope) => [scope.kind, scope.id]);
|
|
84
|
+
const bindingRow = adapter
|
|
85
|
+
.prepare(`
|
|
86
|
+
SELECT 1 AS ok
|
|
87
|
+
FROM memory_scope_bindings msb
|
|
88
|
+
JOIN memory_scopes ms ON ms.id = msb.scope_id
|
|
89
|
+
WHERE msb.memory_id = ?
|
|
90
|
+
AND (${scopeClauses})
|
|
91
|
+
LIMIT 1
|
|
92
|
+
`)
|
|
93
|
+
.get(id, ...params);
|
|
94
|
+
if (bindingRow?.ok) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const columns = tableColumns(adapter, 'decisions');
|
|
98
|
+
if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
|
|
99
|
+
const row = adapter
|
|
100
|
+
.prepare(`
|
|
101
|
+
SELECT 1 AS ok
|
|
102
|
+
FROM decisions
|
|
103
|
+
WHERE id = ?
|
|
104
|
+
AND (${scopes.map(() => '(memory_scope_kind = ? AND memory_scope_id = ?)').join(' OR ')})
|
|
105
|
+
LIMIT 1
|
|
106
|
+
`)
|
|
107
|
+
.get(id, ...params);
|
|
108
|
+
return Boolean(row?.ok);
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function parseCaseScopeRefs(scopeRefs) {
|
|
113
|
+
if (typeof scopeRefs !== 'string' || scopeRefs.length === 0) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const parsed = JSON.parse(scopeRefs);
|
|
118
|
+
if (!Array.isArray(parsed)) {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
return parsed.filter((item) => item !== null &&
|
|
122
|
+
typeof item === 'object' &&
|
|
123
|
+
typeof item.kind === 'string' &&
|
|
124
|
+
typeof item.id === 'string');
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function isCaseVisible(adapter, id, visibility) {
|
|
131
|
+
const row = adapter.prepare('SELECT * FROM case_truth WHERE case_id = ? LIMIT 1').get(id);
|
|
132
|
+
if (!row) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const timestamp = parseTimestamp(row.last_activity_at) ?? parseTimestamp(row.updated_at);
|
|
136
|
+
if (!isAtOrBeforeAsOf(timestamp, visibility)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const scopes = visibility.scopes;
|
|
140
|
+
if (!hasScopes(scopes)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
const requested = new Set(scopes.map(scopeKey));
|
|
144
|
+
const scopeRefs = parseCaseScopeRefs(row.scope_refs);
|
|
145
|
+
if (scopeRefs.some((scope) => requested.has(scopeKey(scope)))) {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
const columns = tableColumns(adapter, 'case_truth');
|
|
149
|
+
if (columns.has('memory_scope_kind') && columns.has('memory_scope_id')) {
|
|
150
|
+
return scopes.some((scope) => row.memory_scope_kind === scope.kind && row.memory_scope_id === scope.id);
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
function isRawVisible(adapter, id, visibility) {
|
|
155
|
+
const row = adapter
|
|
156
|
+
.prepare('SELECT * FROM connector_event_index WHERE event_index_id = ? LIMIT 1')
|
|
157
|
+
.get(id);
|
|
158
|
+
if (!row) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (hasConnectors(visibility.connectors) &&
|
|
162
|
+
!visibility.connectors.includes(String(row.source_connector))) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (hasProjectRefs(visibility.projectRefs) &&
|
|
166
|
+
!visibility.projectRefs.some((project) => project.id === row.project_id)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (typeof visibility.tenantId === 'string' &&
|
|
170
|
+
visibility.tenantId.length > 0 &&
|
|
171
|
+
row.tenant_id !== visibility.tenantId) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (!isAtOrBeforeAsOf(Number(row.event_datetime ?? row.source_timestamp_ms), visibility)) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
if (!hasScopes(visibility.scopes)) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
return visibility.scopes.some((scope) => row.memory_scope_kind === scope.kind && row.memory_scope_id === scope.id);
|
|
181
|
+
}
|
|
182
|
+
function isEntityVisible(adapter, id, visibility) {
|
|
183
|
+
const row = adapter
|
|
184
|
+
.prepare('SELECT scope_kind, scope_id, created_at FROM entity_nodes WHERE id = ? AND status = ? LIMIT 1')
|
|
185
|
+
.get(id, 'active');
|
|
186
|
+
if (!row) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
if (!isAtOrBeforeAsOf(row.created_at, visibility)) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
const scopes = visibility.scopes;
|
|
193
|
+
if (!hasScopes(scopes)) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return scopes.some((scope) => row.scope_kind === scope.kind && row.scope_id === scope.id);
|
|
197
|
+
}
|
|
198
|
+
function isTwinRefVisible(adapter, ref, visibility, visitedEdges, edgeCache) {
|
|
199
|
+
if (ref.kind === 'entity') {
|
|
200
|
+
return isEntityVisible(adapter, ref.id, visibility);
|
|
201
|
+
}
|
|
202
|
+
if (ref.kind === 'report') {
|
|
203
|
+
return !hasScopes(visibility.scopes);
|
|
204
|
+
}
|
|
205
|
+
if (ref.kind === 'memory') {
|
|
206
|
+
return isMemoryVisible(adapter, ref.id, visibility);
|
|
207
|
+
}
|
|
208
|
+
if (ref.kind === 'case') {
|
|
209
|
+
return isCaseVisible(adapter, ref.id, visibility);
|
|
210
|
+
}
|
|
211
|
+
if (ref.kind === 'raw') {
|
|
212
|
+
return isRawVisible(adapter, ref.id, visibility);
|
|
213
|
+
}
|
|
214
|
+
if (visitedEdges.has(ref.id)) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
let edge = edgeCache.get(ref.id);
|
|
218
|
+
if (edge === undefined) {
|
|
219
|
+
edge = (0, store_js_1.getTwinEdge)(adapter, ref.id);
|
|
220
|
+
edgeCache.set(ref.id, edge);
|
|
221
|
+
}
|
|
222
|
+
if (!edge) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
if (!isAtOrBeforeAsOf(edge.created_at, visibility)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
const pathWithCurrent = new Set(visitedEdges);
|
|
229
|
+
pathWithCurrent.add(ref.id);
|
|
230
|
+
return (isTwinRefVisible(adapter, edge.subject_ref, visibility, new Set(pathWithCurrent), edgeCache) &&
|
|
231
|
+
isTwinRefVisible(adapter, edge.object_ref, visibility, new Set(pathWithCurrent), edgeCache));
|
|
232
|
+
}
|
|
233
|
+
function assertTwinRefsVisible(adapter, refs, visibility = {}) {
|
|
234
|
+
const edgeCache = new Map();
|
|
235
|
+
for (const ref of refs) {
|
|
236
|
+
if (!isTwinRefVisible(adapter, ref, visibility, new Set(), edgeCache)) {
|
|
237
|
+
throw new Error(`Twin ref is not visible to requested visibility: ${ref.kind}:${ref.id}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function assertTwinRefsVisibleToScopes(adapter, refs, scopes) {
|
|
242
|
+
assertTwinRefsVisible(adapter, refs, visibilityFromScopes(scopes));
|
|
243
|
+
}
|
|
244
|
+
function listVisibleTwinEdgesForRefs(adapter, refs, options = {}) {
|
|
245
|
+
const edgeCache = new Map();
|
|
246
|
+
const edgeTypes = normalizeEdgeTypes(options.edgeTypes);
|
|
247
|
+
const edges = (0, store_js_1.listTwinEdgesForRefs)(adapter, refs).filter((edge) => (edgeTypes.size === 0 || edgeTypes.has(edge.edge_type)) &&
|
|
248
|
+
(typeof options.asOfMs !== 'number' || edge.created_at <= options.asOfMs) &&
|
|
249
|
+
isTwinRefVisible(adapter, edge.subject_ref, options, new Set(), edgeCache) &&
|
|
250
|
+
isTwinRefVisible(adapter, edge.object_ref, options, new Set(), edgeCache));
|
|
251
|
+
const limit = typeof options.limit === 'number' && Number.isFinite(options.limit)
|
|
252
|
+
? Math.max(0, Math.floor(options.limit))
|
|
253
|
+
: edges.length;
|
|
254
|
+
return edges.slice(0, limit);
|
|
255
|
+
}
|
|
256
|
+
function normalizeEdgeTypes(edgeTypes) {
|
|
257
|
+
if (!Array.isArray(edgeTypes) || edgeTypes.length === 0) {
|
|
258
|
+
return new Set();
|
|
259
|
+
}
|
|
260
|
+
return new Set(edgeTypes);
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=ref-validation.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../db-manager.js';
|
|
2
|
+
import { type InsertTwinEdgeInput, type TwinEdgeRecord, type TwinRef } from './types.js';
|
|
3
|
+
type TwinEdgeReadAdapter = Pick<DatabaseAdapter, 'prepare'>;
|
|
4
|
+
type TwinEdgeAdapter = Pick<DatabaseAdapter, 'prepare' | 'transaction'>;
|
|
5
|
+
export declare function mapTwinEdgeRow(row: Record<string, unknown>): TwinEdgeRecord;
|
|
6
|
+
export declare function getTwinEdge(adapter: TwinEdgeReadAdapter, edgeId: string): TwinEdgeRecord | null;
|
|
7
|
+
export declare function listTwinEdgesForRefs(adapter: TwinEdgeReadAdapter, refs: readonly TwinRef[]): TwinEdgeRecord[];
|
|
8
|
+
export declare function insertTwinEdge(adapter: TwinEdgeAdapter, input: InsertTwinEdgeInput): TwinEdgeRecord;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=store.d.ts.map
|