@askexenow/exe-os 0.9.6 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/bin/backfill-conversations.js +754 -79
  2. package/dist/bin/backfill-responses.js +752 -77
  3. package/dist/bin/backfill-vectors.js +752 -77
  4. package/dist/bin/cleanup-stale-review-tasks.js +668 -37
  5. package/dist/bin/cli.js +1399 -607
  6. package/dist/bin/exe-agent-config.js +123 -95
  7. package/dist/bin/exe-agent.js +41 -25
  8. package/dist/bin/exe-assign.js +732 -57
  9. package/dist/bin/exe-boot.js +795 -155
  10. package/dist/bin/exe-call.js +209 -138
  11. package/dist/bin/exe-cloud.js +35 -12
  12. package/dist/bin/exe-dispatch.js +703 -72
  13. package/dist/bin/exe-doctor.js +648 -26
  14. package/dist/bin/exe-export-behaviors.js +650 -20
  15. package/dist/bin/exe-forget.js +635 -13
  16. package/dist/bin/exe-gateway.js +1064 -273
  17. package/dist/bin/exe-heartbeat.js +676 -45
  18. package/dist/bin/exe-kill.js +646 -16
  19. package/dist/bin/exe-launch-agent.js +887 -97
  20. package/dist/bin/exe-link.js +658 -43
  21. package/dist/bin/exe-new-employee.js +378 -177
  22. package/dist/bin/exe-pending-messages.js +656 -34
  23. package/dist/bin/exe-pending-notifications.js +635 -13
  24. package/dist/bin/exe-pending-reviews.js +659 -37
  25. package/dist/bin/exe-rename.js +645 -30
  26. package/dist/bin/exe-review.js +635 -13
  27. package/dist/bin/exe-search.js +771 -88
  28. package/dist/bin/exe-session-cleanup.js +845 -152
  29. package/dist/bin/exe-settings.js +127 -91
  30. package/dist/bin/exe-start-codex.js +729 -94
  31. package/dist/bin/exe-start-opencode.js +717 -82
  32. package/dist/bin/exe-status.js +668 -37
  33. package/dist/bin/exe-team.js +635 -13
  34. package/dist/bin/git-sweep.js +731 -91
  35. package/dist/bin/graph-backfill.js +643 -13
  36. package/dist/bin/graph-export.js +646 -16
  37. package/dist/bin/install.js +596 -193
  38. package/dist/bin/scan-tasks.js +735 -95
  39. package/dist/bin/setup.js +1038 -210
  40. package/dist/bin/shard-migrate.js +645 -15
  41. package/dist/bin/wiki-sync.js +646 -16
  42. package/dist/gateway/index.js +1038 -247
  43. package/dist/hooks/bug-report-worker.js +902 -172
  44. package/dist/hooks/commit-complete.js +729 -89
  45. package/dist/hooks/error-recall.js +776 -93
  46. package/dist/hooks/exe-heartbeat-hook.js +85 -71
  47. package/dist/hooks/ingest-worker.js +851 -158
  48. package/dist/hooks/ingest.js +90 -73
  49. package/dist/hooks/instructions-loaded.js +669 -38
  50. package/dist/hooks/notification.js +661 -30
  51. package/dist/hooks/post-compact.js +685 -45
  52. package/dist/hooks/pre-compact.js +729 -89
  53. package/dist/hooks/pre-tool-use.js +883 -127
  54. package/dist/hooks/prompt-ingest-worker.js +758 -83
  55. package/dist/hooks/prompt-submit.js +1071 -321
  56. package/dist/hooks/response-ingest-worker.js +758 -83
  57. package/dist/hooks/session-end.js +732 -92
  58. package/dist/hooks/session-start.js +1042 -209
  59. package/dist/hooks/stop.js +691 -51
  60. package/dist/hooks/subagent-stop.js +685 -45
  61. package/dist/hooks/summary-worker.js +827 -134
  62. package/dist/index.js +1026 -234
  63. package/dist/lib/cloud-sync.js +663 -48
  64. package/dist/lib/consolidation.js +26 -3
  65. package/dist/lib/database.js +626 -18
  66. package/dist/lib/db.js +2261 -0
  67. package/dist/lib/device-registry.js +640 -25
  68. package/dist/lib/embedder.js +96 -43
  69. package/dist/lib/employee-templates.js +16 -0
  70. package/dist/lib/employees.js +259 -83
  71. package/dist/lib/exe-daemon-client.js +101 -63
  72. package/dist/lib/exe-daemon.js +905 -164
  73. package/dist/lib/hybrid-search.js +771 -88
  74. package/dist/lib/identity.js +27 -7
  75. package/dist/lib/messaging.js +66 -30
  76. package/dist/lib/reminders.js +21 -1
  77. package/dist/lib/schedules.js +636 -14
  78. package/dist/lib/skill-learning.js +21 -1
  79. package/dist/lib/store.js +643 -13
  80. package/dist/lib/task-router.js +82 -71
  81. package/dist/lib/tasks.js +109 -73
  82. package/dist/lib/tmux-routing.js +98 -62
  83. package/dist/lib/token-spend.js +26 -6
  84. package/dist/mcp/server.js +1807 -472
  85. package/dist/mcp/tools/complete-reminder.js +21 -1
  86. package/dist/mcp/tools/create-reminder.js +21 -1
  87. package/dist/mcp/tools/create-task.js +301 -166
  88. package/dist/mcp/tools/deactivate-behavior.js +24 -4
  89. package/dist/mcp/tools/list-reminders.js +21 -1
  90. package/dist/mcp/tools/list-tasks.js +206 -40
  91. package/dist/mcp/tools/send-message.js +69 -33
  92. package/dist/mcp/tools/update-task.js +86 -50
  93. package/dist/runtime/index.js +731 -91
  94. package/dist/tui/App.js +864 -125
  95. package/package.json +3 -2
