@askexenow/exe-os 0.8.80 → 0.8.82

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 (110) hide show
  1. package/dist/bin/backfill-conversations.js +359 -267
  2. package/dist/bin/backfill-responses.js +357 -265
  3. package/dist/bin/backfill-vectors.js +339 -264
  4. package/dist/bin/cleanup-stale-review-tasks.js +315 -256
  5. package/dist/bin/cli.js +494 -240
  6. package/dist/bin/exe-agent.js +141 -46
  7. package/dist/bin/exe-assign.js +151 -63
  8. package/dist/bin/exe-boot.js +294 -115
  9. package/dist/bin/exe-call.js +76 -51
  10. package/dist/bin/exe-cloud.js +58 -45
  11. package/dist/bin/exe-dispatch.js +434 -277
  12. package/dist/bin/exe-doctor.js +317 -246
  13. package/dist/bin/exe-export-behaviors.js +328 -248
  14. package/dist/bin/exe-forget.js +314 -231
  15. package/dist/bin/exe-gateway.js +2676 -1402
  16. package/dist/bin/exe-heartbeat.js +329 -264
  17. package/dist/bin/exe-kill.js +324 -244
  18. package/dist/bin/exe-launch-agent.js +574 -463
  19. package/dist/bin/exe-link.js +1055 -95
  20. package/dist/bin/exe-new-employee.js +49 -54
  21. package/dist/bin/exe-pending-messages.js +310 -253
  22. package/dist/bin/exe-pending-notifications.js +299 -228
  23. package/dist/bin/exe-pending-reviews.js +314 -245
  24. package/dist/bin/exe-rename.js +259 -195
  25. package/dist/bin/exe-review.js +140 -64
  26. package/dist/bin/exe-search.js +543 -356
  27. package/dist/bin/exe-session-cleanup.js +463 -382
  28. package/dist/bin/exe-settings.js +129 -99
  29. package/dist/bin/exe-start.sh +6 -6
  30. package/dist/bin/exe-status.js +95 -36
  31. package/dist/bin/exe-team.js +116 -51
  32. package/dist/bin/git-sweep.js +482 -307
  33. package/dist/bin/graph-backfill.js +357 -245
  34. package/dist/bin/graph-export.js +324 -244
  35. package/dist/bin/install.js +33 -10
  36. package/dist/bin/scan-tasks.js +481 -307
  37. package/dist/bin/setup.js +1147 -140
  38. package/dist/bin/shard-migrate.js +321 -241
  39. package/dist/bin/update.js +1 -7
  40. package/dist/bin/wiki-sync.js +318 -238
  41. package/dist/gateway/index.js +2656 -1383
  42. package/dist/hooks/bug-report-worker.js +641 -472
  43. package/dist/hooks/commit-complete.js +482 -307
  44. package/dist/hooks/error-recall.js +363 -135
  45. package/dist/hooks/exe-heartbeat-hook.js +97 -27
  46. package/dist/hooks/ingest-worker.js +584 -397
  47. package/dist/hooks/ingest.js +123 -58
  48. package/dist/hooks/instructions-loaded.js +212 -82
  49. package/dist/hooks/notification.js +200 -70
  50. package/dist/hooks/post-compact.js +199 -81
  51. package/dist/hooks/pre-compact.js +352 -140
  52. package/dist/hooks/pre-tool-use.js +416 -278
  53. package/dist/hooks/prompt-ingest-worker.js +376 -299
  54. package/dist/hooks/prompt-submit.js +414 -188
  55. package/dist/hooks/response-ingest-worker.js +408 -338
  56. package/dist/hooks/session-end.js +209 -83
  57. package/dist/hooks/session-start.js +382 -158
  58. package/dist/hooks/stop.js +209 -83
  59. package/dist/hooks/subagent-stop.js +209 -85
  60. package/dist/hooks/summary-worker.js +606 -510
  61. package/dist/index.js +2133 -855
  62. package/dist/lib/cloud-sync.js +1175 -184
  63. package/dist/lib/config.js +1 -9
  64. package/dist/lib/consolidation.js +71 -34
  65. package/dist/lib/database.js +166 -14
  66. package/dist/lib/device-registry.js +189 -117
  67. package/dist/lib/embedder.js +6 -10
  68. package/dist/lib/employee-templates.js +134 -39
  69. package/dist/lib/employees.js +30 -7
  70. package/dist/lib/exe-daemon-client.js +5 -7
  71. package/dist/lib/exe-daemon.js +514 -152
  72. package/dist/lib/hybrid-search.js +543 -356
  73. package/dist/lib/identity-templates.js +15 -15
  74. package/dist/lib/identity.js +19 -15
  75. package/dist/lib/license.js +1 -7
  76. package/dist/lib/messaging.js +157 -135
  77. package/dist/lib/reminders.js +97 -0
  78. package/dist/lib/schedules.js +302 -231
  79. package/dist/lib/skill-learning.js +33 -27
  80. package/dist/lib/status-brief.js +11 -14
  81. package/dist/lib/store.js +326 -237
  82. package/dist/lib/task-router.js +105 -1
  83. package/dist/lib/tasks.js +233 -116
  84. package/dist/lib/tmux-routing.js +173 -56
  85. package/dist/lib/ws-client.js +13 -3
  86. package/dist/mcp/server.js +2009 -1015
  87. package/dist/mcp/tools/complete-reminder.js +97 -0
  88. package/dist/mcp/tools/create-reminder.js +97 -0
  89. package/dist/mcp/tools/create-task.js +426 -262
  90. package/dist/mcp/tools/deactivate-behavior.js +119 -44
  91. package/dist/mcp/tools/list-reminders.js +97 -0
  92. package/dist/mcp/tools/list-tasks.js +56 -57
  93. package/dist/mcp/tools/send-message.js +206 -143
  94. package/dist/mcp/tools/update-task.js +259 -85
  95. package/dist/runtime/index.js +495 -316
  96. package/dist/tui/App.js +1128 -919
  97. package/package.json +2 -10
  98. package/src/commands/exe/afk.md +8 -8
  99. package/src/commands/exe/assign.md +1 -1
  100. package/src/commands/exe/build-adv.md +1 -1
  101. package/src/commands/exe/call.md +10 -10
  102. package/src/commands/exe/employee-heartbeat.md +9 -6
  103. package/src/commands/exe/heartbeat.md +5 -5
  104. package/src/commands/exe/intercom.md +26 -15
  105. package/src/commands/exe/launch.md +2 -2
  106. package/src/commands/exe/new-employee.md +1 -1
  107. package/src/commands/exe/review.md +2 -2
  108. package/src/commands/exe/schedule.md +1 -1
  109. package/src/commands/exe/sessions.md +2 -2
  110. package/src/commands/exe.md +22 -20
package/dist/tui/App.js CHANGED
@@ -410,7 +410,7 @@ function wrapWithRetry(client) {
410
410
  return (sql) => retryOnBusy(() => target.execute(sql), "execute");
411
411
  }
412
412
  if (prop === "batch") {
413
- return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
413
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
414
414
  }
415
415
  return Reflect.get(target, prop, receiver);
416
416
  }
@@ -426,361 +426,801 @@ var init_db_retry = __esm({
426
426
  }
427
427
  });
428
428
 
