@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
@@ -194,6 +194,17 @@ function normalizeOrchestration(raw) {
194
194
  const userOrg = raw.orchestration ?? {};
195
195
  raw.orchestration = { ...defaultOrg, ...userOrg };
196
196
  }
197
+ function normalizeCloudEndpoint(raw) {
198
+ const cloud = raw.cloud;
199
+ if (!cloud?.endpoint) return;
200
+ const ep = String(cloud.endpoint);
201
+ if (ep === "https://askexe.com/cloud" || ep === "https://askexe.com/cloud/") {
202
+ cloud.endpoint = "https://cloud.askexe.com";
203
+ process.stderr.write(
204
+ "[config] Auto-migrated cloud endpoint: askexe.com/cloud \u2192 cloud.askexe.com\n"
205
+ );
206
+ }
207
+ }
197
208
  async function loadConfig() {
198
209
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
199
210
  await ensurePrivateDir(dir);
@@ -219,6 +230,7 @@ async function loadConfig() {
219
230
  normalizeSessionLifecycle(migratedCfg);
220
231
  normalizeAutoUpdate(migratedCfg);
221
232
  normalizeOrchestration(migratedCfg);
233
+ normalizeCloudEndpoint(migratedCfg);
222
234
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
223
235
  if (config.dbPath.startsWith("~")) {
224
236
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -320,11 +332,176 @@ var init_config = __esm({
320
332
  }
321
333
  });
322
334
 
335
+ // src/lib/runtime-table.ts
336
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
337
+ var init_runtime_table = __esm({
338
+ "src/lib/runtime-table.ts"() {
339
+ "use strict";
340
+ RUNTIME_TABLE = {
341
+ codex: {
342
+ binary: "codex",
343
+ launchMode: "interactive",
344
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
345
+ inlineFlag: "--no-alt-screen",
346
+ apiKeyEnv: "OPENAI_API_KEY",
347
+ defaultModel: "gpt-5.5"
348
+ },
349
+ opencode: {
350
+ binary: "opencode",
351
+ launchMode: "exec",
352
+ autoApproveFlag: "--dangerously-skip-permissions",
353
+ inlineFlag: "",
354
+ apiKeyEnv: "ANTHROPIC_API_KEY",
355
+ defaultModel: "anthropic/claude-sonnet-4-6"
356
+ }
357
+ };
358
+ DEFAULT_RUNTIME = "claude";
359
+ }
360
+ });
361
+
362
+ // src/lib/agent-config.ts
363
+ var agent_config_exports = {};
364
+ __export(agent_config_exports, {
365
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
366
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
367
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
368
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
369
+ clearAgentRuntime: () => clearAgentRuntime,
370
+ getAgentRuntime: () => getAgentRuntime,
371
+ loadAgentConfig: () => loadAgentConfig,
372
+ normalizeCcModelName: () => normalizeCcModelName,
373
+ saveAgentConfig: () => saveAgentConfig,
374
+ setAgentMcps: () => setAgentMcps,
375
+ setAgentRuntime: () => setAgentRuntime
376
+ });
377
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
378
+ import path2 from "path";
379
+ function loadAgentConfig() {
380
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
381
+ try {
382
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
383
+ } catch {
384
+ return {};
385
+ }
386
+ }
387
+ function saveAgentConfig(config) {
388
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
389
+ ensurePrivateDirSync(dir);
390
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
391
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
392
+ }
393
+ function getAgentRuntime(agentId) {
394
+ const config = loadAgentConfig();
395
+ const entry = config[agentId];
396
+ if (entry) return entry;
397
+ const orgDefault = config["default"];
398
+ if (orgDefault) return orgDefault;
399
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
400
+ }
401
+ function normalizeCcModelName(model) {
402
+ let ccModel = model.replace(/(\d+)\.(\d+)/g, "$1-$2");
403
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
404
+ ccModel += "[1m]";
405
+ }
406
+ return ccModel;
407
+ }
408
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
409
+ const knownModels = KNOWN_RUNTIMES[runtime];
410
+ if (!knownModels) {
411
+ return {
412
+ ok: false,
413
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
414
+ };
415
+ }
416
+ if (!knownModels.includes(model)) {
417
+ return {
418
+ ok: false,
419
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
420
+ };
421
+ }
422
+ const config = loadAgentConfig();
423
+ const existing = config[agentId];
424
+ const entry = { runtime, model };
425
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
426
+ if (mcps !== void 0) {
427
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
428
+ } else if (existing?.mcps) {
429
+ entry.mcps = existing.mcps;
430
+ }
431
+ config[agentId] = entry;
432
+ saveAgentConfig(config);
433
+ return { ok: true };
434
+ }
435
+ function setAgentMcps(agentId, mcps) {
436
+ const config = loadAgentConfig();
437
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
438
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
439
+ config[agentId] = existing;
440
+ saveAgentConfig(config);
441
+ return { ok: true };
442
+ }
443
+ function clearAgentRuntime(agentId) {
444
+ const config = loadAgentConfig();
445
+ delete config[agentId];
446
+ saveAgentConfig(config);
447
+ }
448
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
449
+ var init_agent_config = __esm({
450
+ "src/lib/agent-config.ts"() {
451
+ "use strict";
452
+ init_config();
453
+ init_runtime_table();
454
+ init_secure_files();
455
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
456
+ KNOWN_RUNTIMES = {
457
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
458
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
459
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
460
+ };
461
+ RUNTIME_LABELS = {
462
+ claude: "Claude Code (Anthropic)",
463
+ codex: "Codex (OpenAI)",
464
+ opencode: "OpenCode (open source)"
465
+ };
466
+ DEFAULT_MODELS = {
467
+ claude: "claude-opus-4.6",
468
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
469
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
470
+ };
471
+ }
472
+ });
473
+
323
474
  // src/lib/employees.ts
