@askexenow/exe-os 0.8.36 → 0.8.37

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 (62) hide show
  1. package/dist/bin/backfill-conversations.js +9 -1
  2. package/dist/bin/backfill-responses.js +9 -1
  3. package/dist/bin/cli.js +255 -31
  4. package/dist/bin/exe-assign.js +9 -1
  5. package/dist/bin/exe-boot.js +554 -23
  6. package/dist/bin/exe-dispatch.js +43 -3
  7. package/dist/bin/exe-export-behaviors.js +7 -0
  8. package/dist/bin/exe-gateway.js +57 -9
  9. package/dist/bin/exe-heartbeat.js +2 -1
  10. package/dist/bin/exe-kill.js +7 -0
  11. package/dist/bin/exe-launch-agent.js +8 -1
  12. package/dist/bin/exe-link.js +503 -12
  13. package/dist/bin/exe-pending-messages.js +2 -1
  14. package/dist/bin/exe-pending-reviews.js +2 -1
  15. package/dist/bin/exe-review.js +9 -1
  16. package/dist/bin/exe-search.js +9 -1
  17. package/dist/bin/exe-session-cleanup.js +11 -2
  18. package/dist/bin/git-sweep.js +9 -1
  19. package/dist/bin/graph-backfill.js +7 -0
  20. package/dist/bin/graph-export.js +7 -0
  21. package/dist/bin/install.js +35 -5
  22. package/dist/bin/scan-tasks.js +9 -1
  23. package/dist/bin/shard-migrate.js +7 -0
  24. package/dist/bin/wiki-sync.js +7 -0
  25. package/dist/gateway/index.js +57 -9
  26. package/dist/hooks/bug-report-worker.js +45 -5
  27. package/dist/hooks/commit-complete.js +9 -1
  28. package/dist/hooks/error-recall.js +10 -2
  29. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  30. package/dist/hooks/ingest-worker.js +56 -8
  31. package/dist/hooks/ingest.js +1 -1
  32. package/dist/hooks/instructions-loaded.js +10 -2
  33. package/dist/hooks/notification.js +10 -2
  34. package/dist/hooks/post-compact.js +10 -2
  35. package/dist/hooks/pre-compact.js +10 -2
  36. package/dist/hooks/pre-tool-use.js +10 -2
  37. package/dist/hooks/prompt-ingest-worker.js +9 -1
  38. package/dist/hooks/prompt-submit.js +56 -8
  39. package/dist/hooks/response-ingest-worker.js +9 -1
  40. package/dist/hooks/session-end.js +10 -2
  41. package/dist/hooks/session-start.js +10 -2
  42. package/dist/hooks/stop.js +10 -2
  43. package/dist/hooks/subagent-stop.js +10 -2
  44. package/dist/hooks/summary-worker.js +512 -13
  45. package/dist/index.js +65 -15
  46. package/dist/lib/cloud-sync.js +502 -11
  47. package/dist/lib/exe-daemon.js +73 -23
  48. package/dist/lib/hybrid-search.js +9 -1
  49. package/dist/lib/messaging.js +43 -3
  50. package/dist/lib/store.js +9 -1
  51. package/dist/lib/tasks.js +47 -7
  52. package/dist/lib/tmux-routing.js +45 -3
  53. package/dist/mcp/server.js +73 -16
  54. package/dist/mcp/tools/create-task.js +48 -8
  55. package/dist/mcp/tools/deactivate-behavior.js +1 -1
  56. package/dist/mcp/tools/list-tasks.js +2 -1
  57. package/dist/mcp/tools/send-message.js +46 -6
  58. package/dist/mcp/tools/update-task.js +3 -2
  59. package/dist/runtime/index.js +54 -4
  60. package/dist/tui/App.js +54 -4
  61. package/package.json +2 -2
  62. package/src/commands/exe/afk.md +116 -0
