@askexenow/exe-os 0.8.33 → 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 (89) hide show
  1. package/dist/bin/backfill-conversations.js +341 -349
  2. package/dist/bin/backfill-responses.js +81 -13
  3. package/dist/bin/backfill-vectors.js +72 -12
  4. package/dist/bin/cleanup-stale-review-tasks.js +63 -3
  5. package/dist/bin/cli.js +1737 -1117
  6. package/dist/bin/exe-assign.js +89 -19
  7. package/dist/bin/exe-boot.js +951 -101
  8. package/dist/bin/exe-call.js +61 -2
  9. package/dist/bin/exe-dispatch.js +61 -13
  10. package/dist/bin/exe-doctor.js +63 -3
  11. package/dist/bin/exe-export-behaviors.js +71 -3
  12. package/dist/bin/exe-forget.js +69 -4
  13. package/dist/bin/exe-gateway.js +178 -45
  14. package/dist/bin/exe-heartbeat.js +79 -14
  15. package/dist/bin/exe-kill.js +71 -3
  16. package/dist/bin/exe-launch-agent.js +148 -14
  17. package/dist/bin/exe-link.js +1437 -0
  18. package/dist/bin/exe-new-employee.js +98 -13
  19. package/dist/bin/exe-pending-messages.js +74 -8
  20. package/dist/bin/exe-pending-notifications.js +63 -3
  21. package/dist/bin/exe-pending-reviews.js +77 -11
  22. package/dist/bin/exe-rename.js +1287 -0
  23. package/dist/bin/exe-review.js +73 -5
  24. package/dist/bin/exe-search.js +88 -14
  25. package/dist/bin/exe-session-cleanup.js +102 -28
  26. package/dist/bin/exe-status.js +64 -4
  27. package/dist/bin/exe-team.js +64 -4
  28. package/dist/bin/git-sweep.js +80 -5
  29. package/dist/bin/graph-backfill.js +71 -3
  30. package/dist/bin/graph-export.js +71 -3
  31. package/dist/bin/install.js +38 -8
  32. package/dist/bin/scan-tasks.js +80 -5
  33. package/dist/bin/setup.js +128 -10
  34. package/dist/bin/shard-migrate.js +71 -3
  35. package/dist/bin/wiki-sync.js +71 -3
  36. package/dist/gateway/index.js +179 -46
  37. package/dist/hooks/bug-report-worker.js +254 -28
  38. package/dist/hooks/commit-complete.js +80 -5
  39. package/dist/hooks/error-recall.js +89 -15
  40. package/dist/hooks/exe-heartbeat-hook.js +1 -1
  41. package/dist/hooks/ingest-worker.js +185 -51
  42. package/dist/hooks/ingest.js +1 -1
  43. package/dist/hooks/instructions-loaded.js +81 -6
  44. package/dist/hooks/notification.js +81 -6
  45. package/dist/hooks/post-compact.js +81 -6
  46. package/dist/hooks/pre-compact.js +81 -6
  47. package/dist/hooks/pre-tool-use.js +423 -196
  48. package/dist/hooks/prompt-ingest-worker.js +91 -23
  49. package/dist/hooks/prompt-submit.js +159 -45
  50. package/dist/hooks/response-ingest-worker.js +96 -23
  51. package/dist/hooks/session-end.js +81 -6
  52. package/dist/hooks/session-start.js +89 -15
  53. package/dist/hooks/stop.js +81 -6
  54. package/dist/hooks/subagent-stop.js +81 -6
  55. package/dist/hooks/summary-worker.js +807 -55
  56. package/dist/index.js +198 -60
  57. package/dist/lib/cloud-sync.js +703 -18
  58. package/dist/lib/consolidation.js +4 -4
  59. package/dist/lib/database.js +64 -2
  60. package/dist/lib/device-registry.js +70 -3
  61. package/dist/lib/employee-templates.js +26 -0
  62. package/dist/lib/employees.js +34 -1
  63. package/dist/lib/exe-daemon.js +207 -74
  64. package/dist/lib/hybrid-search.js +88 -14
  65. package/dist/lib/identity-templates.js +51 -0
  66. package/dist/lib/identity.js +3 -3
  67. package/dist/lib/messaging.js +65 -17
  68. package/dist/lib/reminders.js +3 -3
  69. package/dist/lib/schedules.js +63 -3
  70. package/dist/lib/skill-learning.js +3 -3
  71. package/dist/lib/status-brief.js +63 -5
  72. package/dist/lib/store.js +73 -4
  73. package/dist/lib/task-router.js +4 -2
  74. package/dist/lib/tasks.js +95 -28
  75. package/dist/lib/tmux-routing.js +92 -23
  76. package/dist/mcp/server.js +800 -74
  77. package/dist/mcp/tools/complete-reminder.js +3 -3
  78. package/dist/mcp/tools/create-reminder.js +3 -3
  79. package/dist/mcp/tools/create-task.js +198 -31
  80. package/dist/mcp/tools/deactivate-behavior.js +4 -4
  81. package/dist/mcp/tools/list-reminders.js +3 -3
  82. package/dist/mcp/tools/list-tasks.js +19 -9
  83. package/dist/mcp/tools/send-message.js +69 -21
  84. package/dist/mcp/tools/update-task.js +28 -18
  85. package/dist/runtime/index.js +166 -28
  86. package/dist/tui/App.js +193 -40
  87. package/package.json +7 -3
  88. package/src/commands/exe/afk.md +116 -0
  89. package/src/commands/exe/rename.md +12 -0
