@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.
- package/dist/bin/backfill-conversations.js +754 -79
- package/dist/bin/backfill-responses.js +752 -77
- package/dist/bin/backfill-vectors.js +752 -77
- package/dist/bin/cleanup-stale-review-tasks.js +668 -37
- package/dist/bin/cli.js +1399 -607
- package/dist/bin/exe-agent-config.js +123 -95
- package/dist/bin/exe-agent.js +41 -25
- package/dist/bin/exe-assign.js +732 -57
- package/dist/bin/exe-boot.js +795 -155
- package/dist/bin/exe-call.js +209 -138
- package/dist/bin/exe-cloud.js +35 -12
- package/dist/bin/exe-dispatch.js +703 -72
- package/dist/bin/exe-doctor.js +648 -26
- package/dist/bin/exe-export-behaviors.js +650 -20
- package/dist/bin/exe-forget.js +635 -13
- package/dist/bin/exe-gateway.js +1064 -273
- package/dist/bin/exe-heartbeat.js +676 -45
- package/dist/bin/exe-kill.js +646 -16
- package/dist/bin/exe-launch-agent.js +887 -97
- package/dist/bin/exe-link.js +658 -43
- package/dist/bin/exe-new-employee.js +378 -177
- package/dist/bin/exe-pending-messages.js +656 -34
- package/dist/bin/exe-pending-notifications.js +635 -13
- package/dist/bin/exe-pending-reviews.js +659 -37
- package/dist/bin/exe-rename.js +645 -30
- package/dist/bin/exe-review.js +635 -13
- package/dist/bin/exe-search.js +771 -88
- package/dist/bin/exe-session-cleanup.js +845 -152
- package/dist/bin/exe-settings.js +127 -91
- package/dist/bin/exe-start-codex.js +729 -94
- package/dist/bin/exe-start-opencode.js +717 -82
- package/dist/bin/exe-status.js +668 -37
- package/dist/bin/exe-team.js +635 -13
- package/dist/bin/git-sweep.js +731 -91
- package/dist/bin/graph-backfill.js +643 -13
- package/dist/bin/graph-export.js +646 -16
- package/dist/bin/install.js +596 -193
- package/dist/bin/scan-tasks.js +735 -95
- package/dist/bin/setup.js +1038 -210
- package/dist/bin/shard-migrate.js +645 -15
- package/dist/bin/wiki-sync.js +646 -16
- package/dist/gateway/index.js +1038 -247
- package/dist/hooks/bug-report-worker.js +902 -172
- package/dist/hooks/commit-complete.js +729 -89
- package/dist/hooks/error-recall.js +776 -93
- package/dist/hooks/exe-heartbeat-hook.js +85 -71
- package/dist/hooks/ingest-worker.js +851 -158
- package/dist/hooks/ingest.js +90 -73
- package/dist/hooks/instructions-loaded.js +669 -38
- package/dist/hooks/notification.js +661 -30
- package/dist/hooks/post-compact.js +685 -45
- package/dist/hooks/pre-compact.js +729 -89
- package/dist/hooks/pre-tool-use.js +883 -127
- package/dist/hooks/prompt-ingest-worker.js +758 -83
- package/dist/hooks/prompt-submit.js +1071 -321
- package/dist/hooks/response-ingest-worker.js +758 -83
- package/dist/hooks/session-end.js +732 -92
- package/dist/hooks/session-start.js +1042 -209
- package/dist/hooks/stop.js +691 -51
- package/dist/hooks/subagent-stop.js +685 -45
- package/dist/hooks/summary-worker.js +827 -134
- package/dist/index.js +1026 -234
- package/dist/lib/cloud-sync.js +663 -48
- package/dist/lib/consolidation.js +26 -3
- package/dist/lib/database.js +626 -18
- package/dist/lib/db.js +2261 -0
- package/dist/lib/device-registry.js +640 -25
- package/dist/lib/embedder.js +96 -43
- package/dist/lib/employee-templates.js +16 -0
- package/dist/lib/employees.js +259 -83
- package/dist/lib/exe-daemon-client.js +101 -63
- package/dist/lib/exe-daemon.js +905 -164
- package/dist/lib/hybrid-search.js +771 -88
- package/dist/lib/identity.js +27 -7
- package/dist/lib/messaging.js +66 -30
- package/dist/lib/reminders.js +21 -1
- package/dist/lib/schedules.js +636 -14
- package/dist/lib/skill-learning.js +21 -1
- package/dist/lib/store.js +643 -13
- package/dist/lib/task-router.js +82 -71
- package/dist/lib/tasks.js +109 -73
- package/dist/lib/tmux-routing.js +98 -62
- package/dist/lib/token-spend.js +26 -6
- package/dist/mcp/server.js +1807 -472
- package/dist/mcp/tools/complete-reminder.js +21 -1
- package/dist/mcp/tools/create-reminder.js +21 -1
- package/dist/mcp/tools/create-task.js +301 -166
- package/dist/mcp/tools/deactivate-behavior.js +24 -4
- package/dist/mcp/tools/list-reminders.js +21 -1
- package/dist/mcp/tools/list-tasks.js +206 -40
- package/dist/mcp/tools/send-message.js +69 -33
- package/dist/mcp/tools/update-task.js +86 -50
- package/dist/runtime/index.js +731 -91
- package/dist/tui/App.js +864 -125
- package/package.json +3 -2
package/dist/lib/embedder.js
CHANGED
|
@@ -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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
_socket = null;
|
|
531
|
-
}
|
|
532
|
-
_connected = false;
|
|
533
|
-
_buffer = "";
|
|
561
|
+
}
|
|
562
|
+
function isDaemonTooYoung() {
|
|
534
563
|
try {
|
|
535
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
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
|
|
622
|
+
let d = 200;
|
|
555
623
|
while (Date.now() - start < CONNECT_TIMEOUT_MS) {
|
|
556
|
-
await new Promise((r) => setTimeout(r,
|
|
624
|
+
await new Promise((r) => setTimeout(r, d));
|
|
557
625
|
if (await connectToSocket()) break;
|
|
558
|
-
|
|
626
|
+
d = Math.min(d * 2, 3e3);
|
|
559
627
|
}
|
|
560
628
|
if (!_connected) return null;
|
|
561
629
|
}
|
|
562
630
|
}
|
|
563
|
-
const result = await
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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 = [
|
package/dist/lib/employees.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
30
|
-
var
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
274
|
+
if (!existsSync3(employeesPath)) return [];
|
|
148
275
|
try {
|
|
149
|
-
return JSON.parse(
|
|
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 =
|
|
202
|
-
const oldPath =
|
|
203
|
-
const newPath =
|
|
204
|
-
if (
|
|
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 (
|
|
207
|
-
const content =
|
|
208
|
-
|
|
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 =
|
|
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 =
|
|
249
|
-
if (
|
|
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,
|