@askexenow/exe-os 0.9.113 → 0.9.115

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 (86) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +36 -12
  2. package/dist/bin/agentic-reflection-backfill.js +36 -12
  3. package/dist/bin/agentic-semantic-label.js +36 -12
  4. package/dist/bin/backfill-conversations.js +36 -12
  5. package/dist/bin/backfill-responses.js +36 -12
  6. package/dist/bin/backfill-vectors.js +36 -12
  7. package/dist/bin/bulk-sync-postgres.js +36 -12
  8. package/dist/bin/cleanup-stale-review-tasks.js +470 -113
  9. package/dist/bin/cli.js +413 -62
  10. package/dist/bin/exe-agent.js +27 -0
  11. package/dist/bin/exe-assign.js +36 -12
  12. package/dist/bin/exe-boot.js +246 -54
  13. package/dist/bin/exe-call.js +8 -0
  14. package/dist/bin/exe-cloud.js +47 -12
  15. package/dist/bin/exe-dispatch.js +348 -53
  16. package/dist/bin/exe-doctor.js +51 -13
  17. package/dist/bin/exe-export-behaviors.js +37 -12
  18. package/dist/bin/exe-forget.js +36 -12
  19. package/dist/bin/exe-gateway.js +348 -53
  20. package/dist/bin/exe-heartbeat.js +471 -113
  21. package/dist/bin/exe-kill.js +36 -12
  22. package/dist/bin/exe-launch-agent.js +117 -18
  23. package/dist/bin/exe-new-employee.js +9 -1
  24. package/dist/bin/exe-pending-messages.js +452 -95
  25. package/dist/bin/exe-pending-notifications.js +452 -95
  26. package/dist/bin/exe-pending-reviews.js +452 -95
  27. package/dist/bin/exe-rename.js +36 -12
  28. package/dist/bin/exe-review.js +36 -12
  29. package/dist/bin/exe-search.js +37 -12
  30. package/dist/bin/exe-session-cleanup.js +348 -53
  31. package/dist/bin/exe-settings.js +12 -0
  32. package/dist/bin/exe-start-codex.js +46 -13
  33. package/dist/bin/exe-start-opencode.js +46 -13
  34. package/dist/bin/exe-status.js +460 -114
  35. package/dist/bin/exe-support.js +12 -0
  36. package/dist/bin/exe-team.js +36 -12
  37. package/dist/bin/git-sweep.js +348 -53
  38. package/dist/bin/graph-backfill.js +36 -12
  39. package/dist/bin/graph-export.js +36 -12
  40. package/dist/bin/install.js +9 -1
  41. package/dist/bin/intercom-check.js +255 -53
  42. package/dist/bin/scan-tasks.js +348 -53
  43. package/dist/bin/setup.js +74 -12
  44. package/dist/bin/shard-migrate.js +36 -12
  45. package/dist/gateway/index.js +348 -53
  46. package/dist/hooks/bug-report-worker.js +348 -53
  47. package/dist/hooks/codex-stop-task-finalizer.js +308 -37
  48. package/dist/hooks/commit-complete.js +348 -53
  49. package/dist/hooks/error-recall.js +37 -12
  50. package/dist/hooks/ingest.js +363 -54
  51. package/dist/hooks/instructions-loaded.js +36 -12
  52. package/dist/hooks/notification.js +36 -12
  53. package/dist/hooks/post-compact.js +426 -72
  54. package/dist/hooks/post-tool-combined.js +501 -146
  55. package/dist/hooks/pre-compact.js +348 -53
  56. package/dist/hooks/pre-tool-use.js +92 -13
  57. package/dist/hooks/prompt-submit.js +348 -53
  58. package/dist/hooks/session-end.js +158 -53
  59. package/dist/hooks/session-start.js +66 -13
  60. package/dist/hooks/stop.js +420 -72
  61. package/dist/hooks/subagent-stop.js +419 -72
  62. package/dist/hooks/summary-worker.js +442 -121
  63. package/dist/index.js +375 -53
  64. package/dist/lib/agent-config.js +8 -0
  65. package/dist/lib/cloud-sync.js +35 -12
  66. package/dist/lib/config.js +13 -0
  67. package/dist/lib/consolidation.js +9 -1
  68. package/dist/lib/embedder.js +13 -0
  69. package/dist/lib/employees.js +8 -0
  70. package/dist/lib/exe-daemon.js +524 -60
  71. package/dist/lib/hybrid-search.js +37 -12
  72. package/dist/lib/keychain.js +25 -13
  73. package/dist/lib/messaging.js +395 -74
  74. package/dist/lib/schedules.js +36 -12
  75. package/dist/lib/skill-learning.js +21 -0
  76. package/dist/lib/store.js +36 -12
  77. package/dist/lib/tasks.js +324 -41
  78. package/dist/lib/tmux-routing.js +324 -41
  79. package/dist/mcp/server.js +374 -54
  80. package/dist/mcp/tools/create-task.js +324 -41
  81. package/dist/mcp/tools/list-tasks.js +406 -57
  82. package/dist/mcp/tools/send-message.js +395 -74
  83. package/dist/mcp/tools/update-task.js +324 -41
  84. package/dist/runtime/index.js +375 -53
  85. package/dist/tui/App.js +377 -55
  86. package/package.json +1 -1