@@ -229,11 +229,11 @@ ${synthesisText}`;
229
229
  }
230
230
  const insertSql = vector ? `INSERT INTO memories
231
231
  (id, agent_id, agent_role, session_id, timestamp,
232
- tool_name, project_name, has_error, raw_text, vector, version, consolidated)
233
- VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1)` : `INSERT INTO memories
232
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
233
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, vector32(?), 0, 1, 9)` : `INSERT INTO memories
234
234
  (id, agent_id, agent_role, session_id, timestamp,
235
- tool_name, project_name, has_error, raw_text, vector, version, consolidated)
236
- VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1)`;
235
+ tool_name, project_name, has_error, raw_text, vector, version, consolidated, importance)
236
+ VALUES (?, ?, 'consolidation', 'daemon-consolidation', ?, 'consolidation', ?, 0, ?, NULL, 0, 1, 9)`;
237
237
  const insertArgs = vector ? [consolidatedId, cluster.agentId, now, cluster.projectName, rawText, vectorToBlob(vector)] : [consolidatedId, cluster.agentId, now, cluster.projectName, rawText];
238
238
  await client.execute({ sql: insertSql, args: insertArgs });
239
239
  const sourceIds = cluster.memories.map((m) => m.id);
@@ -1,11 +1,64 @@
1
1
  // src/lib/database.ts
2
2
  import { createClient } from "@libsql/client";
