@askexenow/exe-os 0.9.112 → 0.9.113

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 (95) hide show
  1. package/README.md +9 -7
  2. package/dist/bin/agentic-ontology-backfill.js +54 -11
  3. package/dist/bin/agentic-reflection-backfill.js +29 -1
  4. package/dist/bin/agentic-semantic-label.js +29 -1
  5. package/dist/bin/backfill-conversations.js +53 -10
  6. package/dist/bin/backfill-responses.js +54 -11
  7. package/dist/bin/backfill-vectors.js +29 -1
  8. package/dist/bin/bulk-sync-postgres.js +55 -12
  9. package/dist/bin/cleanup-stale-review-tasks.js +75 -15
  10. package/dist/bin/cli.js +293 -76
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +28 -2
  13. package/dist/bin/exe-assign.js +54 -11
  14. package/dist/bin/exe-boot.js +481 -147
  15. package/dist/bin/exe-call.js +45 -4
  16. package/dist/bin/exe-cloud.js +93 -15
  17. package/dist/bin/exe-dispatch.js +369 -24
  18. package/dist/bin/exe-doctor.js +53 -10
  19. package/dist/bin/exe-export-behaviors.js +54 -11
  20. package/dist/bin/exe-forget.js +54 -11
  21. package/dist/bin/exe-gateway.js +128 -23
  22. package/dist/bin/exe-heartbeat.js +75 -15
  23. package/dist/bin/exe-kill.js +54 -11
  24. package/dist/bin/exe-launch-agent.js +70 -12
  25. package/dist/bin/exe-new-employee.js +175 -7
  26. package/dist/bin/exe-pending-messages.js +75 -15
  27. package/dist/bin/exe-pending-notifications.js +75 -15
  28. package/dist/bin/exe-pending-reviews.js +75 -15
  29. package/dist/bin/exe-rename.js +54 -11
  30. package/dist/bin/exe-review.js +54 -11
  31. package/dist/bin/exe-search.js +54 -11
  32. package/dist/bin/exe-session-cleanup.js +491 -146
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +524 -245
  35. package/dist/bin/exe-start-opencode.js +534 -165
  36. package/dist/bin/exe-status.js +75 -15
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +54 -11
  39. package/dist/bin/git-sweep.js +369 -24
  40. package/dist/bin/graph-backfill.js +54 -11
  41. package/dist/bin/graph-export.js +54 -11
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +491 -146
  44. package/dist/bin/pre-publish.js +13 -1
  45. package/dist/bin/scan-tasks.js +369 -24
  46. package/dist/bin/setup.js +91 -13
  47. package/dist/bin/shard-migrate.js +54 -11
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +128 -23
  51. package/dist/hooks/bug-report-worker.js +128 -23
  52. package/dist/hooks/codex-stop-task-finalizer.js +512 -140
  53. package/dist/hooks/commit-complete.js +369 -24
  54. package/dist/hooks/error-recall.js +54 -11
  55. package/dist/hooks/ingest.js +4575 -252
  56. package/dist/hooks/instructions-loaded.js +54 -11
  57. package/dist/hooks/notification.js +54 -11
  58. package/dist/hooks/post-compact.js +75 -15
  59. package/dist/hooks/post-tool-combined.js +75 -15
  60. package/dist/hooks/pre-compact.js +449 -104
  61. package/dist/hooks/pre-tool-use.js +90 -15
  62. package/dist/hooks/prompt-submit.js +129 -24
  63. package/dist/hooks/session-end.js +451 -109
  64. package/dist/hooks/session-start.js +104 -16
  65. package/dist/hooks/stop.js +74 -14
  66. package/dist/hooks/subagent-stop.js +75 -15
  67. package/dist/hooks/summary-worker.js +73 -7
  68. package/dist/index.js +128 -23
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +38 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +16 -0
  73. package/dist/lib/db.js +16 -0
  74. package/dist/lib/device-registry.js +16 -0
  75. package/dist/lib/employee-templates.js +29 -3
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +268 -42
  78. package/dist/lib/hybrid-search.js +54 -11
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +29 -1
  82. package/dist/lib/skill-learning.js +458 -70
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +54 -11
  85. package/dist/lib/tasks.js +393 -91
  86. package/dist/lib/tmux-routing.js +316 -14
  87. package/dist/mcp/server.js +169 -30
  88. package/dist/mcp/tools/create-task.js +75 -13
  89. package/dist/mcp/tools/deactivate-behavior.js +33 -24
  90. package/dist/mcp/tools/list-tasks.js +21 -4
  91. package/dist/mcp/tools/send-message.js +21 -4
  92. package/dist/mcp/tools/update-task.js +390 -91
  93. package/dist/runtime/index.js +446 -101
  94. package/dist/tui/App.js +208 -54
  95. package/package.json +1 -1
