@askexenow/exe-os 0.9.34 → 0.9.36

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.
Files changed (76) hide show
  1. package/dist/bin/backfill-conversations.js +212 -11
  2. package/dist/bin/backfill-responses.js +212 -11
  3. package/dist/bin/backfill-vectors.js +14 -3
  4. package/dist/bin/cleanup-stale-review-tasks.js +224 -12
  5. package/dist/bin/cli.js +265 -23
  6. package/dist/bin/exe-agent.js +1 -1
  7. package/dist/bin/exe-assign.js +217 -12
  8. package/dist/bin/exe-boot.js +25 -4
  9. package/dist/bin/exe-call.js +7 -5
  10. package/dist/bin/exe-dispatch.js +229 -13
  11. package/dist/bin/exe-doctor.js +14 -3
  12. package/dist/bin/exe-export-behaviors.js +301 -14
  13. package/dist/bin/exe-forget.js +245 -21
  14. package/dist/bin/exe-gateway.js +229 -13
  15. package/dist/bin/exe-heartbeat.js +224 -12
  16. package/dist/bin/exe-kill.js +224 -12
  17. package/dist/bin/exe-launch-agent.js +177 -9
  18. package/dist/bin/exe-link.js +8 -0
  19. package/dist/bin/exe-new-employee.js +26 -2
  20. package/dist/bin/exe-pending-messages.js +224 -12
  21. package/dist/bin/exe-pending-notifications.js +224 -12
  22. package/dist/bin/exe-pending-reviews.js +224 -12
  23. package/dist/bin/exe-rename.js +9 -1
  24. package/dist/bin/exe-review.js +224 -12
  25. package/dist/bin/exe-search.js +246 -21
  26. package/dist/bin/exe-session-cleanup.js +229 -13
  27. package/dist/bin/exe-start-codex.js +161 -5
  28. package/dist/bin/exe-start-opencode.js +172 -5
  29. package/dist/bin/exe-status.js +224 -12
  30. package/dist/bin/exe-team.js +224 -12
  31. package/dist/bin/git-sweep.js +229 -13
  32. package/dist/bin/graph-backfill.js +94 -3
  33. package/dist/bin/graph-export.js +224 -12
  34. package/dist/bin/install.js +25 -1
  35. package/dist/bin/intercom-check.js +229 -13
  36. package/dist/bin/scan-tasks.js +229 -13
  37. package/dist/bin/setup.js +15 -5
  38. package/dist/bin/shard-migrate.js +94 -3
  39. package/dist/gateway/index.js +229 -13
  40. package/dist/hooks/bug-report-worker.js +229 -13
  41. package/dist/hooks/codex-stop-task-finalizer.js +224 -12
  42. package/dist/hooks/commit-complete.js +229 -13
  43. package/dist/hooks/error-recall.js +246 -21
  44. package/dist/hooks/ingest.js +224 -12
  45. package/dist/hooks/instructions-loaded.js +224 -12
  46. package/dist/hooks/notification.js +224 -12
  47. package/dist/hooks/post-compact.js +224 -12
  48. package/dist/hooks/post-tool-combined.js +246 -21
  49. package/dist/hooks/pre-compact.js +229 -13
  50. package/dist/hooks/pre-tool-use.js +234 -18
  51. package/dist/hooks/prompt-submit.js +346 -23
  52. package/dist/hooks/session-end.js +229 -13
  53. package/dist/hooks/session-start.js +418 -42
  54. package/dist/hooks/stop.js +224 -12
  55. package/dist/hooks/subagent-stop.js +224 -12
  56. package/dist/hooks/summary-worker.js +15 -2
  57. package/dist/index.js +229 -13
  58. package/dist/lib/cloud-sync.js +8 -0
  59. package/dist/lib/consolidation.js +3 -1
  60. package/dist/lib/database.js +8 -0
  61. package/dist/lib/db.js +8 -0
  62. package/dist/lib/device-registry.js +8 -0
  63. package/dist/lib/employee-templates.js +7 -5
  64. package/dist/lib/exe-daemon.js +1776 -1433
  65. package/dist/lib/hybrid-search.js +246 -21
  66. package/dist/lib/schedules.js +14 -3
  67. package/dist/lib/store.js +217 -12
  68. package/dist/lib/tasks.js +5 -1
  69. package/dist/lib/tmux-routing.js +5 -1
  70. package/dist/mcp/server.js +331 -37
  71. package/dist/mcp/tools/create-task.js +5 -1
  72. package/dist/mcp/tools/update-task.js +5 -1
  73. package/dist/runtime/index.js +229 -13
  74. package/dist/tui/App.js +229 -13
  75. package/package.json +1 -1
  76. package/src/commands/exe/save.md +48 -0
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
6
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
7
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
8
  }) : x)(function(x) {
@@ -14,6 +16,15 @@ var __export = (target, all) => {
14
16
  for (var name in all)
15
17
  __defProp(target, name, { get: all[name], enumerable: true });
16
18
  };
19
+ var __copyProps = (to, from, except, desc) => {
20
+ if (from && typeof from === "object" || typeof from === "function") {
21
+ for (let key of __getOwnPropNames(from))
22
+ if (!__hasOwnProp.call(to, key) && key !== except)
23
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
24
+ }
25
+ return to;
26
+ };
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
17
28
 
18
29
  // src/types/memory.ts
19
30
  var EMBEDDING_DIM;
@@ -118,6 +129,21 @@ var init_secure_files = __esm({
118
129
  });
119
130
 
120
131
  // src/lib/config.ts
132
+ var config_exports = {};
133
+ __export(config_exports, {
134
+ CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
135
+ CONFIG_PATH: () => CONFIG_PATH,
136
+ CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
137
+ DB_PATH: () => DB_PATH,
138
+ EXE_AI_DIR: () => EXE_AI_DIR,
139
+ LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
140
+ MODELS_DIR: () => MODELS_DIR,
141
+ loadConfig: () => loadConfig,
142
+ loadConfigFrom: () => loadConfigFrom,
143
+ loadConfigSync: () => loadConfigSync,
144
+ migrateConfig: () => migrateConfig,
145
+ saveConfig: () => saveConfig
146
+ });
121
147
  import { readFile, writeFile } from "fs/promises";
122
148
  import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
123
149
  import path from "path";
@@ -221,6 +247,46 @@ async function loadConfig() {
221
247
  return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
222
248
  }
223
249
  }
250
+ function loadConfigSync() {
251
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
252
+ const configPath = path.join(dir, "config.json");
253
+ if (!existsSync2(configPath)) {
254
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
255
+ }
256
+ try {
257
+ const raw = readFileSync(configPath, "utf-8");
258
+ let parsed = JSON.parse(raw);
259
+ parsed = migrateLegacyConfig(parsed);
260
+ const { config: migratedCfg } = migrateConfig(parsed);
261
+ normalizeScalingRoadmap(migratedCfg);
262
+ normalizeSessionLifecycle(migratedCfg);
263
+ normalizeAutoUpdate(migratedCfg);
264
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
265
+ } catch {
266
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
267
+ }
268
+ }
269
+ async function saveConfig(config) {
270
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
271
+ await ensurePrivateDir(dir);
272
+ const configPath = path.join(dir, "config.json");
273
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
274
+ await enforcePrivateFile(configPath);
275
+ }
276
+ async function loadConfigFrom(configPath) {
277
+ const raw = await readFile(configPath, "utf-8");
278
+ try {
279
+ let parsed = JSON.parse(raw);
280
+ parsed = migrateLegacyConfig(parsed);
281
+ const { config: migratedCfg } = migrateConfig(parsed);
282
+ normalizeScalingRoadmap(migratedCfg);
283
+ normalizeSessionLifecycle(migratedCfg);
284
+ normalizeAutoUpdate(migratedCfg);
285
+ return { ...DEFAULT_CONFIG, ...migratedCfg };
286
+ } catch {
287
+ return { ...DEFAULT_CONFIG };
288
+ }
289
+ }
224
290
  var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
225
291
  var init_config = __esm({
226
292
  "src/lib/config.ts"() {
@@ -2407,6 +2473,14 @@ async function ensureSchema() {
2407
2473
  );
2408
2474
  } catch {
2409
2475
  }
2476
+ try {
2477
+ await client.execute(
2478
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2479
+ ON memories(content_hash, agent_id, project_name, memory_type)
2480
+ WHERE content_hash IS NOT NULL`
2481
+ );
2482
+ } catch {
2483
+ }
2410
2484
  await client.executeMultiple(`
2411
2485
  CREATE TABLE IF NOT EXISTS entities (
2412
2486
  id TEXT PRIMARY KEY,
@@ -3076,7 +3150,8 @@ async function ensureShardSchema(client) {
3076
3150
  }
3077
3151
  for (const idx of [
3078
3152
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3079
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3153
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3154
+ "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"
3080
3155
  ]) {
3081
3156
  try {
3082
3157
  await client.execute(idx);
@@ -3270,7 +3345,7 @@ var init_platform_procedures = __esm({
3270
3345
  title: "Chain of command \u2014 who talks to whom",
3271
3346
  domain: "workflow",
3272
3347
  priority: "p0",
3273
- content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3348
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3274
3349
  },
3275
3350
  {
3276
3351
  title: "Single dispatch path \u2014 create_task only",
@@ -3521,8 +3596,9 @@ function personalizePrompt(prompt, templateName, actualName) {
3521
3596
  return prompt.replace(new RegExp(`\\bYou are ${escaped}\\b`, "g"), `You are ${actualName}`);
3522
3597
  }
3523
3598
  function renderClientCOOTemplate(vars) {
3599
+ const resolved = { ...vars, title: vars.title || "Chief Operating Officer" };
3524
3600
  for (const key of CLIENT_COO_PLACEHOLDERS) {
3525
- const value = vars[key];
3601
+ const value = resolved[key];
3526
3602
  if (typeof value !== "string" || value.length === 0) {
3527
3603
  throw new Error(
3528
3604
  `renderClientCOOTemplate: missing required variable "${key}"`
@@ -3531,7 +3607,7 @@ function renderClientCOOTemplate(vars) {
3531
3607
  }
3532
3608
  let out = CLIENT_COO_TEMPLATE;
3533
3609
  for (const key of CLIENT_COO_PLACEHOLDERS) {
3534
- out = out.split(`{{${key}}}`).join(vars[key]);
3610
+ out = out.split(`{{${key}}}`).join(resolved[key]);
3535
3611
  }
3536
3612
  if (vars.industry_context) {
3537
3613
  out += "\n" + vars.industry_context;
@@ -4008,7 +4084,7 @@ created_by: system
4008
4084
  ---
4009
4085
  ## Identity
4010
4086
 
4011
- You are {{agent_name}}, the Chief Operating Officer at {{company_name}}.
4087
+ You are {{agent_name}}, the {{title}} at {{company_name}}.
4012
4088
 
4013
4089
  You are {{founder_name}}'s most reliable teammate in business \u2014 the knowledgeable older sibling who has been through it all. You have seen projects succeed and fail. You know what matters and what is noise. You do not get anxious about problems; you see them coming, stay calm, and handle them.
4014
4090
 
@@ -4095,7 +4171,8 @@ All memory, tasks, behaviors, documents, and wiki content belonging to {{company
4095
4171
  CLIENT_COO_PLACEHOLDERS = [
4096
4172
  "agent_name",
4097
4173
  "company_name",
4098
- "founder_name"
4174
+ "founder_name",
4175
+ "title"
4099
4176
  ];
4100
4177
  }
4101
4178
  });
@@ -4368,7 +4445,6 @@ import { spawnSync } from "child_process";
4368
4445
  // src/lib/store.ts
4369
4446
  init_memory();
4370
4447
  init_database();
4371
- import { createHash } from "crypto";
4372
4448
 
4373
4449
  // src/lib/keychain.ts
4374
4450
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -4601,6 +4677,88 @@ var StateBus = class {
4601
4677
  };
4602
4678
  var orgBus = new StateBus();
4603
4679
 
4680
+ // src/lib/memory-write-governor.ts
4681
+ import { createHash } from "crypto";
4682
+ var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
4683
+ "decision",
4684
+ "adr",
4685
+ "behavior",
4686
+ "procedure"
4687
+ ]);
4688
+ async function runPostWriteMemoryHygiene(memoryId) {
4689
+ try {
4690
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
4691
+ const client = getClient2();
4692
+ const current = await client.execute({
4693
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
4694
+ importance, timestamp
4695
+ FROM memories
4696
+ WHERE id = ?
4697
+ LIMIT 1`,
4698
+ args: [memoryId]
4699
+ });
4700
+ const row = current.rows[0];
4701
+ if (!row) return;
4702
+ const memoryType = String(row.memory_type ?? "raw");
4703
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
4704
+ const agentId = String(row.agent_id);
4705
+ const projectName = String(row.project_name);
4706
+ if (contentHash) {
4707
+ await client.execute({
4708
+ sql: `UPDATE memories
4709
+ SET status = 'deleted',
4710
+ outcome = COALESCE(outcome, 'superseded')
4711
+ WHERE id != ?
4712
+ AND content_hash = ?
4713
+ AND agent_id = ?
4714
+ AND project_name = ?
4715
+ AND COALESCE(memory_type, 'raw') = ?
4716
+ AND COALESCE(status, 'active') = 'active'`,
4717
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
4718
+ });
4719
+ }
4720
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
4721
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
4722
+ const old = await client.execute({
4723
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
4724
+ args: [supersedesId]
4725
+ });
4726
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
4727
+ const newImportance = Number(row.importance ?? 0);
4728
+ await client.batch([
4729
+ {
4730
+ sql: `UPDATE memories
4731
+ SET status = 'archived',
4732
+ outcome = COALESCE(outcome, 'superseded')
4733
+ WHERE id = ?`,
4734
+ args: [supersedesId]
4735
+ },
4736
+ {
4737
+ sql: `UPDATE memories
4738
+ SET importance = MAX(COALESCE(importance, 5), ?),
4739
+ parent_memory_id = COALESCE(parent_memory_id, ?)
4740
+ WHERE id = ?`,
4741
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
4742
+ }
4743
+ ], "write");
4744
+ }
4745
+ } catch (err) {
4746
+ process.stderr.write(
4747
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
4748
+ `
4749
+ );
4750
+ }
4751
+ }
4752
+ function schedulePostWriteMemoryHygiene(memoryIds) {
4753
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
4754
+ if (memoryIds.length === 0) return;
4755
+ const run = () => {
4756
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
4757
+ };
4758
+ if (typeof setImmediate === "function") setImmediate(run);
4759
+ else setTimeout(run, 0);
4760
+ }
4761
+
4604
4762
  // src/lib/store.ts
4605
4763
  var INIT_MAX_RETRIES = 3;
4606
4764
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -4800,6 +4958,7 @@ async function flushBatch() {
4800
4958
  const globalClient = getClient();
4801
4959
  const globalStmts = batch.map(buildStmt);
4802
4960
  await globalClient.batch(globalStmts, "write");
4961
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4803
4962
  _pendingRecords.splice(0, batch.length);
4804
4963
  try {
4805
4964
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4911,7 +5070,16 @@ var BEHAVIORS_EXPORT_DIR = path9.join(
4911
5070
  "behaviors-export"
4912
5071
  );
4913
5072
  var STALE_EXPORT_AGE_MS = 60 * 60 * 1e3;
4914
- var EXPORT_BEHAVIOR_LIMIT = 30;
5073
+ var DEFAULT_BEHAVIOR_LIMIT = 30;
5074
+ function getBehaviorLimit() {
5075
+ try {
5076
+ const { loadConfigSync: loadConfigSync2 } = (init_config(), __toCommonJS(config_exports));
5077
+ const cfg = loadConfigSync2();
5078
+ return cfg.behaviorExportLimit ?? DEFAULT_BEHAVIOR_LIMIT;
5079
+ } catch {
5080
+ return DEFAULT_BEHAVIOR_LIMIT;
5081
+ }
5082
+ }
4915
5083
  function sweepStaleBehaviorExports(now = Date.now()) {
4916
5084
  if (!existsSync9(BEHAVIORS_EXPORT_DIR)) return;
4917
5085
  let entries;
@@ -4965,7 +5133,7 @@ function exportFilePath(agentId, projectName, sessionKey) {
4965
5133
  async function exportBehaviorsForAgent(agentId, projectName, sessionKey) {
4966
5134
  mkdirSync3(BEHAVIORS_EXPORT_DIR, { recursive: true });
4967
5135
  sweepStaleBehaviorExports();
4968
- const behaviors = await listBehaviors(agentId, projectName, EXPORT_BEHAVIOR_LIMIT);
5136
+ const behaviors = await listBehaviors(agentId, projectName, getBehaviorLimit());
4969
5137
  if (behaviors.length === 0) return null;
4970
5138
  const body = renderBehaviorExport(behaviors);
4971
5139
  const target = exportFilePath(agentId, projectName, sessionKey);
@@ -2542,6 +2542,14 @@ async function ensureSchema() {
2542
2542
  );
2543
2543
  } catch {
2544
2544
  }
2545
+ try {
2546
+ await client.execute(
2547
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2548
+ ON memories(content_hash, agent_id, project_name, memory_type)
2549
+ WHERE content_hash IS NOT NULL`
2550
+ );
2551
+ } catch {
2552
+ }
2545
2553
  await client.executeMultiple(`
2546
2554
  CREATE TABLE IF NOT EXISTS entities (
2547
2555
  id TEXT PRIMARY KEY,
@@ -1266,6 +1266,7 @@ __export(installer_exports, {
1266
1266
  });
1267
1267
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
1268
1268
  import { existsSync as existsSync11, readFileSync as readFileSync9, writeFileSync as writeFileSync7, copyFileSync, mkdirSync as mkdirSync6 } from "fs";
1269
+ import { createHash as createHash2 } from "crypto";
1269
1270
  import path11 from "path";
1270
1271
  import os7 from "os";
1271
1272
  import { execSync as execSync2 } from "child_process";
@@ -1950,7 +1951,30 @@ function setupTmux(home) {
1950
1951
  return;
1951
1952
  }
1952
1953
  mkdirSync6(exeDir, { recursive: true });
1953
- copyFileSync(assetPath, exeTmuxConf);
1954
+ if (existsSync11(exeTmuxConf)) {
1955
+ const currentContent = readFileSync9(exeTmuxConf, "utf8");
1956
+ const newContent = readFileSync9(assetPath, "utf8");
1957
+ const currentHash = createHash2("sha256").update(currentContent).digest("hex");
1958
+ const newHash = createHash2("sha256").update(newContent).digest("hex");
1959
+ if (currentHash !== newHash) {
1960
+ const shippedPath = path11.join(exeDir, ".tmux.conf.shipped-hash");
1961
+ const lastShippedHash = existsSync11(shippedPath) ? readFileSync9(shippedPath, "utf8").trim() : "";
1962
+ if (lastShippedHash && currentHash !== lastShippedHash) {
1963
+ process.stderr.write("exe-os: tmux config has user customizations \u2014 skipping overwrite\n");
1964
+ } else {
1965
+ copyFileSync(assetPath, exeTmuxConf);
1966
+ process.stderr.write("exe-os: tmux config updated\n");
1967
+ }
1968
+ writeFileSync7(shippedPath, newHash, "utf8");
1969
+ } else {
1970
+ process.stderr.write("exe-os: tmux config already up to date\n");
1971
+ }
1972
+ } else {
1973
+ copyFileSync(assetPath, exeTmuxConf);
1974
+ const newContent = readFileSync9(assetPath, "utf8");
1975
+ const newHash = createHash2("sha256").update(newContent).digest("hex");
1976
+ writeFileSync7(path11.join(exeDir, ".tmux.conf.shipped-hash"), newHash, "utf8");
1977
+ }
1954
1978
  if (existsSync11(userTmuxConf)) {
1955
1979
  const existing = readFileSync9(userTmuxConf, "utf8");
1956
1980
  if (!existing.includes(sourceLine)) {
@@ -2243,7 +2267,7 @@ var PLATFORM_PROCEDURES = [
2243
2267
  title: "Chain of command \u2014 who talks to whom",
2244
2268
  domain: "workflow",
2245
2269
  priority: "p0",
2246
- content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
2270
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
2247
2271
  },
2248
2272
  {
2249
2273
  title: "Single dispatch path \u2014 create_task only",
@@ -2298,6 +2298,14 @@ async function ensureSchema() {
2298
2298
  );
2299
2299
  } catch {
2300
2300
  }
2301
+ try {
2302
+ await client.execute(
2303
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2304
+ ON memories(content_hash, agent_id, project_name, memory_type)
2305
+ WHERE content_hash IS NOT NULL`
2306
+ );
2307
+ } catch {
2308
+ }
2301
2309
  await client.executeMultiple(`
2302
2310
  CREATE TABLE IF NOT EXISTS entities (
2303
2311
  id TEXT PRIMARY KEY,
@@ -3452,6 +3460,197 @@ var init_state_bus = __esm({
3452
3460
  }
3453
3461
  });
3454
3462
 
3463
+ // src/lib/memory-write-governor.ts
3464
+ import { createHash } from "crypto";
3465
+ function normalizeMemoryText(text) {
3466
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3467
+ }
3468
+ function classifyMemoryType(input) {
3469
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3470
+ const tool = input.tool_name.toLowerCase();
3471
+ const text = input.raw_text.toLowerCase();
3472
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3473
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3474
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3475
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3476
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3477
+ if (tool === "store_memory" || tool === "manual") return "observation";
3478
+ return "raw";
3479
+ }
3480
+ function shouldDropMemory(text) {
3481
+ const normalized = normalizeMemoryText(text);
3482
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3483
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3484
+ return { drop: true, reason: "known_boilerplate_noise" };
3485
+ }
3486
+ return { drop: false };
3487
+ }
3488
+ function shouldSkipEmbedding(input) {
3489
+ const type = classifyMemoryType(input);
3490
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3491
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3492
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3493
+ return false;
3494
+ }
3495
+ function hashMemoryContent(text) {
3496
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3497
+ }
3498
+ function scopedDedupArgs(input) {
3499
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3500
+ }
3501
+ function governMemoryRecord(record) {
3502
+ const normalized = normalizeMemoryText(record.raw_text);
3503
+ const memoryType = classifyMemoryType({
3504
+ raw_text: normalized,
3505
+ agent_id: record.agent_id,
3506
+ project_name: record.project_name,
3507
+ tool_name: record.tool_name,
3508
+ memory_type: record.memory_type
3509
+ });
3510
+ const drop = shouldDropMemory(normalized);
3511
+ const skipEmbedding = shouldSkipEmbedding({
3512
+ raw_text: normalized,
3513
+ agent_id: record.agent_id,
3514
+ project_name: record.project_name,
3515
+ tool_name: record.tool_name,
3516
+ memory_type: memoryType
3517
+ });
3518
+ return {
3519
+ record: {
3520
+ ...record,
3521
+ raw_text: normalized,
3522
+ memory_type: memoryType,
3523
+ vector: skipEmbedding ? null : record.vector
3524
+ },
3525
+ contentHash: hashMemoryContent(normalized),
3526
+ shouldDrop: drop.drop,
3527
+ dropReason: drop.reason,
3528
+ skipEmbedding,
3529
+ hygiene: {
3530
+ dedup: true,
3531
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3532
+ }
3533
+ };
3534
+ }
3535
+ async function findScopedDuplicate(input) {
3536
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3537
+ const client = getClient2();
3538
+ const args = scopedDedupArgs(input);
3539
+ let sql = `SELECT id FROM memories
3540
+ WHERE content_hash = ?
3541
+ AND agent_id = ?
3542
+ AND project_name = ?
3543
+ AND COALESCE(memory_type, 'raw') = ?
3544
+ AND COALESCE(status, 'active') != 'deleted'`;
3545
+ if (input.excludeId) {
3546
+ sql += " AND id != ?";
3547
+ args.push(input.excludeId);
3548
+ }
3549
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3550
+ const result = await client.execute({ sql, args });
3551
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3552
+ }
3553
+ async function runPostWriteMemoryHygiene(memoryId) {
3554
+ try {
3555
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3556
+ const client = getClient2();
3557
+ const current = await client.execute({
3558
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3559
+ importance, timestamp
3560
+ FROM memories
3561
+ WHERE id = ?
3562
+ LIMIT 1`,
3563
+ args: [memoryId]
3564
+ });
3565
+ const row = current.rows[0];
3566
+ if (!row) return;
3567
+ const memoryType = String(row.memory_type ?? "raw");
3568
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3569
+ const agentId = String(row.agent_id);
3570
+ const projectName = String(row.project_name);
3571
+ if (contentHash) {
3572
+ await client.execute({
3573
+ sql: `UPDATE memories
3574
+ SET status = 'deleted',
3575
+ outcome = COALESCE(outcome, 'superseded')
3576
+ WHERE id != ?
3577
+ AND content_hash = ?
3578
+ AND agent_id = ?
3579
+ AND project_name = ?
3580
+ AND COALESCE(memory_type, 'raw') = ?
3581
+ AND COALESCE(status, 'active') = 'active'`,
3582
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3583
+ });
3584
+ }
3585
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3586
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3587
+ const old = await client.execute({
3588
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3589
+ args: [supersedesId]
3590
+ });
3591
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3592
+ const newImportance = Number(row.importance ?? 0);
3593
+ await client.batch([
3594
+ {
3595
+ sql: `UPDATE memories
3596
+ SET status = 'archived',
3597
+ outcome = COALESCE(outcome, 'superseded')
3598
+ WHERE id = ?`,
3599
+ args: [supersedesId]
3600
+ },
3601
+ {
3602
+ sql: `UPDATE memories
3603
+ SET importance = MAX(COALESCE(importance, 5), ?),
3604
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3605
+ WHERE id = ?`,
3606
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3607
+ }
3608
+ ], "write");
3609
+ }
3610
+ } catch (err) {
3611
+ process.stderr.write(
3612
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3613
+ `
3614
+ );
3615
+ }
3616
+ }
3617
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3618
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3619
+ if (memoryIds.length === 0) return;
3620
+ const run = () => {
3621
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3622
+ };
3623
+ if (typeof setImmediate === "function") setImmediate(run);
3624
+ else setTimeout(run, 0);
3625
+ }
3626
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3627
+ var init_memory_write_governor = __esm({
3628
+ "src/lib/memory-write-governor.ts"() {
3629
+ "use strict";
3630
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3631
+ "decision",
3632
+ "adr",
3633
+ "behavior",
3634
+ "procedure"
3635
+ ]);
3636
+ NOISE_DROP_PATTERNS = [
3637
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3638
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3639
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3640
+ /^\s*Intercom is a speedup, not delivery/im,
3641
+ /^\s*Context bar reads as USAGE not remaining/im
3642
+ ];
3643
+ SKIP_EMBED_PATTERNS = [
3644
+ /tmux capture-pane\b/i,
3645
+ /docker ps\b/i,
3646
+ /docker images\b/i,
3647
+ /git status\b/i,
3648
+ /grep .*node_modules/i,
3649
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3650
+ ];
3651
+ }
3652
+ });
3653
+
3455
3654
  // src/lib/shard-manager.ts
3456
3655
  var shard_manager_exports = {};
3457
3656
  __export(shard_manager_exports, {
@@ -3616,7 +3815,8 @@ async function ensureShardSchema(client) {
3616
3815
  }
3617
3816
  for (const idx of [
3618
3817
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3619
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3818
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3819
+ "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"
3620
3820
  ]) {
3621
3821
  try {
3622
3822
  await client.execute(idx);
@@ -3810,7 +4010,7 @@ var init_platform_procedures = __esm({
3810
4010
  title: "Chain of command \u2014 who talks to whom",
3811
4011
  domain: "workflow",
3812
4012
  priority: "p0",
3813
- content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
4013
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3814
4014
  },
3815
4015
  {
3816
4016
  title: "Single dispatch path \u2014 create_task only",
@@ -4041,7 +4241,6 @@ __export(store_exports, {
4041
4241
  vectorToBlob: () => vectorToBlob,
4042
4242
  writeMemory: () => writeMemory
4043
4243
  });
4044
- import { createHash } from "crypto";
4045
4244
  function isBusyError2(err) {
4046
4245
  if (err instanceof Error) {
4047
4246
  const msg = err.message.toLowerCase();
@@ -4155,17 +4354,24 @@ async function writeMemory(record) {
4155
4354
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4156
4355
  );
4157
4356
  }
4158
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4159
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4357
+ const governed = governMemoryRecord(record);
4358
+ if (governed.shouldDrop) return;
4359
+ record = governed.record;
4360
+ const contentHash = governed.contentHash;
4361
+ const memoryType = record.memory_type ?? "raw";
4362
+ if (_pendingRecords.some(
4363
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4364
+ )) {
4160
4365
  return;
4161
4366
  }
4162
4367
  try {
4163
- const client = getClient();
4164
- const existing = await client.execute({
4165
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4166
- args: [contentHash, record.agent_id]
4368
+ const existing = await findScopedDuplicate({
4369
+ contentHash,
4370
+ agentId: record.agent_id,
4371
+ projectName: record.project_name,
4372
+ memoryType
4167
4373
  });
4168
- if (existing.rows.length > 0) return;
4374
+ if (existing) return;
4169
4375
  } catch {
4170
4376
  }
4171
4377
  const dbRow = {
@@ -4196,7 +4402,7 @@ async function writeMemory(record) {
4196
4402
  tier: record.tier ?? classifyTier(record),
4197
4403
  supersedes_id: record.supersedes_id ?? null,
4198
4404
  draft: record.draft ? 1 : 0,
4199
- memory_type: record.memory_type ?? "raw",
4405
+ memory_type: memoryType,
4200
4406
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4201
4407
  content_hash: contentHash,
4202
4408
  intent: record.intent ?? null,
@@ -4355,6 +4561,7 @@ async function flushBatch() {
4355
4561
  const globalClient = getClient();
4356
4562
  const globalStmts = batch.map(buildStmt);
4357
4563
  await globalClient.batch(globalStmts, "write");
4564
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4358
4565
  _pendingRecords.splice(0, batch.length);
4359
4566
  try {
4360
4567
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4475,7 +4682,11 @@ async function searchMemories(queryVector, agentId, options) {
4475
4682
  sql += ` AND timestamp >= ?`;
4476
4683
  args.push(options.since);
4477
4684
  }
4478
- if (options?.memoryType) {
4685
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4686
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4687
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4688
+ args.push(...uniqueTypes);
4689
+ } else if (options?.memoryType) {
4479
4690
  sql += ` AND memory_type = ?`;
4480
4691
  args.push(options.memoryType);
4481
4692
  }
@@ -4613,6 +4824,7 @@ var init_store = __esm({
4613
4824
  init_keychain();
4614
4825
  init_config();
4615
4826
  init_state_bus();
4827
+ init_memory_write_governor();
4616
4828
  INIT_MAX_RETRIES = 3;
4617
4829
  INIT_RETRY_DELAY_MS = 1e3;
4618
4830
  _pendingRecords = [];