@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
|
@@ -116,6 +116,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
116
116
|
const userSL = raw.sessionLifecycle ?? {};
|
|
117
117
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
118
118
|
}
|
|
119
|
+
function normalizeAutoUpdate(raw) {
|
|
120
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
121
|
+
const userAU = raw.autoUpdate ?? {};
|
|
122
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
123
|
+
}
|
|
119
124
|
async function loadConfig() {
|
|
120
125
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
121
126
|
await mkdir(dir, { recursive: true });
|
|
@@ -138,6 +143,7 @@ async function loadConfig() {
|
|
|
138
143
|
}
|
|
139
144
|
normalizeScalingRoadmap(migratedCfg);
|
|
140
145
|
normalizeSessionLifecycle(migratedCfg);
|
|
146
|
+
normalizeAutoUpdate(migratedCfg);
|
|
141
147
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
142
148
|
if (config.dbPath.startsWith("~")) {
|
|
143
149
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -160,6 +166,7 @@ function loadConfigSync() {
|
|
|
160
166
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
161
167
|
normalizeScalingRoadmap(migratedCfg);
|
|
162
168
|
normalizeSessionLifecycle(migratedCfg);
|
|
169
|
+
normalizeAutoUpdate(migratedCfg);
|
|
163
170
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
164
171
|
} catch {
|
|
165
172
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -179,6 +186,7 @@ async function loadConfigFrom(configPath) {
|
|
|
179
186
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
180
187
|
normalizeScalingRoadmap(migratedCfg);
|
|
181
188
|
normalizeSessionLifecycle(migratedCfg);
|
|
189
|
+
normalizeAutoUpdate(migratedCfg);
|
|
182
190
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
183
191
|
} catch {
|
|
184
192
|
return { ...DEFAULT_CONFIG };
|
|
@@ -250,6 +258,11 @@ var init_config = __esm({
|
|
|
250
258
|
idleKillTicksRequired: 3,
|
|
251
259
|
idleKillIntercomAckWindowMs: 1e4,
|
|
252
260
|
maxAutoInstances: 10
|
|
261
|
+
},
|
|
262
|
+
autoUpdate: {
|
|
263
|
+
checkOnBoot: true,
|
|
264
|
+
autoInstall: false,
|
|
265
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
253
266
|
}
|
|
254
267
|
};
|
|
255
268
|
CONFIG_MIGRATIONS = [
|
|
@@ -395,9 +408,10 @@ async function createTaskCore(input) {
|
|
|
395
408
|
} catch {
|
|
396
409
|
}
|
|
397
410
|
}
|
|
411
|
+
const complexity = input.complexity ?? "standard";
|
|
398
412
|
await client.execute({
|
|
399
|
-
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)
|
|
400
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
413
|
+
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)
|
|
414
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
401
415
|
args: [
|
|
402
416
|
id,
|
|
403
417
|
input.title,
|
|
@@ -411,6 +425,11 @@ async function createTaskCore(input) {
|
|
|
411
425
|
parentTaskId,
|
|
412
426
|
input.reviewer ?? null,
|
|
413
427
|
input.context,
|
|
428
|
+
input.budgetTokens ?? null,
|
|
429
|
+
input.budgetFallbackModel ?? null,
|
|
430
|
+
0,
|
|
431
|
+
null,
|
|
432
|
+
complexity,
|
|
414
433
|
now,
|
|
415
434
|
now
|
|
416
435
|
]
|
|
@@ -426,7 +445,11 @@ async function createTaskCore(input) {
|
|
|
426
445
|
taskFile,
|
|
427
446
|
createdAt: now,
|
|
428
447
|
updatedAt: now,
|
|
429
|
-
warning
|
|
448
|
+
warning,
|
|
449
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
450
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
451
|
+
tokensUsed: 0,
|
|
452
|
+
tokensWarnedAt: null
|
|
430
453
|
};
|
|
431
454
|
}
|
|
432
455
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
@@ -786,11 +809,12 @@ function queueIntercom(targetSession, reason) {
|
|
|
786
809
|
}
|
|
787
810
|
writeQueue(queue);
|
|
788
811
|
}
|
|
789
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
812
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
790
813
|
var init_intercom_queue = __esm({
|
|
791
814
|
"src/lib/intercom-queue.ts"() {
|
|
792
815
|
"use strict";
|
|
793
816
|
QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
|
|
817
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
794
818
|
INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
|
|
795
819
|
}
|
|
796
820
|
});
|
|
@@ -913,6 +937,17 @@ function getGitRoot(dir) {
|
|
|
913
937
|
return null;
|
|
914
938
|
}
|
|
915
939
|
}
|
|
940
|
+
function getMainRepoRoot(dir) {
|
|
941
|
+
try {
|
|
942
|
+
const commonDir = execSync5(
|
|
943
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
944
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
945
|
+
).trim();
|
|
946
|
+
return realpath(path9.dirname(commonDir));
|
|
947
|
+
} catch {
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
916
951
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
917
952
|
const label = instanceLabel(employeeName, instance);
|
|
918
953
|
return path9.join(repoRoot, ".worktrees", label);
|
|
@@ -1138,6 +1173,11 @@ function getSessionState(sessionName) {
|
|
|
1138
1173
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
1139
1174
|
try {
|
|
1140
1175
|
const pane = transport.capturePane(sessionName, 5);
|
|
1176
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
1177
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
1178
|
+
return "no_claude";
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1141
1181
|
if (/Running…/.test(pane)) return "tool";
|
|
1142
1182
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
1143
1183
|
return "idle";
|
|
@@ -1145,10 +1185,6 @@ function getSessionState(sessionName) {
|
|
|
1145
1185
|
return "offline";
|
|
1146
1186
|
}
|
|
1147
1187
|
}
|
|
1148
|
-
function isSessionBusy(sessionName) {
|
|
1149
|
-
const state = getSessionState(sessionName);
|
|
1150
|
-
return state === "thinking" || state === "tool";
|
|
1151
|
-
}
|
|
1152
1188
|
function isExeSession(sessionName) {
|
|
1153
1189
|
return /^exe\d*$/.test(sessionName);
|
|
1154
1190
|
}
|
|
@@ -1168,7 +1204,14 @@ function sendIntercom(targetSession) {
|
|
|
1168
1204
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
1169
1205
|
return "failed";
|
|
1170
1206
|
}
|
|
1171
|
-
|
|
1207
|
+
const sessionState = getSessionState(targetSession);
|
|
1208
|
+
if (sessionState === "no_claude") {
|
|
1209
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
1210
|
+
recordDebounce(targetSession);
|
|
1211
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
1212
|
+
return "queued";
|
|
1213
|
+
}
|
|
1214
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
1172
1215
|
queueIntercom(targetSession, "session busy at send time");
|
|
1173
1216
|
recordDebounce(targetSession);
|
|
1174
1217
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -1180,18 +1223,7 @@ function sendIntercom(targetSession) {
|
|
|
1180
1223
|
}
|
|
1181
1224
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
1182
1225
|
recordDebounce(targetSession);
|
|
1183
|
-
|
|
1184
|
-
try {
|
|
1185
|
-
execSync6(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
1186
|
-
} catch {
|
|
1187
|
-
}
|
|
1188
|
-
const state = getSessionState(targetSession);
|
|
1189
|
-
if (state === "thinking" || state === "tool") {
|
|
1190
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
1191
|
-
return "acknowledged";
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
1226
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
1195
1227
|
return "delivered";
|
|
1196
1228
|
} catch {
|
|
1197
1229
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -1245,7 +1277,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1245
1277
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
1246
1278
|
}
|
|
1247
1279
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
1248
|
-
const
|
|
1280
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
1281
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
1249
1282
|
if (wtPath) {
|
|
1250
1283
|
spawnOpts.cwd = wtPath;
|
|
1251
1284
|
}
|
|
@@ -1426,7 +1459,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1426
1459
|
let booted = false;
|
|
1427
1460
|
for (let i = 0; i < 30; i++) {
|
|
1428
1461
|
try {
|
|
1429
|
-
execSync6("sleep
|
|
1462
|
+
execSync6("sleep 0.5");
|
|
1430
1463
|
} catch {
|
|
1431
1464
|
}
|
|
1432
1465
|
try {
|
|
@@ -1446,7 +1479,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1446
1479
|
}
|
|
1447
1480
|
}
|
|
1448
1481
|
if (!booted) {
|
|
1449
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
1482
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
1450
1483
|
}
|
|
1451
1484
|
if (!useExeAgent) {
|
|
1452
1485
|
try {
|
|
@@ -1464,7 +1497,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1464
1497
|
});
|
|
1465
1498
|
return { sessionName };
|
|
1466
1499
|
}
|
|
1467
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
1500
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
1468
1501
|
var init_tmux_routing = __esm({
|
|
1469
1502
|
"src/lib/tmux-routing.ts"() {
|
|
1470
1503
|
"use strict";
|
|
@@ -1484,8 +1517,6 @@ var init_tmux_routing = __esm({
|
|
|
1484
1517
|
DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
|
|
1485
1518
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
1486
1519
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
1487
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
1488
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
1489
1520
|
}
|
|
1490
1521
|
});
|
|
1491
1522
|
|
|
@@ -1523,12 +1554,23 @@ function getProjectName(cwd) {
|
|
|
1523
1554
|
const dir = cwd ?? process.cwd();
|
|
1524
1555
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
1525
1556
|
try {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1557
|
+
let repoRoot;
|
|
1558
|
+
try {
|
|
1559
|
+
const gitCommonDir = execSync7("git rev-parse --path-format=absolute --git-common-dir", {
|
|
1560
|
+
cwd: dir,
|
|
1561
|
+
encoding: "utf8",
|
|
1562
|
+
timeout: 2e3,
|
|
1563
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1564
|
+
}).trim();
|
|
1565
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
1566
|
+
} catch {
|
|
1567
|
+
repoRoot = execSync7("git rev-parse --show-toplevel", {
|
|
1568
|
+
cwd: dir,
|
|
1569
|
+
encoding: "utf8",
|
|
1570
|
+
timeout: 2e3,
|
|
1571
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1572
|
+
}).trim();
|
|
1573
|
+
}
|
|
1532
1574
|
_cached2 = path13.basename(repoRoot);
|
|
1533
1575
|
_cachedCwd = dir;
|
|
1534
1576
|
return _cached2;
|
|
@@ -1634,7 +1676,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
1634
1676
|
return { dispatched, session: sessionName, crossProject };
|
|
1635
1677
|
} else {
|
|
1636
1678
|
const projectDir = input.projectDir ?? process.cwd();
|
|
1637
|
-
const result = ensureEmployee(input.assignedTo, exeSession, projectDir
|
|
1679
|
+
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
1680
|
+
autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
|
|
1681
|
+
});
|
|
1638
1682
|
if (result.status === "failed") {
|
|
1639
1683
|
process.stderr.write(
|
|
1640
1684
|
`[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
|
|
@@ -1770,13 +1814,16 @@ function registerCreateTask(server) {
|
|
|
1770
1814
|
assigned_to: z.string().describe("Employee name to assign to"),
|
|
1771
1815
|
project_name: z.string().describe("Project name"),
|
|
1772
1816
|
priority: z.enum(["p0", "p1", "p2"]).default("p1").describe("Priority level"),
|
|
1817
|
+
complexity: z.enum(["routine", "standard", "complex", "critical"]).default("standard").describe("Bloom taxonomy complexity level. Affects routing tier and review requirements."),
|
|
1773
1818
|
context: z.string().describe("Full task description and context"),
|
|
1774
1819
|
blocked_by: z.string().optional().describe("Task ID or slug of the blocking task. If set, task starts as 'blocked'."),
|
|
1775
1820
|
parent_task_id: z.string().optional().describe("Parent task ID or slug. Links this task as a subtask."),
|
|
1776
|
-
reviewer: z.string().optional().describe("Who should review this task when done. Defaults to assigner.")
|
|
1821
|
+
reviewer: z.string().optional().describe("Who should review this task when done. Defaults to assigner."),
|
|
1822
|
+
budget_tokens: z.number().optional().describe("Max tokens allowed for this task (null = unlimited)"),
|
|
1823
|
+
budget_fallback_model: z.string().optional().describe("Model to route to when budget is exhausted")
|
|
1777
1824
|
}
|
|
1778
1825
|
},
|
|
1779
|
-
async ({ title, assigned_to, project_name, priority, context, blocked_by, parent_task_id, reviewer }) => {
|
|
1826
|
+
async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model }) => {
|
|
1780
1827
|
const { agentId: assignedBy } = getActiveAgent();
|
|
1781
1828
|
const task = await createTask({
|
|
1782
1829
|
title,
|
|
@@ -1784,11 +1831,14 @@ function registerCreateTask(server) {
|
|
|
1784
1831
|
assignedBy,
|
|
1785
1832
|
projectName: project_name,
|
|
1786
1833
|
priority,
|
|
1834
|
+
complexity,
|
|
1787
1835
|
context,
|
|
1788
1836
|
baseDir: process.cwd(),
|
|
1789
1837
|
blockedBy: blocked_by,
|
|
1790
1838
|
parentTaskId: parent_task_id,
|
|
1791
1839
|
reviewer,
|
|
1840
|
+
budgetTokens: budget_tokens,
|
|
1841
|
+
budgetFallbackModel: budget_fallback_model,
|
|
1792
1842
|
// Skip internal dispatch — we handle it below with autoInstance
|
|
1793
1843
|
// support. Without this, createTask fires dispatchTaskToEmployee
|
|
1794
1844
|
// (no autoInstance) in parallel, racing with our ensureEmployee
|
|
@@ -106,6 +106,11 @@ var init_config = __esm({
|
|
|
106
106
|
idleKillTicksRequired: 3,
|
|
107
107
|
idleKillIntercomAckWindowMs: 1e4,
|
|
108
108
|
maxAutoInstances: 10
|
|
109
|
+
},
|
|
110
|
+
autoUpdate: {
|
|
111
|
+
checkOnBoot: true,
|
|
112
|
+
autoInstall: false,
|
|
113
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
109
114
|
}
|
|
110
115
|
};
|
|
111
116
|
}
|
|
@@ -172,7 +177,12 @@ async function listTasks(input) {
|
|
|
172
177
|
status: String(r.status),
|
|
173
178
|
taskFile: String(r.task_file),
|
|
174
179
|
createdAt: String(r.created_at),
|
|
175
|
-
updatedAt: String(r.updated_at)
|
|
180
|
+
updatedAt: String(r.updated_at),
|
|
181
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
182
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
183
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
184
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
185
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
176
186
|
}));
|
|
177
187
|
}
|
|
178
188
|
var init_tasks_crud = __esm({
|
|
@@ -255,11 +265,12 @@ var init_provider_table = __esm({
|
|
|
255
265
|
import { readFileSync as readFileSync4, writeFileSync, renameSync as renameSync2, existsSync as existsSync5, mkdirSync } from "fs";
|
|
256
266
|
import path6 from "path";
|
|
257
267
|
import os4 from "os";
|
|
258
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
268
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
259
269
|
var init_intercom_queue = __esm({
|
|
260
270
|
"src/lib/intercom-queue.ts"() {
|
|
261
271
|
"use strict";
|
|
262
272
|
QUEUE_PATH = path6.join(os4.homedir(), ".exe-os", "intercom-queue.json");
|
|
273
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
263
274
|
INTERCOM_LOG = path6.join(os4.homedir(), ".exe-os", "intercom.log");
|
|
264
275
|
}
|
|
265
276
|
});
|
|
@@ -389,12 +400,23 @@ function getProjectName(cwd) {
|
|
|
389
400
|
const dir = cwd ?? process.cwd();
|
|
390
401
|
if (_cached && _cachedCwd === dir) return _cached;
|
|
391
402
|
try {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
403
|
+
let repoRoot;
|
|
404
|
+
try {
|
|
405
|
+
const gitCommonDir = execSync5("git rev-parse --path-format=absolute --git-common-dir", {
|
|
406
|
+
cwd: dir,
|
|
407
|
+
encoding: "utf8",
|
|
408
|
+
timeout: 2e3,
|
|
409
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
410
|
+
}).trim();
|
|
411
|
+
repoRoot = path13.dirname(gitCommonDir);
|
|
412
|
+
} catch {
|
|
413
|
+
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
414
|
+
cwd: dir,
|
|
415
|
+
encoding: "utf8",
|
|
416
|
+
timeout: 2e3,
|
|
417
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
418
|
+
}).trim();
|
|
419
|
+
}
|
|
398
420
|
_cached = path13.basename(repoRoot);
|
|
399
421
|
_cachedCwd = dir;
|
|
400
422
|
return _cached;
|
|
@@ -443,9 +465,15 @@ function registerListTasks(server) {
|
|
|
443
465
|
content: [{ type: "text", text: "No tasks found." }]
|
|
444
466
|
};
|
|
445
467
|
}
|
|
446
|
-
const lines = tasks.map(
|
|
447
|
-
|
|
448
|
-
|
|
468
|
+
const lines = tasks.map((t) => {
|
|
469
|
+
const cpIndicator = t.checkpointCount && t.checkpointCount > 0 ? ` [cp:${t.checkpointCount}]` : "";
|
|
470
|
+
let budgetNote = "";
|
|
471
|
+
if (t.budgetTokens !== null) {
|
|
472
|
+
const pct = Math.round(t.tokensUsed / t.budgetTokens * 100);
|
|
473
|
+
budgetNote = ` [${t.tokensUsed}/${t.budgetTokens} tokens, ${pct}%]`;
|
|
474
|
+
}
|
|
475
|
+
return `- [${t.priority.toUpperCase()}] ${t.title} (${t.projectName}) \u2014 ${t.status}${cpIndicator}${budgetNote} \u2192 ${t.assignedTo}`;
|
|
476
|
+
});
|
|
449
477
|
return {
|
|
450
478
|
content: [
|
|
451
479
|
{
|
|
@@ -323,11 +323,12 @@ function queueIntercom(targetSession, reason) {
|
|
|
323
323
|
}
|
|
324
324
|
writeQueue(queue);
|
|
325
325
|
}
|
|
326
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
326
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
327
327
|
var init_intercom_queue = __esm({
|
|
328
328
|
"src/lib/intercom-queue.ts"() {
|
|
329
329
|
"use strict";
|
|
330
330
|
QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
|
|
331
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
331
332
|
INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
|
|
332
333
|
}
|
|
333
334
|
});
|
|
@@ -419,6 +420,11 @@ var init_config = __esm({
|
|
|
419
420
|
idleKillTicksRequired: 3,
|
|
420
421
|
idleKillIntercomAckWindowMs: 1e4,
|
|
421
422
|
maxAutoInstances: 10
|
|
423
|
+
},
|
|
424
|
+
autoUpdate: {
|
|
425
|
+
checkOnBoot: true,
|
|
426
|
+
autoInstall: false,
|
|
427
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
422
428
|
}
|
|
423
429
|
};
|
|
424
430
|
}
|
|
@@ -556,6 +562,17 @@ function getGitRoot(dir) {
|
|
|
556
562
|
return null;
|
|
557
563
|
}
|
|
558
564
|
}
|
|
565
|
+
function getMainRepoRoot(dir) {
|
|
566
|
+
try {
|
|
567
|
+
const commonDir = execSync4(
|
|
568
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
569
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
570
|
+
).trim();
|
|
571
|
+
return realpath(path7.dirname(commonDir));
|
|
572
|
+
} catch {
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
559
576
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
560
577
|
const label = instanceLabel(employeeName, instance);
|
|
561
578
|
return path7.join(repoRoot, ".worktrees", label);
|
|
@@ -781,6 +798,11 @@ function getSessionState(sessionName) {
|
|
|
781
798
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
782
799
|
try {
|
|
783
800
|
const pane = transport.capturePane(sessionName, 5);
|
|
801
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
802
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
803
|
+
return "no_claude";
|
|
804
|
+
}
|
|
805
|
+
}
|
|
784
806
|
if (/Running…/.test(pane)) return "tool";
|
|
785
807
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
786
808
|
return "idle";
|
|
@@ -788,10 +810,6 @@ function getSessionState(sessionName) {
|
|
|
788
810
|
return "offline";
|
|
789
811
|
}
|
|
790
812
|
}
|
|
791
|
-
function isSessionBusy(sessionName) {
|
|
792
|
-
const state = getSessionState(sessionName);
|
|
793
|
-
return state === "thinking" || state === "tool";
|
|
794
|
-
}
|
|
795
813
|
function isExeSession(sessionName) {
|
|
796
814
|
return /^exe\d*$/.test(sessionName);
|
|
797
815
|
}
|
|
@@ -811,7 +829,14 @@ function sendIntercom(targetSession) {
|
|
|
811
829
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
812
830
|
return "failed";
|
|
813
831
|
}
|
|
814
|
-
|
|
832
|
+
const sessionState = getSessionState(targetSession);
|
|
833
|
+
if (sessionState === "no_claude") {
|
|
834
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
835
|
+
recordDebounce(targetSession);
|
|
836
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
837
|
+
return "queued";
|
|
838
|
+
}
|
|
839
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
815
840
|
queueIntercom(targetSession, "session busy at send time");
|
|
816
841
|
recordDebounce(targetSession);
|
|
817
842
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -823,18 +848,7 @@ function sendIntercom(targetSession) {
|
|
|
823
848
|
}
|
|
824
849
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
825
850
|
recordDebounce(targetSession);
|
|
826
|
-
|
|
827
|
-
try {
|
|
828
|
-
execSync5(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
829
|
-
} catch {
|
|
830
|
-
}
|
|
831
|
-
const state = getSessionState(targetSession);
|
|
832
|
-
if (state === "thinking" || state === "tool") {
|
|
833
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
834
|
-
return "acknowledged";
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
851
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
838
852
|
return "delivered";
|
|
839
853
|
} catch {
|
|
840
854
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -888,7 +902,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
888
902
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
889
903
|
}
|
|
890
904
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
891
|
-
const
|
|
905
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
906
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
892
907
|
if (wtPath) {
|
|
893
908
|
spawnOpts.cwd = wtPath;
|
|
894
909
|
}
|
|
@@ -1069,7 +1084,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1069
1084
|
let booted = false;
|
|
1070
1085
|
for (let i = 0; i < 30; i++) {
|
|
1071
1086
|
try {
|
|
1072
|
-
execSync5("sleep
|
|
1087
|
+
execSync5("sleep 0.5");
|
|
1073
1088
|
} catch {
|
|
1074
1089
|
}
|
|
1075
1090
|
try {
|
|
@@ -1089,7 +1104,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1089
1104
|
}
|
|
1090
1105
|
}
|
|
1091
1106
|
if (!booted) {
|
|
1092
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
1107
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
1093
1108
|
}
|
|
1094
1109
|
if (!useExeAgent) {
|
|
1095
1110
|
try {
|
|
@@ -1107,7 +1122,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
1107
1122
|
});
|
|
1108
1123
|
return { sessionName };
|
|
1109
1124
|
}
|
|
1110
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
1125
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
1111
1126
|
var init_tmux_routing = __esm({
|
|
1112
1127
|
"src/lib/tmux-routing.ts"() {
|
|
1113
1128
|
"use strict";
|
|
@@ -1127,8 +1142,6 @@ var init_tmux_routing = __esm({
|
|
|
1127
1142
|
DEBOUNCE_FILE = path8.join(SESSION_CACHE, "intercom-debounce.json");
|
|
1128
1143
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
1129
1144
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
1130
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
1131
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
1132
1145
|
}
|
|
1133
1146
|
});
|
|
1134
1147
|
|