package/dist/lib/tasks.js CHANGED
@@ -337,11 +337,168 @@ var init_config = __esm({
337
337
  }
338
338
  });
339
339
 
340
+ // src/lib/runtime-table.ts
341
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
342
+ var init_runtime_table = __esm({
343
+ "src/lib/runtime-table.ts"() {
344
+ "use strict";
345
+ RUNTIME_TABLE = {
346
+ codex: {
347
+ binary: "codex",
348
+ launchMode: "interactive",
349
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
350
+ inlineFlag: "--no-alt-screen",
351
+ apiKeyEnv: "OPENAI_API_KEY",
352
+ defaultModel: "gpt-5.5"
353
+ },
354
+ opencode: {
355
+ binary: "opencode",
356
+ launchMode: "exec",
357
+ autoApproveFlag: "--dangerously-skip-permissions",
358
+ inlineFlag: "",
359
+ apiKeyEnv: "ANTHROPIC_API_KEY",
360
+ defaultModel: "anthropic/claude-sonnet-4-6"
361
+ }
362
+ };
363
+ DEFAULT_RUNTIME = "claude";
364
+ }
365
+ });
366
+
367
+ // src/lib/agent-config.ts
368
+ var agent_config_exports = {};
369
+ __export(agent_config_exports, {
370
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
371
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
372
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
373
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
374
+ clearAgentRuntime: () => clearAgentRuntime,
375
+ getAgentRuntime: () => getAgentRuntime,
376
+ loadAgentConfig: () => loadAgentConfig,
377
+ saveAgentConfig: () => saveAgentConfig,
378
+ setAgentMcps: () => setAgentMcps,
379
+ setAgentRuntime: () => setAgentRuntime
380
+ });
381
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
382
+ import path2 from "path";
383
+ function loadAgentConfig() {
384
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
385
+ try {
386
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
387
+ } catch {
388
+ return {};
389
+ }
390
+ }
391
+ function saveAgentConfig(config) {
392
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
393
+ ensurePrivateDirSync(dir);
394
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
395
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
396
+ }
397
+ function getAgentRuntime(agentId) {
398
+ const config = loadAgentConfig();
399
+ const entry = config[agentId];
400
+ if (entry) return entry;
401
+ const orgDefault = config["default"];
402
+ if (orgDefault) return orgDefault;
403
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
404
+ }
405
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
406
+ const knownModels = KNOWN_RUNTIMES[runtime];
407
+ if (!knownModels) {
408
+ return {
409
+ ok: false,
410
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
411
+ };
412
+ }
413
+ if (!knownModels.includes(model)) {
414
+ return {
415
+ ok: false,
416
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
417
+ };
418
+ }
419
+ const config = loadAgentConfig();
420
+ const existing = config[agentId];
421
+ const entry = { runtime, model };
422
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
423
+ if (mcps !== void 0) {
424
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
425
+ } else if (existing?.mcps) {
426
+ entry.mcps = existing.mcps;
427
+ }
428
+ config[agentId] = entry;
429
+ saveAgentConfig(config);
430
+ return { ok: true };
431
+ }
432
+ function setAgentMcps(agentId, mcps) {
433
+ const config = loadAgentConfig();
434
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
435
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
436
+ config[agentId] = existing;
437
+ saveAgentConfig(config);
438
+ return { ok: true };
439
+ }
440
+ function clearAgentRuntime(agentId) {
441
+ const config = loadAgentConfig();
442
+ delete config[agentId];
443
+ saveAgentConfig(config);
444
+ }
445
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
446
+ var init_agent_config = __esm({
447
+ "src/lib/agent-config.ts"() {
448
+ "use strict";
449
+ init_config();
450
+ init_runtime_table();
451
+ init_secure_files();
452
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
453
+ KNOWN_RUNTIMES = {
454
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
455
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
456
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
457
+ };
458
+ RUNTIME_LABELS = {
459
+ claude: "Claude Code (Anthropic)",
460
+ codex: "Codex (OpenAI)",
461
+ opencode: "OpenCode (open source)"
462
+ };
463
+ DEFAULT_MODELS = {
464
+ claude: "claude-opus-4.6",
465
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
466
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
467
+ };
468
+ }
469
+ });
470
+
340
471
  // src/lib/employees.ts
