@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/index.js
CHANGED
|
@@ -349,11 +349,12 @@ function queueIntercom(targetSession, reason) {
|
|
|
349
349
|
}
|
|
350
350
|
writeQueue(queue);
|
|
351
351
|
}
|
|
352
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
352
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
353
353
|
var init_intercom_queue = __esm({
|
|
354
354
|
"src/lib/intercom-queue.ts"() {
|
|
355
355
|
"use strict";
|
|
356
356
|
QUEUE_PATH = path3.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
357
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
357
358
|
INTERCOM_LOG = path3.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
358
359
|
}
|
|
359
360
|
});
|
|
@@ -579,6 +580,27 @@ async function ensureSchema() {
|
|
|
579
580
|
});
|
|
580
581
|
} catch {
|
|
581
582
|
}
|
|
583
|
+
try {
|
|
584
|
+
await client.execute({
|
|
585
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
586
|
+
args: []
|
|
587
|
+
});
|
|
588
|
+
} catch {
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
await client.execute({
|
|
592
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
593
|
+
args: []
|
|
594
|
+
});
|
|
595
|
+
} catch {
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
await client.execute({
|
|
599
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
600
|
+
args: []
|
|
601
|
+
});
|
|
602
|
+
} catch {
|
|
603
|
+
}
|
|
582
604
|
try {
|
|
583
605
|
await client.execute({
|
|
584
606
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -989,6 +1011,15 @@ async function ensureSchema() {
|
|
|
989
1011
|
} catch {
|
|
990
1012
|
}
|
|
991
1013
|
}
|
|
1014
|
+
for (const col of [
|
|
1015
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1016
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
1017
|
+
]) {
|
|
1018
|
+
try {
|
|
1019
|
+
await client.execute(col);
|
|
1020
|
+
} catch {
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
992
1023
|
await client.executeMultiple(`
|
|
993
1024
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
994
1025
|
ON memories(workspace_id);
|
|
@@ -1053,6 +1084,34 @@ async function ensureSchema() {
|
|
|
1053
1084
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1054
1085
|
ON conversations(channel_id);
|
|
1055
1086
|
`);
|
|
1087
|
+
try {
|
|
1088
|
+
await client.execute({
|
|
1089
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
1090
|
+
args: []
|
|
1091
|
+
});
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
await client.execute({
|
|
1096
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
1097
|
+
args: []
|
|
1098
|
+
});
|
|
1099
|
+
} catch {
|
|
1100
|
+
}
|
|
1101
|
+
try {
|
|
1102
|
+
await client.execute({
|
|
1103
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
1104
|
+
args: []
|
|
1105
|
+
});
|
|
1106
|
+
} catch {
|
|
1107
|
+
}
|
|
1108
|
+
try {
|
|
1109
|
+
await client.execute({
|
|
1110
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
1111
|
+
args: []
|
|
1112
|
+
});
|
|
1113
|
+
} catch {
|
|
1114
|
+
}
|
|
1056
1115
|
await client.executeMultiple(`
|
|
1057
1116
|
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
1058
1117
|
content_text,
|
|
@@ -1079,6 +1138,52 @@ async function ensureSchema() {
|
|
|
1079
1138
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
1080
1139
|
END;
|
|
1081
1140
|
`);
|
|
1141
|
+
try {
|
|
1142
|
+
await client.execute({
|
|
1143
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1144
|
+
args: []
|
|
1145
|
+
});
|
|
1146
|
+
} catch {
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
await client.execute(
|
|
1150
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1151
|
+
);
|
|
1152
|
+
} catch {
|
|
1153
|
+
}
|
|
1154
|
+
try {
|
|
1155
|
+
await client.execute({
|
|
1156
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1157
|
+
args: []
|
|
1158
|
+
});
|
|
1159
|
+
await client.execute({
|
|
1160
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1161
|
+
args: []
|
|
1162
|
+
});
|
|
1163
|
+
} catch {
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
await client.execute({
|
|
1167
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1168
|
+
args: []
|
|
1169
|
+
});
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
try {
|
|
1173
|
+
await client.execute(
|
|
1174
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1175
|
+
);
|
|
1176
|
+
} catch {
|
|
1177
|
+
}
|
|
1178
|
+
for (const col of [
|
|
1179
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1180
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1181
|
+
]) {
|
|
1182
|
+
try {
|
|
1183
|
+
await client.execute(col);
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1082
1187
|
}
|
|
1083
1188
|
async function disposeDatabase() {
|
|
1084
1189
|
if (_client) {
|
|
@@ -1177,6 +1282,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
1177
1282
|
const userSL = raw.sessionLifecycle ?? {};
|
|
1178
1283
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1179
1284
|
}
|
|
1285
|
+
function normalizeAutoUpdate(raw) {
|
|
1286
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1287
|
+
const userAU = raw.autoUpdate ?? {};
|
|
1288
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1289
|
+
}
|
|
1180
1290
|
async function loadConfig() {
|
|
1181
1291
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1182
1292
|
await mkdir(dir, { recursive: true });
|
|
@@ -1199,6 +1309,7 @@ async function loadConfig() {
|
|
|
1199
1309
|
}
|
|
1200
1310
|
normalizeScalingRoadmap(migratedCfg);
|
|
1201
1311
|
normalizeSessionLifecycle(migratedCfg);
|
|
1312
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1202
1313
|
const config2 = { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
|
|
1203
1314
|
if (config2.dbPath.startsWith("~")) {
|
|
1204
1315
|
config2.dbPath = config2.dbPath.replace(/^~/, os4.homedir());
|
|
@@ -1221,6 +1332,7 @@ function loadConfigSync() {
|
|
|
1221
1332
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1222
1333
|
normalizeScalingRoadmap(migratedCfg);
|
|
1223
1334
|
normalizeSessionLifecycle(migratedCfg);
|
|
1335
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1224
1336
|
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db"), ...migratedCfg };
|
|
1225
1337
|
} catch {
|
|
1226
1338
|
return { ...DEFAULT_CONFIG, dbPath: path4.join(dir, "memories.db") };
|
|
@@ -1240,6 +1352,7 @@ async function loadConfigFrom(configPath) {
|
|
|
1240
1352
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1241
1353
|
normalizeScalingRoadmap(migratedCfg);
|
|
1242
1354
|
normalizeSessionLifecycle(migratedCfg);
|
|
1355
|
+
normalizeAutoUpdate(migratedCfg);
|
|
1243
1356
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
1244
1357
|
} catch {
|
|
1245
1358
|
return { ...DEFAULT_CONFIG };
|
|
@@ -1311,6 +1424,11 @@ var init_config = __esm({
|
|
|
1311
1424
|
idleKillTicksRequired: 3,
|
|
1312
1425
|
idleKillIntercomAckWindowMs: 1e4,
|
|
1313
1426
|
maxAutoInstances: 10
|
|
1427
|
+
},
|
|
1428
|
+
autoUpdate: {
|
|
1429
|
+
checkOnBoot: true,
|
|
1430
|
+
autoInstall: false,
|
|
1431
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1314
1432
|
}
|
|
1315
1433
|
};
|
|
1316
1434
|
CONFIG_MIGRATIONS = [
|
|
@@ -1458,6 +1576,17 @@ function getGitRoot(dir) {
|
|
|
1458
1576
|
return null;
|
|
1459
1577
|
}
|
|
1460
1578
|
}
|
|
1579
|
+
function getMainRepoRoot(dir) {
|
|
1580
|
+
try {
|
|
1581
|
+
const commonDir = execSync5(
|
|
1582
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
1583
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
1584
|
+
).trim();
|
|
1585
|
+
return realpath(path8.dirname(commonDir));
|
|
1586
|
+
} catch {
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1461
1590
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
1462
1591
|
const label = instanceLabel(employeeName, instance);
|
|
1463
1592
|
return path8.join(repoRoot, ".worktrees", label);
|
|
@@ -1649,6 +1778,36 @@ import path10 from "path";
|
|
|
1649
1778
|
import { execSync as execSync6 } from "child_process";
|
|
1650
1779
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1651
1780
|
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
1781
|
+
async function writeCheckpoint(input) {
|
|
1782
|
+
const client = getClient();
|
|
1783
|
+
const row = await resolveTask(client, input.taskId);
|
|
1784
|
+
const taskId = String(row.id);
|
|
1785
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1786
|
+
const blockedByIds = [];
|
|
1787
|
+
if (row.blocked_by) {
|
|
1788
|
+
blockedByIds.push(String(row.blocked_by));
|
|
1789
|
+
}
|
|
1790
|
+
const checkpoint = {
|
|
1791
|
+
step: input.step,
|
|
1792
|
+
context_summary: input.contextSummary,
|
|
1793
|
+
files_touched: input.filesTouched ?? [],
|
|
1794
|
+
blocked_by_ids: blockedByIds,
|
|
1795
|
+
last_checkpoint_at: now
|
|
1796
|
+
};
|
|
1797
|
+
const result = await client.execute({
|
|
1798
|
+
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
1799
|
+
args: [JSON.stringify(checkpoint), now, taskId]
|
|
1800
|
+
});
|
|
1801
|
+
if (result.rowsAffected === 0) {
|
|
1802
|
+
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
1803
|
+
}
|
|
1804
|
+
const countResult = await client.execute({
|
|
1805
|
+
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
1806
|
+
args: [taskId]
|
|
1807
|
+
});
|
|
1808
|
+
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
1809
|
+
return { checkpointCount };
|
|
1810
|
+
}
|
|
1652
1811
|
function extractParentFromContext(contextBody) {
|
|
1653
1812
|
if (!contextBody) return null;
|
|
1654
1813
|
const match = contextBody.match(
|
|
@@ -1755,9 +1914,10 @@ async function createTaskCore(input) {
|
|
|
1755
1914
|
} catch {
|
|
1756
1915
|
}
|
|
1757
1916
|
}
|
|
1917
|
+
const complexity = input.complexity ?? "standard";
|
|
1758
1918
|
await client.execute({
|
|
1759
|
-
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)
|
|
1760
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1919
|
+
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)
|
|
1920
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1761
1921
|
args: [
|
|
1762
1922
|
id,
|
|
1763
1923
|
input.title,
|
|
@@ -1771,6 +1931,11 @@ async function createTaskCore(input) {
|
|
|
1771
1931
|
parentTaskId,
|
|
1772
1932
|
input.reviewer ?? null,
|
|
1773
1933
|
input.context,
|
|
1934
|
+
input.budgetTokens ?? null,
|
|
1935
|
+
input.budgetFallbackModel ?? null,
|
|
1936
|
+
0,
|
|
1937
|
+
null,
|
|
1938
|
+
complexity,
|
|
1774
1939
|
now,
|
|
1775
1940
|
now
|
|
1776
1941
|
]
|
|
@@ -1786,7 +1951,11 @@ async function createTaskCore(input) {
|
|
|
1786
1951
|
taskFile,
|
|
1787
1952
|
createdAt: now,
|
|
1788
1953
|
updatedAt: now,
|
|
1789
|
-
warning
|
|
1954
|
+
warning,
|
|
1955
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
1956
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
1957
|
+
tokensUsed: 0,
|
|
1958
|
+
tokensWarnedAt: null
|
|
1790
1959
|
};
|
|
1791
1960
|
}
|
|
1792
1961
|
async function listTasks(input) {
|
|
@@ -1826,7 +1995,12 @@ async function listTasks(input) {
|
|
|
1826
1995
|
status: String(r.status),
|
|
1827
1996
|
taskFile: String(r.task_file),
|
|
1828
1997
|
createdAt: String(r.created_at),
|
|
1829
|
-
updatedAt: String(r.updated_at)
|
|
1998
|
+
updatedAt: String(r.updated_at),
|
|
1999
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
2000
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
2001
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
2002
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
2003
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
1830
2004
|
}));
|
|
1831
2005
|
}
|
|
1832
2006
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
@@ -1834,8 +2008,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
1834
2008
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
1835
2009
|
try {
|
|
1836
2010
|
const since = new Date(taskCreatedAt).toISOString();
|
|
2011
|
+
const branch = execSync6(
|
|
2012
|
+
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
2013
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
2014
|
+
).trim();
|
|
2015
|
+
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
1837
2016
|
const commitCount = execSync6(
|
|
1838
|
-
`git log --oneline --since="${since}" 2>/dev/null | wc -l`,
|
|
2017
|
+
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
1839
2018
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1840
2019
|
).trim();
|
|
1841
2020
|
const count = parseInt(commitCount, 10);
|
|
@@ -1894,6 +2073,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1894
2073
|
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
1895
2074
|
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
1896
2075
|
}
|
|
2076
|
+
try {
|
|
2077
|
+
await writeCheckpoint({
|
|
2078
|
+
taskId,
|
|
2079
|
+
step: "claimed",
|
|
2080
|
+
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
2081
|
+
});
|
|
2082
|
+
} catch {
|
|
2083
|
+
}
|
|
1897
2084
|
return { row, taskFile, now, taskId };
|
|
1898
2085
|
}
|
|
1899
2086
|
if (input.result) {
|
|
@@ -1907,6 +2094,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1907
2094
|
args: [input.status, now, taskId]
|
|
1908
2095
|
});
|
|
1909
2096
|
}
|
|
2097
|
+
try {
|
|
2098
|
+
await writeCheckpoint({
|
|
2099
|
+
taskId,
|
|
2100
|
+
step: `status_transition:${input.status}`,
|
|
2101
|
+
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
2102
|
+
});
|
|
2103
|
+
} catch {
|
|
2104
|
+
}
|
|
1910
2105
|
return { row, taskFile, now, taskId };
|
|
1911
2106
|
}
|
|
1912
2107
|
async function deleteTaskCore(taskId, _baseDir) {
|
|
@@ -2060,23 +2255,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2060
2255
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
2061
2256
|
try {
|
|
2062
2257
|
const client = getClient();
|
|
2063
|
-
const
|
|
2064
|
-
const
|
|
2065
|
-
|
|
2066
|
-
if (parts.length >= 3 && parts[0] === "review") {
|
|
2067
|
-
const agent = parts[1];
|
|
2068
|
-
const slug = parts.slice(2).join("-");
|
|
2069
|
-
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
2258
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2259
|
+
const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
|
|
2260
|
+
if (parentId) {
|
|
2070
2261
|
const result = await client.execute({
|
|
2071
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE
|
|
2072
|
-
args: [
|
|
2262
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
|
|
2263
|
+
args: [now, parentId]
|
|
2073
2264
|
});
|
|
2074
2265
|
if (result.rowsAffected > 0) {
|
|
2075
2266
|
process.stderr.write(
|
|
2076
|
-
`[review-cleanup] Cascaded original task to done: ${
|
|
2267
|
+
`[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
|
|
2077
2268
|
`
|
|
2078
2269
|
);
|
|
2079
2270
|
}
|
|
2271
|
+
} else {
|
|
2272
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
2273
|
+
const reviewPrefix = fileName.replace(".md", "");
|
|
2274
|
+
const parts = reviewPrefix.split("-");
|
|
2275
|
+
if (parts.length >= 3 && parts[0] === "review") {
|
|
2276
|
+
const agent = parts[1];
|
|
2277
|
+
const slug = parts.slice(2).join("-");
|
|
2278
|
+
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
2279
|
+
const result = await client.execute({
|
|
2280
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2281
|
+
args: [now, originalTaskFile]
|
|
2282
|
+
});
|
|
2283
|
+
if (result.rowsAffected > 0) {
|
|
2284
|
+
process.stderr.write(
|
|
2285
|
+
`[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
|
|
2286
|
+
`
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2080
2290
|
}
|
|
2081
2291
|
} catch (err) {
|
|
2082
2292
|
process.stderr.write(
|
|
@@ -2197,12 +2407,23 @@ function getProjectName(cwd) {
|
|
|
2197
2407
|
const dir = cwd ?? process.cwd();
|
|
2198
2408
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
2199
2409
|
try {
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2410
|
+
let repoRoot;
|
|
2411
|
+
try {
|
|
2412
|
+
const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
|
|
2413
|
+
cwd: dir,
|
|
2414
|
+
encoding: "utf8",
|
|
2415
|
+
timeout: 2e3,
|
|
2416
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2417
|
+
}).trim();
|
|
2418
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
2419
|
+
} catch {
|
|
2420
|
+
repoRoot = execSync7("git rev-parse --show-toplevel", {
|
|
2421
|
+
cwd: dir,
|
|
2422
|
+
encoding: "utf8",
|
|
2423
|
+
timeout: 2e3,
|
|
2424
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2425
|
+
}).trim();
|
|
2426
|
+
}
|
|
2206
2427
|
_cached2 = path13.basename(repoRoot);
|
|
2207
2428
|
_cachedCwd = dir;
|
|
2208
2429
|
return _cached2;
|
|
@@ -2308,7 +2529,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
2308
2529
|
return { dispatched, session: sessionName, crossProject };
|
|
2309
2530
|
} else {
|
|
2310
2531
|
const projectDir = input.projectDir ?? process.cwd();
|
|
2311
|
-
const result = ensureEmployee(input.assignedTo, exeSession, projectDir
|
|
2532
|
+
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
2533
|
+
autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
|
|
2534
|
+
});
|
|
2312
2535
|
if (result.status === "failed") {
|
|
2313
2536
|
process.stderr.write(
|
|
2314
2537
|
`[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
|
|
@@ -2734,7 +2957,8 @@ __export(tasks_exports, {
|
|
|
2734
2957
|
resolveTask: () => resolveTask,
|
|
2735
2958
|
slugify: () => slugify,
|
|
2736
2959
|
updateTask: () => updateTask,
|
|
2737
|
-
updateTaskStatus: () => updateTaskStatus
|
|
2960
|
+
updateTaskStatus: () => updateTaskStatus,
|
|
2961
|
+
writeCheckpoint: () => writeCheckpoint
|
|
2738
2962
|
});
|
|
2739
2963
|
import path14 from "path";
|
|
2740
2964
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -2776,10 +3000,11 @@ async function updateTask(input) {
|
|
|
2776
3000
|
try {
|
|
2777
3001
|
const client = getClient();
|
|
2778
3002
|
const taskTitle = String(row.title);
|
|
3003
|
+
const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
2779
3004
|
await client.execute({
|
|
2780
3005
|
sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
|
|
2781
|
-
WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
|
|
2782
|
-
args: [now, `%left
|
|
3006
|
+
WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
|
|
3007
|
+
args: [now, `%left '${escaped}' as in\\_progress%`]
|
|
2783
3008
|
});
|
|
2784
3009
|
} catch {
|
|
2785
3010
|
}
|
|
@@ -2837,6 +3062,10 @@ async function updateTask(input) {
|
|
|
2837
3062
|
taskFile,
|
|
2838
3063
|
createdAt: String(row.created_at),
|
|
2839
3064
|
updatedAt: now,
|
|
3065
|
+
budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
|
|
3066
|
+
budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
|
|
3067
|
+
tokensUsed: Number(row.tokens_used ?? 0),
|
|
3068
|
+
tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
|
|
2840
3069
|
nextTask
|
|
2841
3070
|
};
|
|
2842
3071
|
}
|
|
@@ -3350,6 +3579,11 @@ function getSessionState(sessionName) {
|
|
|
3350
3579
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
3351
3580
|
try {
|
|
3352
3581
|
const pane = transport.capturePane(sessionName, 5);
|
|
3582
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
3583
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
3584
|
+
return "no_claude";
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3353
3587
|
if (/Running…/.test(pane)) return "tool";
|
|
3354
3588
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
3355
3589
|
return "idle";
|
|
@@ -3380,7 +3614,14 @@ function sendIntercom(targetSession) {
|
|
|
3380
3614
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
3381
3615
|
return "failed";
|
|
3382
3616
|
}
|
|
3383
|
-
|
|
3617
|
+
const sessionState = getSessionState(targetSession);
|
|
3618
|
+
if (sessionState === "no_claude") {
|
|
3619
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
3620
|
+
recordDebounce(targetSession);
|
|
3621
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
3622
|
+
return "queued";
|
|
3623
|
+
}
|
|
3624
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
3384
3625
|
queueIntercom(targetSession, "session busy at send time");
|
|
3385
3626
|
recordDebounce(targetSession);
|
|
3386
3627
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -3392,18 +3633,7 @@ function sendIntercom(targetSession) {
|
|
|
3392
3633
|
}
|
|
3393
3634
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3394
3635
|
recordDebounce(targetSession);
|
|
3395
|
-
|
|
3396
|
-
try {
|
|
3397
|
-
execSync8(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
3398
|
-
} catch {
|
|
3399
|
-
}
|
|
3400
|
-
const state = getSessionState(targetSession);
|
|
3401
|
-
if (state === "thinking" || state === "tool") {
|
|
3402
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
3403
|
-
return "acknowledged";
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
3636
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
3407
3637
|
return "delivered";
|
|
3408
3638
|
} catch {
|
|
3409
3639
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3420,7 +3650,17 @@ function notifyParentExe(sessionKey) {
|
|
|
3420
3650
|
process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
|
|
3421
3651
|
`);
|
|
3422
3652
|
const result = sendIntercom(target);
|
|
3423
|
-
|
|
3653
|
+
if (result === "failed") {
|
|
3654
|
+
const rootExe = resolveExeSession();
|
|
3655
|
+
if (rootExe && rootExe !== target) {
|
|
3656
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
3657
|
+
`);
|
|
3658
|
+
const fallback = sendIntercom(rootExe);
|
|
3659
|
+
return fallback !== "failed";
|
|
3660
|
+
}
|
|
3661
|
+
return false;
|
|
3662
|
+
}
|
|
3663
|
+
return true;
|
|
3424
3664
|
}
|
|
3425
3665
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3426
3666
|
if (employeeName === "exe") {
|
|
@@ -3469,7 +3709,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3469
3709
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
3470
3710
|
}
|
|
3471
3711
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
3472
|
-
const
|
|
3712
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
3713
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
3473
3714
|
if (wtPath) {
|
|
3474
3715
|
spawnOpts.cwd = wtPath;
|
|
3475
3716
|
}
|
|
@@ -3650,7 +3891,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3650
3891
|
let booted = false;
|
|
3651
3892
|
for (let i = 0; i < 30; i++) {
|
|
3652
3893
|
try {
|
|
3653
|
-
execSync8("sleep
|
|
3894
|
+
execSync8("sleep 0.5");
|
|
3654
3895
|
} catch {
|
|
3655
3896
|
}
|
|
3656
3897
|
try {
|
|
@@ -3670,7 +3911,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3670
3911
|
}
|
|
3671
3912
|
}
|
|
3672
3913
|
if (!booted) {
|
|
3673
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
3914
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
3674
3915
|
}
|
|
3675
3916
|
if (!useExeAgent) {
|
|
3676
3917
|
try {
|
|
@@ -3688,7 +3929,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3688
3929
|
});
|
|
3689
3930
|
return { sessionName };
|
|
3690
3931
|
}
|
|
3691
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
3932
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
3692
3933
|
var init_tmux_routing = __esm({
|
|
3693
3934
|
"src/lib/tmux-routing.ts"() {
|
|
3694
3935
|
"use strict";
|
|
@@ -3709,8 +3950,6 @@ var init_tmux_routing = __esm({
|
|
|
3709
3950
|
DEBOUNCE_FILE = path15.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3710
3951
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3711
3952
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
3712
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
3713
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
3714
3953
|
}
|
|
3715
3954
|
});
|
|
3716
3955
|
|
|
@@ -3890,13 +4129,27 @@ async function ensureShardSchema(client) {
|
|
|
3890
4129
|
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
3891
4130
|
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
3892
4131
|
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
3893
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
4132
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
4133
|
+
// Source provenance columns (must match database.ts)
|
|
4134
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
4135
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
4136
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
4137
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
3894
4138
|
]) {
|
|
3895
4139
|
try {
|
|
3896
4140
|
await client.execute(col);
|
|
3897
4141
|
} catch {
|
|
3898
4142
|
}
|
|
3899
4143
|
}
|
|
4144
|
+
for (const idx of [
|
|
4145
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
4146
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
4147
|
+
]) {
|
|
4148
|
+
try {
|
|
4149
|
+
await client.execute(idx);
|
|
4150
|
+
} catch {
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
3900
4153
|
try {
|
|
3901
4154
|
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
3902
4155
|
} catch {
|
|
@@ -4005,8 +4258,11 @@ var store_exports = {};
|
|
|
4005
4258
|
__export(store_exports, {
|
|
4006
4259
|
attachDocumentMetadata: () => attachDocumentMetadata,
|
|
4007
4260
|
buildWikiScopeFilter: () => buildWikiScopeFilter,
|
|
4261
|
+
classifyTier: () => classifyTier,
|
|
4008
4262
|
disposeStore: () => disposeStore,
|
|
4009
4263
|
flushBatch: () => flushBatch,
|
|
4264
|
+
flushTier3: () => flushTier3,
|
|
4265
|
+
getMemoryCardinality: () => getMemoryCardinality,
|
|
4010
4266
|
initStore: () => initStore,
|
|
4011
4267
|
reserveVersions: () => reserveVersions,
|
|
4012
4268
|
searchMemories: () => searchMemories,
|
|
@@ -4052,6 +4308,11 @@ async function initStore(options) {
|
|
|
4052
4308
|
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
4053
4309
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
4054
4310
|
}
|
|
4311
|
+
function classifyTier(record) {
|
|
4312
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
4313
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
4314
|
+
return 3;
|
|
4315
|
+
}
|
|
4055
4316
|
async function writeMemory(record) {
|
|
4056
4317
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
4057
4318
|
throw new Error(
|
|
@@ -4079,7 +4340,11 @@ async function writeMemory(record) {
|
|
|
4079
4340
|
document_id: record.document_id ?? null,
|
|
4080
4341
|
user_id: record.user_id ?? null,
|
|
4081
4342
|
char_offset: record.char_offset ?? null,
|
|
4082
|
-
page_number: record.page_number ?? null
|
|
4343
|
+
page_number: record.page_number ?? null,
|
|
4344
|
+
source_path: record.source_path ?? null,
|
|
4345
|
+
source_type: record.source_type ?? null,
|
|
4346
|
+
tier: record.tier ?? classifyTier(record),
|
|
4347
|
+
supersedes_id: record.supersedes_id ?? null
|
|
4083
4348
|
};
|
|
4084
4349
|
_pendingRecords.push(dbRow);
|
|
4085
4350
|
if (_flushTimer === null) {
|
|
@@ -4111,20 +4376,26 @@ async function flushBatch() {
|
|
|
4111
4376
|
const userId = row.user_id ?? null;
|
|
4112
4377
|
const charOffset = row.char_offset ?? null;
|
|
4113
4378
|
const pageNumber = row.page_number ?? null;
|
|
4379
|
+
const sourcePath = row.source_path ?? null;
|
|
4380
|
+
const sourceType = row.source_type ?? null;
|
|
4381
|
+
const tier = row.tier ?? 3;
|
|
4382
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
4114
4383
|
return {
|
|
4115
4384
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
4116
4385
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
4117
4386
|
tool_name, project_name,
|
|
4118
4387
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4119
4388
|
confidence, last_accessed,
|
|
4120
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
4121
|
-
|
|
4389
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4390
|
+
source_path, source_type, tier, supersedes_id)
|
|
4391
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4122
4392
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
4123
4393
|
tool_name, project_name,
|
|
4124
4394
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4125
4395
|
confidence, last_accessed,
|
|
4126
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
4127
|
-
|
|
4396
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4397
|
+
source_path, source_type, tier, supersedes_id)
|
|
4398
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4128
4399
|
args: hasVector ? [
|
|
4129
4400
|
row.id,
|
|
4130
4401
|
row.agent_id,
|
|
@@ -4146,7 +4417,11 @@ async function flushBatch() {
|
|
|
4146
4417
|
documentId,
|
|
4147
4418
|
userId,
|
|
4148
4419
|
charOffset,
|
|
4149
|
-
pageNumber
|
|
4420
|
+
pageNumber,
|
|
4421
|
+
sourcePath,
|
|
4422
|
+
sourceType,
|
|
4423
|
+
tier,
|
|
4424
|
+
supersedesId
|
|
4150
4425
|
] : [
|
|
4151
4426
|
row.id,
|
|
4152
4427
|
row.agent_id,
|
|
@@ -4167,7 +4442,11 @@ async function flushBatch() {
|
|
|
4167
4442
|
documentId,
|
|
4168
4443
|
userId,
|
|
4169
4444
|
charOffset,
|
|
4170
|
-
pageNumber
|
|
4445
|
+
pageNumber,
|
|
4446
|
+
sourcePath,
|
|
4447
|
+
sourceType,
|
|
4448
|
+
tier,
|
|
4449
|
+
supersedesId
|
|
4171
4450
|
]
|
|
4172
4451
|
};
|
|
4173
4452
|
};
|
|
@@ -4241,7 +4520,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4241
4520
|
has_error, raw_text, vector, importance, status,
|
|
4242
4521
|
confidence, last_accessed,
|
|
4243
4522
|
workspace_id, document_id, user_id,
|
|
4244
|
-
char_offset, page_number
|
|
4523
|
+
char_offset, page_number,
|
|
4524
|
+
source_path, source_type
|
|
4245
4525
|
FROM memories
|
|
4246
4526
|
WHERE agent_id = ?
|
|
4247
4527
|
AND vector IS NOT NULL${statusFilter}
|
|
@@ -4290,7 +4570,9 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4290
4570
|
document_id: row.document_id ?? null,
|
|
4291
4571
|
user_id: row.user_id ?? null,
|
|
4292
4572
|
char_offset: row.char_offset ?? null,
|
|
4293
|
-
page_number: row.page_number ?? null
|
|
4573
|
+
page_number: row.page_number ?? null,
|
|
4574
|
+
source_path: row.source_path ?? null,
|
|
4575
|
+
source_type: row.source_type ?? null
|
|
4294
4576
|
}));
|
|
4295
4577
|
}
|
|
4296
4578
|
async function attachDocumentMetadata(records) {
|
|
@@ -4328,6 +4610,25 @@ async function attachDocumentMetadata(records) {
|
|
|
4328
4610
|
}
|
|
4329
4611
|
return records;
|
|
4330
4612
|
}
|
|
4613
|
+
async function flushTier3(agentId, options) {
|
|
4614
|
+
const client = getClient();
|
|
4615
|
+
const maxAge = options?.maxAgeHours ?? 72;
|
|
4616
|
+
const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
|
|
4617
|
+
if (options?.dryRun) {
|
|
4618
|
+
const result2 = await client.execute({
|
|
4619
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
4620
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
4621
|
+
args: [agentId, cutoff]
|
|
4622
|
+
});
|
|
4623
|
+
return { archived: Number(result2.rows[0]?.cnt ?? 0) };
|
|
4624
|
+
}
|
|
4625
|
+
const result = await client.execute({
|
|
4626
|
+
sql: `UPDATE memories SET status = 'archived'
|
|
4627
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
4628
|
+
args: [agentId, cutoff]
|
|
4629
|
+
});
|
|
4630
|
+
return { archived: result.rowsAffected };
|
|
4631
|
+
}
|
|
4331
4632
|
async function disposeStore() {
|
|
4332
4633
|
if (_flushTimer !== null) {
|
|
4333
4634
|
clearInterval(_flushTimer);
|
|
@@ -4358,6 +4659,18 @@ function reserveVersions(count) {
|
|
|
4358
4659
|
}
|
|
4359
4660
|
return reserved;
|
|
4360
4661
|
}
|
|
4662
|
+
async function getMemoryCardinality(agentId) {
|
|
4663
|
+
try {
|
|
4664
|
+
const client = getClient();
|
|
4665
|
+
const result = await client.execute({
|
|
4666
|
+
sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
|
|
4667
|
+
args: [agentId]
|
|
4668
|
+
});
|
|
4669
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
4670
|
+
} catch {
|
|
4671
|
+
return 0;
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4361
4674
|
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
4362
4675
|
var init_store = __esm({
|
|
4363
4676
|
"src/lib/store.ts"() {
|
|
@@ -6462,6 +6775,25 @@ async function* agentLoop(userMessage, history, config2) {
|
|
|
6462
6775
|
}
|
|
6463
6776
|
totalUsage.inputTokens += response.usage.inputTokens;
|
|
6464
6777
|
totalUsage.outputTokens += response.usage.outputTokens;
|
|
6778
|
+
if (config2.tokenBudgetMiddleware) {
|
|
6779
|
+
const result = await config2.tokenBudgetMiddleware.onTokenUsed(
|
|
6780
|
+
response.usage.inputTokens,
|
|
6781
|
+
response.usage.outputTokens
|
|
6782
|
+
);
|
|
6783
|
+
if (result.warned && config2.hooks.onNotification) {
|
|
6784
|
+
await config2.hooks.onNotification(
|
|
6785
|
+
`\u26A0\uFE0F Token budget at ${result.percentUsed}%. Fallback model: ${result.fallback ?? "none (task will terminate at 100%)"}.`
|
|
6786
|
+
);
|
|
6787
|
+
}
|
|
6788
|
+
if (result.exceeded) {
|
|
6789
|
+
if (config2.hooks.onTokenBudgetExceeded) {
|
|
6790
|
+
await config2.hooks.onTokenBudgetExceeded(context, result.fallback);
|
|
6791
|
+
}
|
|
6792
|
+
abortController.abort();
|
|
6793
|
+
yield { type: "error", error: new Error("Token budget exceeded. Task requires manual continuation.") };
|
|
6794
|
+
break;
|
|
6795
|
+
}
|
|
6796
|
+
}
|
|
6465
6797
|
contextManager.updateFromApiUsage(response.usage.inputTokens, response.usage.outputTokens);
|
|
6466
6798
|
contextManager.updateFromMessages(messages);
|
|
6467
6799
|
await contextManager.checkPressure();
|
|
@@ -7007,12 +7339,58 @@ function composeHooks(...pipelines) {
|
|
|
7007
7339
|
for (const p of pipelines) {
|
|
7008
7340
|
if (p.onCrossAgentMessage) await p.onCrossAgentMessage(event);
|
|
7009
7341
|
}
|
|
7342
|
+
},
|
|
7343
|
+
async onTokenBudgetExceeded(ctx, fallback) {
|
|
7344
|
+
for (const p of pipelines) {
|
|
7345
|
+
if (p.onTokenBudgetExceeded) await p.onTokenBudgetExceeded(ctx, fallback);
|
|
7346
|
+
}
|
|
7010
7347
|
}
|
|
7011
7348
|
};
|
|
7012
7349
|
}
|
|
7013
7350
|
|
|
7014
7351
|
// src/lib/task-router.ts
|
|
7015
7352
|
import { randomUUID } from "crypto";
|
|
7353
|
+
var DEFAULT_BLOOM_CONFIG = {
|
|
7354
|
+
complexityToTier: {
|
|
7355
|
+
routine: "junior",
|
|
7356
|
+
standard: "standard",
|
|
7357
|
+
complex: "senior",
|
|
7358
|
+
critical: "specialist"
|
|
7359
|
+
},
|
|
7360
|
+
tierRules: {
|
|
7361
|
+
junior: {
|
|
7362
|
+
eligible: ["tom"],
|
|
7363
|
+
reviewRequired: false,
|
|
7364
|
+
manualOnly: false
|
|
7365
|
+
},
|
|
7366
|
+
standard: {
|
|
7367
|
+
eligible: ["tom"],
|
|
7368
|
+
reviewRequired: false,
|
|
7369
|
+
manualOnly: false
|
|
7370
|
+
},
|
|
7371
|
+
senior: {
|
|
7372
|
+
eligible: [],
|
|
7373
|
+
// any specialist, but review required
|
|
7374
|
+
reviewRequired: true,
|
|
7375
|
+
manualOnly: false
|
|
7376
|
+
},
|
|
7377
|
+
specialist: {
|
|
7378
|
+
eligible: [],
|
|
7379
|
+
reviewRequired: true,
|
|
7380
|
+
manualOnly: true
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7383
|
+
};
|
|
7384
|
+
function resolveBloomRouting(complexity, config2 = DEFAULT_BLOOM_CONFIG) {
|
|
7385
|
+
const tier = config2.complexityToTier[complexity];
|
|
7386
|
+
const rule = config2.tierRules[tier];
|
|
7387
|
+
return {
|
|
7388
|
+
tier,
|
|
7389
|
+
reviewRequired: rule.reviewRequired,
|
|
7390
|
+
manualOnly: rule.manualOnly,
|
|
7391
|
+
eligible: rule.eligible
|
|
7392
|
+
};
|
|
7393
|
+
}
|
|
7016
7394
|
async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
7017
7395
|
const results = await searchFn(taskVector, agentId, { limit: 5 });
|
|
7018
7396
|
if (results.length === 0) {
|
|
@@ -7025,13 +7403,29 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
|
7025
7403
|
}
|
|
7026
7404
|
return { agentId, score: results.length / 5 };
|
|
7027
7405
|
}
|
|
7028
|
-
async function routeTask(taskDescription, employees, embedFn, searchFn) {
|
|
7029
|
-
|
|
7406
|
+
async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
|
|
7407
|
+
let specialists = employees.filter((e) => e.name !== "exe");
|
|
7030
7408
|
if (specialists.length === 0) {
|
|
7031
7409
|
throw new Error(
|
|
7032
7410
|
"No specialist employees available. Create one with /exe-new-employee."
|
|
7033
7411
|
);
|
|
7034
7412
|
}
|
|
7413
|
+
let bloomRouting;
|
|
7414
|
+
if (options?.complexity) {
|
|
7415
|
+
bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
|
|
7416
|
+
if (bloomRouting.manualOnly) {
|
|
7417
|
+
throw new Error(
|
|
7418
|
+
`Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
|
|
7419
|
+
);
|
|
7420
|
+
}
|
|
7421
|
+
if (bloomRouting.eligible.length > 0) {
|
|
7422
|
+
const eligible = new Set(bloomRouting.eligible);
|
|
7423
|
+
const filtered = specialists.filter((e) => eligible.has(e.name));
|
|
7424
|
+
if (filtered.length > 0) {
|
|
7425
|
+
specialists = filtered;
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
7035
7429
|
const taskVector = await embedFn(taskDescription);
|
|
7036
7430
|
const scored = await Promise.all(
|
|
7037
7431
|
specialists.map(async (emp) => {
|
|
@@ -7040,7 +7434,7 @@ async function routeTask(taskDescription, employees, embedFn, searchFn) {
|
|
|
7040
7434
|
})
|
|
7041
7435
|
);
|
|
7042
7436
|
scored.sort((a, b) => b.score - a.score);
|
|
7043
|
-
return scored[0];
|
|
7437
|
+
return { ...scored[0], bloomRouting };
|
|
7044
7438
|
}
|
|
7045
7439
|
|
|
7046
7440
|
// src/runtime/orchestrator.ts
|
|
@@ -11715,7 +12109,92 @@ async function executeCreateTask(params) {
|
|
|
11715
12109
|
baseDir: process.cwd()
|
|
11716
12110
|
});
|
|
11717
12111
|
}
|
|
11718
|
-
async function
|
|
12112
|
+
async function executeUpdateWiki(params) {
|
|
12113
|
+
const apiUrl = process.env.EXE_WIKI_API_URL;
|
|
12114
|
+
const apiKey = process.env.EXE_WIKI_API_KEY;
|
|
12115
|
+
if (!apiUrl || !apiKey) {
|
|
12116
|
+
throw new Error("Wiki not configured: EXE_WIKI_API_URL / EXE_WIKI_API_KEY not set");
|
|
12117
|
+
}
|
|
12118
|
+
const workspace = params.workspace;
|
|
12119
|
+
const content = params.content ?? params.text;
|
|
12120
|
+
const mode = params.mode ?? "append";
|
|
12121
|
+
const section = params.section;
|
|
12122
|
+
if (!workspace || !content) {
|
|
12123
|
+
throw new Error("update_wiki requires 'workspace' and 'content' params");
|
|
12124
|
+
}
|
|
12125
|
+
const documentId = params.document_id;
|
|
12126
|
+
if (documentId && mode === "append") {
|
|
12127
|
+
const readRes = await fetch(`${apiUrl}/v1/document/${documentId}`, {
|
|
12128
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
12129
|
+
signal: AbortSignal.timeout(15e3)
|
|
12130
|
+
});
|
|
12131
|
+
if (!readRes.ok) {
|
|
12132
|
+
throw new Error(`Wiki read failed (${readRes.status})`);
|
|
12133
|
+
}
|
|
12134
|
+
const doc = await readRes.json();
|
|
12135
|
+
const existingContent = String(doc.content ?? "");
|
|
12136
|
+
const title = String(doc.title ?? "Untitled");
|
|
12137
|
+
await fetch(`${apiUrl}/v1/document/${documentId}`, {
|
|
12138
|
+
method: "DELETE",
|
|
12139
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
12140
|
+
signal: AbortSignal.timeout(15e3)
|
|
12141
|
+
}).catch(() => {
|
|
12142
|
+
});
|
|
12143
|
+
const uploadRes = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
12144
|
+
method: "POST",
|
|
12145
|
+
headers: {
|
|
12146
|
+
"Content-Type": "application/json",
|
|
12147
|
+
Authorization: `Bearer ${apiKey}`
|
|
12148
|
+
},
|
|
12149
|
+
body: JSON.stringify({
|
|
12150
|
+
textContent: section ? existingContent + `
|
|
12151
|
+
|
|
12152
|
+
## ${section}
|
|
12153
|
+
${content}` : existingContent + "\n\n" + content,
|
|
12154
|
+
metadata: { title },
|
|
12155
|
+
workspaceSlugs: [workspace]
|
|
12156
|
+
}),
|
|
12157
|
+
signal: AbortSignal.timeout(15e3)
|
|
12158
|
+
});
|
|
12159
|
+
if (!uploadRes.ok) throw new Error(`Wiki upload failed (${uploadRes.status})`);
|
|
12160
|
+
} else {
|
|
12161
|
+
const title = params.title ?? "Auto-generated";
|
|
12162
|
+
const res = await fetch(`${apiUrl}/v1/document/raw-text`, {
|
|
12163
|
+
method: "POST",
|
|
12164
|
+
headers: {
|
|
12165
|
+
"Content-Type": "application/json",
|
|
12166
|
+
Authorization: `Bearer ${apiKey}`
|
|
12167
|
+
},
|
|
12168
|
+
body: JSON.stringify({
|
|
12169
|
+
textContent: content,
|
|
12170
|
+
metadata: { title },
|
|
12171
|
+
workspaceSlugs: [workspace]
|
|
12172
|
+
}),
|
|
12173
|
+
signal: AbortSignal.timeout(15e3)
|
|
12174
|
+
});
|
|
12175
|
+
if (!res.ok) throw new Error(`Wiki create failed (${res.status})`);
|
|
12176
|
+
}
|
|
12177
|
+
}
|
|
12178
|
+
async function routeToApproval(action, resolvedParams, triggerName) {
|
|
12179
|
+
const { createTask: createTask2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
12180
|
+
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)}`;
|
|
12181
|
+
await createTask2({
|
|
12182
|
+
title: `[Approval Required] ${triggerName}: ${action.type}`,
|
|
12183
|
+
assignedTo: "exe",
|
|
12184
|
+
assignedBy: "trigger-engine",
|
|
12185
|
+
projectName: resolvedParams.project ?? "exe-os",
|
|
12186
|
+
priority: "p1",
|
|
12187
|
+
context: `Trigger "${triggerName}" wants to execute this action but requires approval.
|
|
12188
|
+
|
|
12189
|
+
**Action:** ${action.type}
|
|
12190
|
+
**Summary:** ${actionSummary}
|
|
12191
|
+
**Full params:** ${JSON.stringify(resolvedParams, null, 2)}
|
|
12192
|
+
|
|
12193
|
+
To approve, manually run the action via MCP tools.`,
|
|
12194
|
+
baseDir: process.cwd()
|
|
12195
|
+
});
|
|
12196
|
+
}
|
|
12197
|
+
async function executeAction(action, record, executor, triggerName) {
|
|
11719
12198
|
if (executor) {
|
|
11720
12199
|
return executor(action, record);
|
|
11721
12200
|
}
|
|
@@ -11723,6 +12202,17 @@ async function executeAction(action, record, executor) {
|
|
|
11723
12202
|
for (const [key, val] of Object.entries(action.params)) {
|
|
11724
12203
|
resolvedParams[key] = substituteTemplate(val, record);
|
|
11725
12204
|
}
|
|
12205
|
+
if (action.requires_approval) {
|
|
12206
|
+
try {
|
|
12207
|
+
await routeToApproval(action, resolvedParams, triggerName ?? "Unknown trigger");
|
|
12208
|
+
return { success: true };
|
|
12209
|
+
} catch (err) {
|
|
12210
|
+
return {
|
|
12211
|
+
success: false,
|
|
12212
|
+
error: `Approval routing failed: ${err instanceof Error ? err.message : String(err)}`
|
|
12213
|
+
};
|
|
12214
|
+
}
|
|
12215
|
+
}
|
|
11726
12216
|
try {
|
|
11727
12217
|
switch (action.type) {
|
|
11728
12218
|
case "send_whatsapp":
|
|
@@ -11734,6 +12224,9 @@ async function executeAction(action, record, executor) {
|
|
|
11734
12224
|
case "create_task":
|
|
11735
12225
|
await executeCreateTask(resolvedParams);
|
|
11736
12226
|
break;
|
|
12227
|
+
case "update_wiki":
|
|
12228
|
+
await executeUpdateWiki(resolvedParams);
|
|
12229
|
+
break;
|
|
11737
12230
|
case "mcp_tool":
|
|
11738
12231
|
console.log(
|
|
11739
12232
|
`[trigger-engine] mcp_tool action: ${JSON.stringify(resolvedParams)}`
|
|
@@ -11758,7 +12251,7 @@ async function processCRMEvent(event, executor, triggersOverride) {
|
|
|
11758
12251
|
if (!evaluateConditions(trigger.conditions, event.record)) continue;
|
|
11759
12252
|
const actionResults = [];
|
|
11760
12253
|
for (const action of trigger.actions) {
|
|
11761
|
-
const result = await executeAction(action, event.record, executor);
|
|
12254
|
+
const result = await executeAction(action, event.record, executor, trigger.name);
|
|
11762
12255
|
actionResults.push({
|
|
11763
12256
|
type: action.type,
|
|
11764
12257
|
success: result.success,
|