@@ -270,7 +270,11 @@ var _socket = null;
270
270
  var _connected = false;
271
271
  var _buffer = "";
272
272
  var _requestCount = 0;
273
+ var _consecutiveFailures = 0;
273
274
  var HEALTH_CHECK_INTERVAL = 100;
275
+ var MAX_RETRIES_BEFORE_RESTART = 3;
276
+ var RETRY_DELAYS_MS = [1e3, 3e3, 5e3];
277
+ var MIN_DAEMON_AGE_MS = 3e4;
274
278
  var _pending = /* @__PURE__ */ new Map();
275
279
  var MAX_BUFFER = 1e7;
276
280
  function handleData(chunk) {
@@ -512,74 +516,123 @@ async function pingDaemon() {
512
516
  return null;
513
517
  }
514
518
  function killAndRespawnDaemon() {
515
- process.stderr.write("[exed-client] Killing daemon for restart...\n");
516
- if (existsSync2(PID_PATH)) {
517
- try {
518
- const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
519
- if (pid > 0) {
520
- try {
521
- process.kill(pid, "SIGKILL");
522
- } catch {
519
+ if (!acquireSpawnLock()) {
520
+ process.stderr.write("[exed-client] Another process is already restarting daemon \u2014 skipping\n");
521
+ if (_socket) {
522
+ _socket.destroy();
523
+ _socket = null;
524
+ }
525
+ _connected = false;
526
+ _buffer = "";
527
+ return;
528
+ }
529
+ try {
530
+ process.stderr.write("[exed-client] Killing daemon for restart...\n");
531
+ if (existsSync2(PID_PATH)) {
532
+ try {
533
+ const pid = parseInt(readFileSync2(PID_PATH, "utf8").trim(), 10);
534
+ if (pid > 0) {
535
+ try {
536
+ process.kill(pid, "SIGKILL");
537
+ } catch {
538
+ }
523
539
  }
540
+ } catch {
524
541
  }
542
+ }
543
+ if (_socket) {
544
+ _socket.destroy();
545
+ _socket = null;
546
+ }
547
+ _connected = false;
548
+ _buffer = "";
549
+ try {
550
+ unlinkSync(PID_PATH);
525
551
  } catch {
526
552
  }
553
+ try {
554
+ unlinkSync(SOCKET_PATH);
555
+ } catch {
556
+ }
557
+ spawnDaemon();
558
+ } finally {
559
+ releaseSpawnLock();
527
560
  }
528
- if (_socket) {
529
- _socket.destroy();
530
- _socket = null;
531
- }
532
- _connected = false;
533
- _buffer = "";
561
+ }
562
+ function isDaemonTooYoung() {
534
563
  try {
535
- unlinkSync(PID_PATH);
564
+ const stat = statSync(PID_PATH);
565
+ return Date.now() - stat.mtimeMs < MIN_DAEMON_AGE_MS;
536
566
  } catch {
567
+ return false;
537
568
  }
538
- try {
539
- unlinkSync(SOCKET_PATH);
540
- } catch {
569
+ }
570
+ async function retryThenRestart(doRequest, label) {
571
+ const result = await doRequest();
572
+ if (!result.error) {
573
+ _consecutiveFailures = 0;
574
+ return result;
575
+ }
576
+ _consecutiveFailures++;
577
+ for (let i = 0; i < MAX_RETRIES_BEFORE_RESTART; i++) {
578
+ const delayMs = RETRY_DELAYS_MS[i] ?? 5e3;
579
+ process.stderr.write(`[exed-client] ${label} failed (${result.error}), retry ${i + 1}/${MAX_RETRIES_BEFORE_RESTART} in ${delayMs}ms
580
+ `);
581
+ await new Promise((r) => setTimeout(r, delayMs));
582
+ if (!_connected) {
583
+ if (!await connectToSocket()) continue;
584
+ }
585
+ const retry = await doRequest();
586
+ if (!retry.error) {
587
+ _consecutiveFailures = 0;
588
+ return retry;
589
+ }
590
+ _consecutiveFailures++;
541
591
  }
542
- spawnDaemon();
592
+ if (isDaemonTooYoung()) {
593
+ process.stderr.write(`[exed-client] ${label}: daemon too young (< ${MIN_DAEMON_AGE_MS / 1e3}s) \u2014 skipping restart
594
+ `);
595
+ return { error: result.error };
596
+ }
597
+ process.stderr.write(`[exed-client] ${label}: ${_consecutiveFailures} consecutive failures \u2014 restarting daemon
598
+ `);
599
+ killAndRespawnDaemon();
600
+ const start = Date.now();
601
+ let delay = 200;
602
+ while (Date.now() - start < CONNECT_TIMEOUT_MS) {
603
+ await new Promise((r) => setTimeout(r, delay));
604
+ if (await connectToSocket()) break;
605
+ delay = Math.min(delay * 2, 3e3);
606
+ }
607
+ if (!_connected) return { error: "Daemon restart failed" };
608
+ const final = await doRequest();
609
+ if (!final.error) _consecutiveFailures = 0;
610
+ return final;
543
611
  }
544
612
  async function embedViaClient(text, priority = "high") {
545
613
  if (!_connected && !await connectEmbedDaemon()) return null;
546
614
  _requestCount++;
547
615
  if (_requestCount % HEALTH_CHECK_INTERVAL === 0) {
548
616
  const health = await pingDaemon();
549
- if (!health) {
617
+ if (!health && !isDaemonTooYoung()) {
550
618
  process.stderr.write(`[exed-client] Periodic health check failed at request ${_requestCount} \u2014 restarting daemon
551
619
  `);
552
620
  killAndRespawnDaemon();
553
621
  const start = Date.now();
554
- let delay = 200;
622
+ let d = 200;
555
623
  while (Date.now() - start < CONNECT_TIMEOUT_MS) {
556
- await new Promise((r) => setTimeout(r, delay));
624
+ await new Promise((r) => setTimeout(r, d));
557
625
  if (await connectToSocket()) break;
558
- delay = Math.min(delay * 2, 3e3);
626
+ d = Math.min(d * 2, 3e3);
559
627
  }
560
628
  if (!_connected) return null;
561
629
  }
562
630
  }
563
- const result = await sendRequest([text], priority);
564
- if (!result.error && result.vectors?.[0]) return result.vectors[0];
565
- if (result.error) {
566
- process.stderr.write(`[exed-client] Embed failed (${result.error}) \u2014 attempting restart
567
- `);
568
- killAndRespawnDaemon();
569
- const start = Date.now();
570
- let delay = 200;
571
- while (Date.now() - start < CONNECT_TIMEOUT_MS) {
572
- await new Promise((r) => setTimeout(r, delay));
573
- if (await connectToSocket()) break;
574
- delay = Math.min(delay * 2, 3e3);
575
- }
576
- if (!_connected) return null;
577
- const retry = await sendRequest([text], priority);
578
- if (!retry.error && retry.vectors?.[0]) return retry.vectors[0];
579
- process.stderr.write(`[exed-client] Embed retry also failed: ${retry.error ?? "no vector"}
580
- `);
581
- }
582
- return null;
631
+ const result = await retryThenRestart(
632
+ () => sendRequest([text], priority),
633
+ "Embed"
634
+ );
635
+ return !result.error && result.vectors?.[0] ? result.vectors[0] : null;
583
636
  }
584
637
  function disconnectClient() {
585
638
  if (_socket) {
@@ -108,6 +108,22 @@ import { execSync } from "child_process";
108
108
  import path2 from "path";
109
109
  import os2 from "os";
110
110
  var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
111
+ var IDENTITY_DIR = path2.join(EXE_AI_DIR, "identity");
112
+
113
+ // src/lib/database-adapter.ts
114
+ import os3 from "os";
115
+ import path3 from "path";
116
+ import { createRequire } from "module";
117
+ import { pathToFileURL } from "url";
118
+ var BOOLEAN_COLUMNS_BY_TABLE = {
119
+ memories: /* @__PURE__ */ new Set(["has_error", "draft"]),
120
+ behaviors: /* @__PURE__ */ new Set(["active"]),
121
+ notifications: /* @__PURE__ */ new Set(["read"]),
122
+ users: /* @__PURE__ */ new Set(["has_personal_memory"])
123
+ };
124
+ var BOOLEAN_COLUMN_NAMES = new Set(
125
+ Object.values(BOOLEAN_COLUMNS_BY_TABLE).flatMap((cols) => [...cols])
126
+ );
111
127
 
112
128
  // src/lib/platform-procedures.ts
113
129
  var PLATFORM_PROCEDURES = [
@@ -1,9 +1,12 @@
1
- // src/lib/employees.ts
2
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
4
- import { execSync } from "child_process";
5
- import path2 from "path";
6
- import os2 from "os";
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
7
10
 
8
11
  // src/lib/config.ts
9
12
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
@@ -26,72 +29,196 @@ function resolveDataDir() {
26
29
  }
27
30
  return newDir;
28
31
  }
29
- var EXE_AI_DIR = resolveDataDir();
30
- var DB_PATH = path.join(EXE_AI_DIR, "memories.db");
31
- var MODELS_DIR = path.join(EXE_AI_DIR, "models");
32
- var CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
33
- var LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
34
- var CURRENT_CONFIG_VERSION = 1;
35
- var DEFAULT_CONFIG = {
36
- config_version: CURRENT_CONFIG_VERSION,
37
- dbPath: DB_PATH,
38
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
39
- embeddingDim: 1024,
40
- batchSize: 20,
41
- flushIntervalMs: 1e4,
42
- autoIngestion: true,
43
- autoRetrieval: true,
44
- searchMode: "hybrid",
45
- hookSearchMode: "hybrid",
46
- fileGrepEnabled: true,
47
- splashEffect: true,
48
- consolidationEnabled: true,
49
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
50
- consolidationModel: "claude-haiku-4-5-20251001",
51
- consolidationMaxCallsPerRun: 20,
52
- selfQueryRouter: true,
53
- selfQueryModel: "claude-haiku-4-5-20251001",
54
- rerankerEnabled: true,
55
- scalingRoadmap: {
56
- rerankerAutoTrigger: {
57
- enabled: true,
58
- broadQueryMinCardinality: 5e4,
59
- fetchTopK: 150,
60
- returnTopK: 5
61
- }
62
- },
63
- graphRagEnabled: true,
64
- wikiEnabled: false,
65
- wikiUrl: "",
66
- wikiApiKey: "",
67
- wikiSyncIntervalMs: 30 * 60 * 1e3,
68
- wikiWorkspaceMapping: {},
69
- wikiAutoUpdate: true,
70
- wikiAutoUpdateThreshold: 0.5,
71
- wikiAutoUpdateCreateNew: true,
72
- skillLearning: true,
73
- skillThreshold: 3,
74
- skillModel: "claude-haiku-4-5-20251001",
75
- exeHeartbeat: {
76
- enabled: true,
77
- intervalSeconds: 60,
78
- staleInProgressThresholdHours: 2
79
- },
80
- sessionLifecycle: {
81
- idleKillEnabled: true,
82
- idleKillTicksRequired: 3,
83
- idleKillIntercomAckWindowMs: 1e4,
84
- maxAutoInstances: 10
85
- },
86
- autoUpdate: {
87
- checkOnBoot: true,
88
- autoInstall: false,
89
- checkIntervalMs: 24 * 60 * 60 * 1e3
32
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG;
33
+ var init_config = __esm({
34
+ "src/lib/config.ts"() {
35
+ "use strict";
36
+ EXE_AI_DIR = resolveDataDir();
37
+ DB_PATH = path.join(EXE_AI_DIR, "memories.db");
38
+ MODELS_DIR = path.join(EXE_AI_DIR, "models");
39
+ CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
40
+ LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
41
+ CURRENT_CONFIG_VERSION = 1;
42
+ DEFAULT_CONFIG = {
43
+ config_version: CURRENT_CONFIG_VERSION,
44
+ dbPath: DB_PATH,
45
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
46
+ embeddingDim: 1024,
47
+ batchSize: 20,
48
+ flushIntervalMs: 1e4,
49
+ autoIngestion: true,
50
+ autoRetrieval: true,
51
+ searchMode: "hybrid",
52
+ hookSearchMode: "hybrid",
53
+ fileGrepEnabled: true,
54
+ splashEffect: true,
55
+ consolidationEnabled: true,
56
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
57
+ consolidationModel: "claude-haiku-4-5-20251001",
58
+ consolidationMaxCallsPerRun: 20,
59
+ selfQueryRouter: true,
60
+ selfQueryModel: "claude-haiku-4-5-20251001",
61
+ rerankerEnabled: true,
62
+ scalingRoadmap: {
63
+ rerankerAutoTrigger: {
64
+ enabled: true,
65
+ broadQueryMinCardinality: 5e4,
66
+ fetchTopK: 150,
67
+ returnTopK: 5
68
+ }
69
+ },
70
+ graphRagEnabled: true,
71
+ wikiEnabled: false,
72
+ wikiUrl: "",
73
+ wikiApiKey: "",
74
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
75
+ wikiWorkspaceMapping: {},
76
+ wikiAutoUpdate: true,
77
+ wikiAutoUpdateThreshold: 0.5,
78
+ wikiAutoUpdateCreateNew: true,
79
+ skillLearning: true,
80
+ skillThreshold: 3,
81
+ skillModel: "claude-haiku-4-5-20251001",
82
+ exeHeartbeat: {
83
+ enabled: true,
84
+ intervalSeconds: 60,
85
+ staleInProgressThresholdHours: 2
86
+ },
87
+ sessionLifecycle: {
88
+ idleKillEnabled: true,
89
+ idleKillTicksRequired: 3,
90
+ idleKillIntercomAckWindowMs: 1e4,
91
+ maxAutoInstances: 10
92
+ },
93
+ autoUpdate: {
94
+ checkOnBoot: true,
95
+ autoInstall: false,
96
+ checkIntervalMs: 24 * 60 * 60 * 1e3
97
+ }
98
+ };
90
99
  }
91
- };
100
+ });
101
+
102
+ // src/lib/runtime-table.ts
103
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
104
+ var init_runtime_table = __esm({
105
+ "src/lib/runtime-table.ts"() {
106
+ "use strict";
107
+ RUNTIME_TABLE = {
108
+ codex: {
109
+ binary: "codex",
110
+ launchMode: "interactive",
111
+ autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
112
+ inlineFlag: "--no-alt-screen",
113
+ apiKeyEnv: "OPENAI_API_KEY",
114
+ defaultModel: "gpt-5.4"
115
+ },
116
+ opencode: {
117
+ binary: "opencode",
118
+ launchMode: "exec",
119
+ autoApproveFlag: "--dangerously-skip-permissions",
120
+ inlineFlag: "",
121
+ apiKeyEnv: "ANTHROPIC_API_KEY",
122
+ defaultModel: "anthropic/claude-sonnet-4-6"
123
+ }
124
+ };
125
+ DEFAULT_RUNTIME = "claude";
126
+ }
127
+ });
128
+
129
+ // src/lib/agent-config.ts
130
+ var agent_config_exports = {};
131
+ __export(agent_config_exports, {
132
+ AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
133
+ DEFAULT_MODELS: () => DEFAULT_MODELS,
134
+ KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
135
+ RUNTIME_LABELS: () => RUNTIME_LABELS,
136
+ clearAgentRuntime: () => clearAgentRuntime,
137
+ getAgentRuntime: () => getAgentRuntime,
138
+ loadAgentConfig: () => loadAgentConfig,
139
+ saveAgentConfig: () => saveAgentConfig,
140
+ setAgentRuntime: () => setAgentRuntime
141
+ });
142
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync } from "fs";
143
+ import path2 from "path";
144
+ function loadAgentConfig() {
145
+ if (!existsSync2(AGENT_CONFIG_PATH)) return {};
146
+ try {
147
+ return JSON.parse(readFileSync2(AGENT_CONFIG_PATH, "utf-8"));
148
+ } catch {
149
+ return {};
150
+ }
151
+ }
152
+ function saveAgentConfig(config) {
153
+ const dir = path2.dirname(AGENT_CONFIG_PATH);
154
+ if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
155
+ writeFileSync(AGENT_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
156
+ }
157
+ function getAgentRuntime(agentId) {
158
+ const config = loadAgentConfig();
159
+ const entry = config[agentId];
160
+ if (entry) return entry;
161
+ const orgDefault = config["default"];
162
+ if (orgDefault) return orgDefault;
163
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
164
+ }
165
+ function setAgentRuntime(agentId, runtime, model) {
166
+ const knownModels = KNOWN_RUNTIMES[runtime];
167
+ if (!knownModels) {
168
+ return {
169
+ ok: false,
170
+ error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
171
+ };
172
+ }
173
+ if (!knownModels.includes(model)) {
174
+ return {
175
+ ok: false,
176
+ error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
177
+ };
178
+ }
179
+ const config = loadAgentConfig();
180
+ config[agentId] = { runtime, model };
181
+ saveAgentConfig(config);
182
+ return { ok: true };
183
+ }
184
+ function clearAgentRuntime(agentId) {
185
+ const config = loadAgentConfig();
186
+ delete config[agentId];
187
+ saveAgentConfig(config);
188
+ }
189
+ var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
190
+ var init_agent_config = __esm({
191
+ "src/lib/agent-config.ts"() {
192
+ "use strict";
193
+ init_config();
194
+ init_runtime_table();
195
+ AGENT_CONFIG_PATH = path2.join(EXE_AI_DIR, "agent-config.json");
196
+ KNOWN_RUNTIMES = {
197
+ claude: ["claude-opus-4", "claude-sonnet-4", "claude-haiku-4.5"],
198
+ codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
199
+ opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
200
+ };
201
+ RUNTIME_LABELS = {
202
+ claude: "Claude Code (Anthropic)",
203
+ codex: "Codex (OpenAI)",
204
+ opencode: "OpenCode (open source)"
205
+ };
206
+ DEFAULT_MODELS = {
207
+ claude: "claude-opus-4",
208
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
209
+ opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
210
+ };
211
+ }
212
+ });
92
213
 
93
214
  // src/lib/employees.ts
94
- var EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
215
+ init_config();
216
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
217
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
218
+ import { execSync } from "child_process";
219
+ import path3 from "path";
220
+ import os2 from "os";
221
+ var EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
95
222
  var DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
96
223
  var COORDINATOR_ROLE = "COO";
97
224
  function normalizeRole(role) {
@@ -129,7 +256,7 @@ function validateEmployeeName(name) {
129
256
  return { valid: true };
130
257
  }
131
258
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
132
- if (!existsSync2(employeesPath)) {
259
+ if (!existsSync3(employeesPath)) {
133
260
  return [];
134
261
  }
135
262
  const raw = await readFile2(employeesPath, "utf-8");
@@ -140,13 +267,13 @@ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
140
267
  }
141
268
  }
142
269
  async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
143
- await mkdir2(path2.dirname(employeesPath), { recursive: true });
270
+ await mkdir2(path3.dirname(employeesPath), { recursive: true });
144
271
  await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
145
272
  }
146
273
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
147
- if (!existsSync2(employeesPath)) return [];
274
+ if (!existsSync3(employeesPath)) return [];
148
275
  try {
149
- return JSON.parse(readFileSync2(employeesPath, "utf-8"));
276
+ return JSON.parse(readFileSync3(employeesPath, "utf-8"));
150
277
  } catch {
151
278
  return [];
152
279
  }
@@ -189,6 +316,54 @@ function addEmployee(employees, employee) {
189
316
  }
190
317
  return [...employees, normalized];
191
318
  }
319
+ var IDENTITY_DIR = path3.join(EXE_AI_DIR, "identity");
320
+ var TEAM_SECTION_RE = /^## Team\b.*$/m;
321
+ function appendToCoordinatorTeam(employee) {
322
+ const coordinator = getCoordinatorEmployee(loadEmployeesSync());
323
+ if (!coordinator) return;
324
+ const idPath = path3.join(IDENTITY_DIR, `${coordinator.name}.md`);
325
+ if (!existsSync3(idPath)) return;
326
+ const content = readFileSync3(idPath, "utf-8");
327
+ if (content.includes(`**${capitalize(employee.name)}`)) return;
328
+ const teamMatch = content.match(TEAM_SECTION_RE);
329
+ if (!teamMatch || teamMatch.index === void 0) return;
330
+ const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
331
+ const nextHeading = afterTeam.match(/\n## /);
332
+ const entry = `
333
+ **${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
334
+ `;
335
+ let updated;
336
+ if (nextHeading && nextHeading.index !== void 0) {
337
+ const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
338
+ updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
339
+ } else {
340
+ updated = content.trimEnd() + "\n" + entry;
341
+ }
342
+ writeFileSync2(idPath, updated, "utf-8");
343
+ }
344
+ function capitalize(s) {
345
+ return s.charAt(0).toUpperCase() + s.slice(1);
346
+ }
347
+ async function hireEmployee(employee) {
348
+ const employees = await loadEmployees();
349
+ const updated = addEmployee(employees, employee);
350
+ await saveEmployees(updated);
351
+ try {
352
+ appendToCoordinatorTeam(employee);
353
+ } catch {
354
+ }
355
+ try {
356
+ const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
357
+ const config = loadAgentConfig2();
358
+ const name = employee.name.toLowerCase();
359
+ if (!config[name] && config["default"]) {
360
+ config[name] = { ...config["default"] };
361
+ saveAgentConfig2(config);
362
+ }
363
+ } catch {
364
+ }
365
+ return updated;
366
+ }
192
367
  async function normalizeRosterCase(rosterPath) {
193
368
  const employees = await loadEmployees(rosterPath);
194
369
  let changed = false;
@@ -198,14 +373,14 @@ async function normalizeRosterCase(rosterPath) {
198
373
  emp.name = emp.name.toLowerCase();
199
374
  changed = true;
200
375
  try {
201
- const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
202
- const oldPath = path2.join(identityDir, `${oldName}.md`);
203
- const newPath = path2.join(identityDir, `${emp.name}.md`);
204
- if (existsSync2(oldPath) && !existsSync2(newPath)) {
376
+ const identityDir = path3.join(os2.homedir(), ".exe-os", "identity");
377
+ const oldPath = path3.join(identityDir, `${oldName}.md`);
378
+ const newPath = path3.join(identityDir, `${emp.name}.md`);
379
+ if (existsSync3(oldPath) && !existsSync3(newPath)) {
205
380
  renameSync2(oldPath, newPath);
206
- } else if (existsSync2(oldPath) && oldPath !== newPath) {
207
- const content = readFileSync2(oldPath, "utf-8");
208
- writeFileSync(newPath, content, "utf-8");
381
+ } else if (existsSync3(oldPath) && oldPath !== newPath) {
382
+ const content = readFileSync3(oldPath, "utf-8");
383
+ writeFileSync2(newPath, content, "utf-8");
209
384
  if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
210
385
  unlinkSync(oldPath);
211
386
  }
@@ -235,7 +410,7 @@ function registerBinSymlinks(name) {
235
410
  errors.push("Could not find 'exe-os' in PATH");
236
411
  return { created, skipped, errors };
237
412
  }
238
- const binDir = path2.dirname(exeBinPath);
413
+ const binDir = path3.dirname(exeBinPath);
239
414
  let target;
240
415
  try {
241
416
  target = readlinkSync(exeBinPath);
@@ -245,8 +420,8 @@ function registerBinSymlinks(name) {
245
420
  }
246
421
  for (const suffix of ["", "-opencode"]) {
247
422
  const linkName = `${name}${suffix}`;
248
- const linkPath = path2.join(binDir, linkName);
249
- if (existsSync2(linkPath)) {
423
+ const linkPath = path3.join(binDir, linkName);
424
+ if (existsSync3(linkPath)) {
250
425
  skipped.push(linkName);
251
426
  continue;
252
427
  }
@@ -272,6 +447,7 @@ export {
272
447
  getEmployeeByRole,
273
448
  getEmployeeNamesByRole,
274
449
  hasRole,
450
+ hireEmployee,
275
451
  isCoordinatorName,
276
452
  isCoordinatorRole,
277
453
  isMultiInstance,