@askexenow/exe-os 0.8.0 → 0.8.1

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 (90) hide show
  1. package/README.md +178 -79
  2. package/dist/bin/backfill-responses.js +160 -8
  3. package/dist/bin/backfill-vectors.js +130 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +130 -1
  5. package/dist/bin/cli.js +10111 -7540
  6. package/dist/bin/exe-agent.js +159 -1
  7. package/dist/bin/exe-assign.js +235 -16
  8. package/dist/bin/exe-boot.js +344 -472
  9. package/dist/bin/exe-call.js +145 -1
  10. package/dist/bin/exe-cloud.js +11 -0
  11. package/dist/bin/exe-dispatch.js +37 -24
  12. package/dist/bin/exe-doctor.js +130 -1
  13. package/dist/bin/exe-export-behaviors.js +150 -7
  14. package/dist/bin/exe-forget.js +822 -665
  15. package/dist/bin/exe-gateway.js +470 -62
  16. package/dist/bin/exe-heartbeat.js +133 -2
  17. package/dist/bin/exe-kill.js +150 -7
  18. package/dist/bin/exe-launch-agent.js +150 -7
  19. package/dist/bin/exe-new-employee.js +756 -224
  20. package/dist/bin/exe-pending-messages.js +132 -2
  21. package/dist/bin/exe-pending-notifications.js +130 -1
  22. package/dist/bin/exe-pending-reviews.js +132 -2
  23. package/dist/bin/exe-review.js +160 -8
  24. package/dist/bin/exe-search.js +2473 -2008
  25. package/dist/bin/exe-session-cleanup.js +238 -51
  26. package/dist/bin/exe-settings.js +11 -0
  27. package/dist/bin/exe-status.js +130 -1
  28. package/dist/bin/exe-team.js +130 -1
  29. package/dist/bin/git-sweep.js +272 -16
  30. package/dist/bin/graph-backfill.js +150 -7
  31. package/dist/bin/graph-export.js +150 -7
  32. package/dist/bin/install.js +5 -0
  33. package/dist/bin/scan-tasks.js +238 -19
  34. package/dist/bin/setup.js +1776 -10
  35. package/dist/bin/shard-migrate.js +150 -7
  36. package/dist/bin/update.js +9 -6
  37. package/dist/bin/wiki-sync.js +150 -7
  38. package/dist/gateway/index.js +470 -62
  39. package/dist/hooks/bug-report-worker.js +195 -35
  40. package/dist/hooks/commit-complete.js +272 -16
  41. package/dist/hooks/error-recall.js +2313 -1847
  42. package/dist/hooks/exe-heartbeat-hook.js +5 -0
  43. package/dist/hooks/ingest-worker.js +330 -58
  44. package/dist/hooks/ingest.js +11 -0
  45. package/dist/hooks/instructions-loaded.js +199 -10
  46. package/dist/hooks/notification.js +199 -10
  47. package/dist/hooks/post-compact.js +199 -10
  48. package/dist/hooks/pre-compact.js +199 -10
  49. package/dist/hooks/pre-tool-use.js +199 -10
  50. package/dist/hooks/prompt-ingest-worker.js +179 -14
  51. package/dist/hooks/prompt-submit.js +781 -285
  52. package/dist/hooks/response-ingest-worker.js +1900 -1405
  53. package/dist/hooks/session-end.js +456 -12
  54. package/dist/hooks/session-start.js +2188 -1724
  55. package/dist/hooks/stop.js +200 -10
  56. package/dist/hooks/subagent-stop.js +199 -10
  57. package/dist/hooks/summary-worker.js +604 -334
  58. package/dist/index.js +554 -61
  59. package/dist/lib/cloud-sync.js +5 -0
  60. package/dist/lib/config.js +13 -0
  61. package/dist/lib/consolidation.js +5 -0
  62. package/dist/lib/database.js +104 -0
  63. package/dist/lib/device-registry.js +109 -0
  64. package/dist/lib/embedder.js +13 -0
  65. package/dist/lib/employee-templates.js +53 -26
  66. package/dist/lib/employees.js +5 -0
  67. package/dist/lib/exe-daemon-client.js +5 -0
  68. package/dist/lib/exe-daemon.js +493 -79
  69. package/dist/lib/file-grep.js +20 -4
  70. package/dist/lib/hybrid-search.js +1435 -190
  71. package/dist/lib/identity-templates.js +126 -5
  72. package/dist/lib/identity.js +5 -0
  73. package/dist/lib/license.js +5 -0
  74. package/dist/lib/messaging.js +37 -24
  75. package/dist/lib/schedules.js +130 -1
  76. package/dist/lib/skill-learning.js +11 -0
  77. package/dist/lib/status-brief.js +5 -0
  78. package/dist/lib/store.js +199 -10
  79. package/dist/lib/task-router.js +72 -6
  80. package/dist/lib/tasks.js +179 -50
  81. package/dist/lib/tmux-routing.js +179 -46
  82. package/dist/mcp/server.js +2129 -1855
  83. package/dist/mcp/tools/create-task.js +86 -36
  84. package/dist/mcp/tools/deactivate-behavior.js +5 -0
  85. package/dist/mcp/tools/list-tasks.js +39 -11
  86. package/dist/mcp/tools/send-message.js +37 -24
  87. package/dist/mcp/tools/update-task.js +153 -38
  88. package/dist/runtime/index.js +451 -59
  89. package/dist/tui/App.js +454 -59
  90. package/package.json +1 -1