475
+ var employees_exports = {};
476
+ __export(employees_exports, {
477
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
478
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
479
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
480
+ addEmployee: () => addEmployee,
481
+ baseAgentName: () => baseAgentName,
482
+ canCoordinate: () => canCoordinate,
483
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
484
+ getCoordinatorName: () => getCoordinatorName,
485
+ getEmployee: () => getEmployee,
486
+ getEmployeeByRole: () => getEmployeeByRole,
487
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
488
+ hasRole: () => hasRole,
489
+ hireEmployee: () => hireEmployee,
490
+ isCoordinatorName: () => isCoordinatorName,
491
+ isCoordinatorRole: () => isCoordinatorRole,
492
+ isMultiInstance: () => isMultiInstance,
493
+ loadEmployees: () => loadEmployees,
494
+ loadEmployeesSync: () => loadEmployeesSync,
495
+ normalizeRole: () => normalizeRole,
496
+ normalizeRosterCase: () => normalizeRosterCase,
497
+ registerBinSymlinks: () => registerBinSymlinks,
498
+ saveEmployees: () => saveEmployees,
499
+ validateEmployeeName: () => validateEmployeeName
500
+ });
324
501
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
325
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
502
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
326
503
  import { execSync } from "child_process";
327
- import path2 from "path";
504
+ import path3 from "path";
328
505
  import os2 from "os";
329
506
  function normalizeRole(role) {
330
507
  return (role ?? "").trim().toLowerCase();
@@ -338,29 +515,222 @@ function getCoordinatorEmployee(employees) {
338
515
  function getCoordinatorName(employees = loadEmployeesSync()) {
339
516
  return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
340
517
  }
518
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
519
+ if (!agentName) return false;
520
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
521
+ }
522
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
523
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
524
+ }
525
+ function validateEmployeeName(name) {
526
+ if (!name) {
527
+ return { valid: false, error: "Name is required" };
528
+ }
529
+ if (name.length > 32) {
530
+ return { valid: false, error: "Name must be 32 characters or fewer" };
531
+ }
532
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
533
+ return {
534
+ valid: false,
535
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
536
+ };
537
+ }
538
+ return { valid: true };
539
+ }
540
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
541
+ if (!existsSync4(employeesPath)) {
542
+ return [];
543
+ }
544
+ const raw = await readFile2(employeesPath, "utf-8");
545
+ try {
546
+ return JSON.parse(raw);
547
+ } catch {
548
+ return [];
549
+ }
550
+ }
551
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
552
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
553
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
554
+ }
341
555
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
342
- if (!existsSync3(employeesPath)) return [];
556
+ if (!existsSync4(employeesPath)) return [];
343
557
  try {
344
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
558
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
345
559
  } catch {
346
560
  return [];
347
561
  }