3
+
4
+ // src/lib/db-retry.ts
5
+ var MAX_RETRIES = 3;
6
+ var BASE_DELAY_MS = 200;
7
+ var MAX_JITTER_MS = 300;
8
+ function isBusyError(err) {
9
+ if (err instanceof Error) {
10
+ const msg = err.message.toLowerCase();
11
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
12
+ }
13
+ return false;
14
+ }
15
+ function delay(ms) {
16
+ return new Promise((resolve) => setTimeout(resolve, ms));
17
+ }
18
+ async function retryOnBusy(fn, label) {
19
+ let lastError;
20
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
21
+ try {
22
+ return await fn();
23
+ } catch (err) {
24
+ lastError = err;
25
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
26
+ throw err;
27
+ }
28
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
29
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
30
+ process.stderr.write(
31
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
32
+ `
33
+ );
34
+ await delay(backoff + jitter);
35
+ }
36
+ }
37
+ throw lastError;
38
+ }
39
+ function wrapWithRetry(client) {
40
+ return new Proxy(client, {
41
+ get(target, prop, receiver) {
42
+ if (prop === "execute") {
43
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
44
+ }
45
+ if (prop === "batch") {
46
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
47
+ }
48
+ return Reflect.get(target, prop, receiver);
49
+ }
50
+ });
51
+ }
52
+
53
+ // src/lib/database.ts
3
54
  var _client = null;
55
+ var _resilientClient = null;
4
56
  var initTurso = initDatabase;
5
57
  async function initDatabase(config) {
6
58
  if (_client) {
7
59
  _client.close();
8
60
  _client = null;
61
+ _resilientClient = null;
9
62
  }
10
63
  const opts = {
11
64
  url: `file:${config.dbPath}`
@@ -14,20 +67,27 @@ async function initDatabase(config) {
14
67
  opts.encryptionKey = config.encryptionKey;
15
68
  }
16
69
  _client = createClient(opts);
70
+ _resilientClient = wrapWithRetry(_client);
17
71
  }
18
72
  function isInitialized() {
19
73
  return _client !== null;
20
74
  }
21
75
  function getClient() {
76
+ if (!_resilientClient) {
77
+ throw new Error("Database client not initialized. Call initDatabase() first.");
78
+ }
79
+ return _resilientClient;
80
+ }
81
+ function getRawClient() {
22
82
  if (!_client) {
23
83
  throw new Error("Database client not initialized. Call initDatabase() first.");
24
84
  }
25
85
  return _client;
26
86
  }
27
87
  async function ensureSchema() {
28
- const client = getClient();
88
+ const client = getRawClient();
29
89
  await client.execute("PRAGMA journal_mode = WAL");
30
- await client.execute("PRAGMA busy_timeout = 5000");
90
+ await client.execute("PRAGMA busy_timeout = 30000");
31
91
  try {
32
92
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
33
93
  } catch {
@@ -821,6 +881,7 @@ async function disposeDatabase() {
821
881
  if (_client) {
822
882
  _client.close();
823
883
  _client = null;
884
+ _resilientClient = null;
824
885
  }
825
886
  }
826
887
  export {
@@ -828,6 +889,7 @@ export {
828
889
  disposeTurso,
829
890
  ensureSchema,
830
891
  getClient,
892
+ getRawClient,
831
893
  initDatabase,
832
894
  initTurso,
833
895
  isInitialized
@@ -8,6 +8,61 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
+ // src/lib/db-retry.ts
12
+ function isBusyError(err) {
13
+ if (err instanceof Error) {
14
+ const msg = err.message.toLowerCase();
15
+ return msg.includes("sqlite_busy") || msg.includes("database is locked");
16
+ }
17
+ return false;
18
+ }
19
+ function delay(ms) {
20
+ return new Promise((resolve) => setTimeout(resolve, ms));
21
+ }
22
+ async function retryOnBusy(fn, label) {
23
+ let lastError;
24
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
25
+ try {
26
+ return await fn();
27
+ } catch (err) {
28
+ lastError = err;
29
+ if (!isBusyError(err) || attempt === MAX_RETRIES) {
30
+ throw err;
31
+ }
32
+ const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
33
+ const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
34
+ process.stderr.write(
35
+ `[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
36
+ `
37
+ );
38
+ await delay(backoff + jitter);
39
+ }
40
+ }
41
+ throw lastError;
42
+ }
43
+ function wrapWithRetry(client) {
44
+ return new Proxy(client, {
45
+ get(target, prop, receiver) {
46
+ if (prop === "execute") {
47
+ return (sql) => retryOnBusy(() => target.execute(sql), "execute");
48
+ }
49
+ if (prop === "batch") {
50
+ return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
51
+ }
52
+ return Reflect.get(target, prop, receiver);
53
+ }
54
+ });
55
+ }
56
+ var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
57
+ var init_db_retry = __esm({
58
+ "src/lib/db-retry.ts"() {
59
+ "use strict";
60
+ MAX_RETRIES = 3;
61
+ BASE_DELAY_MS = 200;
62
+ MAX_JITTER_MS = 300;
63
+ }
64
+ });
65
+
11
66
  // src/lib/database.ts
12
67
  var database_exports = {};
13
68
  __export(database_exports, {
@@ -15,6 +70,7 @@ __export(database_exports, {
15
70
  disposeTurso: () => disposeTurso,
16
71
  ensureSchema: () => ensureSchema,
17
72
  getClient: () => getClient,
73
+ getRawClient: () => getRawClient,
18
74
  initDatabase: () => initDatabase,
19
75
  initTurso: () => initTurso,
20
76
  isInitialized: () => isInitialized
@@ -24,6 +80,7 @@ async function initDatabase(config) {
24
80
  if (_client) {
25
81
  _client.close();
26
82
  _client = null;
83
+ _resilientClient = null;
27
84
  }
28
85
  const opts = {
29
86
  url: `file:${config.dbPath}`
@@ -32,20 +89,27 @@ async function initDatabase(config) {
32
89
  opts.encryptionKey = config.encryptionKey;
33
90
  }
34
91
  _client = createClient(opts);
92
+ _resilientClient = wrapWithRetry(_client);
35
93
  }
36
94
  function isInitialized() {
37
95
  return _client !== null;
38
96
  }
39
97
  function getClient() {
98
+ if (!_resilientClient) {
99
+ throw new Error("Database client not initialized. Call initDatabase() first.");
100
+ }
101
+ return _resilientClient;
102
+ }
103
+ function getRawClient() {
40
104
  if (!_client) {
41
105
  throw new Error("Database client not initialized. Call initDatabase() first.");
42
106
  }
43
107
  return _client;
44
108
  }
45
109
  async function ensureSchema() {
46
- const client = getClient();
110
+ const client = getRawClient();
47
111
  await client.execute("PRAGMA journal_mode = WAL");
48
- await client.execute("PRAGMA busy_timeout = 5000");
112
+ await client.execute("PRAGMA busy_timeout = 30000");
49
113
  try {
50
114
  await client.execute("PRAGMA libsql_vector_search_ef = 128");
51
115
  } catch {
@@ -838,13 +902,16 @@ async function disposeDatabase() {
838
902
  if (_client) {
839
903
  _client.close();
840
904
  _client = null;
905
+ _resilientClient = null;
841
906
  }
842
907
  }
843
- var _client, initTurso, disposeTurso;
908
+ var _client, _resilientClient, initTurso, disposeTurso;
844
909
  var init_database = __esm({
845
910
  "src/lib/database.ts"() {
846
911
  "use strict";
912
+ init_db_retry();
847
913
  _client = null;
914
+ _resilientClient = null;
848
915
  initTurso = initDatabase;
849
916
  disposeTurso = disposeDatabase;
850
917
  }
@@ -433,6 +433,32 @@ When you analyze a repo:
433
433
  Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
434
434
 
435
435
  Maintain a clear separation between experimental (for evaluation) and production-ready (for shipping). Never recommend something you haven't read the source code for.`
436
+ },
437
+ bob: {
438
+ name: "bob",
439
+ role: "Staff Code Reviewer",
440
+ systemPrompt: `You are bob, the Staff Code Reviewer and System Auditor. You are the last line of defense before code ships to customers. You catch what developers miss \u2014 not just code bugs, but systemic patterns that make entire feature categories break. You report to the COO.
441
+
442
+ Your core job: audit code, find bugs, verify fixes, and ensure customer-readiness. Every audit answers: "Would this break for a customer who customized their setup?"
443
+
444
+ The 7 Audit Patterns (MANDATORY \u2014 apply to EVERY audit):
445
+ 1. "Works on dev, breaks on user install" \u2014 verify scoped paths, npm resolution, dependencies
446
+ 2. "Two code paths, one untested" \u2014 binary symlink vs /exe-call, CLI vs MCP \u2014 verify BOTH
447
+ 3. "Case sensitivity kills non-technical users" \u2014 normalize all user inputs
448
+ 4. "Hardcoded names leak into user-facing content" \u2014 grep for employee names in runtime logic
449
+ 5. "Installer doesn't self-heal on update" \u2014 npm update must auto-fix stale hooks/paths
450
+ 6. "Data written but invisible to the agent" \u2014 verify query path retrieves stored data
451
+ 7. "Partial fixes that miss inline references" \u2014 before/after grep count is mandatory
452
+
453
+ Audit method:
454
+ 1. Read the actual source code \u2014 not summaries
455
+ 2. Send to Codex MCP for initial sweep
456
+ 3. Validate against ARCHITECTURE.md
457
+ 4. Trace full identity chain with a CUSTOM-NAMED employee (e.g., "jarvis" as CTO)
458
+ 5. Count matches before and after any claimed fix
459
+ 6. Write structured report with PASS/FAIL per item
460
+
461
+ After an audit, fix the findings yourself if you can. Don't hand off when you have the context.`
436
462
  }
437
463
  };
438
464
  function buildCustomEmployeePrompt(name, role) {
@@ -1,6 +1,6 @@
1
1
  // src/lib/employees.ts
2
2
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3
- import { existsSync as existsSync2, symlinkSync, readlinkSync } from "fs";
3
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
4
4
  import { execSync } from "child_process";
5
5
  import path2 from "path";
6
6
 
@@ -127,9 +127,37 @@ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
127
127
  await mkdir2(path2.dirname(employeesPath), { recursive: true });
128
128
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
129
129
  }
130
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
131
+ if (!existsSync2(employeesPath)) return [];
132
+ try {
133
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
134
+ } catch {
135
+ return [];
136
+ }
137
+ }
130
138
  function getEmployee(employees, name) {
131
139
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
132
140
  }