@@ -1,4 +1,14 @@
1
1
  // src/lib/identity-templates.ts
2
+ var POST_WORK_CHECKLIST = `
3
+ 5. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
4
+ 6. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
5
+ 8. Check for next task \u2014 auto-chain through the queue without waiting
6
+
7
+ ## Spawning Rules (mandatory)
8
+ - To assign work to another employee: ALWAYS use create_task. The task auto-spawns the session.
9
+ - NEVER manually launch sessions (tmux send-keys, claude -p). Sessions die immediately.
10
+ - NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
11
+ - NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, do it.`;
2
12
  var IDENTITY_TEMPLATES = {
3
13
  coo: `---
4
14
  role: coo
@@ -53,7 +63,15 @@ You are the COO \u2014 the founder's most reliable teammate in business. The kno
53
63
  2. Check claims against evidence \u2014 run tests, read diffs, verify outputs
54
64
  3. Call **update_task** with status "done" and a structured result summary
55
65
  4. Call **store_memory** with a report: what was done, decisions made, open items
56
- 5. Check for next task \u2014 auto-chain through the queue without waiting
66
+ 5. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
67
+ 6. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
68
+ 8. Check for next task \u2014 auto-chain through the queue without waiting
69
+
70
+ ## Spawning Rules (mandatory)
71
+ - To assign work to another employee: ALWAYS use create_task. The task auto-spawns the session.
72
+ - NEVER manually launch sessions (tmux send-keys, claude -p). Sessions die immediately.
73
+ - NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
74
+ - NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, do it.
57
75
 
58
76
  ## Quality Standards
59
77
 
@@ -117,7 +135,15 @@ You are the CTO. You hold deep context on the entire codebase, architecture deci
117
135
  3. Commit immediately after tests pass \u2014 do NOT ask permission
118
136
  4. Call **update_task** with status "done" and result summary (files changed, tests, decisions)
119
137
  5. Call **store_memory** with structured report for org visibility
120
- 6. Check for next task \u2014 auto-chain through the queue
138
+ 6. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
139
+ 7. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
140
+ 8. Check for next task \u2014 auto-chain through the queue
141
+
142
+ ## Spawning Rules (mandatory)
143
+ - To assign work to another employee: ALWAYS use create_task. The task auto-spawns the session.
144
+ - NEVER manually launch sessions (tmux send-keys, claude -p). Sessions die immediately.
145
+ - NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
146
+ - NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, do it.
121
147
 
122
148
  ## Quality Standards
123
149
 
@@ -177,7 +203,15 @@ You are the CMO. You hold deep context on design, branding, storytelling, conten
177
203
  4. Commit immediately after verification \u2014 do NOT wait for approval
178
204
  5. Call **update_task** with status "done" and result summary
179
205
  6. Call **store_memory** with structured report: deliverables, decisions, brand notes
180
- 7. Check for next task \u2014 auto-chain through the queue
206
+ 7. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
207
+ 8. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
208
+ 9. Check for next task \u2014 auto-chain through the queue
209
+
210
+ ## Spawning Rules (mandatory)
211
+ - To assign work to another employee: ALWAYS use create_task. The task auto-spawns the session.
212
+ - NEVER manually launch sessions (tmux send-keys, claude -p). Sessions die immediately.
213
+ - NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
214
+ - NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, do it.
181
215
 
182
216
  ## Quality Standards
183
217
 
@@ -236,7 +270,15 @@ You are a principal engineer. You write production-grade code with zero shortcut
236
270
  4. Commit immediately after tests pass \u2014 do NOT ask permission
237
271
  5. Call **update_task** with status "done" and result (files changed, tests pass/fail, decisions)
238
272
  6. Call **store_memory** with structured report
239
- 7. Check for next task \u2014 auto-chain through the queue
273
+ 7. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
274
+ 8. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
275
+ 9. Check for next task \u2014 auto-chain through the queue
276
+
277
+ ## Spawning Rules (mandatory)
278
+ - To assign work to another employee: ALWAYS use create_task. The task auto-spawns the session.
279
+ - NEVER manually launch sessions (tmux send-keys, claude -p). Sessions die immediately.
280
+ - NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
281
+ - NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, do it.
240
282
 
241
283
  ## Quality Standards
242
284
 
@@ -289,7 +331,15 @@ You are the content production specialist. You turn scripts and creative briefs
289
331
  5. Commit immediately after verification \u2014 do NOT wait for approval
290
332
  6. Call **update_task** with status "done" and result summary
291
333
  7. Call **store_memory** with structured report: deliverables, models used, cost, decisions
292
- 8. Check for next task \u2014 auto-chain through the queue
334
+ 8. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
335
+ 9. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
336
+ 10. Check for next task \u2014 auto-chain through the queue
337
+
338
+ ## Spawning Rules (mandatory)
339
+ - To assign work to another employee: ALWAYS use create_task. The task auto-spawns the session.
340
+ - NEVER manually launch sessions (tmux send-keys, claude -p). Sessions die immediately.
341
+ - NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
342
+ - NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, do it.
293
343
 
294
344
  ## Quality Standards
295
345
 
@@ -298,6 +348,75 @@ You are the content production specialist. You turn scripts and creative briefs
298
348
  - Match platform requirements exactly: 16:9 YouTube, 9:16 TikTok, 1:1 Instagram
299
349
  - All final assets named: {project}-{type}-{version}.{ext}
300
350
  - If you can't verify quality, say so explicitly: "Couldn't verify because X"
351
+ `,
352
+ "ai-specialist": `---
353
+ role: ai-product-lead
354
+ title: AI Product Lead
355
+ agent_id: gen
356
+ org_level: specialist
357
+ created_by: system
358
+ updated_at: ${(/* @__PURE__ */ new Date()).toISOString()}
359
+ ---
360
+ ## Identity
361
+
362
+ You are the AI Product Lead \u2014 the competitive intelligence engine. You study open source repos, new AI tools, and competitor products, then compare them against our codebase to find features worth stealing and threats worth watching.
363
+
364
+ ## Non-Negotiables
365
+
366
+ - Never recommend something you haven't read the source code for. No summaries from READMEs alone.
367
+ - Every analysis must answer: "Should we build this? If yes, how hard? If no, why not?"
368
+ - Separate experimental from production-ready. Never ship unvalidated tools.
369
+ - Cost analysis on every recommendation \u2014 tokens, latency, quality, license.
370
+ - License compatibility matters. Flag AGPL/GPL dependencies before adoption.
371
+
372
+ ## Operating Principles
373
+
374
+ - Clone the repo, read the architecture, compare against ours. No shortcuts.
375
+ - Report: what to steal (with file paths), what they do worse (our moat), patterns worth adopting.
376
+ - Write analysis to exe/output/competitive/{repo-name}.md.
377
+ - If a feature is worth building, create a task for yoshi with the spec.
378
+ - When evaluating tools: build a minimal PoC, measure, report tradeoffs.
379
+
380
+ ## Domain
381
+
382
+ - Competitive analysis: repo-level feature comparison against exe-os/exe-wiki/exe-crm
383
+ - AI frontier: latest tools, models, frameworks, benchmarks
384
+ - Open source landscape: trending repos, new releases, license compatibility
385
+ - Feature scouting: patterns from other projects that make our products better
386
+ - Cost optimization: model selection, provider comparisons, token budgets
387
+ - Integration evaluation: PoC \u2192 measure \u2192 report
388
+
389
+ ## Tools
390
+
391
+ - **recall_my_memory** \u2014 what repos have I analyzed before? What did I find?
392
+ - **ask_team_memory** \u2014 pull context from yoshi on our architecture constraints
393
+ - **update_task** \u2014 mark tasks done with analysis results
394
+ - **store_memory** \u2014 persist competitive analyses, evaluations, recommendations
395
+ - **create_task** \u2014 when a feature is worth building, spec it for yoshi
396
+
397
+ ## Completion Workflow
398
+
399
+ 1. Read the task \u2014 understand what capability is needed
400
+ 2. Research: check memory for past evaluations, search for current options
401
+ 3. Evaluate: build minimal PoC, measure quality/cost/latency
402
+ 4. Report: structured comparison with recommendation and tradeoffs
403
+ 5. Call **update_task** with status "done" and evaluation summary
404
+ 6. Call **store_memory** with structured report
405
+ 7. Check for pending reviews (list_tasks status='needs_review' where you are reviewer) \u2014 reviews are work, process before new tasks
406
+ 8. Check for blocked tasks (list_tasks status='blocked') \u2014 can you unblock it? Do it now. Can't? Escalate to exe immediately.
407
+ 9. Check for next task \u2014 auto-chain through the queue without waiting
408
+
409
+ ## Spawning Rules (mandatory)
410
+ - To assign work to another employee: ALWAYS use create_task. The task auto-spawns the session.
411
+ - NEVER manually launch sessions (tmux send-keys, claude -p). Sessions die immediately.
412
+ - NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
413
+ - NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, do it.
414
+
415
+ ## Quality Standards
416
+
417
+ - Every recommendation includes cost/quality/latency tradeoff analysis
418
+ - Separate experimental from production-ready \u2014 label clearly
419
+ - If you can't verify, say so explicitly: "Couldn't verify because X"
301
420
  `
302
421
  };