@@ -29,9 +29,25 @@ var init_db_retry = __esm({
29
29
  // src/lib/secure-files.ts
30
30
  import { chmodSync, existsSync, mkdirSync } from "fs";
31
31
  import { chmod, mkdir } from "fs/promises";
32
+ function ensurePrivateDirSync(dirPath) {
33
+ mkdirSync(dirPath, { recursive: true, mode: PRIVATE_DIR_MODE });
34
+ try {
35
+ chmodSync(dirPath, PRIVATE_DIR_MODE);
36
+ } catch {
37
+ }
38
+ }
39
+ function enforcePrivateFileSync(filePath) {
40
+ try {
41
+ if (existsSync(filePath)) chmodSync(filePath, PRIVATE_FILE_MODE);
42
+ } catch {
43
+ }
44
+ }
45
+ var PRIVATE_DIR_MODE, PRIVATE_FILE_MODE;
32
46
  var init_secure_files = __esm({
33
47
  "src/lib/secure-files.ts"() {
34
48
  "use strict";
49
+ PRIVATE_DIR_MODE = 448;
50
+ PRIVATE_FILE_MODE = 384;
35
51
  }
36
52
  });
37
53
 
@@ -134,11 +150,176 @@ var init_config = __esm({
134
150
  }
135
151
  });
136
152
 
153
+ // src/lib/runtime-table.ts
154
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
155
+ var init_runtime_table = __esm({
156
+ "src/lib/runtime-table.ts"() {
157
+ "use strict";
158
+ RUNTIME_TABLE = {
159
+ codex: {
160
+ binary: "codex",
161
+ launchMode: "interactive",
162
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
163
+ inlineFlag: "--no-alt-screen",
164
+ apiKeyEnv: "OPENAI_API_KEY",
165
+ defaultModel: "gpt-5.5"
166
+ },
167
+ opencode: {
168
+ binary: "opencode",
169
+ launchMode: "exec",
170
+ autoApproveFlag: "--dangerously-skip-permissions",
171
+ inlineFlag: "",
172
+ apiKeyEnv: "ANTHROPIC_API_KEY",
173
+ defaultModel: "anthropic/claude-sonnet-4-6"
174
+ }
175
+ };
176
+ DEFAULT_RUNTIME = "claude";
177
+ }
178
+ });
179
+
180
+ // src/lib/agent-config.ts
181
+ var agent_config_exports = {};
182
+ __export(agent_config_exports, {
183
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
184
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
185
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
186
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
187
+ clearAgentRuntime: () => clearAgentRuntime,
188
+ getAgentRuntime: () => getAgentRuntime,
189
+ loadAgentConfig: () => loadAgentConfig,
190
+ normalizeCcModelName: () => normalizeCcModelName,
191
+ saveAgentConfig: () => saveAgentConfig,
192
+ setAgentMcps: () => setAgentMcps,
193
+ setAgentRuntime: () => setAgentRuntime
194
+ });
195
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
196
+ import path2 from "path";
197
+ function loadAgentConfig() {
198
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
199
+ try {
200
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
201
+ } catch {
202
+ return {};
203
+ }
204
+ }
205
+ function saveAgentConfig(config) {
206
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
207
+ ensurePrivateDirSync(dir);
208
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
209
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
210
+ }
211
+ function getAgentRuntime(agentId) {
212
+ const config = loadAgentConfig();
213
+ const entry = config[agentId];
214
+ if (entry) return entry;
215
+ const orgDefault = config["default"];
216
+ if (orgDefault) return orgDefault;
217
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
218
+ }
219
+ function normalizeCcModelName(model) {
220
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
221
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
222
+ ccModel += "[1m]";
223
+ }
224
+ return ccModel;
225
+ }
226
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
227
+ const knownModels = KNOWN_RUNTIMES[runtime];
228
+ if (!knownModels) {
229
+ return {
230
+ ok: false,
231
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
232
+ };
233
+ }
234
+ if (!knownModels.includes(model)) {
235
+ return {
236
+ ok: false,
237
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
238
+ };
239
+ }
240
+ const config = loadAgentConfig();
241
+ const existing = config[agentId];
242
+ const entry = { runtime, model };
243
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
244
+ if (mcps !== void 0) {
245
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
246
+ } else if (existing?.mcps) {
247
+ entry.mcps = existing.mcps;
248
+ }
249
+ config[agentId] = entry;
250
+ saveAgentConfig(config);
251
+ return { ok: true };
252
+ }
253
+ function setAgentMcps(agentId, mcps) {
254
+ const config = loadAgentConfig();
255
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
256
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
257
+ config[agentId] = existing;
258
+ saveAgentConfig(config);
259
+ return { ok: true };
260
+ }
261
+ function clearAgentRuntime(agentId) {
262
+ const config = loadAgentConfig();
263
+ delete config[agentId];
264
+ saveAgentConfig(config);
265
+ }
266
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
267
+ var init_agent_config = __esm({
268
+ "src/lib/agent-config.ts"() {
269
+ "use strict";
270
+ init_config();
271
+ init_runtime_table();
272
+ init_secure_files();
273
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
274
+ KNOWN_RUNTIMES = {
275
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
276
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
277
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
278
+ };
279
+ RUNTIME_LABELS = {
280
+ claude: "Claude Code (Anthropic)",
281
+ codex: "Codex (OpenAI)",
282
+ opencode: "OpenCode (open source)"
283
+ };
284
+ DEFAULT_MODELS = {
285
+ claude: "claude-opus-4.6",
286
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
287
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
288
+ };
289
+ }
290
+ });
291
+
137
292
  // src/lib/employees.ts