472
+ var employees_exports = {};
473
+ __export(employees_exports, {
474
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
475
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
476
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
477
+ addEmployee: () => addEmployee,
478
+ baseAgentName: () => baseAgentName,
479
+ canCoordinate: () => canCoordinate,
480
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
481
+ getCoordinatorName: () => getCoordinatorName,
482
+ getEmployee: () => getEmployee,
483
+ getEmployeeByRole: () => getEmployeeByRole,
484
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
485
+ hasRole: () => hasRole,
486
+ hireEmployee: () => hireEmployee,
487
+ isCoordinatorName: () => isCoordinatorName,
488
+ isCoordinatorRole: () => isCoordinatorRole,
489
+ isMultiInstance: () => isMultiInstance,
490
+ loadEmployees: () => loadEmployees,
491
+ loadEmployeesSync: () => loadEmployeesSync,
492
+ normalizeRole: () => normalizeRole,
493
+ normalizeRosterCase: () => normalizeRosterCase,
494
+ registerBinSymlinks: () => registerBinSymlinks,
495
+ saveEmployees: () => saveEmployees,
496
+ validateEmployeeName: () => validateEmployeeName
497
+ });
341
498
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
342
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
499
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
343
500
  import { execSync } from "child_process";
344
- import path2 from "path";
501
+ import path3 from "path";
345
502
  import os2 from "os";
346
503
  function normalizeRole(role) {
347
504
  return (role ?? "").trim().toLowerCase();
@@ -359,8 +516,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
359
516
  if (!agentName) return false;
360
517
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
361
518
  }
519
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
520
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
521
+ }
522
+ function validateEmployeeName(name) {
523
+ if (!name) {
524
+ return { valid: false, error: "Name is required" };
525
+ }
526
+ if (name.length > 32) {
527
+ return { valid: false, error: "Name must be 32 characters or fewer" };
528
+ }
529
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
530
+ return {
531
+ valid: false,
532
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
533
+ };
534
+ }
535
+ return { valid: true };
536
+ }
362
537
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
363
- if (!existsSync3(employeesPath)) {
538
+ if (!existsSync4(employeesPath)) {
364
539
  return [];
365
540
  }
366
541
  const raw = await readFile2(employeesPath, "utf-8");
@@ -370,10 +545,14 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
370
545
  return [];
371
546
  }
372
547
  }
548
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
549
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
550
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
551
+ }
373
552
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
374
- if (!existsSync3(employeesPath)) return [];
553
+ if (!existsSync4(employeesPath)) return [];
375
554
  try {
376
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
555
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
377
556
  } catch {
378
557
  return [];
379
558
  }
@@ -381,6 +560,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
381
560
  function getEmployee(employees, name) {
382
561
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
383
562
  }