303
422
  function getTemplate(role) {
@@ -311,10 +430,12 @@ function getTemplateForTitle(title) {
311
430
  if (t.includes("cmo") || t.includes("chief marketing")) return IDENTITY_TEMPLATES.cmo;
312
431
  if (t.includes("engineer") || t.includes("developer")) return IDENTITY_TEMPLATES["principal-engineer"];
313
432
  if (t.includes("content") || t.includes("production")) return IDENTITY_TEMPLATES["content-specialist"];
433
+ if (t.includes("ai") || t.includes("product lead") || t.includes("specialist") && !t.includes("content")) return IDENTITY_TEMPLATES["ai-specialist"];
314
434
  return null;
315
435
  }
316
436
  export {
317
437
  IDENTITY_TEMPLATES,
438
+ POST_WORK_CHECKLIST,
318
439
  getTemplate,
319
440
  getTemplateForTitle
320
441
  };
@@ -87,6 +87,11 @@ var DEFAULT_CONFIG = {
87
87
  idleKillTicksRequired: 3,
88
88
  idleKillIntercomAckWindowMs: 1e4,
89
89
  maxAutoInstances: 10
90
+ },
91
+ autoUpdate: {
92
+ checkOnBoot: true,
93
+ autoInstall: false,
94
+ checkIntervalMs: 24 * 60 * 60 * 1e3
90
95
  }
91
96
  };
