@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/bin/exe-gateway.js
CHANGED
|
@@ -489,6 +489,27 @@ async function ensureSchema() {
|
|
|
489
489
|
});
|
|
490
490
|
} catch {
|
|
491
491
|
}
|
|
492
|
+
try {
|
|
493
|
+
await client.execute({
|
|
494
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
495
|
+
args: []
|
|
496
|
+
});
|
|
497
|
+
} catch {
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
await client.execute({
|
|
501
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
502
|
+
args: []
|
|
503
|
+
});
|
|
504
|
+
} catch {
|
|
505
|
+
}
|
|
506
|
+
try {
|
|
507
|
+
await client.execute({
|
|
508
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
509
|
+
args: []
|
|
510
|
+
});
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
492
513
|
try {
|
|
493
514
|
await client.execute({
|
|
494
515
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -899,6 +920,15 @@ async function ensureSchema() {
|
|
|
899
920
|
} catch {
|
|
900
921
|
}
|
|
901
922
|
}
|
|
923
|
+
for (const col of [
|
|
924
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
925
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
926
|
+
]) {
|
|
927
|
+
try {
|
|
928
|
+
await client.execute(col);
|
|
929
|
+
} catch {
|
|
930
|
+
}
|
|
931
|
+
}
|
|
902
932
|
await client.executeMultiple(`
|
|
903
933
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
904
934
|
ON memories(workspace_id);
|
|
@@ -963,6 +993,34 @@ async function ensureSchema() {
|
|
|
963
993
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
964
994
|
ON conversations(channel_id);
|
|
965
995
|
`);
|
|
996
|
+
try {
|
|
997
|
+
await client.execute({
|
|
998
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
999
|
+
args: []
|
|
1000
|
+
});
|
|
1001
|
+
} catch {
|
|
1002
|
+
}
|
|
1003
|
+
try {
|
|
1004
|
+
await client.execute({
|
|
1005
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
1006
|
+
args: []
|
|
1007
|
+
});
|
|
1008
|
+
} catch {
|
|
1009
|
+
}
|
|
1010
|
+
try {
|
|
1011
|
+
await client.execute({
|
|
1012
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
1013
|
+
args: []
|
|
1014
|
+
});
|
|
1015
|
+
} catch {
|
|
1016
|
+
}
|
|
1017
|
+
try {
|
|
1018
|
+
await client.execute({
|
|
1019
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
1020
|
+
args: []
|
|
1021
|
+
});
|
|
1022
|
+
} catch {
|
|
1023
|
+
}
|
|
966
1024
|
await client.executeMultiple(`
|
|
967
1025
|
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
968
1026
|
content_text,
|
|
@@ -989,6 +1047,52 @@ async function ensureSchema() {
|
|
|
989
1047
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
990
1048
|
END;
|
|
991
1049
|
`);
|
|
1050
|
+
try {
|
|
1051
|
+
await client.execute({
|
|
1052
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1053
|
+
args: []
|
|
1054
|
+
});
|
|
1055
|
+
} catch {
|
|
1056
|
+
}
|
|
1057
|
+
try {
|
|
1058
|
+
await client.execute(
|
|
1059
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1060
|
+
);
|
|
1061
|
+
} catch {
|
|
1062
|
+
}
|
|
1063
|
+
try {
|
|
1064
|
+
await client.execute({
|
|
1065
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1066
|
+
args: []
|
|
1067
|
+
});
|
|
1068
|
+
await client.execute({
|
|
1069
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1070
|
+
args: []
|
|
1071
|
+
});
|
|
1072
|
+
} catch {
|
|
1073
|
+
}
|
|
1074
|
+
try {
|
|
1075
|
+
await client.execute({
|
|
1076
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1077
|
+
args: []
|
|
1078
|
+
});
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
try {
|
|
1082
|
+
await client.execute(
|
|
1083
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1084
|
+
);
|
|
1085
|
+
} catch {
|
|
1086
|
+
}
|
|
1087
|
+
for (const col of [
|
|
1088
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1089
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1090
|
+
]) {
|
|
1091
|
+
try {
|
|
1092
|
+
await client.execute(col);
|
|
1093
|
+
} catch {
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
992
1096
|
}
|
|
993
1097
|
async function disposeDatabase() {
|
|
994
1098
|
if (_client) {
|
|
@@ -1096,6 +1200,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
1096
1200
|
const userSL = raw.sessionLifecycle ?? {};
|
|
1097
1201
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1098
1202
|
}
|
|
1203
|
+
function normalizeAutoUpdate(raw) {
|
|
1204
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1205
|
+
const userAU = raw.autoUpdate ?? {};
|
|
1206
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1207
|
+
}
|
|
1099
1208
|
async function loadConfig() {
|
|
1100
1209
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1101
1210
|
await mkdir(dir, { recursive: true });
|
|
@@ -1118,6 +1227,7 @@ async function loadConfig() {
|
|
|
1118
1227
|
}
|
|
1119
1228
|
normalizeScalingRoadmap(migratedCfg);
|
|
1120
1229
|
normalizeSessionLifecycle(migratedCfg);
|
|
1230
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1121
1231
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
1122
1232
|
if (config2.dbPath.startsWith("~")) {
|
|
1123
1233
|
config2.dbPath = config2.dbPath.replace(/^~/, os.homedir());
|
|
@@ -1140,6 +1250,7 @@ function loadConfigSync() {
|
|
|
1140
1250
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1141
1251
|
normalizeScalingRoadmap(migratedCfg);
|
|
1142
1252
|
normalizeSessionLifecycle(migratedCfg);
|
|
1253
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1143
1254
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
1144
1255
|
} catch {
|
|
1145
1256
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -1159,6 +1270,7 @@ async function loadConfigFrom(configPath) {
|
|
|
1159
1270
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1160
1271
|
normalizeScalingRoadmap(migratedCfg);
|
|
1161
1272
|
normalizeSessionLifecycle(migratedCfg);
|
|
1273
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1162
1274
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
1163
1275
|
} catch {
|
|
1164
1276
|
return { ...DEFAULT_CONFIG };
|
|
@@ -1230,6 +1342,11 @@ var init_config = __esm({
|
|
|
1230
1342
|
idleKillTicksRequired: 3,
|
|
1231
1343
|
idleKillIntercomAckWindowMs: 1e4,
|
|
1232
1344
|
maxAutoInstances: 10
|
|
1345
|
+
},
|
|
1346
|
+
autoUpdate: {
|
|
1347
|
+
checkOnBoot: true,
|
|
1348
|
+
autoInstall: false,
|
|
1349
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1233
1350
|
}
|
|
1234
1351
|
};
|
|
1235
1352
|
CONFIG_MIGRATIONS = [
|
|
@@ -1816,13 +1933,27 @@ async function ensureShardSchema(client) {
|
|
|
1816
1933
|
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
1817
1934
|
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
1818
1935
|
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
1819
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
1936
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
1937
|
+
// Source provenance columns (must match database.ts)
|
|
1938
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1939
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1940
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1941
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1820
1942
|
]) {
|
|
1821
1943
|
try {
|
|
1822
1944
|
await client.execute(col);
|
|
1823
1945
|
} catch {
|
|
1824
1946
|
}
|
|
1825
1947
|
}
|
|
1948
|
+
for (const idx of [
|
|
1949
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
1950
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
1951
|
+
]) {
|
|
1952
|
+
try {
|
|
1953
|
+
await client.execute(idx);
|
|
1954
|
+
} catch {
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1826
1957
|
try {
|
|
1827
1958
|
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
1828
1959
|
} catch {
|
|
@@ -1931,8 +2062,11 @@ var store_exports = {};
|
|
|
1931
2062
|
__export(store_exports, {
|
|
1932
2063
|
attachDocumentMetadata: () => attachDocumentMetadata,
|
|
1933
2064
|
buildWikiScopeFilter: () => buildWikiScopeFilter,
|
|
2065
|
+
classifyTier: () => classifyTier,
|
|
1934
2066
|
disposeStore: () => disposeStore,
|
|
1935
2067
|
flushBatch: () => flushBatch,
|
|
2068
|
+
flushTier3: () => flushTier3,
|
|
2069
|
+
getMemoryCardinality: () => getMemoryCardinality,
|
|
1936
2070
|
initStore: () => initStore,
|
|
1937
2071
|
reserveVersions: () => reserveVersions,
|
|
1938
2072
|
searchMemories: () => searchMemories,
|
|
@@ -1978,6 +2112,11 @@ async function initStore(options) {
|
|
|
1978
2112
|
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
1979
2113
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
1980
2114
|
}
|
|
2115
|
+
function classifyTier(record) {
|
|
2116
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
2117
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
2118
|
+
return 3;
|
|
2119
|
+
}
|
|
1981
2120
|
async function writeMemory(record) {
|
|
1982
2121
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
1983
2122
|
throw new Error(
|
|
@@ -2005,7 +2144,11 @@ async function writeMemory(record) {
|
|
|
2005
2144
|
document_id: record.document_id ?? null,
|
|
2006
2145
|
user_id: record.user_id ?? null,
|
|
2007
2146
|
char_offset: record.char_offset ?? null,
|
|
2008
|
-
page_number: record.page_number ?? null
|
|
2147
|
+
page_number: record.page_number ?? null,
|
|
2148
|
+
source_path: record.source_path ?? null,
|
|
2149
|
+
source_type: record.source_type ?? null,
|
|
2150
|
+
tier: record.tier ?? classifyTier(record),
|
|
2151
|
+
supersedes_id: record.supersedes_id ?? null
|
|
2009
2152
|
};
|
|
2010
2153
|
_pendingRecords.push(dbRow);
|
|
2011
2154
|
if (_flushTimer === null) {
|
|
@@ -2037,20 +2180,26 @@ async function flushBatch() {
|
|
|
2037
2180
|
const userId = row.user_id ?? null;
|
|
2038
2181
|
const charOffset = row.char_offset ?? null;
|
|
2039
2182
|
const pageNumber = row.page_number ?? null;
|
|
2183
|
+
const sourcePath = row.source_path ?? null;
|
|
2184
|
+
const sourceType = row.source_type ?? null;
|
|
2185
|
+
const tier = row.tier ?? 3;
|
|
2186
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
2040
2187
|
return {
|
|
2041
2188
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
2042
2189
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
2043
2190
|
tool_name, project_name,
|
|
2044
2191
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2045
2192
|
confidence, last_accessed,
|
|
2046
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
2047
|
-
|
|
2193
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2194
|
+
source_path, source_type, tier, supersedes_id)
|
|
2195
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
2048
2196
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
2049
2197
|
tool_name, project_name,
|
|
2050
2198
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
2051
2199
|
confidence, last_accessed,
|
|
2052
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
2053
|
-
|
|
2200
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
2201
|
+
source_path, source_type, tier, supersedes_id)
|
|
2202
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2054
2203
|
args: hasVector ? [
|
|
2055
2204
|
row.id,
|
|
2056
2205
|
row.agent_id,
|
|
@@ -2072,7 +2221,11 @@ async function flushBatch() {
|
|
|
2072
2221
|
documentId,
|
|
2073
2222
|
userId,
|
|
2074
2223
|
charOffset,
|
|
2075
|
-
pageNumber
|
|
2224
|
+
pageNumber,
|
|
2225
|
+
sourcePath,
|
|
2226
|
+
sourceType,
|
|
2227
|
+
tier,
|
|
2228
|
+
supersedesId
|
|
2076
2229
|
] : [
|
|
2077
2230
|
row.id,
|
|
2078
2231
|
row.agent_id,
|
|
@@ -2093,7 +2246,11 @@ async function flushBatch() {
|
|
|
2093
2246
|
documentId,
|
|
2094
2247
|
userId,
|
|
2095
2248
|
charOffset,
|
|
2096
|
-
pageNumber
|
|
2249
|
+
pageNumber,
|
|
2250
|
+
sourcePath,
|
|
2251
|
+
sourceType,
|
|
2252
|
+
tier,
|
|
2253
|
+
supersedesId
|
|
2097
2254
|
]
|
|
2098
2255
|
};
|
|
2099
2256
|
};
|
|
@@ -2167,7 +2324,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
2167
2324
|
has_error, raw_text, vector, importance, status,
|
|
2168
2325
|
confidence, last_accessed,
|
|
2169
2326
|
workspace_id, document_id, user_id,
|
|
2170
|
-
char_offset, page_number
|
|
2327
|
+
char_offset, page_number,
|
|
2328
|
+
source_path, source_type
|
|
2171
2329
|
FROM memories
|
|
2172
2330
|
WHERE agent_id = ?
|
|
2173
2331
|
AND vector IS NOT NULL${statusFilter}
|
|
@@ -2216,7 +2374,9 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
2216
2374
|
document_id: row.document_id ?? null,
|
|
2217
2375
|
user_id: row.user_id ?? null,
|
|
2218
2376
|
char_offset: row.char_offset ?? null,
|
|
2219
|
-
page_number: row.page_number ?? null
|
|
2377
|
+
page_number: row.page_number ?? null,
|
|
2378
|
+
source_path: row.source_path ?? null,
|
|
2379
|
+
source_type: row.source_type ?? null
|
|
2220
2380
|
}));
|
|
2221
2381
|
}
|
|
2222
2382
|
async function attachDocumentMetadata(records) {
|
|
@@ -2254,6 +2414,25 @@ async function attachDocumentMetadata(records) {
|
|
|
2254
2414
|
}
|
|
2255
2415
|
return records;
|
|
2256
2416
|
}
|
|
2417
|
+
async function flushTier3(agentId, options) {
|
|
2418
|
+
const client = getClient();
|
|
2419
|
+
const maxAge = options?.maxAgeHours ?? 72;
|
|
2420
|
+
const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
|
|
2421
|
+
if (options?.dryRun) {
|
|
2422
|
+
const result2 = await client.execute({
|
|
2423
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
2424
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
2425
|
+
args: [agentId, cutoff]
|
|
2426
|
+
});
|
|
2427
|
+
return { archived: Number(result2.rows[0]?.cnt ?? 0) };
|
|
2428
|
+
}
|
|
2429
|
+
const result = await client.execute({
|
|
2430
|
+
sql: `UPDATE memories SET status = 'archived'
|
|
2431
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
2432
|
+
args: [agentId, cutoff]
|
|
2433
|
+
});
|
|
2434
|
+
return { archived: result.rowsAffected };
|
|
2435
|
+
}
|
|
2257
2436
|
async function disposeStore() {
|
|
2258
2437
|
if (_flushTimer !== null) {
|
|
2259
2438
|
clearInterval(_flushTimer);
|
|
@@ -2284,6 +2463,18 @@ function reserveVersions(count) {
|
|
|
2284
2463
|
}
|
|
2285
2464
|
return reserved;
|
|
2286
2465
|
}
|
|
2466
|
+
async function getMemoryCardinality(agentId) {
|
|
2467
|
+
try {
|
|
2468
|
+
const client = getClient();
|
|
2469
|
+
const result = await client.execute({
|
|
2470
|
+
sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
|
|
2471
|
+
args: [agentId]
|
|
2472
|
+
});
|
|
2473
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
2474
|
+
} catch {
|
|
2475
|
+
return 0;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2287
2478
|
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
2288
2479
|
var init_store = __esm({
|
|
2289
2480
|
"src/lib/store.ts"() {
|
|
@@ -4307,11 +4498,12 @@ function queueIntercom(targetSession, reason) {
|
|
|
4307
4498
|
}
|
|
4308
4499
|
writeQueue(queue);
|
|
4309
4500
|
}
|
|
4310
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
4501
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
4311
4502
|
var init_intercom_queue = __esm({
|
|
4312
4503
|
"src/lib/intercom-queue.ts"() {
|
|
4313
4504
|
"use strict";
|
|
4314
4505
|
QUEUE_PATH = path8.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
4506
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
4315
4507
|
INTERCOM_LOG = path8.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
4316
4508
|
}
|
|
4317
4509
|
});
|
|
@@ -4425,6 +4617,17 @@ function getGitRoot(dir) {
|
|
|
4425
4617
|
return null;
|
|
4426
4618
|
}
|
|
4427
4619
|
}
|
|
4620
|
+
function getMainRepoRoot(dir) {
|
|
4621
|
+
try {
|
|
4622
|
+
const commonDir = execSync4(
|
|
4623
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
4624
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
4625
|
+
).trim();
|
|
4626
|
+
return realpath(path11.dirname(commonDir));
|
|
4627
|
+
} catch {
|
|
4628
|
+
return null;
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4428
4631
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
4429
4632
|
const label = instanceLabel(employeeName, instance);
|
|
4430
4633
|
return path11.join(repoRoot, ".worktrees", label);
|
|
@@ -4661,6 +4864,11 @@ function getSessionState(sessionName) {
|
|
|
4661
4864
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
4662
4865
|
try {
|
|
4663
4866
|
const pane = transport.capturePane(sessionName, 5);
|
|
4867
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
4868
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
4869
|
+
return "no_claude";
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4664
4872
|
if (/Running…/.test(pane)) return "tool";
|
|
4665
4873
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
4666
4874
|
return "idle";
|
|
@@ -4668,10 +4876,6 @@ function getSessionState(sessionName) {
|
|
|
4668
4876
|
return "offline";
|
|
4669
4877
|
}
|
|
4670
4878
|
}
|
|
4671
|
-
function isSessionBusy(sessionName) {
|
|
4672
|
-
const state = getSessionState(sessionName);
|
|
4673
|
-
return state === "thinking" || state === "tool";
|
|
4674
|
-
}
|
|
4675
4879
|
function isExeSession(sessionName) {
|
|
4676
4880
|
return /^exe\d*$/.test(sessionName);
|
|
4677
4881
|
}
|
|
@@ -4691,7 +4895,14 @@ function sendIntercom(targetSession) {
|
|
|
4691
4895
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
4692
4896
|
return "failed";
|
|
4693
4897
|
}
|
|
4694
|
-
|
|
4898
|
+
const sessionState = getSessionState(targetSession);
|
|
4899
|
+
if (sessionState === "no_claude") {
|
|
4900
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
4901
|
+
recordDebounce(targetSession);
|
|
4902
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
4903
|
+
return "queued";
|
|
4904
|
+
}
|
|
4905
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
4695
4906
|
queueIntercom(targetSession, "session busy at send time");
|
|
4696
4907
|
recordDebounce(targetSession);
|
|
4697
4908
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -4703,18 +4914,7 @@ function sendIntercom(targetSession) {
|
|
|
4703
4914
|
}
|
|
4704
4915
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
4705
4916
|
recordDebounce(targetSession);
|
|
4706
|
-
|
|
4707
|
-
try {
|
|
4708
|
-
execSync5(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
4709
|
-
} catch {
|
|
4710
|
-
}
|
|
4711
|
-
const state = getSessionState(targetSession);
|
|
4712
|
-
if (state === "thinking" || state === "tool") {
|
|
4713
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
4714
|
-
return "acknowledged";
|
|
4715
|
-
}
|
|
4716
|
-
}
|
|
4717
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
4917
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4718
4918
|
return "delivered";
|
|
4719
4919
|
} catch {
|
|
4720
4920
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -4731,7 +4931,17 @@ function notifyParentExe(sessionKey) {
|
|
|
4731
4931
|
process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
|
|
4732
4932
|
`);
|
|
4733
4933
|
const result = sendIntercom(target);
|
|
4734
|
-
|
|
4934
|
+
if (result === "failed") {
|
|
4935
|
+
const rootExe = resolveExeSession();
|
|
4936
|
+
if (rootExe && rootExe !== target) {
|
|
4937
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
4938
|
+
`);
|
|
4939
|
+
const fallback = sendIntercom(rootExe);
|
|
4940
|
+
return fallback !== "failed";
|
|
4941
|
+
}
|
|
4942
|
+
return false;
|
|
4943
|
+
}
|
|
4944
|
+
return true;
|
|
4735
4945
|
}
|
|
4736
4946
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4737
4947
|
if (employeeName === "exe") {
|
|
@@ -4780,7 +4990,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4780
4990
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
4781
4991
|
}
|
|
4782
4992
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
4783
|
-
const
|
|
4993
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
4994
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
4784
4995
|
if (wtPath) {
|
|
4785
4996
|
spawnOpts.cwd = wtPath;
|
|
4786
4997
|
}
|
|
@@ -4961,7 +5172,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4961
5172
|
let booted = false;
|
|
4962
5173
|
for (let i = 0; i < 30; i++) {
|
|
4963
5174
|
try {
|
|
4964
|
-
execSync5("sleep
|
|
5175
|
+
execSync5("sleep 0.5");
|
|
4965
5176
|
} catch {
|
|
4966
5177
|
}
|
|
4967
5178
|
try {
|
|
@@ -4981,7 +5192,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4981
5192
|
}
|
|
4982
5193
|
}
|
|
4983
5194
|
if (!booted) {
|
|
4984
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
5195
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4985
5196
|
}
|
|
4986
5197
|
if (!useExeAgent) {
|
|
4987
5198
|
try {
|
|
@@ -4999,7 +5210,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4999
5210
|
});
|
|
5000
5211
|
return { sessionName };
|
|
5001
5212
|
}
|
|
5002
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
5213
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
5003
5214
|
var init_tmux_routing = __esm({
|
|
5004
5215
|
"src/lib/tmux-routing.ts"() {
|
|
5005
5216
|
"use strict";
|
|
@@ -5019,8 +5230,6 @@ var init_tmux_routing = __esm({
|
|
|
5019
5230
|
DEBOUNCE_FILE = path12.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5020
5231
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5021
5232
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
5022
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
5023
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
5024
5233
|
}
|
|
5025
5234
|
});
|
|
5026
5235
|
|
|
@@ -5332,6 +5541,36 @@ import path14 from "path";
|
|
|
5332
5541
|
import { execSync as execSync6 } from "child_process";
|
|
5333
5542
|
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
5334
5543
|
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
|
|
5544
|
+
async function writeCheckpoint(input) {
|
|
5545
|
+
const client = getClient();
|
|
5546
|
+
const row = await resolveTask(client, input.taskId);
|
|
5547
|
+
const taskId = String(row.id);
|
|
5548
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5549
|
+
const blockedByIds = [];
|
|
5550
|
+
if (row.blocked_by) {
|
|
5551
|
+
blockedByIds.push(String(row.blocked_by));
|
|
5552
|
+
}
|
|
5553
|
+
const checkpoint = {
|
|
5554
|
+
step: input.step,
|
|
5555
|
+
context_summary: input.contextSummary,
|
|
5556
|
+
files_touched: input.filesTouched ?? [],
|
|
5557
|
+
blocked_by_ids: blockedByIds,
|
|
5558
|
+
last_checkpoint_at: now
|
|
5559
|
+
};
|
|
5560
|
+
const result = await client.execute({
|
|
5561
|
+
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
5562
|
+
args: [JSON.stringify(checkpoint), now, taskId]
|
|
5563
|
+
});
|
|
5564
|
+
if (result.rowsAffected === 0) {
|
|
5565
|
+
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
5566
|
+
}
|
|
5567
|
+
const countResult = await client.execute({
|
|
5568
|
+
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
5569
|
+
args: [taskId]
|
|
5570
|
+
});
|
|
5571
|
+
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
5572
|
+
return { checkpointCount };
|
|
5573
|
+
}
|
|
5335
5574
|
function extractParentFromContext(contextBody) {
|
|
5336
5575
|
if (!contextBody) return null;
|
|
5337
5576
|
const match = contextBody.match(
|
|
@@ -5438,9 +5677,10 @@ async function createTaskCore(input) {
|
|
|
5438
5677
|
} catch {
|
|
5439
5678
|
}
|
|
5440
5679
|
}
|
|
5680
|
+
const complexity = input.complexity ?? "standard";
|
|
5441
5681
|
await client.execute({
|
|
5442
|
-
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)
|
|
5443
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5682
|
+
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)
|
|
5683
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5444
5684
|
args: [
|
|
5445
5685
|
id,
|
|
5446
5686
|
input.title,
|
|
@@ -5454,6 +5694,11 @@ async function createTaskCore(input) {
|
|
|
5454
5694
|
parentTaskId,
|
|
5455
5695
|
input.reviewer ?? null,
|
|
5456
5696
|
input.context,
|
|
5697
|
+
input.budgetTokens ?? null,
|
|
5698
|
+
input.budgetFallbackModel ?? null,
|
|
5699
|
+
0,
|
|
5700
|
+
null,
|
|
5701
|
+
complexity,
|
|
5457
5702
|
now,
|
|
5458
5703
|
now
|
|
5459
5704
|
]
|
|
@@ -5469,7 +5714,11 @@ async function createTaskCore(input) {
|
|
|
5469
5714
|
taskFile,
|
|
5470
5715
|
createdAt: now,
|
|
5471
5716
|
updatedAt: now,
|
|
5472
|
-
warning
|
|
5717
|
+
warning,
|
|
5718
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
5719
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
5720
|
+
tokensUsed: 0,
|
|
5721
|
+
tokensWarnedAt: null
|
|
5473
5722
|
};
|
|
5474
5723
|
}
|
|
5475
5724
|
async function listTasks(input) {
|
|
@@ -5509,7 +5758,12 @@ async function listTasks(input) {
|
|
|
5509
5758
|
status: String(r.status),
|
|
5510
5759
|
taskFile: String(r.task_file),
|
|
5511
5760
|
createdAt: String(r.created_at),
|
|
5512
|
-
updatedAt: String(r.updated_at)
|
|
5761
|
+
updatedAt: String(r.updated_at),
|
|
5762
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
5763
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
5764
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
5765
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
5766
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
5513
5767
|
}));
|
|
5514
5768
|
}
|
|
5515
5769
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
@@ -5517,8 +5771,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
5517
5771
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
5518
5772
|
try {
|
|
5519
5773
|
const since = new Date(taskCreatedAt).toISOString();
|
|
5774
|
+
const branch = execSync6(
|
|
5775
|
+
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
5776
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
5777
|
+
).trim();
|
|
5778
|
+
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
5520
5779
|
const commitCount = execSync6(
|
|
5521
|
-
`git log --oneline --since="${since}" 2>/dev/null | wc -l`,
|
|
5780
|
+
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
5522
5781
|
{ encoding: "utf8", timeout: 5e3 }
|
|
5523
5782
|
).trim();
|
|
5524
5783
|
const count = parseInt(commitCount, 10);
|
|
@@ -5577,6 +5836,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
5577
5836
|
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
5578
5837
|
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
5579
5838
|
}
|
|
5839
|
+
try {
|
|
5840
|
+
await writeCheckpoint({
|
|
5841
|
+
taskId,
|
|
5842
|
+
step: "claimed",
|
|
5843
|
+
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
5844
|
+
});
|
|
5845
|
+
} catch {
|
|
5846
|
+
}
|
|
5580
5847
|
return { row, taskFile, now, taskId };
|
|
5581
5848
|
}
|
|
5582
5849
|
if (input.result) {
|
|
@@ -5590,6 +5857,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
5590
5857
|
args: [input.status, now, taskId]
|
|
5591
5858
|
});
|
|
5592
5859
|
}
|
|
5860
|
+
try {
|
|
5861
|
+
await writeCheckpoint({
|
|
5862
|
+
taskId,
|
|
5863
|
+
step: `status_transition:${input.status}`,
|
|
5864
|
+
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
5865
|
+
});
|
|
5866
|
+
} catch {
|
|
5867
|
+
}
|
|
5593
5868
|
return { row, taskFile, now, taskId };
|
|
5594
5869
|
}
|
|
5595
5870
|
async function deleteTaskCore(taskId, _baseDir) {
|
|
@@ -5743,23 +6018,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
5743
6018
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
5744
6019
|
try {
|
|
5745
6020
|
const client = getClient();
|
|
5746
|
-
const
|
|
5747
|
-
const
|
|
5748
|
-
|
|
5749
|
-
if (parts.length >= 3 && parts[0] === "review") {
|
|
5750
|
-
const agent = parts[1];
|
|
5751
|
-
const slug = parts.slice(2).join("-");
|
|
5752
|
-
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
6021
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6022
|
+
const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
|
|
6023
|
+
if (parentId) {
|
|
5753
6024
|
const result = await client.execute({
|
|
5754
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE
|
|
5755
|
-
args: [
|
|
6025
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
|
|
6026
|
+
args: [now, parentId]
|
|
5756
6027
|
});
|
|
5757
6028
|
if (result.rowsAffected > 0) {
|
|
5758
6029
|
process.stderr.write(
|
|
5759
|
-
`[review-cleanup] Cascaded original task to done: ${
|
|
6030
|
+
`[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
|
|
5760
6031
|
`
|
|
5761
6032
|
);
|
|
5762
6033
|
}
|
|
6034
|
+
} else {
|
|
6035
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
6036
|
+
const reviewPrefix = fileName.replace(".md", "");
|
|
6037
|
+
const parts = reviewPrefix.split("-");
|
|
6038
|
+
if (parts.length >= 3 && parts[0] === "review") {
|
|
6039
|
+
const agent = parts[1];
|
|
6040
|
+
const slug = parts.slice(2).join("-");
|
|
6041
|
+
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
6042
|
+
const result = await client.execute({
|
|
6043
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
6044
|
+
args: [now, originalTaskFile]
|
|
6045
|
+
});
|
|
6046
|
+
if (result.rowsAffected > 0) {
|
|
6047
|
+
process.stderr.write(
|
|
6048
|
+
`[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
|
|
6049
|
+
`
|
|
6050
|
+
);
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
5763
6053
|
}
|
|
5764
6054
|
} catch (err) {
|
|
5765
6055
|
process.stderr.write(
|
|
@@ -5880,12 +6170,23 @@ function getProjectName(cwd) {
|
|
|
5880
6170
|
const dir = cwd ?? process.cwd();
|
|
5881
6171
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
5882
6172
|
try {
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
6173
|
+
let repoRoot;
|
|
6174
|
+
try {
|
|
6175
|
+
const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
|
|
6176
|
+
cwd: dir,
|
|
6177
|
+
encoding: "utf8",
|
|
6178
|
+
timeout: 2e3,
|
|
6179
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6180
|
+
}).trim();
|
|
6181
|
+
repoRoot = path17.dirname(gitCommonDir);
|
|
6182
|
+
} catch {
|
|
6183
|
+
repoRoot = execSync7("git rev-parse --show-toplevel", {
|
|
6184
|
+
cwd: dir,
|
|
6185
|
+
encoding: "utf8",
|
|
6186
|
+
timeout: 2e3,
|
|
6187
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6188
|
+
}).trim();
|
|
6189
|
+
}
|
|
5889
6190
|
_cached2 = path17.basename(repoRoot);
|
|
5890
6191
|
_cachedCwd = dir;
|
|
5891
6192
|
return _cached2;
|
|
@@ -5991,7 +6292,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
5991
6292
|
return { dispatched, session: sessionName, crossProject };
|
|
5992
6293
|
} else {
|
|
5993
6294
|
const projectDir = input.projectDir ?? process.cwd();
|
|
5994
|
-
const result = ensureEmployee(input.assignedTo, exeSession, projectDir
|
|
6295
|
+
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
6296
|
+
autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
|
|
6297
|
+
});
|
|
5995
6298
|
if (result.status === "failed") {
|
|
5996
6299
|
process.stderr.write(
|
|
5997
6300
|
`[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
|
|
@@ -6355,7 +6658,8 @@ __export(tasks_exports, {
|
|
|
6355
6658
|
resolveTask: () => resolveTask,
|
|
6356
6659
|
slugify: () => slugify,
|
|
6357
6660
|
updateTask: () => updateTask,
|
|
6358
|
-
updateTaskStatus: () => updateTaskStatus
|
|
6661
|
+
updateTaskStatus: () => updateTaskStatus,
|
|
6662
|
+
writeCheckpoint: () => writeCheckpoint
|
|
6359
6663
|
});
|
|
6360
6664
|
import path18 from "path";
|
|
6361
6665
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8, unlinkSync as unlinkSync4 } from "fs";
|
|
@@ -6397,10 +6701,11 @@ async function updateTask(input) {
|
|
|
6397
6701
|
try {
|
|
6398
6702
|
const client = getClient();
|
|
6399
6703
|
const taskTitle = String(row.title);
|
|
6704
|
+
const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
6400
6705
|
await client.execute({
|
|
6401
6706
|
sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
|
|
6402
|
-
WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
|
|
6403
|
-
args: [now, `%left
|
|
6707
|
+
WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
|
|
6708
|
+
args: [now, `%left '${escaped}' as in\\_progress%`]
|
|
6404
6709
|
});
|
|
6405
6710
|
} catch {
|
|
6406
6711
|
}
|
|
@@ -6458,6 +6763,10 @@ async function updateTask(input) {
|
|
|
6458
6763
|
taskFile,
|
|
6459
6764
|
createdAt: String(row.created_at),
|
|
6460
6765
|
updatedAt: now,
|
|
6766
|
+
budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
|
|
6767
|
+
budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
|
|
6768
|
+
tokensUsed: Number(row.tokens_used ?? 0),
|
|
6769
|
+
tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
|
|
6461
6770
|
nextTask
|
|
6462
6771
|
};
|
|
6463
6772
|
}
|
|
@@ -6615,7 +6924,92 @@ async function executeCreateTask(params) {
|
|
|
6615
6924
|
baseDir: process.cwd()
|
|
6616
6925
|
});
|
|
6617
6926
|
}
|
|
6618
|
-
async function
|
|
6927
|
+
async function executeUpdateWiki(params) {
|
|
6928
|
+
const apiUrl = process.env.EXE_WIKI_API_URL;
|
|
6929
|
+
const apiKey = process.env.EXE_WIKI_API_KEY;
|
|
6930
|
+
if (!apiUrl || !apiKey) {
|
|
6931
|
+
throw new Error("Wiki not configured: EXE_WIKI_API_URL / EXE_WIKI_API_KEY not set");
|
|
6932
|
+
}
|
|
6933
|
+
const workspace = params.workspace;
|
|
6934
|
+
const content = params.content ?? params.text;
|
|
6935
|
+
const mode = params.mode ?? "append";
|
|
6936
|
+
const section = params.section;
|
|
6937
|
+
if (!workspace || !content) {
|
|
6938
|
+
throw new Error("update_wiki requires 'workspace' and 'content' params");
|
|
6939
|
+
}
|
|
6940
|
+
const documentId = params.document_id;
|
|
6941
|
+
if (documentId && mode === "append") {
|
|
6942
|
+
const readRes = await fetch(`${apiUrl}/v1/document/${documentId}`, {
|
|
6943
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
6944
|
+
signal: AbortSignal.timeout(15e3)
|
|
6945
|
+
});
|
|
6946
|
+
if (!readRes.ok) {
|
|
6947
|
+
throw new Error(`Wiki read failed (${readRes.status})`);
|
|
6948
|
+
}
|
|
6949
|
+
const doc = await readRes.json();
|
|
6950
|
+
const existingContent = String(doc.content ?? "");
|
|
6951
|
+
const title = String(doc.title ?? "Untitled");
|
|
6952
|
+
await fetch(`${apiUrl}/v1/document/${documentId}`, {
|
|
6953
|
+
method: "DELETE",
|
|
6954
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
6955
|
+
signal: AbortSignal.timeout(15e3)
|
|
6956
|
+
}).catch(() => {
|
|
6957
|
+
});
|
|
6958
|
+
const uploadRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
6959
|
+
method: "POST",
|
|
6960
|
+
headers: {
|
|
6961
|
+
"Content-Type": "application/json",
|
|
6962
|
+
Authorization: `Bearer ${apiKey}`
|
|
6963
|
+
},
|
|
6964
|
+
body: JSON.stringify({
|
|
6965
|
+
textContent: section ? existingContent + `
|
|
6966
|
+
|
|
6967
|
+
## ${section}
|
|
6968
|
+
${content}` : existingContent + "\n\n" + content,
|
|
6969
|
+
metadata: { title },
|
|
6970
|
+
workspaceSlugs: [workspace]
|
|
6971
|
+
}),
|
|
6972
|
+
signal: AbortSignal.timeout(15e3)
|
|
6973
|
+
});
|
|
6974
|
+
if (!uploadRes.ok) throw new Error(`Wiki upload failed (${uploadRes.status})`);
|
|
6975
|
+
} else {
|
|
6976
|
+
const title = params.title ?? "Auto-generated";
|
|
6977
|
+
const res = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
6978
|
+
method: "POST",
|
|
6979
|
+
headers: {
|
|
6980
|
+
"Content-Type": "application/json",
|
|
6981
|
+
Authorization: `Bearer ${apiKey}`
|
|
6982
|
+
},
|
|
6983
|
+
body: JSON.stringify({
|
|
6984
|
+
textContent: content,
|
|
6985
|
+
metadata: { title },
|
|
6986
|
+
workspaceSlugs: [workspace]
|
|
6987
|
+
}),
|
|
6988
|
+
signal: AbortSignal.timeout(15e3)
|
|
6989
|
+
});
|
|
6990
|
+
if (!res.ok) throw new Error(`Wiki create failed (${res.status})`);
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
async function routeToApproval(action, resolvedParams, triggerName) {
|
|
6994
|
+
const { createTask: createTask2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
6995
|
+
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)}`;
|
|
6996
|
+
await createTask2({
|
|
6997
|
+
title: `[Approval Required] ${triggerName}: ${action.type}`,
|
|
6998
|
+
assignedTo: "exe",
|
|
6999
|
+
assignedBy: "trigger-engine",
|
|
7000
|
+
projectName: resolvedParams.project ?? "exe-os",
|
|
7001
|
+
priority: "p1",
|
|
7002
|
+
context: `Trigger "${triggerName}" wants to execute this action but requires approval.
|
|
7003
|
+
|
|
7004
|
+
**Action:** ${action.type}
|
|
7005
|
+
**Summary:** ${actionSummary}
|
|
7006
|
+
**Full params:** ${JSON.stringify(resolvedParams, null, 2)}
|
|
7007
|
+
|
|
7008
|
+
To approve, manually run the action via MCP tools.`,
|
|
7009
|
+
baseDir: process.cwd()
|
|
7010
|
+
});
|
|
7011
|
+
}
|
|
7012
|
+
async function executeAction(action, record, executor, triggerName) {
|
|
6619
7013
|
if (executor) {
|
|
6620
7014
|
return executor(action, record);
|
|
6621
7015
|
}
|
|
@@ -6623,6 +7017,17 @@ async function executeAction(action, record, executor) {
|
|
|
6623
7017
|
for (const [key, val] of Object.entries(action.params)) {
|
|
6624
7018
|
resolvedParams[key] = substituteTemplate(val, record);
|
|
6625
7019
|
}
|
|
7020
|
+
if (action.requires_approval) {
|
|
7021
|
+
try {
|
|
7022
|
+
await routeToApproval(action, resolvedParams, triggerName ?? "Unknown trigger");
|
|
7023
|
+
return { success: true };
|
|
7024
|
+
} catch (err) {
|
|
7025
|
+
return {
|
|
7026
|
+
success: false,
|
|
7027
|
+
error: `Approval routing failed: ${err instanceof Error ? err.message : String(err)}`
|
|
7028
|
+
};
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
6626
7031
|
try {
|
|
6627
7032
|
switch (action.type) {
|
|
6628
7033
|
case "send_whatsapp":
|
|
@@ -6634,6 +7039,9 @@ async function executeAction(action, record, executor) {
|
|
|
6634
7039
|
case "create_task":
|
|
6635
7040
|
await executeCreateTask(resolvedParams);
|
|
6636
7041
|
break;
|
|
7042
|
+
case "update_wiki":
|
|
7043
|
+
await executeUpdateWiki(resolvedParams);
|
|
7044
|
+
break;
|
|
6637
7045
|
case "mcp_tool":
|
|
6638
7046
|
console.log(
|
|
6639
7047
|
`[trigger-engine] mcp_tool action: ${JSON.stringify(resolvedParams)}`
|
|
@@ -6658,7 +7066,7 @@ async function processCRMEvent(event, executor, triggersOverride) {
|
|
|
6658
7066
|
if (!evaluateConditions(trigger.conditions, event.record)) continue;
|
|
6659
7067
|
const actionResults = [];
|
|
6660
7068
|
for (const action of trigger.actions) {
|
|
6661
|
-
const result = await executeAction(action, event.record, executor);
|
|
7069
|
+
const result = await executeAction(action, event.record, executor, trigger.name);
|
|
6662
7070
|
actionResults.push({
|
|
6663
7071
|
type: action.type,
|
|
6664
7072
|
success: result.success,
|