@askexenow/exe-os 0.8.80 → 0.8.82

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 (110) hide show
  1. package/dist/bin/backfill-conversations.js +359 -267
  2. package/dist/bin/backfill-responses.js +357 -265
  3. package/dist/bin/backfill-vectors.js +339 -264
  4. package/dist/bin/cleanup-stale-review-tasks.js +315 -256
  5. package/dist/bin/cli.js +494 -240
  6. package/dist/bin/exe-agent.js +141 -46
  7. package/dist/bin/exe-assign.js +151 -63
  8. package/dist/bin/exe-boot.js +294 -115
  9. package/dist/bin/exe-call.js +76 -51
  10. package/dist/bin/exe-cloud.js +58 -45
  11. package/dist/bin/exe-dispatch.js +434 -277
  12. package/dist/bin/exe-doctor.js +317 -246
  13. package/dist/bin/exe-export-behaviors.js +328 -248
  14. package/dist/bin/exe-forget.js +314 -231
  15. package/dist/bin/exe-gateway.js +2676 -1402
  16. package/dist/bin/exe-heartbeat.js +329 -264
  17. package/dist/bin/exe-kill.js +324 -244
  18. package/dist/bin/exe-launch-agent.js +574 -463
  19. package/dist/bin/exe-link.js +1055 -95
  20. package/dist/bin/exe-new-employee.js +49 -54
  21. package/dist/bin/exe-pending-messages.js +310 -253
  22. package/dist/bin/exe-pending-notifications.js +299 -228
  23. package/dist/bin/exe-pending-reviews.js +314 -245
  24. package/dist/bin/exe-rename.js +259 -195
  25. package/dist/bin/exe-review.js +140 -64
  26. package/dist/bin/exe-search.js +543 -356
  27. package/dist/bin/exe-session-cleanup.js +463 -382
  28. package/dist/bin/exe-settings.js +129 -99
  29. package/dist/bin/exe-start.sh +6 -6
  30. package/dist/bin/exe-status.js +95 -36
  31. package/dist/bin/exe-team.js +116 -51
  32. package/dist/bin/git-sweep.js +482 -307
  33. package/dist/bin/graph-backfill.js +357 -245
  34. package/dist/bin/graph-export.js +324 -244
  35. package/dist/bin/install.js +33 -10
  36. package/dist/bin/scan-tasks.js +481 -307
  37. package/dist/bin/setup.js +1147 -140
  38. package/dist/bin/shard-migrate.js +321 -241
  39. package/dist/bin/update.js +1 -7
  40. package/dist/bin/wiki-sync.js +318 -238
  41. package/dist/gateway/index.js +2656 -1383
  42. package/dist/hooks/bug-report-worker.js +641 -472
  43. package/dist/hooks/commit-complete.js +482 -307
  44. package/dist/hooks/error-recall.js +363 -135
  45. package/dist/hooks/exe-heartbeat-hook.js +97 -27
  46. package/dist/hooks/ingest-worker.js +584 -397
  47. package/dist/hooks/ingest.js +123 -58
  48. package/dist/hooks/instructions-loaded.js +212 -82
  49. package/dist/hooks/notification.js +200 -70
  50. package/dist/hooks/post-compact.js +199 -81
  51. package/dist/hooks/pre-compact.js +352 -140
  52. package/dist/hooks/pre-tool-use.js +416 -278
  53. package/dist/hooks/prompt-ingest-worker.js +376 -299
  54. package/dist/hooks/prompt-submit.js +414 -188
  55. package/dist/hooks/response-ingest-worker.js +408 -338
  56. package/dist/hooks/session-end.js +209 -83
  57. package/dist/hooks/session-start.js +382 -158
  58. package/dist/hooks/stop.js +209 -83
  59. package/dist/hooks/subagent-stop.js +209 -85
  60. package/dist/hooks/summary-worker.js +606 -510
  61. package/dist/index.js +2133 -855
  62. package/dist/lib/cloud-sync.js +1175 -184
  63. package/dist/lib/config.js +1 -9
  64. package/dist/lib/consolidation.js +71 -34
  65. package/dist/lib/database.js +166 -14
  66. package/dist/lib/device-registry.js +189 -117
  67. package/dist/lib/embedder.js +6 -10
  68. package/dist/lib/employee-templates.js +134 -39
  69. package/dist/lib/employees.js +30 -7
  70. package/dist/lib/exe-daemon-client.js +5 -7
  71. package/dist/lib/exe-daemon.js +514 -152
  72. package/dist/lib/hybrid-search.js +543 -356
  73. package/dist/lib/identity-templates.js +15 -15
  74. package/dist/lib/identity.js +19 -15
  75. package/dist/lib/license.js +1 -7
  76. package/dist/lib/messaging.js +157 -135
  77. package/dist/lib/reminders.js +97 -0
  78. package/dist/lib/schedules.js +302 -231
  79. package/dist/lib/skill-learning.js +33 -27
  80. package/dist/lib/status-brief.js +11 -14
  81. package/dist/lib/store.js +326 -237
  82. package/dist/lib/task-router.js +105 -1
  83. package/dist/lib/tasks.js +233 -116
  84. package/dist/lib/tmux-routing.js +173 -56
  85. package/dist/lib/ws-client.js +13 -3
  86. package/dist/mcp/server.js +2009 -1015
  87. package/dist/mcp/tools/complete-reminder.js +97 -0
  88. package/dist/mcp/tools/create-reminder.js +97 -0
  89. package/dist/mcp/tools/create-task.js +426 -262
  90. package/dist/mcp/tools/deactivate-behavior.js +119 -44
  91. package/dist/mcp/tools/list-reminders.js +97 -0
  92. package/dist/mcp/tools/list-tasks.js +56 -57
  93. package/dist/mcp/tools/send-message.js +206 -143
  94. package/dist/mcp/tools/update-task.js +259 -85
  95. package/dist/runtime/index.js +495 -316
  96. package/dist/tui/App.js +1128 -919
  97. package/package.json +2 -10
  98. package/src/commands/exe/afk.md +8 -8
  99. package/src/commands/exe/assign.md +1 -1
  100. package/src/commands/exe/build-adv.md +1 -1
  101. package/src/commands/exe/call.md +10 -10
  102. package/src/commands/exe/employee-heartbeat.md +9 -6
  103. package/src/commands/exe/heartbeat.md +5 -5
  104. package/src/commands/exe/intercom.md +26 -15
  105. package/src/commands/exe/launch.md +2 -2
  106. package/src/commands/exe/new-employee.md +1 -1
  107. package/src/commands/exe/review.md +2 -2
  108. package/src/commands/exe/schedule.md +1 -1
  109. package/src/commands/exe/sessions.md +2 -2
  110. package/src/commands/exe.md +22 -20
@@ -25,6 +25,240 @@ var __copyProps = (to, from, except, desc) => {
25
25
  };
26
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
27
 