563
+ function getEmployeeByRole(employees, role) {
564
+ const lower = role.toLowerCase();
565
+ return employees.find((e) => e.role.toLowerCase() === lower);
566
+ }
567
+ function getEmployeeNamesByRole(employees, role) {
568
+ const lower = role.toLowerCase();
569
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
570
+ }
571
+ function hasRole(agentName, role) {
572
+ const employees = loadEmployeesSync();
573
+ const emp = getEmployee(employees, agentName);
574
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
575
+ }
384
576
  function baseAgentName(name, employees) {
385
577
  const match = name.match(/^([a-zA-Z]+)\d+$/);
386
578
  if (!match) return name;
@@ -395,22 +587,147 @@ function isMultiInstance(agentName, employees) {
395
587
  if (!emp) return false;
396
588
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
397
589
  }
398
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
590
+ function addEmployee(employees, employee) {
591
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
592
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
593
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
594
+ throw new Error(`Employee '${normalized.name}' already exists`);
595
+ }
596
+ return [...employees, normalized];
597
+ }
598
+ function appendToCoordinatorTeam(employee) {
599
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
600
+ if (!coordinator) return;
601
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
602
+ if (!existsSync4(idPath)) return;
603
+ const content = readFileSync3(idPath, "utf-8");
604
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
605
+ const teamMatch = content.match(TEAM_SECTION_RE);
606
+ if (!teamMatch || teamMatch.index === void 0) return;
607
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
608
+ const nextHeading = afterTeam.match(/\n## /);
609
+ const entry = `
610
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
611
+ `;
612
+ let updated;
613
+ if (nextHeading && nextHeading.index !== void 0) {
614
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
615
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
616
+ } else {
617
+ updated = content.trimEnd() + "\n" + entry;
618
+ }
619
+ writeFileSync2(idPath, updated, "utf-8");
620
+ }
621
+ function capitalize(s) {
622
+ return s.charAt(0).toUpperCase() + s.slice(1);
623
+ }
624
+ async function hireEmployee(employee) {
625
+ const employees = await loadEmployees();
626
+ const updated = addEmployee(employees, employee);
627
+ await saveEmployees(updated);
628
+ try {
629
+ appendToCoordinatorTeam(employee);
630
+ } catch {
631
+ }
632
+ try {
633
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
634
+ const config = loadAgentConfig2();
635
+ const name = employee.name.toLowerCase();
636
+ if (!config[name] && config["default"]) {
637
+ config[name] = { ...config["default"] };
638
+ saveAgentConfig2(config);
639
+ }
640
+ } catch {
641
+ }
642
+ return updated;
643
+ }
644
+ async function normalizeRosterCase(rosterPath) {
645
+ const employees = await loadEmployees(rosterPath);
646
+ let changed = false;
647
+ for (const emp of employees) {
648
+ if (emp.name !== emp.name.toLowerCase()) {
649
+ const oldName = emp.name;
650
+ emp.name = emp.name.toLowerCase();
651
+ changed = true;
652
+ try {
653
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
654
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
655
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
656
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
657
+ renameSync2(oldPath, newPath);
658
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
659
+ const content = readFileSync3(oldPath, "utf-8");
660
+ writeFileSync2(newPath, content, "utf-8");
661
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
662
+ unlinkSync(oldPath);
663
+ }
664
+ }
665
+ } catch {
666
+ }
667
+ }
668
+ }
669
+ if (changed) {
670
+ await saveEmployees(employees, rosterPath);
671
+ }
672
+ return changed;
673
+ }
674
+ function findExeBin() {
675
+ try {
676
+ return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
677
+ } catch {
678
+ return null;
679
+ }
680
+ }
681
+ function registerBinSymlinks(name) {
682
+ const created = [];
683
+ const skipped = [];
684
+ const errors = [];
685
+ const exeBinPath = findExeBin();
686
+ if (!exeBinPath) {
687
+ errors.push("Could not find 'exe-os' in PATH");
688
+ return { created, skipped, errors };
689
+ }
690
+ const binDir = path3.dirname(exeBinPath);
691
+ let target;
692
+ try {
693
+ target = readlinkSync(exeBinPath);
694
+ } catch {
695
+ errors.push("Could not read 'exe' symlink");
696
+ return { created, skipped, errors };
697
+ }
698
+ for (const suffix of ["", "-opencode"]) {
699
+ const linkName = `${name}${suffix}`;
700
+ const linkPath = path3.join(binDir, linkName);
701
+ if (existsSync4(linkPath)) {
702
+ skipped.push(linkName);
703
+ continue;
704
+ }
705
+ try {
706
+ symlinkSync(target, linkPath);
707
+ created.push(linkName);
708
+ } catch (err) {
709
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
710
+ }
711
+ }
712
+ return { created, skipped, errors };
713
+ }
714
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
399
715
  var init_employees = __esm({
400
716
  "src/lib/employees.ts"() {
401
717
  "use strict";
402
718
  init_config();
403
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
719
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
404
720
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
405
721
  COORDINATOR_ROLE = "COO";
406
722
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
407
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
723
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
724
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
408
725
  }
409
726
  });
410
727
 
411
728
  // src/lib/database-adapter.ts
412
729
  import os3 from "os";
413
- import path3 from "path";
730
+ import path4 from "path";
414
731
  import { createRequire } from "module";
415
732
  import { pathToFileURL } from "url";
416
733
  var BOOLEAN_COLUMNS_BY_TABLE, BOOLEAN_COLUMN_NAMES;
@@ -439,7 +756,7 @@ var init_memory = __esm({
439
756
  });
440
757
 
441
758
  // src/lib/database.ts
442
- import { chmodSync as chmodSync2, existsSync as existsSync4, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
759
+ import { chmodSync as chmodSync2, existsSync as existsSync5, statSync, copyFileSync, unlinkSync as unlinkSync2, openSync, closeSync, mkdirSync as mkdirSync2 } from "fs";
443
760
  import { createClient } from "@libsql/client";
444
761
  import { homedir } from "os";
445
762
  import { join } from "path";
@@ -492,12 +809,12 @@ var init_database = __esm({
492
809
  });
493
810
 
494
811
  // src/lib/session-registry.ts
495
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
496
- import path4 from "path";
812
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync6 } from "fs";
813
+ import path5 from "path";
497
814
  import os4 from "os";
