@askexenow/exe-os 0.9.33 → 0.9.35
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/dist/bin/backfill-conversations.js +228 -12
- package/dist/bin/backfill-responses.js +228 -12
- package/dist/bin/backfill-vectors.js +31 -4
- package/dist/bin/cleanup-stale-review-tasks.js +235 -12
- package/dist/bin/cli.js +265 -22
- package/dist/bin/exe-assign.js +228 -12
- package/dist/bin/exe-boot.js +56 -13
- package/dist/bin/exe-cloud.js +3 -3
- package/dist/bin/exe-dispatch.js +243 -15
- package/dist/bin/exe-doctor.js +34 -9
- package/dist/bin/exe-export-behaviors.js +235 -12
- package/dist/bin/exe-forget.js +244 -18
- package/dist/bin/exe-gateway.js +243 -15
- package/dist/bin/exe-heartbeat.js +235 -12
- package/dist/bin/exe-kill.js +235 -12
- package/dist/bin/exe-launch-agent.js +126 -4
- package/dist/bin/exe-link.js +28 -5
- package/dist/bin/exe-pending-messages.js +235 -12
- package/dist/bin/exe-pending-notifications.js +235 -12
- package/dist/bin/exe-pending-reviews.js +235 -12
- package/dist/bin/exe-rename.js +26 -2
- package/dist/bin/exe-review.js +235 -12
- package/dist/bin/exe-search.js +235 -12
- package/dist/bin/exe-session-cleanup.js +243 -15
- package/dist/bin/exe-settings.js +1 -1
- package/dist/bin/exe-start-codex.js +126 -4
- package/dist/bin/exe-start-opencode.js +126 -4
- package/dist/bin/exe-status.js +235 -12
- package/dist/bin/exe-team.js +235 -12
- package/dist/bin/git-sweep.js +243 -15
- package/dist/bin/graph-backfill.js +110 -4
- package/dist/bin/graph-export.js +235 -12
- package/dist/bin/intercom-check.js +243 -15
- package/dist/bin/scan-tasks.js +243 -15
- package/dist/bin/setup.js +32 -9
- package/dist/bin/shard-migrate.js +110 -4
- package/dist/gateway/index.js +243 -15
- package/dist/hooks/bug-report-worker.js +243 -15
- package/dist/hooks/codex-stop-task-finalizer.js +238 -14
- package/dist/hooks/commit-complete.js +243 -15
- package/dist/hooks/error-recall.js +244 -12
- package/dist/hooks/exe-heartbeat-hook.js +9 -0
- package/dist/hooks/ingest.js +244 -12
- package/dist/hooks/instructions-loaded.js +244 -12
- package/dist/hooks/notification.js +244 -12
- package/dist/hooks/post-compact.js +247 -13
- package/dist/hooks/post-tool-combined.js +251 -12
- package/dist/hooks/pre-compact.js +255 -16
- package/dist/hooks/pre-tool-use.js +247 -13
- package/dist/hooks/prompt-submit.js +255 -16
- package/dist/hooks/session-end.js +255 -16
- package/dist/hooks/session-start.js +251 -12
- package/dist/hooks/stop.js +247 -13
- package/dist/hooks/subagent-stop.js +247 -13
- package/dist/hooks/summary-worker.js +36 -8
- package/dist/index.js +243 -15
- package/dist/lib/cloud-sync.js +28 -5
- package/dist/lib/consolidation.js +3 -1
- package/dist/lib/database.js +25 -1
- package/dist/lib/db.js +25 -1
- package/dist/lib/device-registry.js +26 -2
- package/dist/lib/exe-daemon.js +22905 -9125
- package/dist/lib/hybrid-search.js +235 -12
- package/dist/lib/schedules.js +31 -4
- package/dist/lib/store.js +228 -12
- package/dist/lib/tasks.js +8 -3
- package/dist/lib/tmux-routing.js +8 -3
- package/dist/mcp/server.js +411 -164
- package/dist/mcp/tools/create-task.js +20 -4
- package/dist/mcp/tools/deactivate-behavior.js +9 -0
- package/dist/mcp/tools/list-tasks.js +12 -1
- package/dist/mcp/tools/send-message.js +12 -1
- package/dist/mcp/tools/update-task.js +24 -3
- package/dist/runtime/index.js +243 -15
- package/dist/tui/App.js +243 -15
- package/package.json +1 -1
|
@@ -1586,6 +1586,7 @@ var init_db_daemon_client = __esm({
|
|
|
1586
1586
|
// src/lib/database.ts
|
|
1587
1587
|
var database_exports = {};
|
|
1588
1588
|
__export(database_exports, {
|
|
1589
|
+
SOFT_DELETE_RETENTION_DAYS: () => SOFT_DELETE_RETENTION_DAYS,
|
|
1589
1590
|
disposeDatabase: () => disposeDatabase,
|
|
1590
1591
|
disposeTurso: () => disposeTurso,
|
|
1591
1592
|
ensureSchema: () => ensureSchema,
|
|
@@ -1742,10 +1743,17 @@ async function ensureSchema() {
|
|
|
1742
1743
|
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1743
1744
|
END;
|
|
1744
1745
|
|
|
1745
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories
|
|
1746
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories
|
|
1747
|
+
WHEN new.status IS NULL OR new.status != 'deleted' BEGIN
|
|
1746
1748
|
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1747
1749
|
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1748
1750
|
END;
|
|
1751
|
+
|
|
1752
|
+
-- Soft-delete trigger: remove from FTS when status changes to 'deleted'
|
|
1753
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_soft_delete AFTER UPDATE ON memories
|
|
1754
|
+
WHEN new.status = 'deleted' AND (old.status IS NULL OR old.status != 'deleted') BEGIN
|
|
1755
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1756
|
+
END;
|
|
1749
1757
|
`);
|
|
1750
1758
|
await client.executeMultiple(`
|
|
1751
1759
|
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
@@ -2148,6 +2156,13 @@ async function ensureSchema() {
|
|
|
2148
2156
|
});
|
|
2149
2157
|
} catch {
|
|
2150
2158
|
}
|
|
2159
|
+
try {
|
|
2160
|
+
await client.execute({
|
|
2161
|
+
sql: `ALTER TABLE memories ADD COLUMN deleted_at TEXT`,
|
|
2162
|
+
args: []
|
|
2163
|
+
});
|
|
2164
|
+
} catch {
|
|
2165
|
+
}
|
|
2151
2166
|
try {
|
|
2152
2167
|
await client.execute({
|
|
2153
2168
|
sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
|
|
@@ -2198,6 +2213,14 @@ async function ensureSchema() {
|
|
|
2198
2213
|
);
|
|
2199
2214
|
} catch {
|
|
2200
2215
|
}
|
|
2216
|
+
try {
|
|
2217
|
+
await client.execute(
|
|
2218
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
|
|
2219
|
+
ON memories(content_hash, agent_id, project_name, memory_type)
|
|
2220
|
+
WHERE content_hash IS NOT NULL`
|
|
2221
|
+
);
|
|
2222
|
+
} catch {
|
|
2223
|
+
}
|
|
2201
2224
|
await client.executeMultiple(`
|
|
2202
2225
|
CREATE TABLE IF NOT EXISTS entities (
|
|
2203
2226
|
id TEXT PRIMARY KEY,
|
|
@@ -2684,7 +2707,7 @@ async function disposeDatabase() {
|
|
|
2684
2707
|
_resilientClient = null;
|
|
2685
2708
|
}
|
|
2686
2709
|
}
|
|
2687
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2710
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
|
|
2688
2711
|
var init_database = __esm({
|
|
2689
2712
|
"src/lib/database.ts"() {
|
|
2690
2713
|
"use strict";
|
|
@@ -2698,6 +2721,7 @@ var init_database = __esm({
|
|
|
2698
2721
|
_daemonClient = null;
|
|
2699
2722
|
_adapterClient = null;
|
|
2700
2723
|
initTurso = initDatabase;
|
|
2724
|
+
SOFT_DELETE_RETENTION_DAYS = 7;
|
|
2701
2725
|
disposeTurso = disposeDatabase;
|
|
2702
2726
|
}
|
|
2703
2727
|
});
|
|
@@ -2866,7 +2890,8 @@ async function ensureShardSchema(client) {
|
|
|
2866
2890
|
}
|
|
2867
2891
|
for (const idx of [
|
|
2868
2892
|
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
2869
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
2893
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
|
|
2894
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
|
|
2870
2895
|
]) {
|
|
2871
2896
|
try {
|
|
2872
2897
|
await client.execute(idx);
|
|
@@ -3285,7 +3310,6 @@ import { parseArgs } from "util";
|
|
|
3285
3310
|
// src/lib/store.ts
|
|
3286
3311
|
init_memory();
|
|
3287
3312
|
init_database();
|
|
3288
|
-
import { createHash } from "crypto";
|
|
3289
3313
|
|
|
3290
3314
|
// src/lib/keychain.ts
|
|
3291
3315
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
@@ -3518,6 +3542,190 @@ var StateBus = class {
|
|
|
3518
3542
|
};
|
|
3519
3543
|
var orgBus = new StateBus();
|
|
3520
3544
|
|
|
3545
|
+
// src/lib/memory-write-governor.ts
|
|
3546
|
+
import { createHash } from "crypto";
|
|
3547
|
+
var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
|
|
3548
|
+
"decision",
|
|
3549
|
+
"adr",
|
|
3550
|
+
"behavior",
|
|
3551
|
+
"procedure"
|
|
3552
|
+
]);
|
|
3553
|
+
var NOISE_DROP_PATTERNS = [
|
|
3554
|
+
/^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
|
|
3555
|
+
/^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
|
|
3556
|
+
/^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
|
|
3557
|
+
/^\s*Intercom is a speedup, not delivery/im,
|
|
3558
|
+
/^\s*Context bar reads as USAGE not remaining/im
|
|
3559
|
+
];
|
|
3560
|
+
var SKIP_EMBED_PATTERNS = [
|
|
3561
|
+
/tmux capture-pane\b/i,
|
|
3562
|
+
/docker ps\b/i,
|
|
3563
|
+
/docker images\b/i,
|
|
3564
|
+
/git status\b/i,
|
|
3565
|
+
/grep .*node_modules/i,
|
|
3566
|
+
/npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
|
|
3567
|
+
];
|
|
3568
|
+
function normalizeMemoryText(text) {
|
|
3569
|
+
return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
|
|
3570
|
+
}
|
|
3571
|
+
function classifyMemoryType(input) {
|
|
3572
|
+
if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
|
|
3573
|
+
const tool = input.tool_name.toLowerCase();
|
|
3574
|
+
const text = input.raw_text.toLowerCase();
|
|
3575
|
+
if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
|
|
3576
|
+
if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
|
|
3577
|
+
if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
|
|
3578
|
+
if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
|
|
3579
|
+
if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
|
|
3580
|
+
if (tool === "store_memory" || tool === "manual") return "observation";
|
|
3581
|
+
return "raw";
|
|
3582
|
+
}
|
|
3583
|
+
function shouldDropMemory(text) {
|
|
3584
|
+
const normalized = normalizeMemoryText(text);
|
|
3585
|
+
if (normalized.length < 10) return { drop: true, reason: "too_short" };
|
|
3586
|
+
if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
3587
|
+
return { drop: true, reason: "known_boilerplate_noise" };
|
|
3588
|
+
}
|
|
3589
|
+
return { drop: false };
|
|
3590
|
+
}
|
|
3591
|
+
function shouldSkipEmbedding(input) {
|
|
3592
|
+
const type = classifyMemoryType(input);
|
|
3593
|
+
if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
|
|
3594
|
+
if (type === "raw" && input.raw_text.length > 2e4) return true;
|
|
3595
|
+
if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
|
|
3596
|
+
return false;
|
|
3597
|
+
}
|
|
3598
|
+
function hashMemoryContent(text) {
|
|
3599
|
+
return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
|
|
3600
|
+
}
|
|
3601
|
+
function scopedDedupArgs(input) {
|
|
3602
|
+
return [input.contentHash, input.agentId, input.projectName, input.memoryType];
|
|
3603
|
+
}
|
|
3604
|
+
function governMemoryRecord(record) {
|
|
3605
|
+
const normalized = normalizeMemoryText(record.raw_text);
|
|
3606
|
+
const memoryType = classifyMemoryType({
|
|
3607
|
+
raw_text: normalized,
|
|
3608
|
+
agent_id: record.agent_id,
|
|
3609
|
+
project_name: record.project_name,
|
|
3610
|
+
tool_name: record.tool_name,
|
|
3611
|
+
memory_type: record.memory_type
|
|
3612
|
+
});
|
|
3613
|
+
const drop = shouldDropMemory(normalized);
|
|
3614
|
+
const skipEmbedding = shouldSkipEmbedding({
|
|
3615
|
+
raw_text: normalized,
|
|
3616
|
+
agent_id: record.agent_id,
|
|
3617
|
+
project_name: record.project_name,
|
|
3618
|
+
tool_name: record.tool_name,
|
|
3619
|
+
memory_type: memoryType
|
|
3620
|
+
});
|
|
3621
|
+
return {
|
|
3622
|
+
record: {
|
|
3623
|
+
...record,
|
|
3624
|
+
raw_text: normalized,
|
|
3625
|
+
memory_type: memoryType,
|
|
3626
|
+
vector: skipEmbedding ? null : record.vector
|
|
3627
|
+
},
|
|
3628
|
+
contentHash: hashMemoryContent(normalized),
|
|
3629
|
+
shouldDrop: drop.drop,
|
|
3630
|
+
dropReason: drop.reason,
|
|
3631
|
+
skipEmbedding,
|
|
3632
|
+
hygiene: {
|
|
3633
|
+
dedup: true,
|
|
3634
|
+
supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
|
|
3635
|
+
}
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
async function findScopedDuplicate(input) {
|
|
3639
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3640
|
+
const client = getClient2();
|
|
3641
|
+
const args = scopedDedupArgs(input);
|
|
3642
|
+
let sql = `SELECT id FROM memories
|
|
3643
|
+
WHERE content_hash = ?
|
|
3644
|
+
AND agent_id = ?
|
|
3645
|
+
AND project_name = ?
|
|
3646
|
+
AND COALESCE(memory_type, 'raw') = ?
|
|
3647
|
+
AND COALESCE(status, 'active') != 'deleted'`;
|
|
3648
|
+
if (input.excludeId) {
|
|
3649
|
+
sql += " AND id != ?";
|
|
3650
|
+
args.push(input.excludeId);
|
|
3651
|
+
}
|
|
3652
|
+
sql += " ORDER BY timestamp DESC LIMIT 1";
|
|
3653
|
+
const result = await client.execute({ sql, args });
|
|
3654
|
+
return result.rows[0]?.id ? String(result.rows[0].id) : null;
|
|
3655
|
+
}
|
|
3656
|
+
async function runPostWriteMemoryHygiene(memoryId) {
|
|
3657
|
+
try {
|
|
3658
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3659
|
+
const client = getClient2();
|
|
3660
|
+
const current = await client.execute({
|
|
3661
|
+
sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
|
|
3662
|
+
importance, timestamp
|
|
3663
|
+
FROM memories
|
|
3664
|
+
WHERE id = ?
|
|
3665
|
+
LIMIT 1`,
|
|
3666
|
+
args: [memoryId]
|
|
3667
|
+
});
|
|
3668
|
+
const row = current.rows[0];
|
|
3669
|
+
if (!row) return;
|
|
3670
|
+
const memoryType = String(row.memory_type ?? "raw");
|
|
3671
|
+
const contentHash = row.content_hash ? String(row.content_hash) : null;
|
|
3672
|
+
const agentId = String(row.agent_id);
|
|
3673
|
+
const projectName = String(row.project_name);
|
|
3674
|
+
if (contentHash) {
|
|
3675
|
+
await client.execute({
|
|
3676
|
+
sql: `UPDATE memories
|
|
3677
|
+
SET status = 'deleted',
|
|
3678
|
+
outcome = COALESCE(outcome, 'superseded')
|
|
3679
|
+
WHERE id != ?
|
|
3680
|
+
AND content_hash = ?
|
|
3681
|
+
AND agent_id = ?
|
|
3682
|
+
AND project_name = ?
|
|
3683
|
+
AND COALESCE(memory_type, 'raw') = ?
|
|
3684
|
+
AND COALESCE(status, 'active') = 'active'`,
|
|
3685
|
+
args: [memoryId, contentHash, agentId, projectName, memoryType]
|
|
3686
|
+
});
|
|
3687
|
+
}
|
|
3688
|
+
const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
|
|
3689
|
+
if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
|
|
3690
|
+
const old = await client.execute({
|
|
3691
|
+
sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
|
|
3692
|
+
args: [supersedesId]
|
|
3693
|
+
});
|
|
3694
|
+
const oldImportance = Number(old.rows[0]?.importance ?? 0);
|
|
3695
|
+
const newImportance = Number(row.importance ?? 0);
|
|
3696
|
+
await client.batch([
|
|
3697
|
+
{
|
|
3698
|
+
sql: `UPDATE memories
|
|
3699
|
+
SET status = 'archived',
|
|
3700
|
+
outcome = COALESCE(outcome, 'superseded')
|
|
3701
|
+
WHERE id = ?`,
|
|
3702
|
+
args: [supersedesId]
|
|
3703
|
+
},
|
|
3704
|
+
{
|
|
3705
|
+
sql: `UPDATE memories
|
|
3706
|
+
SET importance = MAX(COALESCE(importance, 5), ?),
|
|
3707
|
+
parent_memory_id = COALESCE(parent_memory_id, ?)
|
|
3708
|
+
WHERE id = ?`,
|
|
3709
|
+
args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
|
|
3710
|
+
}
|
|
3711
|
+
], "write");
|
|
3712
|
+
}
|
|
3713
|
+
} catch (err) {
|
|
3714
|
+
process.stderr.write(
|
|
3715
|
+
`[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
|
|
3716
|
+
`
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
function schedulePostWriteMemoryHygiene(memoryIds) {
|
|
3721
|
+
if (memoryIds.length === 0) return;
|
|
3722
|
+
const run = () => {
|
|
3723
|
+
void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
|
|
3724
|
+
};
|
|
3725
|
+
if (typeof setImmediate === "function") setImmediate(run);
|
|
3726
|
+
else setTimeout(run, 0);
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3521
3729
|
// src/lib/store.ts
|
|
3522
3730
|
var INIT_MAX_RETRIES = 3;
|
|
3523
3731
|
var INIT_RETRY_DELAY_MS = 1e3;
|
|
@@ -3640,17 +3848,24 @@ async function writeMemory(record) {
|
|
|
3640
3848
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
3641
3849
|
);
|
|
3642
3850
|
}
|
|
3643
|
-
const
|
|
3644
|
-
if (
|
|
3851
|
+
const governed = governMemoryRecord(record);
|
|
3852
|
+
if (governed.shouldDrop) return;
|
|
3853
|
+
record = governed.record;
|
|
3854
|
+
const contentHash = governed.contentHash;
|
|
3855
|
+
const memoryType = record.memory_type ?? "raw";
|
|
3856
|
+
if (_pendingRecords.some(
|
|
3857
|
+
(r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
|
|
3858
|
+
)) {
|
|
3645
3859
|
return;
|
|
3646
3860
|
}
|
|
3647
3861
|
try {
|
|
3648
|
-
const
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3862
|
+
const existing = await findScopedDuplicate({
|
|
3863
|
+
contentHash,
|
|
3864
|
+
agentId: record.agent_id,
|
|
3865
|
+
projectName: record.project_name,
|
|
3866
|
+
memoryType
|
|
3652
3867
|
});
|
|
3653
|
-
if (existing
|
|
3868
|
+
if (existing) return;
|
|
3654
3869
|
} catch {
|
|
3655
3870
|
}
|
|
3656
3871
|
const dbRow = {
|
|
@@ -3681,7 +3896,7 @@ async function writeMemory(record) {
|
|
|
3681
3896
|
tier: record.tier ?? classifyTier(record),
|
|
3682
3897
|
supersedes_id: record.supersedes_id ?? null,
|
|
3683
3898
|
draft: record.draft ? 1 : 0,
|
|
3684
|
-
memory_type:
|
|
3899
|
+
memory_type: memoryType,
|
|
3685
3900
|
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
3686
3901
|
content_hash: contentHash,
|
|
3687
3902
|
intent: record.intent ?? null,
|
|
@@ -3840,6 +4055,7 @@ async function flushBatch() {
|
|
|
3840
4055
|
const globalClient = getClient();
|
|
3841
4056
|
const globalStmts = batch.map(buildStmt);
|
|
3842
4057
|
await globalClient.batch(globalStmts, "write");
|
|
4058
|
+
schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
|
|
3843
4059
|
_pendingRecords.splice(0, batch.length);
|
|
3844
4060
|
try {
|
|
3845
4061
|
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
@@ -1586,6 +1586,7 @@ var init_db_daemon_client = __esm({
|
|
|
1586
1586
|
// src/lib/database.ts
|
|
1587
1587
|
var database_exports = {};
|
|
1588
1588
|
__export(database_exports, {
|
|
1589
|
+
SOFT_DELETE_RETENTION_DAYS: () => SOFT_DELETE_RETENTION_DAYS,
|
|
1589
1590
|
disposeDatabase: () => disposeDatabase,
|
|
1590
1591
|
disposeTurso: () => disposeTurso,
|
|
1591
1592
|
ensureSchema: () => ensureSchema,
|
|
@@ -1742,10 +1743,17 @@ async function ensureSchema() {
|
|
|
1742
1743
|
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1743
1744
|
END;
|
|
1744
1745
|
|
|
1745
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories
|
|
1746
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories
|
|
1747
|
+
WHEN new.status IS NULL OR new.status != 'deleted' BEGIN
|
|
1746
1748
|
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1747
1749
|
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
1748
1750
|
END;
|
|
1751
|
+
|
|
1752
|
+
-- Soft-delete trigger: remove from FTS when status changes to 'deleted'
|
|
1753
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_soft_delete AFTER UPDATE ON memories
|
|
1754
|
+
WHEN new.status = 'deleted' AND (old.status IS NULL OR old.status != 'deleted') BEGIN
|
|
1755
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
1756
|
+
END;
|
|
1749
1757
|
`);
|
|
1750
1758
|
await client.executeMultiple(`
|
|
1751
1759
|
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
@@ -2148,6 +2156,13 @@ async function ensureSchema() {
|
|
|
2148
2156
|
});
|
|
2149
2157
|
} catch {
|
|
2150
2158
|
}
|
|
2159
|
+
try {
|
|
2160
|
+
await client.execute({
|
|
2161
|
+
sql: `ALTER TABLE memories ADD COLUMN deleted_at TEXT`,
|
|
2162
|
+
args: []
|
|
2163
|
+
});
|
|
2164
|
+
} catch {
|
|
2165
|
+
}
|
|
2151
2166
|
try {
|
|
2152
2167
|
await client.execute({
|
|
2153
2168
|
sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
|
|
@@ -2198,6 +2213,14 @@ async function ensureSchema() {
|
|
|
2198
2213
|
);
|
|
2199
2214
|
} catch {
|
|
2200
2215
|
}
|
|
2216
|
+
try {
|
|
2217
|
+
await client.execute(
|
|
2218
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
|
|
2219
|
+
ON memories(content_hash, agent_id, project_name, memory_type)
|
|
2220
|
+
WHERE content_hash IS NOT NULL`
|
|
2221
|
+
);
|
|
2222
|
+
} catch {
|
|
2223
|
+
}
|
|
2201
2224
|
await client.executeMultiple(`
|
|
2202
2225
|
CREATE TABLE IF NOT EXISTS entities (
|
|
2203
2226
|
id TEXT PRIMARY KEY,
|
|
@@ -2684,7 +2707,7 @@ async function disposeDatabase() {
|
|
|
2684
2707
|
_resilientClient = null;
|
|
2685
2708
|
}
|
|
2686
2709
|
}
|
|
2687
|
-
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
|
|
2710
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
|
|
2688
2711
|
var init_database = __esm({
|
|
2689
2712
|
"src/lib/database.ts"() {
|
|
2690
2713
|
"use strict";
|
|
@@ -2698,6 +2721,7 @@ var init_database = __esm({
|
|
|
2698
2721
|
_daemonClient = null;
|
|
2699
2722
|
_adapterClient = null;
|
|
2700
2723
|
initTurso = initDatabase;
|
|
2724
|
+
SOFT_DELETE_RETENTION_DAYS = 7;
|
|
2701
2725
|
disposeTurso = disposeDatabase;
|
|
2702
2726
|
}
|
|
2703
2727
|
});
|
|
@@ -2866,7 +2890,8 @@ async function ensureShardSchema(client) {
|
|
|
2866
2890
|
}
|
|
2867
2891
|
for (const idx of [
|
|
2868
2892
|
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
2869
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
2893
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
|
|
2894
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
|
|
2870
2895
|
]) {
|
|
2871
2896
|
try {
|
|
2872
2897
|
await client.execute(idx);
|
|
@@ -3284,7 +3309,6 @@ import { homedir } from "os";
|
|
|
3284
3309
|
// src/lib/store.ts
|
|
3285
3310
|
init_memory();
|
|
3286
3311
|
init_database();
|
|
3287
|
-
import { createHash } from "crypto";
|
|
3288
3312
|
|
|
3289
3313
|
// src/lib/keychain.ts
|
|
3290
3314
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
@@ -3517,6 +3541,190 @@ var StateBus = class {
|
|
|
3517
3541
|
};
|
|
3518
3542
|
var orgBus = new StateBus();
|
|
3519
3543
|
|
|
3544
|
+
// src/lib/memory-write-governor.ts
|
|
3545
|
+
import { createHash } from "crypto";
|
|
3546
|
+
var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
|
|
3547
|
+
"decision",
|
|
3548
|
+
"adr",
|
|
3549
|
+
"behavior",
|
|
3550
|
+
"procedure"
|
|
3551
|
+
]);
|
|
3552
|
+
var NOISE_DROP_PATTERNS = [
|
|
3553
|
+
/^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
|
|
3554
|
+
/^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
|
|
3555
|
+
/^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
|
|
3556
|
+
/^\s*Intercom is a speedup, not delivery/im,
|
|
3557
|
+
/^\s*Context bar reads as USAGE not remaining/im
|
|
3558
|
+
];
|
|
3559
|
+
var SKIP_EMBED_PATTERNS = [
|
|
3560
|
+
/tmux capture-pane\b/i,
|
|
3561
|
+
/docker ps\b/i,
|
|
3562
|
+
/docker images\b/i,
|
|
3563
|
+
/git status\b/i,
|
|
3564
|
+
/grep .*node_modules/i,
|
|
3565
|
+
/npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
|
|
3566
|
+
];
|
|
3567
|
+
function normalizeMemoryText(text) {
|
|
3568
|
+
return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
|
|
3569
|
+
}
|
|
3570
|
+
function classifyMemoryType(input) {
|
|
3571
|
+
if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
|
|
3572
|
+
const tool = input.tool_name.toLowerCase();
|
|
3573
|
+
const text = input.raw_text.toLowerCase();
|
|
3574
|
+
if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
|
|
3575
|
+
if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
|
|
3576
|
+
if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
|
|
3577
|
+
if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
|
|
3578
|
+
if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
|
|
3579
|
+
if (tool === "store_memory" || tool === "manual") return "observation";
|
|
3580
|
+
return "raw";
|
|
3581
|
+
}
|
|
3582
|
+
function shouldDropMemory(text) {
|
|
3583
|
+
const normalized = normalizeMemoryText(text);
|
|
3584
|
+
if (normalized.length < 10) return { drop: true, reason: "too_short" };
|
|
3585
|
+
if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
3586
|
+
return { drop: true, reason: "known_boilerplate_noise" };
|
|
3587
|
+
}
|
|
3588
|
+
return { drop: false };
|
|
3589
|
+
}
|
|
3590
|
+
function shouldSkipEmbedding(input) {
|
|
3591
|
+
const type = classifyMemoryType(input);
|
|
3592
|
+
if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
|
|
3593
|
+
if (type === "raw" && input.raw_text.length > 2e4) return true;
|
|
3594
|
+
if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
|
|
3595
|
+
return false;
|
|
3596
|
+
}
|
|
3597
|
+
function hashMemoryContent(text) {
|
|
3598
|
+
return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
|
|
3599
|
+
}
|
|
3600
|
+
function scopedDedupArgs(input) {
|
|
3601
|
+
return [input.contentHash, input.agentId, input.projectName, input.memoryType];
|
|
3602
|
+
}
|
|
3603
|
+
function governMemoryRecord(record) {
|
|
3604
|
+
const normalized = normalizeMemoryText(record.raw_text);
|
|
3605
|
+
const memoryType = classifyMemoryType({
|
|
3606
|
+
raw_text: normalized,
|
|
3607
|
+
agent_id: record.agent_id,
|
|
3608
|
+
project_name: record.project_name,
|
|
3609
|
+
tool_name: record.tool_name,
|
|
3610
|
+
memory_type: record.memory_type
|
|
3611
|
+
});
|
|
3612
|
+
const drop = shouldDropMemory(normalized);
|
|
3613
|
+
const skipEmbedding = shouldSkipEmbedding({
|
|
3614
|
+
raw_text: normalized,
|
|
3615
|
+
agent_id: record.agent_id,
|
|
3616
|
+
project_name: record.project_name,
|
|
3617
|
+
tool_name: record.tool_name,
|
|
3618
|
+
memory_type: memoryType
|
|
3619
|
+
});
|
|
3620
|
+
return {
|
|
3621
|
+
record: {
|
|
3622
|
+
...record,
|
|
3623
|
+
raw_text: normalized,
|
|
3624
|
+
memory_type: memoryType,
|
|
3625
|
+
vector: skipEmbedding ? null : record.vector
|
|
3626
|
+
},
|
|
3627
|
+
contentHash: hashMemoryContent(normalized),
|
|
3628
|
+
shouldDrop: drop.drop,
|
|
3629
|
+
dropReason: drop.reason,
|
|
3630
|
+
skipEmbedding,
|
|
3631
|
+
hygiene: {
|
|
3632
|
+
dedup: true,
|
|
3633
|
+
supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3637
|
+
async function findScopedDuplicate(input) {
|
|
3638
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3639
|
+
const client = getClient2();
|
|
3640
|
+
const args = scopedDedupArgs(input);
|
|
3641
|
+
let sql = `SELECT id FROM memories
|
|
3642
|
+
WHERE content_hash = ?
|
|
3643
|
+
AND agent_id = ?
|
|
3644
|
+
AND project_name = ?
|
|
3645
|
+
AND COALESCE(memory_type, 'raw') = ?
|
|
3646
|
+
AND COALESCE(status, 'active') != 'deleted'`;
|
|
3647
|
+
if (input.excludeId) {
|
|
3648
|
+
sql += " AND id != ?";
|
|
3649
|
+
args.push(input.excludeId);
|
|
3650
|
+
}
|
|
3651
|
+
sql += " ORDER BY timestamp DESC LIMIT 1";
|
|
3652
|
+
const result = await client.execute({ sql, args });
|
|
3653
|
+
return result.rows[0]?.id ? String(result.rows[0].id) : null;
|
|
3654
|
+
}
|
|
3655
|
+
async function runPostWriteMemoryHygiene(memoryId) {
|
|
3656
|
+
try {
|
|
3657
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3658
|
+
const client = getClient2();
|
|
3659
|
+
const current = await client.execute({
|
|
3660
|
+
sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
|
|
3661
|
+
importance, timestamp
|
|
3662
|
+
FROM memories
|
|
3663
|
+
WHERE id = ?
|
|
3664
|
+
LIMIT 1`,
|
|
3665
|
+
args: [memoryId]
|
|
3666
|
+
});
|
|
3667
|
+
const row = current.rows[0];
|
|
3668
|
+
if (!row) return;
|
|
3669
|
+
const memoryType = String(row.memory_type ?? "raw");
|
|
3670
|
+
const contentHash = row.content_hash ? String(row.content_hash) : null;
|
|
3671
|
+
const agentId = String(row.agent_id);
|
|
3672
|
+
const projectName = String(row.project_name);
|
|
3673
|
+
if (contentHash) {
|
|
3674
|
+
await client.execute({
|
|
3675
|
+
sql: `UPDATE memories
|
|
3676
|
+
SET status = 'deleted',
|
|
3677
|
+
outcome = COALESCE(outcome, 'superseded')
|
|
3678
|
+
WHERE id != ?
|
|
3679
|
+
AND content_hash = ?
|
|
3680
|
+
AND agent_id = ?
|
|
3681
|
+
AND project_name = ?
|
|
3682
|
+
AND COALESCE(memory_type, 'raw') = ?
|
|
3683
|
+
AND COALESCE(status, 'active') = 'active'`,
|
|
3684
|
+
args: [memoryId, contentHash, agentId, projectName, memoryType]
|
|
3685
|
+
});
|
|
3686
|
+
}
|
|
3687
|
+
const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
|
|
3688
|
+
if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
|
|
3689
|
+
const old = await client.execute({
|
|
3690
|
+
sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
|
|
3691
|
+
args: [supersedesId]
|
|
3692
|
+
});
|
|
3693
|
+
const oldImportance = Number(old.rows[0]?.importance ?? 0);
|
|
3694
|
+
const newImportance = Number(row.importance ?? 0);
|
|
3695
|
+
await client.batch([
|
|
3696
|
+
{
|
|
3697
|
+
sql: `UPDATE memories
|
|
3698
|
+
SET status = 'archived',
|
|
3699
|
+
outcome = COALESCE(outcome, 'superseded')
|
|
3700
|
+
WHERE id = ?`,
|
|
3701
|
+
args: [supersedesId]
|
|
3702
|
+
},
|
|
3703
|
+
{
|
|
3704
|
+
sql: `UPDATE memories
|
|
3705
|
+
SET importance = MAX(COALESCE(importance, 5), ?),
|
|
3706
|
+
parent_memory_id = COALESCE(parent_memory_id, ?)
|
|
3707
|
+
WHERE id = ?`,
|
|
3708
|
+
args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
|
|
3709
|
+
}
|
|
3710
|
+
], "write");
|
|
3711
|
+
}
|
|
3712
|
+
} catch (err) {
|
|
3713
|
+
process.stderr.write(
|
|
3714
|
+
`[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
|
|
3715
|
+
`
|
|
3716
|
+
);
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
function schedulePostWriteMemoryHygiene(memoryIds) {
|
|
3720
|
+
if (memoryIds.length === 0) return;
|
|
3721
|
+
const run = () => {
|
|
3722
|
+
void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
|
|
3723
|
+
};
|
|
3724
|
+
if (typeof setImmediate === "function") setImmediate(run);
|
|
3725
|
+
else setTimeout(run, 0);
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3520
3728
|
// src/lib/store.ts
|
|
3521
3729
|
var INIT_MAX_RETRIES = 3;
|
|
3522
3730
|
var INIT_RETRY_DELAY_MS = 1e3;
|
|
@@ -3639,17 +3847,24 @@ async function writeMemory(record) {
|
|
|
3639
3847
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
3640
3848
|
);
|
|
3641
3849
|
}
|
|
3642
|
-
const
|
|
3643
|
-
if (
|
|
3850
|
+
const governed = governMemoryRecord(record);
|
|
3851
|
+
if (governed.shouldDrop) return;
|
|
3852
|
+
record = governed.record;
|
|
3853
|
+
const contentHash = governed.contentHash;
|
|
3854
|
+
const memoryType = record.memory_type ?? "raw";
|
|
3855
|
+
if (_pendingRecords.some(
|
|
3856
|
+
(r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
|
|
3857
|
+
)) {
|
|
3644
3858
|
return;
|
|
3645
3859
|
}
|
|
3646
3860
|
try {
|
|
3647
|
-
const
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3861
|
+
const existing = await findScopedDuplicate({
|
|
3862
|
+
contentHash,
|
|
3863
|
+
agentId: record.agent_id,
|
|
3864
|
+
projectName: record.project_name,
|
|
3865
|
+
memoryType
|
|
3651
3866
|
});
|
|
3652
|
-
if (existing
|
|
3867
|
+
if (existing) return;
|
|
3653
3868
|
} catch {
|
|
3654
3869
|
}
|
|
3655
3870
|
const dbRow = {
|
|
@@ -3680,7 +3895,7 @@ async function writeMemory(record) {
|
|
|
3680
3895
|
tier: record.tier ?? classifyTier(record),
|
|
3681
3896
|
supersedes_id: record.supersedes_id ?? null,
|
|
3682
3897
|
draft: record.draft ? 1 : 0,
|
|
3683
|
-
memory_type:
|
|
3898
|
+
memory_type: memoryType,
|
|
3684
3899
|
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
3685
3900
|
content_hash: contentHash,
|
|
3686
3901
|
intent: record.intent ?? null,
|
|
@@ -3839,6 +4054,7 @@ async function flushBatch() {
|
|
|
3839
4054
|
const globalClient = getClient();
|
|
3840
4055
|
const globalStmts = batch.map(buildStmt);
|
|
3841
4056
|
await globalClient.batch(globalStmts, "write");
|
|
4057
|
+
schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
|
|
3842
4058
|
_pendingRecords.splice(0, batch.length);
|
|
3843
4059
|
try {
|
|
3844
4060
|
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|