28
+ // src/lib/config.ts
29
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
30
+ import { readFileSync, existsSync, renameSync } from "fs";
31
+ import path2 from "path";
32
+ import os2 from "os";
33
+ function resolveDataDir() {
34
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
35
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
36
+ const newDir = path2.join(os2.homedir(), ".exe-os");
37
+ const legacyDir = path2.join(os2.homedir(), ".exe-mem");
38
+ if (!existsSync(newDir) && existsSync(legacyDir)) {
39
+ try {
40
+ renameSync(legacyDir, newDir);
41
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
42
+ `);
43
+ } catch {
44
+ return legacyDir;
45
+ }
46
+ }
47
+ return newDir;
48
+ }
49
+ function migrateLegacyConfig(raw) {
50
+ if ("r2" in raw) {
51
+ process.stderr.write(
52
+ "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
53
+ );
54
+ delete raw.r2;
55
+ }
56
+ if ("syncIntervalMs" in raw) {
57
+ delete raw.syncIntervalMs;
58
+ }
59
+ return raw;
60
+ }
61
+ function migrateConfig(raw) {
62
+ const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
63
+ let currentVersion = fromVersion;
64
+ let migrated = false;
65
+ if (currentVersion > CURRENT_CONFIG_VERSION) {
66
+ return { config: raw, migrated: false, fromVersion };
67
+ }
68
+ for (const migration of CONFIG_MIGRATIONS) {
69
+ if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
70
+ raw = migration.migrate(raw);
71
+ currentVersion = migration.to;
72
+ migrated = true;
73
+ }
74
+ }
75
+ return { config: raw, migrated, fromVersion };
76
+ }
77
+ function normalizeScalingRoadmap(raw) {
78
+ const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
79
+ const userRoadmap = raw.scalingRoadmap ?? {};
80
+ const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
81
+ if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
82
+ userAuto.enabled = raw.rerankerEnabled;
83
+ }
84
+ raw.scalingRoadmap = {
85
+ ...userRoadmap,
86
+ rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
87
+ };
88
+ }
89
+ function normalizeSessionLifecycle(raw) {
90
+ const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
91
+ const userSL = raw.sessionLifecycle ?? {};
92
+ raw.sessionLifecycle = { ...defaultSL, ...userSL };
93
+ }
94
+ function normalizeAutoUpdate(raw) {
95
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
96
+ const userAU = raw.autoUpdate ?? {};
97
+ raw.autoUpdate = { ...defaultAU, ...userAU };
98
+ }
99
+ async function loadConfig() {
100
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
101
+ await mkdir(dir, { recursive: true });
102
+ const configPath = path2.join(dir, "config.json");
103
+ if (!existsSync(configPath)) {
104
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
105
+ }
106
+ const raw = await readFile(configPath, "utf-8");
107
+ try {
108
+ let parsed = JSON.parse(raw);
109
+ parsed = migrateLegacyConfig(parsed);
110
+ const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
111
+ if (migrated) {
112
+ process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
113
+ `);
114
+ try {
115
+ await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
116
+ } catch {
117
+ }
118
+ }
119
+ normalizeScalingRoadmap(migratedCfg);
120
+ normalizeSessionLifecycle(migratedCfg);
121
+ normalizeAutoUpdate(migratedCfg);
122
+ const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
123
+ if (config.dbPath.startsWith("~")) {
124
+ config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
125
+ }
126
+ return config;
127
+ } catch {
128
+ return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
129
+ }
130
+ }
131
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
132
+ var init_config = __esm({
133
+ "src/lib/config.ts"() {
134
+ "use strict";
135
+ EXE_AI_DIR = resolveDataDir();
136
+ DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
137
+ MODELS_DIR = path2.join(EXE_AI_DIR, "models");
138
+ CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
139
+ LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
140
+ CURRENT_CONFIG_VERSION = 1;
141
+ DEFAULT_CONFIG = {
142
+ config_version: CURRENT_CONFIG_VERSION,
143
+ dbPath: DB_PATH,
144
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
145
+ embeddingDim: 1024,
146
+ batchSize: 20,
147
+ flushIntervalMs: 1e4,
148
+ autoIngestion: true,
149
+ autoRetrieval: true,
150
+ searchMode: "hybrid",
151
+ hookSearchMode: "hybrid",
152
+ fileGrepEnabled: true,
153
+ splashEffect: true,
154
+ consolidationEnabled: true,
155
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
156
+ consolidationModel: "claude-haiku-4-5-20251001",
157
+ consolidationMaxCallsPerRun: 20,
158
+ selfQueryRouter: true,
159
+ selfQueryModel: "claude-haiku-4-5-20251001",
160
+ rerankerEnabled: true,
161
+ scalingRoadmap: {
162
+ rerankerAutoTrigger: {
163
+ enabled: true,
164
+ broadQueryMinCardinality: 5e4,
165
+ fetchTopK: 150,
166
+ returnTopK: 5
167
+ }
168
+ },
169
+ graphRagEnabled: true,
170
+ wikiEnabled: false,
171
+ wikiUrl: "",
172
+ wikiApiKey: "",
173
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
174
+ wikiWorkspaceMapping: {},
175
+ wikiAutoUpdate: true,
176
+ wikiAutoUpdateThreshold: 0.5,
177
+ wikiAutoUpdateCreateNew: true,
178
+ skillLearning: true,
179
+ skillThreshold: 3,
180
+ skillModel: "claude-haiku-4-5-20251001",
181
+ exeHeartbeat: {
182
+ enabled: true,
183
+ intervalSeconds: 60,
184
+ staleInProgressThresholdHours: 2
185
+ },
186
+ sessionLifecycle: {
187
+ idleKillEnabled: true,
188
+ idleKillTicksRequired: 3,
189
+ idleKillIntercomAckWindowMs: 1e4,
190
+ maxAutoInstances: 10
191
+ },
192
+ autoUpdate: {
193
+ checkOnBoot: true,
194
+ autoInstall: false,
195
+ checkIntervalMs: 24 * 60 * 60 * 1e3
196
+ }
197
+ };
198
+ CONFIG_MIGRATIONS = [
199
+ {
200
+ from: 0,
201
+ to: 1,
202
+ migrate: (cfg) => {
203
+ cfg.config_version = 1;
204
+ return cfg;
205
+ }
206
+ }
207
+ ];
208
+ }
209
+ });
210
+
211
+ // src/lib/employees.ts
212
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
213
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
214
+ import { execSync } from "child_process";
215
+ import path3 from "path";
216
+ import os3 from "os";
217
+ function normalizeRole(role) {
218
+ return (role ?? "").trim().toLowerCase();
219
+ }
220
+ function isCoordinatorRole(role) {
221
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
222
+ }
223
+ function getCoordinatorEmployee(employees) {
224
+ return employees.find((e) => isCoordinatorRole(e.role));
225
+ }
226
+ function getCoordinatorName(employees = loadEmployeesSync()) {
227
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
228
+ }
229
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
230
+ if (!agentName) return false;
231
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
232
+ }
233
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
234
+ if (!existsSync2(employeesPath)) return [];
235
+ try {
236
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
237
+ } catch {
238
+ return [];
239
+ }
240
+ }
241
+ function getEmployee(employees, name) {
242
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
243
+ }
244
+ function isMultiInstance(agentName, employees) {
245
+ const roster = employees ?? loadEmployeesSync();
246
+ const emp = getEmployee(roster, agentName);
247
+ if (!emp) return false;
248
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
249
+ }
250
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
251
+ var init_employees = __esm({
252
+ "src/lib/employees.ts"() {
253
+ "use strict";
254
+ init_config();
255
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
256
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
257
+ COORDINATOR_ROLE = "COO";
258
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
259
+ }
260
+ });
261
+
28
262
  // src/lib/session-registry.ts