141
+ function getEmployeeByRole(employees, role) {
142
+ const lower = role.toLowerCase();
143
+ return employees.find((e) => e.role.toLowerCase() === lower);
144
+ }
145
+ function getEmployeeNamesByRole(employees, role) {
146
+ const lower = role.toLowerCase();
147
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
148
+ }
149
+ function hasRole(agentName, role) {
150
+ const employees = loadEmployeesSync();
151
+ const emp = getEmployee(employees, agentName);
152
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
153
+ }
154
+ var MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
155
+ function isMultiInstance(agentName, employees) {
156
+ const roster = employees ?? loadEmployeesSync();
157
+ const emp = getEmployee(roster, agentName);
158
+ if (!emp) return false;
159
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
160
+ }
133
161
  function addEmployee(employees, employee) {
134
162
  const normalized = { ...employee, name: employee.name.toLowerCase() };
135
163
  if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
@@ -176,7 +204,12 @@ export {
176
204
  EMPLOYEES_PATH,
177
205
  addEmployee,
178
206
  getEmployee,
207
+ getEmployeeByRole,
208
+ getEmployeeNamesByRole,
209
+ hasRole,
210
+ isMultiInstance,
179
211
  loadEmployees,
212
+ loadEmployeesSync,
180
213
  registerBinSymlinks,
181
214
  saveEmployees,
182
215
  validateEmployeeName