@askexenow/exe-os 0.8.41 → 0.8.43
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/dist/bin/backfill-conversations.js +805 -642
- package/dist/bin/backfill-responses.js +804 -641
- package/dist/bin/backfill-vectors.js +791 -634
- package/dist/bin/cleanup-stale-review-tasks.js +788 -631
- package/dist/bin/cli.js +1345 -660
- package/dist/bin/exe-agent.js +20 -1
- package/dist/bin/exe-assign.js +1503 -1343
- package/dist/bin/exe-boot.js +2518 -1798
- package/dist/bin/exe-call.js +39 -1
- package/dist/bin/exe-cloud.js +15 -1
- package/dist/bin/exe-dispatch.js +39 -2
- package/dist/bin/exe-doctor.js +790 -633
- package/dist/bin/exe-export-behaviors.js +792 -637
- package/dist/bin/exe-forget.js +145 -0
- package/dist/bin/exe-gateway.js +2500 -1877
- package/dist/bin/exe-heartbeat.js +147 -1
- package/dist/bin/exe-kill.js +795 -640
- package/dist/bin/exe-launch-agent.js +2168 -2008
- package/dist/bin/exe-link.js +28 -2
- package/dist/bin/exe-new-employee.js +25 -3
- package/dist/bin/exe-pending-messages.js +146 -1
- package/dist/bin/exe-pending-notifications.js +788 -631
- package/dist/bin/exe-pending-reviews.js +147 -1
- package/dist/bin/exe-rename.js +23 -0
- package/dist/bin/exe-review.js +490 -327
- package/dist/bin/exe-search.js +154 -3
- package/dist/bin/exe-session-cleanup.js +2466 -413
- package/dist/bin/exe-status.js +474 -317
- package/dist/bin/exe-team.js +474 -317
- package/dist/bin/git-sweep.js +2690 -150
- package/dist/bin/graph-backfill.js +794 -637
- package/dist/bin/graph-export.js +798 -641
- package/dist/bin/scan-tasks.js +2951 -44
- package/dist/bin/setup.js +62 -26
- package/dist/bin/shard-migrate.js +792 -637
- package/dist/bin/wiki-sync.js +794 -637
- package/dist/gateway/index.js +2504 -1895
- package/dist/hooks/bug-report-worker.js +2118 -576
- package/dist/hooks/commit-complete.js +2689 -149
- package/dist/hooks/error-recall.js +154 -3
- package/dist/hooks/ingest-worker.js +1439 -815
- package/dist/hooks/instructions-loaded.js +151 -0
- package/dist/hooks/notification.js +153 -2
- package/dist/hooks/post-compact.js +164 -0
- package/dist/hooks/pre-compact.js +3073 -101
- package/dist/hooks/pre-tool-use.js +151 -0
- package/dist/hooks/prompt-ingest-worker.js +1714 -1537
- package/dist/hooks/prompt-submit.js +2658 -1113
- package/dist/hooks/response-ingest-worker.js +170 -6
- package/dist/hooks/session-end.js +153 -2
- package/dist/hooks/session-start.js +154 -3
- package/dist/hooks/stop.js +151 -0
- package/dist/hooks/subagent-stop.js +151 -0
- package/dist/hooks/summary-worker.js +179 -7
- package/dist/index.js +278 -100
- package/dist/lib/cloud-sync.js +28 -2
- package/dist/lib/consolidation.js +69 -2
- package/dist/lib/database.js +19 -0
- package/dist/lib/device-registry.js +19 -0
- package/dist/lib/employee-templates.js +20 -1
- package/dist/lib/exe-daemon.js +236 -16
- package/dist/lib/hybrid-search.js +154 -3
- package/dist/lib/license.js +15 -1
- package/dist/lib/messaging.js +39 -2
- package/dist/lib/schedules.js +792 -637
- package/dist/lib/store.js +796 -636
- package/dist/lib/tasks.js +1614 -1091
- package/dist/lib/tmux-routing.js +149 -9
- package/dist/mcp/server.js +1825 -1138
- package/dist/mcp/tools/create-task.js +2280 -828
- package/dist/mcp/tools/list-tasks.js +2788 -159
- package/dist/mcp/tools/send-message.js +39 -2
- package/dist/mcp/tools/update-task.js +64 -0
- package/dist/runtime/index.js +235 -67
- package/dist/tui/App.js +1452 -644
- package/package.json +3 -2
package/dist/mcp/server.js
CHANGED
|
@@ -987,6 +987,13 @@ async function ensureSchema() {
|
|
|
987
987
|
});
|
|
988
988
|
} catch {
|
|
989
989
|
}
|
|
990
|
+
try {
|
|
991
|
+
await client.execute({
|
|
992
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
993
|
+
args: []
|
|
994
|
+
});
|
|
995
|
+
} catch {
|
|
996
|
+
}
|
|
990
997
|
try {
|
|
991
998
|
await client.execute({
|
|
992
999
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -1433,6 +1440,18 @@ async function ensureSchema() {
|
|
|
1433
1440
|
CREATE INDEX IF NOT EXISTS idx_session_kills_agent
|
|
1434
1441
|
ON session_kills(agent_id);
|
|
1435
1442
|
`);
|
|
1443
|
+
await client.execute(`
|
|
1444
|
+
CREATE TABLE IF NOT EXISTS global_procedures (
|
|
1445
|
+
id TEXT PRIMARY KEY,
|
|
1446
|
+
title TEXT NOT NULL,
|
|
1447
|
+
content TEXT NOT NULL,
|
|
1448
|
+
priority TEXT NOT NULL DEFAULT 'p0',
|
|
1449
|
+
domain TEXT,
|
|
1450
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1451
|
+
created_at TEXT NOT NULL,
|
|
1452
|
+
updated_at TEXT NOT NULL
|
|
1453
|
+
)
|
|
1454
|
+
`);
|
|
1436
1455
|
await client.executeMultiple(`
|
|
1437
1456
|
CREATE TABLE IF NOT EXISTS conversations (
|
|
1438
1457
|
id TEXT PRIMARY KEY,
|
|
@@ -1640,6 +1659,61 @@ var init_keychain = __esm({
|
|
|
1640
1659
|
}
|
|
1641
1660
|
});
|
|
1642
1661
|
|
|
1662
|
+
// src/lib/state-bus.ts
|
|
1663
|
+
var StateBus, orgBus;
|
|
1664
|
+
var init_state_bus = __esm({
|
|
1665
|
+
"src/lib/state-bus.ts"() {
|
|
1666
|
+
"use strict";
|
|
1667
|
+
StateBus = class {
|
|
1668
|
+
handlers = /* @__PURE__ */ new Map();
|
|
1669
|
+
globalHandlers = /* @__PURE__ */ new Set();
|
|
1670
|
+
/** Emit an event to all subscribers */
|
|
1671
|
+
emit(event) {
|
|
1672
|
+
const typeHandlers = this.handlers.get(event.type);
|
|
1673
|
+
if (typeHandlers) {
|
|
1674
|
+
for (const handler of typeHandlers) {
|
|
1675
|
+
try {
|
|
1676
|
+
handler(event);
|
|
1677
|
+
} catch {
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
for (const handler of this.globalHandlers) {
|
|
1682
|
+
try {
|
|
1683
|
+
handler(event);
|
|
1684
|
+
} catch {
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
/** Subscribe to a specific event type */
|
|
1689
|
+
on(type, handler) {
|
|
1690
|
+
if (!this.handlers.has(type)) {
|
|
1691
|
+
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
1692
|
+
}
|
|
1693
|
+
this.handlers.get(type).add(handler);
|
|
1694
|
+
}
|
|
1695
|
+
/** Subscribe to ALL events */
|
|
1696
|
+
onAny(handler) {
|
|
1697
|
+
this.globalHandlers.add(handler);
|
|
1698
|
+
}
|
|
1699
|
+
/** Unsubscribe from a specific event type */
|
|
1700
|
+
off(type, handler) {
|
|
1701
|
+
this.handlers.get(type)?.delete(handler);
|
|
1702
|
+
}
|
|
1703
|
+
/** Unsubscribe from ALL events */
|
|
1704
|
+
offAny(handler) {
|
|
1705
|
+
this.globalHandlers.delete(handler);
|
|
1706
|
+
}
|
|
1707
|
+
/** Remove all listeners */
|
|
1708
|
+
clear() {
|
|
1709
|
+
this.handlers.clear();
|
|
1710
|
+
this.globalHandlers.clear();
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
orgBus = new StateBus();
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
|
|
1643
1717
|
// src/lib/shard-manager.ts
|
|
1644
1718
|
var shard_manager_exports = {};
|
|
1645
1719
|
__export(shard_manager_exports, {
|
|
@@ -1881,6 +1955,71 @@ var init_shard_manager = __esm({
|
|
|
1881
1955
|
}
|
|
1882
1956
|
});
|
|
1883
1957
|
|
|
1958
|
+
// src/lib/global-procedures.ts
|
|
1959
|
+
var global_procedures_exports = {};
|
|
1960
|
+
__export(global_procedures_exports, {
|
|
1961
|
+
deactivateGlobalProcedure: () => deactivateGlobalProcedure,
|
|
1962
|
+
getGlobalProceduresBlock: () => getGlobalProceduresBlock,
|
|
1963
|
+
loadGlobalProcedures: () => loadGlobalProcedures,
|
|
1964
|
+
storeGlobalProcedure: () => storeGlobalProcedure
|
|
1965
|
+
});
|
|
1966
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1967
|
+
async function loadGlobalProcedures() {
|
|
1968
|
+
const client = getClient();
|
|
1969
|
+
const result = await client.execute({
|
|
1970
|
+
sql: "SELECT * FROM global_procedures WHERE active = 1 ORDER BY priority ASC, created_at ASC",
|
|
1971
|
+
args: []
|
|
1972
|
+
});
|
|
1973
|
+
const procedures = result.rows;
|
|
1974
|
+
if (procedures.length > 0) {
|
|
1975
|
+
_cache = procedures.map((p) => `### ${p.title}
|
|
1976
|
+
${p.content}`).join("\n\n");
|
|
1977
|
+
} else {
|
|
1978
|
+
_cache = "";
|
|
1979
|
+
}
|
|
1980
|
+
_cacheLoaded = true;
|
|
1981
|
+
return procedures;
|
|
1982
|
+
}
|
|
1983
|
+
function getGlobalProceduresBlock() {
|
|
1984
|
+
if (!_cacheLoaded) return "";
|
|
1985
|
+
if (!_cache) return "";
|
|
1986
|
+
return `## Organization-Wide Procedures (MANDATORY \u2014 supersedes all other rules)
|
|
1987
|
+
|
|
1988
|
+
${_cache}
|
|
1989
|
+
`;
|
|
1990
|
+
}
|
|
1991
|
+
async function storeGlobalProcedure(input) {
|
|
1992
|
+
const id = randomUUID2();
|
|
1993
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1994
|
+
const client = getClient();
|
|
1995
|
+
await client.execute({
|
|
1996
|
+
sql: `INSERT INTO global_procedures (id, title, content, priority, domain, active, created_at, updated_at)
|
|
1997
|
+
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
1998
|
+
args: [id, input.title, input.content, input.priority ?? "p0", input.domain ?? null, now, now]
|
|
1999
|
+
});
|
|
2000
|
+
await loadGlobalProcedures();
|
|
2001
|
+
return id;
|
|
2002
|
+
}
|
|
2003
|
+
async function deactivateGlobalProcedure(id) {
|
|
2004
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2005
|
+
const client = getClient();
|
|
2006
|
+
const result = await client.execute({
|
|
2007
|
+
sql: "UPDATE global_procedures SET active = 0, updated_at = ? WHERE id = ?",
|
|
2008
|
+
args: [now, id]
|
|
2009
|
+
});
|
|
2010
|
+
await loadGlobalProcedures();
|
|
2011
|
+
return result.rowsAffected > 0;
|
|
2012
|
+
}
|
|
2013
|
+
var _cache, _cacheLoaded;
|
|
2014
|
+
var init_global_procedures = __esm({
|
|
2015
|
+
"src/lib/global-procedures.ts"() {
|
|
2016
|
+
"use strict";
|
|
2017
|
+
init_database();
|
|
2018
|
+
_cache = "";
|
|
2019
|
+
_cacheLoaded = false;
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
|
|
1884
2023
|
// src/lib/store.ts
|
|
1885
2024
|
var store_exports = {};
|
|
1886
2025
|
__export(store_exports, {
|
|
@@ -1960,6 +2099,11 @@ async function initStore(options) {
|
|
|
1960
2099
|
"version-query"
|
|
1961
2100
|
);
|
|
1962
2101
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
2102
|
+
try {
|
|
2103
|
+
const { loadGlobalProcedures: loadGlobalProcedures2 } = await Promise.resolve().then(() => (init_global_procedures(), global_procedures_exports));
|
|
2104
|
+
await loadGlobalProcedures2();
|
|
2105
|
+
} catch {
|
|
2106
|
+
}
|
|
1963
2107
|
}
|
|
1964
2108
|
function classifyTier(record) {
|
|
1965
2109
|
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
@@ -2001,6 +2145,12 @@ async function writeMemory(record) {
|
|
|
2001
2145
|
supersedes_id: record.supersedes_id ?? null
|
|
2002
2146
|
};
|
|
2003
2147
|
_pendingRecords.push(dbRow);
|
|
2148
|
+
orgBus.emit({
|
|
2149
|
+
type: "memory_stored",
|
|
2150
|
+
agentId: record.agent_id,
|
|
2151
|
+
project: record.project_name,
|
|
2152
|
+
timestamp: record.timestamp
|
|
2153
|
+
});
|
|
2004
2154
|
const MAX_PENDING = 1e3;
|
|
2005
2155
|
if (_pendingRecords.length > MAX_PENDING) {
|
|
2006
2156
|
const dropped = _pendingRecords.length - MAX_PENDING;
|
|
@@ -2346,6 +2496,7 @@ var init_store = __esm({
|
|
|
2346
2496
|
init_database();
|
|
2347
2497
|
init_keychain();
|
|
2348
2498
|
init_config();
|
|
2499
|
+
init_state_bus();
|
|
2349
2500
|
INIT_MAX_RETRIES = 3;
|
|
2350
2501
|
INIT_RETRY_DELAY_MS = 1e3;
|
|
2351
2502
|
_pendingRecords = [];
|
|
@@ -3162,7 +3313,7 @@ __export(license_exports, {
|
|
|
3162
3313
|
validateLicense: () => validateLicense
|
|
3163
3314
|
});
|
|
3164
3315
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
3165
|
-
import { randomUUID as
|
|
3316
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
3166
3317
|
import path10 from "path";
|
|
3167
3318
|
import { jwtVerify, importSPKI } from "jose";
|
|
3168
3319
|
async function fetchRetry(url, init) {
|
|
@@ -3189,7 +3340,7 @@ function loadDeviceId() {
|
|
|
3189
3340
|
}
|
|
3190
3341
|
} catch {
|
|
3191
3342
|
}
|
|
3192
|
-
const id =
|
|
3343
|
+
const id = randomUUID3();
|
|
3193
3344
|
mkdirSync3(EXE_AI_DIR, { recursive: true });
|
|
3194
3345
|
writeFileSync2(DEVICE_ID_PATH, id, "utf8");
|
|
3195
3346
|
return id;
|
|
@@ -3304,7 +3455,21 @@ function getCacheAgeMs() {
|
|
|
3304
3455
|
}
|
|
3305
3456
|
}
|
|
3306
3457
|
async function checkLicense() {
|
|
3307
|
-
|
|
3458
|
+
let key = loadLicense();
|
|
3459
|
+
if (!key) {
|
|
3460
|
+
try {
|
|
3461
|
+
const configPath = path10.join(EXE_AI_DIR, "config.json");
|
|
3462
|
+
if (existsSync8(configPath)) {
|
|
3463
|
+
const raw = JSON.parse(readFileSync6(configPath, "utf8"));
|
|
3464
|
+
const cloud = raw.cloud;
|
|
3465
|
+
if (cloud?.apiKey) {
|
|
3466
|
+
key = cloud.apiKey;
|
|
3467
|
+
saveLicense(key);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
} catch {
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3308
3473
|
if (!key) return FREE_LICENSE;
|
|
3309
3474
|
const cached = await getCachedLicense();
|
|
3310
3475
|
if (cached && getCacheAgeMs() < CACHE_MAX_AGE_MS) return cached;
|
|
@@ -3662,1207 +3827,1646 @@ var init_notifications = __esm({
|
|
|
3662
3827
|
}
|
|
3663
3828
|
});
|
|
3664
3829
|
|
|
3665
|
-
// src/lib/
|
|
3666
|
-
import
|
|
3830
|
+
// src/lib/session-registry.ts
|
|
3831
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync11 } from "fs";
|
|
3667
3832
|
import path15 from "path";
|
|
3668
|
-
import
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
const row = await resolveTask(client, input.taskId);
|
|
3674
|
-
const taskId = String(row.id);
|
|
3675
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3676
|
-
const blockedByIds = [];
|
|
3677
|
-
if (row.blocked_by) {
|
|
3678
|
-
blockedByIds.push(String(row.blocked_by));
|
|
3833
|
+
import os4 from "os";
|
|
3834
|
+
function registerSession(entry) {
|
|
3835
|
+
const dir = path15.dirname(REGISTRY_PATH);
|
|
3836
|
+
if (!existsSync11(dir)) {
|
|
3837
|
+
mkdirSync4(dir, { recursive: true });
|
|
3679
3838
|
}
|
|
3680
|
-
const
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
};
|
|
3687
|
-
const result = await client.execute({
|
|
3688
|
-
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
3689
|
-
args: [JSON.stringify(checkpoint), now, taskId]
|
|
3690
|
-
});
|
|
3691
|
-
if (result.rowsAffected === 0) {
|
|
3692
|
-
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
3839
|
+
const sessions = listSessions();
|
|
3840
|
+
const idx = sessions.findIndex((s) => s.windowName === entry.windowName);
|
|
3841
|
+
if (idx >= 0) {
|
|
3842
|
+
sessions[idx] = entry;
|
|
3843
|
+
} else {
|
|
3844
|
+
sessions.push(entry);
|
|
3693
3845
|
}
|
|
3694
|
-
|
|
3695
|
-
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
3696
|
-
args: [taskId]
|
|
3697
|
-
});
|
|
3698
|
-
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
3699
|
-
return { checkpointCount };
|
|
3700
|
-
}
|
|
3701
|
-
function extractParentFromContext(contextBody) {
|
|
3702
|
-
if (!contextBody) return null;
|
|
3703
|
-
const match = contextBody.match(
|
|
3704
|
-
/Parent task:\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
|
|
3705
|
-
);
|
|
3706
|
-
return match ? match[1].toLowerCase() : null;
|
|
3707
|
-
}
|
|
3708
|
-
function slugify(title) {
|
|
3709
|
-
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
3846
|
+
writeFileSync5(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
3710
3847
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
}
|
|
3716
|
-
|
|
3717
|
-
result = await client.execute({
|
|
3718
|
-
sql: "SELECT * FROM tasks WHERE task_file LIKE ?",
|
|
3719
|
-
args: [`%${identifier}%`]
|
|
3720
|
-
});
|
|
3721
|
-
if (result.rows.length === 1) return result.rows[0];
|
|
3722
|
-
if (result.rows.length > 1) {
|
|
3723
|
-
const exact = result.rows.filter(
|
|
3724
|
-
(r) => String(r.task_file).endsWith(`/${identifier}.md`)
|
|
3725
|
-
);
|
|
3726
|
-
if (exact.length === 1) return exact[0];
|
|
3727
|
-
const candidates = exact.length > 1 ? exact : result.rows;
|
|
3728
|
-
const active = candidates.filter(
|
|
3729
|
-
(r) => !["done", "cancelled"].includes(String(r.status))
|
|
3730
|
-
);
|
|
3731
|
-
if (active.length === 1) return active[0];
|
|
3732
|
-
const matches = (active.length > 1 ? active : candidates).map((r) => `${String(r.task_file)} (${String(r.status)}, ${String(r.id)})`).join(", ");
|
|
3733
|
-
throw new Error(
|
|
3734
|
-
`Multiple tasks match "${identifier}": ${matches}. Use a UUID to disambiguate.`
|
|
3735
|
-
);
|
|
3736
|
-
}
|
|
3737
|
-
result = await client.execute({
|
|
3738
|
-
sql: "SELECT * FROM tasks WHERE title LIKE ?",
|
|
3739
|
-
args: [`%${identifier}%`]
|
|
3740
|
-
});
|
|
3741
|
-
if (result.rows.length === 1) return result.rows[0];
|
|
3742
|
-
if (result.rows.length > 1) {
|
|
3743
|
-
const active = result.rows.filter(
|
|
3744
|
-
(r) => !["done", "cancelled"].includes(String(r.status))
|
|
3745
|
-
);
|
|
3746
|
-
if (active.length === 1) return active[0];
|
|
3747
|
-
const matches = (active.length > 1 ? active : result.rows).map((r) => `"${String(r.title)}" (${String(r.status)}, ${String(r.id)})`).join(", ");
|
|
3748
|
-
throw new Error(
|
|
3749
|
-
`Multiple tasks match "${identifier}": ${matches}. Use a UUID to disambiguate.`
|
|
3750
|
-
);
|
|
3848
|
+
function listSessions() {
|
|
3849
|
+
try {
|
|
3850
|
+
const raw = readFileSync9(REGISTRY_PATH, "utf8");
|
|
3851
|
+
return JSON.parse(raw);
|
|
3852
|
+
} catch {
|
|
3853
|
+
return [];
|
|
3751
3854
|
}
|
|
3752
|
-
throw new Error(`Task not found: ${identifier}`);
|
|
3753
3855
|
}
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
|
|
3760
|
-
let blockedById = null;
|
|
3761
|
-
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
3762
|
-
if (input.blockedBy) {
|
|
3763
|
-
const blocker = await resolveTask(client, input.blockedBy);
|
|
3764
|
-
blockedById = String(blocker.id);
|
|
3765
|
-
}
|
|
3766
|
-
let parentTaskId = null;
|
|
3767
|
-
let parentRef = input.parentTaskId;
|
|
3768
|
-
if (!parentRef) {
|
|
3769
|
-
const extracted = extractParentFromContext(input.context);
|
|
3770
|
-
if (extracted) {
|
|
3771
|
-
parentRef = extracted;
|
|
3772
|
-
process.stderr.write(
|
|
3773
|
-
"[create_task] auto-populated parent_task_id from context body \u2014 dispatchers should pass parent_task_id explicitly\n"
|
|
3774
|
-
);
|
|
3775
|
-
}
|
|
3856
|
+
var REGISTRY_PATH;
|
|
3857
|
+
var init_session_registry = __esm({
|
|
3858
|
+
"src/lib/session-registry.ts"() {
|
|
3859
|
+
"use strict";
|
|
3860
|
+
REGISTRY_PATH = path15.join(os4.homedir(), ".exe-os", "session-registry.json");
|
|
3776
3861
|
}
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3862
|
+
});
|
|
3863
|
+
|
|
3864
|
+
// src/lib/tmux-transport.ts
|
|
3865
|
+
var tmux_transport_exports = {};
|
|
3866
|
+
__export(tmux_transport_exports, {
|
|
3867
|
+
TmuxTransport: () => TmuxTransport
|
|
3868
|
+
});
|
|
3869
|
+
import { execFileSync } from "child_process";
|
|
3870
|
+
var QUIET, TmuxTransport;
|
|
3871
|
+
var init_tmux_transport = __esm({
|
|
3872
|
+
"src/lib/tmux-transport.ts"() {
|
|
3873
|
+
"use strict";
|
|
3874
|
+
QUIET = {
|
|
3875
|
+
encoding: "utf8",
|
|
3876
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3877
|
+
};
|
|
3878
|
+
TmuxTransport = class {
|
|
3879
|
+
getMySession() {
|
|
3880
|
+
try {
|
|
3881
|
+
return execFileSync("tmux", ["display-message", "-p", "#{session_name}"], QUIET).trim() || null;
|
|
3882
|
+
} catch {
|
|
3883
|
+
return null;
|
|
3884
|
+
}
|
|
3786
3885
|
}
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
}
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
const conditions = [];
|
|
3854
|
-
const args = [];
|
|
3855
|
-
if (input.assignedTo) {
|
|
3856
|
-
conditions.push("assigned_to = ?");
|
|
3857
|
-
args.push(input.assignedTo);
|
|
3858
|
-
}
|
|
3859
|
-
if (input.status) {
|
|
3860
|
-
conditions.push("status = ?");
|
|
3861
|
-
args.push(input.status);
|
|
3862
|
-
} else {
|
|
3863
|
-
conditions.push("status IN ('open', 'in_progress', 'blocked')");
|
|
3886
|
+
listSessions() {
|
|
3887
|
+
try {
|
|
3888
|
+
return execFileSync("tmux", ["list-sessions", "-F", "#{session_name}"], QUIET).trim().split("\n").filter(Boolean);
|
|
3889
|
+
} catch {
|
|
3890
|
+
return [];
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
isAlive(target) {
|
|
3894
|
+
try {
|
|
3895
|
+
const sessions = this.listSessions();
|
|
3896
|
+
if (!sessions.includes(target)) return false;
|
|
3897
|
+
const paneStatus = execFileSync(
|
|
3898
|
+
"tmux",
|
|
3899
|
+
["list-panes", "-t", target, "-F", "#{pane_dead}"],
|
|
3900
|
+
QUIET
|
|
3901
|
+
).trim();
|
|
3902
|
+
return paneStatus !== "1";
|
|
3903
|
+
} catch {
|
|
3904
|
+
return false;
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
sendKeys(target, keys) {
|
|
3908
|
+
execFileSync("tmux", ["send-keys", "-t", target, keys, "Enter"], QUIET);
|
|
3909
|
+
}
|
|
3910
|
+
capturePane(target, lines) {
|
|
3911
|
+
const args = ["capture-pane", "-t", target, "-p"];
|
|
3912
|
+
if (lines) args.push("-S", `-${lines}`);
|
|
3913
|
+
return execFileSync("tmux", args, { ...QUIET, timeout: 3e3 });
|
|
3914
|
+
}
|
|
3915
|
+
isPaneInCopyMode(target) {
|
|
3916
|
+
try {
|
|
3917
|
+
const result = execFileSync(
|
|
3918
|
+
"tmux",
|
|
3919
|
+
["display-message", "-p", "-t", target, "#{pane_in_mode}"],
|
|
3920
|
+
{ ...QUIET, timeout: 3e3 }
|
|
3921
|
+
).trim();
|
|
3922
|
+
return result === "1";
|
|
3923
|
+
} catch {
|
|
3924
|
+
return false;
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
spawn(name, config2) {
|
|
3928
|
+
try {
|
|
3929
|
+
const args = ["new-session", "-d", "-s", name];
|
|
3930
|
+
if (config2.cwd) args.push("-c", config2.cwd);
|
|
3931
|
+
args.push(config2.command);
|
|
3932
|
+
execFileSync("tmux", args);
|
|
3933
|
+
return { sessionName: name };
|
|
3934
|
+
} catch (e) {
|
|
3935
|
+
return { sessionName: name, error: `spawn failed: ${e}` };
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
kill(target) {
|
|
3939
|
+
try {
|
|
3940
|
+
execFileSync("tmux", ["kill-session", "-t", target], QUIET);
|
|
3941
|
+
} catch {
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
pipeLog(target, logFile) {
|
|
3945
|
+
try {
|
|
3946
|
+
const safePath = logFile.replace(/'/g, "'\\''");
|
|
3947
|
+
execFileSync("tmux", ["pipe-pane", "-t", target, `cat >> '${safePath}'`], QUIET);
|
|
3948
|
+
} catch {
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3864
3952
|
}
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3953
|
+
});
|
|
3954
|
+
|
|
3955
|
+
// src/lib/transport.ts
|
|
3956
|
+
function getTransport() {
|
|
3957
|
+
if (!_transport) {
|
|
3958
|
+
const { TmuxTransport: TmuxTransport2 } = (init_tmux_transport(), __toCommonJS(tmux_transport_exports));
|
|
3959
|
+
_transport = new TmuxTransport2();
|
|
3868
3960
|
}
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3961
|
+
return _transport;
|
|
3962
|
+
}
|
|
3963
|
+
var _transport;
|
|
3964
|
+
var init_transport = __esm({
|
|
3965
|
+
"src/lib/transport.ts"() {
|
|
3966
|
+
"use strict";
|
|
3967
|
+
_transport = null;
|
|
3872
3968
|
}
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
id: String(r.id),
|
|
3880
|
-
title: String(r.title),
|
|
3881
|
-
assignedTo: String(r.assigned_to),
|
|
3882
|
-
assignedBy: String(r.assigned_by),
|
|
3883
|
-
projectName: String(r.project_name),
|
|
3884
|
-
priority: String(r.priority),
|
|
3885
|
-
status: String(r.status),
|
|
3886
|
-
taskFile: String(r.task_file),
|
|
3887
|
-
createdAt: String(r.created_at),
|
|
3888
|
-
updatedAt: String(r.updated_at),
|
|
3889
|
-
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
3890
|
-
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
3891
|
-
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
3892
|
-
tokensUsed: Number(r.tokens_used ?? 0),
|
|
3893
|
-
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
3894
|
-
}));
|
|
3969
|
+
});
|
|
3970
|
+
|
|
3971
|
+
// src/lib/cc-agent-support.ts
|
|
3972
|
+
import { execSync as execSync6 } from "child_process";
|
|
3973
|
+
function _resetCcAgentSupportCache() {
|
|
3974
|
+
_cachedSupport = null;
|
|
3895
3975
|
}
|
|
3896
|
-
function
|
|
3897
|
-
if (
|
|
3898
|
-
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
3976
|
+
function claudeSupportsAgentFlag() {
|
|
3977
|
+
if (_cachedSupport !== null) return _cachedSupport;
|
|
3899
3978
|
try {
|
|
3900
|
-
const
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
)
|
|
3905
|
-
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
3906
|
-
const commitCount = execSync6(
|
|
3907
|
-
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
3908
|
-
{ encoding: "utf8", timeout: 5e3 }
|
|
3909
|
-
).trim();
|
|
3910
|
-
const count = parseInt(commitCount, 10);
|
|
3911
|
-
if (count === 0) {
|
|
3912
|
-
return "WARNING: task closed with no new commits since creation. Verify work was actually produced.";
|
|
3913
|
-
}
|
|
3914
|
-
return null;
|
|
3979
|
+
const helpOutput = execSync6("claude --help 2>&1", {
|
|
3980
|
+
encoding: "utf-8",
|
|
3981
|
+
timeout: 5e3
|
|
3982
|
+
});
|
|
3983
|
+
_cachedSupport = /(^|\s)--agent(\b|=)/.test(helpOutput);
|
|
3915
3984
|
} catch {
|
|
3916
|
-
|
|
3985
|
+
_cachedSupport = false;
|
|
3917
3986
|
}
|
|
3987
|
+
return _cachedSupport;
|
|
3918
3988
|
}
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
const taskFile = String(row.task_file);
|
|
3925
|
-
if (input.status === "done" && String(row.assigned_by) === "system" && taskFile.includes("review-")) {
|
|
3926
|
-
process.stderr.write(
|
|
3927
|
-
`[updateTask] Review task "${String(row.title)}" being marked done (assigned to ${String(row.assigned_to)})
|
|
3928
|
-
`
|
|
3929
|
-
);
|
|
3989
|
+
var _cachedSupport;
|
|
3990
|
+
var init_cc_agent_support = __esm({
|
|
3991
|
+
"src/lib/cc-agent-support.ts"() {
|
|
3992
|
+
"use strict";
|
|
3993
|
+
_cachedSupport = null;
|
|
3930
3994
|
}
|
|
3931
|
-
|
|
3932
|
-
const existingRow = await client.execute({
|
|
3933
|
-
sql: "SELECT context, created_at FROM tasks WHERE id = ?",
|
|
3934
|
-
args: [taskId]
|
|
3935
|
-
});
|
|
3936
|
-
if (existingRow.rows.length > 0) {
|
|
3937
|
-
const ctx = existingRow.rows[0];
|
|
3938
|
-
const warning = checkStaleCompletion(ctx.context, ctx.created_at);
|
|
3939
|
-
if (warning) {
|
|
3940
|
-
input.result = input.result ? `\u26A0\uFE0F ${warning}
|
|
3995
|
+
});
|
|
3941
3996
|
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3997
|
+
// src/lib/mcp-prefix.ts
|
|
3998
|
+
function expandDualPrefixTools(shortNames) {
|
|
3999
|
+
const out = [];
|
|
4000
|
+
for (const name of shortNames) {
|
|
4001
|
+
for (const prefix of MCP_TOOL_PREFIXES) {
|
|
4002
|
+
out.push(prefix + name);
|
|
3946
4003
|
}
|
|
3947
4004
|
}
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
});
|
|
3961
|
-
const cur = current.rows[0];
|
|
3962
|
-
const status = cur?.status ?? "unknown";
|
|
3963
|
-
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
3964
|
-
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
3965
|
-
}
|
|
3966
|
-
try {
|
|
3967
|
-
await writeCheckpoint({
|
|
3968
|
-
taskId,
|
|
3969
|
-
step: "claimed",
|
|
3970
|
-
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
3971
|
-
});
|
|
3972
|
-
} catch {
|
|
3973
|
-
}
|
|
3974
|
-
return { row, taskFile, now, taskId };
|
|
4005
|
+
return out;
|
|
4006
|
+
}
|
|
4007
|
+
var MCP_PRIMARY_KEY, MCP_LEGACY_KEY, MCP_TOOL_PREFIXES;
|
|
4008
|
+
var init_mcp_prefix = __esm({
|
|
4009
|
+
"src/lib/mcp-prefix.ts"() {
|
|
4010
|
+
"use strict";
|
|
4011
|
+
MCP_PRIMARY_KEY = "exe-os";
|
|
4012
|
+
MCP_LEGACY_KEY = "exe-mem";
|
|
4013
|
+
MCP_TOOL_PREFIXES = [
|
|
4014
|
+
`mcp__${MCP_PRIMARY_KEY}__`,
|
|
4015
|
+
`mcp__${MCP_LEGACY_KEY}__`
|
|
4016
|
+
];
|
|
3975
4017
|
}
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
4018
|
+
});
|
|
4019
|
+
|
|
4020
|
+
// src/lib/provider-table.ts
|
|
4021
|
+
function detectActiveProvider(env = process.env) {
|
|
4022
|
+
const baseUrl = env.ANTHROPIC_BASE_URL;
|
|
4023
|
+
if (!baseUrl) return DEFAULT_PROVIDER;
|
|
4024
|
+
for (const [name, cfg] of Object.entries(PROVIDER_TABLE)) {
|
|
4025
|
+
if (cfg.baseUrl === baseUrl) return name;
|
|
4026
|
+
}
|
|
4027
|
+
return DEFAULT_PROVIDER;
|
|
4028
|
+
}
|
|
4029
|
+
var PROVIDER_TABLE, DEFAULT_PROVIDER;
|
|
4030
|
+
var init_provider_table = __esm({
|
|
4031
|
+
"src/lib/provider-table.ts"() {
|
|
4032
|
+
"use strict";
|
|
4033
|
+
PROVIDER_TABLE = {
|
|
4034
|
+
opencode: {
|
|
4035
|
+
baseUrl: "https://opencode.ai/zen/go",
|
|
4036
|
+
apiKeyEnv: "OPENCODE_API_KEY",
|
|
4037
|
+
defaultModel: "minimax-m2.7"
|
|
4038
|
+
}
|
|
4039
|
+
};
|
|
4040
|
+
DEFAULT_PROVIDER = "default";
|
|
3986
4041
|
}
|
|
4042
|
+
});
|
|
4043
|
+
|
|
4044
|
+
// src/lib/intercom-queue.ts
|
|
4045
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync12, mkdirSync as mkdirSync5 } from "fs";
|
|
4046
|
+
import path16 from "path";
|
|
4047
|
+
import os5 from "os";
|
|
4048
|
+
function ensureDir() {
|
|
4049
|
+
const dir = path16.dirname(QUEUE_PATH);
|
|
4050
|
+
if (!existsSync12(dir)) mkdirSync5(dir, { recursive: true });
|
|
4051
|
+
}
|
|
4052
|
+
function readQueue() {
|
|
3987
4053
|
try {
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
step: `status_transition:${input.status}`,
|
|
3991
|
-
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
3992
|
-
});
|
|
4054
|
+
if (!existsSync12(QUEUE_PATH)) return [];
|
|
4055
|
+
return JSON.parse(readFileSync10(QUEUE_PATH, "utf8"));
|
|
3993
4056
|
} catch {
|
|
4057
|
+
return [];
|
|
3994
4058
|
}
|
|
3995
|
-
return { row, taskFile, now, taskId };
|
|
3996
4059
|
}
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
const
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
const assignedTo = String(row.assigned_to);
|
|
4003
|
-
const assignedBy = String(row.assigned_by);
|
|
4004
|
-
await client.execute({ sql: "DELETE FROM tasks WHERE id = ?", args: [id] });
|
|
4005
|
-
const taskSlug = taskFile.split("/").pop()?.replace(".md", "") ?? "";
|
|
4006
|
-
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
4060
|
+
function writeQueue(queue) {
|
|
4061
|
+
ensureDir();
|
|
4062
|
+
const tmp = `${QUEUE_PATH}.tmp`;
|
|
4063
|
+
writeFileSync6(tmp, JSON.stringify(queue, null, 2));
|
|
4064
|
+
renameSync2(tmp, QUEUE_PATH);
|
|
4007
4065
|
}
|
|
4008
|
-
|
|
4009
|
-
const
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
"## Key Components",
|
|
4023
|
-
"",
|
|
4024
|
-
"<!-- List the major modules, services, or subsystems. -->",
|
|
4025
|
-
"",
|
|
4026
|
-
"## Data Flow",
|
|
4027
|
-
"",
|
|
4028
|
-
"<!-- How does data move through the system? What writes where? -->",
|
|
4029
|
-
"",
|
|
4030
|
-
"## Invariants",
|
|
4031
|
-
"",
|
|
4032
|
-
"<!-- Rules that must never be violated. What breaks if these are wrong? -->",
|
|
4033
|
-
"",
|
|
4034
|
-
"## Dependencies",
|
|
4035
|
-
"",
|
|
4036
|
-
"<!-- What depends on what? If I change X, what else is affected? -->",
|
|
4037
|
-
""
|
|
4038
|
-
].join("\n");
|
|
4039
|
-
await writeFile4(archPath, template, "utf-8");
|
|
4040
|
-
} catch {
|
|
4066
|
+
function queueIntercom(targetSession, reason) {
|
|
4067
|
+
const queue = readQueue();
|
|
4068
|
+
const existing = queue.find((q) => q.targetSession === targetSession);
|
|
4069
|
+
if (existing) {
|
|
4070
|
+
existing.attempts++;
|
|
4071
|
+
existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4072
|
+
existing.reason = reason;
|
|
4073
|
+
} else {
|
|
4074
|
+
queue.push({
|
|
4075
|
+
targetSession,
|
|
4076
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4077
|
+
attempts: 0,
|
|
4078
|
+
reason
|
|
4079
|
+
});
|
|
4041
4080
|
}
|
|
4081
|
+
writeQueue(queue);
|
|
4042
4082
|
}
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4083
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
4084
|
+
var init_intercom_queue = __esm({
|
|
4085
|
+
"src/lib/intercom-queue.ts"() {
|
|
4086
|
+
"use strict";
|
|
4087
|
+
QUEUE_PATH = path16.join(os5.homedir(), ".exe-os", "intercom-queue.json");
|
|
4088
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
4089
|
+
INTERCOM_LOG = path16.join(os5.homedir(), ".exe-os", "intercom.log");
|
|
4090
|
+
}
|
|
4091
|
+
});
|
|
4092
|
+
|
|
4093
|
+
// src/lib/session-kill-telemetry.ts
|
|
4094
|
+
import crypto6 from "crypto";
|
|
4095
|
+
async function recordSessionKill(input) {
|
|
4096
|
+
try {
|
|
4097
|
+
const client = getClient();
|
|
4098
|
+
await client.execute({
|
|
4099
|
+
sql: `INSERT INTO session_kills
|
|
4100
|
+
(id, session_name, agent_id, killed_at, reason,
|
|
4101
|
+
ticks_idle, estimated_tokens_saved)
|
|
4102
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
4103
|
+
args: [
|
|
4104
|
+
crypto6.randomUUID(),
|
|
4105
|
+
input.sessionName,
|
|
4106
|
+
input.agentId,
|
|
4107
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
4108
|
+
input.reason,
|
|
4109
|
+
input.ticksIdle ?? null,
|
|
4110
|
+
input.estimatedTokensSaved ?? null
|
|
4111
|
+
]
|
|
4112
|
+
});
|
|
4113
|
+
} catch (err) {
|
|
4114
|
+
process.stderr.write(
|
|
4115
|
+
`[session-kill-telemetry] write failed: ${err instanceof Error ? err.message : String(err)}
|
|
4116
|
+
`
|
|
4117
|
+
);
|
|
4054
4118
|
}
|
|
4055
4119
|
}
|
|
4056
|
-
var
|
|
4057
|
-
|
|
4058
|
-
"src/lib/tasks-crud.ts"() {
|
|
4120
|
+
var init_session_kill_telemetry = __esm({
|
|
4121
|
+
"src/lib/session-kill-telemetry.ts"() {
|
|
4059
4122
|
"use strict";
|
|
4060
4123
|
init_database();
|
|
4061
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
|
|
4062
|
-
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
4063
4124
|
}
|
|
4064
4125
|
});
|
|
4065
4126
|
|
|
4066
|
-
// src/lib/
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4127
|
+
// src/lib/capacity-monitor.ts
|
|
4128
|
+
var capacity_monitor_exports = {};
|
|
4129
|
+
__export(capacity_monitor_exports, {
|
|
4130
|
+
CTX_FLOOR_PERCENT: () => CTX_FLOOR_PERCENT,
|
|
4131
|
+
_resetLastRelaunchCache: () => _resetLastRelaunchCache,
|
|
4132
|
+
_resetPendingCapacityKills: () => _resetPendingCapacityKills,
|
|
4133
|
+
confirmCapacityKill: () => confirmCapacityKill,
|
|
4134
|
+
createOrRefreshResumeTask: () => createOrRefreshResumeTask,
|
|
4135
|
+
extractContextPercent: () => extractContextPercent,
|
|
4136
|
+
isAtCapacity: () => isAtCapacity,
|
|
4137
|
+
isWithinRelaunchCooldown: () => isWithinRelaunchCooldown,
|
|
4138
|
+
pollCapacityDead: () => pollCapacityDead
|
|
4139
|
+
});
|
|
4140
|
+
function resumeTaskTitle(agentId) {
|
|
4141
|
+
return `${RESUME_TITLE_PREFIX} ${agentId} hit context capacity \u2014 continue open tasks`;
|
|
4142
|
+
}
|
|
4143
|
+
function buildResumeContext(agentId, openTasks) {
|
|
4144
|
+
const taskList = openTasks.map(
|
|
4145
|
+
(r, i) => `${i + 1}. [${String(r.priority).toUpperCase()}] ${String(r.title)} (${String(r.task_file)})`
|
|
4146
|
+
).join("\n");
|
|
4147
|
+
return [
|
|
4148
|
+
"## Context",
|
|
4149
|
+
"",
|
|
4150
|
+
`${agentId} hit context capacity and was auto-relaunched by the capacity monitor.`,
|
|
4151
|
+
"Call recall_my_memory first \u2014 search for 'CONTEXT CHECKPOINT'. Pick up where the previous session stopped.",
|
|
4152
|
+
"",
|
|
4153
|
+
`You have ${openTasks.length} open task(s). Work through them in priority order:`,
|
|
4154
|
+
"",
|
|
4155
|
+
taskList,
|
|
4156
|
+
"",
|
|
4157
|
+
"Read each task file and chain through them. Build and commit after each one."
|
|
4158
|
+
].join("\n");
|
|
4159
|
+
}
|
|
4160
|
+
function filterPaneContent(paneOutput) {
|
|
4161
|
+
return paneOutput.split("\n").filter((line) => {
|
|
4162
|
+
if (CONTENT_LINE_PREFIX.test(line)) return false;
|
|
4163
|
+
for (const marker of CONTENT_LINE_MARKERS) {
|
|
4164
|
+
if (line.includes(marker)) return false;
|
|
4165
|
+
}
|
|
4166
|
+
for (const re of SOURCE_CODE_MARKERS) {
|
|
4167
|
+
if (re.test(line)) return false;
|
|
4168
|
+
}
|
|
4169
|
+
return true;
|
|
4170
|
+
}).join("\n");
|
|
4171
|
+
}
|
|
4172
|
+
function extractContextPercent(paneOutput) {
|
|
4173
|
+
const match = paneOutput.match(CC_CONTEXT_BAR_RE);
|
|
4174
|
+
if (!match) return null;
|
|
4175
|
+
const parsed = Number.parseInt(match[2], 10);
|
|
4176
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
4177
|
+
}
|
|
4178
|
+
function isAtCapacity(paneOutput) {
|
|
4179
|
+
const filtered = filterPaneContent(paneOutput);
|
|
4180
|
+
return CAPACITY_PATTERNS.some((p) => p.test(filtered));
|
|
4181
|
+
}
|
|
4182
|
+
function confirmCapacityKill(agentId, now = Date.now()) {
|
|
4183
|
+
const pendingSince = _pendingCapacityKill.get(agentId);
|
|
4184
|
+
if (pendingSince === void 0) {
|
|
4185
|
+
_pendingCapacityKill.set(agentId, now);
|
|
4186
|
+
return false;
|
|
4074
4187
|
}
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
sessions[idx] = entry;
|
|
4079
|
-
} else {
|
|
4080
|
-
sessions.push(entry);
|
|
4188
|
+
if (now - pendingSince > CONFIRMATION_WINDOW_MS) {
|
|
4189
|
+
_pendingCapacityKill.set(agentId, now);
|
|
4190
|
+
return false;
|
|
4081
4191
|
}
|
|
4082
|
-
|
|
4192
|
+
_pendingCapacityKill.delete(agentId);
|
|
4193
|
+
return true;
|
|
4083
4194
|
}
|
|
4084
|
-
function
|
|
4195
|
+
function _resetPendingCapacityKills() {
|
|
4196
|
+
_pendingCapacityKill.clear();
|
|
4197
|
+
}
|
|
4198
|
+
function _resetLastRelaunchCache() {
|
|
4199
|
+
_lastRelaunch.clear();
|
|
4200
|
+
}
|
|
4201
|
+
async function lastResumeCreatedAtMs(agentId) {
|
|
4202
|
+
const client = getClient();
|
|
4203
|
+
const result = await client.execute({
|
|
4204
|
+
sql: `SELECT MAX(created_at) AS last_created_at
|
|
4205
|
+
FROM tasks
|
|
4206
|
+
WHERE assigned_to = ? AND title LIKE ?`,
|
|
4207
|
+
args: [agentId, `${RESUME_TITLE_PREFIX} %`]
|
|
4208
|
+
});
|
|
4209
|
+
const raw = result.rows[0]?.last_created_at;
|
|
4210
|
+
if (raw === null || raw === void 0) return null;
|
|
4211
|
+
const parsed = Date.parse(String(raw));
|
|
4212
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
4213
|
+
}
|
|
4214
|
+
async function isWithinRelaunchCooldown(agentId, now = Date.now()) {
|
|
4215
|
+
const cached = _lastRelaunch.get(agentId);
|
|
4216
|
+
if (cached !== void 0) return now - cached < RELAUNCH_COOLDOWN_MS;
|
|
4217
|
+
const persisted = await lastResumeCreatedAtMs(agentId);
|
|
4218
|
+
if (persisted === null) return false;
|
|
4219
|
+
if (now - persisted >= RELAUNCH_COOLDOWN_MS) return false;
|
|
4220
|
+
_lastRelaunch.set(agentId, persisted);
|
|
4221
|
+
return true;
|
|
4222
|
+
}
|
|
4223
|
+
async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
4224
|
+
const client = getClient();
|
|
4225
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4226
|
+
const context = buildResumeContext(agentId, openTasks);
|
|
4227
|
+
const existing = await client.execute({
|
|
4228
|
+
sql: `SELECT id FROM tasks
|
|
4229
|
+
WHERE assigned_to = ?
|
|
4230
|
+
AND title LIKE ?
|
|
4231
|
+
AND status IN (${RESUME_ACTIVE_STATUSES.map(() => "?").join(", ")})
|
|
4232
|
+
ORDER BY created_at DESC
|
|
4233
|
+
LIMIT 1`,
|
|
4234
|
+
args: [agentId, RESUME_TITLE_LIKE_PATTERN, ...RESUME_ACTIVE_STATUSES]
|
|
4235
|
+
});
|
|
4236
|
+
if (existing.rows.length > 0) {
|
|
4237
|
+
const taskId = String(existing.rows[0].id);
|
|
4238
|
+
await client.execute({
|
|
4239
|
+
sql: `UPDATE tasks SET context = ?, updated_at = ? WHERE id = ?`,
|
|
4240
|
+
args: [context, now, taskId]
|
|
4241
|
+
});
|
|
4242
|
+
return { created: false, taskId };
|
|
4243
|
+
}
|
|
4244
|
+
const { createTask: createTask2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
4245
|
+
const task = await createTask2({
|
|
4246
|
+
title: resumeTaskTitle(agentId),
|
|
4247
|
+
assignedTo: agentId,
|
|
4248
|
+
assignedBy: "system",
|
|
4249
|
+
projectName: projectDir.split("/").pop() ?? "unknown",
|
|
4250
|
+
priority: "p0",
|
|
4251
|
+
context,
|
|
4252
|
+
baseDir: projectDir
|
|
4253
|
+
});
|
|
4254
|
+
return { created: true, taskId: task.id };
|
|
4255
|
+
}
|
|
4256
|
+
async function pollCapacityDead() {
|
|
4257
|
+
const transport = getTransport();
|
|
4258
|
+
const relaunched = [];
|
|
4259
|
+
const registered = listSessions().filter(
|
|
4260
|
+
(s) => s.agentId !== "exe"
|
|
4261
|
+
);
|
|
4262
|
+
if (registered.length === 0) return [];
|
|
4263
|
+
let liveSessions;
|
|
4085
4264
|
try {
|
|
4086
|
-
|
|
4087
|
-
return JSON.parse(raw);
|
|
4265
|
+
liveSessions = transport.listSessions();
|
|
4088
4266
|
} catch {
|
|
4089
4267
|
return [];
|
|
4090
4268
|
}
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
}
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
}
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
return result === "1";
|
|
4159
|
-
} catch {
|
|
4160
|
-
return false;
|
|
4161
|
-
}
|
|
4162
|
-
}
|
|
4163
|
-
spawn(name, config2) {
|
|
4164
|
-
try {
|
|
4165
|
-
const args = ["new-session", "-d", "-s", name];
|
|
4166
|
-
if (config2.cwd) args.push("-c", config2.cwd);
|
|
4167
|
-
args.push(config2.command);
|
|
4168
|
-
execFileSync("tmux", args);
|
|
4169
|
-
return { sessionName: name };
|
|
4170
|
-
} catch (e) {
|
|
4171
|
-
return { sessionName: name, error: `spawn failed: ${e}` };
|
|
4172
|
-
}
|
|
4173
|
-
}
|
|
4174
|
-
kill(target) {
|
|
4175
|
-
try {
|
|
4176
|
-
execFileSync("tmux", ["kill-session", "-t", target], QUIET);
|
|
4177
|
-
} catch {
|
|
4178
|
-
}
|
|
4269
|
+
for (const entry of registered) {
|
|
4270
|
+
const { windowName, agentId, projectDir } = entry;
|
|
4271
|
+
if (!liveSessions.includes(windowName)) continue;
|
|
4272
|
+
if (await isWithinRelaunchCooldown(agentId)) continue;
|
|
4273
|
+
let pane;
|
|
4274
|
+
try {
|
|
4275
|
+
pane = transport.capturePane(windowName, 15);
|
|
4276
|
+
} catch {
|
|
4277
|
+
continue;
|
|
4278
|
+
}
|
|
4279
|
+
if (!isAtCapacity(pane)) continue;
|
|
4280
|
+
const ctxPct = extractContextPercent(pane);
|
|
4281
|
+
if (ctxPct !== null && ctxPct < CTX_FLOOR_PERCENT) {
|
|
4282
|
+
process.stderr.write(
|
|
4283
|
+
`[capacity-monitor] ctx-floor: ${agentId} at ${ctxPct}% in ${windowName} \u2014 below ${CTX_FLOOR_PERCENT}%. Skipping capacity kill (likely self-referential content or false positive).
|
|
4284
|
+
`
|
|
4285
|
+
);
|
|
4286
|
+
continue;
|
|
4287
|
+
}
|
|
4288
|
+
if (!confirmCapacityKill(agentId)) {
|
|
4289
|
+
process.stderr.write(
|
|
4290
|
+
`[capacity-monitor] ${agentId} matched capacity pattern once in ${windowName}. Awaiting confirmation on next tick.
|
|
4291
|
+
`
|
|
4292
|
+
);
|
|
4293
|
+
continue;
|
|
4294
|
+
}
|
|
4295
|
+
const verify = await verifyPaneAtCapacity(windowName);
|
|
4296
|
+
if (!verify.atCapacity) {
|
|
4297
|
+
process.stderr.write(
|
|
4298
|
+
`[capacity-monitor] verifyPaneAtCapacity rejected kill for ${agentId} in ${windowName} (reason: ${verify.reason}). Skipping.
|
|
4299
|
+
`
|
|
4300
|
+
);
|
|
4301
|
+
void recordSessionKill({
|
|
4302
|
+
sessionName: windowName,
|
|
4303
|
+
agentId,
|
|
4304
|
+
reason: "capacity_false_positive_blocked"
|
|
4305
|
+
});
|
|
4306
|
+
continue;
|
|
4307
|
+
}
|
|
4308
|
+
process.stderr.write(
|
|
4309
|
+
`[capacity-monitor] Detected ${agentId} at capacity in session ${windowName} (confirmed). Auto-relaunching.
|
|
4310
|
+
`
|
|
4311
|
+
);
|
|
4312
|
+
try {
|
|
4313
|
+
transport.kill(windowName);
|
|
4314
|
+
void recordSessionKill({
|
|
4315
|
+
sessionName: windowName,
|
|
4316
|
+
agentId,
|
|
4317
|
+
reason: "capacity"
|
|
4318
|
+
});
|
|
4319
|
+
const client = getClient();
|
|
4320
|
+
const openTasks = await client.execute({
|
|
4321
|
+
sql: `SELECT id, title, priority, task_file, status
|
|
4322
|
+
FROM tasks
|
|
4323
|
+
WHERE assigned_to = ? AND status IN ('open', 'in_progress')
|
|
4324
|
+
ORDER BY
|
|
4325
|
+
CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 WHEN 'p2' THEN 2 ELSE 3 END,
|
|
4326
|
+
created_at ASC
|
|
4327
|
+
LIMIT 10`,
|
|
4328
|
+
args: [agentId]
|
|
4329
|
+
});
|
|
4330
|
+
if (openTasks.rows.length === 0) {
|
|
4331
|
+
process.stderr.write(
|
|
4332
|
+
`[capacity-monitor] ${agentId} has no open tasks \u2014 skipping relaunch.
|
|
4333
|
+
`
|
|
4334
|
+
);
|
|
4335
|
+
continue;
|
|
4179
4336
|
}
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4337
|
+
const { created } = await createOrRefreshResumeTask(
|
|
4338
|
+
agentId,
|
|
4339
|
+
projectDir,
|
|
4340
|
+
openTasks.rows
|
|
4341
|
+
);
|
|
4342
|
+
if (created) {
|
|
4343
|
+
await writeNotification({
|
|
4344
|
+
agentId: "system",
|
|
4345
|
+
agentRole: "daemon",
|
|
4346
|
+
event: "capacity_relaunch",
|
|
4347
|
+
project: projectDir.split("/").pop() ?? "unknown",
|
|
4348
|
+
summary: `${agentId} hit context capacity. Auto-relaunched with ${openTasks.rows.length} open task(s).`
|
|
4349
|
+
});
|
|
4186
4350
|
}
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
})
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
_transport = new TmuxTransport2();
|
|
4351
|
+
_lastRelaunch.set(agentId, Date.now());
|
|
4352
|
+
if (created) relaunched.push(agentId);
|
|
4353
|
+
} catch (err) {
|
|
4354
|
+
process.stderr.write(
|
|
4355
|
+
`[capacity-monitor] Failed to relaunch ${agentId}: ${err instanceof Error ? err.message : String(err)}
|
|
4356
|
+
`
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
4196
4359
|
}
|
|
4197
|
-
return
|
|
4360
|
+
return relaunched;
|
|
4198
4361
|
}
|
|
4199
|
-
var
|
|
4200
|
-
var
|
|
4201
|
-
"src/lib/
|
|
4362
|
+
var CAPACITY_PATTERNS, CONTENT_LINE_PREFIX, CONTENT_LINE_MARKERS, SOURCE_CODE_MARKERS, RELAUNCH_COOLDOWN_MS, _lastRelaunch, RESUME_TITLE_PREFIX, RESUME_TITLE_LIKE_PATTERN, RESUME_ACTIVE_STATUSES, CONFIRMATION_WINDOW_MS, _pendingCapacityKill, CC_CONTEXT_BAR_RE, CTX_FLOOR_PERCENT;
|
|
4363
|
+
var init_capacity_monitor = __esm({
|
|
4364
|
+
"src/lib/capacity-monitor.ts"() {
|
|
4202
4365
|
"use strict";
|
|
4203
|
-
|
|
4366
|
+
init_session_registry();
|
|
4367
|
+
init_transport();
|
|
4368
|
+
init_notifications();
|
|
4369
|
+
init_database();
|
|
4370
|
+
init_session_kill_telemetry();
|
|
4371
|
+
init_tmux_routing();
|
|
4372
|
+
CAPACITY_PATTERNS = [
|
|
4373
|
+
/conversation is too long/i,
|
|
4374
|
+
/maximum context length/i,
|
|
4375
|
+
/context window.*(?:limit|exceed|full)/i,
|
|
4376
|
+
/reached.*(?:token|context).*limit/i
|
|
4377
|
+
];
|
|
4378
|
+
CONTENT_LINE_PREFIX = /^[\s>#\-*[]/;
|
|
4379
|
+
CONTENT_LINE_MARKERS = [
|
|
4380
|
+
"RESUME:",
|
|
4381
|
+
"intercom",
|
|
4382
|
+
"capacity-monitor",
|
|
4383
|
+
"CAPACITY_PATTERNS",
|
|
4384
|
+
"isAtCapacity",
|
|
4385
|
+
"CONTENT_LINE_MARKERS",
|
|
4386
|
+
"pollCapacityDead",
|
|
4387
|
+
"confirmCapacityKill",
|
|
4388
|
+
"session_kills",
|
|
4389
|
+
"capacity-monitor.test"
|
|
4390
|
+
];
|
|
4391
|
+
SOURCE_CODE_MARKERS = [
|
|
4392
|
+
/["'`/].*(?:maximum context length|conversation is too long)/i,
|
|
4393
|
+
/(?:maximum context length|conversation is too long).*["'`/]/i
|
|
4394
|
+
];
|
|
4395
|
+
RELAUNCH_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
4396
|
+
_lastRelaunch = /* @__PURE__ */ new Map();
|
|
4397
|
+
RESUME_TITLE_PREFIX = "RESUME:";
|
|
4398
|
+
RESUME_TITLE_LIKE_PATTERN = `${RESUME_TITLE_PREFIX} % hit context capacity%`;
|
|
4399
|
+
RESUME_ACTIVE_STATUSES = ["open", "in_progress"];
|
|
4400
|
+
CONFIRMATION_WINDOW_MS = 3 * 60 * 1e3;
|
|
4401
|
+
_pendingCapacityKill = /* @__PURE__ */ new Map();
|
|
4402
|
+
CC_CONTEXT_BAR_RE = /([\u2588\u2591\u2592\u2593]{10})\s+(\d+)%/;
|
|
4403
|
+
CTX_FLOOR_PERCENT = 50;
|
|
4204
4404
|
}
|
|
4205
4405
|
});
|
|
4206
4406
|
|
|
4207
|
-
// src/lib/
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4407
|
+
// src/lib/tmux-routing.ts
|
|
4408
|
+
var tmux_routing_exports = {};
|
|
4409
|
+
__export(tmux_routing_exports, {
|
|
4410
|
+
acquireSpawnLock: () => acquireSpawnLock2,
|
|
4411
|
+
employeeSessionName: () => employeeSessionName,
|
|
4412
|
+
ensureEmployee: () => ensureEmployee,
|
|
4413
|
+
extractRootExe: () => extractRootExe,
|
|
4414
|
+
findFreeInstance: () => findFreeInstance,
|
|
4415
|
+
getDispatchedBy: () => getDispatchedBy,
|
|
4416
|
+
getMySession: () => getMySession,
|
|
4417
|
+
getParentExe: () => getParentExe,
|
|
4418
|
+
getSessionState: () => getSessionState,
|
|
4419
|
+
isEmployeeAlive: () => isEmployeeAlive,
|
|
4420
|
+
isExeSession: () => isExeSession,
|
|
4421
|
+
isSessionBusy: () => isSessionBusy,
|
|
4422
|
+
notifyParentExe: () => notifyParentExe,
|
|
4423
|
+
parseParentExe: () => parseParentExe,
|
|
4424
|
+
registerParentExe: () => registerParentExe,
|
|
4425
|
+
releaseSpawnLock: () => releaseSpawnLock2,
|
|
4426
|
+
resolveExeSession: () => resolveExeSession,
|
|
4427
|
+
sendIntercom: () => sendIntercom,
|
|
4428
|
+
spawnEmployee: () => spawnEmployee,
|
|
4429
|
+
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
4430
|
+
});
|
|
4431
|
+
import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
|
|
4432
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync13, appendFileSync } from "fs";
|
|
4433
|
+
import path17 from "path";
|
|
4434
|
+
import os6 from "os";
|
|
4435
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4436
|
+
import { unlinkSync as unlinkSync4 } from "fs";
|
|
4437
|
+
function spawnLockPath(sessionName) {
|
|
4438
|
+
return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4211
4439
|
}
|
|
4212
|
-
function
|
|
4213
|
-
if (_cachedSupport !== null) return _cachedSupport;
|
|
4440
|
+
function isProcessAlive(pid) {
|
|
4214
4441
|
try {
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
timeout: 5e3
|
|
4218
|
-
});
|
|
4219
|
-
_cachedSupport = /(^|\s)--agent(\b|=)/.test(helpOutput);
|
|
4442
|
+
process.kill(pid, 0);
|
|
4443
|
+
return true;
|
|
4220
4444
|
} catch {
|
|
4221
|
-
|
|
4445
|
+
return false;
|
|
4222
4446
|
}
|
|
4223
|
-
return _cachedSupport;
|
|
4224
4447
|
}
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
"use strict";
|
|
4229
|
-
_cachedSupport = null;
|
|
4448
|
+
function acquireSpawnLock2(sessionName) {
|
|
4449
|
+
if (!existsSync13(SPAWN_LOCK_DIR)) {
|
|
4450
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
4230
4451
|
}
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4452
|
+
const lockFile = spawnLockPath(sessionName);
|
|
4453
|
+
if (existsSync13(lockFile)) {
|
|
4454
|
+
try {
|
|
4455
|
+
const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
|
|
4456
|
+
const age = Date.now() - lock.timestamp;
|
|
4457
|
+
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4458
|
+
return false;
|
|
4459
|
+
}
|
|
4460
|
+
} catch {
|
|
4239
4461
|
}
|
|
4240
4462
|
}
|
|
4241
|
-
|
|
4463
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4464
|
+
return true;
|
|
4242
4465
|
}
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
MCP_PRIMARY_KEY = "exe-os";
|
|
4248
|
-
MCP_LEGACY_KEY = "exe-mem";
|
|
4249
|
-
MCP_TOOL_PREFIXES = [
|
|
4250
|
-
`mcp__${MCP_PRIMARY_KEY}__`,
|
|
4251
|
-
`mcp__${MCP_LEGACY_KEY}__`
|
|
4252
|
-
];
|
|
4466
|
+
function releaseSpawnLock2(sessionName) {
|
|
4467
|
+
try {
|
|
4468
|
+
unlinkSync4(spawnLockPath(sessionName));
|
|
4469
|
+
} catch {
|
|
4253
4470
|
}
|
|
4254
|
-
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4471
|
+
}
|
|
4472
|
+
function resolveBehaviorsExporterScript() {
|
|
4473
|
+
try {
|
|
4474
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4475
|
+
const scriptPath = path17.join(
|
|
4476
|
+
path17.dirname(thisFile),
|
|
4477
|
+
"..",
|
|
4478
|
+
"bin",
|
|
4479
|
+
"exe-export-behaviors.js"
|
|
4480
|
+
);
|
|
4481
|
+
return existsSync13(scriptPath) ? scriptPath : null;
|
|
4482
|
+
} catch {
|
|
4483
|
+
return null;
|
|
4262
4484
|
}
|
|
4263
|
-
return DEFAULT_PROVIDER;
|
|
4264
4485
|
}
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4486
|
+
function exportBehaviorsSync(agentId, projectName, sessionKey) {
|
|
4487
|
+
const script = resolveBehaviorsExporterScript();
|
|
4488
|
+
if (!script) return null;
|
|
4489
|
+
try {
|
|
4490
|
+
const output = execFileSync2(
|
|
4491
|
+
process.execPath,
|
|
4492
|
+
[script, agentId, projectName, sessionKey],
|
|
4493
|
+
{ encoding: "utf-8", timeout: BEHAVIORS_EXPORT_TIMEOUT_MS }
|
|
4494
|
+
).trim();
|
|
4495
|
+
return output.length > 0 ? output : null;
|
|
4496
|
+
} catch (err) {
|
|
4497
|
+
process.stderr.write(
|
|
4498
|
+
`[tmux-routing] behaviors export failed for ${agentId}: ${err instanceof Error ? err.message : String(err)}
|
|
4499
|
+
`
|
|
4500
|
+
);
|
|
4501
|
+
return null;
|
|
4277
4502
|
}
|
|
4278
|
-
});
|
|
4279
|
-
|
|
4280
|
-
// src/lib/intercom-queue.ts
|
|
4281
|
-
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, renameSync as renameSync2, existsSync as existsSync13, mkdirSync as mkdirSync5 } from "fs";
|
|
4282
|
-
import path17 from "path";
|
|
4283
|
-
import os5 from "os";
|
|
4284
|
-
function ensureDir() {
|
|
4285
|
-
const dir = path17.dirname(QUEUE_PATH);
|
|
4286
|
-
if (!existsSync13(dir)) mkdirSync5(dir, { recursive: true });
|
|
4287
4503
|
}
|
|
4288
|
-
function
|
|
4504
|
+
function getMySession() {
|
|
4505
|
+
return getTransport().getMySession();
|
|
4506
|
+
}
|
|
4507
|
+
function employeeSessionName(employee, exeSession, instance) {
|
|
4508
|
+
if (!/^exe\d+$/.test(exeSession)) {
|
|
4509
|
+
const root = extractRootExe(exeSession);
|
|
4510
|
+
if (root) {
|
|
4511
|
+
process.stderr.write(
|
|
4512
|
+
`[tmux-routing] WARN: exeSession="${exeSession}" is not a root exe session, using "${root}" instead
|
|
4513
|
+
`
|
|
4514
|
+
);
|
|
4515
|
+
exeSession = root;
|
|
4516
|
+
} else {
|
|
4517
|
+
throw new Error(
|
|
4518
|
+
`Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1"), not an agent session`
|
|
4519
|
+
);
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
const suffix = instance != null && instance > 0 ? String(instance) : "";
|
|
4523
|
+
const name = `${employee}${suffix}-${exeSession}`;
|
|
4524
|
+
if (!VALID_SESSION_NAME.test(name)) {
|
|
4525
|
+
throw new Error(
|
|
4526
|
+
`Invalid session name "${name}" \u2014 must match {agent}-exe{N} or {agent}{instance}-exe{N}`
|
|
4527
|
+
);
|
|
4528
|
+
}
|
|
4529
|
+
return name;
|
|
4530
|
+
}
|
|
4531
|
+
function parseParentExe(sessionName, agentId) {
|
|
4532
|
+
const escaped = agentId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4533
|
+
const regex = new RegExp(`^${escaped}\\d*-(.+)$`);
|
|
4534
|
+
const match = sessionName.match(regex);
|
|
4535
|
+
return match?.[1] ?? null;
|
|
4536
|
+
}
|
|
4537
|
+
function extractRootExe(name) {
|
|
4538
|
+
const match = name.match(/(exe\d+)$/);
|
|
4539
|
+
return match?.[1] ?? null;
|
|
4540
|
+
}
|
|
4541
|
+
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4542
|
+
if (!existsSync13(SESSION_CACHE)) {
|
|
4543
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4544
|
+
}
|
|
4545
|
+
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
4546
|
+
const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
4547
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
4548
|
+
parentExe: rootExe,
|
|
4549
|
+
dispatchedBy: dispatchedBy || rootExe,
|
|
4550
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4551
|
+
}));
|
|
4552
|
+
}
|
|
4553
|
+
function getParentExe(sessionKey) {
|
|
4289
4554
|
try {
|
|
4290
|
-
|
|
4291
|
-
return
|
|
4555
|
+
const data = JSON.parse(readFileSync11(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
4556
|
+
return data.parentExe || null;
|
|
4292
4557
|
} catch {
|
|
4293
|
-
return
|
|
4558
|
+
return null;
|
|
4294
4559
|
}
|
|
4295
4560
|
}
|
|
4296
|
-
function
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4561
|
+
function getDispatchedBy(sessionKey) {
|
|
4562
|
+
try {
|
|
4563
|
+
const data = JSON.parse(readFileSync11(
|
|
4564
|
+
path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
4565
|
+
"utf8"
|
|
4566
|
+
));
|
|
4567
|
+
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
4568
|
+
} catch {
|
|
4569
|
+
return null;
|
|
4570
|
+
}
|
|
4301
4571
|
}
|
|
4302
|
-
function
|
|
4303
|
-
const
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4313
|
-
attempts: 0,
|
|
4314
|
-
reason
|
|
4315
|
-
});
|
|
4572
|
+
function resolveExeSession() {
|
|
4573
|
+
const mySession = getMySession();
|
|
4574
|
+
if (!mySession) return null;
|
|
4575
|
+
try {
|
|
4576
|
+
const key = getSessionKey();
|
|
4577
|
+
const parentExe = getParentExe(key);
|
|
4578
|
+
if (parentExe) {
|
|
4579
|
+
return extractRootExe(parentExe) ?? parentExe;
|
|
4580
|
+
}
|
|
4581
|
+
} catch {
|
|
4316
4582
|
}
|
|
4317
|
-
|
|
4583
|
+
return extractRootExe(mySession) ?? mySession;
|
|
4318
4584
|
}
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4585
|
+
function isEmployeeAlive(sessionName) {
|
|
4586
|
+
return getTransport().isAlive(sessionName);
|
|
4587
|
+
}
|
|
4588
|
+
function findFreeInstance(employeeName, exeSession, maxInstances = 10, isAlive = isEmployeeAlive) {
|
|
4589
|
+
const base = employeeSessionName(employeeName, exeSession);
|
|
4590
|
+
if (!isAlive(base) && acquireSpawnLock2(base)) return 0;
|
|
4591
|
+
for (let i = 2; i <= maxInstances; i++) {
|
|
4592
|
+
const candidate = employeeSessionName(employeeName, exeSession, i);
|
|
4593
|
+
if (!isAlive(candidate) && acquireSpawnLock2(candidate)) return i;
|
|
4594
|
+
}
|
|
4595
|
+
return null;
|
|
4596
|
+
}
|
|
4597
|
+
async function verifyPaneAtCapacity(sessionName) {
|
|
4598
|
+
const transport = getTransport();
|
|
4599
|
+
if (!transport.isAlive(sessionName)) {
|
|
4600
|
+
return { atCapacity: false, reason: `session ${sessionName} is not alive` };
|
|
4601
|
+
}
|
|
4602
|
+
let pane;
|
|
4603
|
+
try {
|
|
4604
|
+
pane = transport.capturePane(sessionName, VERIFY_PANE_LINES);
|
|
4605
|
+
} catch (err) {
|
|
4606
|
+
return {
|
|
4607
|
+
atCapacity: false,
|
|
4608
|
+
reason: `capture-pane failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4609
|
+
};
|
|
4610
|
+
}
|
|
4611
|
+
const { isAtCapacity: isAtCapacity2 } = await Promise.resolve().then(() => (init_capacity_monitor(), capacity_monitor_exports));
|
|
4612
|
+
if (!isAtCapacity2(pane)) {
|
|
4613
|
+
return {
|
|
4614
|
+
atCapacity: false,
|
|
4615
|
+
reason: `last ${VERIFY_PANE_LINES} lines show normal work, no capacity banner`
|
|
4616
|
+
};
|
|
4617
|
+
}
|
|
4618
|
+
return {
|
|
4619
|
+
atCapacity: true,
|
|
4620
|
+
reason: "capacity banner matched in recent pane output"
|
|
4621
|
+
};
|
|
4622
|
+
}
|
|
4623
|
+
function readDebounceState() {
|
|
4624
|
+
try {
|
|
4625
|
+
if (!existsSync13(DEBOUNCE_FILE)) return {};
|
|
4626
|
+
return JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
|
|
4627
|
+
} catch {
|
|
4628
|
+
return {};
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
function writeDebounceState(state) {
|
|
4632
|
+
try {
|
|
4633
|
+
if (!existsSync13(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
4634
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
4635
|
+
} catch {
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
function isDebounced(targetSession) {
|
|
4639
|
+
const state = readDebounceState();
|
|
4640
|
+
const lastSent = state[targetSession] ?? 0;
|
|
4641
|
+
return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
|
|
4642
|
+
}
|
|
4643
|
+
function recordDebounce(targetSession) {
|
|
4644
|
+
const state = readDebounceState();
|
|
4645
|
+
state[targetSession] = Date.now();
|
|
4646
|
+
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
4647
|
+
for (const key of Object.keys(state)) {
|
|
4648
|
+
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4326
4649
|
}
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
// src/lib/tmux-routing.ts
|
|
4330
|
-
import { execFileSync as execFileSync2, execSync as execSync8 } from "child_process";
|
|
4331
|
-
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync14, appendFileSync } from "fs";
|
|
4332
|
-
import path18 from "path";
|
|
4333
|
-
import os6 from "os";
|
|
4334
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4335
|
-
import { unlinkSync as unlinkSync4 } from "fs";
|
|
4336
|
-
function spawnLockPath(sessionName) {
|
|
4337
|
-
return path18.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
4650
|
+
writeDebounceState(state);
|
|
4338
4651
|
}
|
|
4339
|
-
function
|
|
4652
|
+
function logIntercom(msg) {
|
|
4653
|
+
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
4654
|
+
`;
|
|
4655
|
+
process.stderr.write(`[intercom] ${msg}
|
|
4656
|
+
`);
|
|
4340
4657
|
try {
|
|
4341
|
-
|
|
4342
|
-
return true;
|
|
4658
|
+
appendFileSync(INTERCOM_LOG2, line);
|
|
4343
4659
|
} catch {
|
|
4344
|
-
return false;
|
|
4345
4660
|
}
|
|
4346
4661
|
}
|
|
4347
|
-
function
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
const age = Date.now() - lock.timestamp;
|
|
4356
|
-
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
4357
|
-
return false;
|
|
4662
|
+
function getSessionState(sessionName) {
|
|
4663
|
+
const transport = getTransport();
|
|
4664
|
+
if (!transport.isAlive(sessionName)) return "offline";
|
|
4665
|
+
try {
|
|
4666
|
+
const pane = transport.capturePane(sessionName, 5);
|
|
4667
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
4668
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
4669
|
+
return "no_claude";
|
|
4358
4670
|
}
|
|
4359
|
-
} catch {
|
|
4360
4671
|
}
|
|
4672
|
+
if (/Running…/.test(pane)) return "tool";
|
|
4673
|
+
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
4674
|
+
return "idle";
|
|
4675
|
+
} catch {
|
|
4676
|
+
return "offline";
|
|
4361
4677
|
}
|
|
4362
|
-
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
4363
|
-
return true;
|
|
4364
4678
|
}
|
|
4365
|
-
function
|
|
4679
|
+
function isSessionBusy(sessionName) {
|
|
4680
|
+
const state = getSessionState(sessionName);
|
|
4681
|
+
return state === "thinking" || state === "tool";
|
|
4682
|
+
}
|
|
4683
|
+
function isExeSession(sessionName) {
|
|
4684
|
+
return /^exe\d*$/.test(sessionName);
|
|
4685
|
+
}
|
|
4686
|
+
function sendIntercom(targetSession) {
|
|
4687
|
+
const transport = getTransport();
|
|
4688
|
+
if (isExeSession(targetSession)) {
|
|
4689
|
+
logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
|
|
4690
|
+
return "skipped_exe";
|
|
4691
|
+
}
|
|
4692
|
+
if (isDebounced(targetSession)) {
|
|
4693
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
|
|
4694
|
+
return "debounced";
|
|
4695
|
+
}
|
|
4366
4696
|
try {
|
|
4367
|
-
|
|
4697
|
+
const sessions = transport.listSessions();
|
|
4698
|
+
if (!sessions.includes(targetSession)) {
|
|
4699
|
+
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
4700
|
+
return "failed";
|
|
4701
|
+
}
|
|
4702
|
+
const sessionState = getSessionState(targetSession);
|
|
4703
|
+
if (sessionState === "no_claude") {
|
|
4704
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
4705
|
+
recordDebounce(targetSession);
|
|
4706
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
4707
|
+
return "queued";
|
|
4708
|
+
}
|
|
4709
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
4710
|
+
queueIntercom(targetSession, "session busy at send time");
|
|
4711
|
+
recordDebounce(targetSession);
|
|
4712
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
4713
|
+
return "queued";
|
|
4714
|
+
}
|
|
4715
|
+
if (transport.isPaneInCopyMode(targetSession)) {
|
|
4716
|
+
logIntercom(`COPY_MODE \u2192 ${targetSession} (exiting copy mode first)`);
|
|
4717
|
+
transport.sendKeys(targetSession, "q");
|
|
4718
|
+
}
|
|
4719
|
+
transport.sendKeys(targetSession, "/exe-intercom");
|
|
4720
|
+
recordDebounce(targetSession);
|
|
4721
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4722
|
+
return "delivered";
|
|
4368
4723
|
} catch {
|
|
4724
|
+
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
4725
|
+
return "failed";
|
|
4369
4726
|
}
|
|
4370
4727
|
}
|
|
4371
|
-
function
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4728
|
+
function notifyParentExe(sessionKey) {
|
|
4729
|
+
const target = getDispatchedBy(sessionKey);
|
|
4730
|
+
if (!target) {
|
|
4731
|
+
process.stderr.write(`[intercom] notifyParentExe: no dispatcher found for key ${sessionKey}
|
|
4732
|
+
`);
|
|
4733
|
+
return false;
|
|
4734
|
+
}
|
|
4735
|
+
process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
|
|
4736
|
+
`);
|
|
4737
|
+
const result = sendIntercom(target);
|
|
4738
|
+
if (result === "failed") {
|
|
4739
|
+
const rootExe = resolveExeSession();
|
|
4740
|
+
if (rootExe && rootExe !== target) {
|
|
4741
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
4742
|
+
`);
|
|
4743
|
+
const fallback = sendIntercom(rootExe);
|
|
4744
|
+
return fallback !== "failed";
|
|
4745
|
+
}
|
|
4746
|
+
return false;
|
|
4383
4747
|
}
|
|
4748
|
+
return true;
|
|
4384
4749
|
}
|
|
4385
|
-
function
|
|
4386
|
-
|
|
4387
|
-
|
|
4750
|
+
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4751
|
+
if (employeeName === "exe") {
|
|
4752
|
+
return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
|
|
4753
|
+
}
|
|
4388
4754
|
try {
|
|
4389
|
-
|
|
4390
|
-
process.execPath,
|
|
4391
|
-
[script, agentId, projectName, sessionKey],
|
|
4392
|
-
{ encoding: "utf-8", timeout: BEHAVIORS_EXPORT_TIMEOUT_MS }
|
|
4393
|
-
).trim();
|
|
4394
|
-
return output.length > 0 ? output : null;
|
|
4755
|
+
assertEmployeeLimitSync();
|
|
4395
4756
|
} catch (err) {
|
|
4396
|
-
|
|
4397
|
-
|
|
4757
|
+
if (err instanceof PlanLimitError) {
|
|
4758
|
+
return { status: "failed", sessionName: "", error: err.message };
|
|
4759
|
+
}
|
|
4760
|
+
}
|
|
4761
|
+
if (/-exe\d*$/.test(employeeName)) {
|
|
4762
|
+
const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
|
|
4763
|
+
return {
|
|
4764
|
+
status: "failed",
|
|
4765
|
+
sessionName: "",
|
|
4766
|
+
error: `Error: pass employee name ('${bare}'), not session name ('${employeeName}')`
|
|
4767
|
+
};
|
|
4768
|
+
}
|
|
4769
|
+
if (!/^exe\d+$/.test(exeSession)) {
|
|
4770
|
+
const root = extractRootExe(exeSession);
|
|
4771
|
+
if (root) {
|
|
4772
|
+
process.stderr.write(
|
|
4773
|
+
`[ensureEmployee] WARN: caller passed exeSession="${exeSession}" (not a root exe). Auto-correcting to "${root}".
|
|
4398
4774
|
`
|
|
4775
|
+
);
|
|
4776
|
+
exeSession = root;
|
|
4777
|
+
} else {
|
|
4778
|
+
return {
|
|
4779
|
+
status: "failed",
|
|
4780
|
+
sessionName: "",
|
|
4781
|
+
error: `Invalid exeSession "${exeSession}" \u2014 must be a root exe session (e.g., "exe1")`
|
|
4782
|
+
};
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
let effectiveInstance = opts?.instance;
|
|
4786
|
+
if (effectiveInstance === void 0 && opts?.autoInstance) {
|
|
4787
|
+
const free = findFreeInstance(
|
|
4788
|
+
employeeName,
|
|
4789
|
+
exeSession,
|
|
4790
|
+
opts.maxAutoInstances ?? 10
|
|
4399
4791
|
);
|
|
4400
|
-
|
|
4792
|
+
if (free === null) {
|
|
4793
|
+
return {
|
|
4794
|
+
status: "failed",
|
|
4795
|
+
sessionName: employeeSessionName(employeeName, exeSession),
|
|
4796
|
+
error: `All ${opts.maxAutoInstances ?? 10} instances of ${employeeName} are alive \u2014 cap reached`
|
|
4797
|
+
};
|
|
4798
|
+
}
|
|
4799
|
+
effectiveInstance = free === 0 ? void 0 : free;
|
|
4401
4800
|
}
|
|
4801
|
+
const sessionName = employeeSessionName(employeeName, exeSession, effectiveInstance);
|
|
4802
|
+
if (isEmployeeAlive(sessionName)) {
|
|
4803
|
+
const result2 = sendIntercom(sessionName);
|
|
4804
|
+
if (result2 === "acknowledged" || result2 === "skipped_exe" || result2 === "debounced" || result2 === "queued") {
|
|
4805
|
+
return { status: "intercom_sent", sessionName };
|
|
4806
|
+
}
|
|
4807
|
+
if (result2 === "delivered") {
|
|
4808
|
+
return { status: "intercom_unprocessed", sessionName };
|
|
4809
|
+
}
|
|
4810
|
+
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
4811
|
+
}
|
|
4812
|
+
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
4813
|
+
const result = spawnEmployee(employeeName, exeSession, projectDir, spawnOpts);
|
|
4814
|
+
if (result.error) {
|
|
4815
|
+
return { status: "failed", sessionName, error: result.error };
|
|
4816
|
+
}
|
|
4817
|
+
return { status: "spawned", sessionName };
|
|
4402
4818
|
}
|
|
4403
|
-
function
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
const
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
function getParentExe(sessionKey) {
|
|
4819
|
+
function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4820
|
+
const transport = getTransport();
|
|
4821
|
+
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
4822
|
+
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
4823
|
+
const logDir = path17.join(os6.homedir(), ".exe-os", "session-logs");
|
|
4824
|
+
const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
4825
|
+
if (!existsSync13(logDir)) {
|
|
4826
|
+
mkdirSync6(logDir, { recursive: true });
|
|
4827
|
+
}
|
|
4828
|
+
transport.kill(sessionName);
|
|
4829
|
+
let cleanupSuffix = "";
|
|
4415
4830
|
try {
|
|
4416
|
-
const
|
|
4417
|
-
|
|
4831
|
+
const thisFile = fileURLToPath2(import.meta.url);
|
|
4832
|
+
const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
4833
|
+
if (existsSync13(cleanupScript)) {
|
|
4834
|
+
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
4835
|
+
}
|
|
4418
4836
|
} catch {
|
|
4419
|
-
return null;
|
|
4420
4837
|
}
|
|
4421
|
-
}
|
|
4422
|
-
function getDispatchedBy(sessionKey) {
|
|
4423
4838
|
try {
|
|
4424
|
-
const
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4839
|
+
const claudeJsonPath = path17.join(os6.homedir(), ".claude.json");
|
|
4840
|
+
let claudeJson = {};
|
|
4841
|
+
try {
|
|
4842
|
+
claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
|
|
4843
|
+
} catch {
|
|
4844
|
+
}
|
|
4845
|
+
if (!claudeJson.projects) claudeJson.projects = {};
|
|
4846
|
+
const projects = claudeJson.projects;
|
|
4847
|
+
const trustDir = opts?.cwd ?? projectDir;
|
|
4848
|
+
if (!projects[trustDir]) projects[trustDir] = {};
|
|
4849
|
+
projects[trustDir].hasTrustDialogAccepted = true;
|
|
4850
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
4429
4851
|
} catch {
|
|
4430
|
-
return null;
|
|
4431
4852
|
}
|
|
4432
|
-
}
|
|
4433
|
-
function resolveExeSession() {
|
|
4434
|
-
const mySession = getMySession();
|
|
4435
|
-
if (!mySession) return null;
|
|
4436
4853
|
try {
|
|
4437
|
-
const
|
|
4438
|
-
const
|
|
4439
|
-
|
|
4440
|
-
|
|
4854
|
+
const settingsDir = path17.join(os6.homedir(), ".claude", "projects");
|
|
4855
|
+
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
4856
|
+
const projSettingsDir = path17.join(settingsDir, normalizedKey);
|
|
4857
|
+
const settingsPath = path17.join(projSettingsDir, "settings.json");
|
|
4858
|
+
let settings = {};
|
|
4859
|
+
try {
|
|
4860
|
+
settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
|
|
4861
|
+
} catch {
|
|
4862
|
+
}
|
|
4863
|
+
const perms = settings.permissions ?? {};
|
|
4864
|
+
const allow = perms.allow ?? [];
|
|
4865
|
+
const toolNames = [
|
|
4866
|
+
"recall_my_memory",
|
|
4867
|
+
"store_memory",
|
|
4868
|
+
"create_task",
|
|
4869
|
+
"update_task",
|
|
4870
|
+
"list_tasks",
|
|
4871
|
+
"get_task",
|
|
4872
|
+
"ask_team_memory",
|
|
4873
|
+
"store_behavior",
|
|
4874
|
+
"get_identity",
|
|
4875
|
+
"send_message"
|
|
4876
|
+
];
|
|
4877
|
+
const requiredTools = expandDualPrefixTools(toolNames);
|
|
4878
|
+
let changed = false;
|
|
4879
|
+
for (const tool of requiredTools) {
|
|
4880
|
+
if (!allow.includes(tool)) {
|
|
4881
|
+
allow.push(tool);
|
|
4882
|
+
changed = true;
|
|
4883
|
+
}
|
|
4884
|
+
}
|
|
4885
|
+
if (changed) {
|
|
4886
|
+
perms.allow = allow;
|
|
4887
|
+
settings.permissions = perms;
|
|
4888
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4889
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4441
4890
|
}
|
|
4442
4891
|
} catch {
|
|
4443
4892
|
}
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
if (!
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
if (
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
}
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4893
|
+
const spawnCwd = opts?.cwd ?? projectDir;
|
|
4894
|
+
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
4895
|
+
const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
4896
|
+
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
4897
|
+
let identityFlag = "";
|
|
4898
|
+
let behaviorsFlag = "";
|
|
4899
|
+
let legacyFallbackWarned = false;
|
|
4900
|
+
if (!useExeAgent && !useBinSymlink) {
|
|
4901
|
+
const identityPath2 = path17.join(
|
|
4902
|
+
os6.homedir(),
|
|
4903
|
+
".exe-os",
|
|
4904
|
+
"identity",
|
|
4905
|
+
`${employeeName}.md`
|
|
4906
|
+
);
|
|
4907
|
+
_resetCcAgentSupportCache();
|
|
4908
|
+
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
4909
|
+
if (hasAgentFlag) {
|
|
4910
|
+
identityFlag = ` --agent ${employeeName}`;
|
|
4911
|
+
} else if (existsSync13(identityPath2)) {
|
|
4912
|
+
identityFlag = ` --append-system-prompt-file ${identityPath2}`;
|
|
4913
|
+
legacyFallbackWarned = true;
|
|
4914
|
+
}
|
|
4915
|
+
const behaviorsFile = exportBehaviorsSync(
|
|
4916
|
+
employeeName,
|
|
4917
|
+
path17.basename(spawnCwd),
|
|
4918
|
+
sessionName
|
|
4919
|
+
);
|
|
4920
|
+
if (behaviorsFile) {
|
|
4921
|
+
behaviorsFlag = ` --append-system-prompt-file ${behaviorsFile}`;
|
|
4922
|
+
}
|
|
4471
4923
|
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
}
|
|
4478
|
-
function recordDebounce(targetSession) {
|
|
4479
|
-
const state = readDebounceState();
|
|
4480
|
-
state[targetSession] = Date.now();
|
|
4481
|
-
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
4482
|
-
for (const key of Object.keys(state)) {
|
|
4483
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
4924
|
+
if (legacyFallbackWarned) {
|
|
4925
|
+
process.stderr.write(
|
|
4926
|
+
`[tmux-routing] claude --agent not supported by installed CC. Falling back to --append-system-prompt-file for ${employeeName}. Upgrade Claude Code to enable native --agent launch.
|
|
4927
|
+
`
|
|
4928
|
+
);
|
|
4484
4929
|
}
|
|
4485
|
-
|
|
4486
|
-
}
|
|
4487
|
-
function logIntercom(msg) {
|
|
4488
|
-
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
4489
|
-
`;
|
|
4490
|
-
process.stderr.write(`[intercom] ${msg}
|
|
4491
|
-
`);
|
|
4930
|
+
let sessionContextFlag = "";
|
|
4492
4931
|
try {
|
|
4493
|
-
|
|
4932
|
+
const ctxDir = path17.join(os6.homedir(), ".exe-os", "session-cache");
|
|
4933
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
4934
|
+
const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4935
|
+
const ctxContent = [
|
|
4936
|
+
`## Session Context`,
|
|
4937
|
+
`You are running in tmux session: ${sessionName}.`,
|
|
4938
|
+
`Your parent exe session is ${exeSession}.`,
|
|
4939
|
+
`Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
|
|
4940
|
+
].join("\n");
|
|
4941
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
4942
|
+
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
4494
4943
|
} catch {
|
|
4495
4944
|
}
|
|
4496
|
-
}
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
4504
|
-
return "no_claude";
|
|
4945
|
+
let envPrefix = `EXE_SESSION=${exeSession} EXE_SESSION_NAME=${sessionName}`;
|
|
4946
|
+
if (ccProvider !== DEFAULT_PROVIDER) {
|
|
4947
|
+
const cfg = PROVIDER_TABLE[ccProvider];
|
|
4948
|
+
if (cfg?.apiKeyEnv) {
|
|
4949
|
+
const keyVal = process.env[cfg.apiKeyEnv];
|
|
4950
|
+
if (keyVal) {
|
|
4951
|
+
envPrefix = `${envPrefix} ${cfg.apiKeyEnv}=${keyVal}`;
|
|
4505
4952
|
}
|
|
4506
4953
|
}
|
|
4507
|
-
if (/Running…/.test(pane)) return "tool";
|
|
4508
|
-
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
4509
|
-
return "idle";
|
|
4510
|
-
} catch {
|
|
4511
|
-
return "offline";
|
|
4512
4954
|
}
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
}
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4955
|
+
let spawnCommand;
|
|
4956
|
+
if (useExeAgent) {
|
|
4957
|
+
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
4958
|
+
} else if (useBinSymlink) {
|
|
4959
|
+
const binName = `${employeeName}-${ccProvider}`;
|
|
4960
|
+
process.stderr.write(
|
|
4961
|
+
`[tmux-routing] provider cascade: ${ccProvider} \u2192 spawning ${binName}
|
|
4962
|
+
`
|
|
4963
|
+
);
|
|
4964
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4965
|
+
} else {
|
|
4966
|
+
spawnCommand = `${envPrefix} claude --dangerously-skip-permissions${identityFlag}${behaviorsFlag}${sessionContextFlag}${cleanupSuffix}`;
|
|
4522
4967
|
}
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4968
|
+
const spawnResult = transport.spawn(sessionName, {
|
|
4969
|
+
cwd: spawnCwd,
|
|
4970
|
+
command: spawnCommand
|
|
4971
|
+
});
|
|
4972
|
+
if (spawnResult.error) {
|
|
4973
|
+
releaseSpawnLock2(sessionName);
|
|
4974
|
+
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4526
4975
|
}
|
|
4976
|
+
transport.pipeLog(sessionName, logFile);
|
|
4527
4977
|
try {
|
|
4528
|
-
const
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
4544
|
-
return "queued";
|
|
4978
|
+
const mySession = getMySession();
|
|
4979
|
+
const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
4980
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
4981
|
+
dispatchedBy: mySession,
|
|
4982
|
+
rootExe: exeSession,
|
|
4983
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
4984
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4985
|
+
}));
|
|
4986
|
+
} catch {
|
|
4987
|
+
}
|
|
4988
|
+
let booted = false;
|
|
4989
|
+
for (let i = 0; i < 30; i++) {
|
|
4990
|
+
try {
|
|
4991
|
+
execSync7("sleep 0.5");
|
|
4992
|
+
} catch {
|
|
4545
4993
|
}
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4994
|
+
try {
|
|
4995
|
+
const pane = transport.capturePane(sessionName);
|
|
4996
|
+
if (useExeAgent) {
|
|
4997
|
+
if (pane.includes("[exe-agent]") || pane.includes("online")) {
|
|
4998
|
+
booted = true;
|
|
4999
|
+
break;
|
|
5000
|
+
}
|
|
5001
|
+
} else {
|
|
5002
|
+
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
5003
|
+
booted = true;
|
|
5004
|
+
break;
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
5007
|
+
} catch {
|
|
4549
5008
|
}
|
|
4550
|
-
transport.sendKeys(targetSession, "/exe-intercom");
|
|
4551
|
-
recordDebounce(targetSession);
|
|
4552
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
4553
|
-
return "delivered";
|
|
4554
|
-
} catch {
|
|
4555
|
-
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
4556
|
-
return "failed";
|
|
4557
5009
|
}
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
if (!target) {
|
|
4562
|
-
process.stderr.write(`[intercom] notifyParentExe: no dispatcher found for key ${sessionKey}
|
|
4563
|
-
`);
|
|
4564
|
-
return false;
|
|
5010
|
+
if (!booted) {
|
|
5011
|
+
releaseSpawnLock2(sessionName);
|
|
5012
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
4565
5013
|
}
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
const rootExe = resolveExeSession();
|
|
4571
|
-
if (rootExe && rootExe !== target) {
|
|
4572
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
4573
|
-
`);
|
|
4574
|
-
const fallback = sendIntercom(rootExe);
|
|
4575
|
-
return fallback !== "failed";
|
|
5014
|
+
if (!useExeAgent) {
|
|
5015
|
+
try {
|
|
5016
|
+
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
5017
|
+
} catch {
|
|
4576
5018
|
}
|
|
4577
|
-
return false;
|
|
4578
5019
|
}
|
|
4579
|
-
|
|
5020
|
+
registerSession({
|
|
5021
|
+
windowName: sessionName,
|
|
5022
|
+
agentId: employeeName,
|
|
5023
|
+
projectDir: spawnCwd,
|
|
5024
|
+
parentExe: exeSession,
|
|
5025
|
+
pid: 0,
|
|
5026
|
+
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5027
|
+
});
|
|
5028
|
+
releaseSpawnLock2(sessionName);
|
|
5029
|
+
return { sessionName };
|
|
4580
5030
|
}
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
5031
|
+
var SPAWN_LOCK_DIR, SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VALID_SESSION_NAME, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
5032
|
+
var init_tmux_routing = __esm({
|
|
5033
|
+
"src/lib/tmux-routing.ts"() {
|
|
5034
|
+
"use strict";
|
|
5035
|
+
init_session_registry();
|
|
5036
|
+
init_session_key();
|
|
5037
|
+
init_transport();
|
|
5038
|
+
init_cc_agent_support();
|
|
5039
|
+
init_mcp_prefix();
|
|
5040
|
+
init_provider_table();
|
|
5041
|
+
init_intercom_queue();
|
|
5042
|
+
init_plan_limits();
|
|
5043
|
+
SPAWN_LOCK_DIR = path17.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
5044
|
+
SESSION_CACHE = path17.join(os6.homedir(), ".exe-os", "session-cache");
|
|
5045
|
+
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
5046
|
+
VALID_SESSION_NAME = /^[a-z]+-exe\d+$|^[a-z]+\d+-exe\d+$/;
|
|
5047
|
+
VERIFY_PANE_LINES = 200;
|
|
5048
|
+
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
5049
|
+
INTERCOM_LOG2 = path17.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
5050
|
+
DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
|
|
5051
|
+
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
5052
|
+
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
4584
5053
|
}
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
5054
|
+
});
|
|
5055
|
+
|
|
5056
|
+
// src/lib/tasks-crud.ts
|
|
5057
|
+
import crypto7 from "crypto";
|
|
5058
|
+
import path18 from "path";
|
|
5059
|
+
import { execSync as execSync8 } from "child_process";
|
|
5060
|
+
import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
|
|
5061
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
5062
|
+
async function writeCheckpoint(input) {
|
|
5063
|
+
const client = getClient();
|
|
5064
|
+
const row = await resolveTask(client, input.taskId);
|
|
5065
|
+
const taskId = String(row.id);
|
|
5066
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5067
|
+
const blockedByIds = [];
|
|
5068
|
+
if (row.blocked_by) {
|
|
5069
|
+
blockedByIds.push(String(row.blocked_by));
|
|
4591
5070
|
}
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
5071
|
+
const checkpoint = {
|
|
5072
|
+
step: input.step,
|
|
5073
|
+
context_summary: input.contextSummary,
|
|
5074
|
+
files_touched: input.filesTouched ?? [],
|
|
5075
|
+
blocked_by_ids: blockedByIds,
|
|
5076
|
+
last_checkpoint_at: now
|
|
5077
|
+
};
|
|
5078
|
+
const result = await client.execute({
|
|
5079
|
+
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
5080
|
+
args: [JSON.stringify(checkpoint), now, taskId]
|
|
5081
|
+
});
|
|
5082
|
+
if (result.rowsAffected === 0) {
|
|
5083
|
+
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
4599
5084
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
5085
|
+
const countResult = await client.execute({
|
|
5086
|
+
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
5087
|
+
args: [taskId]
|
|
5088
|
+
});
|
|
5089
|
+
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
5090
|
+
return { checkpointCount };
|
|
5091
|
+
}
|
|
5092
|
+
function extractParentFromContext(contextBody) {
|
|
5093
|
+
if (!contextBody) return null;
|
|
5094
|
+
const match = contextBody.match(
|
|
5095
|
+
/Parent task:\s*([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
|
|
5096
|
+
);
|
|
5097
|
+
return match ? match[1].toLowerCase() : null;
|
|
5098
|
+
}
|
|
5099
|
+
function slugify(title) {
|
|
5100
|
+
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
5101
|
+
}
|
|
5102
|
+
async function resolveTask(client, identifier) {
|
|
5103
|
+
let result = await client.execute({
|
|
5104
|
+
sql: "SELECT * FROM tasks WHERE id = ?",
|
|
5105
|
+
args: [identifier]
|
|
5106
|
+
});
|
|
5107
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5108
|
+
result = await client.execute({
|
|
5109
|
+
sql: "SELECT * FROM tasks WHERE task_file LIKE ?",
|
|
5110
|
+
args: [`%${identifier}%`]
|
|
5111
|
+
});
|
|
5112
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5113
|
+
if (result.rows.length > 1) {
|
|
5114
|
+
const exact = result.rows.filter(
|
|
5115
|
+
(r) => String(r.task_file).endsWith(`/${identifier}.md`)
|
|
5116
|
+
);
|
|
5117
|
+
if (exact.length === 1) return exact[0];
|
|
5118
|
+
const candidates = exact.length > 1 ? exact : result.rows;
|
|
5119
|
+
const active = candidates.filter(
|
|
5120
|
+
(r) => !["done", "cancelled"].includes(String(r.status))
|
|
5121
|
+
);
|
|
5122
|
+
if (active.length === 1) return active[0];
|
|
5123
|
+
const matches = (active.length > 1 ? active : candidates).map((r) => `${String(r.task_file)} (${String(r.status)}, ${String(r.id)})`).join(", ");
|
|
5124
|
+
throw new Error(
|
|
5125
|
+
`Multiple tasks match "${identifier}": ${matches}. Use a UUID to disambiguate.`
|
|
4606
5126
|
);
|
|
4607
|
-
if (free === null) {
|
|
4608
|
-
return {
|
|
4609
|
-
status: "failed",
|
|
4610
|
-
sessionName: employeeSessionName(employeeName, exeSession),
|
|
4611
|
-
error: `All ${opts.maxAutoInstances ?? 10} instances of ${employeeName} are alive \u2014 cap reached`
|
|
4612
|
-
};
|
|
4613
|
-
}
|
|
4614
|
-
effectiveInstance = free === 0 ? void 0 : free;
|
|
4615
|
-
}
|
|
4616
|
-
const sessionName = employeeSessionName(employeeName, exeSession, effectiveInstance);
|
|
4617
|
-
if (isEmployeeAlive(sessionName)) {
|
|
4618
|
-
const result2 = sendIntercom(sessionName);
|
|
4619
|
-
if (result2 === "acknowledged" || result2 === "skipped_exe" || result2 === "debounced" || result2 === "queued") {
|
|
4620
|
-
return { status: "intercom_sent", sessionName };
|
|
4621
|
-
}
|
|
4622
|
-
if (result2 === "delivered") {
|
|
4623
|
-
return { status: "intercom_unprocessed", sessionName };
|
|
4624
|
-
}
|
|
4625
|
-
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
4626
5127
|
}
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
5128
|
+
result = await client.execute({
|
|
5129
|
+
sql: "SELECT * FROM tasks WHERE title LIKE ?",
|
|
5130
|
+
args: [`%${identifier}%`]
|
|
5131
|
+
});
|
|
5132
|
+
if (result.rows.length === 1) return result.rows[0];
|
|
5133
|
+
if (result.rows.length > 1) {
|
|
5134
|
+
const active = result.rows.filter(
|
|
5135
|
+
(r) => !["done", "cancelled"].includes(String(r.status))
|
|
5136
|
+
);
|
|
5137
|
+
if (active.length === 1) return active[0];
|
|
5138
|
+
const matches = (active.length > 1 ? active : result.rows).map((r) => `"${String(r.title)}" (${String(r.status)}, ${String(r.id)})`).join(", ");
|
|
5139
|
+
throw new Error(
|
|
5140
|
+
`Multiple tasks match "${identifier}": ${matches}. Use a UUID to disambiguate.`
|
|
5141
|
+
);
|
|
4631
5142
|
}
|
|
4632
|
-
|
|
5143
|
+
throw new Error(`Task not found: ${identifier}`);
|
|
4633
5144
|
}
|
|
4634
|
-
function
|
|
4635
|
-
const
|
|
4636
|
-
const
|
|
4637
|
-
const
|
|
4638
|
-
const
|
|
4639
|
-
const
|
|
4640
|
-
|
|
4641
|
-
|
|
5145
|
+
async function createTaskCore(input) {
|
|
5146
|
+
const client = getClient();
|
|
5147
|
+
const id = crypto7.randomUUID();
|
|
5148
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5149
|
+
const slug = slugify(input.title);
|
|
5150
|
+
const taskFile = input.taskFile ?? `exe/${input.assignedTo}/${slug}.md`;
|
|
5151
|
+
let blockedById = null;
|
|
5152
|
+
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
5153
|
+
if (input.blockedBy) {
|
|
5154
|
+
const blocker = await resolveTask(client, input.blockedBy);
|
|
5155
|
+
blockedById = String(blocker.id);
|
|
4642
5156
|
}
|
|
4643
|
-
|
|
4644
|
-
let
|
|
4645
|
-
|
|
4646
|
-
const
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
5157
|
+
let parentTaskId = null;
|
|
5158
|
+
let parentRef = input.parentTaskId;
|
|
5159
|
+
if (!parentRef) {
|
|
5160
|
+
const extracted = extractParentFromContext(input.context);
|
|
5161
|
+
if (extracted) {
|
|
5162
|
+
parentRef = extracted;
|
|
5163
|
+
process.stderr.write(
|
|
5164
|
+
"[create_task] auto-populated parent_task_id from context body \u2014 dispatchers should pass parent_task_id explicitly\n"
|
|
5165
|
+
);
|
|
4650
5166
|
}
|
|
4651
|
-
} catch {
|
|
4652
5167
|
}
|
|
4653
|
-
|
|
4654
|
-
const claudeJsonPath = path18.join(os6.homedir(), ".claude.json");
|
|
4655
|
-
let claudeJson = {};
|
|
5168
|
+
if (parentRef) {
|
|
4656
5169
|
try {
|
|
4657
|
-
|
|
4658
|
-
|
|
5170
|
+
const parent = await resolveTask(client, parentRef);
|
|
5171
|
+
parentTaskId = String(parent.id);
|
|
5172
|
+
} catch (err) {
|
|
5173
|
+
if (!input.parentTaskId) {
|
|
5174
|
+
throw new Error(
|
|
5175
|
+
`create_task: parent reference "${parentRef}" in context body does not resolve to an existing task`
|
|
5176
|
+
);
|
|
5177
|
+
}
|
|
5178
|
+
throw err;
|
|
4659
5179
|
}
|
|
4660
|
-
if (!claudeJson.projects) claudeJson.projects = {};
|
|
4661
|
-
const projects = claudeJson.projects;
|
|
4662
|
-
const trustDir = opts?.cwd ?? projectDir;
|
|
4663
|
-
if (!projects[trustDir]) projects[trustDir] = {};
|
|
4664
|
-
projects[trustDir].hasTrustDialogAccepted = true;
|
|
4665
|
-
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
4666
|
-
} catch {
|
|
4667
5180
|
}
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
5181
|
+
let warning;
|
|
5182
|
+
const dupCheck = await client.execute({
|
|
5183
|
+
sql: "SELECT id FROM tasks WHERE title = ? AND assigned_to = ? AND status IN ('open', 'in_progress', 'blocked')",
|
|
5184
|
+
args: [input.title, input.assignedTo]
|
|
5185
|
+
});
|
|
5186
|
+
if (dupCheck.rows.length > 0) {
|
|
5187
|
+
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
5188
|
+
}
|
|
5189
|
+
if (input.baseDir) {
|
|
4674
5190
|
try {
|
|
4675
|
-
|
|
5191
|
+
await mkdir4(path18.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
5192
|
+
await mkdir4(path18.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
5193
|
+
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
5194
|
+
await ensureGitignoreExe(input.baseDir);
|
|
4676
5195
|
} catch {
|
|
4677
5196
|
}
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
"update_task",
|
|
4685
|
-
"list_tasks",
|
|
4686
|
-
"get_task",
|
|
4687
|
-
"ask_team_memory",
|
|
4688
|
-
"store_behavior",
|
|
4689
|
-
"get_identity",
|
|
4690
|
-
"send_message"
|
|
4691
|
-
];
|
|
4692
|
-
const requiredTools = expandDualPrefixTools(toolNames);
|
|
4693
|
-
let changed = false;
|
|
4694
|
-
for (const tool of requiredTools) {
|
|
4695
|
-
if (!allow.includes(tool)) {
|
|
4696
|
-
allow.push(tool);
|
|
4697
|
-
changed = true;
|
|
4698
|
-
}
|
|
4699
|
-
}
|
|
4700
|
-
if (changed) {
|
|
4701
|
-
perms.allow = allow;
|
|
4702
|
-
settings.permissions = perms;
|
|
4703
|
-
mkdirSync6(projSettingsDir, { recursive: true });
|
|
4704
|
-
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
4705
|
-
}
|
|
5197
|
+
}
|
|
5198
|
+
const complexity = input.complexity ?? "standard";
|
|
5199
|
+
let sessionScope = null;
|
|
5200
|
+
try {
|
|
5201
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
5202
|
+
sessionScope = resolveExeSession2();
|
|
4706
5203
|
} catch {
|
|
4707
5204
|
}
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
5205
|
+
await client.execute({
|
|
5206
|
+
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, session_scope, created_at, updated_at)
|
|
5207
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5208
|
+
args: [
|
|
5209
|
+
id,
|
|
5210
|
+
input.title,
|
|
5211
|
+
input.assignedTo,
|
|
5212
|
+
input.assignedBy,
|
|
5213
|
+
input.projectName,
|
|
5214
|
+
input.priority,
|
|
5215
|
+
initialStatus,
|
|
5216
|
+
taskFile,
|
|
5217
|
+
blockedById,
|
|
5218
|
+
parentTaskId,
|
|
5219
|
+
input.reviewer ?? null,
|
|
5220
|
+
input.context,
|
|
5221
|
+
complexity,
|
|
5222
|
+
input.budgetTokens ?? null,
|
|
5223
|
+
input.budgetFallbackModel ?? null,
|
|
5224
|
+
0,
|
|
5225
|
+
null,
|
|
5226
|
+
sessionScope,
|
|
5227
|
+
now,
|
|
5228
|
+
now
|
|
5229
|
+
]
|
|
5230
|
+
});
|
|
5231
|
+
return {
|
|
5232
|
+
id,
|
|
5233
|
+
title: input.title,
|
|
5234
|
+
assignedTo: input.assignedTo,
|
|
5235
|
+
assignedBy: input.assignedBy,
|
|
5236
|
+
projectName: input.projectName,
|
|
5237
|
+
priority: input.priority,
|
|
5238
|
+
status: initialStatus,
|
|
5239
|
+
taskFile,
|
|
5240
|
+
createdAt: now,
|
|
5241
|
+
updatedAt: now,
|
|
5242
|
+
warning,
|
|
5243
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
5244
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
5245
|
+
tokensUsed: 0,
|
|
5246
|
+
tokensWarnedAt: null
|
|
5247
|
+
};
|
|
5248
|
+
}
|
|
5249
|
+
async function listTasks(input) {
|
|
5250
|
+
const client = getClient();
|
|
5251
|
+
const conditions = [];
|
|
5252
|
+
const args = [];
|
|
5253
|
+
if (input.assignedTo) {
|
|
5254
|
+
conditions.push("assigned_to = ?");
|
|
5255
|
+
args.push(input.assignedTo);
|
|
5256
|
+
}
|
|
5257
|
+
if (input.status) {
|
|
5258
|
+
conditions.push("status = ?");
|
|
5259
|
+
args.push(input.status);
|
|
5260
|
+
} else {
|
|
5261
|
+
conditions.push("status IN ('open', 'in_progress', 'blocked')");
|
|
4738
5262
|
}
|
|
4739
|
-
if (
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
5263
|
+
if (input.projectName) {
|
|
5264
|
+
conditions.push("project_name = ?");
|
|
5265
|
+
args.push(input.projectName);
|
|
5266
|
+
}
|
|
5267
|
+
if (input.priority) {
|
|
5268
|
+
conditions.push("priority = ?");
|
|
5269
|
+
args.push(input.priority);
|
|
4744
5270
|
}
|
|
4745
|
-
let sessionContextFlag = "";
|
|
4746
5271
|
try {
|
|
4747
|
-
const
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
`Your parent exe session is ${exeSession}.`,
|
|
4754
|
-
`Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
|
|
4755
|
-
].join("\n");
|
|
4756
|
-
writeFileSync7(ctxFile, ctxContent);
|
|
4757
|
-
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
5272
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
5273
|
+
const session = resolveExeSession2();
|
|
5274
|
+
if (session) {
|
|
5275
|
+
conditions.push("(session_scope IS NULL OR session_scope = ?)");
|
|
5276
|
+
args.push(session);
|
|
5277
|
+
}
|
|
4758
5278
|
} catch {
|
|
4759
5279
|
}
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
5280
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
5281
|
+
const result = await client.execute({
|
|
5282
|
+
sql: `SELECT * FROM tasks ${where} ORDER BY CASE status WHEN 'blocked' THEN 0 WHEN 'in_progress' THEN 1 WHEN 'open' THEN 2 ELSE 3 END, priority ASC, created_at DESC LIMIT 1000`,
|
|
5283
|
+
args
|
|
5284
|
+
});
|
|
5285
|
+
return result.rows.map((r) => ({
|
|
5286
|
+
id: String(r.id),
|
|
5287
|
+
title: String(r.title),
|
|
5288
|
+
assignedTo: String(r.assigned_to),
|
|
5289
|
+
assignedBy: String(r.assigned_by),
|
|
5290
|
+
projectName: String(r.project_name),
|
|
5291
|
+
priority: String(r.priority),
|
|
5292
|
+
status: String(r.status),
|
|
5293
|
+
taskFile: String(r.task_file),
|
|
5294
|
+
createdAt: String(r.created_at),
|
|
5295
|
+
updatedAt: String(r.updated_at),
|
|
5296
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
5297
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
5298
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
5299
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
5300
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
5301
|
+
}));
|
|
5302
|
+
}
|
|
5303
|
+
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
5304
|
+
if (!taskContext) return null;
|
|
5305
|
+
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
5306
|
+
try {
|
|
5307
|
+
const since = new Date(taskCreatedAt).toISOString();
|
|
5308
|
+
const branch = execSync8(
|
|
5309
|
+
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
5310
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
5311
|
+
).trim();
|
|
5312
|
+
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
5313
|
+
const commitCount = execSync8(
|
|
5314
|
+
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
5315
|
+
{ encoding: "utf8", timeout: 5e3 }
|
|
5316
|
+
).trim();
|
|
5317
|
+
const count = parseInt(commitCount, 10);
|
|
5318
|
+
if (count === 0) {
|
|
5319
|
+
return "WARNING: task closed with no new commits since creation. Verify work was actually produced.";
|
|
4768
5320
|
}
|
|
5321
|
+
return null;
|
|
5322
|
+
} catch {
|
|
5323
|
+
return null;
|
|
4769
5324
|
}
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
5325
|
+
}
|
|
5326
|
+
async function updateTaskStatus(input) {
|
|
5327
|
+
const client = getClient();
|
|
5328
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5329
|
+
const row = await resolveTask(client, input.taskId);
|
|
5330
|
+
const taskId = String(row.id);
|
|
5331
|
+
const taskFile = String(row.task_file);
|
|
5332
|
+
if (input.status === "done" && String(row.assigned_by) === "system" && taskFile.includes("review-")) {
|
|
4775
5333
|
process.stderr.write(
|
|
4776
|
-
`[
|
|
5334
|
+
`[updateTask] Review task "${String(row.title)}" being marked done (assigned to ${String(row.assigned_to)})
|
|
4777
5335
|
`
|
|
4778
5336
|
);
|
|
4779
|
-
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
4780
|
-
} else {
|
|
4781
|
-
spawnCommand = `${envPrefix} claude --dangerously-skip-permissions${identityFlag}${behaviorsFlag}${sessionContextFlag}${cleanupSuffix}`;
|
|
4782
|
-
}
|
|
4783
|
-
const spawnResult = transport.spawn(sessionName, {
|
|
4784
|
-
cwd: spawnCwd,
|
|
4785
|
-
command: spawnCommand
|
|
4786
|
-
});
|
|
4787
|
-
if (spawnResult.error) {
|
|
4788
|
-
releaseSpawnLock2(sessionName);
|
|
4789
|
-
return { sessionName, error: `tmux new-session failed: ${spawnResult.error}` };
|
|
4790
5337
|
}
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
5338
|
+
if (input.status === "done") {
|
|
5339
|
+
const existingRow = await client.execute({
|
|
5340
|
+
sql: "SELECT context, created_at FROM tasks WHERE id = ?",
|
|
5341
|
+
args: [taskId]
|
|
5342
|
+
});
|
|
5343
|
+
if (existingRow.rows.length > 0) {
|
|
5344
|
+
const ctx = existingRow.rows[0];
|
|
5345
|
+
const warning = checkStaleCompletion(ctx.context, ctx.created_at);
|
|
5346
|
+
if (warning) {
|
|
5347
|
+
input.result = input.result ? `\u26A0\uFE0F ${warning}
|
|
5348
|
+
|
|
5349
|
+
${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
5350
|
+
process.stderr.write(`[tasks] ${warning} (task: ${taskId})
|
|
5351
|
+
`);
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
4802
5354
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
5355
|
+
if (input.status === "in_progress") {
|
|
5356
|
+
const tmuxSession = process.env.TMUX_PANE ?? process.env.MY_TMUX_SESSION ?? "unknown";
|
|
5357
|
+
const claim = await client.execute({
|
|
5358
|
+
sql: `UPDATE tasks
|
|
5359
|
+
SET status = 'in_progress', assigned_tmux = ?, updated_at = ?
|
|
5360
|
+
WHERE id = ? AND status = 'open'`,
|
|
5361
|
+
args: [tmuxSession, now, taskId]
|
|
5362
|
+
});
|
|
5363
|
+
if (claim.rowsAffected === 0) {
|
|
5364
|
+
const current = await client.execute({
|
|
5365
|
+
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
5366
|
+
args: [taskId]
|
|
5367
|
+
});
|
|
5368
|
+
const cur = current.rows[0];
|
|
5369
|
+
const status = cur?.status ?? "unknown";
|
|
5370
|
+
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
5371
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
4808
5372
|
}
|
|
4809
5373
|
try {
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
}
|
|
4816
|
-
} else {
|
|
4817
|
-
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
4818
|
-
booted = true;
|
|
4819
|
-
break;
|
|
4820
|
-
}
|
|
4821
|
-
}
|
|
5374
|
+
await writeCheckpoint({
|
|
5375
|
+
taskId,
|
|
5376
|
+
step: "claimed",
|
|
5377
|
+
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
5378
|
+
});
|
|
4822
5379
|
} catch {
|
|
4823
5380
|
}
|
|
5381
|
+
return { row, taskFile, now, taskId };
|
|
4824
5382
|
}
|
|
4825
|
-
if (
|
|
4826
|
-
|
|
4827
|
-
|
|
5383
|
+
if (input.result) {
|
|
5384
|
+
await client.execute({
|
|
5385
|
+
sql: "UPDATE tasks SET status = ?, result = ?, updated_at = ? WHERE id = ?",
|
|
5386
|
+
args: [input.status, input.result, now, taskId]
|
|
5387
|
+
});
|
|
5388
|
+
} else {
|
|
5389
|
+
await client.execute({
|
|
5390
|
+
sql: "UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?",
|
|
5391
|
+
args: [input.status, now, taskId]
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
try {
|
|
5395
|
+
await writeCheckpoint({
|
|
5396
|
+
taskId,
|
|
5397
|
+
step: `status_transition:${input.status}`,
|
|
5398
|
+
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
5399
|
+
});
|
|
5400
|
+
} catch {
|
|
5401
|
+
}
|
|
5402
|
+
return { row, taskFile, now, taskId };
|
|
5403
|
+
}
|
|
5404
|
+
async function deleteTaskCore(taskId, _baseDir) {
|
|
5405
|
+
const client = getClient();
|
|
5406
|
+
const row = await resolveTask(client, taskId);
|
|
5407
|
+
const id = String(row.id);
|
|
5408
|
+
const taskFile = String(row.task_file);
|
|
5409
|
+
const assignedTo = String(row.assigned_to);
|
|
5410
|
+
const assignedBy = String(row.assigned_by);
|
|
5411
|
+
await client.execute({ sql: "DELETE FROM tasks WHERE id = ?", args: [id] });
|
|
5412
|
+
const taskSlug = taskFile.split("/").pop()?.replace(".md", "") ?? "";
|
|
5413
|
+
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
5414
|
+
}
|
|
5415
|
+
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
5416
|
+
const archPath = path18.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
5417
|
+
try {
|
|
5418
|
+
if (existsSync14(archPath)) return;
|
|
5419
|
+
const template = [
|
|
5420
|
+
`# ${projectName} \u2014 System Architecture`,
|
|
5421
|
+
"",
|
|
5422
|
+
"> Employees: read this before every task. Update it when you change system structure.",
|
|
5423
|
+
`> Last updated: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}`,
|
|
5424
|
+
"",
|
|
5425
|
+
"## Overview",
|
|
5426
|
+
"",
|
|
5427
|
+
"<!-- Describe what this system does, its main components, and how they connect. -->",
|
|
5428
|
+
"",
|
|
5429
|
+
"## Key Components",
|
|
5430
|
+
"",
|
|
5431
|
+
"<!-- List the major modules, services, or subsystems. -->",
|
|
5432
|
+
"",
|
|
5433
|
+
"## Data Flow",
|
|
5434
|
+
"",
|
|
5435
|
+
"<!-- How does data move through the system? What writes where? -->",
|
|
5436
|
+
"",
|
|
5437
|
+
"## Invariants",
|
|
5438
|
+
"",
|
|
5439
|
+
"<!-- Rules that must never be violated. What breaks if these are wrong? -->",
|
|
5440
|
+
"",
|
|
5441
|
+
"## Dependencies",
|
|
5442
|
+
"",
|
|
5443
|
+
"<!-- What depends on what? If I change X, what else is affected? -->",
|
|
5444
|
+
""
|
|
5445
|
+
].join("\n");
|
|
5446
|
+
await writeFile4(archPath, template, "utf-8");
|
|
5447
|
+
} catch {
|
|
4828
5448
|
}
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
5449
|
+
}
|
|
5450
|
+
async function ensureGitignoreExe(baseDir) {
|
|
5451
|
+
const gitignorePath = path18.join(baseDir, ".gitignore");
|
|
5452
|
+
try {
|
|
5453
|
+
if (existsSync14(gitignorePath)) {
|
|
5454
|
+
const content = readFileSync12(gitignorePath, "utf-8");
|
|
5455
|
+
if (/^\/?exe\/?$/m.test(content)) return;
|
|
5456
|
+
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
5457
|
+
} else {
|
|
5458
|
+
await writeFile4(gitignorePath, "# Employee task assignments (private)\n/exe/\n", "utf-8");
|
|
4833
5459
|
}
|
|
5460
|
+
} catch {
|
|
4834
5461
|
}
|
|
4835
|
-
registerSession({
|
|
4836
|
-
windowName: sessionName,
|
|
4837
|
-
agentId: employeeName,
|
|
4838
|
-
projectDir: spawnCwd,
|
|
4839
|
-
parentExe: exeSession,
|
|
4840
|
-
pid: 0,
|
|
4841
|
-
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4842
|
-
});
|
|
4843
|
-
releaseSpawnLock2(sessionName);
|
|
4844
|
-
return { sessionName };
|
|
4845
5462
|
}
|
|
4846
|
-
var
|
|
4847
|
-
var
|
|
4848
|
-
"src/lib/
|
|
5463
|
+
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
5464
|
+
var init_tasks_crud = __esm({
|
|
5465
|
+
"src/lib/tasks-crud.ts"() {
|
|
4849
5466
|
"use strict";
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
init_cc_agent_support();
|
|
4854
|
-
init_mcp_prefix();
|
|
4855
|
-
init_provider_table();
|
|
4856
|
-
init_intercom_queue();
|
|
4857
|
-
init_plan_limits();
|
|
4858
|
-
SPAWN_LOCK_DIR = path18.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
4859
|
-
SESSION_CACHE = path18.join(os6.homedir(), ".exe-os", "session-cache");
|
|
4860
|
-
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4861
|
-
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4862
|
-
INTERCOM_LOG2 = path18.join(os6.homedir(), ".exe-os", "intercom.log");
|
|
4863
|
-
DEBOUNCE_FILE = path18.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4864
|
-
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4865
|
-
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
5467
|
+
init_database();
|
|
5468
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
|
|
5469
|
+
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
4866
5470
|
}
|
|
4867
5471
|
});
|
|
4868
5472
|
|
|
@@ -5037,6 +5641,7 @@ var init_tasks_review = __esm({
|
|
|
5037
5641
|
init_tasks_crud();
|
|
5038
5642
|
init_tmux_routing();
|
|
5039
5643
|
init_session_key();
|
|
5644
|
+
init_state_bus();
|
|
5040
5645
|
}
|
|
5041
5646
|
});
|
|
5042
5647
|
|
|
@@ -5159,13 +5764,12 @@ function assertSessionScope(actionType, targetProject) {
|
|
|
5159
5764
|
};
|
|
5160
5765
|
}
|
|
5161
5766
|
process.stderr.write(
|
|
5162
|
-
`[session-scope]
|
|
5767
|
+
`[session-scope] BLOCKED cross-project ${actionType}: session project="${currentProject}" \u2260 target project="${targetProject}"
|
|
5163
5768
|
`
|
|
5164
5769
|
);
|
|
5165
5770
|
return {
|
|
5166
|
-
allowed:
|
|
5167
|
-
|
|
5168
|
-
reason: "cross_session_granted",
|
|
5771
|
+
allowed: false,
|
|
5772
|
+
reason: "cross_session_denied",
|
|
5169
5773
|
currentProject,
|
|
5170
5774
|
targetProject,
|
|
5171
5775
|
targetSession: findSessionForProject(targetProject)?.windowName
|
|
@@ -5191,8 +5795,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
5191
5795
|
try {
|
|
5192
5796
|
const { assertSessionScope: assertSessionScope2 } = (init_session_scope(), __toCommonJS(session_scope_exports));
|
|
5193
5797
|
const check = assertSessionScope2("dispatch_task", input.projectName);
|
|
5194
|
-
if (check.reason === "
|
|
5798
|
+
if (check.reason === "cross_session_denied") {
|
|
5195
5799
|
crossProject = true;
|
|
5800
|
+
return { dispatched: "skipped", crossProject: true };
|
|
5196
5801
|
}
|
|
5197
5802
|
} catch {
|
|
5198
5803
|
}
|
|
@@ -5249,10 +5854,10 @@ var init_tasks_notify = __esm({
|
|
|
5249
5854
|
});
|
|
5250
5855
|
|
|
5251
5856
|
// src/lib/behaviors.ts
|
|
5252
|
-
import
|
|
5857
|
+
import crypto8 from "crypto";
|
|
5253
5858
|
async function storeBehavior(opts) {
|
|
5254
5859
|
const client = getClient();
|
|
5255
|
-
const id =
|
|
5860
|
+
const id = crypto8.randomUUID();
|
|
5256
5861
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5257
5862
|
await client.execute({
|
|
5258
5863
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -5309,7 +5914,7 @@ __export(skill_learning_exports, {
|
|
|
5309
5914
|
storeTrajectory: () => storeTrajectory,
|
|
5310
5915
|
sweepTrajectories: () => sweepTrajectories
|
|
5311
5916
|
});
|
|
5312
|
-
import
|
|
5917
|
+
import crypto9 from "crypto";
|
|
5313
5918
|
async function extractTrajectory(taskId, agentId) {
|
|
5314
5919
|
const client = getClient();
|
|
5315
5920
|
const result = await client.execute({
|
|
@@ -5338,11 +5943,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
5338
5943
|
return signature;
|
|
5339
5944
|
}
|
|
5340
5945
|
function hashSignature(signature) {
|
|
5341
|
-
return
|
|
5946
|
+
return crypto9.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
5342
5947
|
}
|
|
5343
5948
|
async function storeTrajectory(opts) {
|
|
5344
5949
|
const client = getClient();
|
|
5345
|
-
const id =
|
|
5950
|
+
const id = crypto9.randomUUID();
|
|
5346
5951
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5347
5952
|
const signatureHash = hashSignature(opts.signature);
|
|
5348
5953
|
await client.execute({
|
|
@@ -5683,6 +6288,13 @@ async function updateTask(input) {
|
|
|
5683
6288
|
await cascadeUnblock(taskId, input.baseDir, now);
|
|
5684
6289
|
} catch {
|
|
5685
6290
|
}
|
|
6291
|
+
orgBus.emit({
|
|
6292
|
+
type: "task_completed",
|
|
6293
|
+
taskId,
|
|
6294
|
+
employee: String(row.assigned_to),
|
|
6295
|
+
result: input.result ?? "",
|
|
6296
|
+
timestamp: now
|
|
6297
|
+
});
|
|
5686
6298
|
if (row.parent_task_id) {
|
|
5687
6299
|
try {
|
|
5688
6300
|
await checkSubtaskCompletion(String(row.parent_task_id), String(row.project_name));
|
|
@@ -5750,6 +6362,7 @@ var init_tasks = __esm({
|
|
|
5750
6362
|
init_database();
|
|
5751
6363
|
init_config();
|
|
5752
6364
|
init_notifications();
|
|
6365
|
+
init_state_bus();
|
|
5753
6366
|
init_tasks_crud();
|
|
5754
6367
|
init_tasks_review();
|
|
5755
6368
|
init_tasks_crud();
|
|
@@ -5759,87 +6372,6 @@ var init_tasks = __esm({
|
|
|
5759
6372
|
}
|
|
5760
6373
|
});
|
|
5761
6374
|
|
|
5762
|
-
// src/lib/session-kill-telemetry.ts
|
|
5763
|
-
import crypto9 from "crypto";
|
|
5764
|
-
var init_session_kill_telemetry = __esm({
|
|
5765
|
-
"src/lib/session-kill-telemetry.ts"() {
|
|
5766
|
-
"use strict";
|
|
5767
|
-
init_database();
|
|
5768
|
-
}
|
|
5769
|
-
});
|
|
5770
|
-
|
|
5771
|
-
// src/lib/capacity-monitor.ts
|
|
5772
|
-
function resumeTaskTitle(agentId) {
|
|
5773
|
-
return `${RESUME_TITLE_PREFIX} ${agentId} hit context capacity \u2014 continue open tasks`;
|
|
5774
|
-
}
|
|
5775
|
-
function buildResumeContext(agentId, openTasks) {
|
|
5776
|
-
const taskList = openTasks.map(
|
|
5777
|
-
(r, i) => `${i + 1}. [${String(r.priority).toUpperCase()}] ${String(r.title)} (${String(r.task_file)})`
|
|
5778
|
-
).join("\n");
|
|
5779
|
-
return [
|
|
5780
|
-
"## Context",
|
|
5781
|
-
"",
|
|
5782
|
-
`${agentId} hit context capacity and was auto-relaunched by the capacity monitor.`,
|
|
5783
|
-
"Call recall_my_memory first \u2014 search for 'CONTEXT CHECKPOINT'. Pick up where the previous session stopped.",
|
|
5784
|
-
"",
|
|
5785
|
-
`You have ${openTasks.length} open task(s). Work through them in priority order:`,
|
|
5786
|
-
"",
|
|
5787
|
-
taskList,
|
|
5788
|
-
"",
|
|
5789
|
-
"Read each task file and chain through them. Build and commit after each one."
|
|
5790
|
-
].join("\n");
|
|
5791
|
-
}
|
|
5792
|
-
async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
5793
|
-
const client = getClient();
|
|
5794
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5795
|
-
const context = buildResumeContext(agentId, openTasks);
|
|
5796
|
-
const existing = await client.execute({
|
|
5797
|
-
sql: `SELECT id FROM tasks
|
|
5798
|
-
WHERE assigned_to = ?
|
|
5799
|
-
AND title LIKE ?
|
|
5800
|
-
AND status IN (${RESUME_ACTIVE_STATUSES.map(() => "?").join(", ")})
|
|
5801
|
-
ORDER BY created_at DESC
|
|
5802
|
-
LIMIT 1`,
|
|
5803
|
-
args: [agentId, RESUME_TITLE_LIKE_PATTERN, ...RESUME_ACTIVE_STATUSES]
|
|
5804
|
-
});
|
|
5805
|
-
if (existing.rows.length > 0) {
|
|
5806
|
-
const taskId = String(existing.rows[0].id);
|
|
5807
|
-
await client.execute({
|
|
5808
|
-
sql: `UPDATE tasks SET context = ?, updated_at = ? WHERE id = ?`,
|
|
5809
|
-
args: [context, now, taskId]
|
|
5810
|
-
});
|
|
5811
|
-
return { created: false, taskId };
|
|
5812
|
-
}
|
|
5813
|
-
const { createTask: createTask2 } = await Promise.resolve().then(() => (init_tasks(), tasks_exports));
|
|
5814
|
-
const task = await createTask2({
|
|
5815
|
-
title: resumeTaskTitle(agentId),
|
|
5816
|
-
assignedTo: agentId,
|
|
5817
|
-
assignedBy: "system",
|
|
5818
|
-
projectName: projectDir.split("/").pop() ?? "unknown",
|
|
5819
|
-
priority: "p0",
|
|
5820
|
-
context,
|
|
5821
|
-
baseDir: projectDir
|
|
5822
|
-
});
|
|
5823
|
-
return { created: true, taskId: task.id };
|
|
5824
|
-
}
|
|
5825
|
-
var RELAUNCH_COOLDOWN_MS, RESUME_TITLE_PREFIX, RESUME_TITLE_LIKE_PATTERN, RESUME_ACTIVE_STATUSES, CONFIRMATION_WINDOW_MS;
|
|
5826
|
-
var init_capacity_monitor = __esm({
|
|
5827
|
-
"src/lib/capacity-monitor.ts"() {
|
|
5828
|
-
"use strict";
|
|
5829
|
-
init_session_registry();
|
|
5830
|
-
init_transport();
|
|
5831
|
-
init_notifications();
|
|
5832
|
-
init_database();
|
|
5833
|
-
init_session_kill_telemetry();
|
|
5834
|
-
init_tmux_routing();
|
|
5835
|
-
RELAUNCH_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
5836
|
-
RESUME_TITLE_PREFIX = "RESUME:";
|
|
5837
|
-
RESUME_TITLE_LIKE_PATTERN = `${RESUME_TITLE_PREFIX} % hit context capacity%`;
|
|
5838
|
-
RESUME_ACTIVE_STATUSES = ["open", "in_progress"];
|
|
5839
|
-
CONFIRMATION_WINDOW_MS = 3 * 60 * 1e3;
|
|
5840
|
-
}
|
|
5841
|
-
});
|
|
5842
|
-
|
|
5843
6375
|
// src/lib/messaging.ts
|
|
5844
6376
|
import crypto10 from "crypto";
|
|
5845
6377
|
function generateUlid() {
|
|
@@ -6040,7 +6572,7 @@ __export(consolidation_exports, {
|
|
|
6040
6572
|
selectUnconsolidated: () => selectUnconsolidated,
|
|
6041
6573
|
storeConsolidation: () => storeConsolidation
|
|
6042
6574
|
});
|
|
6043
|
-
import { randomUUID as
|
|
6575
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
6044
6576
|
async function selectUnconsolidated(client, limit = 200) {
|
|
6045
6577
|
const result = await client.execute({
|
|
6046
6578
|
sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
|
|
@@ -6136,7 +6668,7 @@ async function consolidateCluster(cluster, model) {
|
|
|
6136
6668
|
return textBlock?.text ?? "";
|
|
6137
6669
|
}
|
|
6138
6670
|
async function storeConsolidation(client, cluster, synthesisText, embedFn) {
|
|
6139
|
-
const consolidatedId =
|
|
6671
|
+
const consolidatedId = randomUUID5();
|
|
6140
6672
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6141
6673
|
const rawText = `CONSOLIDATION [${cluster.dateRange}, ${cluster.projectName}]:
|
|
6142
6674
|
|
|
@@ -6161,7 +6693,7 @@ ${synthesisText}`;
|
|
|
6161
6693
|
const linkStmts = sourceIds.map((sourceId) => ({
|
|
6162
6694
|
sql: `INSERT INTO consolidations (id, consolidated_memory_id, source_memory_id, created_at)
|
|
6163
6695
|
VALUES (?, ?, ?, ?)`,
|
|
6164
|
-
args: [
|
|
6696
|
+
args: [randomUUID5(), consolidatedId, sourceId, now]
|
|
6165
6697
|
}));
|
|
6166
6698
|
const placeholders = sourceIds.map(() => "?").join(",");
|
|
6167
6699
|
const markStmt = {
|
|
@@ -9386,7 +9918,7 @@ import { z as z29 } from "zod";
|
|
|
9386
9918
|
|
|
9387
9919
|
// src/automation/trigger-engine.ts
|
|
9388
9920
|
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync17, mkdirSync as mkdirSync9 } from "fs";
|
|
9389
|
-
import { randomUUID as
|
|
9921
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
9390
9922
|
import path23 from "path";
|
|
9391
9923
|
import os7 from "os";
|
|
9392
9924
|
var TRIGGERS_PATH = path23.join(os7.homedir(), ".exe-os", "triggers.json");
|
|
@@ -9412,7 +9944,7 @@ function saveTriggers(triggers) {
|
|
|
9412
9944
|
function createNewTrigger(input) {
|
|
9413
9945
|
const triggers = loadTriggers();
|
|
9414
9946
|
const trigger = {
|
|
9415
|
-
id:
|
|
9947
|
+
id: randomUUID4().slice(0, 8),
|
|
9416
9948
|
...input
|
|
9417
9949
|
};
|
|
9418
9950
|
triggers.push(trigger);
|
|
@@ -9813,6 +10345,7 @@ import { existsSync as existsSync19, mkdirSync as mkdirSync10, writeFileSync as
|
|
|
9813
10345
|
import path25 from "path";
|
|
9814
10346
|
|
|
9815
10347
|
// src/lib/employee-templates.ts
|
|
10348
|
+
init_global_procedures();
|
|
9816
10349
|
var CLIENT_COO_TEMPLATE = `---
|
|
9817
10350
|
role: client-coo
|
|
9818
10351
|
title: Chief Operating Officer
|
|
@@ -11381,6 +11914,157 @@ Consolidated summaries stored as tier-1 (importance=9) memories.`
|
|
|
11381
11914
|
);
|
|
11382
11915
|
}
|
|
11383
11916
|
|
|
11917
|
+
// src/mcp/tools/store-global-procedure.ts
|
|
11918
|
+
init_global_procedures();
|
|
11919
|
+
init_active_agent();
|
|
11920
|
+
import { z as z41 } from "zod";
|
|
11921
|
+
function registerStoreGlobalProcedure(server2) {
|
|
11922
|
+
server2.registerTool(
|
|
11923
|
+
"store_global_procedure",
|
|
11924
|
+
{
|
|
11925
|
+
title: "Store Global Procedure",
|
|
11926
|
+
description: "Create an organization-wide procedure (Layer 0) that supersedes identity, expertise, and experience. Use for hard rules that every employee must follow. RESTRICTED: only exe or founder sessions.",
|
|
11927
|
+
inputSchema: {
|
|
11928
|
+
title: z41.string().describe("Short title for the procedure"),
|
|
11929
|
+
content: z41.string().max(500).describe("The procedure content \u2014 clear, actionable instruction"),
|
|
11930
|
+
priority: z41.enum(["p0", "p1", "p2"]).optional().describe("Priority tier. p0 = always (default). p1 = standard. p2 = nice-to-have."),
|
|
11931
|
+
domain: z41.string().optional().describe("Category: workflow, code-style, communication, architecture, testing, security")
|
|
11932
|
+
}
|
|
11933
|
+
},
|
|
11934
|
+
async ({ title, content, priority, domain }) => {
|
|
11935
|
+
const caller = getActiveAgent();
|
|
11936
|
+
const allowed = caller.agentId === "default" || caller.agentRole === "COO";
|
|
11937
|
+
if (!allowed) {
|
|
11938
|
+
return {
|
|
11939
|
+
content: [{
|
|
11940
|
+
type: "text",
|
|
11941
|
+
text: `Permission denied. Only exe or founder sessions can create global procedures. You are "${caller.agentId}".`
|
|
11942
|
+
}],
|
|
11943
|
+
isError: true
|
|
11944
|
+
};
|
|
11945
|
+
}
|
|
11946
|
+
const id = await storeGlobalProcedure({
|
|
11947
|
+
title,
|
|
11948
|
+
content,
|
|
11949
|
+
priority: priority ?? "p0",
|
|
11950
|
+
domain: domain ?? void 0
|
|
11951
|
+
});
|
|
11952
|
+
return {
|
|
11953
|
+
content: [{
|
|
11954
|
+
type: "text",
|
|
11955
|
+
text: `Global procedure stored.
|
|
11956
|
+
ID: ${id}
|
|
11957
|
+
Title: ${title}
|
|
11958
|
+
Priority: ${priority ?? "p0"}
|
|
11959
|
+
Domain: ${domain ?? "none"}`
|
|
11960
|
+
}]
|
|
11961
|
+
};
|
|
11962
|
+
}
|
|
11963
|
+
);
|
|
11964
|
+
}
|
|
11965
|
+
|
|
11966
|
+
// src/mcp/tools/list-global-procedures.ts
|
|
11967
|
+
init_global_procedures();
|
|
11968
|
+
function registerListGlobalProcedures(server2) {
|
|
11969
|
+
server2.registerTool(
|
|
11970
|
+
"list_global_procedures",
|
|
11971
|
+
{
|
|
11972
|
+
title: "List Global Procedures",
|
|
11973
|
+
description: "List all active organization-wide procedures (Layer 0). These supersede identity, expertise, and experience.",
|
|
11974
|
+
inputSchema: {}
|
|
11975
|
+
},
|
|
11976
|
+
async () => {
|
|
11977
|
+
const procedures = await loadGlobalProcedures();
|
|
11978
|
+
if (procedures.length === 0) {
|
|
11979
|
+
return {
|
|
11980
|
+
content: [{
|
|
11981
|
+
type: "text",
|
|
11982
|
+
text: "No active global procedures."
|
|
11983
|
+
}]
|
|
11984
|
+
};
|
|
11985
|
+
}
|
|
11986
|
+
const lines = procedures.map(
|
|
11987
|
+
(p) => `[${p.id}] (${p.priority}) ${p.title}${p.domain ? ` [${p.domain}]` : ""}
|
|
11988
|
+
${p.content}`
|
|
11989
|
+
);
|
|
11990
|
+
return {
|
|
11991
|
+
content: [{
|
|
11992
|
+
type: "text",
|
|
11993
|
+
text: `Active global procedures (${procedures.length}):
|
|
11994
|
+
|
|
11995
|
+
${lines.join("\n\n")}`
|
|
11996
|
+
}]
|
|
11997
|
+
};
|
|
11998
|
+
}
|
|
11999
|
+
);
|
|
12000
|
+
}
|
|
12001
|
+
|
|
12002
|
+
// src/mcp/tools/deactivate-global-procedure.ts
|
|
12003
|
+
init_global_procedures();
|
|
12004
|
+
init_active_agent();
|
|
12005
|
+
init_database();
|
|
12006
|
+
import { z as z42 } from "zod";
|
|
12007
|
+
function registerDeactivateGlobalProcedure(server2) {
|
|
12008
|
+
server2.registerTool(
|
|
12009
|
+
"deactivate_global_procedure",
|
|
12010
|
+
{
|
|
12011
|
+
title: "Deactivate Global Procedure",
|
|
12012
|
+
description: "Soft-delete a global procedure by setting active = 0. RESTRICTED: only exe or founder sessions. Use list_global_procedures to find the procedure ID first.",
|
|
12013
|
+
inputSchema: {
|
|
12014
|
+
procedure_id: z42.string().describe("UUID of the global procedure to deactivate")
|
|
12015
|
+
}
|
|
12016
|
+
},
|
|
12017
|
+
async ({ procedure_id }) => {
|
|
12018
|
+
const caller = getActiveAgent();
|
|
12019
|
+
const allowed = caller.agentId === "default" || caller.agentRole === "COO";
|
|
12020
|
+
if (!allowed) {
|
|
12021
|
+
return {
|
|
12022
|
+
content: [{
|
|
12023
|
+
type: "text",
|
|
12024
|
+
text: `Permission denied. Only exe or founder sessions can deactivate global procedures. You are "${caller.agentId}".`
|
|
12025
|
+
}],
|
|
12026
|
+
isError: true
|
|
12027
|
+
};
|
|
12028
|
+
}
|
|
12029
|
+
const client = getClient();
|
|
12030
|
+
const result = await client.execute({
|
|
12031
|
+
sql: "SELECT id, title, content, priority, domain FROM global_procedures WHERE id = ?",
|
|
12032
|
+
args: [procedure_id]
|
|
12033
|
+
});
|
|
12034
|
+
if (result.rows.length === 0) {
|
|
12035
|
+
return {
|
|
12036
|
+
content: [{
|
|
12037
|
+
type: "text",
|
|
12038
|
+
text: `Global procedure not found: ${procedure_id}`
|
|
12039
|
+
}],
|
|
12040
|
+
isError: true
|
|
12041
|
+
};
|
|
12042
|
+
}
|
|
12043
|
+
const row = result.rows[0];
|
|
12044
|
+
const wasActive = await deactivateGlobalProcedure(procedure_id);
|
|
12045
|
+
if (!wasActive) {
|
|
12046
|
+
return {
|
|
12047
|
+
content: [{
|
|
12048
|
+
type: "text",
|
|
12049
|
+
text: `Global procedure ${procedure_id} was already inactive.`
|
|
12050
|
+
}]
|
|
12051
|
+
};
|
|
12052
|
+
}
|
|
12053
|
+
return {
|
|
12054
|
+
content: [{
|
|
12055
|
+
type: "text",
|
|
12056
|
+
text: `Deactivated global procedure.
|
|
12057
|
+
ID: ${row.id}
|
|
12058
|
+
Title: ${row.title}
|
|
12059
|
+
Priority: ${row.priority}
|
|
12060
|
+
Domain: ${row.domain ?? "none"}
|
|
12061
|
+
Content: ${row.content}`
|
|
12062
|
+
}]
|
|
12063
|
+
};
|
|
12064
|
+
}
|
|
12065
|
+
);
|
|
12066
|
+
}
|
|
12067
|
+
|
|
11384
12068
|
// src/lib/telemetry.ts
|
|
11385
12069
|
var ENABLED = process.env.EXE_TELEMETRY === "1";
|
|
11386
12070
|
var initialized = false;
|
|
@@ -11485,6 +12169,9 @@ registerDeployClient(server);
|
|
|
11485
12169
|
registerQueryConversations(server);
|
|
11486
12170
|
registerLoadSkill(server);
|
|
11487
12171
|
registerConsolidateMemories(server);
|
|
12172
|
+
registerStoreGlobalProcedure(server);
|
|
12173
|
+
registerListGlobalProcedures(server);
|
|
12174
|
+
registerDeactivateGlobalProcedure(server);
|
|
11488
12175
|
try {
|
|
11489
12176
|
await initStore();
|
|
11490
12177
|
process.stderr.write("[exe-os] MCP server starting...\n");
|