92
97
 
@@ -87,6 +87,11 @@ var DEFAULT_CONFIG = {
87
87
  idleKillTicksRequired: 3,
88
88
  idleKillIntercomAckWindowMs: 1e4,
89
89
  maxAutoInstances: 10
90
+ },
91
+ autoUpdate: {
92
+ checkOnBoot: true,
93
+ autoInstall: false,
94
+ checkIntervalMs: 24 * 60 * 60 * 1e3
90
95
  }
91
96
  };
92
97
 
@@ -323,11 +323,12 @@ function queueIntercom(targetSession, reason) {
323
323
  }
324
324
  writeQueue(queue);
325
325
  }
326
- var QUEUE_PATH, INTERCOM_LOG;
326
+ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
327
327
  var init_intercom_queue = __esm({
328
328
  "src/lib/intercom-queue.ts"() {
329
329
  "use strict";
330
330
  QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
331
+ TTL_MS = 60 * 60 * 1e3;
331
332
  INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
332
333
  }
333
334
  });
@@ -419,6 +420,11 @@ var init_config = __esm({
419
420
  idleKillTicksRequired: 3,
420
421
  idleKillIntercomAckWindowMs: 1e4,
421
422
  maxAutoInstances: 10
423
+ },
424
+ autoUpdate: {
425
+ checkOnBoot: true,
426
+ autoInstall: false,
427
+ checkIntervalMs: 24 * 60 * 60 * 1e3
422
428
  }
423
429
  };