348
562
  }
349
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, IDENTITY_DIR;
563
+ function getEmployee(employees, name) {
564
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
565
+ }
566
+ function getEmployeeByRole(employees, role) {
567
+ const lower = role.toLowerCase();
568
+ return employees.find((e) => e.role.toLowerCase() === lower);
569
+ }
570
+ function getEmployeeNamesByRole(employees, role) {
571
+ const lower = role.toLowerCase();
572
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
573
+ }
574
+ function hasRole(agentName, role) {
575
+ const employees = loadEmployeesSync();
576
+ const emp = getEmployee(employees, agentName);
577
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
578
+ }
579
+ function baseAgentName(name, employees) {
580
+ const match = name.match(/^([a-zA-Z]+)\d+$/);
581
+ if (!match) return name;
582
+ const base = match[1];
583
+ const roster = employees ?? loadEmployeesSync();
584
+ if (getEmployee(roster, base)) return base;
585
+ return name;
586
+ }
587
+ function isMultiInstance(agentName, employees) {
588
+ const roster = employees ?? loadEmployeesSync();
589
+ const emp = getEmployee(roster, agentName);
590
+ if (!emp) return false;
591
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
592
+ }
593
+ function addEmployee(employees, employee) {
594
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
595
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
596
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
597
+ throw new Error(`Employee '${normalized.name}' already exists`);
598
+ }
599
+ return [...employees, normalized];
600
+ }
601
+ function appendToCoordinatorTeam(employee) {
602
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
603
+ if (!coordinator) return;
604
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
605
+ if (!existsSync4(idPath)) return;
606
+ const content = readFileSync3(idPath, "utf-8");
607
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
608
+ const teamMatch = content.match(TEAM_SECTION_RE);
609
+ if (!teamMatch || teamMatch.index === void 0) return;
610
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
611
+ const nextHeading = afterTeam.match(/\n## /);
612
+ const entry = `
613
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
614
+ `;
615
+ let updated;
616
+ if (nextHeading && nextHeading.index !== void 0) {
617
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
618
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
619
+ } else {
620
+ updated = content.trimEnd() + "\n" + entry;
621
+ }
622
+ writeFileSync2(idPath, updated, "utf-8");
623
+ }
624
+ function capitalize(s) {
625
+ return s.charAt(0).toUpperCase() + s.slice(1);
626
+ }
627
+ async function hireEmployee(employee) {
628
+ const employees = await loadEmployees();
629
+ const updated = addEmployee(employees, employee);
630
+ await saveEmployees(updated);
631
+ try {
632
+ appendToCoordinatorTeam(employee);
633
+ } catch {
634
+ }
635
+ try {
636
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
637
+ const config = loadAgentConfig2();
638
+ const name = employee.name.toLowerCase();
639
+ if (!config[name] && config["default"]) {
640
+ config[name] = { ...config["default"] };
641
+ saveAgentConfig2(config);
642
+ }
643
+ } catch {
644
+ }
645
+ return updated;
646
+ }
647
+ async function normalizeRosterCase(rosterPath) {
648
+ const employees = await loadEmployees(rosterPath);
649
+ let changed = false;
650
+ for (const emp of employees) {
651
+ if (emp.name !== emp.name.toLowerCase()) {
652
+ const oldName = emp.name;
653
+ emp.name = emp.name.toLowerCase();
654
+ changed = true;
655
+ try {
656
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
657
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
658
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
659
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
660
+ renameSync2(oldPath, newPath);
661
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
662
+ const content = readFileSync3(oldPath, "utf-8");
663
+ writeFileSync2(newPath, content, "utf-8");
664
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
665
+ unlinkSync(oldPath);
666
+ }
667
+ }
668
+ } catch {
669
+ }
670
+ }
671
+ }
672
+ if (changed) {
673
+ await saveEmployees(employees, rosterPath);
674
+ }
675
+ return changed;
676
+ }
677
+ function findExeBin() {
678
+ try {
679
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
680
+ } catch {
681
+ return null;
682
+ }
683
+ }
684
+ function registerBinSymlinks(name) {
685
+ const created = [];
686
+ const skipped = [];
687
+ const errors = [];
688
+ const exeBinPath = findExeBin();
689
+ if (!exeBinPath) {
690
+ errors.push("Could not find 'exe-os' in PATH");
691
+ return { created, skipped, errors };
692
+ }
693
+ const binDir = path3.dirname(exeBinPath);
694
+ let target;
695
+ try {
696
+ target = readlinkSync(exeBinPath);
697
+ } catch {
698
+ errors.push("Could not read 'exe' symlink");
699
+ return { created, skipped, errors };
700
+ }
701
+ for (const suffix of ["", "-opencode"]) {
702
+ const linkName = `${name}${suffix}`;
703
+ const linkPath = path3.join(binDir, linkName);
704
+ if (existsSync4(linkPath)) {
705
+ skipped.push(linkName);
706
+ continue;
707
+ }
708
+ try {
709
+ symlinkSync(target, linkPath);
710
+ created.push(linkName);
711
+ } catch (err) {
712
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
713
+ }
714
+ }
715
+ return { created, skipped, errors };
716
+ }
717
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
350
718
  var init_employees = __esm({
351
719
  "src/lib/employees.ts"() {
352
720
  "use strict";
353
721
  init_config();
354
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
722
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
355
723
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
356
724
  COORDINATOR_ROLE = "COO";
357
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
725
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
726
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
727
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
358
728
  }
359
729
  });
