@askexenow/exe-os 0.9.34 → 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 +210 -10
- package/dist/bin/backfill-responses.js +210 -10
- package/dist/bin/backfill-vectors.js +13 -2
- package/dist/bin/cleanup-stale-review-tasks.js +217 -10
- package/dist/bin/cli.js +222 -11
- package/dist/bin/exe-assign.js +210 -10
- package/dist/bin/exe-boot.js +24 -3
- package/dist/bin/exe-dispatch.js +222 -11
- package/dist/bin/exe-doctor.js +13 -2
- package/dist/bin/exe-export-behaviors.js +217 -10
- package/dist/bin/exe-forget.js +217 -10
- package/dist/bin/exe-gateway.js +222 -11
- package/dist/bin/exe-heartbeat.js +217 -10
- package/dist/bin/exe-kill.js +217 -10
- package/dist/bin/exe-launch-agent.js +92 -2
- package/dist/bin/exe-link.js +8 -0
- package/dist/bin/exe-pending-messages.js +217 -10
- package/dist/bin/exe-pending-notifications.js +217 -10
- package/dist/bin/exe-pending-reviews.js +217 -10
- package/dist/bin/exe-rename.js +8 -0
- package/dist/bin/exe-review.js +217 -10
- package/dist/bin/exe-search.js +217 -10
- package/dist/bin/exe-session-cleanup.js +222 -11
- package/dist/bin/exe-start-codex.js +92 -2
- package/dist/bin/exe-start-opencode.js +92 -2
- package/dist/bin/exe-status.js +217 -10
- package/dist/bin/exe-team.js +217 -10
- package/dist/bin/git-sweep.js +222 -11
- package/dist/bin/graph-backfill.js +92 -2
- package/dist/bin/graph-export.js +217 -10
- package/dist/bin/intercom-check.js +222 -11
- package/dist/bin/scan-tasks.js +222 -11
- package/dist/bin/setup.js +8 -0
- package/dist/bin/shard-migrate.js +92 -2
- package/dist/gateway/index.js +222 -11
- package/dist/hooks/bug-report-worker.js +222 -11
- package/dist/hooks/codex-stop-task-finalizer.js +217 -10
- package/dist/hooks/commit-complete.js +222 -11
- package/dist/hooks/error-recall.js +217 -10
- package/dist/hooks/ingest.js +217 -10
- package/dist/hooks/instructions-loaded.js +217 -10
- package/dist/hooks/notification.js +217 -10
- package/dist/hooks/post-compact.js +217 -10
- package/dist/hooks/post-tool-combined.js +217 -10
- package/dist/hooks/pre-compact.js +222 -11
- package/dist/hooks/pre-tool-use.js +217 -10
- package/dist/hooks/prompt-submit.js +222 -11
- package/dist/hooks/session-end.js +222 -11
- package/dist/hooks/session-start.js +217 -10
- package/dist/hooks/stop.js +217 -10
- package/dist/hooks/subagent-stop.js +217 -10
- package/dist/hooks/summary-worker.js +14 -1
- package/dist/index.js +222 -11
- package/dist/lib/cloud-sync.js +8 -0
- package/dist/lib/consolidation.js +3 -1
- package/dist/lib/database.js +8 -0
- package/dist/lib/db.js +8 -0
- package/dist/lib/device-registry.js +8 -0
- package/dist/lib/exe-daemon.js +1667 -1413
- package/dist/lib/hybrid-search.js +217 -10
- package/dist/lib/schedules.js +13 -2
- package/dist/lib/store.js +210 -10
- package/dist/lib/tasks.js +5 -1
- package/dist/lib/tmux-routing.js +5 -1
- package/dist/mcp/server.js +222 -11
- package/dist/mcp/tools/create-task.js +5 -1
- package/dist/mcp/tools/update-task.js +5 -1
- package/dist/runtime/index.js +222 -11
- package/dist/tui/App.js +222 -11
- package/package.json +1 -1
|
@@ -2213,6 +2213,14 @@ async function ensureSchema() {
|
|
|
2213
2213
|
);
|
|
2214
2214
|
} catch {
|
|
2215
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
|
+
}
|
|
2216
2224
|
await client.executeMultiple(`
|
|
2217
2225
|
CREATE TABLE IF NOT EXISTS entities (
|
|
2218
2226
|
id TEXT PRIMARY KEY,
|
|
@@ -2882,7 +2890,8 @@ async function ensureShardSchema(client) {
|
|
|
2882
2890
|
}
|
|
2883
2891
|
for (const idx of [
|
|
2884
2892
|
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
2885
|
-
"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"
|
|
2886
2895
|
]) {
|
|
2887
2896
|
try {
|
|
2888
2897
|
await client.execute(idx);
|
|
@@ -3301,7 +3310,6 @@ import { parseArgs } from "util";
|
|
|
3301
3310
|
// src/lib/store.ts
|
|
3302
3311
|
init_memory();
|
|
3303
3312
|
init_database();
|
|
3304
|
-
import { createHash } from "crypto";
|
|
3305
3313
|
|
|
3306
3314
|
// src/lib/keychain.ts
|
|
3307
3315
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
@@ -3534,6 +3542,190 @@ var StateBus = class {
|
|
|
3534
3542
|
};
|
|
3535
3543
|
var orgBus = new StateBus();
|
|
3536
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
|
+
|
|
3537
3729
|
// src/lib/store.ts
|
|
3538
3730
|
var INIT_MAX_RETRIES = 3;
|
|
3539
3731
|
var INIT_RETRY_DELAY_MS = 1e3;
|
|
@@ -3656,17 +3848,24 @@ async function writeMemory(record) {
|
|
|
3656
3848
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
3657
3849
|
);
|
|
3658
3850
|
}
|
|
3659
|
-
const
|
|
3660
|
-
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
|
+
)) {
|
|
3661
3859
|
return;
|
|
3662
3860
|
}
|
|
3663
3861
|
try {
|
|
3664
|
-
const
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3862
|
+
const existing = await findScopedDuplicate({
|
|
3863
|
+
contentHash,
|
|
3864
|
+
agentId: record.agent_id,
|
|
3865
|
+
projectName: record.project_name,
|
|
3866
|
+
memoryType
|
|
3668
3867
|
});
|
|
3669
|
-
if (existing
|
|
3868
|
+
if (existing) return;
|
|
3670
3869
|
} catch {
|
|
3671
3870
|
}
|
|
3672
3871
|
const dbRow = {
|
|
@@ -3697,7 +3896,7 @@ async function writeMemory(record) {
|
|
|
3697
3896
|
tier: record.tier ?? classifyTier(record),
|
|
3698
3897
|
supersedes_id: record.supersedes_id ?? null,
|
|
3699
3898
|
draft: record.draft ? 1 : 0,
|
|
3700
|
-
memory_type:
|
|
3899
|
+
memory_type: memoryType,
|
|
3701
3900
|
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
3702
3901
|
content_hash: contentHash,
|
|
3703
3902
|
intent: record.intent ?? null,
|
|
@@ -3856,6 +4055,7 @@ async function flushBatch() {
|
|
|
3856
4055
|
const globalClient = getClient();
|
|
3857
4056
|
const globalStmts = batch.map(buildStmt);
|
|
3858
4057
|
await globalClient.batch(globalStmts, "write");
|
|
4058
|
+
schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
|
|
3859
4059
|
_pendingRecords.splice(0, batch.length);
|
|
3860
4060
|
try {
|
|
3861
4061
|
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
@@ -2213,6 +2213,14 @@ async function ensureSchema() {
|
|
|
2213
2213
|
);
|
|
2214
2214
|
} catch {
|
|
2215
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
|
+
}
|
|
2216
2224
|
await client.executeMultiple(`
|
|
2217
2225
|
CREATE TABLE IF NOT EXISTS entities (
|
|
2218
2226
|
id TEXT PRIMARY KEY,
|
|
@@ -2882,7 +2890,8 @@ async function ensureShardSchema(client) {
|
|
|
2882
2890
|
}
|
|
2883
2891
|
for (const idx of [
|
|
2884
2892
|
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
2885
|
-
"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"
|
|
2886
2895
|
]) {
|
|
2887
2896
|
try {
|
|
2888
2897
|
await client.execute(idx);
|
|
@@ -3300,7 +3309,6 @@ import { homedir } from "os";
|
|
|
3300
3309
|
// src/lib/store.ts
|
|
3301
3310
|
init_memory();
|
|
3302
3311
|
init_database();
|
|
3303
|
-
import { createHash } from "crypto";
|
|
3304
3312
|
|
|
3305
3313
|
// src/lib/keychain.ts
|
|
3306
3314
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
@@ -3533,6 +3541,190 @@ var StateBus = class {
|
|
|
3533
3541
|
};
|
|
3534
3542
|
var orgBus = new StateBus();
|
|
3535
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
|
+
|
|
3536
3728
|
// src/lib/store.ts
|
|
3537
3729
|
var INIT_MAX_RETRIES = 3;
|
|
3538
3730
|
var INIT_RETRY_DELAY_MS = 1e3;
|
|
@@ -3655,17 +3847,24 @@ async function writeMemory(record) {
|
|
|
3655
3847
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
3656
3848
|
);
|
|
3657
3849
|
}
|
|
3658
|
-
const
|
|
3659
|
-
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
|
+
)) {
|
|
3660
3858
|
return;
|
|
3661
3859
|
}
|
|
3662
3860
|
try {
|
|
3663
|
-
const
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3861
|
+
const existing = await findScopedDuplicate({
|
|
3862
|
+
contentHash,
|
|
3863
|
+
agentId: record.agent_id,
|
|
3864
|
+
projectName: record.project_name,
|
|
3865
|
+
memoryType
|
|
3667
3866
|
});
|
|
3668
|
-
if (existing
|
|
3867
|
+
if (existing) return;
|
|
3669
3868
|
} catch {
|
|
3670
3869
|
}
|
|
3671
3870
|
const dbRow = {
|
|
@@ -3696,7 +3895,7 @@ async function writeMemory(record) {
|
|
|
3696
3895
|
tier: record.tier ?? classifyTier(record),
|
|
3697
3896
|
supersedes_id: record.supersedes_id ?? null,
|
|
3698
3897
|
draft: record.draft ? 1 : 0,
|
|
3699
|
-
memory_type:
|
|
3898
|
+
memory_type: memoryType,
|
|
3700
3899
|
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
3701
3900
|
content_hash: contentHash,
|
|
3702
3901
|
intent: record.intent ?? null,
|
|
@@ -3855,6 +4054,7 @@ async function flushBatch() {
|
|
|
3855
4054
|
const globalClient = getClient();
|
|
3856
4055
|
const globalStmts = batch.map(buildStmt);
|
|
3857
4056
|
await globalClient.batch(globalStmts, "write");
|
|
4057
|
+
schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
|
|
3858
4058
|
_pendingRecords.splice(0, batch.length);
|
|
3859
4059
|
try {
|
|
3860
4060
|
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
@@ -2209,6 +2209,14 @@ async function ensureSchema() {
|
|
|
2209
2209
|
);
|
|
2210
2210
|
} catch {
|
|
2211
2211
|
}
|
|
2212
|
+
try {
|
|
2213
|
+
await client.execute(
|
|
2214
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
|
|
2215
|
+
ON memories(content_hash, agent_id, project_name, memory_type)
|
|
2216
|
+
WHERE content_hash IS NOT NULL`
|
|
2217
|
+
);
|
|
2218
|
+
} catch {
|
|
2219
|
+
}
|
|
2212
2220
|
await client.executeMultiple(`
|
|
2213
2221
|
CREATE TABLE IF NOT EXISTS entities (
|
|
2214
2222
|
id TEXT PRIMARY KEY,
|
|
@@ -2878,7 +2886,8 @@ async function ensureShardSchema(client) {
|
|
|
2878
2886
|
}
|
|
2879
2887
|
for (const idx of [
|
|
2880
2888
|
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
2881
|
-
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
2889
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
|
|
2890
|
+
"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"
|
|
2882
2891
|
]) {
|
|
2883
2892
|
try {
|
|
2884
2893
|
await client.execute(idx);
|
|
@@ -3288,7 +3297,6 @@ ${p.content}`).join("\n\n");
|
|
|
3288
3297
|
// src/lib/store.ts
|
|
3289
3298
|
init_memory();
|
|
3290
3299
|
init_database();
|
|
3291
|
-
import { createHash } from "crypto";
|
|
3292
3300
|
|
|
3293
3301
|
// src/lib/keychain.ts
|
|
3294
3302
|
import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
|
|
@@ -3521,6 +3529,9 @@ var StateBus = class {
|
|
|
3521
3529
|
};
|
|
3522
3530
|
var orgBus = new StateBus();
|
|
3523
3531
|
|
|
3532
|
+
// src/lib/memory-write-governor.ts
|
|
3533
|
+
import { createHash } from "crypto";
|
|
3534
|
+
|
|
3524
3535
|
// src/lib/store.ts
|
|
3525
3536
|
var INIT_MAX_RETRIES = 3;
|
|
3526
3537
|
var INIT_RETRY_DELAY_MS = 1e3;
|