498
815
  function registerSession(entry) {
499
- const dir = path4.dirname(REGISTRY_PATH);
500
- if (!existsSync5(dir)) {
816
+ const dir = path5.dirname(REGISTRY_PATH);
817
+ if (!existsSync6(dir)) {
501
818
  mkdirSync3(dir, { recursive: true });
502
819
  }
503
820
  const sessions = listSessions();
@@ -507,11 +824,11 @@ function registerSession(entry) {
507
824
  } else {
508
825
  sessions.push(entry);
509
826
  }
510
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
827
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
511
828
  }
512
829
  function listSessions() {
513
830
  try {
514
- const raw = readFileSync3(REGISTRY_PATH, "utf8");
831
+ const raw = readFileSync4(REGISTRY_PATH, "utf8");
515
832
  return JSON.parse(raw);
516
833
  } catch {
517
834
  return [];
@@ -521,7 +838,7 @@ var REGISTRY_PATH;
521
838
  var init_session_registry = __esm({
522
839
  "src/lib/session-registry.ts"() {
523
840
  "use strict";
524
- REGISTRY_PATH = path4.join(os4.homedir(), ".exe-os", "session-registry.json");
841
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
525
842
  }
526
843
  });
527
844
 
@@ -783,68 +1100,6 @@ var init_provider_table = __esm({
783
1100
  }
784
1101
  });
785
1102
 
786
- // src/lib/runtime-table.ts
787
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
788
- var init_runtime_table = __esm({
789
- "src/lib/runtime-table.ts"() {
790
- "use strict";
791
- RUNTIME_TABLE = {
792
- codex: {
793
- binary: "codex",
794
- launchMode: "interactive",
795
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
796
- inlineFlag: "--no-alt-screen",
797
- apiKeyEnv: "OPENAI_API_KEY",
798
- defaultModel: "gpt-5.5"
799
- },
800
- opencode: {
801
- binary: "opencode",
802
- launchMode: "exec",
803
- autoApproveFlag: "--dangerously-skip-permissions",
804
- inlineFlag: "",
805
- apiKeyEnv: "ANTHROPIC_API_KEY",
806
- defaultModel: "anthropic/claude-sonnet-4-6"
807
- }
808
- };
809
- DEFAULT_RUNTIME = "claude";
810
- }
811
- });
812
-
813
- // src/lib/agent-config.ts
814
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync6 } from "fs";
815
- import path5 from "path";
816
- function loadAgentConfig() {
817
- if (!existsSync6(AGENT_CONFIG_PATH)) return {};
818
- try {
819
- return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
820
- } catch {
821
- return {};
822
- }
823
- }
824
- function getAgentRuntime(agentId) {
825
- const config = loadAgentConfig();
826
- const entry = config[agentId];
827
- if (entry) return entry;
828
- const orgDefault = config["default"];
829
- if (orgDefault) return orgDefault;
830
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
831
- }
832
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
833
- var init_agent_config = __esm({
834
- "src/lib/agent-config.ts"() {
835
- "use strict";
836
- init_config();
837
- init_runtime_table();
838
- init_secure_files();
839
- AGENT_CONFIG_PATH = path5.join(EXE_AI_DIR, "agent-config.json");
840
- DEFAULT_MODELS = {
841
- claude: "claude-opus-4.6",
842
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
843
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
844
- };
845
- }
846
- });
847
-
848
1103
  // src/lib/intercom-queue.ts
849
1104
  var intercom_queue_exports = {};
850
1105
  __export(intercom_queue_exports, {
@@ -1339,7 +1594,7 @@ async function assertVpsLicense(opts) {
1339
1594
  }
1340
1595
  if (!transientFailure) {
1341
1596
  throw new Error(
1342
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
1597
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
1343
1598
  );
1344
1599
  }
1345
1600
  const fresh = await getCachedLicense();
@@ -1376,7 +1631,7 @@ async function assertVpsLicense(opts) {
1376
1631
  } catch {
1377
1632
  }
1378
1633
  throw new Error(
1379
- `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://askexe.com/cloud and retry. This VPS image refuses to boot after the offline grace window.`
1634
+ `License validation unreachable for more than ${graceDays} days. Restore network connectivity to https://cloud.askexe.com and retry. This VPS image refuses to boot after the offline grace window.`
1380
1635
  );
1381
1636
  }
1382
1637
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -1408,7 +1663,7 @@ var init_license = __esm({
1408
1663
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
1409
1664
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
1410
1665
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
1411
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
1666
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
1412
1667
  RETRY_DELAY_MS = 500;
1413
1668
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
1414
1669
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -2434,11 +2689,12 @@ function getDispatchedBy(sessionKey) {
2434
2689
  }
2435
2690
  }
2436
2691
  function resolveExeSession() {
2692
+ if (process.env.EXE_SESSION_NAME) {
2693
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
2694
+ if (fromEnv) return fromEnv;
2695
+ }
2437
2696
  const mySession = getMySession();
2438
2697
  if (!mySession) {
2439
- if (process.env.EXE_SESSION_NAME) {
2440
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
2441
- }
2442
2698
  return null;
2443
2699
  }
2444
2700
  const fromSessionName = extractRootExe(mySession);
@@ -2453,6 +2709,10 @@ function resolveExeSession() {
2453
2709
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
2454
2710
  `
2455
2711
  );
2712
+ try {
2713
+ registerParentExe(key, fromSessionName);
2714
+ } catch {
2715
+ }
2456
2716
  candidate = fromSessionName;
2457
2717
  } else {
2458
2718
  candidate = fromCache;
@@ -3406,6 +3666,19 @@ async function resolveTask(client, identifier, scopeSession) {
3406
3666
  args: [identifier, ...scope.args]
3407
3667
  });
3408
3668
  if (result.rows.length === 1) return result.rows[0];
3669
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
3670
+ result = await client.execute({
3671
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
3672
+ args: [`${identifier}%`]
3673
+ });
3674
+ if (result.rows.length === 1) return result.rows[0];
3675
+ if (result.rows.length > 1) {
3676
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
3677
+ throw new Error(
3678
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
3679
+ );
3680
+ }
3681
+ }
3409
3682
  result = await client.execute({
3410
3683
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
3411
3684
  args: [`%${identifier}%`, ...scope.args]
@@ -3952,12 +4225,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
3952
4225
  WHERE blocked_by = ? AND status = 'blocked'`,
3953
4226
  args: [now, taskId]
3954
4227
  });
3955
- if (baseDir && unblocked.rowsAffected > 0) {
3956
- const ubScope = sessionScopeFilter();
3957
- const unblockedRows = await client.execute({
3958
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
3959
- args: [now, ...ubScope.args]
3960
- });
4228
+ if (unblocked.rowsAffected === 0) return;
4229
+ const ubScope = sessionScopeFilter();
4230
+ const unblockedRows = await client.execute({
4231
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
4232
+ args: [now, ...ubScope.args]
4233
+ });
4234
+ if (baseDir) {
3961
4235
  for (const ur of unblockedRows.rows) {
3962
4236
  try {
3963
4237
  const ubFile = path15.join(baseDir, String(ur.task_file));
@@ -3969,6 +4243,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
3969
4243
  }
3970
4244
  }
3971
4245
  }
4246
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
4247
+ try {
4248
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
4249
+ const dispatched = /* @__PURE__ */ new Set();
4250
+ for (const ur of unblockedRows.rows) {
4251
+ const assignee = String(ur.assigned_to);
4252
+ if (dispatched.has(assignee)) continue;
4253
+ dispatched.add(assignee);
4254
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
4255
+ }
4256
+ } catch {
4257
+ }
4258
+ }
3972
4259
  }
3973
4260
  async function findNextTask(assignedTo) {
3974
4261
  const client = getClient();
@@ -4627,6 +4914,15 @@ var init_embedder = __esm({
4627
4914
  // src/lib/behaviors.ts
4628
4915
  import crypto5 from "crypto";
4629
4916
  async function storeBehavior(opts) {
4917
+ try {
4918
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
4919
+ const roster = loadEmployeesSync2();
4920
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
4921
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
4922
+ }
4923
+ } catch (e) {
4924
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
4925
+ }
4630
4926
  const client = getClient();
4631
4927
  const id = crypto5.randomUUID();
4632
4928
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5080,6 +5376,12 @@ async function updateTask(input) {
5080
5376
  }
5081
5377
  }
5082
5378
  }
5379
+ if (input.status === "cancelled") {
5380
+ try {
5381
+ await cascadeUnblock(taskId, input.baseDir, now);
5382
+ } catch {
5383
+ }
5384
+ }
5083
5385
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
5084
5386
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
5085
5387
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({