@askexenow/exe-os 0.9.111 → 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 +62 -12
  3. package/dist/bin/agentic-reflection-backfill.js +37 -2
  4. package/dist/bin/agentic-semantic-label.js +37 -2
  5. package/dist/bin/backfill-conversations.js +61 -11
  6. package/dist/bin/backfill-responses.js +62 -12
  7. package/dist/bin/backfill-vectors.js +37 -2
  8. package/dist/bin/bulk-sync-postgres.js +63 -13
  9. package/dist/bin/cleanup-stale-review-tasks.js +83 -16
  10. package/dist/bin/cli.js +312 -80
  11. package/dist/bin/exe-agent-config.js +7 -1
  12. package/dist/bin/exe-agent.js +29 -3
  13. package/dist/bin/exe-assign.js +62 -12
  14. package/dist/bin/exe-boot.js +500 -151
  15. package/dist/bin/exe-call.js +46 -5
  16. package/dist/bin/exe-cloud.js +101 -16
  17. package/dist/bin/exe-dispatch.js +827 -27
  18. package/dist/bin/exe-doctor.js +61 -11
  19. package/dist/bin/exe-export-behaviors.js +67 -14
  20. package/dist/bin/exe-forget.js +62 -12
  21. package/dist/bin/exe-gateway.js +147 -27
  22. package/dist/bin/exe-heartbeat.js +83 -16
  23. package/dist/bin/exe-kill.js +62 -12
  24. package/dist/bin/exe-launch-agent.js +83 -15
  25. package/dist/bin/exe-new-employee.js +176 -8
  26. package/dist/bin/exe-pending-messages.js +83 -16
  27. package/dist/bin/exe-pending-notifications.js +83 -16
  28. package/dist/bin/exe-pending-reviews.js +83 -16
  29. package/dist/bin/exe-rename.js +62 -12
  30. package/dist/bin/exe-review.js +62 -12
  31. package/dist/bin/exe-search.js +62 -12
  32. package/dist/bin/exe-session-cleanup.js +949 -149
  33. package/dist/bin/exe-settings.js +10 -4
  34. package/dist/bin/exe-start-codex.js +537 -248
  35. package/dist/bin/exe-start-opencode.js +547 -168
  36. package/dist/bin/exe-status.js +83 -16
  37. package/dist/bin/exe-support.js +1 -1
  38. package/dist/bin/exe-team.js +62 -12
  39. package/dist/bin/git-sweep.js +827 -27
  40. package/dist/bin/graph-backfill.js +62 -12
  41. package/dist/bin/graph-export.js +62 -12
  42. package/dist/bin/install.js +62 -4
  43. package/dist/bin/intercom-check.js +949 -149
  44. package/dist/bin/pre-publish.js +14 -2
  45. package/dist/bin/scan-tasks.js +827 -27
  46. package/dist/bin/setup.js +99 -14
  47. package/dist/bin/shard-migrate.js +62 -12
  48. package/dist/bin/stack-update.js +1 -1
  49. package/dist/bin/update.js +3 -3
  50. package/dist/gateway/index.js +586 -26
  51. package/dist/hooks/bug-report-worker.js +586 -26
  52. package/dist/hooks/codex-stop-task-finalizer.js +977 -143
  53. package/dist/hooks/commit-complete.js +827 -27
  54. package/dist/hooks/error-recall.js +62 -12
  55. package/dist/hooks/ingest.js +4579 -249
  56. package/dist/hooks/instructions-loaded.js +62 -12
  57. package/dist/hooks/notification.js +62 -12
  58. package/dist/hooks/post-compact.js +83 -16
  59. package/dist/hooks/post-tool-combined.js +83 -16
  60. package/dist/hooks/pre-compact.js +907 -107
  61. package/dist/hooks/pre-tool-use.js +98 -16
  62. package/dist/hooks/prompt-submit.js +596 -30
  63. package/dist/hooks/session-end.js +909 -112
  64. package/dist/hooks/session-start.js +112 -17
  65. package/dist/hooks/stop.js +82 -15
  66. package/dist/hooks/subagent-stop.js +83 -16
  67. package/dist/hooks/summary-worker.js +81 -8
  68. package/dist/index.js +595 -29
  69. package/dist/lib/agent-config.js +16 -1
  70. package/dist/lib/cloud-sync.js +45 -1
  71. package/dist/lib/consolidation.js +16 -1
  72. package/dist/lib/database.js +23 -0
  73. package/dist/lib/db.js +23 -0
  74. package/dist/lib/device-registry.js +23 -0
  75. package/dist/lib/employee-templates.js +30 -4
  76. package/dist/lib/employees.js +16 -1
  77. package/dist/lib/exe-daemon.js +482 -52
  78. package/dist/lib/hybrid-search.js +62 -12
  79. package/dist/lib/license.js +3 -3
  80. package/dist/lib/messaging.js +21 -4
  81. package/dist/lib/schedules.js +37 -2
  82. package/dist/lib/skill-learning.js +910 -41
  83. package/dist/lib/status-brief.js +14 -1
  84. package/dist/lib/store.js +62 -12
  85. package/dist/lib/tasks.js +843 -93
  86. package/dist/lib/tmux-routing.js +766 -16
  87. package/dist/mcp/server.js +238 -41
  88. package/dist/mcp/tools/create-task.js +525 -15
  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 +840 -93
  93. package/dist/runtime/index.js +913 -107
  94. package/dist/tui/App.js +227 -58
  95. package/package.json +1 -1
@@ -331,11 +331,168 @@ var init_config = __esm({
331
331
  }
332
332
  });
333
333
 
