@askexenow/exe-os 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -79
- package/dist/bin/backfill-responses.js +160 -8
- package/dist/bin/backfill-vectors.js +130 -1
- package/dist/bin/cleanup-stale-review-tasks.js +130 -1
- package/dist/bin/cli.js +10111 -7540
- package/dist/bin/exe-agent.js +159 -1
- package/dist/bin/exe-assign.js +235 -16
- package/dist/bin/exe-boot.js +344 -472
- package/dist/bin/exe-call.js +145 -1
- package/dist/bin/exe-cloud.js +11 -0
- package/dist/bin/exe-dispatch.js +37 -24
- package/dist/bin/exe-doctor.js +130 -1
- package/dist/bin/exe-export-behaviors.js +150 -7
- package/dist/bin/exe-forget.js +822 -665
- package/dist/bin/exe-gateway.js +470 -62
- package/dist/bin/exe-heartbeat.js +133 -2
- package/dist/bin/exe-kill.js +150 -7
- package/dist/bin/exe-launch-agent.js +150 -7
- package/dist/bin/exe-new-employee.js +756 -224
- package/dist/bin/exe-pending-messages.js +132 -2
- package/dist/bin/exe-pending-notifications.js +130 -1
- package/dist/bin/exe-pending-reviews.js +132 -2
- package/dist/bin/exe-review.js +160 -8
- package/dist/bin/exe-search.js +2473 -2008
- package/dist/bin/exe-session-cleanup.js +238 -51
- package/dist/bin/exe-settings.js +11 -0
- package/dist/bin/exe-status.js +130 -1
- package/dist/bin/exe-team.js +130 -1
- package/dist/bin/git-sweep.js +272 -16
- package/dist/bin/graph-backfill.js +150 -7
- package/dist/bin/graph-export.js +150 -7
- package/dist/bin/install.js +5 -0
- package/dist/bin/scan-tasks.js +238 -19
- package/dist/bin/setup.js +1776 -10
- package/dist/bin/shard-migrate.js +150 -7
- package/dist/bin/update.js +9 -6
- package/dist/bin/wiki-sync.js +150 -7
- package/dist/gateway/index.js +470 -62
- package/dist/hooks/bug-report-worker.js +195 -35
- package/dist/hooks/commit-complete.js +272 -16
- package/dist/hooks/error-recall.js +2313 -1847
- package/dist/hooks/exe-heartbeat-hook.js +5 -0
- package/dist/hooks/ingest-worker.js +330 -58
- package/dist/hooks/ingest.js +11 -0
- package/dist/hooks/instructions-loaded.js +199 -10
- package/dist/hooks/notification.js +199 -10
- package/dist/hooks/post-compact.js +199 -10
- package/dist/hooks/pre-compact.js +199 -10
- package/dist/hooks/pre-tool-use.js +199 -10
- package/dist/hooks/prompt-ingest-worker.js +179 -14
- package/dist/hooks/prompt-submit.js +781 -285
- package/dist/hooks/response-ingest-worker.js +1900 -1405
- package/dist/hooks/session-end.js +456 -12
- package/dist/hooks/session-start.js +2188 -1724
- package/dist/hooks/stop.js +200 -10
- package/dist/hooks/subagent-stop.js +199 -10
- package/dist/hooks/summary-worker.js +604 -334
- package/dist/index.js +554 -61
- package/dist/lib/cloud-sync.js +5 -0
- package/dist/lib/config.js +13 -0
- package/dist/lib/consolidation.js +5 -0
- package/dist/lib/database.js +104 -0
- package/dist/lib/device-registry.js +109 -0
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/employee-templates.js +53 -26
- package/dist/lib/employees.js +5 -0
- package/dist/lib/exe-daemon-client.js +5 -0
- package/dist/lib/exe-daemon.js +493 -79
- package/dist/lib/file-grep.js +20 -4
- package/dist/lib/hybrid-search.js +1435 -190
- package/dist/lib/identity-templates.js +126 -5
- package/dist/lib/identity.js +5 -0
- package/dist/lib/license.js +5 -0
- package/dist/lib/messaging.js +37 -24
- package/dist/lib/schedules.js +130 -1
- package/dist/lib/skill-learning.js +11 -0
- package/dist/lib/status-brief.js +5 -0
- package/dist/lib/store.js +199 -10
- package/dist/lib/task-router.js +72 -6
- package/dist/lib/tasks.js +179 -50
- package/dist/lib/tmux-routing.js +179 -46
- package/dist/mcp/server.js +2129 -1855
- package/dist/mcp/tools/create-task.js +86 -36
- package/dist/mcp/tools/deactivate-behavior.js +5 -0
- package/dist/mcp/tools/list-tasks.js +39 -11
- package/dist/mcp/tools/send-message.js +37 -24
- package/dist/mcp/tools/update-task.js +153 -38
- package/dist/runtime/index.js +451 -59
- package/dist/tui/App.js +454 -59
- package/package.json +1 -1
package/dist/lib/tmux-routing.js
CHANGED
|
@@ -307,11 +307,12 @@ function queueIntercom(targetSession, reason) {
|
|
|
307
307
|
}
|
|
308
308
|
writeQueue(queue);
|
|
309
309
|
}
|
|
310
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
310
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
311
311
|
var init_intercom_queue = __esm({
|
|
312
312
|
"src/lib/intercom-queue.ts"() {
|
|
313
313
|
"use strict";
|
|
314
314
|
QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
|
|
315
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
315
316
|
INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
|
|
316
317
|
}
|
|
317
318
|
});
|
|
@@ -398,6 +399,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
398
399
|
const userSL = raw.sessionLifecycle ?? {};
|
|
399
400
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
400
401
|
}
|
|
402
|
+
function normalizeAutoUpdate(raw) {
|
|
403
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
404
|
+
const userAU = raw.autoUpdate ?? {};
|
|
405
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
406
|
+
}
|
|
401
407
|
async function loadConfig() {
|
|
402
408
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
403
409
|
await mkdir(dir, { recursive: true });
|
|
@@ -420,6 +426,7 @@ async function loadConfig() {
|
|
|
420
426
|
}
|
|
421
427
|
normalizeScalingRoadmap(migratedCfg);
|
|
422
428
|
normalizeSessionLifecycle(migratedCfg);
|
|
429
|
+
normalizeAutoUpdate(migratedCfg);
|
|
423
430
|
const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
424
431
|
if (config.dbPath.startsWith("~")) {
|
|
425
432
|
config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
|
|
@@ -495,6 +502,11 @@ var init_config = __esm({
|
|
|
495
502
|
idleKillTicksRequired: 3,
|
|
496
503
|
idleKillIntercomAckWindowMs: 1e4,
|
|
497
504
|
maxAutoInstances: 10
|
|
505
|
+
},
|
|
506
|
+
autoUpdate: {
|
|
507
|
+
checkOnBoot: true,
|
|
508
|
+
autoInstall: false,
|
|
509
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
498
510
|
}
|
|
499
511
|
};
|
|
500
512
|
CONFIG_MIGRATIONS = [
|
|
@@ -642,6 +654,17 @@ function getGitRoot(dir) {
|
|
|
642
654
|
return null;
|
|
643
655
|
}
|
|
644
656
|
}
|
|
657
|
+
function getMainRepoRoot(dir) {
|
|
658
|
+
try {
|
|
659
|
+
const commonDir = execSync4(
|
|
660
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
661
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
662
|
+
).trim();
|
|
663
|
+
return realpath(path7.dirname(commonDir));
|
|
664
|
+
} catch {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
645
668
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
646
669
|
const label = instanceLabel(employeeName, instance);
|
|
647
670
|
return path7.join(repoRoot, ".worktrees", label);
|
|
@@ -833,6 +856,36 @@ import path9 from "path";
|
|
|
833
856
|
import { execSync as execSync5 } from "child_process";
|
|
834
857
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
835
858
|
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
859
|
+
async function writeCheckpoint(input) {
|
|
860
|
+
const client = getClient();
|
|
861
|
+
const row = await resolveTask(client, input.taskId);
|
|
862
|
+
const taskId = String(row.id);
|
|
863
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
864
|
+
const blockedByIds = [];
|
|
865
|
+
if (row.blocked_by) {
|
|
866
|
+
blockedByIds.push(String(row.blocked_by));
|
|
867
|
+
}
|
|
868
|
+
const checkpoint = {
|
|
869
|
+
step: input.step,
|
|
870
|
+
context_summary: input.contextSummary,
|
|
871
|
+
files_touched: input.filesTouched ?? [],
|
|
872
|
+
blocked_by_ids: blockedByIds,
|
|
873
|
+
last_checkpoint_at: now
|
|
874
|
+
};
|
|
875
|
+
const result = await client.execute({
|
|
876
|
+
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
877
|
+
args: [JSON.stringify(checkpoint), now, taskId]
|
|
878
|
+
});
|
|
879
|
+
if (result.rowsAffected === 0) {
|
|
880
|
+
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
881
|
+
}
|
|
882
|
+
const countResult = await client.execute({
|
|
883
|
+
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
884
|
+
args: [taskId]
|
|
885
|
+
});
|
|
886
|
+
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
887
|
+
return { checkpointCount };
|
|
888
|
+
}
|
|
836
889
|
function extractParentFromContext(contextBody) {
|
|
837
890
|
if (!contextBody) return null;
|
|
838
891
|
const match = contextBody.match(
|
|
@@ -939,9 +992,10 @@ async function createTaskCore(input) {
|
|
|
939
992
|
} catch {
|
|
940
993
|
}
|
|
941
994
|
}
|
|
995
|
+
const complexity = input.complexity ?? "standard";
|
|
942
996
|
await client.execute({
|
|
943
|
-
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)
|
|
944
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
997
|
+
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)
|
|
998
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
945
999
|
args: [
|
|
946
1000
|
id,
|
|
947
1001
|
input.title,
|
|
@@ -955,6 +1009,11 @@ async function createTaskCore(input) {
|
|
|
955
1009
|
parentTaskId,
|
|
956
1010
|
input.reviewer ?? null,
|
|
957
1011
|
input.context,
|
|
1012
|
+
input.budgetTokens ?? null,
|
|
1013
|
+
input.budgetFallbackModel ?? null,
|
|
1014
|
+
0,
|
|
1015
|
+
null,
|
|
1016
|
+
complexity,
|
|
958
1017
|
now,
|
|
959
1018
|
now
|
|
960
1019
|
]
|
|
@@ -970,7 +1029,11 @@ async function createTaskCore(input) {
|
|
|
970
1029
|
taskFile,
|
|
971
1030
|
createdAt: now,
|
|
972
1031
|
updatedAt: now,
|
|
973
|
-
warning
|
|
1032
|
+
warning,
|
|
1033
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
1034
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
1035
|
+
tokensUsed: 0,
|
|
1036
|
+
tokensWarnedAt: null
|
|
974
1037
|
};
|
|
975
1038
|
}
|
|
976
1039
|
async function listTasks(input) {
|
|
@@ -1010,7 +1073,12 @@ async function listTasks(input) {
|
|
|
1010
1073
|
status: String(r.status),
|
|
1011
1074
|
taskFile: String(r.task_file),
|
|
1012
1075
|
createdAt: String(r.created_at),
|
|
1013
|
-
updatedAt: String(r.updated_at)
|
|
1076
|
+
updatedAt: String(r.updated_at),
|
|
1077
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
1078
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
1079
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
1080
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
1081
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
1014
1082
|
}));
|
|
1015
1083
|
}
|
|
1016
1084
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
@@ -1018,8 +1086,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
1018
1086
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
1019
1087
|
try {
|
|
1020
1088
|
const since = new Date(taskCreatedAt).toISOString();
|
|
1089
|
+
const branch = execSync5(
|
|
1090
|
+
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
1091
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
1092
|
+
).trim();
|
|
1093
|
+
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
1021
1094
|
const commitCount = execSync5(
|
|
1022
|
-
`git log --oneline --since="${since}" 2>/dev/null | wc -l`,
|
|
1095
|
+
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
1023
1096
|
{ encoding: "utf8", timeout: 5e3 }
|
|
1024
1097
|
).trim();
|
|
1025
1098
|
const count = parseInt(commitCount, 10);
|
|
@@ -1078,6 +1151,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1078
1151
|
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
1079
1152
|
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
1080
1153
|
}
|
|
1154
|
+
try {
|
|
1155
|
+
await writeCheckpoint({
|
|
1156
|
+
taskId,
|
|
1157
|
+
step: "claimed",
|
|
1158
|
+
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
1159
|
+
});
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1081
1162
|
return { row, taskFile, now, taskId };
|
|
1082
1163
|
}
|
|
1083
1164
|
if (input.result) {
|
|
@@ -1091,6 +1172,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1091
1172
|
args: [input.status, now, taskId]
|
|
1092
1173
|
});
|
|
1093
1174
|
}
|
|
1175
|
+
try {
|
|
1176
|
+
await writeCheckpoint({
|
|
1177
|
+
taskId,
|
|
1178
|
+
step: `status_transition:${input.status}`,
|
|
1179
|
+
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
1180
|
+
});
|
|
1181
|
+
} catch {
|
|
1182
|
+
}
|
|
1094
1183
|
return { row, taskFile, now, taskId };
|
|
1095
1184
|
}
|
|
1096
1185
|
async function deleteTaskCore(taskId, _baseDir) {
|
|
@@ -1244,23 +1333,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
1244
1333
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
1245
1334
|
try {
|
|
1246
1335
|
const client = getClient();
|
|
1247
|
-
const
|
|
1248
|
-
const
|
|
1249
|
-
|
|
1250
|
-
if (parts.length >= 3 && parts[0] === "review") {
|
|
1251
|
-
const agent = parts[1];
|
|
1252
|
-
const slug = parts.slice(2).join("-");
|
|
1253
|
-
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
1336
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1337
|
+
const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
|
|
1338
|
+
if (parentId) {
|
|
1254
1339
|
const result = await client.execute({
|
|
1255
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE
|
|
1256
|
-
args: [
|
|
1340
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
|
|
1341
|
+
args: [now, parentId]
|
|
1257
1342
|
});
|
|
1258
1343
|
if (result.rowsAffected > 0) {
|
|
1259
1344
|
process.stderr.write(
|
|
1260
|
-
`[review-cleanup] Cascaded original task to done: ${
|
|
1345
|
+
`[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
|
|
1261
1346
|
`
|
|
1262
1347
|
);
|
|
1263
1348
|
}
|
|
1349
|
+
} else {
|
|
1350
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
1351
|
+
const reviewPrefix = fileName.replace(".md", "");
|
|
1352
|
+
const parts = reviewPrefix.split("-");
|
|
1353
|
+
if (parts.length >= 3 && parts[0] === "review") {
|
|
1354
|
+
const agent = parts[1];
|
|
1355
|
+
const slug = parts.slice(2).join("-");
|
|
1356
|
+
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
1357
|
+
const result = await client.execute({
|
|
1358
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
1359
|
+
args: [now, originalTaskFile]
|
|
1360
|
+
});
|
|
1361
|
+
if (result.rowsAffected > 0) {
|
|
1362
|
+
process.stderr.write(
|
|
1363
|
+
`[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
|
|
1364
|
+
`
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1264
1368
|
}
|
|
1265
1369
|
} catch (err) {
|
|
1266
1370
|
process.stderr.write(
|
|
@@ -1381,12 +1485,23 @@ function getProjectName(cwd) {
|
|
|
1381
1485
|
const dir = cwd ?? process.cwd();
|
|
1382
1486
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
1383
1487
|
try {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1488
|
+
let repoRoot;
|
|
1489
|
+
try {
|
|
1490
|
+
const gitCommonDir = execSync6("git rev-parse --path-format=absolute --git-common-dir", {
|
|
1491
|
+
cwd: dir,
|
|
1492
|
+
encoding: "utf8",
|
|
1493
|
+
timeout: 2e3,
|
|
1494
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1495
|
+
}).trim();
|
|
1496
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
1497
|
+
} catch {
|
|
1498
|
+
repoRoot = execSync6("git rev-parse --show-toplevel", {
|
|
1499
|
+
cwd: dir,
|
|
1500
|
+
encoding: "utf8",
|
|
1501
|
+
timeout: 2e3,
|
|
1502
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1503
|
+
}).trim();
|
|
1504
|
+
}
|
|
1390
1505
|
_cached2 = path12.basename(repoRoot);
|
|
1391
1506
|
_cachedCwd = dir;
|
|
1392
1507
|
return _cached2;
|
|
@@ -1492,7 +1607,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
1492
1607
|
return { dispatched, session: sessionName, crossProject };
|
|
1493
1608
|
} else {
|
|
1494
1609
|
const projectDir = input.projectDir ?? process.cwd();
|
|
1495
|
-
const result = ensureEmployee(input.assignedTo, exeSession, projectDir
|
|
1610
|
+
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
1611
|
+
autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
|
|
1612
|
+
});
|
|
1496
1613
|
if (result.status === "failed") {
|
|
1497
1614
|
process.stderr.write(
|
|
1498
1615
|
`[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
|
|
@@ -1856,7 +1973,8 @@ __export(tasks_exports, {
|
|
|
1856
1973
|
resolveTask: () => resolveTask,
|
|
1857
1974
|
slugify: () => slugify,
|
|
1858
1975
|
updateTask: () => updateTask,
|
|
1859
|
-
updateTaskStatus: () => updateTaskStatus
|
|
1976
|
+
updateTaskStatus: () => updateTaskStatus,
|
|
1977
|
+
writeCheckpoint: () => writeCheckpoint
|
|
1860
1978
|
});
|
|
1861
1979
|
import path13 from "path";
|
|
1862
1980
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -1898,10 +2016,11 @@ async function updateTask(input) {
|
|
|
1898
2016
|
try {
|
|
1899
2017
|
const client = getClient();
|
|
1900
2018
|
const taskTitle = String(row.title);
|
|
2019
|
+
const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
1901
2020
|
await client.execute({
|
|
1902
2021
|
sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
|
|
1903
|
-
WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
|
|
1904
|
-
args: [now, `%left
|
|
2022
|
+
WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
|
|
2023
|
+
args: [now, `%left '${escaped}' as in\\_progress%`]
|
|
1905
2024
|
});
|
|
1906
2025
|
} catch {
|
|
1907
2026
|
}
|
|
@@ -1959,6 +2078,10 @@ async function updateTask(input) {
|
|
|
1959
2078
|
taskFile,
|
|
1960
2079
|
createdAt: String(row.created_at),
|
|
1961
2080
|
updatedAt: now,
|
|
2081
|
+
budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
|
|
2082
|
+
budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
|
|
2083
|
+
tokensUsed: Number(row.tokens_used ?? 0),
|
|
2084
|
+
tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
|
|
1962
2085
|
nextTask
|
|
1963
2086
|
};
|
|
1964
2087
|
}
|
|
@@ -2451,6 +2574,11 @@ function getSessionState(sessionName) {
|
|
|
2451
2574
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
2452
2575
|
try {
|
|
2453
2576
|
const pane = transport.capturePane(sessionName, 5);
|
|
2577
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
2578
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
2579
|
+
return "no_claude";
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2454
2582
|
if (/Running…/.test(pane)) return "tool";
|
|
2455
2583
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
2456
2584
|
return "idle";
|
|
@@ -2481,7 +2609,14 @@ function sendIntercom(targetSession) {
|
|
|
2481
2609
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
2482
2610
|
return "failed";
|
|
2483
2611
|
}
|
|
2484
|
-
|
|
2612
|
+
const sessionState = getSessionState(targetSession);
|
|
2613
|
+
if (sessionState === "no_claude") {
|
|
2614
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
2615
|
+
recordDebounce(targetSession);
|
|
2616
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
2617
|
+
return "queued";
|
|
2618
|
+
}
|
|
2619
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
2485
2620
|
queueIntercom(targetSession, "session busy at send time");
|
|
2486
2621
|
recordDebounce(targetSession);
|
|
2487
2622
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -2493,18 +2628,7 @@ function sendIntercom(targetSession) {
|
|
|
2493
2628
|
}
|
|
2494
2629
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
2495
2630
|
recordDebounce(targetSession);
|
|
2496
|
-
|
|
2497
|
-
try {
|
|
2498
|
-
execSync7(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
2499
|
-
} catch {
|
|
2500
|
-
}
|
|
2501
|
-
const state = getSessionState(targetSession);
|
|
2502
|
-
if (state === "thinking" || state === "tool") {
|
|
2503
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
2504
|
-
return "acknowledged";
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
2631
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
2508
2632
|
return "delivered";
|
|
2509
2633
|
} catch {
|
|
2510
2634
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -2521,7 +2645,17 @@ function notifyParentExe(sessionKey) {
|
|
|
2521
2645
|
process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
|
|
2522
2646
|
`);
|
|
2523
2647
|
const result = sendIntercom(target);
|
|
2524
|
-
|
|
2648
|
+
if (result === "failed") {
|
|
2649
|
+
const rootExe = resolveExeSession();
|
|
2650
|
+
if (rootExe && rootExe !== target) {
|
|
2651
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
2652
|
+
`);
|
|
2653
|
+
const fallback = sendIntercom(rootExe);
|
|
2654
|
+
return fallback !== "failed";
|
|
2655
|
+
}
|
|
2656
|
+
return false;
|
|
2657
|
+
}
|
|
2658
|
+
return true;
|
|
2525
2659
|
}
|
|
2526
2660
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
2527
2661
|
if (employeeName === "exe") {
|
|
@@ -2570,7 +2704,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2570
2704
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
2571
2705
|
}
|
|
2572
2706
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
2573
|
-
const
|
|
2707
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
2708
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
2574
2709
|
if (wtPath) {
|
|
2575
2710
|
spawnOpts.cwd = wtPath;
|
|
2576
2711
|
}
|
|
@@ -2751,7 +2886,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2751
2886
|
let booted = false;
|
|
2752
2887
|
for (let i = 0; i < 30; i++) {
|
|
2753
2888
|
try {
|
|
2754
|
-
execSync7("sleep
|
|
2889
|
+
execSync7("sleep 0.5");
|
|
2755
2890
|
} catch {
|
|
2756
2891
|
}
|
|
2757
2892
|
try {
|
|
@@ -2771,7 +2906,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2771
2906
|
}
|
|
2772
2907
|
}
|
|
2773
2908
|
if (!booted) {
|
|
2774
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
2909
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
2775
2910
|
}
|
|
2776
2911
|
if (!useExeAgent) {
|
|
2777
2912
|
try {
|
|
@@ -2789,7 +2924,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
2789
2924
|
});
|
|
2790
2925
|
return { sessionName };
|
|
2791
2926
|
}
|
|
2792
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
2927
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
2793
2928
|
var init_tmux_routing = __esm({
|
|
2794
2929
|
"src/lib/tmux-routing.ts"() {
|
|
2795
2930
|
init_session_registry();
|
|
@@ -2809,8 +2944,6 @@ var init_tmux_routing = __esm({
|
|
|
2809
2944
|
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
2810
2945
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
2811
2946
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
2812
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
2813
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
2814
2947
|
}
|
|
2815
2948
|
});
|
|
2816
2949
|
init_tmux_routing();
|