293
+ var employees_exports = {};
294
+ __export(employees_exports, {
295
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
296
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
297
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
298
+ addEmployee: () => addEmployee,
299
+ baseAgentName: () => baseAgentName,
300
+ canCoordinate: () => canCoordinate,
301
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
302
+ getCoordinatorName: () => getCoordinatorName,
303
+ getEmployee: () => getEmployee,
304
+ getEmployeeByRole: () => getEmployeeByRole,
305
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
306
+ hasRole: () => hasRole,
307
+ hireEmployee: () => hireEmployee,
308
+ isCoordinatorName: () => isCoordinatorName,
309
+ isCoordinatorRole: () => isCoordinatorRole,
310
+ isMultiInstance: () => isMultiInstance,
311
+ loadEmployees: () => loadEmployees,
312
+ loadEmployeesSync: () => loadEmployeesSync,
313
+ normalizeRole: () => normalizeRole,
314
+ normalizeRosterCase: () => normalizeRosterCase,
315
+ registerBinSymlinks: () => registerBinSymlinks,
316
+ saveEmployees: () => saveEmployees,
317
+ validateEmployeeName: () => validateEmployeeName
318
+ });
138
319
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
139
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
320
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
140
321
  import { execSync } from "child_process";
141
- import path2 from "path";
322
+ import path3 from "path";
142
323
  import os2 from "os";
