@askexenow/exe-os 0.8.83 → 0.8.85
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 +746 -595
- package/dist/bin/backfill-responses.js +745 -594
- package/dist/bin/backfill-vectors.js +312 -226
- package/dist/bin/cleanup-stale-review-tasks.js +97 -2
- package/dist/bin/cli.js +14350 -12518
- package/dist/bin/exe-agent.js +97 -88
- package/dist/bin/exe-assign.js +1003 -854
- package/dist/bin/exe-boot.js +1257 -320
- package/dist/bin/exe-call.js +10 -0
- package/dist/bin/exe-cloud.js +29 -6
- package/dist/bin/exe-dispatch.js +210 -34
- package/dist/bin/exe-doctor.js +403 -6
- package/dist/bin/exe-export-behaviors.js +175 -72
- package/dist/bin/exe-forget.js +97 -2
- package/dist/bin/exe-gateway.js +550 -171
- package/dist/bin/exe-healthcheck.js +1 -0
- package/dist/bin/exe-heartbeat.js +100 -5
- package/dist/bin/exe-kill.js +175 -72
- package/dist/bin/exe-launch-agent.js +189 -76
- package/dist/bin/exe-link.js +902 -80
- package/dist/bin/exe-new-employee.js +38 -8
- package/dist/bin/exe-pending-messages.js +96 -2
- package/dist/bin/exe-pending-notifications.js +97 -2
- package/dist/bin/exe-pending-reviews.js +98 -3
- package/dist/bin/exe-rename.js +564 -23
- package/dist/bin/exe-review.js +231 -73
- package/dist/bin/exe-search.js +989 -226
- package/dist/bin/exe-session-cleanup.js +4806 -1665
- package/dist/bin/exe-settings.js +20 -5
- package/dist/bin/exe-status.js +97 -2
- package/dist/bin/exe-team.js +97 -2
- package/dist/bin/git-sweep.js +899 -207
- package/dist/bin/graph-backfill.js +175 -72
- package/dist/bin/graph-export.js +175 -72
- package/dist/bin/install.js +38 -7
- package/dist/bin/list-providers.js +1 -0
- package/dist/bin/scan-tasks.js +904 -211
- package/dist/bin/setup.js +867 -268
- package/dist/bin/shard-migrate.js +175 -72
- package/dist/bin/update.js +1 -0
- package/dist/bin/wiki-sync.js +175 -72
- package/dist/gateway/index.js +548 -166
- package/dist/hooks/bug-report-worker.js +208 -23
- package/dist/hooks/commit-complete.js +897 -205
- package/dist/hooks/error-recall.js +988 -226
- package/dist/hooks/ingest-worker.js +1638 -1194
- package/dist/hooks/ingest.js +3 -0
- package/dist/hooks/instructions-loaded.js +707 -97
- package/dist/hooks/notification.js +699 -89
- package/dist/hooks/post-compact.js +714 -104
- package/dist/hooks/pre-compact.js +897 -205
- package/dist/hooks/pre-tool-use.js +742 -123
- package/dist/hooks/prompt-ingest-worker.js +242 -101
- package/dist/hooks/prompt-submit.js +995 -233
- package/dist/hooks/response-ingest-worker.js +242 -101
- package/dist/hooks/session-end.js +3941 -400
- package/dist/hooks/session-start.js +1001 -226
- package/dist/hooks/stop.js +725 -115
- package/dist/hooks/subagent-stop.js +714 -104
- package/dist/hooks/summary-worker.js +1964 -1330
- package/dist/index.js +1651 -1053
- package/dist/lib/cloud-sync.js +907 -86
- package/dist/lib/consolidation.js +2 -1
- package/dist/lib/database.js +642 -87
- package/dist/lib/db-daemon-client.js +503 -0
- package/dist/lib/device-registry.js +547 -7
- package/dist/lib/embedder.js +14 -28
- package/dist/lib/employee-templates.js +84 -74
- package/dist/lib/employees.js +9 -0
- package/dist/lib/exe-daemon-client.js +16 -29
- package/dist/lib/exe-daemon.js +1955 -922
- package/dist/lib/hybrid-search.js +988 -226
- package/dist/lib/identity.js +87 -67
- package/dist/lib/keychain.js +9 -1
- package/dist/lib/messaging.js +8 -1
- package/dist/lib/reminders.js +91 -74
- package/dist/lib/schedules.js +96 -2
- package/dist/lib/skill-learning.js +103 -85
- package/dist/lib/store.js +234 -73
- package/dist/lib/tasks.js +111 -22
- package/dist/lib/tmux-routing.js +120 -31
- package/dist/lib/token-spend.js +273 -0
- package/dist/lib/ws-client.js +11 -0
- package/dist/mcp/server.js +5222 -475
- package/dist/mcp/tools/complete-reminder.js +94 -77
- package/dist/mcp/tools/create-reminder.js +94 -77
- package/dist/mcp/tools/create-task.js +120 -22
- package/dist/mcp/tools/deactivate-behavior.js +95 -77
- package/dist/mcp/tools/list-reminders.js +94 -77
- package/dist/mcp/tools/list-tasks.js +31 -1
- package/dist/mcp/tools/send-message.js +8 -1
- package/dist/mcp/tools/update-task.js +39 -10
- package/dist/runtime/index.js +911 -219
- package/dist/tui/App.js +997 -295
- package/package.json +6 -1
package/dist/bin/exe-call.js
CHANGED
|
@@ -107,6 +107,7 @@ __export(employees_exports, {
|
|
|
107
107
|
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
108
108
|
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
109
109
|
addEmployee: () => addEmployee,
|
|
110
|
+
baseAgentName: () => baseAgentName,
|
|
110
111
|
canCoordinate: () => canCoordinate,
|
|
111
112
|
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
112
113
|
getCoordinatorName: () => getCoordinatorName,
|
|
@@ -203,6 +204,14 @@ function hasRole(agentName, role) {
|
|
|
203
204
|
const emp = getEmployee(employees, agentName);
|
|
204
205
|
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
205
206
|
}
|
|
207
|
+
function baseAgentName(name, employees) {
|
|
208
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
209
|
+
if (!match) return name;
|
|
210
|
+
const base = match[1];
|
|
211
|
+
const roster = employees ?? loadEmployeesSync();
|
|
212
|
+
if (getEmployee(roster, base)) return base;
|
|
213
|
+
return name;
|
|
214
|
+
}
|
|
206
215
|
function isMultiInstance(agentName, employees) {
|
|
207
216
|
const roster = employees ?? loadEmployeesSync();
|
|
208
217
|
const emp = getEmployee(roster, agentName);
|
|
@@ -1080,6 +1089,7 @@ import { realpathSync } from "fs";
|
|
|
1080
1089
|
import { fileURLToPath } from "url";
|
|
1081
1090
|
function isMainModule(importMetaUrl) {
|
|
1082
1091
|
if (process.argv[1] == null) return false;
|
|
1092
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
1083
1093
|
try {
|
|
1084
1094
|
const scriptPath = realpathSync(process.argv[1]);
|
|
1085
1095
|
const modulePath = realpathSync(fileURLToPath(importMetaUrl));
|
package/dist/bin/exe-cloud.js
CHANGED
|
@@ -46,12 +46,20 @@ async function getMasterKey() {
|
|
|
46
46
|
}
|
|
47
47
|
const keyPath = getKeyPath();
|
|
48
48
|
if (!existsSync(keyPath)) {
|
|
49
|
+
process.stderr.write(
|
|
50
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
51
|
+
`
|
|
52
|
+
);
|
|
49
53
|
return null;
|
|
50
54
|
}
|
|
51
55
|
try {
|
|
52
56
|
const content = await readFile(keyPath, "utf-8");
|
|
53
57
|
return Buffer.from(content.trim(), "base64");
|
|
54
|
-
} catch {
|
|
58
|
+
} catch (err) {
|
|
59
|
+
process.stderr.write(
|
|
60
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
61
|
+
`
|
|
62
|
+
);
|
|
55
63
|
return null;
|
|
56
64
|
}
|
|
57
65
|
}
|
|
@@ -720,6 +728,19 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
|
|
|
720
728
|
}
|
|
721
729
|
});
|
|
722
730
|
|
|
731
|
+
// src/lib/crdt-sync.ts
|
|
732
|
+
import * as Y from "yjs";
|
|
733
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
734
|
+
import path5 from "path";
|
|
735
|
+
import { homedir } from "os";
|
|
736
|
+
var DEFAULT_STATE_PATH;
|
|
737
|
+
var init_crdt_sync = __esm({
|
|
738
|
+
"src/lib/crdt-sync.ts"() {
|
|
739
|
+
"use strict";
|
|
740
|
+
DEFAULT_STATE_PATH = path5.join(homedir(), ".exe-os", "crdt-state.bin");
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
|
|
723
744
|
// src/bin/exe-cloud.ts
|
|
724
745
|
init_keychain();
|
|
725
746
|
init_config();
|
|
@@ -745,6 +766,7 @@ import { realpathSync } from "fs";
|
|
|
745
766
|
import { fileURLToPath } from "url";
|
|
746
767
|
function isMainModule(importMetaUrl) {
|
|
747
768
|
if (process.argv[1] == null) return false;
|
|
769
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
748
770
|
try {
|
|
749
771
|
const scriptPath = realpathSync(process.argv[1]);
|
|
750
772
|
const modulePath = realpathSync(fileURLToPath(importMetaUrl));
|
|
@@ -756,10 +778,10 @@ function isMainModule(importMetaUrl) {
|
|
|
756
778
|
|
|
757
779
|
// src/lib/cloud-sync.ts
|
|
758
780
|
init_database();
|
|
759
|
-
import { readFileSync as
|
|
781
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync3, appendFileSync, unlinkSync as unlinkSync3, openSync, closeSync } from "fs";
|
|
760
782
|
import crypto3 from "crypto";
|
|
761
|
-
import
|
|
762
|
-
import { homedir } from "os";
|
|
783
|
+
import path6 from "path";
|
|
784
|
+
import { homedir as homedir2 } from "os";
|
|
763
785
|
|
|
764
786
|
// src/lib/crypto.ts
|
|
765
787
|
import crypto2 from "crypto";
|
|
@@ -770,9 +792,10 @@ import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
|
|
|
770
792
|
// src/lib/cloud-sync.ts
|
|
771
793
|
init_license();
|
|
772
794
|
init_config();
|
|
795
|
+
init_crdt_sync();
|
|
773
796
|
init_employees();
|
|
774
797
|
var LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
775
|
-
var ROSTER_LOCK_PATH =
|
|
798
|
+
var ROSTER_LOCK_PATH = path6.join(EXE_AI_DIR, "roster-merge.lock");
|
|
776
799
|
function assertSecureEndpoint(endpoint) {
|
|
777
800
|
if (endpoint.startsWith("https://")) return;
|
|
778
801
|
if (endpoint.startsWith("http://")) {
|
|
@@ -787,7 +810,7 @@ function assertSecureEndpoint(endpoint) {
|
|
|
787
810
|
);
|
|
788
811
|
}
|
|
789
812
|
}
|
|
790
|
-
var ROSTER_DELETIONS_PATH =
|
|
813
|
+
var ROSTER_DELETIONS_PATH = path6.join(EXE_AI_DIR, "roster-deletions.json");
|
|
791
814
|
|
|
792
815
|
// src/bin/exe-cloud.ts
|
|
793
816
|
var BAR = "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550";
|
package/dist/bin/exe-dispatch.js
CHANGED
|
@@ -628,6 +628,12 @@ function getClient() {
|
|
|
628
628
|
if (!_resilientClient) {
|
|
629
629
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
630
630
|
}
|
|
631
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
632
|
+
return _resilientClient;
|
|
633
|
+
}
|
|
634
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
635
|
+
return _daemonClient;
|
|
636
|
+
}
|
|
631
637
|
return _resilientClient;
|
|
632
638
|
}
|
|
633
639
|
function getRawClient() {
|
|
@@ -1116,6 +1122,12 @@ async function ensureSchema() {
|
|
|
1116
1122
|
} catch {
|
|
1117
1123
|
}
|
|
1118
1124
|
}
|
|
1125
|
+
try {
|
|
1126
|
+
await client.execute(
|
|
1127
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_content_hash ON memories(content_hash, agent_id)`
|
|
1128
|
+
);
|
|
1129
|
+
} catch {
|
|
1130
|
+
}
|
|
1119
1131
|
await client.executeMultiple(`
|
|
1120
1132
|
CREATE TABLE IF NOT EXISTS entities (
|
|
1121
1133
|
id TEXT PRIMARY KEY,
|
|
@@ -1168,7 +1180,30 @@ async function ensureSchema() {
|
|
|
1168
1180
|
entity_id TEXT NOT NULL,
|
|
1169
1181
|
PRIMARY KEY (hyperedge_id, entity_id)
|
|
1170
1182
|
);
|
|
1183
|
+
|
|
1184
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
1185
|
+
name,
|
|
1186
|
+
content=entities,
|
|
1187
|
+
content_rowid=rowid
|
|
1188
|
+
);
|
|
1189
|
+
|
|
1190
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ai AFTER INSERT ON entities BEGIN
|
|
1191
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1192
|
+
END;
|
|
1193
|
+
|
|
1194
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_ad AFTER DELETE ON entities BEGIN
|
|
1195
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1196
|
+
END;
|
|
1197
|
+
|
|
1198
|
+
CREATE TRIGGER IF NOT EXISTS entities_fts_au AFTER UPDATE ON entities BEGIN
|
|
1199
|
+
INSERT INTO entities_fts(entities_fts, rowid, name) VALUES('delete', old.rowid, old.name);
|
|
1200
|
+
INSERT INTO entities_fts(rowid, name) VALUES (new.rowid, new.name);
|
|
1201
|
+
END;
|
|
1171
1202
|
`);
|
|
1203
|
+
try {
|
|
1204
|
+
await client.execute("INSERT INTO entities_fts(entities_fts) VALUES('rebuild')");
|
|
1205
|
+
} catch {
|
|
1206
|
+
}
|
|
1172
1207
|
await client.executeMultiple(`
|
|
1173
1208
|
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
1174
1209
|
alias TEXT NOT NULL PRIMARY KEY,
|
|
@@ -1349,6 +1384,33 @@ async function ensureSchema() {
|
|
|
1349
1384
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
1350
1385
|
ON conversations(channel_id);
|
|
1351
1386
|
`);
|
|
1387
|
+
await client.executeMultiple(`
|
|
1388
|
+
CREATE TABLE IF NOT EXISTS session_agent_map (
|
|
1389
|
+
session_uuid TEXT PRIMARY KEY,
|
|
1390
|
+
agent_id TEXT NOT NULL,
|
|
1391
|
+
session_name TEXT,
|
|
1392
|
+
task_id TEXT,
|
|
1393
|
+
project_name TEXT,
|
|
1394
|
+
started_at TEXT NOT NULL
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
CREATE INDEX IF NOT EXISTS idx_session_agent_map_agent
|
|
1398
|
+
ON session_agent_map(agent_id);
|
|
1399
|
+
`);
|
|
1400
|
+
try {
|
|
1401
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
1402
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
1403
|
+
await client.execute({
|
|
1404
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
1405
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
1406
|
+
FROM memories
|
|
1407
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
1408
|
+
GROUP BY session_id, agent_id`,
|
|
1409
|
+
args: []
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
} catch {
|
|
1413
|
+
}
|
|
1352
1414
|
try {
|
|
1353
1415
|
await client.execute({
|
|
1354
1416
|
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
@@ -1482,8 +1544,30 @@ async function ensureSchema() {
|
|
|
1482
1544
|
});
|
|
1483
1545
|
} catch {
|
|
1484
1546
|
}
|
|
1547
|
+
for (const col of [
|
|
1548
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
1549
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
1550
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
1551
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
1552
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
1553
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
1554
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
1555
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
1556
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
1557
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
1558
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
1559
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
1560
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
1561
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
1562
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
1563
|
+
]) {
|
|
1564
|
+
try {
|
|
1565
|
+
await client.execute(col);
|
|
1566
|
+
} catch {
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1485
1569
|
}
|
|
1486
|
-
var _client, _resilientClient, initTurso;
|
|
1570
|
+
var _client, _resilientClient, _daemonClient, initTurso;
|
|
1487
1571
|
var init_database = __esm({
|
|
1488
1572
|
"src/lib/database.ts"() {
|
|
1489
1573
|
"use strict";
|
|
@@ -1491,6 +1575,7 @@ var init_database = __esm({
|
|
|
1491
1575
|
init_employees();
|
|
1492
1576
|
_client = null;
|
|
1493
1577
|
_resilientClient = null;
|
|
1578
|
+
_daemonClient = null;
|
|
1494
1579
|
initTurso = initDatabase;
|
|
1495
1580
|
}
|
|
1496
1581
|
});
|
|
@@ -1764,6 +1849,7 @@ var init_state_bus = __esm({
|
|
|
1764
1849
|
// src/lib/tasks-crud.ts
|
|
1765
1850
|
import crypto3 from "crypto";
|
|
1766
1851
|
import path8 from "path";
|
|
1852
|
+
import os6 from "os";
|
|
1767
1853
|
import { execSync as execSync4 } from "child_process";
|
|
1768
1854
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1769
1855
|
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
@@ -1807,6 +1893,35 @@ function extractParentFromContext(contextBody) {
|
|
|
1807
1893
|
function slugify(title) {
|
|
1808
1894
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1809
1895
|
}
|
|
1896
|
+
function buildKeywordIndex() {
|
|
1897
|
+
const idx = /* @__PURE__ */ new Map();
|
|
1898
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
1899
|
+
for (const kw of keywords) {
|
|
1900
|
+
const existing = idx.get(kw) ?? [];
|
|
1901
|
+
existing.push(role);
|
|
1902
|
+
idx.set(kw, existing);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
return idx;
|
|
1906
|
+
}
|
|
1907
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
1908
|
+
const employees = loadEmployeesSync();
|
|
1909
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
1910
|
+
if (!employee) return void 0;
|
|
1911
|
+
const assigneeRole = employee.role;
|
|
1912
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
1913
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
1914
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
1915
|
+
if (text.includes(keyword)) {
|
|
1916
|
+
for (const role of roles) matchedRoles.add(role);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (matchedRoles.size === 0) return void 0;
|
|
1920
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
1921
|
+
if (assigneeRole === "COO") return void 0;
|
|
1922
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
1923
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
1924
|
+
}
|
|
1810
1925
|
async function resolveTask(client, identifier, scopeSession) {
|
|
1811
1926
|
const scope = sessionScopeFilter(scopeSession);
|
|
1812
1927
|
let result2 = await client.execute({
|
|
@@ -1856,7 +1971,14 @@ async function createTaskCore(input) {
|
|
|
1856
1971
|
const id = crypto3.randomUUID();
|
|
1857
1972
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1858
1973
|
const slug = slugify(input.title);
|
|
1859
|
-
|
|
1974
|
+
let earlySessionScope = null;
|
|
1975
|
+
try {
|
|
1976
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1977
|
+
earlySessionScope = resolveExeSession2();
|
|
1978
|
+
} catch {
|
|
1979
|
+
}
|
|
1980
|
+
const scope = earlySessionScope ?? "default";
|
|
1981
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
1860
1982
|
let blockedById = null;
|
|
1861
1983
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
1862
1984
|
if (input.blockedBy) {
|
|
@@ -1896,6 +2018,13 @@ async function createTaskCore(input) {
|
|
|
1896
2018
|
if (dupCheck.rows.length > 0) {
|
|
1897
2019
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
1898
2020
|
}
|
|
2021
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
2022
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
2023
|
+
if (laneWarning) {
|
|
2024
|
+
warning = warning ? `${warning}
|
|
2025
|
+
${laneWarning}` : laneWarning;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
1899
2028
|
if (input.baseDir) {
|
|
1900
2029
|
try {
|
|
1901
2030
|
await mkdir3(path8.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
@@ -1906,12 +2035,7 @@ async function createTaskCore(input) {
|
|
|
1906
2035
|
}
|
|
1907
2036
|
}
|
|
1908
2037
|
const complexity = input.complexity ?? "standard";
|
|
1909
|
-
|
|
1910
|
-
try {
|
|
1911
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1912
|
-
sessionScope = resolveExeSession2();
|
|
1913
|
-
} catch {
|
|
1914
|
-
}
|
|
2038
|
+
const sessionScope = earlySessionScope;
|
|
1915
2039
|
await client.execute({
|
|
1916
2040
|
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)
|
|
1917
2041
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1938,6 +2062,39 @@ async function createTaskCore(input) {
|
|
|
1938
2062
|
now
|
|
1939
2063
|
]
|
|
1940
2064
|
});
|
|
2065
|
+
if (input.baseDir) {
|
|
2066
|
+
try {
|
|
2067
|
+
const EXE_OS_DIR = path8.join(os6.homedir(), ".exe-os");
|
|
2068
|
+
const mdPath = path8.join(EXE_OS_DIR, taskFile);
|
|
2069
|
+
const mdDir = path8.dirname(mdPath);
|
|
2070
|
+
if (!existsSync8(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
2071
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
2072
|
+
const mdContent = `# ${input.title}
|
|
2073
|
+
|
|
2074
|
+
**ID:** ${id}
|
|
2075
|
+
**Status:** ${initialStatus}
|
|
2076
|
+
**Priority:** ${input.priority}
|
|
2077
|
+
**Assigned by:** ${input.assignedBy}
|
|
2078
|
+
**Assigned to:** ${input.assignedTo}
|
|
2079
|
+
**Project:** ${input.projectName}
|
|
2080
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
2081
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
2082
|
+
**Reviewer:** ${reviewer}
|
|
2083
|
+
|
|
2084
|
+
## Context
|
|
2085
|
+
|
|
2086
|
+
${input.context}
|
|
2087
|
+
|
|
2088
|
+
## MANDATORY: When done
|
|
2089
|
+
|
|
2090
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
2091
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
2092
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
2093
|
+
`;
|
|
2094
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
2095
|
+
} catch {
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
1941
2098
|
return {
|
|
1942
2099
|
id,
|
|
1943
2100
|
title: input.title,
|
|
@@ -2130,7 +2287,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2130
2287
|
return { row, taskFile, now, taskId };
|
|
2131
2288
|
}
|
|
2132
2289
|
}
|
|
2133
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
2290
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
2134
2291
|
process.stderr.write(
|
|
2135
2292
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2136
2293
|
`
|
|
@@ -2242,12 +2399,22 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
2242
2399
|
} catch {
|
|
2243
2400
|
}
|
|
2244
2401
|
}
|
|
2245
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2402
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
2246
2403
|
var init_tasks_crud = __esm({
|
|
2247
2404
|
"src/lib/tasks-crud.ts"() {
|
|
2248
2405
|
"use strict";
|
|
2249
2406
|
init_database();
|
|
2250
2407
|
init_task_scope();
|
|
2408
|
+
init_employees();
|
|
2409
|
+
LANE_KEYWORDS = {
|
|
2410
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
2411
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
2412
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
2413
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
2414
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
2415
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
2416
|
+
};
|
|
2417
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
2251
2418
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2252
2419
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2253
2420
|
}
|
|
@@ -2277,7 +2444,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
2277
2444
|
const result3 = await client.execute({
|
|
2278
2445
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
2279
2446
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
2280
|
-
AND
|
|
2447
|
+
AND session_scope = ?`,
|
|
2281
2448
|
args: [sinceIso, sessionScope]
|
|
2282
2449
|
});
|
|
2283
2450
|
return Number(result3.rows[0]?.cnt) || 0;
|
|
@@ -2295,7 +2462,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
2295
2462
|
const result3 = await client.execute({
|
|
2296
2463
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
2297
2464
|
WHERE status = 'needs_review'
|
|
2298
|
-
AND
|
|
2465
|
+
AND session_scope = ?
|
|
2299
2466
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
2300
2467
|
args: [sessionScope, limit]
|
|
2301
2468
|
});
|
|
@@ -2416,14 +2583,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
2416
2583
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
2417
2584
|
const agent = parts[1];
|
|
2418
2585
|
const slug = parts.slice(2).join("-");
|
|
2419
|
-
const
|
|
2586
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
2420
2587
|
const result2 = await client.execute({
|
|
2421
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
2422
|
-
args: [now,
|
|
2588
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
2589
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
2423
2590
|
});
|
|
2424
2591
|
if (result2.rowsAffected > 0) {
|
|
2425
2592
|
process.stderr.write(
|
|
2426
|
-
`[review-cleanup] Cascaded original task to done
|
|
2593
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
2427
2594
|
`
|
|
2428
2595
|
);
|
|
2429
2596
|
}
|
|
@@ -2605,7 +2772,7 @@ function findSessionForProject(projectName) {
|
|
|
2605
2772
|
const sessions = listSessions();
|
|
2606
2773
|
for (const s of sessions) {
|
|
2607
2774
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2608
|
-
if (proj === projectName &&
|
|
2775
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
2609
2776
|
}
|
|
2610
2777
|
return null;
|
|
2611
2778
|
}
|
|
@@ -2651,7 +2818,7 @@ var init_session_scope = __esm({
|
|
|
2651
2818
|
|
|
2652
2819
|
// src/lib/tasks-notify.ts
|
|
2653
2820
|
async function dispatchTaskToEmployee(input) {
|
|
2654
|
-
if (
|
|
2821
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2655
2822
|
let crossProject = false;
|
|
2656
2823
|
if (input.projectName) {
|
|
2657
2824
|
try {
|
|
@@ -3130,7 +3297,7 @@ async function updateTask(input) {
|
|
|
3130
3297
|
}
|
|
3131
3298
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3132
3299
|
if (isTerminal) {
|
|
3133
|
-
const isCoordinator =
|
|
3300
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
3134
3301
|
if (!isCoordinator) {
|
|
3135
3302
|
notifyTaskDone();
|
|
3136
3303
|
}
|
|
@@ -3155,7 +3322,7 @@ async function updateTask(input) {
|
|
|
3155
3322
|
}
|
|
3156
3323
|
}
|
|
3157
3324
|
}
|
|
3158
|
-
if (input.status === "done" &&
|
|
3325
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3159
3326
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3160
3327
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3161
3328
|
taskId,
|
|
@@ -3171,7 +3338,7 @@ async function updateTask(input) {
|
|
|
3171
3338
|
});
|
|
3172
3339
|
}
|
|
3173
3340
|
let nextTask;
|
|
3174
|
-
if (isTerminal &&
|
|
3341
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
3175
3342
|
try {
|
|
3176
3343
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3177
3344
|
} catch {
|
|
@@ -3539,7 +3706,7 @@ __export(tmux_routing_exports, {
|
|
|
3539
3706
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
3540
3707
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
|
|
3541
3708
|
import path13 from "path";
|
|
3542
|
-
import
|
|
3709
|
+
import os7 from "os";
|
|
3543
3710
|
import { fileURLToPath } from "url";
|
|
3544
3711
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
3545
3712
|
function spawnLockPath(sessionName) {
|
|
@@ -3863,7 +4030,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3863
4030
|
return true;
|
|
3864
4031
|
}
|
|
3865
4032
|
function ensureEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
3866
|
-
if (
|
|
4033
|
+
if (isCoordinatorName(employeeName2)) {
|
|
3867
4034
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3868
4035
|
}
|
|
3869
4036
|
try {
|
|
@@ -3935,7 +4102,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
3935
4102
|
const transport = getTransport();
|
|
3936
4103
|
const sessionName = employeeSessionName(employeeName2, exeSession2, opts?.instance);
|
|
3937
4104
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName2}${opts.instance}` : employeeName2;
|
|
3938
|
-
const logDir = path13.join(
|
|
4105
|
+
const logDir = path13.join(os7.homedir(), ".exe-os", "session-logs");
|
|
3939
4106
|
const logFile = path13.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3940
4107
|
if (!existsSync10(logDir)) {
|
|
3941
4108
|
mkdirSync5(logDir, { recursive: true });
|
|
@@ -3951,7 +4118,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
3951
4118
|
} catch {
|
|
3952
4119
|
}
|
|
3953
4120
|
try {
|
|
3954
|
-
const claudeJsonPath = path13.join(
|
|
4121
|
+
const claudeJsonPath = path13.join(os7.homedir(), ".claude.json");
|
|
3955
4122
|
let claudeJson = {};
|
|
3956
4123
|
try {
|
|
3957
4124
|
claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
|
|
@@ -3966,7 +4133,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
3966
4133
|
} catch {
|
|
3967
4134
|
}
|
|
3968
4135
|
try {
|
|
3969
|
-
const settingsDir = path13.join(
|
|
4136
|
+
const settingsDir = path13.join(os7.homedir(), ".claude", "projects");
|
|
3970
4137
|
const normalizedKey = (opts?.cwd ?? projectDir2).replace(/\//g, "-").replace(/^-/, "");
|
|
3971
4138
|
const projSettingsDir = path13.join(settingsDir, normalizedKey);
|
|
3972
4139
|
const settingsPath = path13.join(projSettingsDir, "settings.json");
|
|
@@ -4014,7 +4181,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4014
4181
|
let legacyFallbackWarned = false;
|
|
4015
4182
|
if (!useExeAgent && !useBinSymlink) {
|
|
4016
4183
|
const identityPath = path13.join(
|
|
4017
|
-
|
|
4184
|
+
os7.homedir(),
|
|
4018
4185
|
".exe-os",
|
|
4019
4186
|
"identity",
|
|
4020
4187
|
`${employeeName2}.md`
|
|
@@ -4044,7 +4211,7 @@ function spawnEmployee(employeeName2, exeSession2, projectDir2, opts) {
|
|
|
4044
4211
|
}
|
|
4045
4212
|
let sessionContextFlag = "";
|
|
4046
4213
|
try {
|
|
4047
|
-
const ctxDir = path13.join(
|
|
4214
|
+
const ctxDir = path13.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4048
4215
|
mkdirSync5(ctxDir, { recursive: true });
|
|
4049
4216
|
const ctxFile = path13.join(ctxDir, `session-context-${sessionName}.md`);
|
|
4050
4217
|
const ctxContent = [
|
|
@@ -4156,13 +4323,13 @@ var init_tmux_routing = __esm({
|
|
|
4156
4323
|
init_intercom_queue();
|
|
4157
4324
|
init_plan_limits();
|
|
4158
4325
|
init_employees();
|
|
4159
|
-
SPAWN_LOCK_DIR = path13.join(
|
|
4160
|
-
SESSION_CACHE = path13.join(
|
|
4326
|
+
SPAWN_LOCK_DIR = path13.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
4327
|
+
SESSION_CACHE = path13.join(os7.homedir(), ".exe-os", "session-cache");
|
|
4161
4328
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
4162
4329
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
4163
4330
|
VERIFY_PANE_LINES = 200;
|
|
4164
4331
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
4165
|
-
INTERCOM_LOG2 = path13.join(
|
|
4332
|
+
INTERCOM_LOG2 = path13.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
4166
4333
|
DEBOUNCE_FILE = path13.join(SESSION_CACHE, "intercom-debounce.json");
|
|
4167
4334
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
4168
4335
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
@@ -4600,17 +4767,18 @@ init_tmux_routing();
|
|
|
4600
4767
|
init_tasks_crud();
|
|
4601
4768
|
|
|
4602
4769
|
// src/lib/store.ts
|
|
4770
|
+
import { createHash } from "crypto";
|
|
4603
4771
|
init_database();
|
|
4604
4772
|
|
|
4605
4773
|
// src/lib/keychain.ts
|
|
4606
4774
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
4607
4775
|
import { existsSync as existsSync11 } from "fs";
|
|
4608
4776
|
import path14 from "path";
|
|
4609
|
-
import
|
|
4777
|
+
import os8 from "os";
|
|
4610
4778
|
var SERVICE = "exe-mem";
|
|
4611
4779
|
var ACCOUNT = "master-key";
|
|
4612
4780
|
function getKeyDir() {
|
|
4613
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(
|
|
4781
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path14.join(os8.homedir(), ".exe-os");
|
|
4614
4782
|
}
|
|
4615
4783
|
function getKeyPath() {
|
|
4616
4784
|
return path14.join(getKeyDir(), "master.key");
|
|
@@ -4635,12 +4803,20 @@ async function getMasterKey() {
|
|
|
4635
4803
|
}
|
|
4636
4804
|
const keyPath = getKeyPath();
|
|
4637
4805
|
if (!existsSync11(keyPath)) {
|
|
4806
|
+
process.stderr.write(
|
|
4807
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os8.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
4808
|
+
`
|
|
4809
|
+
);
|
|
4638
4810
|
return null;
|
|
4639
4811
|
}
|
|
4640
4812
|
try {
|
|
4641
4813
|
const content = await readFile4(keyPath, "utf-8");
|
|
4642
4814
|
return Buffer.from(content.trim(), "base64");
|
|
4643
|
-
} catch {
|
|
4815
|
+
} catch (err) {
|
|
4816
|
+
process.stderr.write(
|
|
4817
|
+
`[keychain] Key read failed at ${keyPath}: ${err instanceof Error ? err.message : String(err)}
|
|
4818
|
+
`
|
|
4819
|
+
);
|
|
4644
4820
|
return null;
|
|
4645
4821
|
}
|
|
4646
4822
|
}
|