334
+ // src/lib/runtime-table.ts
335
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
336
+ var init_runtime_table = __esm({
337
+ "src/lib/runtime-table.ts"() {
338
+ "use strict";
339
+ RUNTIME_TABLE = {
340
+ codex: {
341
+ binary: "codex",
342
+ launchMode: "interactive",
343
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
344
+ inlineFlag: "--no-alt-screen",
345
+ apiKeyEnv: "OPENAI_API_KEY",
346
+ defaultModel: "gpt-5.5"
347
+ },
348
+ opencode: {
349
+ binary: "opencode",
350
+ launchMode: "exec",
351
+ autoApproveFlag: "--dangerously-skip-permissions",
352
+ inlineFlag: "",
353
+ apiKeyEnv: "ANTHROPIC_API_KEY",
354
+ defaultModel: "anthropic/claude-sonnet-4-6"
355
+ }
356
+ };
357
+ DEFAULT_RUNTIME = "claude";
358
+ }
359
+ });
360
+
361
+ // src/lib/agent-config.ts
362
+ var agent_config_exports = {};
363
+ __export(agent_config_exports, {
364
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
365
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
366
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
367
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
368
+ clearAgentRuntime: () => clearAgentRuntime,
369
+ getAgentRuntime: () => getAgentRuntime,
370
+ loadAgentConfig: () => loadAgentConfig,
371
+ saveAgentConfig: () => saveAgentConfig,
372
+ setAgentMcps: () => setAgentMcps,
373
+ setAgentRuntime: () => setAgentRuntime
374
+ });
375
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync3 } from "fs";
376
+ import path2 from "path";
377
+ function loadAgentConfig() {
378
+ if (!existsSync3(AGENT_CONFIG_PATH)) return {};
379
+ try {
380
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
381
+ } catch {
382
+ return {};
383
+ }
384
+ }
385
+ function saveAgentConfig(config) {
386
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
387
+ ensurePrivateDirSync(dir);
388
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
389
+ enforcePrivateFileSync(AGENT_CONFIG_PATH);
390
+ }
391
+ function getAgentRuntime(agentId) {
392
+ const config = loadAgentConfig();
393
+ const entry = config[agentId];
394
+ if (entry) return entry;
395
+ const orgDefault = config["default"];
396
+ if (orgDefault) return orgDefault;
397
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
398
+ }
399
+ function setAgentRuntime(agentId, runtime, model, reasoning_effort, mcps) {
400
+ const knownModels = KNOWN_RUNTIMES[runtime];
401
+ if (!knownModels) {
402
+ return {
403
+ ok: false,
404
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
405
+ };
406
+ }
407
+ if (!knownModels.includes(model)) {
408
+ return {
409
+ ok: false,
410
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
411
+ };
412
+ }
413
+ const config = loadAgentConfig();
414
+ const existing = config[agentId];
415
+ const entry = { runtime, model };
416
+ if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
417
+ if (mcps !== void 0) {
418
+ entry.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
419
+ } else if (existing?.mcps) {
420
+ entry.mcps = existing.mcps;
421
+ }
422
+ config[agentId] = entry;
423
+ saveAgentConfig(config);
424
+ return { ok: true };
425
+ }
426
+ function setAgentMcps(agentId, mcps) {
427
+ const config = loadAgentConfig();
428
+ const existing = config[agentId] ?? getAgentRuntime(agentId);
429
+ existing.mcps = mcps.includes("exe-os") ? mcps : ["exe-os", ...mcps];
430
+ config[agentId] = existing;
431
+ saveAgentConfig(config);
432
+ return { ok: true };
433
+ }
434
+ function clearAgentRuntime(agentId) {
435
+ const config = loadAgentConfig();
436
+ delete config[agentId];
437
+ saveAgentConfig(config);
438
+ }
439
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
440
+ var init_agent_config = __esm({
441
+ "src/lib/agent-config.ts"() {
442
+ "use strict";
443
+ init_config();
444
+ init_runtime_table();
445
+ init_secure_files();
446
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
447
+ KNOWN_RUNTIMES = {
448
+ claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
449
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
450
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
451
+ };
452
+ RUNTIME_LABELS = {
453
+ claude: "Claude Code (Anthropic)",
454
+ codex: "Codex (OpenAI)",
455
+ opencode: "OpenCode (open source)"
456
+ };
457
+ DEFAULT_MODELS = {
458
+ claude: "claude-opus-4.6",
459
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
460
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
461
+ };
462
+ }
463
+ });
464
+
334
465
  // src/lib/employees.ts
466
+ var employees_exports = {};
467
+ __export(employees_exports, {
468
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
469
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
470
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
471
+ addEmployee: () => addEmployee,
472
+ baseAgentName: () => baseAgentName,
473
+ canCoordinate: () => canCoordinate,
474
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
475
+ getCoordinatorName: () => getCoordinatorName,
476
+ getEmployee: () => getEmployee,
477
+ getEmployeeByRole: () => getEmployeeByRole,
478
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
479
+ hasRole: () => hasRole,
480
+ hireEmployee: () => hireEmployee,
481
+ isCoordinatorName: () => isCoordinatorName,
482
+ isCoordinatorRole: () => isCoordinatorRole,
483
+ isMultiInstance: () => isMultiInstance,
484
+ loadEmployees: () => loadEmployees,
485
+ loadEmployeesSync: () => loadEmployeesSync,
486
+ normalizeRole: () => normalizeRole,
487
+ normalizeRosterCase: () => normalizeRosterCase,
488
+ registerBinSymlinks: () => registerBinSymlinks,
489
+ saveEmployees: () => saveEmployees,
490
+ validateEmployeeName: () => validateEmployeeName
491
+ });
335
492
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
336
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
493
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
337
494
  import { execSync } from "child_process";
338
- import path2 from "path";
495
+ import path3 from "path";
339
496
  import os2 from "os";
340
497
  function normalizeRole(role) {
341
498
  return (role ?? "").trim().toLowerCase();
@@ -353,8 +510,26 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
353
510
  if (!agentName) return false;
354
511
  return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
355
512
  }
513
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
514
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
515
+ }
516
+ function validateEmployeeName(name) {
517
+ if (!name) {
518
+ return { valid: false, error: "Name is required" };
519
+ }
520
+ if (name.length > 32) {
521
+ return { valid: false, error: "Name must be 32 characters or fewer" };
522
+ }
523
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
524
+ return {
525
+ valid: false,
526
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
527
+ };
528
+ }
529
+ return { valid: true };
530
+ }
356
531
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
357
- if (!existsSync3(employeesPath)) {
532
+ if (!existsSync4(employeesPath)) {
358
533
  return [];
359
534
  }
360
535
  const raw = await readFile2(employeesPath, "utf-8");
@@ -365,13 +540,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
365
540
  }
366
541
  }
367
542
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
368
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
543
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
369
544
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
370
545
  }
371
546
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
372
- if (!existsSync3(employeesPath)) return [];
547
+ if (!existsSync4(employeesPath)) return [];
373
548
  try {
374
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
549
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
375
550
  } catch {
376
551
  return [];
377
552
  }
@@ -379,6 +554,19 @@ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
379
554
  function getEmployee(employees, name) {
380
555
  return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
381
556
  }