143
324
  function normalizeRole(role) {
144
325
  return (role ?? "").trim().toLowerCase();
@@ -152,10 +333,47 @@ function getCoordinatorEmployee(employees) {
152
333
  function getCoordinatorName(employees = loadEmployeesSync()) {
153
334
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
154
335
  }
336
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
337
+ if (!agentName) return false;
338
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
339
+ }
340
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
341
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
342
+ }
343
+ function validateEmployeeName(name) {
344
+ if (!name) {
345
+ return { valid: false, error: "Name is required" };
346
+ }
347
+ if (name.length > 32) {
348
+ return { valid: false, error: "Name must be 32 characters or fewer" };
349
+ }
350
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
351
+ return {
352
+ valid: false,
353
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
354
+ };
355
+ }
356
+ return { valid: true };
357
+ }
358
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
359
+ if (!existsSync4(employeesPath)) {
360
+ return [];
361
+ }
362
+ const raw = await readFile2(employeesPath, "utf-8");
363
+ try {
364
+ return JSON.parse(raw);
365
+ } catch {
366
+ return [];
367
+ }
368
+ }
369
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
370
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
371
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
372
+ }
155
373
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
156
- if (!existsSync3(employeesPath)) return [];
374
+ if (!existsSync4(employeesPath)) return [];
157
375
  try {
158
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
376
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
159
377
  } catch {
160
378
  return [];
161
379
  }
@@ -163,6 +381,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
163
381
  function getEmployee(employees, name) {
164
382
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
165
383
  }
