@askexenow/exe-os 0.8.0 → 0.8.1
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/README.md +178 -79
- package/dist/bin/backfill-responses.js +160 -8
- package/dist/bin/backfill-vectors.js +130 -1
- package/dist/bin/cleanup-stale-review-tasks.js +130 -1
- package/dist/bin/cli.js +10111 -7540
- package/dist/bin/exe-agent.js +159 -1
- package/dist/bin/exe-assign.js +235 -16
- package/dist/bin/exe-boot.js +344 -472
- package/dist/bin/exe-call.js +145 -1
- package/dist/bin/exe-cloud.js +11 -0
- package/dist/bin/exe-dispatch.js +37 -24
- package/dist/bin/exe-doctor.js +130 -1
- package/dist/bin/exe-export-behaviors.js +150 -7
- package/dist/bin/exe-forget.js +822 -665
- package/dist/bin/exe-gateway.js +470 -62
- package/dist/bin/exe-heartbeat.js +133 -2
- package/dist/bin/exe-kill.js +150 -7
- package/dist/bin/exe-launch-agent.js +150 -7
- package/dist/bin/exe-new-employee.js +756 -224
- package/dist/bin/exe-pending-messages.js +132 -2
- package/dist/bin/exe-pending-notifications.js +130 -1
- package/dist/bin/exe-pending-reviews.js +132 -2
- package/dist/bin/exe-review.js +160 -8
- package/dist/bin/exe-search.js +2473 -2008
- package/dist/bin/exe-session-cleanup.js +238 -51
- package/dist/bin/exe-settings.js +11 -0
- package/dist/bin/exe-status.js +130 -1
- package/dist/bin/exe-team.js +130 -1
- package/dist/bin/git-sweep.js +272 -16
- package/dist/bin/graph-backfill.js +150 -7
- package/dist/bin/graph-export.js +150 -7
- package/dist/bin/install.js +5 -0
- package/dist/bin/scan-tasks.js +238 -19
- package/dist/bin/setup.js +1776 -10
- package/dist/bin/shard-migrate.js +150 -7
- package/dist/bin/update.js +9 -6
- package/dist/bin/wiki-sync.js +150 -7
- package/dist/gateway/index.js +470 -62
- package/dist/hooks/bug-report-worker.js +195 -35
- package/dist/hooks/commit-complete.js +272 -16
- package/dist/hooks/error-recall.js +2313 -1847
- package/dist/hooks/exe-heartbeat-hook.js +5 -0
- package/dist/hooks/ingest-worker.js +330 -58
- package/dist/hooks/ingest.js +11 -0
- package/dist/hooks/instructions-loaded.js +199 -10
- package/dist/hooks/notification.js +199 -10
- package/dist/hooks/post-compact.js +199 -10
- package/dist/hooks/pre-compact.js +199 -10
- package/dist/hooks/pre-tool-use.js +199 -10
- package/dist/hooks/prompt-ingest-worker.js +179 -14
- package/dist/hooks/prompt-submit.js +781 -285
- package/dist/hooks/response-ingest-worker.js +1900 -1405
- package/dist/hooks/session-end.js +456 -12
- package/dist/hooks/session-start.js +2188 -1724
- package/dist/hooks/stop.js +200 -10
- package/dist/hooks/subagent-stop.js +199 -10
- package/dist/hooks/summary-worker.js +604 -334
- package/dist/index.js +554 -61
- package/dist/lib/cloud-sync.js +5 -0
- package/dist/lib/config.js +13 -0
- package/dist/lib/consolidation.js +5 -0
- package/dist/lib/database.js +104 -0
- package/dist/lib/device-registry.js +109 -0
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/employee-templates.js +53 -26
- package/dist/lib/employees.js +5 -0
- package/dist/lib/exe-daemon-client.js +5 -0
- package/dist/lib/exe-daemon.js +493 -79
- package/dist/lib/file-grep.js +20 -4
- package/dist/lib/hybrid-search.js +1435 -190
- package/dist/lib/identity-templates.js +126 -5
- package/dist/lib/identity.js +5 -0
- package/dist/lib/license.js +5 -0
- package/dist/lib/messaging.js +37 -24
- package/dist/lib/schedules.js +130 -1
- package/dist/lib/skill-learning.js +11 -0
- package/dist/lib/status-brief.js +5 -0
- package/dist/lib/store.js +199 -10
- package/dist/lib/task-router.js +72 -6
- package/dist/lib/tasks.js +179 -50
- package/dist/lib/tmux-routing.js +179 -46
- package/dist/mcp/server.js +2129 -1855
- package/dist/mcp/tools/create-task.js +86 -36
- package/dist/mcp/tools/deactivate-behavior.js +5 -0
- package/dist/mcp/tools/list-tasks.js +39 -11
- package/dist/mcp/tools/send-message.js +37 -24
- package/dist/mcp/tools/update-task.js +153 -38
- package/dist/runtime/index.js +451 -59
- package/dist/tui/App.js +454 -59
- package/package.json +1 -1
package/dist/lib/exe-daemon.js
CHANGED
|
@@ -106,6 +106,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
106
106
|
const userSL = raw.sessionLifecycle ?? {};
|
|
107
107
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
108
108
|
}
|
|
109
|
+
function normalizeAutoUpdate(raw) {
|
|
110
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
111
|
+
const userAU = raw.autoUpdate ?? {};
|
|
112
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
113
|
+
}
|
|
109
114
|
async function loadConfig() {
|
|
110
115
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
111
116
|
await mkdir(dir, { recursive: true });
|
|
@@ -128,6 +133,7 @@ async function loadConfig() {
|
|
|
128
133
|
}
|
|
129
134
|
normalizeScalingRoadmap(migratedCfg);
|
|
130
135
|
normalizeSessionLifecycle(migratedCfg);
|
|
136
|
+
normalizeAutoUpdate(migratedCfg);
|
|
131
137
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
132
138
|
if (config.dbPath.startsWith("~")) {
|
|
133
139
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -150,6 +156,7 @@ function loadConfigSync() {
|
|
|
150
156
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
151
157
|
normalizeScalingRoadmap(migratedCfg);
|
|
152
158
|
normalizeSessionLifecycle(migratedCfg);
|
|
159
|
+
normalizeAutoUpdate(migratedCfg);
|
|
153
160
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
154
161
|
} catch {
|
|
155
162
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -169,6 +176,7 @@ async function loadConfigFrom(configPath) {
|
|
|
169
176
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
170
177
|
normalizeScalingRoadmap(migratedCfg);
|
|
171
178
|
normalizeSessionLifecycle(migratedCfg);
|
|
179
|
+
normalizeAutoUpdate(migratedCfg);
|
|
172
180
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
173
181
|
} catch {
|
|
174
182
|
return { ...DEFAULT_CONFIG };
|
|
@@ -240,6 +248,11 @@ var init_config = __esm({
|
|
|
240
248
|
idleKillTicksRequired: 3,
|
|
241
249
|
idleKillIntercomAckWindowMs: 1e4,
|
|
242
250
|
maxAutoInstances: 10
|
|
251
|
+
},
|
|
252
|
+
autoUpdate: {
|
|
253
|
+
checkOnBoot: true,
|
|
254
|
+
autoInstall: false,
|
|
255
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
243
256
|
}
|
|
244
257
|
};
|
|
245
258
|
CONFIG_MIGRATIONS = [
|
|
@@ -485,6 +498,27 @@ async function ensureSchema() {
|
|
|
485
498
|
});
|
|
486
499
|
} catch {
|
|
487
500
|
}
|
|
501
|
+
try {
|
|
502
|
+
await client.execute({
|
|
503
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
504
|
+
args: []
|
|
505
|
+
});
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
await client.execute({
|
|
510
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
511
|
+
args: []
|
|
512
|
+
});
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
await client.execute({
|
|
517
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
518
|
+
args: []
|
|
519
|
+
});
|
|
520
|
+
} catch {
|
|
521
|
+
}
|
|
488
522
|
try {
|
|
489
523
|
await client.execute({
|
|
490
524
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -895,6 +929,15 @@ async function ensureSchema() {
|
|
|
895
929
|
} catch {
|
|
896
930
|
}
|
|
897
931
|
}
|
|
932
|
+
for (const col of [
|
|
933
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
934
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
935
|
+
]) {
|
|
936
|
+
try {
|
|
937
|
+
await client.execute(col);
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
}
|
|
898
941
|
await client.executeMultiple(`
|
|
899
942
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
900
943
|
ON memories(workspace_id);
|
|
@@ -959,6 +1002,34 @@ async function ensureSchema() {
|
|
|
959
1002
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
960
1003
|
ON conversations(channel_id);
|
|
961
1004
|
`);
|
|
1005
|
+
try {
|
|
1006
|
+
await client.execute({
|
|
1007
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
1008
|
+
args: []
|
|
1009
|
+
});
|
|
1010
|
+
} catch {
|
|
1011
|
+
}
|
|
1012
|
+
try {
|
|
1013
|
+
await client.execute({
|
|
1014
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
1015
|
+
args: []
|
|
1016
|
+
});
|
|
1017
|
+
} catch {
|
|
1018
|
+
}
|
|
1019
|
+
try {
|
|
1020
|
+
await client.execute({
|
|
1021
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
1022
|
+
args: []
|
|
1023
|
+
});
|
|
1024
|
+
} catch {
|
|
1025
|
+
}
|
|
1026
|
+
try {
|
|
1027
|
+
await client.execute({
|
|
1028
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
1029
|
+
args: []
|
|
1030
|
+
});
|
|
1031
|
+
} catch {
|
|
1032
|
+
}
|
|
962
1033
|
await client.executeMultiple(`
|
|
963
1034
|
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
964
1035
|
content_text,
|
|
@@ -985,6 +1056,52 @@ async function ensureSchema() {
|
|
|
985
1056
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
986
1057
|
END;
|
|
987
1058
|
`);
|
|
1059
|
+
try {
|
|
1060
|
+
await client.execute({
|
|
1061
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1062
|
+
args: []
|
|
1063
|
+
});
|
|
1064
|
+
} catch {
|
|
1065
|
+
}
|
|
1066
|
+
try {
|
|
1067
|
+
await client.execute(
|
|
1068
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1069
|
+
);
|
|
1070
|
+
} catch {
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
await client.execute({
|
|
1074
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1075
|
+
args: []
|
|
1076
|
+
});
|
|
1077
|
+
await client.execute({
|
|
1078
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1079
|
+
args: []
|
|
1080
|
+
});
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
try {
|
|
1084
|
+
await client.execute({
|
|
1085
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1086
|
+
args: []
|
|
1087
|
+
});
|
|
1088
|
+
} catch {
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
await client.execute(
|
|
1092
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1093
|
+
);
|
|
1094
|
+
} catch {
|
|
1095
|
+
}
|
|
1096
|
+
for (const col of [
|
|
1097
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1098
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1099
|
+
]) {
|
|
1100
|
+
try {
|
|
1101
|
+
await client.execute(col);
|
|
1102
|
+
} catch {
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
988
1105
|
}
|
|
989
1106
|
async function disposeDatabase() {
|
|
990
1107
|
if (_client) {
|
|
@@ -1266,13 +1383,27 @@ async function ensureShardSchema(client) {
|
|
|
1266
1383
|
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
1267
1384
|
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
1268
1385
|
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
1269
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
1386
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
1387
|
+
// Source provenance columns (must match database.ts)
|
|
1388
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1389
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1390
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1391
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1270
1392
|
]) {
|
|
1271
1393
|
try {
|
|
1272
1394
|
await client.execute(col);
|
|
1273
1395
|
} catch {
|
|
1274
1396
|
}
|
|
1275
1397
|
}
|
|
1398
|
+
for (const idx of [
|
|
1399
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
1400
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
1401
|
+
]) {
|
|
1402
|
+
try {
|
|
1403
|
+
await client.execute(idx);
|
|
1404
|
+
} catch {
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1276
1407
|
try {
|
|
1277
1408
|
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
1278
1409
|
} catch {
|
|
@@ -1381,8 +1512,11 @@ var store_exports = {};
|
|
|
1381
1512
|
__export(store_exports, {
|
|
1382
1513
|
attachDocumentMetadata: () => attachDocumentMetadata,
|
|
1383
1514
|
buildWikiScopeFilter: () => buildWikiScopeFilter,
|
|
1515
|
+
classifyTier: () => classifyTier,
|
|
1384
1516
|
disposeStore: () => disposeStore,
|
|
1385
1517
|
flushBatch: () => flushBatch,
|
|
1518
|
+
flushTier3: () => flushTier3,
|
|
1519
|
+
getMemoryCardinality: () => getMemoryCardinality,
|
|
1386
1520
|
initStore: () => initStore,
|
|
1387
1521
|
reserveVersions: () => reserveVersions,
|
|
1388
1522
|
searchMemories: () => searchMemories,
|
|
@@ -1428,6 +1562,11 @@ async function initStore(options) {
|
|
|
1428
1562
|
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1429
1563
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1430
1564
|
}
|
|
1565
|
+
function classifyTier(record) {
|
|
1566
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
1567
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
1568
|
+
return 3;
|
|
1569
|
+
}
|
|
1431
1570
|
async function writeMemory(record) {
|
|
1432
1571
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
1433
1572
|
throw new Error(
|
|
@@ -1455,7 +1594,11 @@ async function writeMemory(record) {
|
|
|
1455
1594
|
document_id: record.document_id ?? null,
|
|
1456
1595
|
user_id: record.user_id ?? null,
|
|
1457
1596
|
char_offset: record.char_offset ?? null,
|
|
1458
|
-
page_number: record.page_number ?? null
|
|
1597
|
+
page_number: record.page_number ?? null,
|
|
1598
|
+
source_path: record.source_path ?? null,
|
|
1599
|
+
source_type: record.source_type ?? null,
|
|
1600
|
+
tier: record.tier ?? classifyTier(record),
|
|
1601
|
+
supersedes_id: record.supersedes_id ?? null
|
|
1459
1602
|
};
|
|
1460
1603
|
_pendingRecords.push(dbRow);
|
|
1461
1604
|
if (_flushTimer === null) {
|
|
@@ -1487,20 +1630,26 @@ async function flushBatch() {
|
|
|
1487
1630
|
const userId = row.user_id ?? null;
|
|
1488
1631
|
const charOffset = row.char_offset ?? null;
|
|
1489
1632
|
const pageNumber = row.page_number ?? null;
|
|
1633
|
+
const sourcePath = row.source_path ?? null;
|
|
1634
|
+
const sourceType = row.source_type ?? null;
|
|
1635
|
+
const tier = row.tier ?? 3;
|
|
1636
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
1490
1637
|
return {
|
|
1491
1638
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
1492
1639
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
1493
1640
|
tool_name, project_name,
|
|
1494
1641
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
1495
1642
|
confidence, last_accessed,
|
|
1496
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
1497
|
-
|
|
1643
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
1644
|
+
source_path, source_type, tier, supersedes_id)
|
|
1645
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
1498
1646
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
1499
1647
|
tool_name, project_name,
|
|
1500
1648
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
1501
1649
|
confidence, last_accessed,
|
|
1502
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
1503
|
-
|
|
1650
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
1651
|
+
source_path, source_type, tier, supersedes_id)
|
|
1652
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1504
1653
|
args: hasVector ? [
|
|
1505
1654
|
row.id,
|
|
1506
1655
|
row.agent_id,
|
|
@@ -1522,7 +1671,11 @@ async function flushBatch() {
|
|
|
1522
1671
|
documentId,
|
|
1523
1672
|
userId,
|
|
1524
1673
|
charOffset,
|
|
1525
|
-
pageNumber
|
|
1674
|
+
pageNumber,
|
|
1675
|
+
sourcePath,
|
|
1676
|
+
sourceType,
|
|
1677
|
+
tier,
|
|
1678
|
+
supersedesId
|
|
1526
1679
|
] : [
|
|
1527
1680
|
row.id,
|
|
1528
1681
|
row.agent_id,
|
|
@@ -1543,7 +1696,11 @@ async function flushBatch() {
|
|
|
1543
1696
|
documentId,
|
|
1544
1697
|
userId,
|
|
1545
1698
|
charOffset,
|
|
1546
|
-
pageNumber
|
|
1699
|
+
pageNumber,
|
|
1700
|
+
sourcePath,
|
|
1701
|
+
sourceType,
|
|
1702
|
+
tier,
|
|
1703
|
+
supersedesId
|
|
1547
1704
|
]
|
|
1548
1705
|
};
|
|
1549
1706
|
};
|
|
@@ -1617,7 +1774,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
1617
1774
|
has_error, raw_text, vector, importance, status,
|
|
1618
1775
|
confidence, last_accessed,
|
|
1619
1776
|
workspace_id, document_id, user_id,
|
|
1620
|
-
char_offset, page_number
|
|
1777
|
+
char_offset, page_number,
|
|
1778
|
+
source_path, source_type
|
|
1621
1779
|
FROM memories
|
|
1622
1780
|
WHERE agent_id = ?
|
|
1623
1781
|
AND vector IS NOT NULL${statusFilter}
|
|
@@ -1666,7 +1824,9 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
1666
1824
|
document_id: row.document_id ?? null,
|
|
1667
1825
|
user_id: row.user_id ?? null,
|
|
1668
1826
|
char_offset: row.char_offset ?? null,
|
|
1669
|
-
page_number: row.page_number ?? null
|
|
1827
|
+
page_number: row.page_number ?? null,
|
|
1828
|
+
source_path: row.source_path ?? null,
|
|
1829
|
+
source_type: row.source_type ?? null
|
|
1670
1830
|
}));
|
|
1671
1831
|
}
|
|
1672
1832
|
async function attachDocumentMetadata(records) {
|
|
@@ -1704,6 +1864,25 @@ async function attachDocumentMetadata(records) {
|
|
|
1704
1864
|
}
|
|
1705
1865
|
return records;
|
|
1706
1866
|
}
|
|
1867
|
+
async function flushTier3(agentId, options) {
|
|
1868
|
+
const client = getClient();
|
|
1869
|
+
const maxAge = options?.maxAgeHours ?? 72;
|
|
1870
|
+
const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
|
|
1871
|
+
if (options?.dryRun) {
|
|
1872
|
+
const result2 = await client.execute({
|
|
1873
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
1874
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
1875
|
+
args: [agentId, cutoff]
|
|
1876
|
+
});
|
|
1877
|
+
return { archived: Number(result2.rows[0]?.cnt ?? 0) };
|
|
1878
|
+
}
|
|
1879
|
+
const result = await client.execute({
|
|
1880
|
+
sql: `UPDATE memories SET status = 'archived'
|
|
1881
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
1882
|
+
args: [agentId, cutoff]
|
|
1883
|
+
});
|
|
1884
|
+
return { archived: result.rowsAffected };
|
|
1885
|
+
}
|
|
1707
1886
|
async function disposeStore() {
|
|
1708
1887
|
if (_flushTimer !== null) {
|
|
1709
1888
|
clearInterval(_flushTimer);
|
|
@@ -1734,6 +1913,18 @@ function reserveVersions(count) {
|
|
|
1734
1913
|
}
|
|
1735
1914
|
return reserved;
|
|
1736
1915
|
}
|
|
1916
|
+
async function getMemoryCardinality(agentId) {
|
|
1917
|
+
try {
|
|
1918
|
+
const client = getClient();
|
|
1919
|
+
const result = await client.execute({
|
|
1920
|
+
sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
|
|
1921
|
+
args: [agentId]
|
|
1922
|
+
});
|
|
1923
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
1924
|
+
} catch {
|
|
1925
|
+
return 0;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1737
1928
|
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
1738
1929
|
var init_store = __esm({
|
|
1739
1930
|
"src/lib/store.ts"() {
|
|
@@ -2087,6 +2278,12 @@ function drainQueue(isSessionBusy2, sendKeys) {
|
|
|
2087
2278
|
let drained = 0;
|
|
2088
2279
|
let failed = 0;
|
|
2089
2280
|
for (const item of queue) {
|
|
2281
|
+
const age = Date.now() - new Date(item.queuedAt).getTime();
|
|
2282
|
+
if (age > TTL_MS) {
|
|
2283
|
+
logQueue(`EXPIRED \u2192 ${item.targetSession} (${Math.round(age / 6e4)}min old, reason: ${item.reason})`);
|
|
2284
|
+
failed++;
|
|
2285
|
+
continue;
|
|
2286
|
+
}
|
|
2090
2287
|
try {
|
|
2091
2288
|
if (!isSessionBusy2(item.targetSession)) {
|
|
2092
2289
|
const success = sendKeys(item.targetSession);
|
|
@@ -2133,12 +2330,13 @@ function logQueue(msg) {
|
|
|
2133
2330
|
} catch {
|
|
2134
2331
|
}
|
|
2135
2332
|
}
|
|
2136
|
-
var QUEUE_PATH, MAX_RETRIES, INTERCOM_LOG;
|
|
2333
|
+
var QUEUE_PATH, MAX_RETRIES, TTL_MS, INTERCOM_LOG;
|
|
2137
2334
|
var init_intercom_queue = __esm({
|
|
2138
2335
|
"src/lib/intercom-queue.ts"() {
|
|
2139
2336
|
"use strict";
|
|
2140
2337
|
QUEUE_PATH = path5.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
2141
2338
|
MAX_RETRIES = 5;
|
|
2339
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
2142
2340
|
INTERCOM_LOG = path5.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
2143
2341
|
}
|
|
2144
2342
|
});
|
|
@@ -2289,6 +2487,17 @@ function getGitRoot(dir) {
|
|
|
2289
2487
|
return null;
|
|
2290
2488
|
}
|
|
2291
2489
|
}
|
|
2490
|
+
function getMainRepoRoot(dir) {
|
|
2491
|
+
try {
|
|
2492
|
+
const commonDir = execSync5(
|
|
2493
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
2494
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
2495
|
+
).trim();
|
|
2496
|
+
return realpath(path9.dirname(commonDir));
|
|
2497
|
+
} catch {
|
|
2498
|
+
return null;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2292
2501
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
2293
2502
|
const label = instanceLabel(employeeName, instance);
|
|
2294
2503
|
return path9.join(repoRoot, ".worktrees", label);
|
|
@@ -2550,6 +2759,36 @@ import path11 from "path";
|
|
|
2550
2759
|
import { execSync as execSync6 } from "child_process";
|
|
2551
2760
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
2552
2761
|
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
2762
|
+
async function writeCheckpoint(input) {
|
|
2763
|
+
const client = getClient();
|
|
2764
|
+
const row = await resolveTask(client, input.taskId);
|
|
2765
|
+
const taskId = String(row.id);
|
|
2766
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2767
|
+
const blockedByIds = [];
|
|
2768
|
+
if (row.blocked_by) {
|
|
2769
|
+
blockedByIds.push(String(row.blocked_by));
|
|
2770
|
+
}
|
|
2771
|
+
const checkpoint = {
|
|
2772
|
+
step: input.step,
|
|
2773
|
+
context_summary: input.contextSummary,
|
|
2774
|
+
files_touched: input.filesTouched ?? [],
|
|
2775
|
+
blocked_by_ids: blockedByIds,
|
|
2776
|
+
last_checkpoint_at: now
|
|
2777
|
+
};
|
|
2778
|
+
const result = await client.execute({
|
|
2779
|
+
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
2780
|
+
args: [JSON.stringify(checkpoint), now, taskId]
|
|
2781
|
+
});
|
|
2782
|
+
if (result.rowsAffected === 0) {
|
|
2783
|
+
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
2784
|
+
}
|
|
2785
|
+
const countResult = await client.execute({
|
|
2786
|
+
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
2787
|
+
args: [taskId]
|
|
2788
|
+
});
|
|
2789
|
+
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
2790
|
+
return { checkpointCount };
|
|
2791
|
+
}
|
|
2553
2792
|
function extractParentFromContext(contextBody) {
|
|
2554
2793
|
if (!contextBody) return null;
|
|
2555
2794
|
const match = contextBody.match(
|
|
@@ -2656,9 +2895,10 @@ async function createTaskCore(input) {
|
|
|
2656
2895
|
} catch {
|
|
2657
2896
|
}
|
|
2658
2897
|
}
|
|
2898
|
+
const complexity = input.complexity ?? "standard";
|
|
2659
2899
|
await client.execute({
|
|
2660
|
-
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, created_at, updated_at)
|
|
2661
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2900
|
+
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, created_at, updated_at)
|
|
2901
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2662
2902
|
args: [
|
|
2663
2903
|
id,
|
|
2664
2904
|
input.title,
|
|
@@ -2672,6 +2912,11 @@ async function createTaskCore(input) {
|
|
|
2672
2912
|
parentTaskId,
|
|
2673
2913
|
input.reviewer ?? null,
|
|
2674
2914
|
input.context,
|
|
2915
|
+
input.budgetTokens ?? null,
|
|
2916
|
+
input.budgetFallbackModel ?? null,
|
|
2917
|
+
0,
|
|
2918
|
+
null,
|
|
2919
|
+
complexity,
|
|
2675
2920
|
now,
|
|
2676
2921
|
now
|
|
2677
2922
|
]
|
|
@@ -2687,7 +2932,11 @@ async function createTaskCore(input) {
|
|
|
2687
2932
|
taskFile,
|
|
2688
2933
|
createdAt: now,
|
|
2689
2934
|
updatedAt: now,
|
|
2690
|
-
warning
|
|
2935
|
+
warning,
|
|
2936
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
2937
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
2938
|
+
tokensUsed: 0,
|
|
2939
|
+
tokensWarnedAt: null
|
|
2691
2940
|
};
|
|
2692
2941
|
}
|
|
2693
2942
|
async function listTasks(input) {
|
|
@@ -2727,7 +2976,12 @@ async function listTasks(input) {
|
|
|
2727
2976
|
status: String(r.status),
|
|
2728
2977
|
taskFile: String(r.task_file),
|
|
2729
2978
|
createdAt: String(r.created_at),
|
|
2730
|
-
updatedAt: String(r.updated_at)
|
|
2979
|
+
updatedAt: String(r.updated_at),
|
|
2980
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
2981
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
2982
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
2983
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
2984
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
2731
2985
|
}));
|
|
2732
2986
|
}
|
|
2733
2987
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
@@ -2735,8 +2989,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
2735
2989
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
2736
2990
|
try {
|
|
2737
2991
|
const since = new Date(taskCreatedAt).toISOString();
|
|
2992
|
+
const branch = execSync6(
|
|
2993
|
+
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
2994
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
2995
|
+
).trim();
|
|
2996
|
+
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
2738
2997
|
const commitCount = execSync6(
|
|
2739
|
-
`git log --oneline --since="${since}" 2>/dev/null | wc -l`,
|
|
2998
|
+
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
2740
2999
|
{ encoding: "utf8", timeout: 5e3 }
|
|
2741
3000
|
).trim();
|
|
2742
3001
|
const count = parseInt(commitCount, 10);
|
|
@@ -2795,6 +3054,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2795
3054
|
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
2796
3055
|
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
2797
3056
|
}
|
|
3057
|
+
try {
|
|
3058
|
+
await writeCheckpoint({
|
|
3059
|
+
taskId,
|
|
3060
|
+
step: "claimed",
|
|
3061
|
+
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
3062
|
+
});
|
|
3063
|
+
} catch {
|
|
3064
|
+
}
|
|
2798
3065
|
return { row, taskFile, now, taskId };
|
|
2799
3066
|
}
|
|
2800
3067
|
if (input.result) {
|
|
@@ -2808,6 +3075,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2808
3075
|
args: [input.status, now, taskId]
|
|
2809
3076
|
});
|
|
2810
3077
|
}
|
|
3078
|
+
try {
|
|
3079
|
+
await writeCheckpoint({
|
|
3080
|
+
taskId,
|
|
3081
|
+
step: `status_transition:${input.status}`,
|
|
3082
|
+
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
3083
|
+
});
|
|
3084
|
+
} catch {
|
|
3085
|
+
}
|
|
2811
3086
|
return { row, taskFile, now, taskId };
|
|
2812
3087
|
}
|
|
2813
3088
|
async function deleteTaskCore(taskId, _baseDir) {
|
|
@@ -3012,6 +3287,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
3012
3287
|
"- **Approved:** mark this review task as done",
|
|
3013
3288
|
"- **Needs work:** re-open the original task with notes, mark this review as done"
|
|
3014
3289
|
].join("\n");
|
|
3290
|
+
const originalTaskId = String(row.id);
|
|
3015
3291
|
const reviewTask = await createTaskCore({
|
|
3016
3292
|
title: `Review: ${agent} completed "${taskTitle}"`,
|
|
3017
3293
|
assignedTo: reviewer,
|
|
@@ -3020,6 +3296,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
3020
3296
|
priority: "p1",
|
|
3021
3297
|
context: reviewContext,
|
|
3022
3298
|
taskFile: reviewFile,
|
|
3299
|
+
parentTaskId: originalTaskId,
|
|
3023
3300
|
skipDispatch: true
|
|
3024
3301
|
});
|
|
3025
3302
|
const reviewId = reviewTask.id;
|
|
@@ -3058,23 +3335,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
3058
3335
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
3059
3336
|
try {
|
|
3060
3337
|
const client = getClient();
|
|
3061
|
-
const
|
|
3062
|
-
const
|
|
3063
|
-
|
|
3064
|
-
if (parts.length >= 3 && parts[0] === "review") {
|
|
3065
|
-
const agent = parts[1];
|
|
3066
|
-
const slug = parts.slice(2).join("-");
|
|
3067
|
-
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
3338
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3339
|
+
const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
|
|
3340
|
+
if (parentId) {
|
|
3068
3341
|
const result = await client.execute({
|
|
3069
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE
|
|
3070
|
-
args: [
|
|
3342
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
|
|
3343
|
+
args: [now, parentId]
|
|
3071
3344
|
});
|
|
3072
3345
|
if (result.rowsAffected > 0) {
|
|
3073
3346
|
process.stderr.write(
|
|
3074
|
-
`[review-cleanup] Cascaded original task to done: ${
|
|
3347
|
+
`[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
|
|
3075
3348
|
`
|
|
3076
3349
|
);
|
|
3077
3350
|
}
|
|
3351
|
+
} else {
|
|
3352
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
3353
|
+
const reviewPrefix = fileName.replace(".md", "");
|
|
3354
|
+
const parts = reviewPrefix.split("-");
|
|
3355
|
+
if (parts.length >= 3 && parts[0] === "review") {
|
|
3356
|
+
const agent = parts[1];
|
|
3357
|
+
const slug = parts.slice(2).join("-");
|
|
3358
|
+
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
3359
|
+
const result = await client.execute({
|
|
3360
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
3361
|
+
args: [now, originalTaskFile]
|
|
3362
|
+
});
|
|
3363
|
+
if (result.rowsAffected > 0) {
|
|
3364
|
+
process.stderr.write(
|
|
3365
|
+
`[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
|
|
3366
|
+
`
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3078
3370
|
}
|
|
3079
3371
|
} catch (err) {
|
|
3080
3372
|
process.stderr.write(
|
|
@@ -3195,12 +3487,23 @@ function getProjectName(cwd) {
|
|
|
3195
3487
|
const dir = cwd ?? process.cwd();
|
|
3196
3488
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
3197
3489
|
try {
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3490
|
+
let repoRoot;
|
|
3491
|
+
try {
|
|
3492
|
+
const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
|
|
3493
|
+
cwd: dir,
|
|
3494
|
+
encoding: "utf8",
|
|
3495
|
+
timeout: 2e3,
|
|
3496
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3497
|
+
}).trim();
|
|
3498
|
+
repoRoot = path14.dirname(gitCommonDir);
|
|
3499
|
+
} catch {
|
|
3500
|
+
repoRoot = execSync7("git rev-parse --show-toplevel", {
|
|
3501
|
+
cwd: dir,
|
|
3502
|
+
encoding: "utf8",
|
|
3503
|
+
timeout: 2e3,
|
|
3504
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3505
|
+
}).trim();
|
|
3506
|
+
}
|
|
3204
3507
|
_cached2 = path14.basename(repoRoot);
|
|
3205
3508
|
_cachedCwd = dir;
|
|
3206
3509
|
return _cached2;
|
|
@@ -3306,7 +3609,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
3306
3609
|
return { dispatched, session: sessionName, crossProject };
|
|
3307
3610
|
} else {
|
|
3308
3611
|
const projectDir = input.projectDir ?? process.cwd();
|
|
3309
|
-
const result = ensureEmployee(input.assignedTo, exeSession, projectDir
|
|
3612
|
+
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
3613
|
+
autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
|
|
3614
|
+
});
|
|
3310
3615
|
if (result.status === "failed") {
|
|
3311
3616
|
process.stderr.write(
|
|
3312
3617
|
`[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
|
|
@@ -3670,7 +3975,8 @@ __export(tasks_exports, {
|
|
|
3670
3975
|
resolveTask: () => resolveTask,
|
|
3671
3976
|
slugify: () => slugify,
|
|
3672
3977
|
updateTask: () => updateTask,
|
|
3673
|
-
updateTaskStatus: () => updateTaskStatus
|
|
3978
|
+
updateTaskStatus: () => updateTaskStatus,
|
|
3979
|
+
writeCheckpoint: () => writeCheckpoint
|
|
3674
3980
|
});
|
|
3675
3981
|
import path15 from "path";
|
|
3676
3982
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync6, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -3712,10 +4018,11 @@ async function updateTask(input) {
|
|
|
3712
4018
|
try {
|
|
3713
4019
|
const client = getClient();
|
|
3714
4020
|
const taskTitle = String(row.title);
|
|
4021
|
+
const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
3715
4022
|
await client.execute({
|
|
3716
4023
|
sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
|
|
3717
|
-
WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
|
|
3718
|
-
args: [now, `%left
|
|
4024
|
+
WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
|
|
4025
|
+
args: [now, `%left '${escaped}' as in\\_progress%`]
|
|
3719
4026
|
});
|
|
3720
4027
|
} catch {
|
|
3721
4028
|
}
|
|
@@ -3773,6 +4080,10 @@ async function updateTask(input) {
|
|
|
3773
4080
|
taskFile,
|
|
3774
4081
|
createdAt: String(row.created_at),
|
|
3775
4082
|
updatedAt: now,
|
|
4083
|
+
budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
|
|
4084
|
+
budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
|
|
4085
|
+
tokensUsed: Number(row.tokens_used ?? 0),
|
|
4086
|
+
tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
|
|
3776
4087
|
nextTask
|
|
3777
4088
|
};
|
|
3778
4089
|
}
|
|
@@ -4286,6 +4597,11 @@ function getSessionState(sessionName) {
|
|
|
4286
4597
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
4287
4598
|
try {
|
|
4288
4599
|
const pane = transport.capturePane(sessionName, 5);
|
|
4600
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
4601
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
4602
|
+
return "no_claude";
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4289
4605
|
if (/Running…/.test(pane)) return "tool";
|
|
4290
4606
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
4291
4607
|
return "idle";
|
|
@@ -4316,7 +4632,14 @@ function sendIntercom(targetSession) {
|
|
|
4316
4632
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
4317
4633
|
return "failed";
|
|
4318
4634
|
}
|
|
4319
|
-
|
|
4635
|
+
const sessionState = getSessionState(targetSession);
|
|
4636
|
+
if (sessionState === "no_claude") {
|
|
4637
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
4638
|
+
recordDebounce(targetSession);
|
|
4639
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
4640
|
+
return "queued";
|
|
4641
|
+
}
|
|
4642
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
4320
4643
|
queueIntercom(targetSession, "session busy at send time");
|
|
4321
4644
|
recordDebounce(targetSession);
|
|
4322
4645
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -4328,18 +4651,7 @@ function sendIntercom(targetSession) {
|
|
|
4328
4651
|
}
|
|
4329
4652
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
4330
4653
|
recordDebounce(targetSession);
|
|
4331
|
-
|
|
4332
|
-
try {
|
|
4333
|
-
execSync8(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
4334
|
-
} catch {
|
|
4335
|
-
}
|
|
4336
|
-
const state = getSessionState(targetSession);
|
|
4337
|
-
if (state === "thinking" || state === "tool") {
|
|
4338
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
4339
|
-
return "acknowledged";
|
|
4340
|
-
}
|
|
4341
|
-
}
|
|
4342
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
4654
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4343
4655
|
return "delivered";
|
|
4344
4656
|
} catch {
|
|
4345
4657
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -4356,7 +4668,17 @@ function notifyParentExe(sessionKey) {
|
|
|
4356
4668
|
process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
|
|
4357
4669
|
`);
|
|
4358
4670
|
const result = sendIntercom(target);
|
|
4359
|
-
|
|
4671
|
+
if (result === "failed") {
|
|
4672
|
+
const rootExe = resolveExeSession();
|
|
4673
|
+
if (rootExe && rootExe !== target) {
|
|
4674
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
4675
|
+
`);
|
|
4676
|
+
const fallback = sendIntercom(rootExe);
|
|
4677
|
+
return fallback !== "failed";
|
|
4678
|
+
}
|
|
4679
|
+
return false;
|
|
4680
|
+
}
|
|
4681
|
+
return true;
|
|
4360
4682
|
}
|
|
4361
4683
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4362
4684
|
if (employeeName === "exe") {
|
|
@@ -4405,7 +4727,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4405
4727
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
4406
4728
|
}
|
|
4407
4729
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
4408
|
-
const
|
|
4730
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
4731
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
4409
4732
|
if (wtPath) {
|
|
4410
4733
|
spawnOpts.cwd = wtPath;
|
|
4411
4734
|
}
|
|
@@ -4586,7 +4909,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4586
4909
|
let booted = false;
|
|
4587
4910
|
for (let i = 0; i < 30; i++) {
|
|
4588
4911
|
try {
|
|
4589
|
-
execSync8("sleep
|
|
4912
|
+
execSync8("sleep 0.5");
|
|
4590
4913
|
} catch {
|
|
4591
4914
|
}
|
|
4592
4915
|
try {
|
|
@@ -4606,7 +4929,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4606
4929
|
}
|
|
4607
4930
|
}
|
|
4608
4931
|
if (!booted) {
|
|
4609
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
4932
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4610
4933
|
}
|
|
4611
4934
|
if (!useExeAgent) {
|
|
4612
4935
|
try {
|
|
@@ -4624,7 +4947,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4624
4947
|
});
|
|
4625
4948
|
return { sessionName };
|
|
4626
4949
|
}
|
|
4627
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
4950
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
4628
4951
|
var init_tmux_routing = __esm({
|
|
4629
4952
|
"src/lib/tmux-routing.ts"() {
|
|
4630
4953
|
"use strict";
|
|
@@ -4645,8 +4968,6 @@ var init_tmux_routing = __esm({
|
|
|
4645
4968
|
DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4646
4969
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4647
4970
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4648
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
4649
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
4650
4971
|
}
|
|
4651
4972
|
});
|
|
4652
4973
|
|
|
@@ -4774,9 +5095,8 @@ function createRealDeps(getClient2) {
|
|
|
4774
5095
|
AND r.title LIKE 'Review:%'
|
|
4775
5096
|
AND NOT EXISTS (
|
|
4776
5097
|
SELECT 1 FROM tasks t
|
|
4777
|
-
WHERE t.
|
|
5098
|
+
WHERE t.id = r.parent_task_id
|
|
4778
5099
|
AND t.status = 'done'
|
|
4779
|
-
AND r.title LIKE '%"' || t.title || '"%'
|
|
4780
5100
|
AND t.updated_at > r.created_at
|
|
4781
5101
|
)`,
|
|
4782
5102
|
args: []
|
|
@@ -4797,8 +5117,7 @@ function createRealDeps(getClient2) {
|
|
|
4797
5117
|
AND t.assigned_to != 'exe'
|
|
4798
5118
|
AND NOT EXISTS (
|
|
4799
5119
|
SELECT 1 FROM tasks r
|
|
4800
|
-
WHERE r.
|
|
4801
|
-
AND r.title LIKE '%' || t.title || '%'
|
|
5120
|
+
WHERE r.parent_task_id = t.id
|
|
4802
5121
|
AND r.status IN ('open', 'in_progress', 'done', 'cancelled')
|
|
4803
5122
|
)`,
|
|
4804
5123
|
args: []
|
|
@@ -6231,8 +6550,8 @@ async function embedDirect(text) {
|
|
|
6231
6550
|
const llamaCpp = await import("node-llama-cpp");
|
|
6232
6551
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6233
6552
|
const { existsSync: existsSync18 } = await import("fs");
|
|
6234
|
-
const
|
|
6235
|
-
const modelPath =
|
|
6553
|
+
const path22 = await import("path");
|
|
6554
|
+
const modelPath = path22.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
6236
6555
|
if (!existsSync18(modelPath)) {
|
|
6237
6556
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
6238
6557
|
}
|
|
@@ -6922,8 +7241,8 @@ __export(wiki_sync_exports, {
|
|
|
6922
7241
|
listWorkspaces: () => listWorkspaces,
|
|
6923
7242
|
syncMemories: () => syncMemories
|
|
6924
7243
|
});
|
|
6925
|
-
async function wikiRequest(config,
|
|
6926
|
-
const url = `${config.wikiUrl}/api/v1${
|
|
7244
|
+
async function wikiRequest(config, path22, method = "GET", body) {
|
|
7245
|
+
const url = `${config.wikiUrl}/api/v1${path22}`;
|
|
6927
7246
|
const headers = {
|
|
6928
7247
|
"Authorization": `Bearer ${config.wikiApiKey}`,
|
|
6929
7248
|
"Content-Type": "application/json"
|
|
@@ -6934,7 +7253,7 @@ async function wikiRequest(config, path21, method = "GET", body) {
|
|
|
6934
7253
|
body: body ? JSON.stringify(body) : void 0
|
|
6935
7254
|
});
|
|
6936
7255
|
if (!response.ok) {
|
|
6937
|
-
throw new Error(`Wiki API ${method} ${
|
|
7256
|
+
throw new Error(`Wiki API ${method} ${path22}: ${response.status} ${response.statusText}`);
|
|
6938
7257
|
}
|
|
6939
7258
|
return response.json();
|
|
6940
7259
|
}
|
|
@@ -7038,6 +7357,54 @@ var init_wiki_sync = __esm({
|
|
|
7038
7357
|
}
|
|
7039
7358
|
});
|
|
7040
7359
|
|
|
7360
|
+
// src/lib/update-check.ts
|
|
7361
|
+
var update_check_exports = {};
|
|
7362
|
+
__export(update_check_exports, {
|
|
7363
|
+
checkForUpdate: () => checkForUpdate,
|
|
7364
|
+
getLocalVersion: () => getLocalVersion,
|
|
7365
|
+
getRemoteVersion: () => getRemoteVersion
|
|
7366
|
+
});
|
|
7367
|
+
import { execSync as execSync12 } from "child_process";
|
|
7368
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
7369
|
+
import path19 from "path";
|
|
7370
|
+
function getLocalVersion(packageRoot) {
|
|
7371
|
+
const pkgPath = path19.join(packageRoot, "package.json");
|
|
7372
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
7373
|
+
return pkg.version;
|
|
7374
|
+
}
|
|
7375
|
+
function getRemoteVersion() {
|
|
7376
|
+
try {
|
|
7377
|
+
const output = execSync12("npm view @askexenow/exe-os version", {
|
|
7378
|
+
encoding: "utf-8",
|
|
7379
|
+
timeout: 15e3,
|
|
7380
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
7381
|
+
});
|
|
7382
|
+
return output.trim();
|
|
7383
|
+
} catch {
|
|
7384
|
+
return null;
|
|
7385
|
+
}
|
|
7386
|
+
}
|
|
7387
|
+
function checkForUpdate(packageRoot) {
|
|
7388
|
+
const localVersion = getLocalVersion(packageRoot);
|
|
7389
|
+
const remoteVersion = getRemoteVersion();
|
|
7390
|
+
if (!remoteVersion) {
|
|
7391
|
+
return {
|
|
7392
|
+
updateAvailable: false,
|
|
7393
|
+
localVersion,
|
|
7394
|
+
error: "Could not reach npm registry or package not published yet"
|
|
7395
|
+
};
|
|
7396
|
+
}
|
|
7397
|
+
if (remoteVersion === localVersion) {
|
|
7398
|
+
return { updateAvailable: false, localVersion, remoteVersion };
|
|
7399
|
+
}
|
|
7400
|
+
return { updateAvailable: true, localVersion, remoteVersion };
|
|
7401
|
+
}
|
|
7402
|
+
var init_update_check = __esm({
|
|
7403
|
+
"src/lib/update-check.ts"() {
|
|
7404
|
+
"use strict";
|
|
7405
|
+
}
|
|
7406
|
+
});
|
|
7407
|
+
|
|
7041
7408
|
// src/lib/ws-auth.ts
|
|
7042
7409
|
var ws_auth_exports = {};
|
|
7043
7410
|
__export(ws_auth_exports, {
|
|
@@ -7074,12 +7441,12 @@ __export(device_registry_exports, {
|
|
|
7074
7441
|
});
|
|
7075
7442
|
import crypto9 from "crypto";
|
|
7076
7443
|
import os7 from "os";
|
|
7077
|
-
import { readFileSync as
|
|
7078
|
-
import
|
|
7444
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync6, mkdirSync as mkdirSync8, existsSync as existsSync16 } from "fs";
|
|
7445
|
+
import path20 from "path";
|
|
7079
7446
|
function getDeviceInfo() {
|
|
7080
7447
|
if (existsSync16(DEVICE_JSON_PATH)) {
|
|
7081
7448
|
try {
|
|
7082
|
-
const raw =
|
|
7449
|
+
const raw = readFileSync13(DEVICE_JSON_PATH, "utf8");
|
|
7083
7450
|
const data = JSON.parse(raw);
|
|
7084
7451
|
if (data.deviceId && data.friendlyName && data.hostname) {
|
|
7085
7452
|
return data;
|
|
@@ -7093,7 +7460,7 @@ function getDeviceInfo() {
|
|
|
7093
7460
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
7094
7461
|
hostname
|
|
7095
7462
|
};
|
|
7096
|
-
mkdirSync8(
|
|
7463
|
+
mkdirSync8(path20.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
7097
7464
|
writeFileSync6(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
7098
7465
|
return info;
|
|
7099
7466
|
}
|
|
@@ -7134,7 +7501,7 @@ var init_device_registry = __esm({
|
|
|
7134
7501
|
"src/lib/device-registry.ts"() {
|
|
7135
7502
|
"use strict";
|
|
7136
7503
|
init_config();
|
|
7137
|
-
DEVICE_JSON_PATH =
|
|
7504
|
+
DEVICE_JSON_PATH = path20.join(EXE_AI_DIR, "device.json");
|
|
7138
7505
|
}
|
|
7139
7506
|
});
|
|
7140
7507
|
|
|
@@ -7574,11 +7941,11 @@ var init_messaging = __esm({
|
|
|
7574
7941
|
init_config();
|
|
7575
7942
|
init_memory();
|
|
7576
7943
|
import net2 from "net";
|
|
7577
|
-
import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync5, mkdirSync as mkdirSync9, existsSync as existsSync17, readFileSync as
|
|
7578
|
-
import
|
|
7944
|
+
import { writeFileSync as writeFileSync7, unlinkSync as unlinkSync5, mkdirSync as mkdirSync9, existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
7945
|
+
import path21 from "path";
|
|
7579
7946
|
import { getLlama } from "node-llama-cpp";
|
|
7580
|
-
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
7581
|
-
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
7947
|
+
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path21.join(EXE_AI_DIR, "exed.sock");
|
|
7948
|
+
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path21.join(EXE_AI_DIR, "exed.pid");
|
|
7582
7949
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
7583
7950
|
var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
7584
7951
|
var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
|
|
@@ -7593,7 +7960,7 @@ var _idleTimer = null;
|
|
|
7593
7960
|
var _requestsServed = 0;
|
|
7594
7961
|
var _startedAt = Date.now();
|
|
7595
7962
|
async function loadModel() {
|
|
7596
|
-
const modelPath =
|
|
7963
|
+
const modelPath = path21.join(MODELS_DIR, MODEL_FILE);
|
|
7597
7964
|
if (!existsSync17(modelPath)) {
|
|
7598
7965
|
process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
|
|
7599
7966
|
`);
|
|
@@ -7716,12 +8083,12 @@ async function handleHealthCheck(socket, requestId) {
|
|
|
7716
8083
|
}
|
|
7717
8084
|
}
|
|
7718
8085
|
function startServer() {
|
|
7719
|
-
mkdirSync9(
|
|
8086
|
+
mkdirSync9(path21.dirname(SOCKET_PATH2), { recursive: true });
|
|
7720
8087
|
for (const oldFile of ["embed.sock", "embed.pid"]) {
|
|
7721
|
-
const oldPath =
|
|
8088
|
+
const oldPath = path21.join(path21.dirname(SOCKET_PATH2), oldFile);
|
|
7722
8089
|
try {
|
|
7723
8090
|
if (oldFile.endsWith(".pid")) {
|
|
7724
|
-
const pid = parseInt(
|
|
8091
|
+
const pid = parseInt(readFileSync14(oldPath, "utf8").trim(), 10);
|
|
7725
8092
|
if (pid > 0) try {
|
|
7726
8093
|
process.kill(pid, "SIGKILL");
|
|
7727
8094
|
} catch {
|
|
@@ -8145,7 +8512,7 @@ process.on("SIGTERM", () => void shutdown());
|
|
|
8145
8512
|
function checkExistingDaemon() {
|
|
8146
8513
|
try {
|
|
8147
8514
|
if (!existsSync17(PID_PATH2)) return false;
|
|
8148
|
-
const pid = parseInt(
|
|
8515
|
+
const pid = parseInt(readFileSync14(PID_PATH2, "utf8").trim(), 10);
|
|
8149
8516
|
if (!pid || isNaN(pid)) return false;
|
|
8150
8517
|
process.kill(pid, 0);
|
|
8151
8518
|
process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
|
|
@@ -8164,6 +8531,52 @@ function checkExistingDaemon() {
|
|
|
8164
8531
|
return false;
|
|
8165
8532
|
}
|
|
8166
8533
|
}
|
|
8534
|
+
function startAutoUpdateCheck() {
|
|
8535
|
+
const tick = async () => {
|
|
8536
|
+
try {
|
|
8537
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
8538
|
+
const config = await loadConfig2();
|
|
8539
|
+
const { checkIntervalMs, autoInstall } = config.autoUpdate;
|
|
8540
|
+
if (!autoInstall && !config.autoUpdate.checkOnBoot) return;
|
|
8541
|
+
const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update_check(), update_check_exports));
|
|
8542
|
+
const packageRoot = new URL("../..", import.meta.url).pathname;
|
|
8543
|
+
const result = checkForUpdate2(packageRoot);
|
|
8544
|
+
if (!result.updateAvailable) return;
|
|
8545
|
+
process.stderr.write(
|
|
8546
|
+
`[exed] Update available: v${result.localVersion} \u2192 v${result.remoteVersion}
|
|
8547
|
+
`
|
|
8548
|
+
);
|
|
8549
|
+
if (autoInstall) {
|
|
8550
|
+
process.stderr.write("[exed] Auto-installing update...\n");
|
|
8551
|
+
const { execSync: execSync13 } = await import("child_process");
|
|
8552
|
+
execSync13("npm install -g @askexenow/exe-os@latest", {
|
|
8553
|
+
timeout: 12e4,
|
|
8554
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
8555
|
+
});
|
|
8556
|
+
process.stderr.write(
|
|
8557
|
+
`[exed] Updated to v${result.remoteVersion}. Restart daemon to apply.
|
|
8558
|
+
`
|
|
8559
|
+
);
|
|
8560
|
+
}
|
|
8561
|
+
void checkIntervalMs;
|
|
8562
|
+
} catch (err) {
|
|
8563
|
+
process.stderr.write(
|
|
8564
|
+
`[exed] Auto-update check error: ${err instanceof Error ? err.message : String(err)}
|
|
8565
|
+
`
|
|
8566
|
+
);
|
|
8567
|
+
}
|
|
8568
|
+
};
|
|
8569
|
+
Promise.resolve().then(() => (init_config(), config_exports)).then(({ loadConfig: loadConfig2 }) => loadConfig2()).then((config) => {
|
|
8570
|
+
const intervalMs = config.autoUpdate.checkIntervalMs;
|
|
8571
|
+
const timer = setInterval(() => void tick(), intervalMs);
|
|
8572
|
+
timer.unref();
|
|
8573
|
+
process.stderr.write(
|
|
8574
|
+
`[exed] Auto-update check started (every ${Math.round(intervalMs / 36e5)}h, autoInstall: ${config.autoUpdate.autoInstall})
|
|
8575
|
+
`
|
|
8576
|
+
);
|
|
8577
|
+
}).catch(() => {
|
|
8578
|
+
});
|
|
8579
|
+
}
|
|
8167
8580
|
if (checkExistingDaemon()) {
|
|
8168
8581
|
process.exit(0);
|
|
8169
8582
|
}
|
|
@@ -8181,6 +8594,7 @@ try {
|
|
|
8181
8594
|
startWikiSync();
|
|
8182
8595
|
startIntercomQueueDrain();
|
|
8183
8596
|
startConfidenceDecay();
|
|
8597
|
+
startAutoUpdateCheck();
|
|
8184
8598
|
try {
|
|
8185
8599
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
8186
8600
|
const config = await loadConfig2();
|