557
+ function getEmployeeByRole(employees, role) {
558
+ const lower = role.toLowerCase();
559
+ return employees.find((e) => e.role.toLowerCase() === lower);
560
+ }
561
+ function getEmployeeNamesByRole(employees, role) {
562
+ const lower = role.toLowerCase();
563
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
564
+ }
565
+ function hasRole(agentName, role) {
566
+ const employees = loadEmployeesSync();
567
+ const emp = getEmployee(employees, agentName);
568
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
569
+ }
382
570
  function baseAgentName(name, employees) {
383
571
  const match = name.match(/^([a-zA-Z]+)\d+$/);
384
572
  if (!match) return name;
@@ -393,6 +581,90 @@ function isMultiInstance(agentName, employees) {
393
581
  if (!emp) return false;
394
582
  return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
395
583
  }
584
+ function addEmployee(employees, employee) {
585
+ const { systemPrompt: _legacyPrompt, ...rest } = employee;
586
+ const normalized = { ...rest, name: employee.name.toLowerCase() };
587
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
588
+ throw new Error(`Employee '${normalized.name}' already exists`);
589
+ }
590
+ return [...employees, normalized];
591
+ }
592
+ function appendToCoordinatorTeam(employee) {
593
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
594
+ if (!coordinator) return;
595
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
596
+ if (!existsSync4(idPath)) return;
597
+ const content = readFileSync3(idPath, "utf-8");
598
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
599
+ const teamMatch = content.match(TEAM_SECTION_RE);
600
+ if (!teamMatch || teamMatch.index === void 0) return;
601
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
602
+ const nextHeading = afterTeam.match(/\n## /);
603
+ const entry = `
604
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
605
+ `;
606
+ let updated;
607
+ if (nextHeading && nextHeading.index !== void 0) {
608
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
609
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
610
+ } else {
611
+ updated = content.trimEnd() + "\n" + entry;
612
+ }
613
+ writeFileSync2(idPath, updated, "utf-8");
614
+ }
615
+ function capitalize(s) {
616
+ return s.charAt(0).toUpperCase() + s.slice(1);
617
+ }
618
+ async function hireEmployee(employee) {
619
+ const employees = await loadEmployees();
620
+ const updated = addEmployee(employees, employee);
621
+ await saveEmployees(updated);
622
+ try {
623
+ appendToCoordinatorTeam(employee);
624
+ } catch {
625
+ }
626
+ try {
627
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
628
+ const config = loadAgentConfig2();
629
+ const name = employee.name.toLowerCase();
630
+ if (!config[name] && config["default"]) {
631
+ config[name] = { ...config["default"] };
632
+ saveAgentConfig2(config);
633
+ }
634
+ } catch {
635
+ }
636
+ return updated;
637
+ }
638
+ async function normalizeRosterCase(rosterPath) {
639
+ const employees = await loadEmployees(rosterPath);
640
+ let changed = false;
641
+ for (const emp of employees) {
642
+ if (emp.name !== emp.name.toLowerCase()) {
643
+ const oldName = emp.name;
644
+ emp.name = emp.name.toLowerCase();
645
+ changed = true;
646
+ try {
647
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
648
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
649
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
650
+ if (existsSync4(oldPath) && !existsSync4(newPath)) {
651
+ renameSync2(oldPath, newPath);
652
+ } else if (existsSync4(oldPath) && oldPath !== newPath) {
653
+ const content = readFileSync3(oldPath, "utf-8");
654
+ writeFileSync2(newPath, content, "utf-8");
655
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
656
+ unlinkSync(oldPath);
657
+ }
658
+ }
659
+ } catch {
660
+ }
661
+ }
662
+ }
663
+ if (changed) {
664
+ await saveEmployees(employees, rosterPath);
665
+ }
666
+ return changed;
667
+ }
396
668
  function findExeBin() {
397
669
  try {
398
670
  return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
@@ -409,7 +681,7 @@ function registerBinSymlinks(name) {
409
681
  errors.push("Could not find 'exe-os' in PATH");
410
682
  return { created, skipped, errors };
411
683
  }
412
- const binDir = path2.dirname(exeBinPath);
684
+ const binDir = path3.dirname(exeBinPath);
413
685
  let target;
414
686
  try {
415
687
  target = readlinkSync(exeBinPath);
@@ -419,8 +691,8 @@ function registerBinSymlinks(name) {
419
691
  }
420
692
  for (const suffix of ["", "-opencode"]) {
421
693
  const linkName = `${name}${suffix}`;
422
- const linkPath = path2.join(binDir, linkName);
423
- if (existsSync3(linkPath)) {
694
+ const linkPath = path3.join(binDir, linkName);
695
+ if (existsSync4(linkPath)) {
424
696
  skipped.push(linkName);
425
697
  continue;
426
698
  }
@@ -433,16 +705,17 @@ function registerBinSymlinks(name) {
433
705
  }
434
706
  return { created, skipped, errors };
435
707
  }
436
- var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR;
708
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
437
709
  var init_employees = __esm({
438
710
  "src/lib/employees.ts"() {
439
711
  "use strict";
440
712
  init_config();
441
- EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
713
+ EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
442
714
  DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
443
715
  COORDINATOR_ROLE = "COO";
444
716
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
445
- IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
717
+ IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
718
+ TEAM_SECTION_RE = /^## Team\b.*$/m;
446
719
  }
447
720
  });
448
721
 
@@ -503,7 +776,7 @@ var init_db_retry = __esm({
503
776
 
504
777
  // src/lib/database-adapter.ts
505
778
  import os3 from "os";
506
- import path3 from "path";
779
+ import path4 from "path";
507
780
  import { createRequire } from "module";
508
781
  import { pathToFileURL } from "url";
509
782
  function quotedIdentifier(identifier) {
@@ -814,8 +1087,8 @@ async function loadPrismaClient() {
814
1087
  }
815
1088
  return new PrismaClient2();
816
1089
  }
817
- const exeDbRoot = process.env.EXE_DB_ROOT ?? path3.join(os3.homedir(), "exe-db");
818
- const requireFromExeDb = createRequire(path3.join(exeDbRoot, "package.json"));
1090
+ const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os3.homedir(), "exe-db");
1091
+ const requireFromExeDb = createRequire(path4.join(exeDbRoot, "package.json"));
819
1092
  const prismaEntry = requireFromExeDb.resolve("@prisma/client");
820
1093
  const module = await import(pathToFileURL(prismaEntry).href);
821
1094
  const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
@@ -1096,8 +1369,8 @@ var init_memory = __esm({
1096
1369
 
1097
1370
  // src/lib/daemon-auth.ts
1098
1371
  import crypto from "crypto";
1099
- import path4 from "path";
1100
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
1372
+ import path5 from "path";
1373
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1101
1374
  function normalizeToken(token) {
1102
1375
  if (!token) return null;
1103
1376
  const trimmed = token.trim();
@@ -1105,8 +1378,8 @@ function normalizeToken(token) {
1105
1378
  }
1106
1379
  function readDaemonToken() {
1107
1380
  try {
1108
- if (!existsSync4(DAEMON_TOKEN_PATH)) return null;
1109
- return normalizeToken(readFileSync3(DAEMON_TOKEN_PATH, "utf8"));
1381
+ if (!existsSync5(DAEMON_TOKEN_PATH)) return null;
1382
+ return normalizeToken(readFileSync4(DAEMON_TOKEN_PATH, "utf8"));
1110
1383
  } catch {
1111
1384
  return null;
1112
1385
  }
@@ -1116,7 +1389,7 @@ function ensureDaemonToken(seed) {
1116
1389
  if (existing) return existing;
1117
1390
  const token = normalizeToken(seed) ?? crypto.randomBytes(32).toString("hex");
1118
1391
  ensurePrivateDirSync(EXE_AI_DIR);
1119
- writeFileSync2(DAEMON_TOKEN_PATH, `${token}
1392
+ writeFileSync3(DAEMON_TOKEN_PATH, `${token}
1120
1393
  `, "utf8");
1121
1394
  enforcePrivateFileSync(DAEMON_TOKEN_PATH);
1122
1395
  return token;
@@ -1127,7 +1400,7 @@ var init_daemon_auth = __esm({
1127
1400
  "use strict";
1128
1401
  init_config();
1129
1402
  init_secure_files();
1130
- DAEMON_TOKEN_PATH = path4.join(EXE_AI_DIR, "exed.token");
1403
+ DAEMON_TOKEN_PATH = path5.join(EXE_AI_DIR, "exed.token");
1131
1404
  }
1132
1405
  });
1133
1406
 
@@ -1136,8 +1409,8 @@ import net from "net";
1136
1409
  import os4 from "os";
1137
1410
  import { spawn, execSync as execSync2 } from "child_process";
1138
1411
  import { randomUUID } from "crypto";
1139
- import { existsSync as existsSync5, unlinkSync as unlinkSync2, readFileSync as readFileSync4, openSync, closeSync, statSync } from "fs";
1140
- import path5 from "path";
1412
+ import { existsSync as existsSync6, unlinkSync as unlinkSync2, readFileSync as readFileSync5, openSync, closeSync, statSync } from "fs";
1413
+ import path6 from "path";
1141
1414
  import { fileURLToPath } from "url";
1142
1415
  function handleData(chunk) {
1143
1416
  _buffer += chunk.toString();
@@ -1173,9 +1446,9 @@ function isZombie(pid) {
1173
1446
  }
1174
1447
  }
1175
1448
  function cleanupStaleFiles() {
1176
- if (existsSync5(PID_PATH)) {
1449
+ if (existsSync6(PID_PATH)) {
1177
1450
  try {
1178
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1451
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1179
1452
  if (pid > 0) {
1180
1453
  try {
1181
1454
  process.kill(pid, 0);
@@ -1200,11 +1473,11 @@ function cleanupStaleFiles() {
1200
1473
  }
1201
1474
  }
1202
1475
  function findPackageRoot() {
1203
- let dir = path5.dirname(fileURLToPath(import.meta.url));
1204
- const { root } = path5.parse(dir);
1476
+ let dir = path6.dirname(fileURLToPath(import.meta.url));
1477
+ const { root } = path6.parse(dir);
1205
1478
  while (dir !== root) {
1206
- if (existsSync5(path5.join(dir, "package.json"))) return dir;
1207
- dir = path5.dirname(dir);
1479
+ if (existsSync6(path6.join(dir, "package.json"))) return dir;
1480
+ dir = path6.dirname(dir);
1208
1481
  }
1209
1482
  return null;
1210
1483
  }
@@ -1222,8 +1495,8 @@ function spawnDaemon() {
1222
1495
  process.stderr.write("[exed-client] WARN: cannot find package root\n");
1223
1496
  return;
1224
1497
  }
1225
- const daemonPath = path5.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1226
- if (!existsSync5(daemonPath)) {
1498
+ const daemonPath = path6.join(pkgRoot, "dist", "lib", "exe-daemon.js");
1499
+ if (!existsSync6(daemonPath)) {
1227
1500
  process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
1228
1501
  `);
1229
1502
  return;
@@ -1232,7 +1505,7 @@ function spawnDaemon() {
1232
1505
  const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
1233
1506
  process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
1234
1507
  `);
1235
- const logPath = path5.join(path5.dirname(SOCKET_PATH), "exed.log");
1508
+ const logPath = path6.join(path6.dirname(SOCKET_PATH), "exed.log");
1236
1509
  let stderrFd = "ignore";
1237
1510
  try {
1238
1511
  stderrFd = openSync(logPath, "a");
@@ -1397,9 +1670,9 @@ function killAndRespawnDaemon() {
1397
1670
  }
1398
1671
  try {
1399
1672
  process.stderr.write("[exed-client] Killing daemon for restart...\n");
1400
- if (existsSync5(PID_PATH)) {
1673
+ if (existsSync6(PID_PATH)) {
1401
1674
  try {
1402
- const pid = parseInt(readFileSync4(PID_PATH, "utf8").trim(), 10);
1675
+ const pid = parseInt(readFileSync5(PID_PATH, "utf8").trim(), 10);
1403
1676
  if (pid > 0) {
1404
1677
  try {
1405
1678
  process.kill(pid, "SIGKILL");
@@ -1525,9 +1798,9 @@ var init_exe_daemon_client = __esm({
1525
1798
  "use strict";
1526
1799
  init_config();
1527
1800
  init_daemon_auth();
1528
- SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path5.join(EXE_AI_DIR, "exed.sock");
1529
- PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path5.join(EXE_AI_DIR, "exed.pid");
1530
- SPAWN_LOCK_PATH = path5.join(EXE_AI_DIR, "exed-spawn.lock");
1801
+ SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path6.join(EXE_AI_DIR, "exed.sock");
1802
+ PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path6.join(EXE_AI_DIR, "exed.pid");
1803
+ SPAWN_LOCK_PATH = path6.join(EXE_AI_DIR, "exed-spawn.lock");
1531
1804
  SPAWN_LOCK_STALE_MS = 3e4;
1532
1805
  CONNECT_TIMEOUT_MS = 15e3;
1533
1806
  REQUEST_TIMEOUT_MS = 3e4;
@@ -1760,7 +2033,7 @@ __export(database_exports, {
1760
2033
  isInitialized: () => isInitialized,
1761
2034
  setExternalClient: () => setExternalClient
1762
2035
  });
1763
- 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";
2036
+ 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";
1764
2037
  import { createClient } from "@libsql/client";
1765
2038
  import { homedir } from "os";
1766
2039
  import { join } from "path";
@@ -1813,11 +2086,11 @@ function releaseDbLock() {
1813
2086
  }
1814
2087
  async function initDatabase(config) {
1815
2088
  acquireDbLock();
1816
- if (existsSync6(config.dbPath)) {
2089
+ if (existsSync7(config.dbPath)) {
1817
2090
  const dbStat = statSync2(config.dbPath);
1818
2091
  if (dbStat.size === 0) {
1819
2092
  const walPath = config.dbPath + "-wal";
1820
- if (existsSync6(walPath) && statSync2(walPath).size > 0) {
2093
+ if (existsSync7(walPath) && statSync2(walPath).size > 0) {
1821
2094
  const backupPath = config.dbPath + ".zeroed-" + Date.now();
1822
2095
  copyFileSync(config.dbPath, backupPath);
1823
2096
  unlinkSync3(config.dbPath);
@@ -2113,6 +2386,13 @@ async function ensureSchema() {
2113
2386
  } catch (e) {
2114
2387
  logCatchDebug("migration", e);
2115
2388
  }
2389
+ for (const col of ["created_by_agent TEXT", "created_by_device TEXT", "source_session_id TEXT"]) {
2390
+ try {
2391
+ await client.execute({ sql: `ALTER TABLE behaviors ADD COLUMN ${col}`, args: [] });
2392
+ } catch (e) {
2393
+ logCatchDebug("migration", e);
2394
+ }
2395
+ }
2116
2396
  try {
2117
2397
  await client.execute({
2118
2398
  sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
@@ -3329,6 +3609,22 @@ async function ensureSchema() {
3329
3609
  } catch (e) {
3330
3610
  logCatchDebug("migration", e);
3331
3611
  }
3612
+ try {
3613
+ await client.execute({
3614
+ sql: `ALTER TABLE memories ADD COLUMN visibility TEXT DEFAULT 'private'`,
3615
+ args: []
3616
+ });
3617
+ } catch (e) {
3618
+ logCatchDebug("migration", e);
3619
+ }
3620
+ try {
3621
+ await client.execute({
3622
+ sql: `ALTER TABLE memories ADD COLUMN strength REAL DEFAULT 1.0`,
3623
+ args: []
3624
+ });
3625
+ } catch (e) {
3626
+ logCatchDebug("migration", e);
3627
+ }
3332
3628
  }
3333
3629
  async function disposeDatabase() {
3334
3630
  if (_walCheckpointTimer) {
@@ -3431,11 +3727,17 @@ var init_platform_procedures = __esm({
3431
3727
  content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3432
3728
  },
3433
3729
  {
3434
- title: "Customer orchestration maturity \u2014 recommend, never trap",
3730
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
3435
3731
  domain: "workflow",
3436
3732
  priority: "p1",
3437
3733
  content: "New customers start best in Phase 1: founder \u2194 coordinator/Chief of Staff, building company context. Suggest Phase 2 executives when domain work repeats; suggest Phase 3 parallel execution only when review/permission gates are ready. This is guidance, not a blocker: users may jump phases anytime. Never overwrite their phase, role titles, identities, or custom org design."
3438
3734
  },
3735
+ {
3736
+ title: "Routing slot vs display title \u2014 internal 'coo' is plumbing, not your name",
3737
+ domain: "identity",
3738
+ priority: "p0",
3739
+ content: "These procedures reference 'COO' as a shorthand for the coordinator role. This is an INTERNAL routing slot used by exe-os code (chain-of-command checks, dispatch logic, session detection). It is NOT your display title. Your actual title comes from your identity file's `title:` field \u2014 that is what you use externally: introductions, sign-offs, team comms, and any user-facing text. If your identity says `title: AI Chief of Staff`, you are the AI Chief of Staff. The routing slot stays `role: coo` for code compatibility \u2014 never rename it, but also never introduce yourself as 'COO' unless your identity file explicitly says so. The founder chose your title; respect it."
3740
+ },
3439
3741
  {
3440
3742
  title: "Single dispatch path \u2014 create_task only",
3441
3743
  domain: "workflow",
@@ -3469,6 +3771,12 @@ var init_platform_procedures = __esm({
3469
3771
  priority: "p0",
3470
3772
  content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
3471
3773
  },
3774
+ {
3775
+ title: "Destructive operations \u2014 mandatory reviewer gate",
3776
+ domain: "security",
3777
+ priority: "p0",
3778
+ content: "Before ANY destructive operation (delete, remove, overwrite, drop, reset, force-push, truncate), you MUST: (1) Have your full task spec accessible \u2014 if you cannot read it, STOP and report to your reviewer. Never improvise destructive actions. (2) Confirm with your reviewer (assigned_by or COO) before executing. (3) If the task spec explicitly authorizes the operation, proceed \u2014 but log it. Violation = immediate task failure. This applies to ALL agents regardless of role."
3779
+ },
3472
3780
  {
3473
3781
  title: "Customer patch triage \u2014 upstream bug vs customization",
3474
3782
  domain: "support",
@@ -3620,7 +3928,7 @@ var init_platform_procedures = __esm({
3620
3928
  title: "MCP tool dispatch \u2014 all tools use action parameter",
3621
3929
  domain: "tool-use",
3622
3930
  priority: "p0",
3623
- content: 'exe-os MCP tools come in two surfaces depending on EXE_MCP_TOOL_SURFACE config. Consolidated (19 tools): action-based dispatch \u2014 memory(action="recall"), task(action="create"), etc. Legacy (108 tools): one tool per operation \u2014 recall_my_memory, create_task, etc. Both surfaces have identical functionality. Use whichever tool names are available in your session. If you see domain tools (memory, task, config, etc.), use the action parameter. If you see specific tools (recall_my_memory, create_task, etc.), call them directly.'
3931
+ content: 'exe-os MCP tools use consolidated action-based dispatch by default (19 tools). Call domain tools with an action parameter: memory(action="recall"), task(action="create"), config(action="list_employees"), etc. Legacy mode (108 separate tools like recall_my_memory, create_task) is still available via EXE_MCP_TOOL_SURFACE=legacy but will be removed in a future version. If you see specific tool names, call them directly \u2014 both surfaces are identical. Consolidated is the default and recommended surface.'
3624
3932
  },
3625
3933
  {
3626
3934
  title: "MCP tools \u2014 memory, decision, and search",
@@ -3753,15 +4061,15 @@ __export(keychain_exports, {
3753
4061
  setMasterKey: () => setMasterKey
3754
4062
  });
3755
4063
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3756
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
4064
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3757
4065
  import { execSync as execSync3 } from "child_process";
3758
- import path6 from "path";
4066
+ import path7 from "path";
3759
4067
  import os5 from "os";
3760
4068
  function getKeyDir() {
3761
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path6.join(os5.homedir(), ".exe-os");
4069
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path7.join(os5.homedir(), ".exe-os");
3762
4070
  }
3763
4071
  function getKeyPath() {
3764
- return path6.join(getKeyDir(), "master.key");
4072
+ return path7.join(getKeyDir(), "master.key");
3765
4073
  }
3766
4074
  function nativeKeychainAllowed() {
3767
4075
  return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
@@ -3792,7 +4100,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
3792
4100
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3793
4101
  if (uid === 0) return true;
3794
4102
  const exeOsDir = process.env.EXE_OS_DIR;
3795
- return Boolean(exeOsDir && path6.resolve(keyPath).startsWith(path6.resolve(exeOsDir) + path6.sep));
4103
+ return Boolean(exeOsDir && path7.resolve(keyPath).startsWith(path7.resolve(exeOsDir) + path7.sep));
3796
4104
  } catch {
3797
4105
  return false;
3798
4106
  }
@@ -3989,7 +4297,7 @@ async function getMasterKey() {
3989
4297
  }
3990
4298
  }
3991
4299
  const keyPath = getKeyPath();
3992
- if (!existsSync7(keyPath)) {
4300
+ if (!existsSync8(keyPath)) {
3993
4301
  process.stderr.write(
3994
4302
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
3995
4303
  `
@@ -4097,7 +4405,7 @@ async function getKeyStorageInfo() {
4097
4405
  }
4098
4406
  }
4099
4407
  const keyPath = getKeyPath();
4100
- if (!existsSync7(keyPath)) {
4408
+ if (!existsSync8(keyPath)) {
4101
4409
  return {
4102
4410
  kind: "missing",
4103
4411
  secure: false,
@@ -4193,7 +4501,7 @@ async function deleteMasterKey() {
4193
4501
  }
4194
4502
  }
4195
4503
  const keyPath = getKeyPath();
4196
- if (existsSync7(keyPath)) {
4504
+ if (existsSync8(keyPath)) {
4197
4505
  await unlink(keyPath);
4198
4506
  }
4199
4507
  }
@@ -4316,14 +4624,14 @@ __export(shard_manager_exports, {
4316
4624
  listShards: () => listShards,
4317
4625
  shardExists: () => shardExists
4318
4626
  });
4319
- import path7 from "path";
4320
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
4627
+ import path8 from "path";
4628
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readdirSync, renameSync as renameSync3, statSync as statSync4 } from "fs";
4321
4629
  import { createClient as createClient2 } from "@libsql/client";
4322
4630
  function initShardManager(encryptionKey) {
4323
4631
  _encryptionKey = encryptionKey;
4324
4632
  _keyValidated = false;
4325
4633
  _keyValidationPromise = null;
4326
- if (!existsSync8(SHARDS_DIR)) {
4634
+ if (!existsSync9(SHARDS_DIR)) {
4327
4635
  mkdirSync3(SHARDS_DIR, { recursive: true });
4328
4636
  }
4329
4637
  const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
@@ -4344,7 +4652,7 @@ async function validateEncryptionKey() {
4344
4652
  return true;
4345
4653
  }
4346
4654
  for (const shardFile of existingShards.slice(0, 3)) {
4347
- const dbPath = path7.join(SHARDS_DIR, shardFile);
4655
+ const dbPath = path8.join(SHARDS_DIR, shardFile);
4348
4656
  const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
4349
4657
  try {
4350
4658
  await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
@@ -4387,7 +4695,7 @@ function getShardClient(projectName) {
4387
4695
  while (_shards.size >= MAX_OPEN_SHARDS) {
4388
4696
  evictLRU();
4389
4697
  }
4390
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4698
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4391
4699
  const client = createClient2({
4392
4700
  url: `file:${dbPath}`,
4393
4701
  encryptionKey: _encryptionKey
@@ -4398,13 +4706,13 @@ function getShardClient(projectName) {
4398
4706
  }
4399
4707
  function shardExists(projectName) {
4400
4708
  const safeName = safeShardName(projectName);
4401
- return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
4709
+ return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
4402
4710
  }
4403
4711
  function safeShardName(projectName) {
4404
4712
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4405
4713
  }
4406
4714
  function listShards() {
4407
- if (!existsSync8(SHARDS_DIR)) return [];
4715
+ if (!existsSync9(SHARDS_DIR)) return [];
4408
4716
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4409
4717
  }
4410
4718
  async function auditShardHealth(options = {}) {
@@ -4416,7 +4724,7 @@ async function auditShardHealth(options = {}) {
4416
4724
  const names = listShards();
4417
4725
  const shards = [];
4418
4726
  for (const name of names) {
4419
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
4727
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
4420
4728
  const stat = statSync4(dbPath);
4421
4729
  const item = {
4422
4730
  name,
@@ -4451,7 +4759,7 @@ async function auditShardHealth(options = {}) {
4451
4759
  _shards.delete(name);
4452
4760
  _shardLastAccess.delete(name);
4453
4761
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4454
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4762
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4455
4763
  renameSync3(dbPath, archivedPath);
4456
4764
  item.archivedPath = archivedPath;
4457
4765
  }
@@ -4679,11 +4987,11 @@ async function getReadyShardClient(projectName) {
4679
4987
  client.close();
4680
4988
  _shards.delete(safeName);
4681
4989
  _shardLastAccess.delete(safeName);
4682
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4683
- if (existsSync8(dbPath)) {
4990
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4991
+ if (existsSync9(dbPath)) {
4684
4992
  const stat = statSync4(dbPath);
4685
4993
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4686
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4994
+ const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4687
4995
  renameSync3(dbPath, archivedPath);
4688
4996
  process.stderr.write(
4689
4997
  `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
@@ -4751,7 +5059,7 @@ var init_shard_manager = __esm({
4751
5059
  "src/lib/shard-manager.ts"() {
4752
5060
  "use strict";
4753
5061
  init_config();
4754
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
5062
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
4755
5063
  SHARD_IDLE_MS = 5 * 60 * 1e3;
4756
5064
  MAX_OPEN_SHARDS = 10;
4757
5065
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -4880,13 +5188,13 @@ __export(session_registry_exports, {
4880
5188
  refreshSessionProject: () => refreshSessionProject,
4881
5189
  registerSession: () => registerSession
4882
5190
  });
4883
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
5191
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
4884
5192
  import { execSync as execSync4 } from "child_process";
4885
- import path8 from "path";
5193
+ import path9 from "path";
4886
5194
  import os6 from "os";
4887
5195
  function registerSession(entry) {
4888
- const dir = path8.dirname(REGISTRY_PATH);
4889
- if (!existsSync9(dir)) {
5196
+ const dir = path9.dirname(REGISTRY_PATH);
5197
+ if (!existsSync10(dir)) {
4890
5198
  mkdirSync4(dir, { recursive: true });
4891
5199
  }
4892
5200
  const sessions = listSessions();
@@ -4896,7 +5204,7 @@ function registerSession(entry) {
4896
5204
  } else {
4897
5205
  sessions.push(entry);
4898
5206
  }
4899
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5207
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
4900
5208
  }
4901
5209
  function refreshSessionProject(windowName, projectDir) {
4902
5210
  const sessions = listSessions();
@@ -4904,13 +5212,13 @@ function refreshSessionProject(windowName, projectDir) {
4904
5212
  if (!entry || entry.projectDir === projectDir) return;
4905
5213
  entry.projectDir = projectDir;
4906
5214
  try {
4907
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5215
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
4908
5216
  } catch {
4909
5217
  }
4910
5218
  }
4911
5219
  function listSessions() {
4912
5220
  try {
4913
- const raw = readFileSync5(REGISTRY_PATH, "utf8");
5221
+ const raw = readFileSync6(REGISTRY_PATH, "utf8");
4914
5222
  return JSON.parse(raw);
4915
5223
  } catch {
4916
5224
  return [];
@@ -4931,7 +5239,7 @@ function pruneStaleSessions() {
4931
5239
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
4932
5240
  const pruned = sessions.length - alive.length;
4933
5241
  if (pruned > 0) {
4934
- writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
5242
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(alive, null, 2));
4935
5243
  }
4936
5244
  return pruned;
4937
5245
  }
@@ -4939,7 +5247,7 @@ var REGISTRY_PATH;
4939
5247
  var init_session_registry = __esm({
4940
5248
  "src/lib/session-registry.ts"() {
4941
5249
  "use strict";
4942
- REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
5250
+ REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
4943
5251
  }
4944
5252
  });
4945
5253
 
@@ -5201,68 +5509,6 @@ var init_provider_table = __esm({
5201
5509
  }
5202
5510
  });
5203
5511
 
5204
- // src/lib/runtime-table.ts
5205
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
5206
- var init_runtime_table = __esm({
5207
- "src/lib/runtime-table.ts"() {
5208
- "use strict";
5209
- RUNTIME_TABLE = {
5210
- codex: {
5211
- binary: "codex",
5212
- launchMode: "interactive",
5213
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
5214
- inlineFlag: "--no-alt-screen",
5215
- apiKeyEnv: "OPENAI_API_KEY",
5216
- defaultModel: "gpt-5.5"
5217
- },
5218
- opencode: {
5219
- binary: "opencode",
5220
- launchMode: "exec",
5221
- autoApproveFlag: "--dangerously-skip-permissions",
5222
- inlineFlag: "",
5223
- apiKeyEnv: "ANTHROPIC_API_KEY",
5224
- defaultModel: "anthropic/claude-sonnet-4-6"
5225
- }
5226
- };
5227
- DEFAULT_RUNTIME = "claude";
5228
- }
5229
- });
5230
-
5231
- // src/lib/agent-config.ts
5232
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
5233
- import path9 from "path";
5234
- function loadAgentConfig() {
5235
- if (!existsSync10(AGENT_CONFIG_PATH)) return {};
5236
- try {
5237
- return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
5238
- } catch {
5239
- return {};
5240
- }
5241
- }
5242
- function getAgentRuntime(agentId) {
5243
- const config = loadAgentConfig();
5244
- const entry = config[agentId];
5245
- if (entry) return entry;
5246
- const orgDefault = config["default"];
5247
- if (orgDefault) return orgDefault;
5248
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
5249
- }
5250
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
5251
- var init_agent_config = __esm({
5252
- "src/lib/agent-config.ts"() {
5253
- "use strict";
5254
- init_config();
5255
- init_runtime_table();
5256
- init_secure_files();
5257
- AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
5258
- DEFAULT_MODELS = {
5259
- claude: "claude-opus-4.6",
5260
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
5261
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
5262
- };
5263
- }
5264
- });
5265
-
5266
5512
  // src/lib/intercom-queue.ts
5267
5513
  var intercom_queue_exports = {};
5268
5514
  __export(intercom_queue_exports, {
@@ -5757,7 +6003,7 @@ async function assertVpsLicense(opts) {
5757
6003
  }
5758
6004
  if (!transientFailure) {
5759
6005
  throw new Error(
5760
- "License validation failed: unknown backend state. Restore network connectivity to https://askexe.com/cloud and retry."
6006
+ "License validation failed: unknown backend state. Restore network connectivity to https://cloud.askexe.com and retry."
5761
6007
  );
5762
6008
  }
5763
6009
  const fresh = await getCachedLicense();
@@ -5794,7 +6040,7 @@ async function assertVpsLicense(opts) {
5794
6040
  } catch {
5795
6041
  }
5796
6042
  throw new Error(
5797
- `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.`
6043
+ `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.`
5798
6044
  );
5799
6045
  }
5800
6046
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -5826,7 +6072,7 @@ var init_license = __esm({
5826
6072
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
5827
6073
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
5828
6074
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
5829
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
6075
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
5830
6076
  RETRY_DELAY_MS = 500;
5831
6077
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
5832
6078
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -6483,6 +6729,19 @@ async function resolveTask(client, identifier, scopeSession) {
6483
6729
  args: [identifier, ...scope.args]
6484
6730
  });
6485
6731
  if (result.rows.length === 1) return result.rows[0];
6732
+ if (/^[a-f0-9]{7,12}$/i.test(identifier)) {
6733
+ result = await client.execute({
6734
+ sql: `SELECT * FROM tasks WHERE id LIKE ?`,
6735
+ args: [`${identifier}%`]
6736
+ });
6737
+ if (result.rows.length === 1) return result.rows[0];
6738
+ if (result.rows.length > 1) {
6739
+ const matches = result.rows.map((r) => `${String(r.id)} "${String(r.title)}" (${String(r.status)})`).join(", ");
6740
+ throw new Error(
6741
+ `Multiple tasks match short-ID "${identifier}": ${matches}. Use a longer prefix to disambiguate.`
6742
+ );
6743
+ }
6744
+ }
6486
6745
  result = await client.execute({
6487
6746
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
6488
6747
  args: [`%${identifier}%`, ...scope.args]
@@ -7337,12 +7596,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
7337
7596
  WHERE blocked_by = ? AND status = 'blocked'`,
7338
7597
  args: [now, taskId]
7339
7598
  });
7340
- if (baseDir && unblocked.rowsAffected > 0) {
7341
- const ubScope = sessionScopeFilter();
7342
- const unblockedRows = await client.execute({
7343
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
7344
- args: [now, ...ubScope.args]
7345
- });
7599
+ if (unblocked.rowsAffected === 0) return;
7600
+ const ubScope = sessionScopeFilter();
7601
+ const unblockedRows = await client.execute({
7602
+ sql: `SELECT id, title, assigned_to, priority, task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
7603
+ args: [now, ...ubScope.args]
7604
+ });
7605
+ if (baseDir) {
7346
7606
  for (const ur of unblockedRows.rows) {
7347
7607
  try {
7348
7608
  const ubFile = path18.join(baseDir, String(ur.task_file));
@@ -7354,6 +7614,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
7354
7614
  }
7355
7615
  }
7356
7616
  }
7617
+ if (unblockedRows.rows.length > 0 && !process.env.VITEST) {
7618
+ try {
7619
+ const { queueIntercom: queueIntercom2 } = await Promise.resolve().then(() => (init_intercom_queue(), intercom_queue_exports));
7620
+ const dispatched = /* @__PURE__ */ new Set();
7621
+ for (const ur of unblockedRows.rows) {
7622
+ const assignee = String(ur.assigned_to);
7623
+ if (dispatched.has(assignee)) continue;
7624
+ dispatched.add(assignee);
7625
+ queueIntercom2(`${assignee}`, `unblocked: "${String(ur.title)}" is now ready`);
7626
+ }
7627
+ } catch {
7628
+ }
7629
+ }
7357
7630
  }
7358
7631
  async function findNextTask(assignedTo) {
7359
7632
  const client = getClient();
@@ -7563,6 +7836,15 @@ var init_embedder = __esm({
7563
7836
  // src/lib/behaviors.ts
7564
7837
  import crypto5 from "crypto";
7565
7838
  async function storeBehavior(opts) {
7839
+ try {
7840
+ const { loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
7841
+ const roster = loadEmployeesSync2();
7842
+ if (roster.length > 0 && !roster.some((e) => e.name === opts.agentId)) {
7843
+ throw new Error(`Agent "${opts.agentId}" not found in roster. Cannot store behavior for unregistered agent.`);
7844
+ }
7845
+ } catch (e) {
7846
+ if (e instanceof Error && e.message.includes("not found in roster")) throw e;
7847
+ }
7566
7848
  const client = getClient();
7567
7849
  const id = crypto5.randomUUID();
7568
7850
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7573,10 +7855,18 @@ async function storeBehavior(opts) {
7573
7855
  vector = new Float32Array(vec);
7574
7856
  } catch {
7575
7857
  }
7858
+ let createdByDevice = null;
7859
+ try {
7860
+ const { loadDeviceId: loadDeviceId2 } = await Promise.resolve().then(() => (init_license(), license_exports));
7861
+ createdByDevice = loadDeviceId2() ?? null;
7862
+ } catch {
7863
+ }
7864
+ const createdByAgent = process.env.AGENT_ID ?? null;
7865
+ const sourceSessionId = process.env.CLAUDE_SESSION_ID ?? process.env.SESSION_ID ?? null;
7576
7866
  await client.execute({
7577
- sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector)
7578
- VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?)`,
7579
- args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null]
7867
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at, vector, created_by_agent, created_by_device, source_session_id)
7868
+ VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)`,
7869
+ args: [id, opts.agentId, opts.projectName ?? null, opts.domain ?? null, opts.priority ?? "p1", opts.content, now, now, vector ? vector.buffer : null, createdByAgent, createdByDevice, sourceSessionId]
7580
7870
  });
7581
7871
  return id;
7582
7872
  }
@@ -8008,6 +8298,12 @@ async function updateTask(input) {
8008
8298
  }
8009
8299
  }
8010
8300
  }
8301
+ if (input.status === "cancelled") {
8302
+ try {
8303
+ await cascadeUnblock(taskId, input.baseDir, now);
8304
+ } catch {
8305
+ }
8306
+ }
8011
8307
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
8012
8308
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
8013
8309
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -8539,11 +8835,12 @@ function getDispatchedBy(sessionKey) {
8539
8835
  }
8540
8836
  }
8541
8837
  function resolveExeSession() {
8838
+ if (process.env.EXE_SESSION_NAME) {
8839
+ const fromEnv = extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
8840
+ if (fromEnv) return fromEnv;
8841
+ }
8542
8842
  const mySession = getMySession();
8543
8843
  if (!mySession) {
8544
- if (process.env.EXE_SESSION_NAME) {
8545
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
8546
- }
8547
8844
  return null;
8548
8845
  }
8549
8846
  const fromSessionName = extractRootExe(mySession);
@@ -8558,6 +8855,10 @@ function resolveExeSession() {
8558
8855
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
8559
8856
  `
8560
8857
  );
8858
+ try {
8859
+ registerParentExe(key, fromSessionName);
8860
+ } catch {
8861
+ }
8561
8862
  candidate = fromSessionName;
8562
8863
  } else {
8563
8864
  candidate = fromCache;
@@ -10246,6 +10547,27 @@ async function cloudSync(config) {
10246
10547
  if (stmts.length > 0) await client.batch(stmts, "write");
10247
10548
  pulled = pullResult.records.length;
10248
10549
  } else {
10550
+ try {
10551
+ const incomingIds = pullResult.records.map((r) => sqlSafe(r.id));
10552
+ if (incomingIds.length > 0) {
10553
+ const ph = incomingIds.map(() => "?").join(",");
10554
+ const existing = await client.execute({
10555
+ sql: `SELECT id, version, timestamp FROM memories WHERE id IN (${ph})`,
10556
+ args: incomingIds
10557
+ });
10558
+ const localMap = new Map(existing.rows.map((r) => [String(r.id), r]));
10559
+ for (const rec of pullResult.records) {
10560
+ const local = localMap.get(String(rec.id));
10561
+ if (local && Number(local.version) > 0 && Number(local.version) !== Number(rec.version ?? 0)) {
10562
+ process.stderr.write(
10563
+ `[cloud-sync] CONFLICT: memory ${String(rec.id).slice(0, 8)} \u2014 local v${local.version} vs remote v${rec.version ?? 0}. Remote wins (LWW).
10564
+ `
10565
+ );
10566
+ }
10567
+ }
10568
+ }
10569
+ } catch {
10570
+ }
10249
10571
  const stmts = pullResult.records.map((rec) => ({
10250
10572
  sql: `INSERT OR REPLACE INTO memories
10251
10573
  (id, agent_id, agent_role, session_id, timestamp,
@@ -11593,9 +11915,23 @@ var PROCEDURES_MARKER = "EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES";
11593
11915
  function getSessionPrompt(storedPrompt) {
11594
11916
  const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
11595
11917
  const withoutProcedures = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
11918
+ let titlePrefix = "";
11919
+ const frontmatterMatch = withoutProcedures.match(/^---\r?\n([\s\S]*?)\r?\n---/);
11920
+ if (frontmatterMatch) {
11921
+ const titleMatch = frontmatterMatch[1].match(/^title:\s*(.+)$/m);
11922
+ const roleMatch = frontmatterMatch[1].match(/^role:\s*(.+)$/m);
11923
+ if (titleMatch) {
11924
+ const title = titleMatch[1].trim();
11925
+ const role = roleMatch ? roleMatch[1].trim() : "";
11926
+ if (title && role && title.toLowerCase() !== role.toLowerCase()) {
11927
+ titlePrefix = `## Your Identity
11928
+ You are **${title}** (specialist). `;
11929
+ }
11930
+ }
11931
+ }
11596
11932
  const rolePrompt = withoutProcedures.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").replace(/<!--[\s\S]*?-->/g, "").trimStart();
11597
11933
  const globalBlock = getGlobalProceduresBlock();
11598
- return `${globalBlock}${rolePrompt}
11934
+ return `${globalBlock}${titlePrefix}${rolePrompt}
11599
11935
  ${BASE_OPERATING_PROCEDURES}`;
11600
11936
  }
11601
11937
 
@@ -11802,7 +12138,20 @@ function buildActionRequired(data) {
11802
12138
  const blockedTasks = data.globalTasks.filter((t) => t.status === "blocked");
11803
12139
  if (blockedTasks.length > 0) {
11804
12140
  hasIssues = true;
11805
- lines.push(` \u{1F534} ${blockedTasks.length} blocked task${blockedTasks.length === 1 ? "" : "s"} \u2014 needs your decision`);
12141
+ const ESCALATION_MS = 48 * 60 * 60 * 1e3;
12142
+ const stale = blockedTasks.filter((t) => {
12143
+ if (!t.updatedAt) return false;
12144
+ return Date.now() - new Date(t.updatedAt).getTime() > ESCALATION_MS;
12145
+ });
12146
+ if (stale.length > 0) {
12147
+ lines.push(` \u{1F534} ESCALATION: ${stale.length} task${stale.length === 1 ? "" : "s"} blocked >48h:`);
12148
+ for (const t of stale) {
12149
+ const ageH = Math.round((Date.now() - new Date(t.updatedAt).getTime()) / 36e5);
12150
+ lines.push(` - "${t.title}" (${t.assignedTo}) \u2014 blocked ${ageH}h`);
12151
+ }
12152
+ } else {
12153
+ lines.push(` \u{1F534} ${blockedTasks.length} blocked task${blockedTasks.length === 1 ? "" : "s"} \u2014 needs your decision`);
12154
+ }
11806
12155
  }
11807
12156
  if (data.flaggedIssues.length > 0) {
11808
12157
  hasIssues = true;