384
+ function getEmployeeByRole(employees, role) {
385
+ const lower = role.toLowerCase();
386
+ return employees.find((e) => e.role.toLowerCase() === lower);
387
+ }
388
+ function getEmployeeNamesByRole(employees, role) {
389
+ const lower = role.toLowerCase();
390
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
391
+ }
392
+ function hasRole(agentName, role) {
393
+ const employees = loadEmployeesSync();
394
+ const emp = getEmployee(employees, agentName);
395
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
396
+ }
166
397
  function baseAgentName(name, employees) {
167
398
  const match = name.match(/^([a-zA-Z]+)\d+$/);
168
399
  if (!match) return name;
@@ -171,21 +402,153 @@ function baseAgentName(name, employees) {
171
402
  if (getEmployee(roster, base)) return base;
172
403
  return name;
173
404
  }
174
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
405
+ function isMultiInstance(agentName, employees) {
406
+ const roster = employees ?? loadEmployeesSync();
407
+ const emp = getEmployee(roster, agentName);
408
+ if (!emp) return false;
409
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
410
+ }
411
+ function addEmployee(employees, employee) {
412
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
413
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
414
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
415
+ throw new Error(`Employee '${normalized.name}' already exists`);
416
+ }
417
+ return [...employees, normalized];
418
+ }
419
+ function appendToCoordinatorTeam(employee) {
420
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
421
+ if (!coordinator) return;
422
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
423
+ if (!existsSync4(idPath)) return;
424
+ const content = readFileSync3(idPath, "utf-8");
425
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
426
+ const teamMatch = content.match(TEAM_SECTION_RE);
427
+ if (!teamMatch || teamMatch.index === void 0) return;
428
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
429
+ const nextHeading = afterTeam.match(/\n## /);
430
+ const entry = `
431
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
432
+ `;
433
+ let updated;
434
+ if (nextHeading && nextHeading.index !== void 0) {
435
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
436
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
437
+ } else {
438
+ updated = content.trimEnd() + "\n" + entry;
439
+ }
440
+ writeFileSync2(idPath, updated, "utf-8");
441
+ }
442
+ function capitalize(s) {
443
+ return s.charAt(0).toUpperCase() + s.slice(1);
444
+ }
445
+ async function hireEmployee(employee) {
446
+ const employees = await loadEmployees();
447
+ const updated = addEmployee(employees, employee);
448
+ await saveEmployees(updated);
449
+ try {
450
+ appendToCoordinatorTeam(employee);
451
+ } catch {
452
+ }
453
+ try {
454
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
455
+ const config = loadAgentConfig2();
456
+ const name = employee.name.toLowerCase();
457
+ if (!config[name] && config["default"]) {
458
+ config[name] = { ...config["default"] };
459
+ saveAgentConfig2(config);
460
+ }
461
+ } catch {
462
+ }
463
+ return updated;
464
+ }
465
+ async function normalizeRosterCase(rosterPath) {
466
+ const employees = await loadEmployees(rosterPath);
467
+ let changed = false;
468
+ for (const emp of employees) {
469
+ if (emp.name !== emp.name.toLowerCase()) {
470
+ const oldName = emp.name;
471
+ emp.name = emp.name.toLowerCase();
472
+ changed = true;
473
+ try {
474
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
475
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
476
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
477
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
478
+ renameSync2(oldPath, newPath);
479
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
480
+ const content = readFileSync3(oldPath, "utf-8");
481
+ writeFileSync2(newPath, content, "utf-8");
482
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
483
+ unlinkSync(oldPath);
484
+ }
485
+ }
486
+ } catch {
487
+ }
488
+ }
489
+ }
490
+ if (changed) {
491
+ await saveEmployees(employees, rosterPath);
492
+ }
493
+ return changed;
494
+ }
495
+ function findExeBin() {
496
+ try {
497
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
498
+ } catch {
499
+ return null;
500
+ }
501
+ }
502
+ function registerBinSymlinks(name) {
503
+ const created = [];
504
+ const skipped = [];
505
+ const errors = [];
506
+ const exeBinPath = findExeBin();
507
+ if (!exeBinPath) {
508
+ errors.push("Could not find 'exe-os' in PATH");
509
+ return { created, skipped, errors };
510
+ }
511
+ const binDir = path3.dirname(exeBinPath);
512
+ let target;
513
+ try {
514
+ target = readlinkSync(exeBinPath);
515
+ } catch {
516
+ errors.push("Could not read 'exe' symlink");
517
+ return { created, skipped, errors };
518
+ }
519
+ for (const suffix of ["", "-opencode"]) {
520
+ const linkName = `${name}${suffix}`;
521
+ const linkPath = path3.join(binDir, linkName);
522
+ if (existsSync4(linkPath)) {
523
+ skipped.push(linkName);
524
+ continue;
525
+ }
526
+ try {
527
+ symlinkSync(target, linkPath);
528
+ created.push(linkName);
529
+ } catch (err) {
530
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
531
+ }
532
+ }
533
+ return { created, skipped, errors };
534
+ }
535
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
175
536
  var init_employees = __esm({
176
537
  "src/lib/employees.ts"() {
177
538
  "use strict";
178
539
  init_config();
179
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
540
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
180
541
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
181
542
  COORDINATOR_ROLE = "COO";
182
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
543
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
544
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
545
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
183
546
  }
184
547
  });
185
548
 
186
549
  // src/lib/database-adapter.ts
187
550
  import os3 from "os";
188
- import path3 from "path";
551
+ import path4 from "path";
189
552
  import { createRequire } from "module";
190
553
  import { pathToFileURL } from "url";
191
554
  var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
@@ -212,7 +575,7 @@ var init_memory = __esm({
212
575
  });
213
576
 
214
577
  // src/lib/database.ts