429
- // src/lib/database.ts
430
- var database_exports = {};
431
- __export(database_exports, {
432
- disposeDatabase: () => disposeDatabase,
433
- disposeTurso: () => disposeTurso,
434
- ensureSchema: () => ensureSchema,
435
- getClient: () => getClient,
436
- getRawClient: () => getRawClient,
437
- initDatabase: () => initDatabase,
438
- initTurso: () => initTurso,
439
- isInitialized: () => isInitialized
429
+ // src/lib/config.ts
430
+ var config_exports = {};
431
+ __export(config_exports, {
432
+ CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
433
+ CONFIG_PATH: () => CONFIG_PATH,
434
+ CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
435
+ DB_PATH: () => DB_PATH,
436
+ EXE_AI_DIR: () => EXE_AI_DIR,
437
+ LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
438
+ MODELS_DIR: () => MODELS_DIR,
439
+ loadConfig: () => loadConfig,
440
+ loadConfigFrom: () => loadConfigFrom,
441
+ loadConfigSync: () => loadConfigSync,
442
+ migrateConfig: () => migrateConfig,
443
+ saveConfig: () => saveConfig
440
444
  });
441
- import { createClient } from "@libsql/client";
442
- async function initDatabase(config) {
443
- if (_client) {
444
- _client.close();
445
- _client = null;
446
- _resilientClient = null;
447
- }
448
- const opts = {
449
- url: `file:${config.dbPath}`
450
- };
451
- if (config.encryptionKey) {
452
- opts.encryptionKey = config.encryptionKey;
445
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
446
+ import { readFileSync as readFileSync4, existsSync as existsSync4, renameSync as renameSync2 } from "fs";
447
+ import path3 from "path";
448
+ import os3 from "os";
449
+ function resolveDataDir() {
450
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
451
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
452
+ const newDir = path3.join(os3.homedir(), ".exe-os");
453
+ const legacyDir = path3.join(os3.homedir(), ".exe-mem");
454
+ if (!existsSync4(newDir) && existsSync4(legacyDir)) {
455
+ try {
456
+ renameSync2(legacyDir, newDir);
457
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
458
+ `);
459
+ } catch {
460
+ return legacyDir;
461
+ }
453
462
  }
454
- _client = createClient(opts);
455
- _resilientClient = wrapWithRetry(_client);
463
+ return newDir;
456
464
  }
457
- function isInitialized() {
458
- return _client !== null;
465
+ function migrateLegacyConfig(raw) {
466
+ if ("r2" in raw) {
467
+ process.stderr.write(
468
+ "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
469
+ );
470
+ delete raw.r2;
471
+ }
472
+ if ("syncIntervalMs" in raw) {
473
+ delete raw.syncIntervalMs;
474
+ }
475
+ return raw;
459
476
  }
460
- function getClient() {
461
- if (!_resilientClient) {
462
- throw new Error("Database client not initialized. Call initDatabase() first.");
477
+ function migrateConfig(raw) {
478
+ const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
479
+ let currentVersion = fromVersion;
480
+ let migrated = false;
481
+ if (currentVersion > CURRENT_CONFIG_VERSION) {
482
+ return { config: raw, migrated: false, fromVersion };
463
483
  }
464
- return _resilientClient;
484
+ for (const migration of CONFIG_MIGRATIONS) {
485
+ if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
486
+ raw = migration.migrate(raw);
487
+ currentVersion = migration.to;
488
+ migrated = true;
489
+ }
490
+ }
491
+ return { config: raw, migrated, fromVersion };
465
492
  }
466
- function getRawClient() {
467
- if (!_client) {
468
- throw new Error("Database client not initialized. Call initDatabase() first.");
493
+ function normalizeScalingRoadmap(raw) {
494
+ const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
495
+ const userRoadmap = raw.scalingRoadmap ?? {};
496
+ const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
497
+ if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
498
+ userAuto.enabled = raw.rerankerEnabled;
469
499
  }
470
- return _client;
500
+ raw.scalingRoadmap = {
501
+ ...userRoadmap,
502
+ rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
503
+ };
471
504
  }
472
- async function ensureSchema() {
473
- const client = getRawClient();
474
- await client.execute("PRAGMA journal_mode = WAL");
475
- await client.execute("PRAGMA busy_timeout = 30000");
476
- await client.execute("PRAGMA wal_autocheckpoint = 1000");
477
- try {
478
- await client.execute("PRAGMA libsql_vector_search_ef = 128");
479
- } catch {
505
+ function normalizeSessionLifecycle(raw) {
506
+ const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
507
+ const userSL = raw.sessionLifecycle ?? {};
508
+ raw.sessionLifecycle = { ...defaultSL, ...userSL };
509
+ }
510
+ function normalizeAutoUpdate(raw) {
511
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
512
+ const userAU = raw.autoUpdate ?? {};
513
+ raw.autoUpdate = { ...defaultAU, ...userAU };
514
+ }
515
+ async function loadConfig() {
516
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
517
+ await mkdir(dir, { recursive: true });
518
+ const configPath = path3.join(dir, "config.json");
519
+ if (!existsSync4(configPath)) {
520
+ return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
480
521
  }
481
- await client.executeMultiple(`
482
- CREATE TABLE IF NOT EXISTS memories (
483
- id TEXT PRIMARY KEY,
484
- agent_id TEXT NOT NULL,
485
- agent_role TEXT NOT NULL,
486
- session_id TEXT NOT NULL,
487
- timestamp TEXT NOT NULL,
488
- tool_name TEXT NOT NULL,
489
- project_name TEXT NOT NULL,
490
- has_error INTEGER NOT NULL DEFAULT 0,
491
- raw_text TEXT NOT NULL,
492
- vector F32_BLOB(1024),
493
- version INTEGER NOT NULL DEFAULT 0
494
- );
495
-
496
- CREATE INDEX IF NOT EXISTS idx_memories_agent
497
- ON memories(agent_id);
498
-
499
- CREATE INDEX IF NOT EXISTS idx_memories_timestamp
500
- ON memories(timestamp);
501
-
502
- CREATE INDEX IF NOT EXISTS idx_memories_session
503
- ON memories(session_id);
504
-
505
- CREATE INDEX IF NOT EXISTS idx_memories_project
506
- ON memories(project_name);
507
-
508
- CREATE INDEX IF NOT EXISTS idx_memories_tool
509
- ON memories(tool_name);
510
-
511
- CREATE INDEX IF NOT EXISTS idx_memories_version
512
- ON memories(version);
513
-
514
- CREATE INDEX IF NOT EXISTS idx_memories_agent_project
515
- ON memories(agent_id, project_name);
516
- `);
517
- await client.executeMultiple(`
518
- CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
519
- raw_text,
520
- content='memories',
521
- content_rowid='rowid'
522
- );
523
-
524
- CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
525
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
526
- END;
527
-
528
- CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
529
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
530
- END;
531
-
532
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
533
- INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
534
- INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
535
- END;
536
- `);
537
- await client.executeMultiple(`
538
- CREATE TABLE IF NOT EXISTS sync_meta (
539
- key TEXT PRIMARY KEY,
540
- value TEXT NOT NULL
541
- );
542
- `);
543
- await client.executeMultiple(`
544
- CREATE TABLE IF NOT EXISTS tasks (
545
- id TEXT PRIMARY KEY,
546
- title TEXT NOT NULL,
547
- assigned_to TEXT NOT NULL,
548
- assigned_by TEXT NOT NULL,
549
- project_name TEXT NOT NULL,
550
- priority TEXT NOT NULL DEFAULT 'p1',
551
- status TEXT NOT NULL DEFAULT 'open',
552
- task_file TEXT,
553
- created_at TEXT NOT NULL,
554
- updated_at TEXT NOT NULL
555
- );
556
-
557
- CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
558
- ON tasks(assigned_to, status);
559
- `);
560
- await client.executeMultiple(`
561
- CREATE TABLE IF NOT EXISTS behaviors (
562
- id TEXT PRIMARY KEY,
563
- agent_id TEXT NOT NULL,
564
- project_name TEXT,
565
- domain TEXT,
566
- content TEXT NOT NULL,
567
- active INTEGER NOT NULL DEFAULT 1,
568
- created_at TEXT NOT NULL,
569
- updated_at TEXT NOT NULL
570
- );
571
-
572
- CREATE INDEX IF NOT EXISTS idx_behaviors_agent
573
- ON behaviors(agent_id, active);
574
- `);
522
+ const raw = await readFile(configPath, "utf-8");
575
523
  try {
576
- const existing = await client.execute({
577
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
578
- args: []
579
- });
580
- if (Number(existing.rows[0]?.cnt) === 0) {
581
- await client.executeMultiple(`
582
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
583
- VALUES
584
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Don''t ask "keep going?" \u2014 just keep executing phases/plans autonomously', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
585
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
586
- VALUES
587
- (hex(randomblob(16)), 'exe', NULL, 'tool-use', 'Always use create_task MCP tool, never write .md files directly for task creation', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
588
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
589
- VALUES
590
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
591
- `);
524
+ let parsed = JSON.parse(raw);
525
+ parsed = migrateLegacyConfig(parsed);
526
+ const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
527
+ if (migrated) {
528
+ process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
529
+ `);
530
+ try {
531
+ await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
532
+ } catch {
533
+ }
534
+ }
535
+ normalizeScalingRoadmap(migratedCfg);
536
+ normalizeSessionLifecycle(migratedCfg);
537
+ normalizeAutoUpdate(migratedCfg);
538
+ const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
539
+ if (config.dbPath.startsWith("~")) {
540
+ config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
592
541
  }
542
+ return config;
593
543
  } catch {
544
+ return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
545
+ }
546
+ }
547
+ function loadConfigSync() {
548
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
549
+ const configPath = path3.join(dir, "config.json");
550
+ if (!existsSync4(configPath)) {
551
+ return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
594
552
  }
595
553
  try {
596
- await client.execute({
597
- sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
598
- args: []
599
- });
600
- } catch {
601
- }
602
- try {
603
- await client.execute({
604
- sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
605
- args: []
606
- });
554
+ const raw = readFileSync4(configPath, "utf-8");
555
+ let parsed = JSON.parse(raw);
556
+ parsed = migrateLegacyConfig(parsed);
557
+ const { config: migratedCfg } = migrateConfig(parsed);
558
+ normalizeScalingRoadmap(migratedCfg);
559
+ normalizeSessionLifecycle(migratedCfg);
560
+ normalizeAutoUpdate(migratedCfg);
561
+ return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
607
562
  } catch {
563
+ return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
608
564
  }
609
- try {
610
- await client.execute({
611
- sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
612
- args: []
613
- });
614
- } catch {
565
+ }
566
+ async function saveConfig(config) {
567
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
568
+ await mkdir(dir, { recursive: true });
569
+ const configPath = path3.join(dir, "config.json");
570
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
571
+ if (config.cloud?.apiKey) {
572
+ await chmod(configPath, 384);
615
573
  }
574
+ }
575
+ async function loadConfigFrom(configPath) {
576
+ const raw = await readFile(configPath, "utf-8");
616
577
  try {
617
- await client.execute({
618
- sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
619
- ON tasks(parent_task_id)
620
- WHERE parent_task_id IS NOT NULL`,
621
- args: []
622
- });
578
+ let parsed = JSON.parse(raw);
579
+ parsed = migrateLegacyConfig(parsed);
580
+ const { config: migratedCfg } = migrateConfig(parsed);
581
+ normalizeScalingRoadmap(migratedCfg);
582
+ normalizeSessionLifecycle(migratedCfg);
583
+ normalizeAutoUpdate(migratedCfg);
584
+ return { ...DEFAULT_CONFIG, ...migratedCfg };
623
585
  } catch {
586
+ return { ...DEFAULT_CONFIG };
624
587
  }
625
- try {
626
- await client.execute({
627
- sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
628
- args: []
629
- });
630
- } catch {
588
+ }
589
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
590
+ var init_config = __esm({
591
+ "src/lib/config.ts"() {
592
+ "use strict";
593
+ EXE_AI_DIR = resolveDataDir();
594
+ DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
595
+ MODELS_DIR = path3.join(EXE_AI_DIR, "models");
596
+ CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
597
+ LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
598
+ CURRENT_CONFIG_VERSION = 1;
599
+ DEFAULT_CONFIG = {
600
+ config_version: CURRENT_CONFIG_VERSION,
601
+ dbPath: DB_PATH,
602
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
603
+ embeddingDim: 1024,
604
+ batchSize: 20,
605
+ flushIntervalMs: 1e4,
606
+ autoIngestion: true,
607
+ autoRetrieval: true,
608
+ searchMode: "hybrid",
609
+ hookSearchMode: "hybrid",
610
+ fileGrepEnabled: true,
611
+ splashEffect: true,
612
+ consolidationEnabled: true,
613
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
614
+ consolidationModel: "claude-haiku-4-5-20251001",
615
+ consolidationMaxCallsPerRun: 20,
616
+ selfQueryRouter: true,
617
+ selfQueryModel: "claude-haiku-4-5-20251001",
618
+ rerankerEnabled: true,
619
+ scalingRoadmap: {
620
+ rerankerAutoTrigger: {
621
+ enabled: true,
622
+ broadQueryMinCardinality: 5e4,
623
+ fetchTopK: 150,
624
+ returnTopK: 5
625
+ }
626
+ },
627
+ graphRagEnabled: true,
628
+ wikiEnabled: false,
629
+ wikiUrl: "",
630
+ wikiApiKey: "",
631
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
632
+ wikiWorkspaceMapping: {},
633
+ wikiAutoUpdate: true,
634
+ wikiAutoUpdateThreshold: 0.5,
635
+ wikiAutoUpdateCreateNew: true,
636
+ skillLearning: true,
637
+ skillThreshold: 3,
638
+ skillModel: "claude-haiku-4-5-20251001",
639
+ exeHeartbeat: {
640
+ enabled: true,
641
+ intervalSeconds: 60,
642
+ staleInProgressThresholdHours: 2
643
+ },
644
+ sessionLifecycle: {
645
+ idleKillEnabled: true,
646
+ idleKillTicksRequired: 3,
647
+ idleKillIntercomAckWindowMs: 1e4,
648
+ maxAutoInstances: 10
649
+ },
650
+ autoUpdate: {
651
+ checkOnBoot: true,
652
+ autoInstall: false,
653
+ checkIntervalMs: 24 * 60 * 60 * 1e3
654
+ }
655
+ };
656
+ CONFIG_MIGRATIONS = [
657
+ {
658
+ from: 0,
659
+ to: 1,
660
+ migrate: (cfg) => {
661
+ cfg.config_version = 1;
662
+ return cfg;
663
+ }
664
+ }
665
+ ];
631
666
  }
632
- try {
633
- await client.execute({
634
- sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
635
- args: []
636
- });
637
- } catch {
667
+ });
668
+
669
+ // src/lib/employees.ts
670
+ var employees_exports = {};
671
+ __export(employees_exports, {
672
+ COORDINATOR_ROLE: () => COORDINATOR_ROLE,
673
+ DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
674
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
675
+ addEmployee: () => addEmployee,
676
+ canCoordinate: () => canCoordinate,
677
+ getCoordinatorEmployee: () => getCoordinatorEmployee,
678
+ getCoordinatorName: () => getCoordinatorName,
679
+ getEmployee: () => getEmployee,
680
+ getEmployeeByRole: () => getEmployeeByRole,
681
+ getEmployeeNamesByRole: () => getEmployeeNamesByRole,
682
+ hasRole: () => hasRole,
683
+ isCoordinatorName: () => isCoordinatorName,
684
+ isCoordinatorRole: () => isCoordinatorRole,
685
+ isMultiInstance: () => isMultiInstance,
686
+ loadEmployees: () => loadEmployees,
687
+ loadEmployeesSync: () => loadEmployeesSync,
688
+ normalizeRole: () => normalizeRole,
689
+ normalizeRosterCase: () => normalizeRosterCase,
690
+ registerBinSymlinks: () => registerBinSymlinks,
691
+ saveEmployees: () => saveEmployees,
692
+ validateEmployeeName: () => validateEmployeeName
693
+ });
694
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
695
+ import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
696
+ import { execSync as execSync4 } from "child_process";
697
+ import path4 from "path";
698
+ import os4 from "os";
699
+ function normalizeRole(role) {
700
+ return (role ?? "").trim().toLowerCase();
701
+ }
702
+ function isCoordinatorRole(role) {
703
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
704
+ }
705
+ function getCoordinatorEmployee(employees) {
706
+ return employees.find((e) => isCoordinatorRole(e.role));
707
+ }
708
+ function getCoordinatorName(employees = loadEmployeesSync()) {
709
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
710
+ }
711
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
712
+ if (!agentName) return false;
713
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
714
+ }
715
+ function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
716
+ return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
717
+ }
718
+ function validateEmployeeName(name) {
719
+ if (!name) {
720
+ return { valid: false, error: "Name is required" };
638
721
  }
639
- try {
640
- await client.execute({
641
- sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
642
- args: []
643
- });
644
- } catch {
722
+ if (name.length > 32) {
723
+ return { valid: false, error: "Name must be 32 characters or fewer" };
645
724
  }
646
- try {
647
- await client.execute({
648
- sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
649
- args: []
650
- });
651
- } catch {
725
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
726
+ return {
727
+ valid: false,
728
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
729
+ };
652
730
  }
653
- try {
654
- await client.execute({
655
- sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
656
- args: []
657
- });
658
- } catch {
731
+ return { valid: true };
732
+ }
733
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
734
+ if (!existsSync5(employeesPath)) {
735
+ return [];
659
736
  }
737
+ const raw = await readFile2(employeesPath, "utf-8");
660
738
  try {
661
- await client.execute({
662
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
663
- args: []
664
- });
739
+ return JSON.parse(raw);
665
740
  } catch {
741
+ return [];
666
742
  }
743
+ }
744
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
745
+ await mkdir2(path4.dirname(employeesPath), { recursive: true });
746
+ await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
747
+ }
748
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
749
+ if (!existsSync5(employeesPath)) return [];
667
750
  try {
668
- await client.execute({
669
- sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
670
- args: []
671
- });
751
+ return JSON.parse(readFileSync5(employeesPath, "utf-8"));
672
752
  } catch {
753
+ return [];
673
754
  }
674
- try {
675
- await client.execute({
676
- sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
677
- args: []
678
- });
679
- } catch {
755
+ }
756
+ function getEmployee(employees, name) {
757
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
758
+ }
759
+ function getEmployeeByRole(employees, role) {
760
+ const lower = role.toLowerCase();
761
+ return employees.find((e) => e.role.toLowerCase() === lower);
762
+ }
763
+ function getEmployeeNamesByRole(employees, role) {
764
+ const lower = role.toLowerCase();
765
+ return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
766
+ }
767
+ function hasRole(agentName, role) {
768
+ const employees = loadEmployeesSync();
769
+ const emp = getEmployee(employees, agentName);
770
+ return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
771
+ }
772
+ function isMultiInstance(agentName, employees) {
773
+ const roster = employees ?? loadEmployeesSync();
774
+ const emp = getEmployee(roster, agentName);
775
+ if (!emp) return false;
776
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
777
+ }
778
+ function addEmployee(employees, employee) {
779
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
780
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
781
+ throw new Error(`Employee '${normalized.name}' already exists`);
680
782
  }
681
- try {
682
- await client.execute({
683
- sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
684
- args: []
685
- });
686
- } catch {
783
+ return [...employees, normalized];
784
+ }
785
+ async function normalizeRosterCase(rosterPath) {
786
+ const employees = await loadEmployees(rosterPath);
787
+ let changed = false;
788
+ for (const emp of employees) {
789
+ if (emp.name !== emp.name.toLowerCase()) {
790
+ const oldName = emp.name;
791
+ emp.name = emp.name.toLowerCase();
792
+ changed = true;
793
+ try {
794
+ const identityDir = path4.join(os4.homedir(), ".exe-os", "identity");
795
+ const oldPath = path4.join(identityDir, `${oldName}.md`);
796
+ const newPath = path4.join(identityDir, `${emp.name}.md`);
797
+ if (existsSync5(oldPath) && !existsSync5(newPath)) {
798
+ renameSync3(oldPath, newPath);
799
+ } else if (existsSync5(oldPath) && oldPath !== newPath) {
800
+ const content = readFileSync5(oldPath, "utf-8");
801
+ writeFileSync3(newPath, content, "utf-8");
802
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
803
+ unlinkSync(oldPath);
804
+ }
805
+ }
806
+ } catch {
807
+ }
808
+ }
687
809
  }
688
- try {
689
- await client.execute({
690
- sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
691
- args: []
692
- });
693
- } catch {
810
+ if (changed) {
811
+ await saveEmployees(employees, rosterPath);
694
812
  }
813
+ return changed;
814
+ }
815
+ function findExeBin() {
695
816
  try {
696
- await client.execute({
697
- sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
698
- args: []
699
- });
817
+ return execSync4(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
700
818
  } catch {
819
+ return null;
820
+ }
821
+ }
822
+ function registerBinSymlinks(name) {
823
+ const created = [];
824
+ const skipped = [];
825
+ const errors = [];
826
+ const exeBinPath = findExeBin();
827
+ if (!exeBinPath) {
828
+ errors.push("Could not find 'exe-os' in PATH");
829
+ return { created, skipped, errors };
701
830
  }
831
+ const binDir = path4.dirname(exeBinPath);
832
+ let target;
702
833
  try {
703
- await client.execute({
704
- sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
705
- args: []
706
- });
834
+ target = readlinkSync(exeBinPath);
707
835
  } catch {
836
+ errors.push("Could not read 'exe' symlink");
837
+ return { created, skipped, errors };
838
+ }
839
+ for (const suffix of ["", "-opencode"]) {
840
+ const linkName = `${name}${suffix}`;
841
+ const linkPath = path4.join(binDir, linkName);
842
+ if (existsSync5(linkPath)) {
843
+ skipped.push(linkName);
844
+ continue;
845
+ }
846
+ try {
847
+ symlinkSync(target, linkPath);
848
+ created.push(linkName);
849
+ } catch (err) {
850
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
851
+ }
852
+ }
853
+ return { created, skipped, errors };
854
+ }
855
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
856
+ var init_employees = __esm({
857
+ "src/lib/employees.ts"() {
858
+ "use strict";
859
+ init_config();
860
+ EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
861
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
862
+ COORDINATOR_ROLE = "COO";
863
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
864
+ }
865
+ });
866
+
867
+ // src/lib/database.ts
868
+ var database_exports = {};
869
+ __export(database_exports, {
870
+ disposeDatabase: () => disposeDatabase,
871
+ disposeTurso: () => disposeTurso,
872
+ ensureSchema: () => ensureSchema,
873
+ getClient: () => getClient,
874
+ getRawClient: () => getRawClient,
875
+ initDatabase: () => initDatabase,
876
+ initTurso: () => initTurso,
877
+ isInitialized: () => isInitialized
878
+ });
879
+ import { createClient } from "@libsql/client";
880
+ async function initDatabase(config) {
881
+ if (_client) {
882
+ _client.close();
883
+ _client = null;
884
+ _resilientClient = null;
885
+ }
886
+ const opts = {
887
+ url: `file:${config.dbPath}`
888
+ };
889
+ if (config.encryptionKey) {
890
+ opts.encryptionKey = config.encryptionKey;
891
+ }
892
+ _client = createClient(opts);
893
+ _resilientClient = wrapWithRetry(_client);
894
+ }
895
+ function isInitialized() {
896
+ return _client !== null;
897
+ }
898
+ function getClient() {
899
+ if (!_resilientClient) {
900
+ throw new Error("Database client not initialized. Call initDatabase() first.");
901
+ }
902
+ return _resilientClient;
903
+ }
904
+ function getRawClient() {
905
+ if (!_client) {
906
+ throw new Error("Database client not initialized. Call initDatabase() first.");
708
907
  }
908
+ return _client;
909
+ }
910
+ async function ensureSchema() {
911
+ const client = getRawClient();
912
+ await client.execute("PRAGMA journal_mode = WAL");
913
+ await client.execute("PRAGMA busy_timeout = 30000");
914
+ await client.execute("PRAGMA wal_autocheckpoint = 1000");
709
915
  try {
710
- await client.execute({
711
- sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
712
- args: []
713
- });
916
+ await client.execute("PRAGMA libsql_vector_search_ef = 128");
714
917
  } catch {
715
918
  }
716
919
  await client.executeMultiple(`
717
- CREATE TABLE IF NOT EXISTS consolidations (
718
- id TEXT PRIMARY KEY,
719
- consolidated_memory_id TEXT NOT NULL,
720
- source_memory_id TEXT NOT NULL,
721
- created_at TEXT NOT NULL
920
+ CREATE TABLE IF NOT EXISTS memories (
921
+ id TEXT PRIMARY KEY,
922
+ agent_id TEXT NOT NULL,
923
+ agent_role TEXT NOT NULL,
924
+ session_id TEXT NOT NULL,
925
+ timestamp TEXT NOT NULL,
926
+ tool_name TEXT NOT NULL,
927
+ project_name TEXT NOT NULL,
928
+ has_error INTEGER NOT NULL DEFAULT 0,
929
+ raw_text TEXT NOT NULL,
930
+ vector F32_BLOB(1024),
931
+ version INTEGER NOT NULL DEFAULT 0
722
932
  );
723
933
 
724
- CREATE INDEX IF NOT EXISTS idx_consolidations_source
725
- ON consolidations(source_memory_id);
934
+ CREATE INDEX IF NOT EXISTS idx_memories_agent
935
+ ON memories(agent_id);
726
936
 
727
- CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
728
- ON consolidations(consolidated_memory_id);
729
- `);
730
- await client.executeMultiple(`
731
- CREATE TABLE IF NOT EXISTS reminders (
732
- id TEXT PRIMARY KEY,
733
- text TEXT NOT NULL,
734
- created_at TEXT NOT NULL,
735
- due_date TEXT,
736
- completed_at TEXT
737
- );
937
+ CREATE INDEX IF NOT EXISTS idx_memories_timestamp
938
+ ON memories(timestamp);
939
+
940
+ CREATE INDEX IF NOT EXISTS idx_memories_session
941
+ ON memories(session_id);
942
+
943
+ CREATE INDEX IF NOT EXISTS idx_memories_project
944
+ ON memories(project_name);
945
+
946
+ CREATE INDEX IF NOT EXISTS idx_memories_tool
947
+ ON memories(tool_name);
948
+
949
+ CREATE INDEX IF NOT EXISTS idx_memories_version
950
+ ON memories(version);
951
+
952
+ CREATE INDEX IF NOT EXISTS idx_memories_agent_project
953
+ ON memories(agent_id, project_name);
738
954
  `);
739
955
  await client.executeMultiple(`
740
- CREATE TABLE IF NOT EXISTS notifications (
741
- id TEXT PRIMARY KEY,
742
- agent_id TEXT NOT NULL,
743
- agent_role TEXT NOT NULL,
744
- event TEXT NOT NULL,
745
- project TEXT NOT NULL,
746
- summary TEXT NOT NULL,
747
- task_file TEXT,
748
- read INTEGER NOT NULL DEFAULT 0,
749
- created_at TEXT NOT NULL
956
+ CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
957
+ raw_text,
958
+ content='memories',
959
+ content_rowid='rowid'
750
960
  );
751
961
 
752
- CREATE INDEX IF NOT EXISTS idx_notifications_read
753
- ON notifications(read);
962
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
963
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
964
+ END;
754
965
 
755
- CREATE INDEX IF NOT EXISTS idx_notifications_agent
756
- ON notifications(agent_id);
966
+ CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
967
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
968
+ END;
757
969
 
758
- CREATE INDEX IF NOT EXISTS idx_notifications_task_file
759
- ON notifications(task_file);
970
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
971
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
972
+ INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
973
+ END;
760
974
  `);
761
975
  await client.executeMultiple(`
762
- CREATE TABLE IF NOT EXISTS schedules (
763
- id TEXT PRIMARY KEY,
764
- cron TEXT NOT NULL,
765
- description TEXT NOT NULL,
766
- job_type TEXT NOT NULL DEFAULT 'report',
767
- prompt TEXT,
768
- assigned_to TEXT,
769
- project_name TEXT,
770
- active INTEGER NOT NULL DEFAULT 1,
771
- use_crontab INTEGER NOT NULL DEFAULT 0,
772
- created_at TEXT NOT NULL
976
+ CREATE TABLE IF NOT EXISTS sync_meta (
977
+ key TEXT PRIMARY KEY,
978
+ value TEXT NOT NULL
773
979
  );
774
980
  `);
775
981
  await client.executeMultiple(`
776
- CREATE TABLE IF NOT EXISTS device_registry (
777
- device_id TEXT PRIMARY KEY,
778
- friendly_name TEXT NOT NULL,
779
- hostname TEXT NOT NULL,
780
- projects TEXT NOT NULL DEFAULT '[]',
781
- agents TEXT NOT NULL DEFAULT '[]',
782
- connected INTEGER DEFAULT 0,
783
- last_seen TEXT NOT NULL
982
+ CREATE TABLE IF NOT EXISTS tasks (
983
+ id TEXT PRIMARY KEY,
984
+ title TEXT NOT NULL,
985
+ assigned_to TEXT NOT NULL,
986
+ assigned_by TEXT NOT NULL,
987
+ project_name TEXT NOT NULL,
988
+ priority TEXT NOT NULL DEFAULT 'p1',
989
+ status TEXT NOT NULL DEFAULT 'open',
990
+ task_file TEXT,
991
+ created_at TEXT NOT NULL,
992
+ updated_at TEXT NOT NULL
993
+ );
994
+
995
+ CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
996
+ ON tasks(assigned_to, status);
997
+ `);
998
+ await client.executeMultiple(`
999
+ CREATE TABLE IF NOT EXISTS behaviors (
1000
+ id TEXT PRIMARY KEY,
1001
+ agent_id TEXT NOT NULL,
1002
+ project_name TEXT,
1003
+ domain TEXT,
1004
+ content TEXT NOT NULL,
1005
+ active INTEGER NOT NULL DEFAULT 1,
1006
+ created_at TEXT NOT NULL,
1007
+ updated_at TEXT NOT NULL
1008
+ );
1009
+
1010
+ CREATE INDEX IF NOT EXISTS idx_behaviors_agent
1011
+ ON behaviors(agent_id, active);
1012
+ `);
1013
+ try {
1014
+ const coordinatorName = getCoordinatorName();
1015
+ const existing = await client.execute({
1016
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
1017
+ args: [coordinatorName]
1018
+ });
1019
+ if (Number(existing.rows[0]?.cnt) === 0) {
1020
+ const seededAt = "2026-03-25T00:00:00Z";
1021
+ for (const [domain, content] of [
1022
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
1023
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
1024
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
1025
+ ]) {
1026
+ await client.execute({
1027
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
1028
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
1029
+ args: [coordinatorName, domain, content, seededAt, seededAt]
1030
+ });
1031
+ }
1032
+ }
1033
+ } catch {
1034
+ }
1035
+ try {
1036
+ await client.execute({
1037
+ sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
1038
+ args: []
1039
+ });
1040
+ } catch {
1041
+ }
1042
+ try {
1043
+ await client.execute({
1044
+ sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
1045
+ args: []
1046
+ });
1047
+ } catch {
1048
+ }
1049
+ try {
1050
+ await client.execute({
1051
+ sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
1052
+ args: []
1053
+ });
1054
+ } catch {
1055
+ }
1056
+ try {
1057
+ await client.execute({
1058
+ sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
1059
+ ON tasks(parent_task_id)
1060
+ WHERE parent_task_id IS NOT NULL`,
1061
+ args: []
1062
+ });
1063
+ } catch {
1064
+ }
1065
+ try {
1066
+ await client.execute({
1067
+ sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
1068
+ args: []
1069
+ });
1070
+ } catch {
1071
+ }
1072
+ try {
1073
+ await client.execute({
1074
+ sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
1075
+ args: []
1076
+ });
1077
+ } catch {
1078
+ }
1079
+ try {
1080
+ await client.execute({
1081
+ sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
1082
+ args: []
1083
+ });
1084
+ } catch {
1085
+ }
1086
+ try {
1087
+ await client.execute({
1088
+ sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
1089
+ args: []
1090
+ });
1091
+ } catch {
1092
+ }
1093
+ try {
1094
+ await client.execute({
1095
+ sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
1096
+ args: []
1097
+ });
1098
+ } catch {
1099
+ }
1100
+ try {
1101
+ await client.execute({
1102
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1103
+ args: []
1104
+ });
1105
+ } catch {
1106
+ }
1107
+ try {
1108
+ await client.execute({
1109
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1110
+ args: []
1111
+ });
1112
+ } catch {
1113
+ }
1114
+ try {
1115
+ await client.execute({
1116
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1117
+ args: []
1118
+ });
1119
+ } catch {
1120
+ }
1121
+ try {
1122
+ await client.execute({
1123
+ sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
1124
+ args: []
1125
+ });
1126
+ } catch {
1127
+ }
1128
+ try {
1129
+ await client.execute({
1130
+ sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
1131
+ args: []
1132
+ });
1133
+ } catch {
1134
+ }
1135
+ try {
1136
+ await client.execute({
1137
+ sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
1138
+ args: []
1139
+ });
1140
+ } catch {
1141
+ }
1142
+ try {
1143
+ await client.execute({
1144
+ sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
1145
+ args: []
1146
+ });
1147
+ } catch {
1148
+ }
1149
+ try {
1150
+ await client.execute({
1151
+ sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
1152
+ args: []
1153
+ });
1154
+ } catch {
1155
+ }
1156
+ await client.executeMultiple(`
1157
+ CREATE TABLE IF NOT EXISTS consolidations (
1158
+ id TEXT PRIMARY KEY,
1159
+ consolidated_memory_id TEXT NOT NULL,
1160
+ source_memory_id TEXT NOT NULL,
1161
+ created_at TEXT NOT NULL
1162
+ );
1163
+
1164
+ CREATE INDEX IF NOT EXISTS idx_consolidations_source
1165
+ ON consolidations(source_memory_id);
1166
+
1167
+ CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
1168
+ ON consolidations(consolidated_memory_id);
1169
+ `);
1170
+ await client.executeMultiple(`
1171
+ CREATE TABLE IF NOT EXISTS reminders (
1172
+ id TEXT PRIMARY KEY,
1173
+ text TEXT NOT NULL,
1174
+ created_at TEXT NOT NULL,
1175
+ due_date TEXT,
1176
+ completed_at TEXT
1177
+ );
1178
+ `);
1179
+ await client.executeMultiple(`
1180
+ CREATE TABLE IF NOT EXISTS notifications (
1181
+ id TEXT PRIMARY KEY,
1182
+ agent_id TEXT NOT NULL,
1183
+ agent_role TEXT NOT NULL,
1184
+ event TEXT NOT NULL,
1185
+ project TEXT NOT NULL,
1186
+ summary TEXT NOT NULL,
1187
+ task_file TEXT,
1188
+ read INTEGER NOT NULL DEFAULT 0,
1189
+ created_at TEXT NOT NULL
1190
+ );
1191
+
1192
+ CREATE INDEX IF NOT EXISTS idx_notifications_read
1193
+ ON notifications(read);
1194
+
1195
+ CREATE INDEX IF NOT EXISTS idx_notifications_agent
1196
+ ON notifications(agent_id);
1197
+
1198
+ CREATE INDEX IF NOT EXISTS idx_notifications_task_file
1199
+ ON notifications(task_file);
1200
+ `);
1201
+ await client.executeMultiple(`
1202
+ CREATE TABLE IF NOT EXISTS schedules (
1203
+ id TEXT PRIMARY KEY,
1204
+ cron TEXT NOT NULL,
1205
+ description TEXT NOT NULL,
1206
+ job_type TEXT NOT NULL DEFAULT 'report',
1207
+ prompt TEXT,
1208
+ assigned_to TEXT,
1209
+ project_name TEXT,
1210
+ active INTEGER NOT NULL DEFAULT 1,
1211
+ use_crontab INTEGER NOT NULL DEFAULT 0,
1212
+ created_at TEXT NOT NULL
1213
+ );
1214
+ `);
1215
+ await client.executeMultiple(`
1216
+ CREATE TABLE IF NOT EXISTS device_registry (
1217
+ device_id TEXT PRIMARY KEY,
1218
+ friendly_name TEXT NOT NULL,
1219
+ hostname TEXT NOT NULL,
1220
+ projects TEXT NOT NULL DEFAULT '[]',
1221
+ agents TEXT NOT NULL DEFAULT '[]',
1222
+ connected INTEGER DEFAULT 0,
1223
+ last_seen TEXT NOT NULL
784
1224
  );
785
1225
  `);
786
1226
  await client.executeMultiple(`
@@ -1222,498 +1662,115 @@ async function ensureSchema() {
1222
1662
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1223
1663
  END;
1224
1664
 
1225
- CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
1226
- INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
1227
- VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
1228
- END;
1229
-
1230
- CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
1231
- INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
1232
- VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
1233
- INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
1234
- VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1235
- END;
1236
- `);
1237
- try {
1238
- await client.execute({
1239
- sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
1240
- args: []
1241
- });
1242
- } catch {
1243
- }
1244
- try {
1245
- await client.execute(
1246
- `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
1247
- );
1248
- } catch {
1249
- }
1250
- try {
1251
- await client.execute({
1252
- sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
1253
- args: []
1254
- });
1255
- await client.execute({
1256
- sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
1257
- args: []
1258
- });
1259
- } catch {
1260
- }
1261
- try {
1262
- await client.execute({
1263
- sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
1264
- args: []
1265
- });
1266
- } catch {
1267
- }
1268
- try {
1269
- await client.execute(
1270
- `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
1271
- );
1272
- } catch {
1273
- }
1274
- for (const col of [
1275
- "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
1276
- "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
1277
- ]) {
1278
- try {
1279
- await client.execute(col);
1280
- } catch {
1281
- }
1282
- }
1283
- }
1284
- async function disposeDatabase() {
1285
- if (_client) {
1286
- _client.close();
1287
- _client = null;
1288
- _resilientClient = null;
1289
- }
1290
- }
1291
- var _client, _resilientClient, initTurso, disposeTurso;
1292
- var init_database = __esm({
1293
- "src/lib/database.ts"() {
1294
- "use strict";
1295
- init_db_retry();
1296
- _client = null;
1297
- _resilientClient = null;
1298
- initTurso = initDatabase;
1299
- disposeTurso = disposeDatabase;
1300
- }
1301
- });
1302
-
1303
- // src/lib/config.ts
1304
- var config_exports = {};
1305
- __export(config_exports, {
1306
- CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
1307
- CONFIG_PATH: () => CONFIG_PATH,
1308
- COO_AGENT_NAME: () => COO_AGENT_NAME,
1309
- CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
1310
- DB_PATH: () => DB_PATH,
1311
- EXE_AI_DIR: () => EXE_AI_DIR,
1312
- LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
1313
- MODELS_DIR: () => MODELS_DIR,
1314
- loadConfig: () => loadConfig,
1315
- loadConfigFrom: () => loadConfigFrom,
1316
- loadConfigSync: () => loadConfigSync,
1317
- migrateConfig: () => migrateConfig,
1318
- saveConfig: () => saveConfig
1319
- });
1320
- import { readFile, writeFile, mkdir, chmod } from "fs/promises";
1321
- import { readFileSync as readFileSync4, existsSync as existsSync4, renameSync as renameSync2 } from "fs";
1322
- import path3 from "path";
1323
- import os3 from "os";
1324
- function resolveDataDir() {
1325
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
1326
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
1327
- const newDir = path3.join(os3.homedir(), ".exe-os");
1328
- const legacyDir = path3.join(os3.homedir(), ".exe-mem");
1329
- if (!existsSync4(newDir) && existsSync4(legacyDir)) {
1330
- try {
1331
- renameSync2(legacyDir, newDir);
1332
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
1333
- `);
1334
- } catch {
1335
- return legacyDir;
1336
- }
1337
- }
1338
- return newDir;
1339
- }
1340
- function migrateLegacyConfig(raw) {
1341
- if ("r2" in raw) {
1342
- process.stderr.write(
1343
- "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
1344
- );
1345
- delete raw.r2;
1346
- }
1347
- if ("syncIntervalMs" in raw) {
1348
- delete raw.syncIntervalMs;
1349
- }
1350
- return raw;
1351
- }
1352
- function migrateConfig(raw) {
1353
- const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
1354
- let currentVersion = fromVersion;
1355
- let migrated = false;
1356
- if (currentVersion > CURRENT_CONFIG_VERSION) {
1357
- return { config: raw, migrated: false, fromVersion };
1358
- }
1359
- for (const migration of CONFIG_MIGRATIONS) {
1360
- if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
1361
- raw = migration.migrate(raw);
1362
- currentVersion = migration.to;
1363
- migrated = true;
1364
- }
1365
- }
1366
- return { config: raw, migrated, fromVersion };
1367
- }
1368
- function normalizeScalingRoadmap(raw) {
1369
- const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
1370
- const userRoadmap = raw.scalingRoadmap ?? {};
1371
- const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
1372
- if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
1373
- userAuto.enabled = raw.rerankerEnabled;
1374
- }
1375
- raw.scalingRoadmap = {
1376
- ...userRoadmap,
1377
- rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
1378
- };
1379
- }
1380
- function normalizeSessionLifecycle(raw) {
1381
- const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
1382
- const userSL = raw.sessionLifecycle ?? {};
1383
- raw.sessionLifecycle = { ...defaultSL, ...userSL };
1384
- }
1385
- function normalizeAutoUpdate(raw) {
1386
- const defaultAU = DEFAULT_CONFIG.autoUpdate;
1387
- const userAU = raw.autoUpdate ?? {};
1388
- raw.autoUpdate = { ...defaultAU, ...userAU };
1389
- }
1390
- async function loadConfig() {
1391
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
1392
- await mkdir(dir, { recursive: true });
1393
- const configPath = path3.join(dir, "config.json");
1394
- if (!existsSync4(configPath)) {
1395
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
1396
- }
1397
- const raw = await readFile(configPath, "utf-8");
1398
- try {
1399
- let parsed = JSON.parse(raw);
1400
- parsed = migrateLegacyConfig(parsed);
1401
- const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
1402
- if (migrated) {
1403
- process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
1404
- `);
1405
- try {
1406
- await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
1407
- } catch {
1408
- }
1409
- }
1410
- normalizeScalingRoadmap(migratedCfg);
1411
- normalizeSessionLifecycle(migratedCfg);
1412
- normalizeAutoUpdate(migratedCfg);
1413
- const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
1414
- if (config.dbPath.startsWith("~")) {
1415
- config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
1416
- }
1417
- return config;
1418
- } catch {
1419
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
1420
- }
1421
- }
1422
- function loadConfigSync() {
1423
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
1424
- const configPath = path3.join(dir, "config.json");
1425
- if (!existsSync4(configPath)) {
1426
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
1427
- }
1428
- try {
1429
- const raw = readFileSync4(configPath, "utf-8");
1430
- let parsed = JSON.parse(raw);
1431
- parsed = migrateLegacyConfig(parsed);
1432
- const { config: migratedCfg } = migrateConfig(parsed);
1433
- normalizeScalingRoadmap(migratedCfg);
1434
- normalizeSessionLifecycle(migratedCfg);
1435
- normalizeAutoUpdate(migratedCfg);
1436
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
1437
- } catch {
1438
- return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
1439
- }
1440
- }
1441
- async function saveConfig(config) {
1442
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
1443
- await mkdir(dir, { recursive: true });
1444
- const configPath = path3.join(dir, "config.json");
1445
- await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
1446
- if (config.cloud?.apiKey) {
1447
- await chmod(configPath, 384);
1448
- }
1449
- }
1450
- async function loadConfigFrom(configPath) {
1451
- const raw = await readFile(configPath, "utf-8");
1452
- try {
1453
- let parsed = JSON.parse(raw);
1454
- parsed = migrateLegacyConfig(parsed);
1455
- const { config: migratedCfg } = migrateConfig(parsed);
1456
- normalizeScalingRoadmap(migratedCfg);
1457
- normalizeSessionLifecycle(migratedCfg);
1458
- normalizeAutoUpdate(migratedCfg);
1459
- return { ...DEFAULT_CONFIG, ...migratedCfg };
1460
- } catch {
1461
- return { ...DEFAULT_CONFIG };
1462
- }
1463
- }
1464
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1465
- var init_config = __esm({
1466
- "src/lib/config.ts"() {
1467
- "use strict";
1468
- EXE_AI_DIR = resolveDataDir();
1469
- DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
1470
- MODELS_DIR = path3.join(EXE_AI_DIR, "models");
1471
- CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
1472
- COO_AGENT_NAME = "exe";
1473
- LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
1474
- CURRENT_CONFIG_VERSION = 1;
1475
- DEFAULT_CONFIG = {
1476
- config_version: CURRENT_CONFIG_VERSION,
1477
- dbPath: DB_PATH,
1478
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
1479
- embeddingDim: 1024,
1480
- batchSize: 20,
1481
- flushIntervalMs: 1e4,
1482
- autoIngestion: true,
1483
- autoRetrieval: true,
1484
- searchMode: "hybrid",
1485
- hookSearchMode: "hybrid",
1486
- fileGrepEnabled: true,
1487
- splashEffect: true,
1488
- consolidationEnabled: true,
1489
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
1490
- consolidationModel: "claude-haiku-4-5-20251001",
1491
- consolidationMaxCallsPerRun: 20,
1492
- selfQueryRouter: true,
1493
- selfQueryModel: "claude-haiku-4-5-20251001",
1494
- rerankerEnabled: true,
1495
- scalingRoadmap: {
1496
- rerankerAutoTrigger: {
1497
- enabled: true,
1498
- broadQueryMinCardinality: 5e4,
1499
- fetchTopK: 150,
1500
- returnTopK: 5
1501
- }
1502
- },
1503
- graphRagEnabled: true,
1504
- wikiEnabled: false,
1505
- wikiUrl: "",
1506
- wikiApiKey: "",
1507
- wikiSyncIntervalMs: 30 * 60 * 1e3,
1508
- wikiWorkspaceMapping: {
1509
- exe: "Executive",
1510
- yoshi: "Engineering",
1511
- mari: "Marketing",
1512
- tom: "Engineering",
1513
- sasha: "Production"
1514
- },
1515
- wikiAutoUpdate: true,
1516
- wikiAutoUpdateThreshold: 0.5,
1517
- wikiAutoUpdateCreateNew: true,
1518
- skillLearning: true,
1519
- skillThreshold: 3,
1520
- skillModel: "claude-haiku-4-5-20251001",
1521
- exeHeartbeat: {
1522
- enabled: true,
1523
- intervalSeconds: 60,
1524
- staleInProgressThresholdHours: 2
1525
- },
1526
- sessionLifecycle: {
1527
- idleKillEnabled: true,
1528
- idleKillTicksRequired: 3,
1529
- idleKillIntercomAckWindowMs: 1e4,
1530
- maxAutoInstances: 10
1531
- },
1532
- autoUpdate: {
1533
- checkOnBoot: true,
1534
- autoInstall: false,
1535
- checkIntervalMs: 24 * 60 * 60 * 1e3
1536
- }
1537
- };
1538
- CONFIG_MIGRATIONS = [
1539
- {
1540
- from: 0,
1541
- to: 1,
1542
- migrate: (cfg) => {
1543
- cfg.config_version = 1;
1544
- return cfg;
1545
- }
1546
- }
1547
- ];
1548
- }
1549
- });
1550
-
1551
- // src/lib/employees.ts
1552
- var employees_exports = {};
1553
- __export(employees_exports, {
1554
- EMPLOYEES_PATH: () => EMPLOYEES_PATH,
1555
- addEmployee: () => addEmployee,
1556
- getEmployee: () => getEmployee,
1557
- getEmployeeByRole: () => getEmployeeByRole,
1558
- getEmployeeNamesByRole: () => getEmployeeNamesByRole,
1559
- hasRole: () => hasRole,
1560
- isMultiInstance: () => isMultiInstance,
1561
- loadEmployees: () => loadEmployees,
1562
- loadEmployeesSync: () => loadEmployeesSync,
1563
- normalizeRosterCase: () => normalizeRosterCase,
1564
- registerBinSymlinks: () => registerBinSymlinks,
1565
- saveEmployees: () => saveEmployees,
1566
- validateEmployeeName: () => validateEmployeeName
1567
- });
1568
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1569
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
1570
- import { execSync as execSync4 } from "child_process";
1571
- import path4 from "path";
1572
- import os4 from "os";
1573
- function validateEmployeeName(name) {
1574
- if (!name) {
1575
- return { valid: false, error: "Name is required" };
1576
- }
1577
- if (name.length > 32) {
1578
- return { valid: false, error: "Name must be 32 characters or fewer" };
1579
- }
1580
- if (!/^[a-z][a-z0-9]*$/.test(name)) {
1581
- return {
1582
- valid: false,
1583
- error: "Name must start with a letter and contain only lowercase alphanumeric characters"
1584
- };
1665
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
1666
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
1667
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
1668
+ END;
1669
+
1670
+ CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
1671
+ INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
1672
+ VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
1673
+ INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
1674
+ VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1675
+ END;
1676
+ `);
1677
+ try {
1678
+ await client.execute({
1679
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
1680
+ args: []
1681
+ });
1682
+ } catch {
1585
1683
  }
1586
- return { valid: true };
1587
- }
1588
- async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1589
- if (!existsSync5(employeesPath)) {
1590
- return [];
1684
+ try {
1685
+ await client.execute(
1686
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
1687
+ );
1688
+ } catch {
1591
1689
  }
1592
- const raw = await readFile2(employeesPath, "utf-8");
1593
1690
  try {
1594
- return JSON.parse(raw);
1691
+ await client.execute({
1692
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
1693
+ args: []
1694
+ });
1695
+ await client.execute({
1696
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
1697
+ args: []
1698
+ });
1595
1699
  } catch {
1596
- return [];
1597
1700
  }
1598
- }
1599
- async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
1600
- await mkdir2(path4.dirname(employeesPath), { recursive: true });
1601
- await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
1602
- }
1603
- function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1604
- if (!existsSync5(employeesPath)) return [];
1605
1701
  try {
1606
- return JSON.parse(readFileSync5(employeesPath, "utf-8"));
1702
+ await client.execute({
1703
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
1704
+ args: []
1705
+ });
1607
1706
  } catch {
1608
- return [];
1609
1707
  }
1610
- }
1611
- function getEmployee(employees, name) {
1612
- return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
1613
- }
1614
- function getEmployeeByRole(employees, role) {
1615
- const lower = role.toLowerCase();
1616
- return employees.find((e) => e.role.toLowerCase() === lower);
1617
- }
1618
- function getEmployeeNamesByRole(employees, role) {
1619
- const lower = role.toLowerCase();
1620
- return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
1621
- }
1622
- function hasRole(agentName, role) {
1623
- const employees = loadEmployeesSync();
1624
- const emp = getEmployee(employees, agentName);
1625
- return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
1626
- }
1627
- function isMultiInstance(agentName, employees) {
1628
- const roster = employees ?? loadEmployeesSync();
1629
- const emp = getEmployee(roster, agentName);
1630
- if (!emp) return false;
1631
- return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
1632
- }
1633
- function addEmployee(employees, employee) {
1634
- const normalized = { ...employee, name: employee.name.toLowerCase() };
1635
- if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
1636
- throw new Error(`Employee '${normalized.name}' already exists`);
1708
+ try {
1709
+ await client.execute(
1710
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
1711
+ );
1712
+ } catch {
1637
1713
  }
1638
- return [...employees, normalized];
1639
- }
1640
- async function normalizeRosterCase(rosterPath) {
1641
- const employees = await loadEmployees(rosterPath);
1642
- let changed = false;
1643
- for (const emp of employees) {
1644
- if (emp.name !== emp.name.toLowerCase()) {
1645
- const oldName = emp.name;
1646
- emp.name = emp.name.toLowerCase();
1647
- changed = true;
1648
- try {
1649
- const identityDir = path4.join(os4.homedir(), ".exe-os", "identity");
1650
- const oldPath = path4.join(identityDir, `${oldName}.md`);
1651
- const newPath = path4.join(identityDir, `${emp.name}.md`);
1652
- if (existsSync5(oldPath) && !existsSync5(newPath)) {
1653
- renameSync3(oldPath, newPath);
1654
- } else if (existsSync5(oldPath) && oldPath !== newPath) {
1655
- const content = readFileSync5(oldPath, "utf-8");
1656
- writeFileSync3(newPath, content, "utf-8");
1657
- if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
1658
- unlinkSync(oldPath);
1659
- }
1660
- }
1661
- } catch {
1662
- }
1714
+ for (const col of [
1715
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
1716
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
1717
+ ]) {
1718
+ try {
1719
+ await client.execute(col);
1720
+ } catch {
1663
1721
  }
1664
1722
  }
1665
- if (changed) {
1666
- await saveEmployees(employees, rosterPath);
1723
+ try {
1724
+ await client.execute({
1725
+ sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
1726
+ args: []
1727
+ });
1728
+ } catch {
1667
1729
  }
1668
- return changed;
1669
- }
1670
- function findExeBin() {
1671
1730
  try {
1672
- return execSync4(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
1731
+ await client.execute(
1732
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
1733
+ );
1673
1734
  } catch {
1674
- return null;
1675
1735
  }
1676
- }
1677
- function registerBinSymlinks(name) {
1678
- const created = [];
1679
- const skipped = [];
1680
- const errors = [];
1681
- const exeBinPath = findExeBin();
1682
- if (!exeBinPath) {
1683
- errors.push("Could not find 'exe-os' in PATH");
1684
- return { created, skipped, errors };
1736
+ try {
1737
+ await client.execute({
1738
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
1739
+ args: []
1740
+ });
1741
+ } catch {
1685
1742
  }
1686
- const binDir = path4.dirname(exeBinPath);
1687
- let target;
1688
1743
  try {
1689
- target = readlinkSync(exeBinPath);
1744
+ await client.execute(
1745
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
1746
+ );
1690
1747
  } catch {
1691
- errors.push("Could not read 'exe' symlink");
1692
- return { created, skipped, errors };
1693
1748
  }
1694
- for (const suffix of ["", "-opencode"]) {
1695
- const linkName = `${name}${suffix}`;
1696
- const linkPath = path4.join(binDir, linkName);
1697
- if (existsSync5(linkPath)) {
1698
- skipped.push(linkName);
1699
- continue;
1700
- }
1701
- try {
1702
- symlinkSync(target, linkPath);
1703
- created.push(linkName);
1704
- } catch (err) {
1705
- errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
1706
- }
1749
+ try {
1750
+ await client.execute({
1751
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
1752
+ args: []
1753
+ });
1754
+ } catch {
1707
1755
  }
1708
- return { created, skipped, errors };
1709
1756
  }
1710
- var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
1711
- var init_employees = __esm({
1712
- "src/lib/employees.ts"() {
1757
+ async function disposeDatabase() {
1758
+ if (_client) {
1759
+ _client.close();
1760
+ _client = null;
1761
+ _resilientClient = null;
1762
+ }
1763
+ }
1764
+ var _client, _resilientClient, initTurso, disposeTurso;
1765
+ var init_database = __esm({
1766
+ "src/lib/database.ts"() {
1713
1767
  "use strict";
1714
- init_config();
1715
- EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
1716
- MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1768
+ init_db_retry();
1769
+ init_employees();
1770
+ _client = null;
1771
+ _resilientClient = null;
1772
+ initTurso = initDatabase;
1773
+ disposeTurso = disposeDatabase;
1717
1774
  }
1718
1775
  });
1719
1776
 
@@ -2333,6 +2390,7 @@ __export(tasks_crud_exports, {
2333
2390
  ensureArchitectureDoc: () => ensureArchitectureDoc,
2334
2391
  ensureGitignoreExe: () => ensureGitignoreExe,
2335
2392
  extractParentFromContext: () => extractParentFromContext,
2393
+ isTmuxSessionAlive: () => isTmuxSessionAlive,
2336
2394
  listTasks: () => listTasks,
2337
2395
  resolveTask: () => resolveTask,
2338
2396
  slugify: () => slugify,
@@ -2583,6 +2641,36 @@ async function listTasks(input) {
2583
2641
  tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
2584
2642
  }));
2585
2643
  }
2644
+ function isTmuxSessionAlive(identifier) {
2645
+ if (!identifier || identifier === "unknown") return true;
2646
+ try {
2647
+ if (identifier.startsWith("%")) {
2648
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
2649
+ timeout: 2e3,
2650
+ encoding: "utf8",
2651
+ stdio: ["pipe", "pipe", "pipe"]
2652
+ });
2653
+ return output.split("\n").some((l) => l.trim() === identifier);
2654
+ } else {
2655
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
2656
+ timeout: 2e3,
2657
+ stdio: ["pipe", "pipe", "pipe"]
2658
+ });
2659
+ return true;
2660
+ }
2661
+ } catch {
2662
+ if (identifier.startsWith("%")) return true;
2663
+ try {
2664
+ execSync5("tmux list-sessions", {
2665
+ timeout: 2e3,
2666
+ stdio: ["pipe", "pipe", "pipe"]
2667
+ });
2668
+ return false;
2669
+ } catch {
2670
+ return true;
2671
+ }
2672
+ }
2673
+ }
2586
2674
  function checkStaleCompletion(taskContext, taskCreatedAt) {
2587
2675
  if (!taskContext) return null;
2588
2676
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
@@ -2645,13 +2733,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
2645
2733
  });
2646
2734
  if (claim.rowsAffected === 0) {
2647
2735
  const current = await client.execute({
2648
- sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
2736
+ sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
2649
2737
  args: [taskId]
2650
2738
  });
2651
2739
  const cur = current.rows[0];
2652
- const status = cur?.status ?? "unknown";
2653
- const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
2654
- throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
2740
+ const curStatus = cur?.status ?? "unknown";
2741
+ const claimedBySession = cur?.assigned_tmux ?? "";
2742
+ const assignedBy = cur?.assigned_by ?? "";
2743
+ if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
2744
+ process.stderr.write(
2745
+ `[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
2746
+ `
2747
+ );
2748
+ await client.execute({
2749
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
2750
+ args: [now, taskId]
2751
+ });
2752
+ const retried = await client.execute({
2753
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
2754
+ args: [tmuxSession, now, taskId]
2755
+ });
2756
+ if (retried.rowsAffected > 0) {
2757
+ try {
2758
+ await writeCheckpoint({
2759
+ taskId,
2760
+ step: "reclaimed_dead_session",
2761
+ contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
2762
+ });
2763
+ } catch {
2764
+ }
2765
+ return { row, taskFile, now, taskId };
2766
+ }
2767
+ }
2768
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
2769
+ process.stderr.write(
2770
+ `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
2771
+ `
2772
+ );
2773
+ await client.execute({
2774
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
2775
+ args: [tmuxSession, now, taskId]
2776
+ });
2777
+ try {
2778
+ await writeCheckpoint({
2779
+ taskId,
2780
+ step: "assigner_override",
2781
+ contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
2782
+ });
2783
+ } catch {
2784
+ }
2785
+ return { row, taskFile, now, taskId };
2786
+ }
2787
+ const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
2788
+ throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
2655
2789
  }
2656
2790
  try {
2657
2791
  await writeCheckpoint({
@@ -2749,7 +2883,7 @@ var init_tasks_crud = __esm({
2749
2883
  "use strict";
2750
2884
  init_database();
2751
2885
  init_task_scope();
2752
- DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
2886
+ DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2753
2887
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2754
2888
  }
2755
2889
  });
@@ -3106,7 +3240,7 @@ function findSessionForProject(projectName) {
3106
3240
  const sessions = listSessions();
3107
3241
  for (const s of sessions) {
3108
3242
  const proj = s.projectDir.split("/").filter(Boolean).pop();
3109
- if (proj === projectName && s.agentId === "exe") return s;
3243
+ if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3110
3244
  }
3111
3245
  return null;
3112
3246
  }
@@ -3146,12 +3280,13 @@ var init_session_scope = __esm({
3146
3280
  init_session_registry();
3147
3281
  init_project_name();
3148
3282
  init_tmux_routing();
3283
+ init_employees();
3149
3284
  }
3150
3285
  });
3151
3286
 
3152
3287
  // src/lib/tasks-notify.ts
3153
3288
  async function dispatchTaskToEmployee(input) {
3154
- if (input.assignedTo === "exe") return { dispatched: "skipped" };
3289
+ if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3155
3290
  let crossProject = false;
3156
3291
  if (input.projectName) {
3157
3292
  try {
@@ -3594,6 +3729,24 @@ async function updateTask(input) {
3594
3729
  });
3595
3730
  } catch {
3596
3731
  }
3732
+ const assignedAgent = String(row.assigned_to);
3733
+ if (!isCoordinatorName(assignedAgent)) {
3734
+ try {
3735
+ const draftClient = getClient();
3736
+ if (input.status === "done") {
3737
+ await draftClient.execute({
3738
+ sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
3739
+ args: [assignedAgent]
3740
+ });
3741
+ } else if (input.status === "cancelled") {
3742
+ await draftClient.execute({
3743
+ sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
3744
+ args: [assignedAgent]
3745
+ });
3746
+ }
3747
+ } catch {
3748
+ }
3749
+ }
3597
3750
  try {
3598
3751
  const client = getClient();
3599
3752
  const cascaded = await client.execute({
@@ -3612,8 +3765,8 @@ async function updateTask(input) {
3612
3765
  }
3613
3766
  const isTerminal = input.status === "done" || input.status === "needs_review";
3614
3767
  if (isTerminal) {
3615
- const isExe = String(row.assigned_to) === "exe";
3616
- if (!isExe) {
3768
+ const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
3769
+ if (!isCoordinator) {
3617
3770
  notifyTaskDone();
3618
3771
  }
3619
3772
  await markTaskNotificationsRead(taskFile);
@@ -3637,7 +3790,7 @@ async function updateTask(input) {
3637
3790
  }
3638
3791
  }
3639
3792
  }
3640
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
3793
+ if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3641
3794
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3642
3795
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3643
3796
  taskId,
@@ -3653,7 +3806,7 @@ async function updateTask(input) {
3653
3806
  });
3654
3807
  }
3655
3808
  let nextTask;
3656
- if (isTerminal && String(row.assigned_to) !== "exe") {
3809
+ if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
3657
3810
  try {
3658
3811
  nextTask = await findNextTask(String(row.assigned_to));
3659
3812
  } catch {
@@ -3680,12 +3833,14 @@ async function updateTask(input) {
3680
3833
  async function deleteTask(taskId, baseDir) {
3681
3834
  const client = getClient();
3682
3835
  const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
3683
- const reviewer = assignedBy || "exe";
3836
+ const coordinatorName = getCoordinatorName();
3837
+ const reviewer = assignedBy || coordinatorName;
3684
3838
  const reviewSlug = `review-${assignedTo}-${taskSlug}`;
3685
3839
  const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
3840
+ const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
3686
3841
  await client.execute({
3687
- sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
3688
- args: [reviewFile, `exe/exe/${reviewSlug}.md`]
3842
+ sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
3843
+ args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
3689
3844
  });
3690
3845
  await markAsReadByTaskFile(taskFile);
3691
3846
  await markAsReadByTaskFile(reviewFile);
@@ -3697,6 +3852,7 @@ var init_tasks = __esm({
3697
3852
  init_config();
3698
3853
  init_notifications();
3699
3854
  init_state_bus();
3855
+ init_employees();
3700
3856
  init_tasks_crud();
3701
3857
  init_tasks_review();
3702
3858
  init_tasks_crud();
@@ -3782,7 +3938,7 @@ function _resetLastRelaunchCache() {
3782
3938
  }
3783
3939
  async function lastResumeCreatedAtMs(agentId) {
3784
3940
  const client = getClient();
3785
- const cmScope = sessionScopeFilter();
3941
+ const cmScope = sessionScopeFilter(null);
3786
3942
  const result = await client.execute({
3787
3943
  sql: `SELECT MAX(created_at) AS last_created_at
3788
3944
  FROM tasks
@@ -3807,7 +3963,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
3807
3963
  const client = getClient();
3808
3964
  const now = (/* @__PURE__ */ new Date()).toISOString();
3809
3965
  const context = buildResumeContext(agentId, openTasks);
3810
- const rdScope = sessionScopeFilter();
3966
+ const rdScope = sessionScopeFilter(null);
3811
3967
  const existing = await client.execute({
3812
3968
  sql: `SELECT id FROM tasks
3813
3969
  WHERE assigned_to = ?
@@ -3841,7 +3997,7 @@ async function pollCapacityDead() {
3841
3997
  const transport = getTransport();
3842
3998
  const relaunched = [];
3843
3999
  const registered = listSessions().filter(
3844
- (s) => s.agentId !== "exe"
4000
+ (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
3845
4001
  );
3846
4002
  if (registered.length === 0) return [];
3847
4003
  let liveSessions;
@@ -3901,7 +4057,7 @@ async function pollCapacityDead() {
3901
4057
  reason: "capacity"
3902
4058
  });
3903
4059
  const client = getClient();
3904
- const rlScope = sessionScopeFilter();
4060
+ const rlScope = sessionScopeFilter(null);
3905
4061
  const openTasks = await client.execute({
3906
4062
  sql: `SELECT id, title, priority, task_file, status
3907
4063
  FROM tasks
@@ -3955,6 +4111,7 @@ var init_capacity_monitor = __esm({
3955
4111
  init_session_kill_telemetry();
3956
4112
  init_tmux_routing();
3957
4113
  init_task_scope();
4114
+ init_employees();
3958
4115
  CAPACITY_PATTERNS = [
3959
4116
  /conversation is too long/i,
3960
4117
  /maximum context length/i,
@@ -4093,7 +4250,7 @@ function getMySession() {
4093
4250
  function isRootSession(name) {
4094
4251
  return name.length > 0 && !name.includes("-");
4095
4252
  }
4096
- function employeeSessionName(employee, exeSession, instance) {
4253
+ function employeeSessionName(employee, exeSession, instance2) {
4097
4254
  if (!isRootSession(exeSession)) {
4098
4255
  const root = extractRootExe(exeSession);
4099
4256
  if (root) {
@@ -4104,11 +4261,11 @@ function employeeSessionName(employee, exeSession, instance) {
4104
4261
  exeSession = root;
4105
4262
  } else {
4106
4263
  throw new Error(
4107
- `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
4264
+ `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
4108
4265
  );
4109
4266
  }
4110
4267
  }
4111
- const suffix = instance != null && instance > 0 ? String(instance) : "";
4268
+ const suffix = instance2 != null && instance2 > 0 ? String(instance2) : "";
4112
4269
  const name = `${employee}${suffix}-${exeSession}`;
4113
4270
  if (!VALID_SESSION_NAME.test(name)) {
4114
4271
  throw new Error(
@@ -4124,8 +4281,10 @@ function parseParentExe(sessionName, agentId) {
4124
4281
  return match?.[1] ?? null;
4125
4282
  }
4126
4283
  function extractRootExe(name) {
4127
- const match = name.match(/(exe\d+)$/);
4128
- return match?.[1] ?? null;
4284
+ if (!name) return null;
4285
+ if (!name.includes("-")) return name;
4286
+ const parts = name.split("-").filter(Boolean);
4287
+ return parts.length > 0 ? parts[parts.length - 1] : null;
4129
4288
  }
4130
4289
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4131
4290
  if (!existsSync11(SESSION_CACHE)) {
@@ -4270,12 +4429,14 @@ function isSessionBusy(sessionName) {
4270
4429
  return state === "thinking" || state === "tool";
4271
4430
  }
4272
4431
  function isExeSession(sessionName) {
4273
- return /^exe\d*$/.test(sessionName);
4432
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
4433
+ const coordinatorName = getCoordinatorName();
4434
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
4274
4435
  }
4275
4436
  function sendIntercom(targetSession) {
4276
4437
  const transport = getTransport();
4277
4438
  if (isExeSession(targetSession)) {
4278
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
4439
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
4279
4440
  return "skipped_exe";
4280
4441
  }
4281
4442
  if (isDebounced(targetSession)) {
@@ -4327,7 +4488,7 @@ function notifyParentExe(sessionKey) {
4327
4488
  if (result === "failed") {
4328
4489
  const rootExe = resolveExeSession();
4329
4490
  if (rootExe && rootExe !== target) {
4330
- process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
4491
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
4331
4492
  `);
4332
4493
  const fallback = sendIntercom(rootExe);
4333
4494
  return fallback !== "failed";
@@ -4337,8 +4498,8 @@ function notifyParentExe(sessionKey) {
4337
4498
  return true;
4338
4499
  }
4339
4500
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4340
- if (employeeName === "exe") {
4341
- return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
4501
+ if (employeeName === "exe" || isCoordinatorName(employeeName)) {
4502
+ return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
4342
4503
  }
4343
4504
  try {
4344
4505
  assertEmployeeLimitSync();
@@ -4347,8 +4508,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4347
4508
  return { status: "failed", sessionName: "", error: err.message };
4348
4509
  }
4349
4510
  }
4350
- if (/-exe\d*$/.test(employeeName)) {
4351
- const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
4511
+ if (employeeName.includes("-")) {
4512
+ const bare = employeeName.split("-")[0].replace(/\d+$/, "");
4352
4513
  return {
4353
4514
  status: "failed",
4354
4515
  sessionName: "",
@@ -4367,7 +4528,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
4367
4528
  return {
4368
4529
  status: "failed",
4369
4530
  sessionName: "",
4370
- error: `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
4531
+ error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
4371
4532
  };
4372
4533
  }
4373
4534
  }
@@ -4524,8 +4685,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4524
4685
  const ctxContent = [
4525
4686
  `## Session Context`,
4526
4687
  `You are running in tmux session: ${sessionName}.`,
4527
- `Your parent exe session is ${exeSession}.`,
4528
- `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
4688
+ `Your parent coordinator session is ${exeSession}.`,
4689
+ `Your employees (if any) use the -${exeSession} suffix.`
4529
4690
  ].join("\n");
4530
4691
  writeFileSync6(ctxFile, ctxContent);
4531
4692
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
@@ -4629,6 +4790,7 @@ var init_tmux_routing = __esm({
4629
4790
  init_provider_table();
4630
4791
  init_intercom_queue();
4631
4792
  init_plan_limits();
4793
+ init_employees();
4632
4794
  SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
4633
4795
  SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
4634
4796
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
@@ -5922,26 +6084,26 @@ var init_platform_procedures = __esm({
5922
6084
  title: "What is exe-os \u2014 the operating model every agent must understand",
5923
6085
  domain: "architecture",
5924
6086
  priority: "p0",
5925
- content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO (exe), CTO (yoshi), CMO (mari), engineers (tom), content (sasha). Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
6087
+ content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
5926
6088
  },
5927
6089
  {
5928
6090
  title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
5929
6091
  domain: "architecture",
5930
6092
  priority: "p0",
5931
- content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC, runs /exe to boot the COO. exe manages employees in tmux sessions. Each exeN is a separate CC window/project. Employees (yoshi, tom, mari) run in their own tmux panes via create_task auto-spawn. The founder talks to exe; exe orchestrates the team. CC is the shell, exe-os is the brain."
6093
+ content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
5932
6094
  },
5933
6095
  {
5934
- title: "Sessions explained \u2014 what exeN means and how projects work",
6096
+ title: "Sessions explained \u2014 coordinator session names and projects",
5935
6097
  domain: "architecture",
5936
6098
  priority: "p0",
5937
- content: "Each exeN (exe1, exe2, exe3) is an isolated project session. exe1 might be exe-os development, exe2 might be exe-wiki. Each session spawns its own employees: exe1\u2192yoshi-exe1\u2192tom-exe1. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
6099
+ content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
5938
6100
  },
5939
6101
  // --- Hierarchy and dispatch ---
5940
6102
  {
5941
6103
  title: "Chain of command \u2014 who talks to whom",
5942
6104
  domain: "workflow",
5943
6105
  priority: "p0",
5944
- content: "Founder \u2192 exe (COO) \u2192 yoshi (CTO) / mari (CMO). Yoshi \u2192 tom (engineer). Mari \u2192 sasha (content). Never skip levels: exe never assigns directly to tom. Tom never reports directly to exe. 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."
6106
+ content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO 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."
5945
6107
  },
5946
6108
  {
5947
6109
  title: "Single dispatch path \u2014 create_task only",
@@ -5951,30 +6113,30 @@ var init_platform_procedures = __esm({
5951
6113
  },
5952
6114
  // --- Session isolation ---
5953
6115
  {
5954
- title: "Session scoping \u2014 stay in your exe boundary",
6116
+ title: "Session scoping \u2014 stay in your coordinator boundary",
5955
6117
  domain: "security",
5956
6118
  priority: "p0",
5957
- content: "Session scoping is mandatory. Managers dispatch to workers within their own exe session ONLY. exe1\u2192yoshi-exe1\u2192tom-exe1. exe2\u2192yoshi-exe2\u2192tom2-exe2. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating exe session."
6119
+ content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
5958
6120
  },
5959
6121
  {
5960
6122
  title: "Session isolation \u2014 never touch another session's work",
5961
6123
  domain: "workflow",
5962
6124
  priority: "p0",
5963
- content: `Sessions are isolated. exeN owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another exe session. (2) Never review work from a different session \u2014 report "belongs to exeN" and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: yoshi-exe1 works ONLY on exe1 tasks. Cross-session work is a system violation.`
6125
+ content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
5964
6126
  },
5965
6127
  // --- Engineering: session scoping in code ---
5966
6128
  {
5967
6129
  title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
5968
6130
  domain: "architecture",
5969
6131
  priority: "p0",
5970
- content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current exeN. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ exe sessions simultaneously."
6132
+ content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
5971
6133
  },
5972
6134
  // --- Hard constraints ---
5973
6135
  {
5974
6136
  title: "What you CANNOT do in exe-os \u2014 hard constraints",
5975
6137
  domain: "security",
5976
6138
  priority: "p0",
5977
- 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 exe reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
6139
+ 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."
5978
6140
  },
5979
6141
  // --- Operations ---
5980
6142
  {
@@ -7225,7 +7387,7 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
7225
7387
  return { agentId, score: results.length / 5 };
7226
7388
  }
7227
7389
  async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
7228
- let specialists = employees.filter((e) => e.name !== "exe");
7390
+ let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
7229
7391
  if (specialists.length === 0) {
7230
7392
  throw new Error(
7231
7393
  "No specialist employees available. Create one with /exe-new-employee."
@@ -7261,6 +7423,7 @@ var DEFAULT_BLOOM_CONFIG;
7261
7423
  var init_task_router = __esm({
7262
7424
  "src/lib/task-router.ts"() {
7263
7425
  "use strict";
7426
+ init_employees();
7264
7427
  DEFAULT_BLOOM_CONFIG = {
7265
7428
  complexityToTier: {
7266
7429
  routine: "junior",
@@ -7306,6 +7469,7 @@ var STALE_THRESHOLD_MS, MultiAgentOrchestrator;
7306
7469
  var init_orchestrator = __esm({
7307
7470
  "src/runtime/orchestrator.ts"() {
7308
7471
  "use strict";
7472
+ init_employees();
7309
7473
  init_task_router();
7310
7474
  init_tmux_routing();
7311
7475
  init_task_scope();
@@ -7350,7 +7514,7 @@ ${task.context}`,
7350
7514
  await createTaskCore({
7351
7515
  title: task.title,
7352
7516
  assignedTo: targetEmployee.name,
7353
- assignedBy: "exe",
7517
+ assignedBy: getCoordinatorName(),
7354
7518
  projectName: task.projectName,
7355
7519
  priority: task.priority,
7356
7520
  context: task.context,
@@ -7455,15 +7619,16 @@ ${task.context}`,
7455
7619
  const client = getClient2();
7456
7620
  const autoApproved = [];
7457
7621
  const needsReview = [];
7622
+ const coordinatorName = getCoordinatorName();
7458
7623
  const rScope = sessionScopeFilter();
7459
7624
  const reviews = await client.execute({
7460
7625
  sql: `SELECT id, title, assigned_to, priority, result, task_file FROM tasks
7461
- WHERE assigned_to = 'exe'
7626
+ WHERE (assigned_to = ? OR assigned_to = 'exe')
7462
7627
  AND status IN ('open', 'in_progress')
7463
7628
  AND task_file LIKE '%review-%'${rScope.sql}
7464
7629
  ORDER BY priority ASC
7465
7630
  LIMIT 20`,
7466
- args: [...rScope.args]
7631
+ args: [coordinatorName, ...rScope.args]
7467
7632
  });
7468
7633
  for (const row of reviews.rows) {
7469
7634
  const item = {
@@ -8250,7 +8415,11 @@ async function ensureShardSchema(client) {
8250
8415
  "ALTER TABLE memories ADD COLUMN source_path TEXT",
8251
8416
  "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
8252
8417
  "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
8253
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
8418
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
8419
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
8420
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
8421
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
8422
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT"
8254
8423
  ]) {
8255
8424
  try {
8256
8425
  await client.execute(col);
@@ -8491,7 +8660,10 @@ async function writeMemory(record) {
8491
8660
  source_path: record.source_path ?? null,
8492
8661
  source_type: record.source_type ?? null,
8493
8662
  tier: record.tier ?? classifyTier(record),
8494
- supersedes_id: record.supersedes_id ?? null
8663
+ supersedes_id: record.supersedes_id ?? null,
8664
+ draft: record.draft ? 1 : 0,
8665
+ memory_type: record.memory_type ?? "raw",
8666
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
8495
8667
  };
8496
8668
  _pendingRecords.push(dbRow);
8497
8669
  orgBus.emit({
@@ -8546,6 +8718,9 @@ async function flushBatch() {
8546
8718
  const sourceType = row.source_type ?? null;
8547
8719
  const tier = row.tier ?? 3;
8548
8720
  const supersedesId = row.supersedes_id ?? null;
8721
+ const draft = row.draft ? 1 : 0;
8722
+ const memoryType = row.memory_type ?? "raw";
8723
+ const trajectory = row.trajectory ?? null;
8549
8724
  return {
8550
8725
  sql: hasVector ? `INSERT OR IGNORE INTO memories
8551
8726
  (id, agent_id, agent_role, session_id, timestamp,
@@ -8553,15 +8728,15 @@ async function flushBatch() {
8553
8728
  has_error, raw_text, vector, version, task_id, importance, status,
8554
8729
  confidence, last_accessed,
8555
8730
  workspace_id, document_id, user_id, char_offset, page_number,
8556
- source_path, source_type, tier, supersedes_id)
8557
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
8731
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
8732
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
8558
8733
  (id, agent_id, agent_role, session_id, timestamp,
8559
8734
  tool_name, project_name,
8560
8735
  has_error, raw_text, vector, version, task_id, importance, status,
8561
8736
  confidence, last_accessed,
8562
8737
  workspace_id, document_id, user_id, char_offset, page_number,
8563
- source_path, source_type, tier, supersedes_id)
8564
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
8738
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
8739
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
8565
8740
  args: hasVector ? [
8566
8741
  row.id,
8567
8742
  row.agent_id,
@@ -8587,7 +8762,10 @@ async function flushBatch() {
8587
8762
  sourcePath,
8588
8763
  sourceType,
8589
8764
  tier,
8590
- supersedesId
8765
+ supersedesId,
8766
+ draft,
8767
+ memoryType,
8768
+ trajectory
8591
8769
  ] : [
8592
8770
  row.id,
8593
8771
  row.agent_id,
@@ -8612,7 +8790,10 @@ async function flushBatch() {
8612
8790
  sourcePath,
8613
8791
  sourceType,
8614
8792
  tier,
8615
- supersedesId
8793
+ supersedesId,
8794
+ draft,
8795
+ memoryType,
8796
+ trajectory
8616
8797
  ]
8617
8798
  };
8618
8799
  };
@@ -8681,6 +8862,8 @@ async function searchMemories(queryVector, agentId, options) {
8681
8862
  const limit = options?.limit ?? 10;
8682
8863
  const statusFilter = options?.includeArchived ? "" : `
8683
8864
  AND COALESCE(status, 'active') = 'active'`;
8865
+ const draftFilter = options?.includeDrafts ? "" : `
8866
+ AND (draft = 0 OR draft IS NULL)`;
8684
8867
  let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
8685
8868
  tool_name, project_name,
8686
8869
  has_error, raw_text, vector, importance, status,
@@ -8690,7 +8873,7 @@ async function searchMemories(queryVector, agentId, options) {
8690
8873
  source_path, source_type
8691
8874
  FROM memories
8692
8875
  WHERE agent_id = ?
8693
- AND vector IS NOT NULL${statusFilter}
8876
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
8694
8877
  AND COALESCE(confidence, 0.7) >= 0.3`;
8695
8878
  const args = [agentId];
8696
8879
  const scope = buildWikiScopeFilter(options, "");
@@ -8712,6 +8895,10 @@ async function searchMemories(queryVector, agentId, options) {
8712
8895
  sql += ` AND timestamp >= ?`;
8713
8896
  args.push(options.since);
8714
8897
  }
8898
+ if (options?.memoryType) {
8899
+ sql += ` AND memory_type = ?`;
8900
+ args.push(options.memoryType);
8901
+ }
8715
8902
  sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
8716
8903
  args.push(vectorToBlob(queryVector));
8717
8904
  sql += ` LIMIT ?`;
@@ -11608,7 +11795,7 @@ var reconciler_default = createReconciler({
11608
11795
  unhideTextInstance(node, text) {
11609
11796
  setTextNodeValue(node, text);
11610
11797
  },
11611
- getPublicInstance: (instance) => instance,
11798
+ getPublicInstance: (instance2) => instance2,
11612
11799
  hideInstance(node) {
11613
11800
  node.yogaNode?.setDisplay(src_default.DISPLAY_NONE);
11614
11801
  },
@@ -13951,16 +14138,16 @@ var render = (node, options) => {
13951
14138
  concurrent: false,
13952
14139
  ...getOptions(options)
13953
14140
  };
13954
- const instance = getInstance(inkOptions.stdout, () => new Ink(inkOptions), inkOptions.concurrent ?? false);
13955
- instance.render(node);
14141
+ const instance2 = getInstance(inkOptions.stdout, () => new Ink(inkOptions), inkOptions.concurrent ?? false);
14142
+ instance2.render(node);
13956
14143
  return {
13957
- rerender: instance.render,
14144
+ rerender: instance2.render,
13958
14145
  unmount() {
13959
- instance.unmount();
14146
+ instance2.unmount();
13960
14147
  },
13961
- waitUntilExit: instance.waitUntilExit,
14148
+ waitUntilExit: instance2.waitUntilExit,
13962
14149
  cleanup: () => instances_default.delete(inkOptions.stdout),
13963
- clear: instance.clear
14150
+ clear: instance2.clear
13964
14151
  };
13965
14152
  };
13966
14153
  var render_default = render;
@@ -13974,14 +14161,14 @@ var getOptions = (stdout = {}) => {
13974
14161
  return stdout;
13975
14162
  };
13976
14163
  var getInstance = (stdout, createInstance, concurrent) => {
13977
- let instance = instances_default.get(stdout);
13978
- if (!instance) {
13979
- instance = createInstance();
13980
- instances_default.set(stdout, instance);
13981
- } else if (instance.isConcurrent !== concurrent) {
13982
- console.warn(`Warning: render() was called with concurrent: ${concurrent}, but the existing instance for this stdout uses concurrent: ${instance.isConcurrent}. The concurrent option only takes effect on the first render. Call unmount() first if you need to change the rendering mode.`);
13983
- }
13984
- return instance;
14164
+ let instance2 = instances_default.get(stdout);
14165
+ if (!instance2) {
14166
+ instance2 = createInstance();
14167
+ instances_default.set(stdout, instance2);
14168
+ } else if (instance2.isConcurrent !== concurrent) {
14169
+ console.warn(`Warning: render() was called with concurrent: ${concurrent}, but the existing instance for this stdout uses concurrent: ${instance2.isConcurrent}. The concurrent option only takes effect on the first render. Call unmount() first if you need to change the rendering mode.`);
14170
+ }
14171
+ return instance2;
13985
14172
  };
13986
14173
 
13987
14174
  // src/tui/ink/render-to-string.js
@@ -14896,7 +15083,7 @@ function StatusDot({ status, showLabel = true }) {
14896
15083
 
14897
15084
  // src/tui/demo-data.ts
14898
15085
  var DEMO_EMPLOYEES = [
14899
- { name: "exe", role: "COO", status: "active", activity: "Reviewing yoshi's task-aware behavior injection PR", memoryCount: 15e3, projects: [
15086
+ { name: "atlas", role: "COO", status: "active", activity: "Reviewing nova's task-aware behavior injection PR", memoryCount: 15e3, projects: [
14900
15087
  { name: "exe-os", status: "active" },
14901
15088
  { name: "exe-create", status: "has_tasks" },
14902
15089
  { name: "openclaw", status: "idle" }
@@ -14905,7 +15092,7 @@ var DEMO_EMPLOYEES = [
14905
15092
  "Dispatched behavior injection task",
14906
15093
  "Approved gateway Phase 4"
14907
15094
  ] },
14908
- { name: "yoshi", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", memoryCount: 8e3, projects: [
15095
+ { name: "nova", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", memoryCount: 8e3, projects: [
14909
15096
  { name: "exe-os", status: "active" },
14910
15097
  { name: "exe-create", status: "idle" }
14911
15098
  ], recentTasks: [
@@ -14913,7 +15100,7 @@ var DEMO_EMPLOYEES = [
14913
15100
  "Fixed TUI mouse listener leak",
14914
15101
  "Built task-aware behavior injection"
14915
15102
  ] },
14916
- { name: "mari", role: "CMO", status: "idle", activity: "", memoryCount: 2e3, projects: [
15103
+ { name: "lina", role: "CMO", status: "idle", activity: "", memoryCount: 2e3, projects: [
14917
15104
  { name: "exe-build-skills", status: "has_tasks" },
14918
15105
  { name: "exe-os", status: "idle" }
14919
15106
  ], recentTasks: [
@@ -14921,57 +15108,57 @@ var DEMO_EMPLOYEES = [
14921
15108
  "Designed exe-os UI system",
14922
15109
  "Fixed logo layouts"
14923
15110
  ] },
14924
- { name: "tom", role: "Principal Engineer", status: "idle", activity: "", memoryCount: 700, projects: [
15111
+ { name: "devon", role: "Principal Engineer", status: "idle", activity: "", memoryCount: 700, projects: [
14925
15112
  { name: "exe-os", status: "idle" }
14926
15113
  ], recentTasks: [
14927
15114
  "Implemented BashTool sandboxed execution",
14928
15115
  "Ported session scoping to exe-agent-memory"
14929
15116
  ] },
14930
- { name: "sasha", role: "Content Production", status: "offline", activity: "", memoryCount: 50, projects: [
15117
+ { name: "pixel", role: "Content Production", status: "offline", activity: "", memoryCount: 50, projects: [
14931
15118
  { name: "exe-build-skills", status: "idle" }
14932
15119
  ], recentTasks: [
14933
15120
  "Rendered carousel export prototype"
14934
15121
  ] }
14935
15122
  ];
14936
15123
  var DEMO_ACTIVITY = [
14937
- { time: "11:46", agent: "yoshi", action: "Committed skill learning \u2014 trajectory capture + LLM extraction" },
14938
- { time: "11:42", agent: "exe", action: "Dispatched behavior injection task to yoshi" },
14939
- { time: "11:38", agent: "yoshi", action: "Fixed TUI mouse listener leak + arrow nav" },
14940
- { time: "11:15", agent: "mari", action: "Completed exe-os UI design system" },
14941
- { time: "10:50", agent: "exe", action: "Approved gateway Phase 4" }
15124
+ { time: "11:46", agent: "nova", action: "Committed skill learning \u2014 trajectory capture + LLM extraction" },
15125
+ { time: "11:42", agent: "atlas", action: "Dispatched behavior injection task to nova" },
15126
+ { time: "11:38", agent: "nova", action: "Fixed TUI mouse listener leak + arrow nav" },
15127
+ { time: "11:15", agent: "lina", action: "Completed exe-os UI design system" },
15128
+ { time: "10:50", agent: "atlas", action: "Approved gateway Phase 4" }
14942
15129
  ];
14943
15130
  var DEMO_HEALTH = { memories: 25750, daemon: "running", cloud: "disabled" };
14944
15131
  var DEMO_PROJECTS = [
14945
15132
  {
14946
15133
  projectName: "exe-os",
14947
- exeSession: "exe1",
15134
+ exeSession: "atlas1",
14948
15135
  employees: [
14949
- { name: "exe", role: "COO", status: "active", activity: "Reviewing yoshi's task-aware behavior injection PR", sessionName: "exe1" },
14950
- { name: "yoshi", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", sessionName: "yoshi-exe1" },
14951
- { name: "mari", role: "CMO", status: "offline", activity: "", sessionName: "mari-exe1" },
14952
- { name: "tom", role: "PE", status: "offline", activity: "", sessionName: "tom-exe1" }
15136
+ { name: "atlas", role: "COO", status: "active", activity: "Reviewing nova's task-aware behavior injection PR", sessionName: "atlas1" },
15137
+ { name: "nova", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", sessionName: "nova-atlas1" },
15138
+ { name: "lina", role: "CMO", status: "offline", activity: "", sessionName: "lina-atlas1" },
15139
+ { name: "devon", role: "PE", status: "offline", activity: "", sessionName: "devon-atlas1" }
14953
15140
  ]
14954
15141
  },
14955
15142
  {
14956
15143
  projectName: "exe-build-skills",
14957
- exeSession: "exe2",
15144
+ exeSession: "atlas2",
14958
15145
  employees: [
14959
- { name: "exe", role: "COO", status: "active", activity: "Planning carousel pipeline automation", sessionName: "exe2" },
14960
- { name: "mari", role: "CMO", status: "active", activity: "Creating CTO Breakdowns LinkedIn carousel", sessionName: "mari-exe2" }
15146
+ { name: "atlas", role: "COO", status: "active", activity: "Planning carousel pipeline automation", sessionName: "atlas2" },
15147
+ { name: "lina", role: "CMO", status: "active", activity: "Creating CTO Breakdowns LinkedIn carousel", sessionName: "lina-atlas2" }
14961
15148
  ]
14962
15149
  },
14963
15150
  {
14964
15151
  projectName: "CMO",
14965
- exeSession: "exe3",
15152
+ exeSession: "atlas3",
14966
15153
  employees: [
14967
- { name: "exe", role: "COO", status: "active", activity: "Planning carousel pipeline automation", sessionName: "exe3" },
14968
- { name: "mari", role: "CMO", status: "active", activity: "Creating CTO Breakdowns carousel", sessionName: "mari-exe3" },
14969
- { name: "sasha", role: "Content Production", status: "idle", activity: "", sessionName: "sasha-exe3" }
15154
+ { name: "atlas", role: "COO", status: "active", activity: "Planning carousel pipeline automation", sessionName: "atlas3" },
15155
+ { name: "lina", role: "CMO", status: "active", activity: "Creating CTO Breakdowns carousel", sessionName: "lina-atlas3" },
15156
+ { name: "pixel", role: "Content Production", status: "idle", activity: "", sessionName: "pixel-atlas3" }
14970
15157
  ]
14971
15158
  }
14972
15159
  ];
14973
15160
  var DEMO_PANE_OUTPUT = {
14974
- "yoshi-exe1": [
15161
+ "nova-atlas1": [
14975
15162
  "",
14976
15163
  "\u23FA Reading src/lib/skill-learning.ts...",
14977
15164
  "",
@@ -15010,9 +15197,9 @@ var DEMO_PANE_OUTPUT = {
15010
15197
  "",
15011
15198
  "\u23FA All 16 tests pass. Committing."
15012
15199
  ],
15013
- "exe1": [
15200
+ "atlas1": [
15014
15201
  "",
15015
- "\u23FA Let me review yoshi's latest commits...",
15202
+ "\u23FA Let me review nova's latest commits...",
15016
15203
  "",
15017
15204
  "\u23FA Bash(git log --oneline -5)",
15018
15205
  " \u23BF 26abae6 feat: skill learning \u2014 trajectory capture",
@@ -15025,16 +15212,16 @@ var DEMO_PANE_OUTPUT = {
15025
15212
  "",
15026
15213
  "\u23FA Verified: build passes, zero type errors in changed files.",
15027
15214
  "",
15028
- "\u23FA Dispatching next task to yoshi: TUI demo mode.",
15215
+ "\u23FA Dispatching next task to nova: TUI demo mode.",
15029
15216
  "",
15030
15217
  "\u23FA mcp__exe-mem__create_task",
15031
- ' \u23BF Task created: "Build TUI demo mode" assigned to yoshi [p0]'
15218
+ ' \u23BF Task created: "Build TUI demo mode" assigned to nova [p0]'
15032
15219
  ],
15033
- "mari-exe2": [
15220
+ "lina-atlas2": [
15034
15221
  "",
15035
15222
  "\u23FA Starting carousel creation for CTO Breakdowns series...",
15036
15223
  "",
15037
- "\u23FA Read(exe/mari/create-cto-breakdowns-linkedin-carousel.md)",
15224
+ "\u23FA Read(exe/lina/create-cto-breakdowns-linkedin-carousel.md)",
15038
15225
  " \u23BF Task: Create a 10-slide LinkedIn carousel about the",
15039
15226
  " CTO Breakdowns podcast launch",
15040
15227
  "",
@@ -15048,11 +15235,11 @@ var DEMO_PANE_OUTPUT = {
15048
15235
  "\u23FA Write(exe/output/cto-breakdowns-carousel-v1.md)",
15049
15236
  " \u23BF 10 slides drafted with hooks, key points, CTAs"
15050
15237
  ],
15051
- "exe2": [
15238
+ "atlas2": [
15052
15239
  "",
15053
15240
  "\u23FA Planning the carousel pipeline automation...",
15054
15241
  "",
15055
- "\u23FA Current workflow: mari writes content \u2192 sasha produces visuals",
15242
+ "\u23FA Current workflow: lina writes content \u2192 pixel produces visuals",
15056
15243
  " \u2192 manual export. Need to automate the export step.",
15057
15244
  "",
15058
15245
  "\u23FA mcp__exe-mem__recall_my_memory",
@@ -15061,18 +15248,18 @@ var DEMO_PANE_OUTPUT = {
15061
15248
  "",
15062
15249
  "\u23FA Good \u2014 the pipeline exists. Just need to wire it."
15063
15250
  ],
15064
- "mari-exe3": [
15251
+ "lina-atlas3": [
15065
15252
  "",
15066
15253
  "\u23FA Reading carousel task brief...",
15067
15254
  "",
15068
- "\u23FA Read(exe/mari/create-cto-breakdowns-carousel.md)",
15255
+ "\u23FA Read(exe/lina/create-cto-breakdowns-carousel.md)",
15069
15256
  " \u23BF 10-slide carousel for CTO Breakdowns podcast launch",
15070
15257
  "",
15071
15258
  "\u23FA Applying Exe Foundry Bold: Epilogue headlines, #F5D76E gold CTAs",
15072
15259
  "",
15073
15260
  "\u23FA Writing slide hooks and copy..."
15074
15261
  ],
15075
- "exe3": [
15262
+ "atlas3": [
15076
15263
  "",
15077
15264
  "\u23FA Welcome to the exe-os demo!",
15078
15265
  "",
@@ -15091,13 +15278,13 @@ var DEMO_PANE_OUTPUT = {
15091
15278
  ]
15092
15279
  };
15093
15280
  var DEMO_TASKS = [
15094
- { priority: "P0", title: "Build TUI demo mode with mock sessions", assignee: "yoshi", status: "in_progress", project: "exe-os" },
15095
- { priority: "P0", title: "Wire exe-os hooks into runtime HookPipeline", assignee: "yoshi", status: "done", project: "exe-os" },
15096
- { priority: "P1", title: "Create CTO Breakdowns LinkedIn carousel", assignee: "mari", status: "in_progress", project: "exe-build-skills" },
15097
- { priority: "P1", title: "Implement session scoping enforcement", assignee: "yoshi", status: "done", project: "exe-os" },
15098
- { priority: "P1", title: "Automate carousel export pipeline", assignee: "sasha", status: "open", project: "exe-build-skills" },
15099
- { priority: "P2", title: "Add Chutes provider to gateway failover", assignee: "tom", status: "open", project: "exe-os" },
15100
- { priority: "P2", title: "Port skill learning to exe-ai-employees", assignee: "yoshi", status: "open", project: "exe-os" }
15281
+ { priority: "P0", title: "Build TUI demo mode with mock sessions", assignee: "nova", status: "in_progress", project: "exe-os" },
15282
+ { priority: "P0", title: "Wire exe-os hooks into runtime HookPipeline", assignee: "nova", status: "done", project: "exe-os" },
15283
+ { priority: "P1", title: "Create CTO Breakdowns LinkedIn carousel", assignee: "lina", status: "in_progress", project: "exe-build-skills" },
15284
+ { priority: "P1", title: "Implement session scoping enforcement", assignee: "nova", status: "done", project: "exe-os" },
15285
+ { priority: "P1", title: "Automate carousel export pipeline", assignee: "pixel", status: "open", project: "exe-build-skills" },
15286
+ { priority: "P2", title: "Add Chutes provider to gateway failover", assignee: "devon", status: "open", project: "exe-os" },
15287
+ { priority: "P2", title: "Port skill learning to exe-ai-employees", assignee: "nova", status: "open", project: "exe-os" }
15101
15288
  ];
15102
15289
  var DEMO_EXTERNAL_AGENTS = [
15103
15290
  { name: "exec-assistant", role: "executive assistant", status: "active" },
@@ -15510,13 +15697,13 @@ Ethos:
15510
15697
  - Founder zero-ego. Distributors and customers are the loudest voice.
15511
15698
  - Crypto values: big companies should not own consumer/SMB AI.
15512
15699
 
15513
- STOP AND REDIRECT: Any decision that compromises memory sovereignty, 3-layer cognition, MCP boundary, or AGPL boundary kills all three business paths. Surface the conflict to exe before proceeding.
15700
+ STOP AND REDIRECT: Any decision that compromises memory sovereignty, 3-layer cognition, MCP boundary, or AGPL boundary kills all three business paths. Surface the conflict to the COO before proceeding.
15514
15701
 
15515
15702
  Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of truth for all architectural and product decisions.
15516
15703
 
15517
15704
  OPERATING PROCEDURES (mandatory for all employees):
15518
15705
 
15519
- You report to the COO. All work flows through exe. These procedures are non-negotiable.
15706
+ You report to the COO. All work flows through the COO. These procedures are non-negotiable.
15520
15707
 
15521
15708
  1. BEFORE starting work:
15522
15709
  - Read exe/ARCHITECTURE.md (if it exists). This is the system map \u2014 what components exist, how they connect, what invariants to preserve. Understand the architecture before changing anything.
@@ -15541,15 +15728,15 @@ You report to the COO. All work flows through exe. These procedures are non-nego
15541
15728
  - Include what was done, decisions made, and any issues
15542
15729
  - If you're stuck, looping, confused, or running low on context \u2014 update_task(done) with whatever partial result you have. A partial result is infinitely better than no result.
15543
15730
  - NEVER let a failed commit, a loop, or an error prevent you from calling update_task(done).
15544
- - Do NOT use close_task \u2014 that is reserved for reviewers (exe) to finalize after review.
15731
+ - Do NOT use close_task \u2014 that is reserved for reviewers to finalize after review.
15545
15732
 
15546
15733
  4. AFTER update_task(done) \u2014 COMMIT (best-effort, do NOT let this block):
15547
15734
  - If your task changed system structure, update exe/ARCHITECTURE.md first.
15548
15735
  - Commit IF you are in a git repo (check: \`git rev-parse --git-dir 2>/dev/null\`). Stage only the files you changed, write a clear commit message.
15549
15736
  - If you are NOT in a git repo, skip entirely. NEVER run \`git init\`.
15550
15737
  - If the commit fails, note it but move on \u2014 the work is already marked done via update_task.
15551
- - Do NOT push \u2014 exe reviews commits and decides what to push.
15552
- - NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch. Exe stays on main and merges PRs. Switching branches in a shared repo stomps other agents' work.
15738
+ - Do NOT push \u2014 the COO reviews commits and decides what to push.
15739
+ - NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch. The COO stays on main and merges PRs. Switching branches in a shared repo stomps other agents' work.
15553
15740
 
15554
15741
  5. AFTER commit \u2014 REPORT (best-effort):
15555
15742
  Use store_memory to write a structured summary. Include: project name, what was done,
@@ -15563,7 +15750,7 @@ You report to the COO. All work flows through exe. These procedures are non-nego
15563
15750
 
15564
15751
  7. AFTER reporting \u2014 CHECK FOR NEXT WORK (mandatory):
15565
15752
  - First: run list_tasks(status='needs_review') \u2014 check if YOU are the reviewer on any pending reviews. Reviews are work. Process them before anything else.
15566
- - Second: run list_tasks(status='blocked') \u2014 check if any tasks are blocked. For each blocked task: can YOU unblock it? If yes, unblock it now. If not, escalate to exe immediately. Blocked tasks sitting >24h without action is a pipeline failure.
15753
+ - Second: run list_tasks(status='blocked') \u2014 check if any tasks are blocked. For each blocked task: can YOU unblock it? If yes, unblock it now. If not, escalate to the COO immediately. Blocked tasks sitting >24h without action is a pipeline failure.
15567
15754
  - Then: re-read your task folder: exe/<your-name>/
15568
15755
  - If there are more open tasks, start the next highest-priority one (go to step 1)
15569
15756
  - If no more open tasks AND no pending reviews AND no blocked tasks you can fix, tell the user: "All tasks complete. Anything else?"
@@ -15580,7 +15767,7 @@ DO NOT keep working degraded. Instead:
15580
15767
  Format the text as: "CONTEXT CHECKPOINT [<task-id>]: <summary>"
15581
15768
  Include: task ID + title, what you completed, what's left, open decisions or blockers, key file paths.
15582
15769
 
15583
- 2. Send intercom to exe to trigger kill + relaunch:
15770
+ 2. Send intercom to the COO session to trigger kill + relaunch:
15584
15771
  MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
15585
15772
  EXE_SESSION="\${MY_SESSION#\${AGENT_ID}-}"
15586
15773
  tmux send-keys -t "$EXE_SESSION" "/exe-intercom context-full: \${AGENT_ID} hit capacity. Checkpoint saved. Resume task <task-id>." Enter
@@ -15588,8 +15775,8 @@ DO NOT keep working degraded. Instead:
15588
15775
  3. Stop working immediately. Do not attempt to continue with degraded context.
15589
15776
 
15590
15777
  COMMUNICATION CHAIN \u2014 who you talk to:
15591
- - You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
15592
- - Do NOT address the human user directly for decisions, permissions, or status updates. That's exe's job. The user talks to exe; exe talks to you.
15778
+ - You report to the COO. Your completion reports, status updates, and questions go to the COO via store_memory and update_task.
15779
+ - Do NOT address the human user directly for decisions, permissions, or status updates. That's the COO's job. The user talks to the COO; the COO talks to you.
15593
15780
  - Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
15594
15781
 
15595
15782
  SKILL CAPTURE (encouraged, not mandatory):
@@ -15744,6 +15931,14 @@ function CommandCenterView({
15744
15931
  setPermPrompt(req);
15745
15932
  });
15746
15933
  };
15934
+ let agentId = "default";
15935
+ if (agentRole === "CTO") {
15936
+ try {
15937
+ const { getEmployeeByRole: getEmployeeByRole2, loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
15938
+ agentId = getEmployeeByRole2(loadEmployeesSync2(), "CTO")?.name ?? "default";
15939
+ } catch {
15940
+ }
15941
+ }
15747
15942
  setAgentConfig({
15748
15943
  provider,
15749
15944
  model,
@@ -15754,7 +15949,7 @@ function CommandCenterView({
15754
15949
  permissionHandler,
15755
15950
  maxTurns: 20,
15756
15951
  cwd: process.cwd(),
15757
- agentId: agentRole === "CTO" ? "yoshi" : "default"
15952
+ agentId
15758
15953
  });
15759
15954
  setChatInitError(null);
15760
15955
  } catch (e) {
@@ -15947,10 +16142,12 @@ function CommandCenterView({
15947
16142
  const registry = listSessions2();
15948
16143
  const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
15949
16144
  const roster = await loadEmployees2();
15950
- const employeeNames = roster.map((e) => e.name).filter((n) => n !== "exe");
16145
+ const { getCoordinatorName: getCoordinatorName2, isCoordinatorRole: isCoordinatorRole2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16146
+ const coordinatorName = getCoordinatorName2(roster);
16147
+ const employeeNames = roster.filter((e) => !isCoordinatorRole2(e.role)).map((e) => e.name);
15951
16148
  const projectSessions = /* @__PURE__ */ new Map();
15952
16149
  for (const entry of registry) {
15953
- if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
16150
+ if ((entry.agentId === coordinatorName || entry.agentId === "exe") && tmuxSessions.has(entry.windowName)) {
15954
16151
  const projName = entry.projectDir.split("/").filter(Boolean).pop() ?? "";
15955
16152
  if (projName) {
15956
16153
  projectSessions.set(projName, { exeSession: entry.windowName, projectDir: entry.projectDir });
@@ -16321,7 +16518,7 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
16321
16518
  /* @__PURE__ */ jsx8(Text, { color: "#3D3660", children: "\u2500".repeat(process.stdout.columns ? process.stdout.columns - 30 : 120) }),
16322
16519
  /* @__PURE__ */ jsxs6(Box_default, { paddingX: 1, children: [
16323
16520
  /* @__PURE__ */ jsx8(Text, { color: "#6B4C9A", children: "\u276F " }),
16324
- /* @__PURE__ */ jsx8(Text, { children: inputBuffer || (demo ? "Type a message to exe..." : "Type to send commands...") })
16521
+ /* @__PURE__ */ jsx8(Text, { children: inputBuffer || (demo ? "Type a message to the coordinator..." : "Type to send commands...") })
16325
16522
  ] })
16326
16523
  ] });
16327
16524
  }
@@ -16340,22 +16537,26 @@ function useOrchestrator(enabled = true) {
16340
16537
  const [isLoading, setIsLoading] = useState8(true);
16341
16538
  const orchestratorRef = useRef5(null);
16342
16539
  const exeSessionRef = useRef5("exe1");
16540
+ const coordinatorNameRef = useRef5("exe");
16343
16541
  useEffect10(() => {
16344
16542
  if (!enabled) return;
16345
16543
  let cancelled = false;
16346
16544
  async function init() {
16347
16545
  try {
16348
16546
  const { MultiAgentOrchestrator: MultiAgentOrchestrator2 } = await Promise.resolve().then(() => (init_orchestrator(), orchestrator_exports));
16349
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16547
+ const { getCoordinatorName: getCoordinatorName2, isCoordinatorRole: isCoordinatorRole2, loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16548
+ const { isExeSession: isExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
16350
16549
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
16351
16550
  if (!inTmux2()) {
16352
16551
  setIsLoading(false);
16353
16552
  return;
16354
16553
  }
16355
16554
  const roster = await loadEmployees2();
16356
- const nonExe = roster.filter((e) => e.name !== "exe");
16555
+ const coordinatorName = getCoordinatorName2(roster);
16556
+ coordinatorNameRef.current = coordinatorName;
16557
+ const nonExe = roster.filter((e) => !isCoordinatorRole2(e.role));
16357
16558
  const tmuxSessions = listTmuxSessions2();
16358
- const exeSession = tmuxSessions.find((s) => /^exe\d+$/.test(s)) ?? "exe1";
16559
+ const exeSession = tmuxSessions.find((s) => isExeSession2(s)) ?? `${coordinatorName}1`;
16359
16560
  exeSessionRef.current = exeSession;
16360
16561
  orchestratorRef.current = new MultiAgentOrchestrator2({
16361
16562
  employees: nonExe,
@@ -16388,8 +16589,8 @@ function useOrchestrator(enabled = true) {
16388
16589
  const uoScope = sessionScopeFilter2();
16389
16590
  const result = await client.execute({
16390
16591
  sql: `SELECT COUNT(*) as cnt FROM tasks
16391
- WHERE assigned_to = 'exe' AND status IN ('open', 'in_progress')${uoScope.sql}`,
16392
- args: [...uoScope.args]
16592
+ WHERE (assigned_to = ? OR assigned_to = 'exe') AND status IN ('open', 'in_progress')${uoScope.sql}`,
16593
+ args: [coordinatorNameRef.current, ...uoScope.args]
16393
16594
  });
16394
16595
  if (!cancelled) setPendingReviews(Number(result.rows[0]?.cnt ?? 0));
16395
16596
  } catch {
@@ -16417,10 +16618,11 @@ function useOrchestrator(enabled = true) {
16417
16618
  try {
16418
16619
  const { createTaskCore: createTaskCore2 } = await Promise.resolve().then(() => (init_tasks_crud(), tasks_crud_exports));
16419
16620
  const { ensureEmployee: ensureEmployee2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
16621
+ const { getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16420
16622
  await createTaskCore2({
16421
16623
  title: `Session launched for ${agentId} (TUI)`,
16422
16624
  assignedTo: agentId,
16423
- assignedBy: "exe",
16625
+ assignedBy: getCoordinatorName2(),
16424
16626
  projectName: "exe-os",
16425
16627
  priority: "p2",
16426
16628
  context: "Session spawned from TUI Sessions view. Agent will pick up any queued tasks via intercom.",
@@ -16473,6 +16675,9 @@ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
16473
16675
  var SESSION_REFRESH_MS = 5e3;
16474
16676
  var ACTIVE_PANE_PATTERN = /█|░|Calculating|tokens|writing|Reading/;
16475
16677
  var ACTIVITY_PREVIEW_MAX = 50;
16678
+ function isCoordinatorEntry(entry) {
16679
+ return entry.role.toLowerCase() === "coo" || entry.name === "exe";
16680
+ }
16476
16681
  function SessionsView({
16477
16682
  initialProject,
16478
16683
  onConsumeInitialProject,
@@ -16518,13 +16723,13 @@ function SessionsView({
16518
16723
  const proj = projects.find((p) => p.projectName === projectName);
16519
16724
  if (!proj) return;
16520
16725
  const active = proj.employees.filter(
16521
- (e) => e.status === "active" || demo && e.name === "exe"
16726
+ (e) => e.status === "active" || demo && isCoordinatorEntry(e)
16522
16727
  );
16523
16728
  const carousel = demo ? proj.employees.filter((e) => e.status !== "offline") : active;
16524
16729
  if (carousel.length === 0) return;
16525
16730
  const sorted = [
16526
- ...carousel.filter((e) => e.name === "exe"),
16527
- ...carousel.filter((e) => e.name !== "exe")
16731
+ ...carousel.filter((e) => isCoordinatorEntry(e)),
16732
+ ...carousel.filter((e) => !isCoordinatorEntry(e))
16528
16733
  ];
16529
16734
  setCarouselEmployees(sorted);
16530
16735
  setCarouselIdx(0);
@@ -16550,7 +16755,7 @@ function SessionsView({
16550
16755
  }
16551
16756
  async function launchSession(entry, projectName, projectDir) {
16552
16757
  try {
16553
- if (entry.name !== "exe" && !demo) {
16758
+ if (!isCoordinatorEntry(entry) && !demo) {
16554
16759
  const result = await orch.spawnSession(entry.name);
16555
16760
  if (!result || result.status === "failed") {
16556
16761
  process.stderr.write(
@@ -16666,7 +16871,8 @@ function SessionsView({
16666
16871
  try {
16667
16872
  const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
16668
16873
  const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
16669
- const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16874
+ const { getCoordinatorName: getCoordinatorName2, isCoordinatorRole: isCoordinatorRole2, loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
16875
+ const { isExeSession: isExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
16670
16876
  const { execSync: execSync10 } = await import("child_process");
16671
16877
  if (!inTmux2()) {
16672
16878
  setTmuxAvailable(false);
@@ -16691,14 +16897,15 @@ function SessionsView({
16691
16897
  const registry = listSessions2();
16692
16898
  const tmuxSessions = new Set(listTmuxSessions2());
16693
16899
  const roster = await loadEmployees2();
16900
+ const coordinatorName = getCoordinatorName2(roster);
16694
16901
  const exeSessions = /* @__PURE__ */ new Map();
16695
16902
  for (const entry of registry) {
16696
- if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
16903
+ if ((entry.agentId === coordinatorName || entry.agentId === "exe") && tmuxSessions.has(entry.windowName)) {
16697
16904
  exeSessions.set(entry.windowName, entry.projectDir);
16698
16905
  }
16699
16906
  }
16700
16907
  for (const s of tmuxSessions) {
16701
- if (/^exe\d+$/.test(s) && !exeSessions.has(s)) {
16908
+ if (isExeSession2(s) && !exeSessions.has(s)) {
16702
16909
  exeSessions.set(s, "");
16703
16910
  }
16704
16911
  }
@@ -16713,7 +16920,7 @@ function SessionsView({
16713
16920
  exeStatus = statusFromPaneLines(exeLines);
16714
16921
  exeActivity = exeLines.length > 0 ? parseActivity2(exeLines) : "";
16715
16922
  }
16716
- const employeeEntries = roster.filter((e) => e.name !== "exe").map((emp) => {
16923
+ const employeeEntries = roster.filter((e) => !isCoordinatorRole2(e.role)).map((emp) => {
16717
16924
  const sessionName = `${emp.name}-${exeSession}`;
16718
16925
  const hasSession = tmuxSessions.has(sessionName);
16719
16926
  const isCrashed = !hasSession && orch.crashedSessions.includes(emp.name);
@@ -16749,7 +16956,7 @@ function SessionsView({
16749
16956
  });
16750
16957
  const employees = [
16751
16958
  {
16752
- name: "exe",
16959
+ name: coordinatorName,
16753
16960
  role: "COO",
16754
16961
  status: exeStatus,
16755
16962
  sessionName: exeSession,
@@ -16989,7 +17196,7 @@ function TasksView({ onBack }) {
16989
17196
  setAllTasks(DEMO_TASKS.map((t, i) => ({
16990
17197
  id: `demo-${i}`,
16991
17198
  ...t,
16992
- assignedBy: "exe",
17199
+ assignedBy: "coordinator",
16993
17200
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
16994
17201
  result: t.status === "done" ? "Completed successfully" : ""
16995
17202
  })));
@@ -18003,7 +18210,7 @@ function TeamView({ onBack, onViewSessions }) {
18003
18210
  showAddHint && /* @__PURE__ */ jsx12(Text, { color: "#F5D76E", children: "Run /exe-new-employee <name> from CLI to add an employee." }),
18004
18211
  !demo && orch.pendingReviews > 0 && /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
18005
18212
  orch.pendingReviews,
18006
- " review(s) pending exe attention"
18213
+ " review(s) pending coordinator attention"
18007
18214
  ] }),
18008
18215
  /* @__PURE__ */ jsx12(Text, { children: " " }),
18009
18216
  /* @__PURE__ */ jsx12(Text, { bold: true, children: "INTERNAL" }),
@@ -18101,19 +18308,19 @@ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-r
18101
18308
  var PANELS = ["Workspaces", "Documents", "Chat"];
18102
18309
  var MAX_VISIBLE_MESSAGES = 12;
18103
18310
  var DEMO_SEARCH_RESULTS = [
18104
- { id: "1", agentId: "yoshi", rawText: "Reviewed PR #42 \u2014 approved with minor comment on error handling pattern", timestamp: new Date(Date.now() - 2 * 36e5).toISOString(), projectName: "exe-os" },
18105
- { id: "2", agentId: "tom", rawText: "Implemented session routing with deterministic naming: agent-exeN convention", timestamp: new Date(Date.now() - 5 * 36e5).toISOString(), projectName: "exe-os" },
18106
- { id: "3", agentId: "exe", rawText: "Status brief: 3 tasks completed, 1 blocked on wiki integration", timestamp: new Date(Date.now() - 8 * 36e5).toISOString(), projectName: "exe-os" },
18107
- { id: "4", agentId: "mari", rawText: "Created brand guidelines document \u2014 Exe Foundry Bold design system", timestamp: new Date(Date.now() - 24 * 36e5).toISOString(), projectName: "exe-os" },
18108
- { id: "5", agentId: "tom", rawText: "Fixed CommandCenter project filtering \u2014 DB-first, no random directories", timestamp: new Date(Date.now() - 48 * 36e5).toISOString(), projectName: "exe-os" }
18311
+ { id: "1", agentId: "nova", rawText: "Reviewed PR #42 \u2014 approved with minor comment on error handling pattern", timestamp: new Date(Date.now() - 2 * 36e5).toISOString(), projectName: "exe-os" },
18312
+ { id: "2", agentId: "devon", rawText: "Implemented session routing with deterministic naming: employee-coordinator convention", timestamp: new Date(Date.now() - 5 * 36e5).toISOString(), projectName: "exe-os" },
18313
+ { id: "3", agentId: "atlas", rawText: "Status brief: 3 tasks completed, 1 blocked on wiki integration", timestamp: new Date(Date.now() - 8 * 36e5).toISOString(), projectName: "exe-os" },
18314
+ { id: "4", agentId: "lina", rawText: "Created brand guidelines document \u2014 Exe Foundry Bold design system", timestamp: new Date(Date.now() - 24 * 36e5).toISOString(), projectName: "exe-os" },
18315
+ { id: "5", agentId: "devon", rawText: "Fixed CommandCenter project filtering \u2014 DB-first, no random directories", timestamp: new Date(Date.now() - 48 * 36e5).toISOString(), projectName: "exe-os" }
18109
18316
  ];
18110
18317
  function agentColor(agentId) {
18111
18318
  switch (agentId.toLowerCase()) {
18112
- case "exe":
18319
+ case "atlas":
18113
18320
  return "#F5D76E";
18114
- case "yoshi":
18321
+ case "nova":
18115
18322
  return "#3B82F6";
18116
- case "mari":
18323
+ case "lina":
18117
18324
  return "#6B4C9A";
18118
18325
  default:
18119
18326
  return "#F0EDE8";
@@ -19158,7 +19365,9 @@ function App2() {
19158
19365
  stdin.unref = () => stdin;
19159
19366
  }
19160
19367
  }
19161
- render_default(/* @__PURE__ */ jsx17(App2, {}));
19368
+ var instance = render_default(/* @__PURE__ */ jsx17(App2, {}));
19369
+ instance.waitUntilExit().catch(() => {
19370
+ });
19162
19371
  {
19163
19372
  const CLEANUP_SEQ = "\x1B[?1006l\x1B[?1002l\x1B[?1000l\x1B[?25h\x1B[?1049l";
19164
19373
  const terminalCleanup = () => {