360
730
 
361
731
  // src/lib/database-adapter.ts
362
732
  import os3 from "os";
363
- import path3 from "path";
733
+ import path4 from "path";
364
734
  import { createRequire } from "module";
365
735
  import { pathToFileURL } from "url";
366
736
  function quotedIdentifier(identifier) {
@@ -671,8 +1041,8 @@ async function loadPrismaClient() {
671
1041
  }
672
1042
  return new PrismaClient2();
673
1043
  }
674
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
675
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1044
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1045
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
676
1046
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
677
1047
  const module = await import(pathToFileURL(prismaEntry).href);
678
1048
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -953,8 +1323,8 @@ var init_memory = __esm({
953
1323
 
954
1324
  // src/lib/daemon-auth.ts
955
1325
  import crypto from "crypto";
956
- import path4 from "path";
957
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1326
+ import path5 from "path";
1327
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
958
1328
  function normalizeToken(token) {
959
1329
  if (!token) return null;
960
1330
  const trimmed = token.trim();
@@ -962,8 +1332,8 @@ function normalizeToken(token) {
962
1332
  }
963
1333
  function readDaemonToken() {
964
1334
  try {
965
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
966
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1335
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
1336
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
967
1337
  } catch {
968
1338
  return null;
969
1339
  }
@@ -973,7 +1343,7 @@ function ensureDaemonToken(seed) {
973
1343
  if (existing) return existing;
974
1344
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
975
1345
  ensurePrivateDirSync(EXE_AI_DIR);
976
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1346
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
977
1347
  `, "utf8");
978
1348
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
979
1349
  return token;
@@ -984,7 +1354,7 @@ var init_daemon_auth = __esm({
984
1354
  "use strict";
985
1355
  init_config();
986
1356
  init_secure_files();
987
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1357
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
988
1358
  }
989
1359
  });
990
1360
 
@@ -1004,8 +1374,8 @@ import net from "net";
1004
1374
  import os4 from "os";
1005
1375
  import { spawn, execSync as execSync2 } from "child_process";
1006
1376
  import { randomUUID } from "crypto";
1007
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1008
- import path5 from "path";
1377
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1378
+ import path6 from "path";
1009
1379
  import { fileURLToPath } from "url";
1010
1380
  function handleData(chunk) {
1011
1381
  _buffer += chunk.toString();
@@ -1041,9 +1411,9 @@ function isZombie(pid) {
1041
1411
  }
1042
1412
  }
1043
1413
  function cleanupStaleFiles() {
1044
- if (existsSync5(PID_PATH)) {
1414
+ if (existsSync6(PID_PATH)) {
1045
1415
  try {
1046
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1416
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1047
1417
  if (pid > 0) {
1048
1418
  try {
1049
1419
  process.kill(pid, 0);
@@ -1068,11 +1438,11 @@ function cleanupStaleFiles() {
1068
1438
  }
1069
1439
  }
1070
1440
  function findPackageRoot() {
1071
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1072
- const { root } = path5.parse(dir);
1441
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1442
+ const { root } = path6.parse(dir);
1073
1443
  while (dir !== root) {
1074
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1075
- dir = path5.dirname(dir);
1444
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1445
+ dir = path6.dirname(dir);
1076
1446
  }
1077
1447
  return null;
1078
1448
  }
@@ -1090,8 +1460,8 @@ function spawnDaemon() {
1090
1460
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1091
1461
  return;
1092
1462
  }
1093
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1094
- if (!existsSync5(daemonPath)) {
1463
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1464
+ if (!existsSync6(daemonPath)) {
1095
1465
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1096
1466
  `);
1097
1467
  return;
@@ -1100,7 +1470,7 @@ function spawnDaemon() {
1100
1470
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1101
1471
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1102
1472
  `);
1103
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1473
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1104
1474
  let stderrFd = "ignore";
1105
1475
  try {
1106
1476
  stderrFd = openSync(logPath, "a");
@@ -1265,9 +1635,9 @@ function killAndRespawnDaemon() {
1265
1635
  }
1266
1636
  try {
1267
1637
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1268
- if (existsSync5(PID_PATH)) {
1638
+ if (existsSync6(PID_PATH)) {
1269
1639
  try {
1270
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1640
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1271
1641
  if (pid > 0) {
1272
1642
  try {
1273
1643
  process.kill(pid, "SIGKILL");
@@ -1413,9 +1783,9 @@ var init_exe_daemon_client = __esm({
1413
1783
  "use strict";
1414
1784
  init_config();
1415
1785
  init_daemon_auth();
1416
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1417
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1418
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1786
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1787
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1788
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1419
1789
  SPAWN_LOCK_STALE_MS = 3e4;
1420
1790
  CONNECT_TIMEOUT_MS = 15e3;
1421
1791
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1683,7 +2053,7 @@ __export(database_exports, {
1683
2053
  isInitialized: () => isInitialized,
1684
2054
  setExternalClient: () => setExternalClient
1685
2055
  });
1686
- import { chmodSync as chmodSync2, existsSync as existsSync6, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
2056
+ import { chmodSync as chmodSync2, existsSync as existsSync7, statSync as statSync2, copyFileSync, unlinkSync as unlinkSync3, openSync as openSync2, closeSync as closeSync2, mkdirSync as mkdirSync2 } from "fs";
1687
2057
  import { createClient } from "@libsql/client";
1688
2058
  import { homedir } from "os";
1689
2059
  import { join } from "path";
@@ -1736,11 +2106,11 @@ function releaseDbLock() {
1736
2106
  }
1737
2107
  async function initDatabase(config) {
1738
2108
  acquireDbLock();
1739
- if (existsSync6(config.dbPath)) {
2109
+ if (existsSync7(config.dbPath)) {
1740
2110
  const dbStat = statSync2(config.dbPath);
1741
2111
  if (dbStat.size === 0) {
1742
2112
  const walPath = config.dbPath + "-wal";
1743
- if (existsSync6(walPath) && statSync2(walPath).size > 0) {
2113
+ if (existsSync7(walPath) && statSync2(walPath).size > 0) {
1744
2114
  const backupPath = config.dbPath + ".zeroed-" + Date.now();
1745
2115
  copyFileSync(config.dbPath, backupPath);
1746
2116
  unlinkSync3(config.dbPath);
@@ -3326,13 +3696,13 @@ var init_database = __esm({
3326
3696
  });
3327
3697
 
3328
3698
  // src/lib/session-registry.ts
3329
- import path6 from "path";
3699
+ import path7 from "path";
3330
3700
  import os5 from "os";
3331
3701
  var REGISTRY_PATH;
3332
3702
  var init_session_registry = __esm({
3333
3703
  "src/lib/session-registry.ts"() {
3334
3704
  "use strict";
3335
- REGISTRY_PATH = path6.join(os5.homedir(), ".exe-os", "session-registry.json");
3705
+ REGISTRY_PATH = path7.join(os5.homedir(), ".exe-os", "session-registry.json");
3336
3706
  }
3337
3707
  });
3338
3708
 
@@ -3550,51 +3920,6 @@ var init_provider_table = __esm({
3550
3920
  }
3551
3921
  });
3552
3922
 
3553
- // src/lib/runtime-table.ts
3554
- var RUNTIME_TABLE;
3555
- var init_runtime_table = __esm({
3556
- "src/lib/runtime-table.ts"() {
3557
- "use strict";
3558
- RUNTIME_TABLE = {
3559
- codex: {
3560
- binary: "codex",
3561
- launchMode: "interactive",
3562
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
3563
- inlineFlag: "--no-alt-screen",
3564
- apiKeyEnv: "OPENAI_API_KEY",
3565
- defaultModel: "gpt-5.5"
3566
- },
3567
- opencode: {
3568
- binary: "opencode",
3569
- launchMode: "exec",
3570
- autoApproveFlag: "--dangerously-skip-permissions",
3571
- inlineFlag: "",
3572
- apiKeyEnv: "ANTHROPIC_API_KEY",
3573
- defaultModel: "anthropic/claude-sonnet-4-6"
3574
- }
3575
- };
3576
- }
3577
- });
3578
-
3579
- // src/lib/agent-config.ts
3580
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7 } from "fs";
3581
- import path7 from "path";
3582
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
3583
- var init_agent_config = __esm({
3584
- "src/lib/agent-config.ts"() {
3585
- "use strict";
3586
- init_config();
3587
- init_runtime_table();
3588
- init_secure_files();
3589
- AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
3590
- DEFAULT_MODELS = {
3591
- claude: "claude-opus-4.6",
3592
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
3593
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
3594
- };
3595
- }
3596
- });
3597
-
3598
3923
  // src/lib/intercom-queue.ts
3599
3924
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
3600
3925
  import path8 from "path";
@@ -3675,6 +4000,21 @@ function isRootSession(name) {
3675
4000
  function extractRootExe(name) {
3676
4001
  if (!name) return null;
3677
4002
  if (!name.includes("-")) return name;
4003
+ try {
4004
+ const roster = (init_employees(), __toCommonJS(employees_exports)).loadEmployeesSync();
4005
+ if (roster.length > 0) {
4006
+ const sortedNames = roster.map((e) => e.name).sort((a, b) => b.length - a.length);
4007
+ for (const agentName of sortedNames) {
4008
+ const escaped = agentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4009
+ const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
4010
+ const match = name.match(regex);
4011
+ if (match) {
4012
+ return extractRootExe(match[1]);
4013
+ }
4014
+ }
4015
+ }
4016
+ } catch {
4017
+ }
3678
4018
  const parts = name.split("-").filter(Boolean);
3679
4019
  return parts.length > 0 ? parts[parts.length - 1] : null;
3680
4020
  }
@@ -3693,6 +4033,10 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3693
4033
  function getParentExe(sessionKey) {
3694
4034
  try {
3695
4035
  const data = JSON.parse(readFileSync9(path12.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
4036
+ if (data.registeredAt) {
4037
+ const age = Date.now() - new Date(data.registeredAt).getTime();
4038
+ if (age > PARENT_EXE_CACHE_TTL_MS) return null;
4039
+ }
3696
4040
  return data.parentExe || null;
3697
4041
  } catch {
3698
4042
  return null;
@@ -3765,7 +4109,7 @@ function resolveExeSession() {
3765
4109
  }
3766
4110
  return candidate;
3767
4111
  }
3768
- var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
4112
+ var SPAWN_LOCK_DIR, SESSION_CACHE, PARENT_EXE_CACHE_TTL_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3769
4113
  var init_tmux_routing = __esm({
3770
4114
  "src/lib/tmux-routing.ts"() {
3771
4115
  "use strict";
@@ -3783,6 +4127,7 @@ var init_tmux_routing = __esm({
3783
4127
  init_agent_symlinks();
3784
4128
  SPAWN_LOCK_DIR = path12.join(os9.homedir(), ".exe-os", "spawn-locks");
3785
4129
  SESSION_CACHE = path12.join(os9.homedir(), ".exe-os", "session-cache");
4130
+ PARENT_EXE_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
3786
4131
  INTERCOM_LOG2 = path12.join(os9.homedir(), ".exe-os", "intercom.log");
3787
4132
  DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
3788
4133
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
@@ -3814,7 +4159,7 @@ var init_task_scope = __esm({
3814
4159
  });
3815
4160
 
3816
4161
  // src/lib/keychain.ts
3817
- import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
4162
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2, rename, copyFile } from "fs/promises";
3818
4163
  import { existsSync as existsSync13, statSync as statSync3 } from "fs";
3819
4164
  import { execSync as execSync5 } from "child_process";
3820
4165
  import path13 from "path";
@@ -3849,12 +4194,14 @@ function linuxSecretAvailable() {
3849
4194
  function isRootOnlyTrustedServerKeyFile(keyPath) {
3850
4195
  if (process.platform !== "linux") return false;
3851
4196
  try {
3852
- const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3853
4197
  const st = statSync3(keyPath);
3854
4198
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
4199
+ const uid = typeof os10.userInfo().uid === "number" ? os10.userInfo().uid : -1;
3855
4200
  if (uid === 0) return true;
3856
4201
  const exeOsDir = process.env.EXE_OS_DIR;
3857
- return Boolean(exeOsDir && path13.resolve(keyPath).startsWith(path13.resolve(exeOsDir) + path13.sep));
4202
+ if (exeOsDir && path13.resolve(keyPath).startsWith(path13.resolve(exeOsDir) + path13.sep)) return true;
4203
+ if (!linuxSecretAvailable()) return true;
4204
+ return false;
3858
4205
  } catch {
3859
4206
  return false;
3860
4207
  }
@@ -4004,15 +4351,25 @@ async function writeMachineBoundFileFallback(b64) {
4004
4351
  await mkdir3(dir, { recursive: true });
4005
4352
  const keyPath = getKeyPath();
4006
4353
  const machineKey = deriveMachineKey();
4007
- if (machineKey) {
4008
- const encrypted = encryptWithMachineKey(b64, machineKey);
4009
- await writeFile3(keyPath, encrypted + "\n", "utf-8");
4010
- await chmod2(keyPath, 384);
4011
- return "encrypted";
4354
+ const content = machineKey ? encryptWithMachineKey(b64, machineKey) + "\n" : b64 + "\n";
4355
+ const result = machineKey ? "encrypted" : "plaintext";
4356
+ const tmpPath = keyPath + ".tmp";
4357
+ try {
4358
+ if (existsSync13(keyPath)) {
4359
+ await copyFile(keyPath, keyPath + ".bak").catch(() => {
4360
+ });
4361
+ }
4362
+ await writeFile3(tmpPath, content, "utf-8");
4363
+ await chmod2(tmpPath, 384);
4364
+ await rename(tmpPath, keyPath);
4365
+ } catch (err) {
4366
+ try {
4367
+ await unlink(tmpPath);
4368
+ } catch {
4369
+ }
4370
+ throw err;
4012
4371
  }
4013
- await writeFile3(keyPath, b64 + "\n", "utf-8");
4014
- await chmod2(keyPath, 384);
4015
- return "plaintext";
4372
+ return result;
4016
4373
  }
4017
4374
  async function getMasterKey() {
4018
4375
  let nativeValue = macKeychainGet() ?? linuxSecretGet();
@@ -4079,7 +4436,7 @@ async function getMasterKey() {
4079
4436
  b64Value = content;
4080
4437
  }
4081
4438
  const key = Buffer.from(b64Value, "base64");
4082
- if (!content.startsWith(ENCRYPTED_PREFIX) && isRootOnlyTrustedServerKeyFile(keyPath)) {
4439
+ if (isRootOnlyTrustedServerKeyFile(keyPath)) {
4083
4440
  return key;
4084
4441
  }
4085
4442
  const migrated = macKeychainSet(b64Value) || linuxSecretSet(b64Value);