@@ -1445,7 +1445,8 @@ async function writeMemory(record) {
1445
1445
  has_error: record.has_error ? 1 : 0,
1446
1446
  raw_text: record.raw_text,
1447
1447
  vector: record.vector,
1448
- version: _nextVersion++,
1448
+ version: 0,
1449
+ // Placeholder — assigned atomically at flush time
1449
1450
  task_id: record.task_id ?? null,
1450
1451
  importance: record.importance ?? 5,
1451
1452
  status: record.status ?? "active",
@@ -1479,6 +1480,13 @@ async function flushBatch() {
1479
1480
  _flushing = true;
1480
1481
  try {
1481
1482
  const batch = _pendingRecords.slice(0);
1483
+ const client = getClient();
1484
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1485
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1486
+ for (const row of batch) {
1487
+ row.version = baseVersion++;
1488
+ }
1489
+ _nextVersion = baseVersion;
1482
1490
  const buildStmt = (row) => {
1483
1491
  const hasVector = row.vector !== null;
1484
1492
  const taskId = row.task_id ?? null;
@@ -1444,7 +1444,8 @@ async function writeMemory(record) {
1444
1444
  has_error: record.has_error ? 1 : 0,
1445
1445
  raw_text: record.raw_text,
1446
1446
  vector: record.vector,
1447
- version: _nextVersion++,
1447
+ version: 0,
1448
+ // Placeholder — assigned atomically at flush time
1448
1449
  task_id: record.task_id ?? null,
1449
1450
  importance: record.importance ?? 5,
1450
1451
  status: record.status ?? "active",
@@ -1478,6 +1479,13 @@ async function flushBatch() {
1478
1479
  _flushing = true;
1479
1480
  try {
1480
1481
  const batch = _pendingRecords.slice(0);
1482
+ const client = getClient();
1483
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1484
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1485
+ for (const row of batch) {
1486
+ row.version = baseVersion++;
1487
+ }
1488
+ _nextVersion = baseVersion;
1481
1489
  const buildStmt = (row) => {
1482
1490
  const hasVector = row.vector !== null;
1483
1491
  const taskId = row.task_id ?? null;
package/dist/bin/cli.js CHANGED
@@ -807,26 +807,56 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
807
807
  permissions.allow = [];
808
808
  }
809
809
  const toolNames = [
810
+ // Core memory
810
811
  "store_memory",
811
812
  "recall_my_memory",
813
+ "commit_to_long_term_memory",
814
+ "consolidate_memories",
815
+ "ask_team_memory",
816
+ "get_session_context",
817
+ // Tasks
812
818
  "create_task",
813
819
  "list_tasks",
814
820
  "get_task",
815
821
  "update_task",
816
822
  "close_task",
823
+ "checkpoint_task",
824
+ // Behaviors
817
825
  "store_behavior",
818
826
  "deactivate_behavior",
819
- "send_message",
827
+ "list_behaviors",
828
+ // Identity
820
829
  "get_identity",
821
830
  "update_identity",
822
- "ask_team_memory",
823
- "get_session_context",
831
+ // Messaging
832
+ "send_message",
833
+ "acknowledge_messages",
834
+ "send_whatsapp",
835
+ "query_conversations",
836
+ // Reminders + triggers
824
837
  "create_reminder",
825
838
  "complete_reminder",
826
839
  "list_reminders",
827
- "list_behaviors",
840
+ "create_trigger",
841
+ "list_triggers",
842
+ // GraphRAG
828
843
  "query_relationships",
829
- "commit_to_long_term_memory"
844
+ "merge_entities",
845
+ // Documents + wiki
846
+ "ingest_document",
847
+ "list_documents",
848
+ "purge_document",
849
+ "rerank_documents",
850
+ "set_document_importance",
851
+ "create_wiki_page",
852
+ "update_wiki_page",
853
+ "get_wiki_page",
854
+ "list_wiki_pages",
855
+ // System
856
+ "load_skill",
857
+ "apply_starter_pack",
858
+ "resume_employee",
859
+ "deploy_client"
830
860
  ];
831
861
  const allowList = permissions.allow;
832
862
  for (const tool of expandDualPrefixTools(toolNames)) {
@@ -2063,8 +2093,8 @@ function shardExists(projectName) {
2063
2093
  }
2064
2094
  function listShards() {
2065
2095
  if (!existsSync6(SHARDS_DIR)) return [];
2066
- const { readdirSync: readdirSync4 } = __require("fs");
2067
- return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2096
+ const { readdirSync: readdirSync5 } = __require("fs");
2097
+ return readdirSync5(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
2068
2098
  }
2069
2099
  async function ensureShardSchema(client) {
2070
2100
  await client.execute("PRAGMA journal_mode = WAL");
@@ -2329,7 +2359,8 @@ async function writeMemory(record) {
2329
2359
  has_error: record.has_error ? 1 : 0,
2330
2360
  raw_text: record.raw_text,
2331
2361
  vector: record.vector,
2332
- version: _nextVersion++,
2362
+ version: 0,
2363
+ // Placeholder — assigned atomically at flush time
2333
2364
  task_id: record.task_id ?? null,
2334
2365
  importance: record.importance ?? 5,
2335
2366
  status: record.status ?? "active",
@@ -2363,6 +2394,13 @@ async function flushBatch() {
2363
2394
  _flushing = true;
2364
2395
  try {
2365
2396
  const batch = _pendingRecords.slice(0);
2397
+ const client = getClient();
2398
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
2399
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
2400
+ for (const row of batch) {
2401
+ row.version = baseVersion++;
2402
+ }
2403
+ _nextVersion = baseVersion;
2366
2404
  const buildStmt = (row) => {
2367
2405
  const hasVector = row.vector !== null;
2368
2406
  const taskId = row.task_id ?? null;
@@ -17943,6 +17981,7 @@ var init_capacity_monitor = __esm({
17943
17981
  // src/lib/tmux-routing.ts
17944
17982
  var tmux_routing_exports = {};
17945
17983
  __export(tmux_routing_exports, {
17984
+ acquireSpawnLock: () => acquireSpawnLock2,
17946
17985
  employeeSessionName: () => employeeSessionName,
17947
17986
  ensureEmployee: () => ensureEmployee,
17948
17987
  extractRootExe: () => extractRootExe,
@@ -17957,6 +17996,7 @@ __export(tmux_routing_exports, {
17957
17996
  notifyParentExe: () => notifyParentExe,
17958
17997
  parseParentExe: () => parseParentExe,
17959
17998
  registerParentExe: () => registerParentExe,
17999
+ releaseSpawnLock: () => releaseSpawnLock2,
17960
18000
  resolveExeSession: () => resolveExeSession,
17961
18001
  sendIntercom: () => sendIntercom,
17962
18002
  spawnEmployee: () => spawnEmployee,
@@ -17967,6 +18007,42 @@ import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, mkdirS
17967
18007
  import path29 from "path";
17968
18008
  import os9 from "os";
17969
18009
  import { fileURLToPath as fileURLToPath4 } from "url";
18010
+ import { unlinkSync as unlinkSync7 } from "fs";
18011
+ function spawnLockPath(sessionName) {
18012
+ return path29.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
18013
+ }
18014
+ function isProcessAlive(pid) {
18015
+ try {
18016
+ process.kill(pid, 0);
18017
+ return true;
18018
+ } catch {
18019
+ return false;
18020
+ }
18021
+ }
18022
+ function acquireSpawnLock2(sessionName) {
18023
+ if (!existsSync20(SPAWN_LOCK_DIR)) {
18024
+ mkdirSync9(SPAWN_LOCK_DIR, { recursive: true });
18025
+ }
18026
+ const lockFile = spawnLockPath(sessionName);
18027
+ if (existsSync20(lockFile)) {
18028
+ try {
18029
+ const lock = JSON.parse(readFileSync15(lockFile, "utf8"));
18030
+ const age = Date.now() - lock.timestamp;
18031
+ if (isProcessAlive(lock.pid) && age < 6e4) {
18032
+ return false;
18033
+ }
18034
+ } catch {
18035
+ }
18036
+ }
18037
+ writeFileSync8(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
18038
+ return true;
18039
+ }
18040
+ function releaseSpawnLock2(sessionName) {
18041
+ try {
18042
+ unlinkSync7(spawnLockPath(sessionName));
18043
+ } catch {
18044
+ }
18045
+ }
17970
18046
  function resolveBehaviorsExporterScript() {
17971
18047
  try {
17972
18048
  const thisFile = fileURLToPath4(import.meta.url);
@@ -18065,10 +18141,10 @@ function isEmployeeAlive(sessionName) {
18065
18141
  }
18066
18142
  function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
18067
18143
  const base = employeeSessionName(employeeName, exeSession);
18068
- if (!isAlive(base)) return 0;
18144
+ if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
18069
18145
  for (let i = 2; i <= maxInstances; i++) {
18070
18146
  const candidate = employeeSessionName(employeeName, exeSession, i);
18071
- if (!isAlive(candidate)) return i;
18147
+ if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
18072
18148
  }
18073
18149
  return null;
18074
18150
  }
@@ -18432,6 +18508,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18432
18508
  command: spawnCommand
18433
18509
  });
18434
18510
  if (spawnResult.error) {
18511
+ releaseSpawnLock2(sessionName);
18435
18512
  return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
18436
18513
  }
18437
18514
  transport.pipeLog(sessionName, logFile);
@@ -18469,6 +18546,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18469
18546
  }
18470
18547
  }
18471
18548
  if (!booted) {
18549
+ releaseSpawnLock2(sessionName);
18472
18550
  return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
18473
18551
  }
18474
18552
  if (!useExeAgent) {
@@ -18485,9 +18563,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
18485
18563
  pid: 0,
18486
18564
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
18487
18565
  });
18566
+ releaseSpawnLock2(sessionName);
18488
18567
  return { sessionName };
18489
18568
  }
18490
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
18569
+ var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
18491
18570
  var init_tmux_routing = __esm({
18492
18571
  "src/lib/tmux-routing.ts"() {
18493
18572
  "use strict";
@@ -18499,6 +18578,7 @@ var init_tmux_routing = __esm({
18499
18578
  init_provider_table();
18500
18579
  init_intercom_queue();
18501
18580
  init_plan_limits();
18581
+ SPAWN_LOCK_DIR = path29.join(os9.homedir(), ".exe-os", "spawn-locks");
18502
18582
  SESSION_CACHE = path29.join(os9.homedir(), ".exe-os", "session-cache");
18503
18583
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
18504
18584
  VERIFY_PANE_LINES = 200;
@@ -21747,7 +21827,7 @@ var init_update = __esm({
21747
21827
  });
21748
21828
 
21749
21829
  // src/bin/cli.ts
21750
- import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
21830
+ import { existsSync as existsSync21, readFileSync as readFileSync17, writeFileSync as writeFileSync9, readdirSync as readdirSync4, rmSync } from "fs";
21751
21831
  import path31 from "path";
21752
21832
  import os10 from "os";
21753
21833
  var args = process.argv.slice(2);
@@ -21779,7 +21859,7 @@ if (args.includes("--global")) {
21779
21859
  await runClaudeCheck();
21780
21860
  break;
21781
21861
  case "uninstall":
21782
- await runClaudeUninstall();
21862
+ await runClaudeUninstall(args.slice(2));
21783
21863
  break;
21784
21864
  default:
21785
21865
  await runClaudeInstall();
@@ -21863,11 +21943,11 @@ async function runClaudeCheck() {
21863
21943
  console.log("\x1B[31m\u2717\x1B[0m claude.json not found");
21864
21944
  ok = false;
21865
21945
  }
21866
- const commandsDir = path31.join(claudeDir, "commands");
21867
- if (existsSync21(commandsDir)) {
21868
- console.log("\x1B[32m\u2713\x1B[0m Slash commands directory exists");
21946
+ const skillsDir = path31.join(claudeDir, "skills");
21947
+ if (existsSync21(skillsDir)) {
21948
+ console.log("\x1B[32m\u2713\x1B[0m Slash skills directory exists");
21869
21949
  } else {
21870
- console.log("\x1B[31m\u2717\x1B[0m Slash commands directory missing");
21950
+ console.log("\x1B[31m\u2717\x1B[0m Slash skills directory missing");
21871
21951
  ok = false;
21872
21952
  }
21873
21953
  if (!ok) {
@@ -21877,11 +21957,16 @@ async function runClaudeCheck() {
21877
21957
  console.log("\nAll checks passed.");
21878
21958
  }
21879
21959
  }
21880
- async function runClaudeUninstall() {
21881
- const claudeDir = path31.join(os10.homedir(), ".claude");
21960
+ async function runClaudeUninstall(flags = []) {
21961
+ const dryRun = flags.includes("--dry-run");
21962
+ const purge = flags.includes("--purge");
21963
+ const homeDir = os10.homedir();
21964
+ const claudeDir = path31.join(homeDir, ".claude");
21882
21965
  const settingsPath = path31.join(claudeDir, "settings.json");
21883
- const claudeJsonPath = path31.join(os10.homedir(), ".claude.json");
21966
+ const claudeJsonPath = path31.join(homeDir, ".claude.json");
21967
+ const exeOsDir = path31.join(homeDir, ".exe-os");
21884
21968
  let removed = 0;
21969
+ const log = (msg) => console.log(dryRun ? `[dry-run] ${msg}` : msg);
21885
21970
  if (existsSync21(settingsPath)) {
21886
21971
  const settings = JSON.parse(readFileSync17(settingsPath, "utf8"));
21887
21972
  if (settings.hooks) {
@@ -21903,9 +21988,19 @@ async function runClaudeUninstall() {
21903
21988
  delete settings.hooks[key];
21904
21989
  }
21905
21990
  }
21906
- const { writeFileSync: writeFileSync9 } = await import("fs");
21907
- writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
21908
- console.log("Removed exe-os hooks from settings.json");
21991
+ let permCount = 0;
21992
+ if (Array.isArray(settings.permissions?.allow)) {
21993
+ const before = settings.permissions.allow.length;
21994
+ settings.permissions.allow = settings.permissions.allow.filter(
21995
+ (p) => !p.startsWith("mcp__exe-mem__") && !p.startsWith("mcp__exe-os__")
21996
+ );
21997
+ permCount = before - settings.permissions.allow.length;
21998
+ }
21999
+ if (!dryRun) {
22000
+ writeFileSync9(settingsPath, JSON.stringify(settings, null, 2) + "\n");
22001
+ }
22002
+ log("\u2713 Removed exe-os hooks from settings.json");
22003
+ if (permCount > 0) log(`\u2713 Removed ${permCount} MCP permission entries`);
21909
22004
  removed++;
21910
22005
  }
21911
22006
  }
@@ -21915,25 +22010,154 @@ async function runClaudeUninstall() {
21915
22010
  let removedMcp = false;
21916
22011
  for (const key of ["exe-mem", "exe-os"]) {
21917
22012
  if (claudeJson.mcpServers[key]) {
21918
- delete claudeJson.mcpServers[key];
22013
+ if (!dryRun) delete claudeJson.mcpServers[key];
21919
22014
  removedMcp = true;
21920
22015
  }
21921
22016
  }
21922
22017
  if (removedMcp) {
21923
- const { writeFileSync: writeFileSync9 } = await import("fs");
21924
- writeFileSync9(
21925
- claudeJsonPath,
21926
- JSON.stringify(claudeJson, null, 2) + "\n"
21927
- );
21928
- console.log("Removed exe-os MCP server from claude.json");
22018
+ if (!dryRun) {
22019
+ writeFileSync9(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
22020
+ }
22021
+ log("\u2713 Removed exe-os MCP server from claude.json");
21929
22022
  removed++;
21930
22023
  }
21931
22024
  }
21932
22025
  }
22026
+ const skillsDir = path31.join(claudeDir, "skills");
22027
+ if (existsSync21(skillsDir)) {
22028
+ let skillCount = 0;
22029
+ try {
22030
+ const entries = readdirSync4(skillsDir);
22031
+ for (const entry of entries) {
22032
+ if (entry.startsWith("exe")) {
22033
+ const fullPath = path31.join(skillsDir, entry);
22034
+ if (!dryRun) rmSync(fullPath, { recursive: true, force: true });
22035
+ skillCount++;
22036
+ }
22037
+ }
22038
+ } catch {
22039
+ }
22040
+ if (skillCount > 0) {
22041
+ log(`\u2713 Removed ${skillCount} skill directories`);
22042
+ removed++;
22043
+ }
22044
+ }
22045
+ const claudeMdPath = path31.join(claudeDir, "CLAUDE.md");
22046
+ if (existsSync21(claudeMdPath)) {
22047
+ const content = readFileSync17(claudeMdPath, "utf8");
22048
+ const startMarker = "<!-- exe-os:orchestration-start -->";
22049
+ const endMarker = "<!-- exe-os:orchestration-end -->";
22050
+ const startIdx = content.indexOf(startMarker);
22051
+ const endIdx = content.indexOf(endMarker);
22052
+ if (startIdx !== -1 && endIdx !== -1) {
22053
+ const cleaned = (content.slice(0, startIdx) + content.slice(endIdx + endMarker.length)).replace(/\n{3,}/g, "\n\n").trim() + "\n";
22054
+ if (!dryRun) writeFileSync9(claudeMdPath, cleaned);
22055
+ log("\u2713 Removed orchestration block from CLAUDE.md");
22056
+ removed++;
22057
+ }
22058
+ }
22059
+ const agentsDir = path31.join(claudeDir, "agents");
22060
+ if (existsSync21(agentsDir)) {
22061
+ let agentCount = 0;
22062
+ try {
22063
+ const entries = readdirSync4(agentsDir).filter((f) => f.endsWith(".md"));
22064
+ let knownNames = /* @__PURE__ */ new Set();
22065
+ const rosterPath = path31.join(exeOsDir, "exe-employees.json");
22066
+ if (existsSync21(rosterPath)) {
22067
+ try {
22068
+ const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
22069
+ knownNames = new Set(roster.map((e) => e.name));
22070
+ } catch {
22071
+ }
22072
+ }
22073
+ for (const entry of entries) {
22074
+ const name = entry.replace(/\.md$/, "");
22075
+ if (knownNames.has(name)) {
22076
+ if (!dryRun) rmSync(path31.join(agentsDir, entry), { force: true });
22077
+ agentCount++;
22078
+ }
22079
+ }
22080
+ } catch {
22081
+ }
22082
+ if (agentCount > 0) {
22083
+ log(`\u2713 Removed ${agentCount} agent identity files`);
22084
+ removed++;
22085
+ }
22086
+ }
22087
+ const projectsDir = path31.join(claudeDir, "projects");
22088
+ if (existsSync21(projectsDir)) {
22089
+ let projectCount = 0;
22090
+ try {
22091
+ const projects = readdirSync4(projectsDir);
22092
+ for (const proj of projects) {
22093
+ const projSettings = path31.join(projectsDir, proj, "settings.json");
22094
+ if (!existsSync21(projSettings)) continue;
22095
+ try {
22096
+ const pSettings = JSON.parse(readFileSync17(projSettings, "utf8"));
22097
+ let changed = false;
22098
+ if (Array.isArray(pSettings.permissions?.allow)) {
22099
+ const before = pSettings.permissions.allow.length;
22100
+ pSettings.permissions.allow = pSettings.permissions.allow.filter(
22101
+ (p) => !p.startsWith("mcp__exe-mem__") && !p.startsWith("mcp__exe-os__")
22102
+ );
22103
+ if (pSettings.permissions.allow.length < before) changed = true;
22104
+ }
22105
+ if (changed && !dryRun) {
22106
+ writeFileSync9(projSettings, JSON.stringify(pSettings, null, 2) + "\n");
22107
+ }
22108
+ if (changed) projectCount++;
22109
+ } catch {
22110
+ }
22111
+ }
22112
+ } catch {
22113
+ }
22114
+ if (projectCount > 0) {
22115
+ log(`\u2713 Cleaned exe-os entries from ${projectCount} project settings`);
22116
+ removed++;
22117
+ }
22118
+ }
22119
+ try {
22120
+ const { execSync: execSync13 } = await import("child_process");
22121
+ const exeBinPath = execSync13("which exe", { encoding: "utf-8" }).trim();
22122
+ const binDir = path31.dirname(exeBinPath);
22123
+ let symlinkCount = 0;
22124
+ const rosterPath = path31.join(exeOsDir, "exe-employees.json");
22125
+ if (existsSync21(rosterPath)) {
22126
+ const roster = JSON.parse(readFileSync17(rosterPath, "utf8"));
22127
+ for (const emp of roster) {
22128
+ if (emp.name === "exe") continue;
22129
+ for (const suffix of ["", "-opencode"]) {
22130
+ const linkPath = path31.join(binDir, `${emp.name}${suffix}`);
22131
+ if (existsSync21(linkPath)) {
22132
+ if (!dryRun) rmSync(linkPath, { force: true });
22133
+ symlinkCount++;
22134
+ }
22135
+ }
22136
+ }
22137
+ }
22138
+ if (symlinkCount > 0) {
22139
+ log(`\u2713 Removed ${symlinkCount} employee symlinks`);
22140
+ removed++;
22141
+ }
22142
+ } catch {
22143
+ }
22144
+ if (purge && existsSync21(exeOsDir)) {
22145
+ if (!dryRun) {
22146
+ process.stdout.write("\x1B[33m\u26A0 This will delete all memories, identities, and agent data.\x1B[0m\n");
22147
+ process.stdout.write(" Removing ~/.exe-os...\n");
22148
+ rmSync(exeOsDir, { recursive: true, force: true });
22149
+ }
22150
+ log("\u2713 Purged ~/.exe-os data directory");
22151
+ removed++;
22152
+ }
21933
22153
  if (removed === 0) {
21934
22154
  console.log("Nothing to remove \u2014 exe-os was not installed.");
21935
22155
  } else {
21936
- console.log("Done. Run \x1B[36mexe-os claude install\x1B[0m to reinstall.");
22156
+ if (dryRun) {
22157
+ console.log("\nDry run complete. Re-run without --dry-run to apply.");
22158
+ } else {
22159
+ console.log("\nDone. Run \x1B[36mexe-os claude install\x1B[0m to reinstall.");
22160
+ }
21937
22161
  }
21938
22162
  }
21939
22163
  async function checkForUpdateOnBoot() {
@@ -1915,7 +1915,8 @@ async function writeMemory(record) {
1915
1915
  has_error: record.has_error ? 1 : 0,
1916
1916
  raw_text: record.raw_text,
1917
1917
  vector: record.vector,
1918
- version: _nextVersion++,
1918
+ version: 0,
1919
+ // Placeholder — assigned atomically at flush time
1919
1920
  task_id: record.task_id ?? null,
1920
1921
  importance: record.importance ?? 5,
1921
1922
  status: record.status ?? "active",
@@ -1949,6 +1950,13 @@ async function flushBatch() {
1949
1950
  _flushing = true;
1950
1951
  try {
1951
1952
  const batch = _pendingRecords.slice(0);
1953
+ const client = getClient();
1954
+ const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1955
+ let baseVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1956
+ for (const row of batch) {
1957
+ row.version = baseVersion++;
1958
+ }
1959
+ _nextVersion = baseVersion;
1952
1960
  const buildStmt = (row) => {
1953
1961
  const hasVector = row.vector !== null;
1954
1962
  const taskId = row.task_id ?? null;