215
- import { chmodSync as chmodSync2, existsSync as existsSync4, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
578
+ import { chmodSync as chmodSync2, existsSync as existsSync5, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
216
579
  import { createClient } from "@libsql/client";
217
580
  import { homedir } from "os";
218
581
  import { join } from "path";
@@ -265,13 +628,13 @@ var init_database = __esm({
265
628
  });
266
629
 
267
630
  // src/lib/session-registry.ts
268
- import path4 from "path";
631
+ import path5 from "path";
269
632
  import os4 from "os";
270
633
  var REGISTRY_PATH;
271
634
  var init_session_registry = __esm({
272
635
  "src/lib/session-registry.ts"() {
273
636
  "use strict";
274
- REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
637
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
275
638
  }
276
639
  });
277
640
 
@@ -489,68 +852,6 @@ var init_provider_table = __esm({
489
852
  }
490
853
  });
491
854
 
492
- // src/lib/runtime-table.ts
493
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
494
- var init_runtime_table = __esm({
495
- "src/lib/runtime-table.ts"() {
496
- "use strict";
497
- RUNTIME_TABLE = {
498
- codex: {
499
- binary: "codex",
500
- launchMode: "interactive",
501
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
502
- inlineFlag: "--no-alt-screen",
503
- apiKeyEnv: "OPENAI_API_KEY",
504
- defaultModel: "gpt-5.5"
505
- },
506
- opencode: {
507
- binary: "opencode",
508
- launchMode: "exec",
509
- autoApproveFlag: "--dangerously-skip-permissions",
510
- inlineFlag: "",
511
- apiKeyEnv: "ANTHROPIC_API_KEY",
512
- defaultModel: "anthropic/claude-sonnet-4-6"
513
- }
514
- };
515
- DEFAULT_RUNTIME = "claude";
516
- }
517
- });
518
-
519
- // src/lib/agent-config.ts
520
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
521
- import path5 from "path";
522
- function loadAgentConfig() {
523
- if (!existsSync5(AGENT_CONFIG_PATH)) return {};
524
- try {
525
- return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
526
- } catch {
527
- return {};
528
- }
529
- }
530
- function getAgentRuntime(agentId) {
531
- const config = loadAgentConfig();
532
- const entry = config[agentId];
533
- if (entry) return entry;
534
- const orgDefault = config["default"];
535
- if (orgDefault) return orgDefault;
536
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
537
- }
538
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
539
- var init_agent_config = __esm({
540
- "src/lib/agent-config.ts"() {
541
- "use strict";
542
- init_config();
543
- init_runtime_table();
544
- init_secure_files();
545
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
546
- DEFAULT_MODELS = {
547
- claude: "claude-opus-4.6",
548
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
549
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
550
- };
551
- }
552
- });
553
-
554
855
  // src/lib/intercom-queue.ts
555
856
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
556
857
  import path6 from "path";
@@ -690,6 +991,21 @@ function employeeSessionName(employee, exeSession, instance) {
690
991
  function extractRootExe(name) {
691
992
  if (!name) return null;
692
993
  if (!name.includes("-")) return name;
994
+ try {
995
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
996
+ if (roster.length > 0) {
997
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
998
+ for (const agentName of sortedNames) {
999
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1000
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
1001
+ const match = name.match(regex);
1002
+ if (match) {
1003
+ return extractRootExe(match[1]);
1004
+ }
1005
+ }
1006
+ }
1007
+ } catch {
1008
+ }
693
1009
  const parts = name.split("-").filter(Boolean);
694
1010
  return parts.length > 0 ? parts[parts.length - 1] : null;
695
1011
  }
@@ -708,6 +1024,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
708
1024
  function getParentExe(sessionKey) {
709
1025
  try {
710
1026
  const data = JSON.parse(readFileSync7(path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
1027
+ if (data.registeredAt) {
1028
+ const age = Date.now() - new Date(data.registeredAt).getTime();
1029
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
1030
+ }
711
1031
  return data.parentExe || null;
712
1032
  } catch {
713
1033
  return null;
@@ -949,7 +1269,7 @@ function sendIntercom(targetSession) {
949
1269
  return "failed";
950
1270
  }
951
1271
  }
952
- var SPAWN_LOCK_DIR, SESSION_CACHE, VALID_SESSION_NAME, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
1272
+ var SPAWN_LOCK_DIR, SESSION_CACHE, VALID_SESSION_NAME, PARENT_EXE_CACHE_TTL_MS, INTERCOM_DEBOUNCE_MS, CODEX_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
953
1273
  var init_tmux_routing = __esm({
954
1274
  "src/lib/tmux-routing.ts"() {
955
1275
  "use strict";
@@ -968,6 +1288,7 @@ var init_tmux_routing = __esm({
968
1288
  SPAWN_LOCK_DIR = path10.join(os8.homedir(), ".exe-os", "spawn-locks");
969
1289
  SESSION_CACHE = path10.join(os8.homedir(), ".exe-os", "session-cache");
970
1290
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
1291
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
971
1292
  INTERCOM_DEBOUNCE_MS = 3e4;
972
1293
  CODEX_DEBOUNCE_MS = 12e4;
973
1294
  INTERCOM_LOG2 = path10.join(os8.homedir(), ".exe-os", "intercom.log");
@@ -183,6 +183,17 @@ function normalizeOrchestration(raw) {
183
183
  const userOrg = raw.orchestration ?? {};
184
184
  raw.orchestration = { ...defaultOrg, ...userOrg };
185
185
  }
186
+ function normalizeCloudEndpoint(raw) {
187
+ const cloud = raw.cloud;
188
+ if (!cloud?.endpoint) return;
189
+ const ep = String(cloud.endpoint);
190
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
191
+ cloud.endpoint = "https://cloud.askexe.com";
192
+ process.stderr.write(
193
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
194
+ );
195
+ }
196
+ }
186
197
  async function loadConfig() {
187
198
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
188
199
  await ensurePrivateDir(dir);
@@ -208,6 +219,7 @@ async function loadConfig() {
208
219
  normalizeSessionLifecycle(migratedCfg);
209
220
  normalizeAutoUpdate(migratedCfg);
210
221
  normalizeOrchestration(migratedCfg);
222
+ normalizeCloudEndpoint(migratedCfg);
211
223
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
212
224
  if (config.dbPath.startsWith("~")) {
213
225
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -3948,7 +3960,7 @@ init_memory();
3948
3960
  init_database();
3949
3961
 
3950
3962
  // src/lib/keychain.ts
3951
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3963
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3952
3964
  import { existsSync as existsSync7, statSync as statSync3 } from "fs";
3953
3965
  import { execSync as execSync3 } from "child_process";
3954
3966
  import path6 from "path";
@@ -3987,12 +3999,14 @@ function linuxSecretAvailable() {
3987
3999
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3988
4000
  if (process.platform !== "linux") return false;
3989
4001
  try {
3990
- const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3991
4002
  const st = statSync3(keyPath);
3992
4003
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4004
+ const uid = typeof os5.userInfo().uid === "number" ? os5.userInfo().uid : -1;
3993
4005
  if (uid === 0) return true;
3994
4006
  const exeOsDir = process.env.EXE_OS_DIR;
3995
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
4007
+ if (exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep)) return true;
4008
+ if (!linuxSecretAvailable()) return true;
4009
+ return false;
3996
4010
  } catch {
3997
4011
  return false;
3998
4012
  }
@@ -4143,15 +4157,25 @@ async function writeMachineBoundFileFallback(b64) {
4143
4157
  await mkdir3(dir, { recursive: true });
4144
4158
  const keyPath = getKeyPath();
4145
4159
  const machineKey = deriveMachineKey();
4146
- if (machineKey) {
4147
- const encrypted = encryptWithMachineKey(b64, machineKey);
4148
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4149
- await chmod2(keyPath, 384);
4150
- return "encrypted";
4160
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4161
+ const result = machineKey ? "encrypted" : "plaintext";
4162
+ const tmpPath = keyPath + ".tmp";
4163
+ try {
4164
+ if (existsSync7(keyPath)) {
4165
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4166
+ });
4167
+ }
4168
+ await writeFile3(tmpPath, content, "utf-8");
4169
+ await chmod2(tmpPath, 384);
4170
+ await rename(tmpPath, keyPath);
4171
+ } catch (err) {
4172
+ try {
4173
+ await unlink(tmpPath);
4174
+ } catch {
4175
+ }
4176
+ throw err;
4151
4177
  }
4152
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4153
- await chmod2(keyPath, 384);
4154
- return "plaintext";
4178
+ return result;
4155
4179
  }
4156
4180
  async function getMasterKey() {
4157
4181
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4218,7 +4242,7 @@ async function getMasterKey() {
4218
4242
  b64Value = content;
4219
4243
  }
4220
4244
  const key = Buffer.from(b64Value, "base64");
4221
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4245
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4222
4246
  return key;
4223
4247
  }
4224
4248
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);