@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/gateway/index.js
CHANGED
|
@@ -488,6 +488,27 @@ async function ensureSchema() {
|
|
|
488
488
|
});
|
|
489
489
|
} catch {
|
|
490
490
|
}
|
|
491
|
+
try {
|
|
492
|
+
await client.execute({
|
|
493
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
494
|
+
args: []
|
|
495
|
+
});
|
|
496
|
+
} catch {
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
await client.execute({
|
|
500
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
501
|
+
args: []
|
|
502
|
+
});
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
try {
|
|
506
|
+
await client.execute({
|
|
507
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
508
|
+
args: []
|
|
509
|
+
});
|
|
510
|
+
} catch {
|
|
511
|
+
}
|
|
491
512
|
try {
|
|
492
513
|
await client.execute({
|
|
493
514
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -898,6 +919,15 @@ async function ensureSchema() {
|
|
|
898
919
|
} catch {
|
|
899
920
|
}
|
|
900
921
|
}
|
|
922
|
+
for (const col of [
|
|
923
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
924
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
925
|
+
]) {
|
|
926
|
+
try {
|
|
927
|
+
await client.execute(col);
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
}
|
|
901
931
|
await client.executeMultiple(`
|
|
902
932
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
903
933
|
ON memories(workspace_id);
|
|
@@ -962,6 +992,34 @@ async function ensureSchema() {
|
|
|
962
992
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
963
993
|
ON conversations(channel_id);
|
|
964
994
|
`);
|
|
995
|
+
try {
|
|
996
|
+
await client.execute({
|
|
997
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
998
|
+
args: []
|
|
999
|
+
});
|
|
1000
|
+
} catch {
|
|
1001
|
+
}
|
|
1002
|
+
try {
|
|
1003
|
+
await client.execute({
|
|
1004
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
1005
|
+
args: []
|
|
1006
|
+
});
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
1009
|
+
try {
|
|
1010
|
+
await client.execute({
|
|
1011
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
1012
|
+
args: []
|
|
1013
|
+
});
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
try {
|
|
1017
|
+
await client.execute({
|
|
1018
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
1019
|
+
args: []
|
|
1020
|
+
});
|
|
1021
|
+
} catch {
|
|
1022
|
+
}
|
|
965
1023
|
await client.executeMultiple(`
|
|
966
1024
|
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
967
1025
|
content_text,
|
|
@@ -988,6 +1046,52 @@ async function ensureSchema() {
|
|
|
988
1046
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
989
1047
|
END;
|
|
990
1048
|
`);
|
|
1049
|
+
try {
|
|
1050
|
+
await client.execute({
|
|
1051
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1052
|
+
args: []
|
|
1053
|
+
});
|
|
1054
|
+
} catch {
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
await client.execute(
|
|
1058
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1059
|
+
);
|
|
1060
|
+
} catch {
|
|
1061
|
+
}
|
|
1062
|
+
try {
|
|
1063
|
+
await client.execute({
|
|
1064
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1065
|
+
args: []
|
|
1066
|
+
});
|
|
1067
|
+
await client.execute({
|
|
1068
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1069
|
+
args: []
|
|
1070
|
+
});
|
|
1071
|
+
} catch {
|
|
1072
|
+
}
|
|
1073
|
+
try {
|
|
1074
|
+
await client.execute({
|
|
1075
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1076
|
+
args: []
|
|
1077
|
+
});
|
|
1078
|
+
} catch {
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
await client.execute(
|
|
1082
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1083
|
+
);
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
for (const col of [
|
|
1087
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1088
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1089
|
+
]) {
|
|
1090
|
+
try {
|
|
1091
|
+
await client.execute(col);
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
991
1095
|
}
|
|
992
1096
|
async function disposeDatabase() {
|
|
993
1097
|
if (_client) {
|
|
@@ -1095,6 +1199,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
1095
1199
|
const userSL = raw.sessionLifecycle ?? {};
|
|
1096
1200
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1097
1201
|
}
|
|
1202
|
+
function normalizeAutoUpdate(raw) {
|
|
1203
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1204
|
+
const userAU = raw.autoUpdate ?? {};
|
|
1205
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1206
|
+
}
|
|
1098
1207
|
async function loadConfig() {
|
|
1099
1208
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1100
1209
|
await mkdir(dir, { recursive: true });
|
|
@@ -1117,6 +1226,7 @@ async function loadConfig() {
|
|
|
1117
1226
|
}
|
|
1118
1227
|
normalizeScalingRoadmap(migratedCfg);
|
|
1119
1228
|
normalizeSessionLifecycle(migratedCfg);
|
|
1229
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1120
1230
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
1121
1231
|
if (config2.dbPath.startsWith("~")) {
|
|
1122
1232
|
config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
|
|
@@ -1139,6 +1249,7 @@ function loadConfigSync() {
|
|
|
1139
1249
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1140
1250
|
normalizeScalingRoadmap(migratedCfg);
|
|
1141
1251
|
normalizeSessionLifecycle(migratedCfg);
|
|
1252
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1142
1253
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
1143
1254
|
} catch {
|
|
1144
1255
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -1158,6 +1269,7 @@ async function loadConfigFrom(configPath) {
|
|
|
1158
1269
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1159
1270
|
normalizeScalingRoadmap(migratedCfg);
|
|
1160
1271
|
normalizeSessionLifecycle(migratedCfg);
|
|
1272
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1161
1273
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
1162
1274
|
} catch {
|
|
1163
1275
|
return { ...DEFAULT_CONFIG };
|
|
@@ -1229,6 +1341,11 @@ var init_config = __esm({
|
|
|
1229
1341
|
idleKillTicksRequired: 3,
|
|
1230
1342
|
idleKillIntercomAckWindowMs: 1e4,
|
|
1231
1343
|
maxAutoInstances: 10
|
|
1344
|
+
},
|
|
1345
|
+
autoUpdate: {
|
|
1346
|
+
checkOnBoot: true,
|
|
1347
|
+
autoInstall: false,
|
|
1348
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1232
1349
|
}
|
|
1233
1350
|
};
|
|
1234
1351
|
CONFIG_MIGRATIONS = [
|
|
@@ -1815,13 +1932,27 @@ async function ensureShardSchema(client) {
|
|
|
1815
1932
|
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
1816
1933
|
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
1817
1934
|
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
1818
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
1935
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
1936
|
+
// Source provenance columns (must match database.ts)
|
|
1937
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1938
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1939
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1940
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1819
1941
|
]) {
|
|
1820
1942
|
try {
|
|
1821
1943
|
await client.execute(col);
|
|
1822
1944
|
} catch {
|
|
1823
1945
|
}
|
|
1824
1946
|
}
|
|
1947
|
+
for (const idx of [
|
|
1948
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
1949
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
1950
|
+
]) {
|
|
1951
|
+
try {
|
|
1952
|
+
await client.execute(idx);
|
|
1953
|
+
} catch {
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1825
1956
|
try {
|
|
1826
1957
|
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
1827
1958
|
} catch {
|
|
@@ -1930,8 +2061,11 @@ var store_exports = {};
|
|
|
1930
2061
|
__export(store_exports, {
|
|
1931
2062
|
attachDocumentMetadata: () => attachDocumentMetadata,
|
|
1932
2063
|
buildWikiScopeFilter: () => buildWikiScopeFilter,
|
|
2064
|
+
classifyTier: () => classifyTier,
|
|
1933
2065
|
disposeStore: () => disposeStore,
|
|
1934
2066
|
flushBatch: () => flushBatch,
|
|
2067
|
+
flushTier3: () => flushTier3,
|
|
2068
|
+
getMemoryCardinality: () => getMemoryCardinality,
|
|
1935
2069
|
initStore: () => initStore,
|
|
1936
2070
|
reserveVersions: () => reserveVersions,
|
|
1937
2071
|
searchMemories: () => searchMemories,
|
|
@@ -1977,6 +2111,11 @@ async function initStore(options) {
|
|
|
1977
2111
|
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1978
2112
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1979
2113
|
}
|
|
2114
|
+
function classifyTier(record) {
|
|
2115
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
2116
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
2117
|
+
return 3;
|
|
2118
|
+
}
|
|
1980
2119
|
async function writeMemory(record) {
|
|
1981
2120
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
1982
2121
|
throw new Error(
|
|
@@ -2004,7 +2143,11 @@ async function writeMemory(record) {
|
|
|
2004
2143
|
document_id: record.document_id ?? null,
|
|
2005
2144
|
user_id: record.user_id ?? null,
|
|
2006
2145
|
char_offset: record.char_offset ?? null,
|
|
2007
|
-
page_number: record.page_number ?? null
|
|
2146
|
+
page_number: record.page_number ?? null,
|
|
2147
|
+
source_path: record.source_path ?? null,
|
|
2148
|
+
source_type: record.source_type ?? null,
|
|
2149
|
+
tier: record.tier ?? classifyTier(record),
|
|
2150
|
+
supersedes_id: record.supersedes_id ?? null
|
|
2008
2151
|
};
|
|
2009
2152
|
_pendingRecords.push(dbRow);
|
|
2010
2153
|
if (_flushTimer === null) {
|
|
@@ -2036,20 +2179,26 @@ async function flushBatch() {
|
|
|
2036
2179
|
const userId = row.user_id ?? null;
|
|
2037
2180
|
const charOffset = row.char_offset ?? null;
|
|
2038
2181
|
const pageNumber = row.page_number ?? null;
|
|
2182
|
+
const sourcePath = row.source_path ?? null;
|
|
2183
|
+
const sourceType = row.source_type ?? null;
|
|
2184
|
+
const tier = row.tier ?? 3;
|
|
2185
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
2039
2186
|
return {
|
|
2040
2187
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
2041
2188
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
2042
2189
|
tool_name, project_name,
|
|
2043
2190
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2044
2191
|
confidence, last_accessed,
|
|
2045
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
2046
|
-
|
|
2192
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2193
|
+
source_path, source_type, tier, supersedes_id)
|
|
2194
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
2047
2195
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
2048
2196
|
tool_name, project_name,
|
|
2049
2197
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2050
2198
|
confidence, last_accessed,
|
|
2051
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
2052
|
-
|
|
2199
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2200
|
+
source_path, source_type, tier, supersedes_id)
|
|
2201
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2053
2202
|
args: hasVector ? [
|
|
2054
2203
|
row.id,
|
|
2055
2204
|
row.agent_id,
|
|
@@ -2071,7 +2220,11 @@ async function flushBatch() {
|
|
|
2071
2220
|
documentId,
|
|
2072
2221
|
userId,
|
|
2073
2222
|
charOffset,
|
|
2074
|
-
pageNumber
|
|
2223
|
+
pageNumber,
|
|
2224
|
+
sourcePath,
|
|
2225
|
+
sourceType,
|
|
2226
|
+
tier,
|
|
2227
|
+
supersedesId
|
|
2075
2228
|
] : [
|
|
2076
2229
|
row.id,
|
|
2077
2230
|
row.agent_id,
|
|
@@ -2092,7 +2245,11 @@ async function flushBatch() {
|
|
|
2092
2245
|
documentId,
|
|
2093
2246
|
userId,
|
|
2094
2247
|
charOffset,
|
|
2095
|
-
pageNumber
|
|
2248
|
+
pageNumber,
|
|
2249
|
+
sourcePath,
|
|
2250
|
+
sourceType,
|
|
2251
|
+
tier,
|
|
2252
|
+
supersedesId
|
|
2096
2253
|
]
|
|
2097
2254
|
};
|
|
2098
2255
|
};
|
|
@@ -2166,7 +2323,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
2166
2323
|
has_error, raw_text, vector, importance, status,
|
|
2167
2324
|
confidence, last_accessed,
|
|
2168
2325
|
workspace_id, document_id, user_id,
|
|
2169
|
-
char_offset, page_number
|
|
2326
|
+
char_offset, page_number,
|
|
2327
|
+
source_path, source_type
|
|
2170
2328
|
FROM memories
|
|
2171
2329
|
WHERE agent_id = ?
|
|
2172
2330
|
AND vector IS NOT NULL${statusFilter}
|
|
@@ -2215,7 +2373,9 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
2215
2373
|
document_id: row.document_id ?? null,
|
|
2216
2374
|
user_id: row.user_id ?? null,
|
|
2217
2375
|
char_offset: row.char_offset ?? null,
|
|
2218
|
-
page_number: row.page_number ?? null
|
|
2376
|
+
page_number: row.page_number ?? null,
|
|
2377
|
+
source_path: row.source_path ?? null,
|
|
2378
|
+
source_type: row.source_type ?? null
|
|
2219
2379
|
}));
|
|
2220
2380
|
}
|
|
2221
2381
|
async function attachDocumentMetadata(records) {
|
|
@@ -2253,6 +2413,25 @@ async function attachDocumentMetadata(records) {
|
|
|
2253
2413
|
}
|
|
2254
2414
|
return records;
|
|
2255
2415
|
}
|
|
2416
|
+
async function flushTier3(agentId, options) {
|
|
2417
|
+
const client = getClient();
|
|
2418
|
+
const maxAge = options?.maxAgeHours ?? 72;
|
|
2419
|
+
const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
|
|
2420
|
+
if (options?.dryRun) {
|
|
2421
|
+
const result2 = await client.execute({
|
|
2422
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
2423
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
2424
|
+
args: [agentId, cutoff]
|
|
2425
|
+
});
|
|
2426
|
+
return { archived: Number(result2.rows[0]?.cnt ?? 0) };
|
|
2427
|
+
}
|
|
2428
|
+
const result = await client.execute({
|
|
2429
|
+
sql: `UPDATE memories SET status = 'archived'
|
|
2430
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
2431
|
+
args: [agentId, cutoff]
|
|
2432
|
+
});
|
|
2433
|
+
return { archived: result.rowsAffected };
|
|
2434
|
+
}
|
|
2256
2435
|
async function disposeStore() {
|
|
2257
2436
|
if (_flushTimer !== null) {
|
|
2258
2437
|
clearInterval(_flushTimer);
|
|
@@ -2283,6 +2462,18 @@ function reserveVersions(count) {
|
|
|
2283
2462
|
}
|
|
2284
2463
|
return reserved;
|
|
2285
2464
|
}
|
|
2465
|
+
async function getMemoryCardinality(agentId) {
|
|
2466
|
+
try {
|
|
2467
|
+
const client = getClient();
|
|
2468
|
+
const result = await client.execute({
|
|
2469
|
+
sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
|
|
2470
|
+
args: [agentId]
|
|
2471
|
+
});
|
|
2472
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
2473
|
+
} catch {
|
|
2474
|
+
return 0;
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2286
2477
|
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
2287
2478
|
var init_store = __esm({
|
|
2288
2479
|
"src/lib/store.ts"() {
|
|
@@ -2766,11 +2957,12 @@ function queueIntercom(targetSession, reason) {
|
|
|
2766
2957
|
}
|
|
2767
2958
|
writeQueue(queue);
|
|
2768
2959
|
}
|
|
2769
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
2960
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
2770
2961
|
var init_intercom_queue = __esm({
|
|
2771
2962
|
"src/lib/intercom-queue.ts"() {
|
|
2772
2963
|
"use strict";
|
|
2773
2964
|
QUEUE_PATH = path7.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
2965
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
2774
2966
|
INTERCOM_LOG = path7.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
2775
2967
|
}
|
|
2776
2968
|
});
|
|
@@ -2907,6 +3099,17 @@ function getGitRoot(dir) {
|
|
|
2907
3099
|
return null;
|
|
2908
3100
|
}
|
|
2909
3101
|
}
|
|
3102
|
+
function getMainRepoRoot(dir) {
|
|
3103
|
+
try {
|
|
3104
|
+
const commonDir = execSync4(
|
|
3105
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
3106
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
3107
|
+
).trim();
|
|
3108
|
+
return realpath(path11.dirname(commonDir));
|
|
3109
|
+
} catch {
|
|
3110
|
+
return null;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
2910
3113
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
2911
3114
|
const label = instanceLabel(employeeName, instance);
|
|
2912
3115
|
return path11.join(repoRoot, ".worktrees", label);
|
|
@@ -3143,6 +3346,11 @@ function getSessionState(sessionName) {
|
|
|
3143
3346
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
3144
3347
|
try {
|
|
3145
3348
|
const pane = transport.capturePane(sessionName, 5);
|
|
3349
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
3350
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
3351
|
+
return "no_claude";
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3146
3354
|
if (/Running…/.test(pane)) return "tool";
|
|
3147
3355
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
3148
3356
|
return "idle";
|
|
@@ -3150,10 +3358,6 @@ function getSessionState(sessionName) {
|
|
|
3150
3358
|
return "offline";
|
|
3151
3359
|
}
|
|
3152
3360
|
}
|
|
3153
|
-
function isSessionBusy(sessionName) {
|
|
3154
|
-
const state = getSessionState(sessionName);
|
|
3155
|
-
return state === "thinking" || state === "tool";
|
|
3156
|
-
}
|
|
3157
3361
|
function isExeSession(sessionName) {
|
|
3158
3362
|
return /^exe\d*$/.test(sessionName);
|
|
3159
3363
|
}
|
|
@@ -3173,7 +3377,14 @@ function sendIntercom(targetSession) {
|
|
|
3173
3377
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
3174
3378
|
return "failed";
|
|
3175
3379
|
}
|
|
3176
|
-
|
|
3380
|
+
const sessionState = getSessionState(targetSession);
|
|
3381
|
+
if (sessionState === "no_claude") {
|
|
3382
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
3383
|
+
recordDebounce(targetSession);
|
|
3384
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
3385
|
+
return "queued";
|
|
3386
|
+
}
|
|
3387
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3177
3388
|
queueIntercom(targetSession, "session busy at send time");
|
|
3178
3389
|
recordDebounce(targetSession);
|
|
3179
3390
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -3185,18 +3396,7 @@ function sendIntercom(targetSession) {
|
|
|
3185
3396
|
}
|
|
3186
3397
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3187
3398
|
recordDebounce(targetSession);
|
|
3188
|
-
|
|
3189
|
-
try {
|
|
3190
|
-
execSync5(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
3191
|
-
} catch {
|
|
3192
|
-
}
|
|
3193
|
-
const state = getSessionState(targetSession);
|
|
3194
|
-
if (state === "thinking" || state === "tool") {
|
|
3195
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
3196
|
-
return "acknowledged";
|
|
3197
|
-
}
|
|
3198
|
-
}
|
|
3199
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
3399
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
3200
3400
|
return "delivered";
|
|
3201
3401
|
} catch {
|
|
3202
3402
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3213,7 +3413,17 @@ function notifyParentExe(sessionKey) {
|
|
|
3213
3413
|
process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
|
|
3214
3414
|
`);
|
|
3215
3415
|
const result = sendIntercom(target);
|
|
3216
|
-
|
|
3416
|
+
if (result === "failed") {
|
|
3417
|
+
const rootExe = resolveExeSession();
|
|
3418
|
+
if (rootExe && rootExe !== target) {
|
|
3419
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
3420
|
+
`);
|
|
3421
|
+
const fallback = sendIntercom(rootExe);
|
|
3422
|
+
return fallback !== "failed";
|
|
3423
|
+
}
|
|
3424
|
+
return false;
|
|
3425
|
+
}
|
|
3426
|
+
return true;
|
|
3217
3427
|
}
|
|
3218
3428
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3219
3429
|
if (employeeName === "exe") {
|
|
@@ -3262,7 +3472,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3262
3472
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
3263
3473
|
}
|
|
3264
3474
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
3265
|
-
const
|
|
3475
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
3476
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
3266
3477
|
if (wtPath) {
|
|
3267
3478
|
spawnOpts.cwd = wtPath;
|
|
3268
3479
|
}
|
|
@@ -3443,7 +3654,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3443
3654
|
let booted = false;
|
|
3444
3655
|
for (let i = 0; i < 30; i++) {
|
|
3445
3656
|
try {
|
|
3446
|
-
execSync5("sleep
|
|
3657
|
+
execSync5("sleep 0.5");
|
|
3447
3658
|
} catch {
|
|
3448
3659
|
}
|
|
3449
3660
|
try {
|
|
@@ -3463,7 +3674,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3463
3674
|
}
|
|
3464
3675
|
}
|
|
3465
3676
|
if (!booted) {
|
|
3466
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
3677
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3467
3678
|
}
|
|
3468
3679
|
if (!useExeAgent) {
|
|
3469
3680
|
try {
|
|
@@ -3481,7 +3692,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3481
3692
|
});
|
|
3482
3693
|
return { sessionName };
|
|
3483
3694
|
}
|
|
3484
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
3695
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3485
3696
|
var init_tmux_routing = __esm({
|
|
3486
3697
|
"src/lib/tmux-routing.ts"() {
|
|
3487
3698
|
"use strict";
|
|
@@ -3501,8 +3712,6 @@ var init_tmux_routing = __esm({
|
|
|
3501
3712
|
DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3502
3713
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3503
3714
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
3504
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
3505
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
3506
3715
|
}
|
|
3507
3716
|
});
|
|
3508
3717
|
|
|
@@ -3814,6 +4023,36 @@ import path14 from "path";
|
|
|
3814
4023
|
import { execSync as execSync6 } from "child_process";
|
|
3815
4024
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
3816
4025
|
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
4026
|
+
async function writeCheckpoint(input) {
|
|
4027
|
+
const client = getClient();
|
|
4028
|
+
const row = await resolveTask(client, input.taskId);
|
|
4029
|
+
const taskId = String(row.id);
|
|
4030
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4031
|
+
const blockedByIds = [];
|
|
4032
|
+
if (row.blocked_by) {
|
|
4033
|
+
blockedByIds.push(String(row.blocked_by));
|
|
4034
|
+
}
|
|
4035
|
+
const checkpoint = {
|
|
4036
|
+
step: input.step,
|
|
4037
|
+
context_summary: input.contextSummary,
|
|
4038
|
+
files_touched: input.filesTouched ?? [],
|
|
4039
|
+
blocked_by_ids: blockedByIds,
|
|
4040
|
+
last_checkpoint_at: now
|
|
4041
|
+
};
|
|
4042
|
+
const result = await client.execute({
|
|
4043
|
+
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
4044
|
+
args: [JSON.stringify(checkpoint), now, taskId]
|
|
4045
|
+
});
|
|
4046
|
+
if (result.rowsAffected === 0) {
|
|
4047
|
+
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
4048
|
+
}
|
|
4049
|
+
const countResult = await client.execute({
|
|
4050
|
+
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
4051
|
+
args: [taskId]
|
|
4052
|
+
});
|
|
4053
|
+
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
4054
|
+
return { checkpointCount };
|
|
4055
|
+
}
|
|
3817
4056
|
function extractParentFromContext(contextBody) {
|
|
3818
4057
|
if (!contextBody) return null;
|
|
3819
4058
|
const match = contextBody.match(
|
|
@@ -3920,9 +4159,10 @@ async function createTaskCore(input) {
|
|
|
3920
4159
|
} catch {
|
|
3921
4160
|
}
|
|
3922
4161
|
}
|
|
4162
|
+
const complexity = input.complexity ?? "standard";
|
|
3923
4163
|
await client.execute({
|
|
3924
|
-
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)
|
|
3925
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4164
|
+
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)
|
|
4165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
3926
4166
|
args: [
|
|
3927
4167
|
id,
|
|
3928
4168
|
input.title,
|
|
@@ -3936,6 +4176,11 @@ async function createTaskCore(input) {
|
|
|
3936
4176
|
parentTaskId,
|
|
3937
4177
|
input.reviewer ?? null,
|
|
3938
4178
|
input.context,
|
|
4179
|
+
input.budgetTokens ?? null,
|
|
4180
|
+
input.budgetFallbackModel ?? null,
|
|
4181
|
+
0,
|
|
4182
|
+
null,
|
|
4183
|
+
complexity,
|
|
3939
4184
|
now,
|
|
3940
4185
|
now
|
|
3941
4186
|
]
|
|
@@ -3951,7 +4196,11 @@ async function createTaskCore(input) {
|
|
|
3951
4196
|
taskFile,
|
|
3952
4197
|
createdAt: now,
|
|
3953
4198
|
updatedAt: now,
|
|
3954
|
-
warning
|
|
4199
|
+
warning,
|
|
4200
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
4201
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
4202
|
+
tokensUsed: 0,
|
|
4203
|
+
tokensWarnedAt: null
|
|
3955
4204
|
};
|
|
3956
4205
|
}
|
|
3957
4206
|
async function listTasks(input) {
|
|
@@ -3991,7 +4240,12 @@ async function listTasks(input) {
|
|
|
3991
4240
|
status: String(r.status),
|
|
3992
4241
|
taskFile: String(r.task_file),
|
|
3993
4242
|
createdAt: String(r.created_at),
|
|
3994
|
-
updatedAt: String(r.updated_at)
|
|
4243
|
+
updatedAt: String(r.updated_at),
|
|
4244
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
4245
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
4246
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
4247
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
4248
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
3995
4249
|
}));
|
|
3996
4250
|
}
|
|
3997
4251
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
@@ -3999,8 +4253,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
3999
4253
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
4000
4254
|
try {
|
|
4001
4255
|
const since = new Date(taskCreatedAt).toISOString();
|
|
4256
|
+
const branch = execSync6(
|
|
4257
|
+
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
4258
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
4259
|
+
).trim();
|
|
4260
|
+
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
4002
4261
|
const commitCount = execSync6(
|
|
4003
|
-
`git log --oneline --since="${since}" 2>/dev/null | wc -l`,
|
|
4262
|
+
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
4004
4263
|
{ encoding: "utf8", timeout: 5e3 }
|
|
4005
4264
|
).trim();
|
|
4006
4265
|
const count = parseInt(commitCount, 10);
|
|
@@ -4059,6 +4318,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4059
4318
|
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
4060
4319
|
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
4061
4320
|
}
|
|
4321
|
+
try {
|
|
4322
|
+
await writeCheckpoint({
|
|
4323
|
+
taskId,
|
|
4324
|
+
step: "claimed",
|
|
4325
|
+
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
4326
|
+
});
|
|
4327
|
+
} catch {
|
|
4328
|
+
}
|
|
4062
4329
|
return { row, taskFile, now, taskId };
|
|
4063
4330
|
}
|
|
4064
4331
|
if (input.result) {
|
|
@@ -4072,6 +4339,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4072
4339
|
args: [input.status, now, taskId]
|
|
4073
4340
|
});
|
|
4074
4341
|
}
|
|
4342
|
+
try {
|
|
4343
|
+
await writeCheckpoint({
|
|
4344
|
+
taskId,
|
|
4345
|
+
step: `status_transition:${input.status}`,
|
|
4346
|
+
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
4347
|
+
});
|
|
4348
|
+
} catch {
|
|
4349
|
+
}
|
|
4075
4350
|
return { row, taskFile, now, taskId };
|
|
4076
4351
|
}
|
|
4077
4352
|
async function deleteTaskCore(taskId, _baseDir) {
|
|
@@ -4225,23 +4500,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4225
4500
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
4226
4501
|
try {
|
|
4227
4502
|
const client = getClient();
|
|
4228
|
-
const
|
|
4229
|
-
const
|
|
4230
|
-
|
|
4231
|
-
if (parts.length >= 3 && parts[0] === "review") {
|
|
4232
|
-
const agent = parts[1];
|
|
4233
|
-
const slug = parts.slice(2).join("-");
|
|
4234
|
-
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
4503
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4504
|
+
const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
|
|
4505
|
+
if (parentId) {
|
|
4235
4506
|
const result = await client.execute({
|
|
4236
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE
|
|
4237
|
-
args: [
|
|
4507
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
|
|
4508
|
+
args: [now, parentId]
|
|
4238
4509
|
});
|
|
4239
4510
|
if (result.rowsAffected > 0) {
|
|
4240
4511
|
process.stderr.write(
|
|
4241
|
-
`[review-cleanup] Cascaded original task to done: ${
|
|
4512
|
+
`[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
|
|
4242
4513
|
`
|
|
4243
4514
|
);
|
|
4244
4515
|
}
|
|
4516
|
+
} else {
|
|
4517
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
4518
|
+
const reviewPrefix = fileName.replace(".md", "");
|
|
4519
|
+
const parts = reviewPrefix.split("-");
|
|
4520
|
+
if (parts.length >= 3 && parts[0] === "review") {
|
|
4521
|
+
const agent = parts[1];
|
|
4522
|
+
const slug = parts.slice(2).join("-");
|
|
4523
|
+
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
4524
|
+
const result = await client.execute({
|
|
4525
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
4526
|
+
args: [now, originalTaskFile]
|
|
4527
|
+
});
|
|
4528
|
+
if (result.rowsAffected > 0) {
|
|
4529
|
+
process.stderr.write(
|
|
4530
|
+
`[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
|
|
4531
|
+
`
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4245
4535
|
}
|
|
4246
4536
|
} catch (err) {
|
|
4247
4537
|
process.stderr.write(
|
|
@@ -4362,12 +4652,23 @@ function getProjectName(cwd) {
|
|
|
4362
4652
|
const dir = cwd ?? process.cwd();
|
|
4363
4653
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4364
4654
|
try {
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4655
|
+
let repoRoot;
|
|
4656
|
+
try {
|
|
4657
|
+
const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
|
|
4658
|
+
cwd: dir,
|
|
4659
|
+
encoding: "utf8",
|
|
4660
|
+
timeout: 2e3,
|
|
4661
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4662
|
+
}).trim();
|
|
4663
|
+
repoRoot = path17.dirname(gitCommonDir);
|
|
4664
|
+
} catch {
|
|
4665
|
+
repoRoot = execSync7("git rev-parse --show-toplevel", {
|
|
4666
|
+
cwd: dir,
|
|
4667
|
+
encoding: "utf8",
|
|
4668
|
+
timeout: 2e3,
|
|
4669
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4670
|
+
}).trim();
|
|
4671
|
+
}
|
|
4371
4672
|
_cached2 = path17.basename(repoRoot);
|
|
4372
4673
|
_cachedCwd = dir;
|
|
4373
4674
|
return _cached2;
|
|
@@ -4473,7 +4774,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
4473
4774
|
return { dispatched, session: sessionName, crossProject };
|
|
4474
4775
|
} else {
|
|
4475
4776
|
const projectDir = input.projectDir ?? process.cwd();
|
|
4476
|
-
const result = ensureEmployee(input.assignedTo, exeSession, projectDir
|
|
4777
|
+
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
4778
|
+
autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
|
|
4779
|
+
});
|
|
4477
4780
|
if (result.status === "failed") {
|
|
4478
4781
|
process.stderr.write(
|
|
4479
4782
|
`[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
|
|
@@ -4837,7 +5140,8 @@ __export(tasks_exports, {
|
|
|
4837
5140
|
resolveTask: () => resolveTask,
|
|
4838
5141
|
slugify: () => slugify,
|
|
4839
5142
|
updateTask: () => updateTask,
|
|
4840
|
-
updateTaskStatus: () => updateTaskStatus
|
|
5143
|
+
updateTaskStatus: () => updateTaskStatus,
|
|
5144
|
+
writeCheckpoint: () => writeCheckpoint
|
|
4841
5145
|
});
|
|
4842
5146
|
import path18 from "path";
|
|
4843
5147
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8, unlinkSync as unlinkSync4 } from "fs";
|
|
@@ -4879,10 +5183,11 @@ async function updateTask(input) {
|
|
|
4879
5183
|
try {
|
|
4880
5184
|
const client = getClient();
|
|
4881
5185
|
const taskTitle = String(row.title);
|
|
5186
|
+
const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
4882
5187
|
await client.execute({
|
|
4883
5188
|
sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
|
|
4884
|
-
WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
|
|
4885
|
-
args: [now, `%left
|
|
5189
|
+
WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
|
|
5190
|
+
args: [now, `%left '${escaped}' as in\\_progress%`]
|
|
4886
5191
|
});
|
|
4887
5192
|
} catch {
|
|
4888
5193
|
}
|
|
@@ -4940,6 +5245,10 @@ async function updateTask(input) {
|
|
|
4940
5245
|
taskFile,
|
|
4941
5246
|
createdAt: String(row.created_at),
|
|
4942
5247
|
updatedAt: now,
|
|
5248
|
+
budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
|
|
5249
|
+
budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
|
|
5250
|
+
tokensUsed: Number(row.tokens_used ?? 0),
|
|
5251
|
+
tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
|
|
4943
5252
|
nextTask
|
|
4944
5253
|
};
|
|
4945
5254
|
}
|
|
@@ -8680,7 +8989,92 @@ async function executeCreateTask(params) {
|
|
|
8680
8989
|
baseDir: process.cwd()
|
|
8681
8990
|
});
|
|
8682
8991
|
}
|
|
8683
|
-
async function
|
|
8992
|
+
async function executeUpdateWiki(params) {
|
|
8993
|
+
const apiUrl = process.env.EXE_WIKI_API_URL;
|
|
8994
|
+
const apiKey = process.env.EXE_WIKI_API_KEY;
|
|
8995
|
+
if (!apiUrl || !apiKey) {
|
|
8996
|
+
throw new Error("Wiki not configured: EXE_WIKI_API_URL / EXE_WIKI_API_KEY not set");
|
|
8997
|
+
}
|
|
8998
|
+
const workspace = params.workspace;
|
|
8999
|
+
const content = params.content ?? params.text;
|
|
9000
|
+
const mode = params.mode ?? "append";
|
|
9001
|
+
const section = params.section;
|
|
9002
|
+
if (!workspace || !content) {
|
|
9003
|
+
throw new Error("update_wiki requires 'workspace' and 'content' params");
|
|
9004
|
+
}
|
|
9005
|
+
const documentId = params.document_id;
|
|
9006
|
+
if (documentId && mode === "append") {
|
|
9007
|
+
const readRes = await fetch(`${apiUrl}/v1/document/${documentId}`, {
|
|
9008
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
9009
|
+
signal: AbortSignal.timeout(15e3)
|
|
9010
|
+
});
|
|
9011
|
+
if (!readRes.ok) {
|
|
9012
|
+
throw new Error(`Wiki read failed (${readRes.status})`);
|
|
9013
|
+
}
|
|
9014
|
+
const doc = await readRes.json();
|
|
9015
|
+
const existingContent = String(doc.content ?? "");
|
|
9016
|
+
const title = String(doc.title ?? "Untitled");
|
|
9017
|
+
await fetch(`${apiUrl}/v1/document/${documentId}`, {
|
|
9018
|
+
method: "DELETE",
|
|
9019
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
9020
|
+
signal: AbortSignal.timeout(15e3)
|
|
9021
|
+
}).catch(() => {
|
|
9022
|
+
});
|
|
9023
|
+
const uploadRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
9024
|
+
method: "POST",
|
|
9025
|
+
headers: {
|
|
9026
|
+
"Content-Type": "application/json",
|
|
9027
|
+
Authorization: `Bearer ${apiKey}`
|
|
9028
|
+
},
|
|
9029
|
+
body: JSON.stringify({
|
|
9030
|
+
textContent: section ? existingContent + `
|
|
9031
|
+
|
|
9032
|
+
## ${section}
|
|
9033
|
+
${content}` : existingContent + "\n\n" + content,
|
|
9034
|
+
metadata: { title },
|
|
9035
|
+
workspaceSlugs: [workspace]
|
|
9036
|
+
}),
|
|
9037
|
+
signal: AbortSignal.timeout(15e3)
|
|
9038
|
+
});
|
|
9039
|
+
if (!uploadRes.ok) throw new Error(`Wiki upload failed (${uploadRes.status})`);
|
|
9040
|
+
} else {
|
|
9041
|
+
const title = params.title ?? "Auto-generated";
|
|
9042
|
+
const res = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
9043
|
+
method: "POST",
|
|
9044
|
+
headers: {
|
|
9045
|
+
"Content-Type": "application/json",
|
|
9046
|
+
Authorization: `Bearer ${apiKey}`
|
|
9047
|
+
},
|
|
9048
|
+
body: JSON.stringify({
|
|
9049
|
+
textContent: content,
|
|
9050
|
+
metadata: { title },
|
|
9051
|
+
workspaceSlugs: [workspace]
|
|
9052
|
+
}),
|
|
9053
|
+
signal: AbortSignal.timeout(15e3)
|
|
9054
|
+
});
|
|
9055
|
+
if (!res.ok) throw new Error(`Wiki create failed (${res.status})`);
|
|
9056
|
+
}
|
|
9057
|
+
}
|
|
9058
|
+
async function routeToApproval(action, resolvedParams, triggerName) {
|
|
9059
|
+
const { createTask: createTask2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
9060
|
+
const actionSummary = action.type === "send_whatsapp" ? `Send WhatsApp to ${resolvedParams.to ?? resolvedParams.recipient ?? "unknown"}: "${(resolvedParams.message ?? resolvedParams.text ?? "").slice(0, 100)}"` : `${action.type}: ${JSON.stringify(resolvedParams).slice(0, 200)}`;
|
|
9061
|
+
await createTask2({
|
|
9062
|
+
title: `[Approval Required] ${triggerName}: ${action.type}`,
|
|
9063
|
+
assignedTo: "exe",
|
|
9064
|
+
assignedBy: "trigger-engine",
|
|
9065
|
+
projectName: resolvedParams.project ?? "exe-os",
|
|
9066
|
+
priority: "p1",
|
|
9067
|
+
context: `Trigger "${triggerName}" wants to execute this action but requires approval.
|
|
9068
|
+
|
|
9069
|
+
**Action:** ${action.type}
|
|
9070
|
+
**Summary:** ${actionSummary}
|
|
9071
|
+
**Full params:** ${JSON.stringify(resolvedParams, null, 2)}
|
|
9072
|
+
|
|
9073
|
+
To approve, manually run the action via MCP tools.`,
|
|
9074
|
+
baseDir: process.cwd()
|
|
9075
|
+
});
|
|
9076
|
+
}
|
|
9077
|
+
async function executeAction(action, record, executor, triggerName) {
|
|
8684
9078
|
if (executor) {
|
|
8685
9079
|
return executor(action, record);
|
|
8686
9080
|
}
|
|
@@ -8688,6 +9082,17 @@ async function executeAction(action, record, executor) {
|
|
|
8688
9082
|
for (const [key, val] of Object.entries(action.params)) {
|
|
8689
9083
|
resolvedParams[key] = substituteTemplate(val, record);
|
|
8690
9084
|
}
|
|
9085
|
+
if (action.requires_approval) {
|
|
9086
|
+
try {
|
|
9087
|
+
await routeToApproval(action, resolvedParams, triggerName ?? "Unknown trigger");
|
|
9088
|
+
return { success: true };
|
|
9089
|
+
} catch (err) {
|
|
9090
|
+
return {
|
|
9091
|
+
success: false,
|
|
9092
|
+
error: `Approval routing failed: ${err instanceof Error ? err.message : String(err)}`
|
|
9093
|
+
};
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
8691
9096
|
try {
|
|
8692
9097
|
switch (action.type) {
|
|
8693
9098
|
case "send_whatsapp":
|
|
@@ -8699,6 +9104,9 @@ async function executeAction(action, record, executor) {
|
|
|
8699
9104
|
case "create_task":
|
|
8700
9105
|
await executeCreateTask(resolvedParams);
|
|
8701
9106
|
break;
|
|
9107
|
+
case "update_wiki":
|
|
9108
|
+
await executeUpdateWiki(resolvedParams);
|
|
9109
|
+
break;
|
|
8702
9110
|
case "mcp_tool":
|
|
8703
9111
|
console.log(
|
|
8704
9112
|
`[trigger-engine] mcp_tool action: ${JSON.stringify(resolvedParams)}`
|
|
@@ -8723,7 +9131,7 @@ async function processCRMEvent(event, executor, triggersOverride) {
|
|
|
8723
9131
|
if (!evaluateConditions(trigger.conditions, event.record)) continue;
|
|
8724
9132
|
const actionResults = [];
|
|
8725
9133
|
for (const action of trigger.actions) {
|
|
8726
|
-
const result = await executeAction(action, event.record, executor);
|
|
9134
|
+
const result = await executeAction(action, event.record, executor, trigger.name);
|
|
8727
9135
|
actionResults.push({
|
|
8728
9136
|
type: action.type,
|
|
8729
9137
|
success: result.success,
|