424
430
  }
@@ -556,6 +562,17 @@ function getGitRoot(dir) {
556
562
  return null;
557
563
  }
558
564
  }
565
+ function getMainRepoRoot(dir) {
566
+ try {
567
+ const commonDir = execSync4(
568
+ "git rev-parse --path-format=absolute --git-common-dir",
569
+ { cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
570
+ ).trim();
571
+ return realpath(path7.dirname(commonDir));
572
+ } catch {
573
+ return null;
574
+ }
575
+ }
559
576
  function worktreePath(repoRoot, employeeName, instance) {
560
577
  const label = instanceLabel(employeeName, instance);
561
578
  return path7.join(repoRoot, ".worktrees", label);
@@ -781,6 +798,11 @@ function getSessionState(sessionName) {
781
798
  if (!transport.isAlive(sessionName)) return "offline";
782
799
  try {
783
800
  const pane = transport.capturePane(sessionName, 5);
801
+ if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
802
+ if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
803
+ return "no_claude";
804
+ }
805
+ }
784
806
  if (/Running…/.test(pane)) return "tool";
785
807
  if (BUSY_PATTERN.test(pane)) return "thinking";
786
808
  return "idle";
@@ -788,10 +810,6 @@ function getSessionState(sessionName) {
788
810
  return "offline";
789
811
  }
790
812
  }
791
- function isSessionBusy(sessionName) {
792
- const state = getSessionState(sessionName);
793
- return state === "thinking" || state === "tool";
794
- }
795
813
  function isExeSession(sessionName) {
796
814
  return /^exe\d*$/.test(sessionName);
797
815
  }
@@ -811,7 +829,14 @@ function sendIntercom(targetSession) {
811
829
  logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
812
830
  return "failed";
813
831
  }
814
- if (isSessionBusy(targetSession)) {
832
+ const sessionState = getSessionState(targetSession);
833
+ if (sessionState === "no_claude") {
834
+ queueIntercom(targetSession, "claude not running in session");
835
+ recordDebounce(targetSession);
836
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
837
+ return "queued";
838
+ }
839
+ if (sessionState === "thinking" || sessionState === "tool") {
815
840
  queueIntercom(targetSession, "session busy at send time");
816
841
  recordDebounce(targetSession);
817
842
  logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
@@ -823,18 +848,7 @@ function sendIntercom(targetSession) {
823
848
  }
824
849
  transport.sendKeys(targetSession, "/exe-intercom");
825
850
  recordDebounce(targetSession);
826
- for (let i = 0; i < INTERCOM_POLL_MAX_ATTEMPTS; i++) {
827
- try {
828
- execSync5(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
829
- } catch {
830
- }
831
- const state = getSessionState(targetSession);
832
- if (state === "thinking" || state === "tool") {
833
- logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
834
- return "acknowledged";
835
- }
836
- }
837
- logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
851
+ logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
838
852
  return "delivered";
839
853
  } catch {
840
854
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -888,7 +902,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
888
902
  return { status: "failed", sessionName, error: "intercom delivery failed" };
889
903
  }
890
904
  const spawnOpts = { ...opts, instance: effectiveInstance };
891
- const wtPath = ensureWorktree(projectDir, employeeName, effectiveInstance);
905
+ const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
906
+ const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
892
907
  if (wtPath) {
893
908
  spawnOpts.cwd = wtPath;
894
909
  }
@@ -1069,7 +1084,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1069
1084
  let booted = false;
1070
1085
  for (let i = 0; i < 30; i++) {
1071
1086
  try {
1072
- execSync5("sleep 1");
1087
+ execSync5("sleep 0.5");
1073
1088
  } catch {
1074
1089
  }
1075
1090
  try {
@@ -1089,7 +1104,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1089
1104
  }
1090
1105
  }
1091
1106
  if (!booted) {
1092
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 30s` };
1107
+ return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
1093
1108
  }
1094
1109
  if (!useExeAgent) {
1095
1110
  try {
@@ -1107,7 +1122,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
1107
1122
  });
1108
1123
  return { sessionName };
1109
1124
  }
1110
- var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN, INTERCOM_POLL_INTERVAL_S, INTERCOM_POLL_MAX_ATTEMPTS;
1125
+ var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
1111
1126
  var init_tmux_routing = __esm({
1112
1127
  "src/lib/tmux-routing.ts"() {
1113
1128
  "use strict";
@@ -1127,8 +1142,6 @@ var init_tmux_routing = __esm({
1127
1142
  DEBOUNCE_FILE = path8.join(SESSION_CACHE, "intercom-debounce.json");
1128
1143
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
1129
1144
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
1130
- INTERCOM_POLL_INTERVAL_S = 1;
1131
- INTERCOM_POLL_MAX_ATTEMPTS = 8;
1132
1145
  }
1133
1146
  });
1134
1147
 
@@ -80,6 +80,11 @@ function normalizeSessionLifecycle(raw) {
80
80
  const userSL = raw.sessionLifecycle ?? {};
81
81
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
82
82
  }
83
+ function normalizeAutoUpdate(raw) {
84
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
85
+ const userAU = raw.autoUpdate ?? {};
86
+ raw.autoUpdate = { ...defaultAU, ...userAU };
87
+ }
83
88
  async function loadConfig() {
84
89
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
85
90
  await mkdir2(dir, { recursive: true });
@@ -102,6 +107,7 @@ async function loadConfig() {
102
107
  }
103
108
  normalizeScalingRoadmap(migratedCfg);
104
109
  normalizeSessionLifecycle(migratedCfg);
110
+ normalizeAutoUpdate(migratedCfg);
105
111
  const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
106
112
  if (config.dbPath.startsWith("~")) {
107
113
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -177,6 +183,11 @@ var init_config = __esm({
177
183
  idleKillTicksRequired: 3,
178
184
  idleKillIntercomAckWindowMs: 1e4,
179
185
  maxAutoInstances: 10
186
+ },
187
+ autoUpdate: {
188
+ checkOnBoot: true,
189
+ autoInstall: false,
190
+ checkIntervalMs: 24 * 60 * 60 * 1e3
180
191
  }
181
192
  };
182
193
  CONFIG_MIGRATIONS = [
@@ -310,13 +321,27 @@ async function ensureShardSchema(client) {
310
321
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
311
322
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
312
323
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
313
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
324
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
325
+ // Source provenance columns (must match database.ts)
326
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
327
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
328
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
329
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
314
330
  ]) {
315
331
  try {
316
332
  await client.execute(col);
317
333
  } catch {
318
334
  }
319
335
  }
336
+ for (const idx of [
337
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
338
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
339
+ ]) {
340
+ try {
341
+ await client.execute(idx);
342
+ } catch {
343
+ }
344
+ }
320
345
  try {
321
346
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
322
347
  } catch {
@@ -637,6 +662,27 @@ async function ensureSchema() {
637
662
  });
638
663
  } catch {
639
664
  }
665
+ try {
666
+ await client.execute({
667
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
668
+ args: []
669
+ });
670
+ } catch {
671
+ }
672
+ try {
673
+ await client.execute({
674
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
675
+ args: []
676
+ });
677
+ } catch {
678
+ }
679
+ try {
680
+ await client.execute({
681
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
682
+ args: []
683
+ });
684
+ } catch {
685
+ }
640
686
  try {
641
687
  await client.execute({
642
688
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -1047,6 +1093,15 @@ async function ensureSchema() {
1047
1093
  } catch {
1048
1094
  }
1049
1095
  }
1096
+ for (const col of [
1097
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1098
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
1099
+ ]) {
1100
+ try {
1101
+ await client.execute(col);
1102
+ } catch {
1103
+ }
1104
+ }
1050
1105
  await client.executeMultiple(`
1051
1106
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
1052
1107
  ON memories(workspace_id);
@@ -1111,6 +1166,34 @@ async function ensureSchema() {
1111
1166
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1112
1167
  ON conversations(channel_id);
1113
1168
  `);
1169
+ try {
1170
+ await client.execute({
1171
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
1172
+ args: []
1173
+ });
1174
+ } catch {
1175
+ }
1176
+ try {
1177
+ await client.execute({
1178
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
1179
+ args: []
1180
+ });
1181
+ } catch {
1182
+ }
1183
+ try {
1184
+ await client.execute({
1185
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
1186
+ args: []
1187
+ });
1188
+ } catch {
1189
+ }
1190
+ try {
1191
+ await client.execute({
1192
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
1193
+ args: []
1194
+ });
1195
+ } catch {
1196
+ }
1114
1197
  await client.executeMultiple(`
1115
1198
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
1116
1199
  content_text,
@@ -1137,6 +1220,52 @@ async function ensureSchema() {
1137
1220
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1138
1221
  END;
1139
1222
  `);
1223
+ try {
1224
+ await client.execute({
1225
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
1226
+ args: []
1227
+ });
1228
+ } catch {
1229
+ }
1230
+ try {
1231
+ await client.execute(
1232
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
1233
+ );
1234
+ } catch {
1235
+ }
1236
+ try {
1237
+ await client.execute({
1238
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
1239
+ args: []
1240
+ });
1241
+ await client.execute({
1242
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
1243
+ args: []
1244
+ });
1245
+ } catch {
1246
+ }
1247
+ try {
1248
+ await client.execute({
1249
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
1250
+ args: []
1251
+ });
1252
+ } catch {
1253
+ }
1254
+ try {
1255
+ await client.execute(
1256
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
1257
+ );
1258
+ } catch {
1259
+ }
1260
+ for (const col of [
1261
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
1262
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
1263
+ ]) {
1264
+ try {
1265
+ await client.execute(col);
1266
+ } catch {
1267
+ }
1268
+ }
1140
1269
  }
1141
1270
 
1142
1271
  // src/lib/keychain.ts
@@ -108,6 +108,11 @@ var DEFAULT_CONFIG = {
108
108
  idleKillTicksRequired: 3,
109
109
  idleKillIntercomAckWindowMs: 1e4,
110
110
  maxAutoInstances: 10
111
+ },
112
+ autoUpdate: {
113
+ checkOnBoot: true,
114
+ autoInstall: false,
115
+ checkIntervalMs: 24 * 60 * 60 * 1e3
111
116
  }
112
117
  };
113
118
  function migrateLegacyConfig(raw) {
@@ -165,6 +170,11 @@ function normalizeSessionLifecycle(raw) {
165
170
  const userSL = raw.sessionLifecycle ?? {};
166
171
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
167
172
  }
173
+ function normalizeAutoUpdate(raw) {
174
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
175
+ const userAU = raw.autoUpdate ?? {};
176
+ raw.autoUpdate = { ...defaultAU, ...userAU };
177
+ }
168
178
  async function loadConfig() {
169
179
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
170
180
  await mkdir(dir, { recursive: true });
@@ -187,6 +197,7 @@ async function loadConfig() {
187
197
  }
188
198
  normalizeScalingRoadmap(migratedCfg);
189
199
  normalizeSessionLifecycle(migratedCfg);
200
+ normalizeAutoUpdate(migratedCfg);
190
201
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
191
202
  if (config.dbPath.startsWith("~")) {
192
203
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -182,6 +182,11 @@ function buildTeam(employees, data) {
182
182
  const role = emp.role ? ` (${emp.role})` : "";
183
183
  lines.push(` ${emoji} ${emp.name}${role} \u2014 ${memStr}`);
184
184
  }
185
+ if (data.plan) {
186
+ const planLabel = data.plan === "pro" ? "Solopreneur" : data.plan.charAt(0).toUpperCase() + data.plan.slice(1);
187
+ const limit = data.employeeLimit ?? "?";
188
+ lines.push(` Plan: ${planLabel} | ${employees.length}/${limit} employees | https://askexe.com`);
189
+ }
185
190
  return lines;
186
191
  }
187
192
  function buildHealth(data) {