29
263
  var session_registry_exports = {};
30
264
  __export(session_registry_exports, {
@@ -32,13 +266,13 @@ __export(session_registry_exports, {
32
266
  pruneStaleSessions: () => pruneStaleSessions,
33
267
  registerSession: () => registerSession
34
268
  });
35
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
36
- import { execSync } from "child_process";
37
- import path2 from "path";
38
- import os2 from "os";
269
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync3 } from "fs";
270
+ import { execSync as execSync2 } from "child_process";
271
+ import path4 from "path";
272
+ import os4 from "os";
39
273
  function registerSession(entry) {
40
- const dir = path2.dirname(REGISTRY_PATH);
41
- if (!existsSync(dir)) {
274
+ const dir = path4.dirname(REGISTRY_PATH);
275
+ if (!existsSync3(dir)) {
42
276
  mkdirSync(dir, { recursive: true });
43
277
  }
44
278
  const sessions = listSessions();
@@ -48,11 +282,11 @@ function registerSession(entry) {
48
282
  } else {
49
283
  sessions.push(entry);
50
284
  }
51
- writeFileSync(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
285
+ writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
52
286
  }
53
287
  function listSessions() {
54
288
  try {
55
- const raw = readFileSync(REGISTRY_PATH, "utf8");
289
+ const raw = readFileSync3(REGISTRY_PATH, "utf8");
56
290
  return JSON.parse(raw);
57
291
  } catch {
58
292
  return [];
@@ -63,7 +297,7 @@ function pruneStaleSessions() {
63
297
  if (sessions.length === 0) return 0;
64
298
  let liveSessions = [];
65
299
  try {
66
- liveSessions = execSync("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
300
+ liveSessions = execSync2("tmux list-sessions -F '#{session_name}' 2>/dev/null", {
67
301
  encoding: "utf8"
68
302
  }).trim().split("\n").filter(Boolean);
69
303
  } catch {
@@ -73,7 +307,7 @@ function pruneStaleSessions() {
73
307
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
74
308
  const pruned = sessions.length - alive.length;
75
309
  if (pruned > 0) {
76
- writeFileSync(REGISTRY_PATH, JSON.stringify(alive, null, 2));
310
+ writeFileSync2(REGISTRY_PATH, JSON.stringify(alive, null, 2));
77
311
  }
78
312
  return pruned;
79
313
  }
@@ -81,18 +315,18 @@ var REGISTRY_PATH;
81
315
  var init_session_registry = __esm({
82
316
  "src/lib/session-registry.ts"() {
83
317
  "use strict";
84
- REGISTRY_PATH = path2.join(os2.homedir(), ".exe-os", "session-registry.json");
318
+ REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
85
319
  }
86
320
  });
87
321
 
88
322
  // src/lib/session-key.ts
89
- import { execSync as execSync2 } from "child_process";
323
+ import { execSync as execSync3 } from "child_process";
90
324
  function getSessionKey() {
91
325
  if (_cached) return _cached;
92
326
  let pid = process.ppid;
93
327
  for (let i = 0; i < 10; i++) {
94
328
  try {
95
- const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
329
+ const info = execSync3(`ps -p ${pid} -o ppid=,comm=`, {
96
330
  encoding: "utf8",
97
331
  timeout: 2e3
98
332
  }).trim();
@@ -228,14 +462,14 @@ var init_transport = __esm({
228
462
  });
229
463
 
230
464
  // src/lib/cc-agent-support.ts
231
- import { execSync as execSync3 } from "child_process";
465
+ import { execSync as execSync4 } from "child_process";
232
466
  function _resetCcAgentSupportCache() {
233
467
  _cachedSupport = null;
234
468
  }
235
469
  function claudeSupportsAgentFlag() {
236
470
  if (_cachedSupport !== null) return _cachedSupport;
237
471
  try {
238
- const helpOutput = execSync3("claude --help 2>&1", {
472
+ const helpOutput = execSync4("claude --help 2>&1", {
239
473
  encoding: "utf-8",
240
474
  timeout: 5e3
241
475
  });
@@ -311,17 +545,17 @@ var init_provider_table = __esm({
311
545
  });
312
546
 
313
547
  // src/lib/intercom-queue.ts
314
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
315
- import path3 from "path";
316
- import os3 from "os";
548
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
549
+ import path5 from "path";
550
+ import os5 from "os";
317
551
  function ensureDir() {
318
- const dir = path3.dirname(QUEUE_PATH);
319
- if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
552
+ const dir = path5.dirname(QUEUE_PATH);
553
+ if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
320
554
  }
321
555
  function readQueue() {
322
556
  try {
323
- if (!existsSync2(QUEUE_PATH)) return [];
324
- return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
557
+ if (!existsSync4(QUEUE_PATH)) return [];
558
+ return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
325
559
  } catch {
326
560
  return [];
327
561
  }
@@ -329,8 +563,8 @@ function readQueue() {
329
563
  function writeQueue(queue) {
330
564
  ensureDir();
331
565
  const tmp = `${QUEUE_PATH}.tmp`;
332
- writeFileSync2(tmp, JSON.stringify(queue, null, 2));
333
- renameSync(tmp, QUEUE_PATH);
566
+ writeFileSync3(tmp, JSON.stringify(queue, null, 2));
567
+ renameSync3(tmp, QUEUE_PATH);
334
568
  }
335
569
  function queueIntercom(targetSession, reason) {
336
570
  const queue = readQueue();
@@ -353,9 +587,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
353
587
  var init_intercom_queue = __esm({
354
588
  "src/lib/intercom-queue.ts"() {
355
589
  "use strict";
356
- QUEUE_PATH = path3.join(os3.homedir(), ".exe-os", "intercom-queue.json");
590
+ QUEUE_PATH = path5.join(os5.homedir(), ".exe-os", "intercom-queue.json");
357
591
  TTL_MS = 60 * 60 * 1e3;
358
- INTERCOM_LOG = path3.join(os3.homedir(), ".exe-os", "intercom.log");
592
+ INTERCOM_LOG = path5.join(os5.homedir(), ".exe-os", "intercom.log");
359
593
  }
360
594
  });
361
595
 
@@ -398,7 +632,7 @@ function wrapWithRetry(client) {
398
632
  return (sql) => retryOnBusy(() => target.execute(sql), "execute");
399
633
  }
400
634
  if (prop === "batch") {
401
- return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
635
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
402
636
  }
403
637
  return Reflect.get(target, prop, receiver);
404
638
  }
@@ -561,22 +795,24 @@ async function ensureSchema() {
561
795
  ON behaviors(agent_id, active);
562
796
  `);
563
797
  try {
798
+ const coordinatorName = getCoordinatorName();
564
799
  const existing = await client.execute({
565
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
566
- args: []
800
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
801
+ args: [coordinatorName]
567
802
  });
568
803
  if (Number(existing.rows[0]?.cnt) === 0) {
569
- await client.executeMultiple(`
570
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
571
- VALUES
572
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Don''t ask "keep going?" \u2014 just keep executing phases/plans autonomously', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
573
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
574
- VALUES
575
- (hex(randomblob(16)), 'exe', NULL, 'tool-use', 'Always use create_task MCP tool, never write .md files directly for task creation', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
576
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
577
- VALUES
578
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
579
- `);
804
+ const seededAt = "2026-03-25T00:00:00Z";
805
+ for (const [domain, content] of [
806
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
807
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
808
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
809
+ ]) {
810
+ await client.execute({
811
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
812
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
813
+ args: [coordinatorName, domain, content, seededAt, seededAt]
814
+ });
815
+ }
580
816
  }
581
817
  } catch {
582
818
  }
@@ -1268,6 +1504,39 @@ async function ensureSchema() {
1268
1504
  } catch {
1269
1505
  }
1270
1506
  }
1507
+ try {
1508
+ await client.execute({
1509
+ sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
1510
+ args: []
1511
+ });
1512
+ } catch {
1513
+ }
1514
+ try {
1515
+ await client.execute(
1516
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
1517
+ );
1518
+ } catch {
1519
+ }
1520
+ try {
1521
+ await client.execute({
1522
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
1523
+ args: []
1524
+ });
1525
+ } catch {
1526
+ }
1527
+ try {
1528
+ await client.execute(
1529
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
1530
+ );
1531
+ } catch {
1532
+ }
1533
+ try {
1534
+ await client.execute({
1535
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
1536
+ args: []
1537
+ });
1538
+ } catch {
1539
+ }
1271
1540
  }
1272
1541
  async function disposeDatabase() {
1273
1542
  if (_client) {
@@ -1281,6 +1550,7 @@ var init_database = __esm({
1281
1550
  "src/lib/database.ts"() {
1282
1551
  "use strict";
1283
1552
  init_db_retry();
1553
+ init_employees();
1284
1554
  _client = null;
1285
1555
  _resilientClient = null;
1286
1556
  initTurso = initDatabase;
@@ -1288,228 +1558,6 @@ var init_database = __esm({
1288
1558
  }
1289
1559
  });
1290
1560
 
1291
- // src/lib/config.ts
1292
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
1293
- import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
1294
- import path4 from "path";
1295
- import os4 from "os";
1296
- function resolveDataDir() {
1297
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
1298
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
1299
- const newDir = path4.join(os4.homedir(), ".exe-os");
1300
- const legacyDir = path4.join(os4.homedir(), ".exe-mem");
1301
- if (!existsSync3(newDir) && existsSync3(legacyDir)) {
1302
- try {
1303
- renameSync2(legacyDir, newDir);
1304
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
1305
- `);
1306
- } catch {
1307
- return legacyDir;
1308
- }
1309
- }
1310
- return newDir;
1311
- }
1312
- function migrateLegacyConfig(raw) {
1313
- if ("r2" in raw) {
1314
- process.stderr.write(
1315
- "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
1316
- );
1317
- delete raw.r2;
1318
- }
1319
- if ("syncIntervalMs" in raw) {
1320
- delete raw.syncIntervalMs;
1321
- }
1322
- return raw;
1323
- }
1324
- function migrateConfig(raw) {
1325
- const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
1326
- let currentVersion = fromVersion;
1327
- let migrated = false;
1328
- if (currentVersion > CURRENT_CONFIG_VERSION) {
1329
- return { config: raw, migrated: false, fromVersion };
1330
- }
1331
- for (const migration of CONFIG_MIGRATIONS) {
1332
- if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
1333
- raw = migration.migrate(raw);
1334
- currentVersion = migration.to;
1335
- migrated = true;
1336
- }
1337
- }
1338
- return { config: raw, migrated, fromVersion };
1339
- }
1340
- function normalizeScalingRoadmap(raw) {
1341
- const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
1342
- const userRoadmap = raw.scalingRoadmap ?? {};
1343
- const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
1344
- if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
1345
- userAuto.enabled = raw.rerankerEnabled;
1346
- }
1347
- raw.scalingRoadmap = {
1348
- ...userRoadmap,
1349
- rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
1350
- };
1351
- }
1352
- function normalizeSessionLifecycle(raw) {
1353
- const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
1354
- const userSL = raw.sessionLifecycle ?? {};
1355
- raw.sessionLifecycle = { ...defaultSL, ...userSL };
1356
- }
1357
- function normalizeAutoUpdate(raw) {
1358
- const defaultAU = DEFAULT_CONFIG.autoUpdate;
1359
- const userAU = raw.autoUpdate ?? {};
1360
- raw.autoUpdate = { ...defaultAU, ...userAU };
1361
- }
1362
- async function loadConfig() {
1363
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
1364
- await mkdir(dir, { recursive: true });
1365
- const configPath = path4.join(dir, "config.json");
1366
- if (!existsSync3(configPath)) {
1367
- return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
1368
- }
1369
- const raw = await readFile(configPath, "utf-8");
1370
- try {
1371
- let parsed = JSON.parse(raw);
1372
- parsed = migrateLegacyConfig(parsed);
1373
- const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
1374
- if (migrated) {
1375
- process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
1376
- `);
1377
- try {
1378
- await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
1379
- } catch {
1380
- }
1381
- }
1382
- normalizeScalingRoadmap(migratedCfg);
1383
- normalizeSessionLifecycle(migratedCfg);
1384
- normalizeAutoUpdate(migratedCfg);
1385
- const config = { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
1386
- if (config.dbPath.startsWith("~")) {
1387
- config.dbPath = config.dbPath.replace(/^~/, os4.homedir());
1388
- }
1389
- return config;
1390
- } catch {
1391
- return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
1392
- }
1393
- }
1394
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1395
- var init_config = __esm({
1396
- "src/lib/config.ts"() {
1397
- "use strict";
1398
- EXE_AI_DIR = resolveDataDir();
1399
- DB_PATH = path4.join(EXE_AI_DIR, "memories.db");
1400
- MODELS_DIR = path4.join(EXE_AI_DIR, "models");
1401
- CONFIG_PATH = path4.join(EXE_AI_DIR, "config.json");
1402
- LEGACY_LANCE_PATH = path4.join(EXE_AI_DIR, "local.lance");
1403
- CURRENT_CONFIG_VERSION = 1;
1404
- DEFAULT_CONFIG = {
1405
- config_version: CURRENT_CONFIG_VERSION,
1406
- dbPath: DB_PATH,
1407
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
1408
- embeddingDim: 1024,
1409
- batchSize: 20,
1410
- flushIntervalMs: 1e4,
1411
- autoIngestion: true,
1412
- autoRetrieval: true,
1413
- searchMode: "hybrid",
1414
- hookSearchMode: "hybrid",
1415
- fileGrepEnabled: true,
1416
- splashEffect: true,
1417
- consolidationEnabled: true,
1418
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
1419
- consolidationModel: "claude-haiku-4-5-20251001",
1420
- consolidationMaxCallsPerRun: 20,
1421
- selfQueryRouter: true,
1422
- selfQueryModel: "claude-haiku-4-5-20251001",
1423
- rerankerEnabled: true,
1424
- scalingRoadmap: {
1425
- rerankerAutoTrigger: {
1426
- enabled: true,
1427
- broadQueryMinCardinality: 5e4,
1428
- fetchTopK: 150,
1429
- returnTopK: 5
1430
- }
1431
- },
1432
- graphRagEnabled: true,
1433
- wikiEnabled: false,
1434
- wikiUrl: "",
1435
- wikiApiKey: "",
1436
- wikiSyncIntervalMs: 30 * 60 * 1e3,
1437
- wikiWorkspaceMapping: {
1438
- exe: "Executive",
1439
- yoshi: "Engineering",
1440
- mari: "Marketing",
1441
- tom: "Engineering",
1442
- sasha: "Production"
1443
- },
1444
- wikiAutoUpdate: true,
1445
- wikiAutoUpdateThreshold: 0.5,
1446
- wikiAutoUpdateCreateNew: true,
1447
- skillLearning: true,
1448
- skillThreshold: 3,
1449
- skillModel: "claude-haiku-4-5-20251001",
1450
- exeHeartbeat: {
1451
- enabled: true,
1452
- intervalSeconds: 60,
1453
- staleInProgressThresholdHours: 2
1454
- },
1455
- sessionLifecycle: {
1456
- idleKillEnabled: true,
1457
- idleKillTicksRequired: 3,
1458
- idleKillIntercomAckWindowMs: 1e4,
1459
- maxAutoInstances: 10
1460
- },
1461
- autoUpdate: {
1462
- checkOnBoot: true,
1463
- autoInstall: false,
1464
- checkIntervalMs: 24 * 60 * 60 * 1e3
1465
- }
1466
- };
1467
- CONFIG_MIGRATIONS = [
1468
- {
1469
- from: 0,
1470
- to: 1,
1471
- migrate: (cfg) => {
1472
- cfg.config_version = 1;
1473
- return cfg;
1474
- }
1475
- }
1476
- ];
1477
- }
1478
- });
1479
-
1480
- // src/lib/employees.ts
1481
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1482
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
1483
- import { execSync as execSync4 } from "child_process";
1484
- import path5 from "path";
1485
- import os5 from "os";
1486
- function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1487
- if (!existsSync4(employeesPath)) return [];
1488
- try {
1489
- return JSON.parse(readFileSync4(employeesPath, "utf-8"));
1490
- } catch {
1491
- return [];
1492
- }
1493
- }
1494
- function getEmployee(employees, name) {
1495
- return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
1496
- }
1497
- function isMultiInstance(agentName, employees) {
1498
- const roster = employees ?? loadEmployeesSync();
1499
- const emp = getEmployee(roster, agentName);
1500
- if (!emp) return false;
1501
- return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
1502
- }
1503
- var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
1504
- var init_employees = __esm({
1505
- "src/lib/employees.ts"() {
1506
- "use strict";
1507
- init_config();
1508
- EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
1509
- MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1510
- }
1511
- });
1512
-
1513
1561
  // src/lib/license.ts
1514
1562
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1515
1563
  import { randomUUID as randomUUID2 } from "crypto";
@@ -2026,6 +2074,36 @@ async function listTasks(input) {
2026
2074
  tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
2027
2075
  }));
2028
2076
  }
2077
+ function isTmuxSessionAlive(identifier) {
2078
+ if (!identifier || identifier === "unknown") return true;
2079
+ try {
2080
+ if (identifier.startsWith("%")) {
2081
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
2082
+ timeout: 2e3,
2083
+ encoding: "utf8",
2084
+ stdio: ["pipe", "pipe", "pipe"]
2085
+ });
2086
+ return output.split("\n").some((l) => l.trim() === identifier);
2087
+ } else {
2088
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
2089
+ timeout: 2e3,
2090
+ stdio: ["pipe", "pipe", "pipe"]
2091
+ });
2092
+ return true;
2093
+ }
2094
+ } catch {
2095
+ if (identifier.startsWith("%")) return true;
2096
+ try {
2097
+ execSync5("tmux list-sessions", {
2098
+ timeout: 2e3,
2099
+ stdio: ["pipe", "pipe", "pipe"]
2100
+ });
2101
+ return false;
2102
+ } catch {
2103
+ return true;
2104
+ }
2105
+ }
2106
+ }
2029
2107
  function checkStaleCompletion(taskContext, taskCreatedAt) {
2030
2108
  if (!taskContext) return null;
2031
2109
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
@@ -2088,13 +2166,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2088
2166
  });
2089
2167
  if (claim.rowsAffected === 0) {
2090
2168
  const current = await client.execute({
2091
- sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
2169
+ sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
2092
2170
  args: [taskId]
2093
2171
  });
2094
2172
  const cur = current.rows[0];
2095
- const status = cur?.status ?? "unknown";
2096
- const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
2097
- throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
2173
+ const curStatus = cur?.status ?? "unknown";
2174
+ const claimedBySession = cur?.assigned_tmux ?? "";
2175
+ const assignedBy = cur?.assigned_by ?? "";
2176
+ if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
2177
+ process.stderr.write(
2178
+ `[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
2179
+ `
2180
+ );
2181
+ await client.execute({
2182
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
2183
+ args: [now, taskId]
2184
+ });
2185
+ const retried = await client.execute({
2186
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
2187
+ args: [tmuxSession, now, taskId]
2188
+ });
2189
+ if (retried.rowsAffected > 0) {
2190
+ try {
2191
+ await writeCheckpoint({
2192
+ taskId,
2193
+ step: "reclaimed_dead_session",
2194
+ contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
2195
+ });
2196
+ } catch {
2197
+ }
2198
+ return { row, taskFile, now, taskId };
2199
+ }
2200
+ }
2201
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
2202
+ process.stderr.write(
2203
+ `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2204
+ `
2205
+ );
2206
+ await client.execute({
2207
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
2208
+ args: [tmuxSession, now, taskId]
2209
+ });
2210
+ try {
2211
+ await writeCheckpoint({
2212
+ taskId,
2213
+ step: "assigner_override",
2214
+ contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
2215
+ });
2216
+ } catch {
2217
+ }
2218
+ return { row, taskFile, now, taskId };
2219
+ }
2220
+ const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
2221
+ throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
2098
2222
  }
2099
2223
  try {
2100
2224
  await writeCheckpoint({
@@ -2192,7 +2316,7 @@ var init_tasks_crud = __esm({
2192
2316
  "use strict";
2193
2317
  init_database();
2194
2318
  init_task_scope();
2195
- DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
2319
+ DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2196
2320
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2197
2321
  }
2198
2322
  });
@@ -2549,7 +2673,7 @@ function findSessionForProject(projectName) {
2549
2673
  const sessions = listSessions();
2550
2674
  for (const s of sessions) {
2551
2675
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2552
- if (proj === projectName && s.agentId === "exe") return s;
2676
+ if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
2553
2677
  }
2554
2678
  return null;
2555
2679
  }
@@ -2589,12 +2713,13 @@ var init_session_scope = __esm({
2589
2713
  init_session_registry();
2590
2714
  init_project_name();
2591
2715
  init_tmux_routing();
2716
+ init_employees();
2592
2717
  }
2593
2718
  });
2594
2719
 
2595
2720
  // src/lib/tasks-notify.ts
2596
2721
  async function dispatchTaskToEmployee(input) {
2597
- if (input.assignedTo === "exe") return { dispatched: "skipped" };
2722
+ if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
2598
2723
  let crossProject = false;
2599
2724
  if (input.projectName) {
2600
2725
  try {
@@ -3099,6 +3224,24 @@ async function updateTask(input) {
3099
3224
  });
3100
3225
  } catch {
3101
3226
  }
3227
+ const assignedAgent = String(row.assigned_to);
3228
+ if (!isCoordinatorName(assignedAgent)) {
3229
+ try {
3230
+ const draftClient = getClient();
3231
+ if (input.status === "done") {
3232
+ await draftClient.execute({
3233
+ sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
3234
+ args: [assignedAgent]
3235
+ });
3236
+ } else if (input.status === "cancelled") {
3237
+ await draftClient.execute({
3238
+ sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
3239
+ args: [assignedAgent]
3240
+ });
3241
+ }
3242
+ } catch {
3243
+ }
3244
+ }
3102
3245
  try {
3103
3246
  const client = getClient();
3104
3247
  const cascaded = await client.execute({
@@ -3117,8 +3260,8 @@ async function updateTask(input) {
3117
3260
  }
3118
3261
  const isTerminal = input.status === "done" || input.status === "needs_review";
3119
3262
  if (isTerminal) {
3120
- const isExe = String(row.assigned_to) === "exe";
3121
- if (!isExe) {
3263
+ const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
3264
+ if (!isCoordinator) {
3122
3265
  notifyTaskDone();
3123
3266
  }
3124
3267
  await markTaskNotificationsRead(taskFile);
@@ -3142,7 +3285,7 @@ async function updateTask(input) {
3142
3285
  }
3143
3286
  }
3144
3287
  }
3145
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
3288
+ if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3146
3289
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3147
3290
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3148
3291
  taskId,
@@ -3158,7 +3301,7 @@ async function updateTask(input) {
3158
3301
  });
3159
3302
  }
3160
3303
  let nextTask;
3161
- if (isTerminal && String(row.assigned_to) !== "exe") {
3304
+ if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
3162
3305
  try {
3163
3306
  nextTask = await findNextTask(String(row.assigned_to));
3164
3307
  } catch {
@@ -3185,12 +3328,14 @@ async function updateTask(input) {
3185
3328
  async function deleteTask(taskId, baseDir) {
3186
3329
  const client = getClient();
3187
3330
  const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
3188
- const reviewer = assignedBy || "exe";
3331
+ const coordinatorName = getCoordinatorName();
3332
+ const reviewer = assignedBy || coordinatorName;
3189
3333
  const reviewSlug = `review-${assignedTo}-${taskSlug}`;
3190
3334
  const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
3335
+ const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
3191
3336
  await client.execute({
3192
- sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
3193
- args: [reviewFile, `exe/exe/${reviewSlug}.md`]
3337
+ sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
3338
+ args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
3194
3339
  });
3195
3340
  await markAsReadByTaskFile(taskFile);
3196
3341
  await markAsReadByTaskFile(reviewFile);
@@ -3202,6 +3347,7 @@ var init_tasks = __esm({
3202
3347
  init_config();
3203
3348
  init_notifications();
3204
3349
  init_state_bus();
3350
+ init_employees();
3205
3351
  init_tasks_crud();
3206
3352
  init_tasks_review();
3207
3353
  init_tasks_crud();
@@ -3287,7 +3433,7 @@ function _resetLastRelaunchCache() {
3287
3433
  }
3288
3434
  async function lastResumeCreatedAtMs(agentId) {
3289
3435
  const client = getClient();
3290
- const cmScope = sessionScopeFilter();
3436
+ const cmScope = sessionScopeFilter(null);
3291
3437
  const result = await client.execute({
3292
3438
  sql: `SELECT MAX(created_at) AS last_created_at
3293
3439
  FROM tasks
@@ -3312,7 +3458,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
3312
3458
  const client = getClient();
3313
3459
  const now = (/* @__PURE__ */ new Date()).toISOString();
3314
3460
  const context = buildResumeContext(agentId, openTasks);
3315
- const rdScope = sessionScopeFilter();
3461
+ const rdScope = sessionScopeFilter(null);
3316
3462
  const existing = await client.execute({
3317
3463
  sql: `SELECT id FROM tasks
3318
3464
  WHERE assigned_to = ?
@@ -3346,7 +3492,7 @@ async function pollCapacityDead() {
3346
3492
  const transport = getTransport();
3347
3493
  const relaunched = [];
3348
3494
  const registered = listSessions().filter(
3349
- (s) => s.agentId !== "exe"
3495
+ (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
3350
3496
  );
3351
3497
  if (registered.length === 0) return [];
3352
3498
  let liveSessions;
@@ -3406,7 +3552,7 @@ async function pollCapacityDead() {
3406
3552
  reason: "capacity"
3407
3553
  });
3408
3554
  const client = getClient();
3409
- const rlScope = sessionScopeFilter();
3555
+ const rlScope = sessionScopeFilter(null);
3410
3556
  const openTasks = await client.execute({
3411
3557
  sql: `SELECT id, title, priority, task_file, status
3412
3558
  FROM tasks
@@ -3460,6 +3606,7 @@ var init_capacity_monitor = __esm({
3460
3606
  init_session_kill_telemetry();
3461
3607
  init_tmux_routing();
3462
3608
  init_task_scope();
3609
+ init_employees();
3463
3610
  CAPACITY_PATTERNS = [
3464
3611
  /conversation is too long/i,
3465
3612
  /maximum context length/i,
@@ -3609,7 +3756,7 @@ function employeeSessionName(employee, exeSession, instance) {
3609
3756
  exeSession = root;
3610
3757
  } else {
3611
3758
  throw new Error(
3612
- `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
3759
+ `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
3613
3760
  );
3614
3761
  }
3615
3762
  }
@@ -3629,8 +3776,10 @@ function parseParentExe(sessionName, agentId) {
3629
3776
  return match?.[1] ?? null;
3630
3777
  }
3631
3778
  function extractRootExe(name) {
3632
- const match = name.match(/(exe\d+)$/);
3633
- return match?.[1] ?? null;
3779
+ if (!name) return null;
3780
+ if (!name.includes("-")) return name;
3781
+ const parts = name.split("-").filter(Boolean);
3782
+ return parts.length > 0 ? parts[parts.length - 1] : null;
3634
3783
  }
3635
3784
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3636
3785
  if (!existsSync10(SESSION_CACHE)) {
@@ -3775,12 +3924,14 @@ function isSessionBusy(sessionName) {
3775
3924
  return state === "thinking" || state === "tool";
3776
3925
  }
3777
3926
  function isExeSession(sessionName) {
3778
- return /^exe\d*$/.test(sessionName);
3927
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
3928
+ const coordinatorName = getCoordinatorName();
3929
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
3779
3930
  }
3780
3931
  function sendIntercom(targetSession) {
3781
3932
  const transport = getTransport();
3782
3933
  if (isExeSession(targetSession)) {
3783
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
3934
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
3784
3935
  return "skipped_exe";
3785
3936
  }
3786
3937
  if (isDebounced(targetSession)) {
@@ -3832,7 +3983,7 @@ function notifyParentExe(sessionKey) {
3832
3983
  if (result === "failed") {
3833
3984
  const rootExe = resolveExeSession();
3834
3985
  if (rootExe && rootExe !== target) {
3835
- process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
3986
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
3836
3987
  `);
3837
3988
  const fallback = sendIntercom(rootExe);
3838
3989
  return fallback !== "failed";
@@ -3842,8 +3993,8 @@ function notifyParentExe(sessionKey) {
3842
3993
  return true;
3843
3994
  }
3844
3995
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3845
- if (employeeName === "exe") {
3846
- return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
3996
+ if (employeeName === "exe" || isCoordinatorName(employeeName)) {
3997
+ return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
3847
3998
  }
3848
3999
  try {
3849
4000
  assertEmployeeLimitSync();
@@ -3852,8 +4003,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3852
4003
  return { status: "failed", sessionName: "", error: err.message };
3853
4004
  }
3854
4005
  }
3855
- if (/-exe\d*$/.test(employeeName)) {
3856
- const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
4006
+ if (employeeName.includes("-")) {
4007
+ const bare = employeeName.split("-")[0].replace(/\d+$/, "");
3857
4008
  return {
3858
4009
  status: "failed",
3859
4010
  sessionName: "",
@@ -3872,7 +4023,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3872
4023
  return {
3873
4024
  status: "failed",
3874
4025
  sessionName: "",
3875
- error: `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
4026
+ error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
3876
4027
  };
3877
4028
  }
3878
4029
  }
@@ -4029,8 +4180,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4029
4180
  const ctxContent = [
4030
4181
  `## Session Context`,
4031
4182
  `You are running in tmux session: ${sessionName}.`,
4032
- `Your parent exe session is ${exeSession}.`,
4033
- `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
4183
+ `Your parent coordinator session is ${exeSession}.`,
4184
+ `Your employees (if any) use the -${exeSession} suffix.`
4034
4185
  ].join("\n");
4035
4186
  writeFileSync6(ctxFile, ctxContent);
4036
4187
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
@@ -4134,6 +4285,7 @@ var init_tmux_routing = __esm({
4134
4285
  init_provider_table();
4135
4286
  init_intercom_queue();
4136
4287
  init_plan_limits();
4288
+ init_employees();
4137
4289
  SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4138
4290
  SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4139
4291
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
@@ -4327,7 +4479,11 @@ async function ensureShardSchema(client) {
4327
4479
  "ALTER TABLE memories ADD COLUMN source_path TEXT",
4328
4480
  "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
4329
4481
  "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
4330
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
4482
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
4483
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
4484
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
4485
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
4486
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT"
4331
4487
  ]) {
4332
4488
  try {
4333
4489
  await client.execute(col);
@@ -4457,26 +4613,26 @@ var init_platform_procedures = __esm({
4457
4613
  title: "What is exe-os \u2014 the operating model every agent must understand",
4458
4614
  domain: "architecture",
4459
4615
  priority: "p0",
4460
- content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO (exe), CTO (yoshi), CMO (mari), engineers (tom), content (sasha). Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
4616
+ content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
4461
4617
  },
4462
4618
  {
4463
4619
  title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
4464
4620
  domain: "architecture",
4465
4621
  priority: "p0",
4466
- content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC, runs /exe to boot the COO. exe manages employees in tmux sessions. Each exeN is a separate CC window/project. Employees (yoshi, tom, mari) run in their own tmux panes via create_task auto-spawn. The founder talks to exe; exe orchestrates the team. CC is the shell, exe-os is the brain."
4622
+ content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
4467
4623
  },
4468
4624
  {
4469
- title: "Sessions explained \u2014 what exeN means and how projects work",
4625
+ title: "Sessions explained \u2014 coordinator session names and projects",
4470
4626
  domain: "architecture",
4471
4627
  priority: "p0",
4472
- content: "Each exeN (exe1, exe2, exe3) is an isolated project session. exe1 might be exe-os development, exe2 might be exe-wiki. Each session spawns its own employees: exe1\u2192yoshi-exe1\u2192tom-exe1. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
4628
+ content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
4473
4629
  },
4474
4630
  // --- Hierarchy and dispatch ---
4475
4631
  {
4476
4632
  title: "Chain of command \u2014 who talks to whom",
4477
4633
  domain: "workflow",
4478
4634
  priority: "p0",
4479
- content: "Founder \u2192 exe (COO) \u2192 yoshi (CTO) / mari (CMO). Yoshi \u2192 tom (engineer). Mari \u2192 sasha (content). Never skip levels: exe never assigns directly to tom. Tom never reports directly to exe. 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."
4635
+ 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."
4480
4636
  },
4481
4637
  {
4482
4638
  title: "Single dispatch path \u2014 create_task only",
@@ -4486,30 +4642,30 @@ var init_platform_procedures = __esm({
4486
4642
  },
4487
4643
  // --- Session isolation ---
4488
4644
  {
4489
- title: "Session scoping \u2014 stay in your exe boundary",
4645
+ title: "Session scoping \u2014 stay in your coordinator boundary",
4490
4646
  domain: "security",
4491
4647
  priority: "p0",
4492
- content: "Session scoping is mandatory. Managers dispatch to workers within their own exe session ONLY. exe1\u2192yoshi-exe1\u2192tom-exe1. exe2\u2192yoshi-exe2\u2192tom2-exe2. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating exe session."
4648
+ content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
4493
4649
  },
4494
4650
  {
4495
4651
  title: "Session isolation \u2014 never touch another session's work",
4496
4652
  domain: "workflow",
4497
4653
  priority: "p0",
4498
- content: `Sessions are isolated. exeN owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another exe session. (2) Never review work from a different session \u2014 report "belongs to exeN" and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: yoshi-exe1 works ONLY on exe1 tasks. Cross-session work is a system violation.`
4654
+ content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
4499
4655
  },
4500
4656
  // --- Engineering: session scoping in code ---
4501
4657
  {
4502
4658
  title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
4503
4659
  domain: "architecture",
4504
4660
  priority: "p0",
4505
- content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current exeN. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ exe sessions simultaneously."
4661
+ content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
4506
4662
  },
4507
4663
  // --- Hard constraints ---
4508
4664
  {
4509
4665
  title: "What you CANNOT do in exe-os \u2014 hard constraints",
4510
4666
  domain: "security",
4511
4667
  priority: "p0",
4512
- content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 exe reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
4668
+ content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
4513
4669
  },
4514
4670
  // --- Operations ---
4515
4671
  {
@@ -4749,7 +4905,10 @@ async function writeMemory(record) {
4749
4905
  source_path: record.source_path ?? null,
4750
4906
  source_type: record.source_type ?? null,
4751
4907
  tier: record.tier ?? classifyTier(record),
4752
- supersedes_id: record.supersedes_id ?? null
4908
+ supersedes_id: record.supersedes_id ?? null,
4909
+ draft: record.draft ? 1 : 0,
4910
+ memory_type: record.memory_type ?? "raw",
4911
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
4753
4912
  };
4754
4913
  _pendingRecords.push(dbRow);
4755
4914
  orgBus.emit({
@@ -4804,6 +4963,9 @@ async function flushBatch() {
4804
4963
  const sourceType = row.source_type ?? null;
4805
4964
  const tier = row.tier ?? 3;
4806
4965
  const supersedesId = row.supersedes_id ?? null;
4966
+ const draft = row.draft ? 1 : 0;
4967
+ const memoryType = row.memory_type ?? "raw";
4968
+ const trajectory = row.trajectory ?? null;
4807
4969
  return {
4808
4970
  sql: hasVector ? `INSERT OR IGNORE INTO memories
4809
4971
  (id, agent_id, agent_role, session_id, timestamp,
@@ -4811,15 +4973,15 @@ async function flushBatch() {
4811
4973
  has_error, raw_text, vector, version, task_id, importance, status,
4812
4974
  confidence, last_accessed,
4813
4975
  workspace_id, document_id, user_id, char_offset, page_number,
4814
- source_path, source_type, tier, supersedes_id)
4815
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4976
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4977
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4816
4978
  (id, agent_id, agent_role, session_id, timestamp,
4817
4979
  tool_name, project_name,
4818
4980
  has_error, raw_text, vector, version, task_id, importance, status,
4819
4981
  confidence, last_accessed,
4820
4982
  workspace_id, document_id, user_id, char_offset, page_number,
4821
- source_path, source_type, tier, supersedes_id)
4822
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4983
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4984
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4823
4985
  args: hasVector ? [
4824
4986
  row.id,
4825
4987
  row.agent_id,
@@ -4845,7 +5007,10 @@ async function flushBatch() {
4845
5007
  sourcePath,
4846
5008
  sourceType,
4847
5009
  tier,
4848
- supersedesId
5010
+ supersedesId,
5011
+ draft,
5012
+ memoryType,
5013
+ trajectory
4849
5014
  ] : [
4850
5015
  row.id,
4851
5016
  row.agent_id,
@@ -4870,7 +5035,10 @@ async function flushBatch() {
4870
5035
  sourcePath,
4871
5036
  sourceType,
4872
5037
  tier,
4873
- supersedesId
5038
+ supersedesId,
5039
+ draft,
5040
+ memoryType,
5041
+ trajectory
4874
5042
  ]
4875
5043
  };
4876
5044
  };
@@ -4939,6 +5107,8 @@ async function searchMemories(queryVector, agentId, options) {
4939
5107
  const limit = options?.limit ?? 10;
4940
5108
  const statusFilter = options?.includeArchived ? "" : `
4941
5109
  AND COALESCE(status, 'active') = 'active'`;
5110
+ const draftFilter = options?.includeDrafts ? "" : `
5111
+ AND (draft = 0 OR draft IS NULL)`;
4942
5112
  let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4943
5113
  tool_name, project_name,
4944
5114
  has_error, raw_text, vector, importance, status,
@@ -4948,7 +5118,7 @@ async function searchMemories(queryVector, agentId, options) {
4948
5118
  source_path, source_type
4949
5119
  FROM memories
4950
5120
  WHERE agent_id = ?
4951
- AND vector IS NOT NULL${statusFilter}
5121
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
4952
5122
  AND COALESCE(confidence, 0.7) >= 0.3`;
4953
5123
  const args = [agentId];
4954
5124
  const scope = buildWikiScopeFilter(options, "");
@@ -4970,6 +5140,10 @@ async function searchMemories(queryVector, agentId, options) {
4970
5140
  sql += ` AND timestamp >= ?`;
4971
5141
  args.push(options.since);
4972
5142
  }
5143
+ if (options?.memoryType) {
5144
+ sql += ` AND memory_type = ?`;
5145
+ args.push(options.memoryType);
5146
+ }
4973
5147
  sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
4974
5148
  args.push(vectorToBlob(queryVector));
4975
5149
  sql += ` LIMIT ?`;
@@ -6643,7 +6817,11 @@ function composeHooks(...pipelines) {
6643
6817
  };
6644
6818
  }
6645
6819
 
6820
+ // src/runtime/orchestrator.ts
6821
+ init_employees();
6822
+
6646
6823
  // src/lib/task-router.ts
6824
+ init_employees();
6647
6825
  import { randomUUID } from "crypto";
6648
6826
  var DEFAULT_BLOOM_CONFIG = {
6649
6827
  complexityToTier: {
@@ -6701,7 +6879,7 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
6701
6879
  return { agentId, score: results.length / 5 };
6702
6880
  }
6703
6881
  async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
6704
- let specialists = employees.filter((e) => e.name !== "exe");
6882
+ let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
6705
6883
  if (specialists.length === 0) {
6706
6884
  throw new Error(
6707
6885
  "No specialist employees available. Create one with /exe-new-employee."
@@ -6778,7 +6956,7 @@ ${task.context}`,
6778
6956
  await createTaskCore({
6779
6957
  title: task.title,
6780
6958
  assignedTo: targetEmployee.name,
6781
- assignedBy: "exe",
6959
+ assignedBy: getCoordinatorName(),
6782
6960
  projectName: task.projectName,
6783
6961
  priority: task.priority,
6784
6962
  context: task.context,
@@ -6883,15 +7061,16 @@ ${task.context}`,
6883
7061
  const client = getClient2();
6884
7062
  const autoApproved = [];
6885
7063
  const needsReview = [];
7064
+ const coordinatorName = getCoordinatorName();
6886
7065
  const rScope = sessionScopeFilter();
6887
7066
  const reviews = await client.execute({
6888
7067
  sql: `SELECT id, title, assigned_to, priority, result, task_file FROM tasks
6889
- WHERE assigned_to = 'exe'
7068
+ WHERE (assigned_to = ? OR assigned_to = 'exe')
6890
7069
  AND status IN ('open', 'in_progress')
6891
7070
  AND task_file LIKE '%review-%'${rScope.sql}
6892
7071
  ORDER BY priority ASC
6893
7072
  LIMIT 20`,
6894
- args: [...rScope.args]
7073
+ args: [coordinatorName, ...rScope.args]
6895
7074
  });
6896
7075
  for (const row of reviews.rows) {
6897
7076
  const item = {