@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
@@ -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);
@@ -3336,6 +3609,22 @@ async function ensureSchema() {
3336
3609
  } catch (e) {
3337
3610
  logCatchDebug("migration", e);
3338
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
+ }
3339
3628
  }
3340
3629
  async function disposeDatabase() {
3341
3630
  if (_walCheckpointTimer) {
@@ -3438,11 +3727,17 @@ var init_platform_procedures = __esm({
3438
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."
3439
3728
  },
3440
3729
  {
3441
- title: "Customer orchestration maturity \u2014 recommend, never trap",
3730
+ title: "Orchestration phase guidance \u2014 recommend, never trap",
3442
3731
  domain: "workflow",
3443
3732
  priority: "p1",
3444
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."
3445
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
+ },
3446
3741
  {
3447
3742
  title: "Single dispatch path \u2014 create_task only",
3448
3743
  domain: "workflow",
@@ -3476,6 +3771,12 @@ var init_platform_procedures = __esm({
3476
3771
  priority: "p0",
3477
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."
3478
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
+ },
3479
3780
  {
3480
3781
  title: "Customer patch triage \u2014 upstream bug vs customization",
3481
3782
  domain: "support",
@@ -3760,15 +4061,15 @@ __export(keychain_exports, {
3760
4061
  setMasterKey: () => setMasterKey
3761
4062
  });
3762
4063
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3763
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
4064
+ import { existsSync as existsSync8, statSync as statSync3 } from "fs";
3764
4065
  import { execSync as execSync3 } from "child_process";
3765
- import path6 from "path";
4066
+ import path7 from "path";
3766
4067
  import os5 from "os";
3767
4068
  function getKeyDir() {
3768
- 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");
3769
4070
  }
3770
4071
  function getKeyPath() {
3771
- return path6.join(getKeyDir(), "master.key");
4072
+ return path7.join(getKeyDir(), "master.key");
3772
4073
  }
3773
4074
  function nativeKeychainAllowed() {
3774
4075
  return process.env.EXE_OS_DISABLE_NATIVE_KEYCHAIN !== "1";
@@ -3799,7 +4100,7 @@ function isRootOnlyTrustedServerKeyFile(keyPath) {
3799
4100
  if (!st.isFile() || (st.mode & 63) !== 0) return false;
3800
4101
  if (uid === 0) return true;
3801
4102
  const exeOsDir = process.env.EXE_OS_DIR;
3802
- 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));
3803
4104
  } catch {
3804
4105
  return false;
3805
4106
  }
@@ -3996,7 +4297,7 @@ async function getMasterKey() {
3996
4297
  }
3997
4298
  }
3998
4299
  const keyPath = getKeyPath();
3999
- if (!existsSync7(keyPath)) {
4300
+ if (!existsSync8(keyPath)) {
4000
4301
  process.stderr.write(
4001
4302
  `[keychain] Key not found at ${keyPath} (HOME=${os5.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
4002
4303
  `
@@ -4104,7 +4405,7 @@ async function getKeyStorageInfo() {
4104
4405
  }
4105
4406
  }
4106
4407
  const keyPath = getKeyPath();
4107
- if (!existsSync7(keyPath)) {
4408
+ if (!existsSync8(keyPath)) {
4108
4409
  return {
4109
4410
  kind: "missing",
4110
4411
  secure: false,
@@ -4200,7 +4501,7 @@ async function deleteMasterKey() {
4200
4501
  }
4201
4502
  }
4202
4503
  const keyPath = getKeyPath();
4203
- if (existsSync7(keyPath)) {
4504
+ if (existsSync8(keyPath)) {
4204
4505
  await unlink(keyPath);
4205
4506
  }
4206
4507
  }
@@ -4323,14 +4624,14 @@ __export(shard_manager_exports, {
4323
4624
  listShards: () => listShards,
4324
4625
  shardExists: () => shardExists
4325
4626
  });
4326
- import path7 from "path";
4327
- 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";
4328
4629
  import { createClient as createClient2 } from "@libsql/client";
4329
4630
  function initShardManager(encryptionKey) {
4330
4631
  _encryptionKey = encryptionKey;
4331
4632
  _keyValidated = false;
4332
4633
  _keyValidationPromise = null;
4333
- if (!existsSync8(SHARDS_DIR)) {
4634
+ if (!existsSync9(SHARDS_DIR)) {
4334
4635
  mkdirSync3(SHARDS_DIR, { recursive: true });
4335
4636
  }
4336
4637
  const existingShards = readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db"));
@@ -4351,7 +4652,7 @@ async function validateEncryptionKey() {
4351
4652
  return true;
4352
4653
  }
4353
4654
  for (const shardFile of existingShards.slice(0, 3)) {
4354
- const dbPath = path7.join(SHARDS_DIR, shardFile);
4655
+ const dbPath = path8.join(SHARDS_DIR, shardFile);
4355
4656
  const testClient = createClient2({ url: `file:${dbPath}`, encryptionKey: _encryptionKey });
4356
4657
  try {
4357
4658
  await testClient.execute("SELECT COUNT(*) FROM sqlite_schema");
@@ -4394,7 +4695,7 @@ function getShardClient(projectName) {
4394
4695
  while (_shards.size >= MAX_OPEN_SHARDS) {
4395
4696
  evictLRU();
4396
4697
  }
4397
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4698
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4398
4699
  const client = createClient2({
4399
4700
  url: `file:${dbPath}`,
4400
4701
  encryptionKey: _encryptionKey
@@ -4405,13 +4706,13 @@ function getShardClient(projectName) {
4405
4706
  }
4406
4707
  function shardExists(projectName) {
4407
4708
  const safeName = safeShardName(projectName);
4408
- return existsSync8(path7.join(SHARDS_DIR, `${safeName}.db`));
4709
+ return existsSync9(path8.join(SHARDS_DIR, `${safeName}.db`));
4409
4710
  }
4410
4711
  function safeShardName(projectName) {
4411
4712
  return projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
4412
4713
  }
4413
4714
  function listShards() {
4414
- if (!existsSync8(SHARDS_DIR)) return [];
4715
+ if (!existsSync9(SHARDS_DIR)) return [];
4415
4716
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
4416
4717
  }
4417
4718
  async function auditShardHealth(options = {}) {
@@ -4423,7 +4724,7 @@ async function auditShardHealth(options = {}) {
4423
4724
  const names = listShards();
4424
4725
  const shards = [];
4425
4726
  for (const name of names) {
4426
- const dbPath = path7.join(SHARDS_DIR, `${name}.db`);
4727
+ const dbPath = path8.join(SHARDS_DIR, `${name}.db`);
4427
4728
  const stat = statSync4(dbPath);
4428
4729
  const item = {
4429
4730
  name,
@@ -4458,7 +4759,7 @@ async function auditShardHealth(options = {}) {
4458
4759
  _shards.delete(name);
4459
4760
  _shardLastAccess.delete(name);
4460
4761
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4461
- const archivedPath = path7.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4762
+ const archivedPath = path8.join(SHARDS_DIR, `${name}.db.broken-${stamp}`);
4462
4763
  renameSync3(dbPath, archivedPath);
4463
4764
  item.archivedPath = archivedPath;
4464
4765
  }
@@ -4686,11 +4987,11 @@ async function getReadyShardClient(projectName) {
4686
4987
  client.close();
4687
4988
  _shards.delete(safeName);
4688
4989
  _shardLastAccess.delete(safeName);
4689
- const dbPath = path7.join(SHARDS_DIR, `${safeName}.db`);
4690
- if (existsSync8(dbPath)) {
4990
+ const dbPath = path8.join(SHARDS_DIR, `${safeName}.db`);
4991
+ if (existsSync9(dbPath)) {
4691
4992
  const stat = statSync4(dbPath);
4692
4993
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4693
- const archivedPath = path7.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4994
+ const archivedPath = path8.join(SHARDS_DIR, `${safeName}.db.broken-${stamp}`);
4694
4995
  renameSync3(dbPath, archivedPath);
4695
4996
  process.stderr.write(
4696
4997
  `[shard-manager] Archived unreadable shard ${safeName}: ${archivedPath} (${stat.size} bytes, mtime ${stat.mtime.toISOString()})
@@ -4758,7 +5059,7 @@ var init_shard_manager = __esm({
4758
5059
  "src/lib/shard-manager.ts"() {
4759
5060
  "use strict";
4760
5061
  init_config();
4761
- SHARDS_DIR = path7.join(EXE_AI_DIR, "shards");
5062
+ SHARDS_DIR = path8.join(EXE_AI_DIR, "shards");
4762
5063
  SHARD_IDLE_MS = 5 * 60 * 1e3;
4763
5064
  MAX_OPEN_SHARDS = 10;
4764
5065
  EVICTION_INTERVAL_MS = 60 * 1e3;
@@ -4887,13 +5188,13 @@ __export(session_registry_exports, {
4887
5188
  refreshSessionProject: () => refreshSessionProject,
4888
5189
  registerSession: () => registerSession
4889
5190
  });
4890
- 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";
4891
5192
  import { execSync as execSync4 } from "child_process";
4892
- import path8 from "path";
5193
+ import path9 from "path";
4893
5194
  import os6 from "os";
4894
5195
  function registerSession(entry) {
4895
- const dir = path8.dirname(REGISTRY_PATH);
4896
- if (!existsSync9(dir)) {
5196
+ const dir = path9.dirname(REGISTRY_PATH);
5197
+ if (!existsSync10(dir)) {
4897
5198
  mkdirSync4(dir, { recursive: true });
4898
5199
  }
4899
5200
  const sessions = listSessions();
@@ -4903,7 +5204,7 @@ function registerSession(entry) {
4903
5204
  } else {
4904
5205
  sessions.push(entry);
4905
5206
  }
4906
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5207
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
4907
5208
  }
4908
5209
  function refreshSessionProject(windowName, projectDir) {
4909
5210
  const sessions = listSessions();
@@ -4911,13 +5212,13 @@ function refreshSessionProject(windowName, projectDir) {
4911
5212
  if (!entry || entry.projectDir === projectDir) return;
4912
5213
  entry.projectDir = projectDir;
4913
5214
  try {
4914
- writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
5215
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
4915
5216
  } catch {
4916
5217
  }
4917
5218
  }
4918
5219
  function listSessions() {
4919
5220
  try {
4920
- const raw = readFileSync5(REGISTRY_PATH, "utf8");
5221
+ const raw = readFileSync6(REGISTRY_PATH, "utf8");
4921
5222
  return JSON.parse(raw);
4922
5223
  } catch {
4923
5224
  return [];
@@ -4938,7 +5239,7 @@ function pruneStaleSessions() {
4938
5239
  const alive = sessions.filter((s) => liveSet.has(s.windowName));
4939
5240
  const pruned = sessions.length - alive.length;
4940
5241
  if (pruned > 0) {
4941
- writeFileSync3(REGISTRY_PATH, JSON.stringify(alive, null, 2));
5242
+ writeFileSync4(REGISTRY_PATH, JSON.stringify(alive, null, 2));
4942
5243
  }
4943
5244
  return pruned;
4944
5245
  }
@@ -4946,7 +5247,7 @@ var REGISTRY_PATH;
4946
5247
  var init_session_registry = __esm({
4947
5248
  "src/lib/session-registry.ts"() {
4948
5249
  "use strict";
4949
- REGISTRY_PATH = path8.join(os6.homedir(), ".exe-os", "session-registry.json");
5250
+ REGISTRY_PATH = path9.join(os6.homedir(), ".exe-os", "session-registry.json");
4950
5251
  }
4951
5252
  });
4952
5253
 
@@ -5208,68 +5509,6 @@ var init_provider_table = __esm({
5208
5509
  }
5209
5510
  });
5210
5511
 
5211
- // src/lib/runtime-table.ts
5212
- var RUNTIME_TABLE, DEFAULT_RUNTIME;
5213
- var init_runtime_table = __esm({
5214
- "src/lib/runtime-table.ts"() {
5215
- "use strict";
5216
- RUNTIME_TABLE = {
5217
- codex: {
5218
- binary: "codex",
5219
- launchMode: "interactive",
5220
- autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
5221
- inlineFlag: "--no-alt-screen",
5222
- apiKeyEnv: "OPENAI_API_KEY",
5223
- defaultModel: "gpt-5.5"
5224
- },
5225
- opencode: {
5226
- binary: "opencode",
5227
- launchMode: "exec",
5228
- autoApproveFlag: "--dangerously-skip-permissions",
5229
- inlineFlag: "",
5230
- apiKeyEnv: "ANTHROPIC_API_KEY",
5231
- defaultModel: "anthropic/claude-sonnet-4-6"
5232
- }
5233
- };
5234
- DEFAULT_RUNTIME = "claude";
5235
- }
5236
- });
5237
-
5238
- // src/lib/agent-config.ts
5239
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
5240
- import path9 from "path";
5241
- function loadAgentConfig() {
5242
- if (!existsSync10(AGENT_CONFIG_PATH)) return {};
5243
- try {
5244
- return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf-8"));
5245
- } catch {
5246
- return {};
5247
- }
5248
- }
5249
- function getAgentRuntime(agentId) {
5250
- const config = loadAgentConfig();
5251
- const entry = config[agentId];
5252
- if (entry) return entry;
5253
- const orgDefault = config["default"];
5254
- if (orgDefault) return orgDefault;
5255
- return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
5256
- }
5257
- var AGENT_CONFIG_PATH, DEFAULT_MODELS;
5258
- var init_agent_config = __esm({
5259
- "src/lib/agent-config.ts"() {
5260
- "use strict";
5261
- init_config();
5262
- init_runtime_table();
5263
- init_secure_files();
5264
- AGENT_CONFIG_PATH = path9.join(EXE_AI_DIR, "agent-config.json");
5265
- DEFAULT_MODELS = {
5266
- claude: "claude-opus-4.6",
5267
- codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
5268
- opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
5269
- };
5270
- }
5271
- });
5272
-
5273
5512
  // src/lib/intercom-queue.ts
5274
5513
  var intercom_queue_exports = {};
5275
5514
  __export(intercom_queue_exports, {
@@ -5764,7 +6003,7 @@ async function assertVpsLicense(opts) {
5764
6003
  }
5765
6004
  if (!transientFailure) {
5766
6005
  throw new Error(
5767
- "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."
5768
6007
  );
5769
6008
  }
5770
6009
  const fresh = await getCachedLicense();
@@ -5801,7 +6040,7 @@ async function assertVpsLicense(opts) {
5801
6040
  } catch {
5802
6041
  }
5803
6042
  throw new Error(
5804
- `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.`
5805
6044
  );
5806
6045
  }
5807
6046
  function startLicenseRevalidation(intervalMs = 36e5) {
@@ -5833,7 +6072,7 @@ var init_license = __esm({
5833
6072
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
5834
6073
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
5835
6074
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
5836
- API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
6075
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://cloud.askexe.com";
5837
6076
  RETRY_DELAY_MS = 500;
5838
6077
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
5839
6078
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -6490,6 +6729,19 @@ async function resolveTask(client, identifier, scopeSession) {
6490
6729
  args: [identifier, ...scope.args]
6491
6730
  });
6492
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
+ }
6493
6745
  result = await client.execute({
6494
6746
  sql: `SELECT * FROM tasks WHERE task_file LIKE ?${scope.sql}`,
6495
6747
  args: [`%${identifier}%`, ...scope.args]
@@ -7344,12 +7596,13 @@ async function cascadeUnblock(taskId, baseDir, now) {
7344
7596
  WHERE blocked_by = ? AND status = 'blocked'`,
7345
7597
  args: [now, taskId]
7346
7598
  });
7347
- if (baseDir && unblocked.rowsAffected > 0) {
7348
- const ubScope = sessionScopeFilter();
7349
- const unblockedRows = await client.execute({
7350
- sql: `SELECT task_file FROM tasks WHERE blocked_by IS NULL AND updated_at = ?${ubScope.sql}`,
7351
- args: [now, ...ubScope.args]
7352
- });
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) {
7353
7606
  for (const ur of unblockedRows.rows) {
7354
7607
  try {
7355
7608
  const ubFile = path18.join(baseDir, String(ur.task_file));
@@ -7361,6 +7614,19 @@ async function cascadeUnblock(taskId, baseDir, now) {
7361
7614
  }
7362
7615
  }
7363
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
+ }
7364
7630
  }
7365
7631
  async function findNextTask(assignedTo) {
7366
7632
  const client = getClient();
@@ -7570,6 +7836,15 @@ var init_embedder = __esm({
7570
7836
  // src/lib/behaviors.ts
7571
7837
  import crypto5 from "crypto";
7572
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
+ }
7573
7848
  const client = getClient();
7574
7849
  const id = crypto5.randomUUID();
7575
7850
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -8023,6 +8298,12 @@ async function updateTask(input) {
8023
8298
  }
8024
8299
  }
8025
8300
  }
8301
+ if (input.status === "cancelled") {
8302
+ try {
8303
+ await cascadeUnblock(taskId, input.baseDir, now);
8304
+ } catch {
8305
+ }
8306
+ }
8026
8307
  if ((input.status === "done" || input.status === "closed") && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
8027
8308
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
8028
8309
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
@@ -8554,11 +8835,12 @@ function getDispatchedBy(sessionKey) {
8554
8835
  }
8555
8836
  }
8556
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
+ }
8557
8842
  const mySession = getMySession();
8558
8843
  if (!mySession) {
8559
- if (process.env.EXE_SESSION_NAME) {
8560
- return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
8561
- }
8562
8844
  return null;
8563
8845
  }
8564
8846
  const fromSessionName = extractRootExe(mySession);
@@ -8573,6 +8855,10 @@ function resolveExeSession() {
8573
8855
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
8574
8856
  `
8575
8857
  );
8858
+ try {
8859
+ registerParentExe(key, fromSessionName);
8860
+ } catch {
8861
+ }
8576
8862
  candidate = fromSessionName;
8577
8863
  } else {
8578
8864
  candidate = fromCache;
@@ -10261,6 +10547,27 @@ async function cloudSync(config) {
10261
10547
  if (stmts.length > 0) await client.batch(stmts, "write");
10262
10548
  pulled = pullResult.records.length;
10263
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
+ }
10264
10571
  const stmts = pullResult.records.map((rec) => ({
10265
10572
  sql: `INSERT OR REPLACE INTO memories
10266
10573
  (id, agent_id, agent_role, session_id, timestamp,
@@ -11608,9 +11915,23 @@ var PROCEDURES_MARKER = "EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES";
11608
11915
  function getSessionPrompt(storedPrompt) {
11609
11916
  const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
11610
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
+ }
11611
11932
  const rolePrompt = withoutProcedures.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").replace(/<!--[\s\S]*?-->/g, "").trimStart();
11612
11933
  const globalBlock = getGlobalProceduresBlock();
11613
- return `${globalBlock}${rolePrompt}
11934
+ return `${globalBlock}${titlePrefix}${rolePrompt}
11614
11935
  ${BASE_OPERATING_PROCEDURES}`;
11615
11936
  }
11616
11937
 
@@ -11817,7 +12138,20 @@ function buildActionRequired(data) {
11817
12138
  const blockedTasks = data.globalTasks.filter((t) => t.status === "blocked");
11818
12139
  if (blockedTasks.length > 0) {
11819
12140
  hasIssues = true;
11820
- 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
+ }
11821
12155
  }
11822
12156
  if (data.flaggedIssues.length > 0) {
11823
12157
  hasIssues = true;