@askexenow/exe-os 0.8.80 → 0.8.82
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 +359 -267
- package/dist/bin/backfill-responses.js +357 -265
- package/dist/bin/backfill-vectors.js +339 -264
- package/dist/bin/cleanup-stale-review-tasks.js +315 -256
- package/dist/bin/cli.js +494 -240
- package/dist/bin/exe-agent.js +141 -46
- package/dist/bin/exe-assign.js +151 -63
- package/dist/bin/exe-boot.js +294 -115
- package/dist/bin/exe-call.js +76 -51
- package/dist/bin/exe-cloud.js +58 -45
- package/dist/bin/exe-dispatch.js +434 -277
- package/dist/bin/exe-doctor.js +317 -246
- package/dist/bin/exe-export-behaviors.js +328 -248
- package/dist/bin/exe-forget.js +314 -231
- package/dist/bin/exe-gateway.js +2676 -1402
- package/dist/bin/exe-heartbeat.js +329 -264
- package/dist/bin/exe-kill.js +324 -244
- package/dist/bin/exe-launch-agent.js +574 -463
- package/dist/bin/exe-link.js +1055 -95
- package/dist/bin/exe-new-employee.js +49 -54
- package/dist/bin/exe-pending-messages.js +310 -253
- package/dist/bin/exe-pending-notifications.js +299 -228
- package/dist/bin/exe-pending-reviews.js +314 -245
- package/dist/bin/exe-rename.js +259 -195
- package/dist/bin/exe-review.js +140 -64
- package/dist/bin/exe-search.js +543 -356
- package/dist/bin/exe-session-cleanup.js +463 -382
- package/dist/bin/exe-settings.js +129 -99
- package/dist/bin/exe-start.sh +6 -6
- package/dist/bin/exe-status.js +95 -36
- package/dist/bin/exe-team.js +116 -51
- package/dist/bin/git-sweep.js +482 -307
- package/dist/bin/graph-backfill.js +357 -245
- package/dist/bin/graph-export.js +324 -244
- package/dist/bin/install.js +33 -10
- package/dist/bin/scan-tasks.js +481 -307
- package/dist/bin/setup.js +1147 -140
- package/dist/bin/shard-migrate.js +321 -241
- package/dist/bin/update.js +1 -7
- package/dist/bin/wiki-sync.js +318 -238
- package/dist/gateway/index.js +2656 -1383
- package/dist/hooks/bug-report-worker.js +641 -472
- package/dist/hooks/commit-complete.js +482 -307
- package/dist/hooks/error-recall.js +363 -135
- package/dist/hooks/exe-heartbeat-hook.js +97 -27
- package/dist/hooks/ingest-worker.js +584 -397
- package/dist/hooks/ingest.js +123 -58
- package/dist/hooks/instructions-loaded.js +212 -82
- package/dist/hooks/notification.js +200 -70
- package/dist/hooks/post-compact.js +199 -81
- package/dist/hooks/pre-compact.js +352 -140
- package/dist/hooks/pre-tool-use.js +416 -278
- package/dist/hooks/prompt-ingest-worker.js +376 -299
- package/dist/hooks/prompt-submit.js +414 -188
- package/dist/hooks/response-ingest-worker.js +408 -338
- package/dist/hooks/session-end.js +209 -83
- package/dist/hooks/session-start.js +382 -158
- package/dist/hooks/stop.js +209 -83
- package/dist/hooks/subagent-stop.js +209 -85
- package/dist/hooks/summary-worker.js +606 -510
- package/dist/index.js +2133 -855
- package/dist/lib/cloud-sync.js +1175 -184
- package/dist/lib/config.js +1 -9
- package/dist/lib/consolidation.js +71 -34
- package/dist/lib/database.js +166 -14
- package/dist/lib/device-registry.js +189 -117
- package/dist/lib/embedder.js +6 -10
- package/dist/lib/employee-templates.js +134 -39
- package/dist/lib/employees.js +30 -7
- package/dist/lib/exe-daemon-client.js +5 -7
- package/dist/lib/exe-daemon.js +514 -152
- package/dist/lib/hybrid-search.js +543 -356
- package/dist/lib/identity-templates.js +15 -15
- package/dist/lib/identity.js +19 -15
- package/dist/lib/license.js +1 -7
- package/dist/lib/messaging.js +157 -135
- package/dist/lib/reminders.js +97 -0
- package/dist/lib/schedules.js +302 -231
- package/dist/lib/skill-learning.js +33 -27
- package/dist/lib/status-brief.js +11 -14
- package/dist/lib/store.js +326 -237
- package/dist/lib/task-router.js +105 -1
- package/dist/lib/tasks.js +233 -116
- package/dist/lib/tmux-routing.js +173 -56
- package/dist/lib/ws-client.js +13 -3
- package/dist/mcp/server.js +2009 -1015
- package/dist/mcp/tools/complete-reminder.js +97 -0
- package/dist/mcp/tools/create-reminder.js +97 -0
- package/dist/mcp/tools/create-task.js +426 -262
- package/dist/mcp/tools/deactivate-behavior.js +119 -44
- package/dist/mcp/tools/list-reminders.js +97 -0
- package/dist/mcp/tools/list-tasks.js +56 -57
- package/dist/mcp/tools/send-message.js +206 -143
- package/dist/mcp/tools/update-task.js +259 -85
- package/dist/runtime/index.js +495 -316
- package/dist/tui/App.js +1128 -919
- package/package.json +2 -10
- package/src/commands/exe/afk.md +8 -8
- package/src/commands/exe/assign.md +1 -1
- package/src/commands/exe/build-adv.md +1 -1
- package/src/commands/exe/call.md +10 -10
- package/src/commands/exe/employee-heartbeat.md +9 -6
- package/src/commands/exe/heartbeat.md +5 -5
- package/src/commands/exe/intercom.md +26 -15
- package/src/commands/exe/launch.md +2 -2
- package/src/commands/exe/new-employee.md +1 -1
- package/src/commands/exe/review.md +2 -2
- package/src/commands/exe/schedule.md +1 -1
- package/src/commands/exe/sessions.md +2 -2
- package/src/commands/exe.md +22 -20
package/dist/lib/exe-daemon.js
CHANGED
|
@@ -30,7 +30,6 @@ var config_exports = {};
|
|
|
30
30
|
__export(config_exports, {
|
|
31
31
|
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
32
32
|
CONFIG_PATH: () => CONFIG_PATH,
|
|
33
|
-
COO_AGENT_NAME: () => COO_AGENT_NAME,
|
|
34
33
|
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
35
34
|
DB_PATH: () => DB_PATH,
|
|
36
35
|
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
@@ -186,7 +185,7 @@ async function loadConfigFrom(configPath) {
|
|
|
186
185
|
return { ...DEFAULT_CONFIG };
|
|
187
186
|
}
|
|
188
187
|
}
|
|
189
|
-
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH,
|
|
188
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
190
189
|
var init_config = __esm({
|
|
191
190
|
"src/lib/config.ts"() {
|
|
192
191
|
"use strict";
|
|
@@ -194,7 +193,6 @@ var init_config = __esm({
|
|
|
194
193
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
195
194
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
196
195
|
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
197
|
-
COO_AGENT_NAME = "exe";
|
|
198
196
|
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
199
197
|
CURRENT_CONFIG_VERSION = 1;
|
|
200
198
|
DEFAULT_CONFIG = {
|
|
@@ -230,13 +228,7 @@ var init_config = __esm({
|
|
|
230
228
|
wikiUrl: "",
|
|
231
229
|
wikiApiKey: "",
|
|
232
230
|
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
233
|
-
wikiWorkspaceMapping: {
|
|
234
|
-
exe: "Executive",
|
|
235
|
-
yoshi: "Engineering",
|
|
236
|
-
mari: "Marketing",
|
|
237
|
-
tom: "Engineering",
|
|
238
|
-
sasha: "Production"
|
|
239
|
-
},
|
|
231
|
+
wikiWorkspaceMapping: {},
|
|
240
232
|
wikiAutoUpdate: true,
|
|
241
233
|
wikiAutoUpdateThreshold: 0.5,
|
|
242
234
|
wikiAutoUpdateCreateNew: true,
|
|
@@ -720,7 +712,7 @@ function wrapWithRetry(client) {
|
|
|
720
712
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
721
713
|
}
|
|
722
714
|
if (prop === "batch") {
|
|
723
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
715
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
724
716
|
}
|
|
725
717
|
return Reflect.get(target, prop, receiver);
|
|
726
718
|
}
|
|
@@ -736,6 +728,68 @@ var init_db_retry = __esm({
|
|
|
736
728
|
}
|
|
737
729
|
});
|
|
738
730
|
|
|
731
|
+
// src/lib/employees.ts
|
|
732
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
733
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
734
|
+
import { execSync as execSync4 } from "child_process";
|
|
735
|
+
import path4 from "path";
|
|
736
|
+
import os4 from "os";
|
|
737
|
+
function normalizeRole(role) {
|
|
738
|
+
return (role ?? "").trim().toLowerCase();
|
|
739
|
+
}
|
|
740
|
+
function isCoordinatorRole(role) {
|
|
741
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
742
|
+
}
|
|
743
|
+
function getCoordinatorEmployee(employees) {
|
|
744
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
745
|
+
}
|
|
746
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
747
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
748
|
+
}
|
|
749
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
750
|
+
if (!agentName) return false;
|
|
751
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
752
|
+
}
|
|
753
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
754
|
+
if (!existsSync4(employeesPath)) {
|
|
755
|
+
return [];
|
|
756
|
+
}
|
|
757
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
758
|
+
try {
|
|
759
|
+
return JSON.parse(raw);
|
|
760
|
+
} catch {
|
|
761
|
+
return [];
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
765
|
+
if (!existsSync4(employeesPath)) return [];
|
|
766
|
+
try {
|
|
767
|
+
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
768
|
+
} catch {
|
|
769
|
+
return [];
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
function getEmployee(employees, name) {
|
|
773
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
774
|
+
}
|
|
775
|
+
function isMultiInstance(agentName, employees) {
|
|
776
|
+
const roster = employees ?? loadEmployeesSync();
|
|
777
|
+
const emp = getEmployee(roster, agentName);
|
|
778
|
+
if (!emp) return false;
|
|
779
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
780
|
+
}
|
|
781
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
782
|
+
var init_employees = __esm({
|
|
783
|
+
"src/lib/employees.ts"() {
|
|
784
|
+
"use strict";
|
|
785
|
+
init_config();
|
|
786
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
787
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
788
|
+
COORDINATOR_ROLE = "COO";
|
|
789
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
|
|
739
793
|
// src/lib/database.ts
|
|
740
794
|
var database_exports = {};
|
|
741
795
|
__export(database_exports, {
|
|
@@ -883,22 +937,24 @@ async function ensureSchema() {
|
|
|
883
937
|
ON behaviors(agent_id, active);
|
|
884
938
|
`);
|
|
885
939
|
try {
|
|
940
|
+
const coordinatorName = getCoordinatorName();
|
|
886
941
|
const existing = await client.execute({
|
|
887
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
888
|
-
args: []
|
|
942
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
943
|
+
args: [coordinatorName]
|
|
889
944
|
});
|
|
890
945
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
946
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
947
|
+
for (const [domain, content] of [
|
|
948
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
949
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
950
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
951
|
+
]) {
|
|
952
|
+
await client.execute({
|
|
953
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
954
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
955
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
956
|
+
});
|
|
957
|
+
}
|
|
902
958
|
}
|
|
903
959
|
} catch {
|
|
904
960
|
}
|
|
@@ -1590,6 +1646,39 @@ async function ensureSchema() {
|
|
|
1590
1646
|
} catch {
|
|
1591
1647
|
}
|
|
1592
1648
|
}
|
|
1649
|
+
try {
|
|
1650
|
+
await client.execute({
|
|
1651
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1652
|
+
args: []
|
|
1653
|
+
});
|
|
1654
|
+
} catch {
|
|
1655
|
+
}
|
|
1656
|
+
try {
|
|
1657
|
+
await client.execute(
|
|
1658
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1659
|
+
);
|
|
1660
|
+
} catch {
|
|
1661
|
+
}
|
|
1662
|
+
try {
|
|
1663
|
+
await client.execute({
|
|
1664
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1665
|
+
args: []
|
|
1666
|
+
});
|
|
1667
|
+
} catch {
|
|
1668
|
+
}
|
|
1669
|
+
try {
|
|
1670
|
+
await client.execute(
|
|
1671
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1672
|
+
);
|
|
1673
|
+
} catch {
|
|
1674
|
+
}
|
|
1675
|
+
try {
|
|
1676
|
+
await client.execute({
|
|
1677
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1678
|
+
args: []
|
|
1679
|
+
});
|
|
1680
|
+
} catch {
|
|
1681
|
+
}
|
|
1593
1682
|
}
|
|
1594
1683
|
async function disposeDatabase() {
|
|
1595
1684
|
if (_client) {
|
|
@@ -1603,6 +1692,7 @@ var init_database = __esm({
|
|
|
1603
1692
|
"src/lib/database.ts"() {
|
|
1604
1693
|
"use strict";
|
|
1605
1694
|
init_db_retry();
|
|
1695
|
+
init_employees();
|
|
1606
1696
|
_client = null;
|
|
1607
1697
|
_resilientClient = null;
|
|
1608
1698
|
initTurso = initDatabase;
|
|
@@ -1610,50 +1700,6 @@ var init_database = __esm({
|
|
|
1610
1700
|
}
|
|
1611
1701
|
});
|
|
1612
1702
|
|
|
1613
|
-
// src/lib/employees.ts
|
|
1614
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1615
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1616
|
-
import { execSync as execSync4 } from "child_process";
|
|
1617
|
-
import path4 from "path";
|
|
1618
|
-
import os4 from "os";
|
|
1619
|
-
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
1620
|
-
if (!existsSync4(employeesPath)) {
|
|
1621
|
-
return [];
|
|
1622
|
-
}
|
|
1623
|
-
const raw = await readFile2(employeesPath, "utf-8");
|
|
1624
|
-
try {
|
|
1625
|
-
return JSON.parse(raw);
|
|
1626
|
-
} catch {
|
|
1627
|
-
return [];
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1631
|
-
if (!existsSync4(employeesPath)) return [];
|
|
1632
|
-
try {
|
|
1633
|
-
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
1634
|
-
} catch {
|
|
1635
|
-
return [];
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
function getEmployee(employees, name) {
|
|
1639
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
1640
|
-
}
|
|
1641
|
-
function isMultiInstance(agentName, employees) {
|
|
1642
|
-
const roster = employees ?? loadEmployeesSync();
|
|
1643
|
-
const emp = getEmployee(roster, agentName);
|
|
1644
|
-
if (!emp) return false;
|
|
1645
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1646
|
-
}
|
|
1647
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1648
|
-
var init_employees = __esm({
|
|
1649
|
-
"src/lib/employees.ts"() {
|
|
1650
|
-
"use strict";
|
|
1651
|
-
init_config();
|
|
1652
|
-
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
1653
|
-
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1654
|
-
}
|
|
1655
|
-
});
|
|
1656
|
-
|
|
1657
1703
|
// src/lib/license.ts
|
|
1658
1704
|
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
1659
1705
|
import { randomUUID } from "crypto";
|
|
@@ -2235,6 +2281,36 @@ async function listTasks(input) {
|
|
|
2235
2281
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
2236
2282
|
}));
|
|
2237
2283
|
}
|
|
2284
|
+
function isTmuxSessionAlive(identifier) {
|
|
2285
|
+
if (!identifier || identifier === "unknown") return true;
|
|
2286
|
+
try {
|
|
2287
|
+
if (identifier.startsWith("%")) {
|
|
2288
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
2289
|
+
timeout: 2e3,
|
|
2290
|
+
encoding: "utf8",
|
|
2291
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2292
|
+
});
|
|
2293
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2294
|
+
} else {
|
|
2295
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2296
|
+
timeout: 2e3,
|
|
2297
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2298
|
+
});
|
|
2299
|
+
return true;
|
|
2300
|
+
}
|
|
2301
|
+
} catch {
|
|
2302
|
+
if (identifier.startsWith("%")) return true;
|
|
2303
|
+
try {
|
|
2304
|
+
execSync5("tmux list-sessions", {
|
|
2305
|
+
timeout: 2e3,
|
|
2306
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2307
|
+
});
|
|
2308
|
+
return false;
|
|
2309
|
+
} catch {
|
|
2310
|
+
return true;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2238
2314
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
2239
2315
|
if (!taskContext) return null;
|
|
2240
2316
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -2297,13 +2373,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2297
2373
|
});
|
|
2298
2374
|
if (claim.rowsAffected === 0) {
|
|
2299
2375
|
const current = await client.execute({
|
|
2300
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
2376
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
2301
2377
|
args: [taskId]
|
|
2302
2378
|
});
|
|
2303
2379
|
const cur = current.rows[0];
|
|
2304
|
-
const
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2380
|
+
const curStatus = cur?.status ?? "unknown";
|
|
2381
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
2382
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
2383
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
2384
|
+
process.stderr.write(
|
|
2385
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
2386
|
+
`
|
|
2387
|
+
);
|
|
2388
|
+
await client.execute({
|
|
2389
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
2390
|
+
args: [now, taskId]
|
|
2391
|
+
});
|
|
2392
|
+
const retried = await client.execute({
|
|
2393
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
2394
|
+
args: [tmuxSession, now, taskId]
|
|
2395
|
+
});
|
|
2396
|
+
if (retried.rowsAffected > 0) {
|
|
2397
|
+
try {
|
|
2398
|
+
await writeCheckpoint({
|
|
2399
|
+
taskId,
|
|
2400
|
+
step: "reclaimed_dead_session",
|
|
2401
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
2402
|
+
});
|
|
2403
|
+
} catch {
|
|
2404
|
+
}
|
|
2405
|
+
return { row, taskFile, now, taskId };
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
2409
|
+
process.stderr.write(
|
|
2410
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2411
|
+
`
|
|
2412
|
+
);
|
|
2413
|
+
await client.execute({
|
|
2414
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
2415
|
+
args: [tmuxSession, now, taskId]
|
|
2416
|
+
});
|
|
2417
|
+
try {
|
|
2418
|
+
await writeCheckpoint({
|
|
2419
|
+
taskId,
|
|
2420
|
+
step: "assigner_override",
|
|
2421
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
2422
|
+
});
|
|
2423
|
+
} catch {
|
|
2424
|
+
}
|
|
2425
|
+
return { row, taskFile, now, taskId };
|
|
2426
|
+
}
|
|
2427
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
2428
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
2307
2429
|
}
|
|
2308
2430
|
try {
|
|
2309
2431
|
await writeCheckpoint({
|
|
@@ -2401,7 +2523,7 @@ var init_tasks_crud = __esm({
|
|
|
2401
2523
|
"use strict";
|
|
2402
2524
|
init_database();
|
|
2403
2525
|
init_task_scope();
|
|
2404
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
2526
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2405
2527
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2406
2528
|
}
|
|
2407
2529
|
});
|
|
@@ -2557,21 +2679,23 @@ function getReviewChecklist(role, agent, taskSlug) {
|
|
|
2557
2679
|
}
|
|
2558
2680
|
async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
2559
2681
|
const taskFile = String(row.task_file);
|
|
2560
|
-
|
|
2682
|
+
const employees = await loadEmployees();
|
|
2683
|
+
const coordinatorName = getCoordinatorName(employees);
|
|
2684
|
+
if (String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to), employees)) return;
|
|
2561
2685
|
if (String(row.title).startsWith("Review:")) return;
|
|
2562
2686
|
const fileName = taskFile.split("/").pop() ?? "";
|
|
2563
2687
|
if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
|
|
2564
2688
|
if (fileName.startsWith("review-") && String(row.assigned_to) === "system") return;
|
|
2565
2689
|
const client = getClient();
|
|
2566
2690
|
const agent = String(row.assigned_to);
|
|
2567
|
-
const
|
|
2691
|
+
const rawReviewer = row.reviewer || row.assigned_by;
|
|
2692
|
+
const reviewer = rawReviewer ? String(rawReviewer) : coordinatorName;
|
|
2568
2693
|
const currentStatus = String(row.status ?? "");
|
|
2569
2694
|
if (currentStatus === "done") return;
|
|
2570
2695
|
const existingResult = String(row.result ?? "");
|
|
2571
2696
|
if (existingResult.includes("## Review notes")) return;
|
|
2572
2697
|
let reviewerRole = "unknown";
|
|
2573
2698
|
try {
|
|
2574
|
-
const employees = await loadEmployees();
|
|
2575
2699
|
const emp = getEmployee(employees, reviewer);
|
|
2576
2700
|
if (emp) reviewerRole = emp.role;
|
|
2577
2701
|
} catch {
|
|
@@ -2616,7 +2740,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
|
|
|
2616
2740
|
agentRole: String(row.assigned_to),
|
|
2617
2741
|
event: "task_complete",
|
|
2618
2742
|
project: String(row.project_name),
|
|
2619
|
-
summary: `completed "${taskTitle}" \u2014
|
|
2743
|
+
summary: `completed "${taskTitle}" \u2014 ready for review`,
|
|
2620
2744
|
taskFile
|
|
2621
2745
|
});
|
|
2622
2746
|
const originalPriority = String(row.priority).toLowerCase();
|
|
@@ -2855,7 +2979,7 @@ function findSessionForProject(projectName) {
|
|
|
2855
2979
|
const sessions = listSessions();
|
|
2856
2980
|
for (const s of sessions) {
|
|
2857
2981
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2858
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
2982
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
2859
2983
|
}
|
|
2860
2984
|
return null;
|
|
2861
2985
|
}
|
|
@@ -2895,12 +3019,13 @@ var init_session_scope = __esm({
|
|
|
2895
3019
|
init_session_registry();
|
|
2896
3020
|
init_project_name();
|
|
2897
3021
|
init_tmux_routing();
|
|
3022
|
+
init_employees();
|
|
2898
3023
|
}
|
|
2899
3024
|
});
|
|
2900
3025
|
|
|
2901
3026
|
// src/lib/tasks-notify.ts
|
|
2902
3027
|
async function dispatchTaskToEmployee(input) {
|
|
2903
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
3028
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2904
3029
|
let crossProject = false;
|
|
2905
3030
|
if (input.projectName) {
|
|
2906
3031
|
try {
|
|
@@ -3343,6 +3468,24 @@ async function updateTask(input) {
|
|
|
3343
3468
|
});
|
|
3344
3469
|
} catch {
|
|
3345
3470
|
}
|
|
3471
|
+
const assignedAgent = String(row.assigned_to);
|
|
3472
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
3473
|
+
try {
|
|
3474
|
+
const draftClient = getClient();
|
|
3475
|
+
if (input.status === "done") {
|
|
3476
|
+
await draftClient.execute({
|
|
3477
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3478
|
+
args: [assignedAgent]
|
|
3479
|
+
});
|
|
3480
|
+
} else if (input.status === "cancelled") {
|
|
3481
|
+
await draftClient.execute({
|
|
3482
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
3483
|
+
args: [assignedAgent]
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
} catch {
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3346
3489
|
try {
|
|
3347
3490
|
const client = getClient();
|
|
3348
3491
|
const cascaded = await client.execute({
|
|
@@ -3361,8 +3504,8 @@ async function updateTask(input) {
|
|
|
3361
3504
|
}
|
|
3362
3505
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3363
3506
|
if (isTerminal) {
|
|
3364
|
-
const
|
|
3365
|
-
if (!
|
|
3507
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
3508
|
+
if (!isCoordinator) {
|
|
3366
3509
|
notifyTaskDone();
|
|
3367
3510
|
}
|
|
3368
3511
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -3386,7 +3529,7 @@ async function updateTask(input) {
|
|
|
3386
3529
|
}
|
|
3387
3530
|
}
|
|
3388
3531
|
}
|
|
3389
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
3532
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3390
3533
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3391
3534
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3392
3535
|
taskId,
|
|
@@ -3402,7 +3545,7 @@ async function updateTask(input) {
|
|
|
3402
3545
|
});
|
|
3403
3546
|
}
|
|
3404
3547
|
let nextTask;
|
|
3405
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
3548
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
3406
3549
|
try {
|
|
3407
3550
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3408
3551
|
} catch {
|
|
@@ -3429,12 +3572,14 @@ async function updateTask(input) {
|
|
|
3429
3572
|
async function deleteTask(taskId, baseDir) {
|
|
3430
3573
|
const client = getClient();
|
|
3431
3574
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
3432
|
-
const
|
|
3575
|
+
const coordinatorName = getCoordinatorName();
|
|
3576
|
+
const reviewer = assignedBy || coordinatorName;
|
|
3433
3577
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
3434
3578
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
3579
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
3435
3580
|
await client.execute({
|
|
3436
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
3437
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3581
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
3582
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3438
3583
|
});
|
|
3439
3584
|
await markAsReadByTaskFile(taskFile);
|
|
3440
3585
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -3446,6 +3591,7 @@ var init_tasks = __esm({
|
|
|
3446
3591
|
init_config();
|
|
3447
3592
|
init_notifications();
|
|
3448
3593
|
init_state_bus();
|
|
3594
|
+
init_employees();
|
|
3449
3595
|
init_tasks_crud();
|
|
3450
3596
|
init_tasks_review();
|
|
3451
3597
|
init_tasks_crud();
|
|
@@ -3531,7 +3677,7 @@ function _resetLastRelaunchCache() {
|
|
|
3531
3677
|
}
|
|
3532
3678
|
async function lastResumeCreatedAtMs(agentId) {
|
|
3533
3679
|
const client = getClient();
|
|
3534
|
-
const cmScope = sessionScopeFilter();
|
|
3680
|
+
const cmScope = sessionScopeFilter(null);
|
|
3535
3681
|
const result = await client.execute({
|
|
3536
3682
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
3537
3683
|
FROM tasks
|
|
@@ -3556,7 +3702,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
3556
3702
|
const client = getClient();
|
|
3557
3703
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3558
3704
|
const context = buildResumeContext(agentId, openTasks);
|
|
3559
|
-
const rdScope = sessionScopeFilter();
|
|
3705
|
+
const rdScope = sessionScopeFilter(null);
|
|
3560
3706
|
const existing = await client.execute({
|
|
3561
3707
|
sql: `SELECT id FROM tasks
|
|
3562
3708
|
WHERE assigned_to = ?
|
|
@@ -3590,7 +3736,7 @@ async function pollCapacityDead() {
|
|
|
3590
3736
|
const transport = getTransport();
|
|
3591
3737
|
const relaunched = [];
|
|
3592
3738
|
const registered = listSessions().filter(
|
|
3593
|
-
(s) => s.agentId !== "exe"
|
|
3739
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
3594
3740
|
);
|
|
3595
3741
|
if (registered.length === 0) return [];
|
|
3596
3742
|
let liveSessions;
|
|
@@ -3650,7 +3796,7 @@ async function pollCapacityDead() {
|
|
|
3650
3796
|
reason: "capacity"
|
|
3651
3797
|
});
|
|
3652
3798
|
const client = getClient();
|
|
3653
|
-
const rlScope = sessionScopeFilter();
|
|
3799
|
+
const rlScope = sessionScopeFilter(null);
|
|
3654
3800
|
const openTasks = await client.execute({
|
|
3655
3801
|
sql: `SELECT id, title, priority, task_file, status
|
|
3656
3802
|
FROM tasks
|
|
@@ -3704,6 +3850,7 @@ var init_capacity_monitor = __esm({
|
|
|
3704
3850
|
init_session_kill_telemetry();
|
|
3705
3851
|
init_tmux_routing();
|
|
3706
3852
|
init_task_scope();
|
|
3853
|
+
init_employees();
|
|
3707
3854
|
CAPACITY_PATTERNS = [
|
|
3708
3855
|
/conversation is too long/i,
|
|
3709
3856
|
/maximum context length/i,
|
|
@@ -3853,7 +4000,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
3853
4000
|
exeSession = root;
|
|
3854
4001
|
} else {
|
|
3855
4002
|
throw new Error(
|
|
3856
|
-
`Invalid
|
|
4003
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3857
4004
|
);
|
|
3858
4005
|
}
|
|
3859
4006
|
}
|
|
@@ -3873,8 +4020,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
3873
4020
|
return match?.[1] ?? null;
|
|
3874
4021
|
}
|
|
3875
4022
|
function extractRootExe(name) {
|
|
3876
|
-
|
|
3877
|
-
|
|
4023
|
+
if (!name) return null;
|
|
4024
|
+
if (!name.includes("-")) return name;
|
|
4025
|
+
const parts = name.split("-").filter(Boolean);
|
|
4026
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3878
4027
|
}
|
|
3879
4028
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3880
4029
|
if (!existsSync10(SESSION_CACHE)) {
|
|
@@ -4019,12 +4168,14 @@ function isSessionBusy(sessionName) {
|
|
|
4019
4168
|
return state === "thinking" || state === "tool";
|
|
4020
4169
|
}
|
|
4021
4170
|
function isExeSession(sessionName) {
|
|
4022
|
-
|
|
4171
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
4172
|
+
const coordinatorName = getCoordinatorName();
|
|
4173
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
4023
4174
|
}
|
|
4024
4175
|
function sendIntercom(targetSession) {
|
|
4025
4176
|
const transport = getTransport();
|
|
4026
4177
|
if (isExeSession(targetSession)) {
|
|
4027
|
-
logIntercom(`
|
|
4178
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
4028
4179
|
return "skipped_exe";
|
|
4029
4180
|
}
|
|
4030
4181
|
if (isDebounced(targetSession)) {
|
|
@@ -4076,7 +4227,7 @@ function notifyParentExe(sessionKey) {
|
|
|
4076
4227
|
if (result === "failed") {
|
|
4077
4228
|
const rootExe = resolveExeSession();
|
|
4078
4229
|
if (rootExe && rootExe !== target) {
|
|
4079
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
4230
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
4080
4231
|
`);
|
|
4081
4232
|
const fallback = sendIntercom(rootExe);
|
|
4082
4233
|
return fallback !== "failed";
|
|
@@ -4086,8 +4237,8 @@ function notifyParentExe(sessionKey) {
|
|
|
4086
4237
|
return true;
|
|
4087
4238
|
}
|
|
4088
4239
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4089
|
-
if (employeeName === "exe") {
|
|
4090
|
-
return { status: "failed", sessionName: "", error: "
|
|
4240
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
4241
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
4091
4242
|
}
|
|
4092
4243
|
try {
|
|
4093
4244
|
assertEmployeeLimitSync();
|
|
@@ -4096,8 +4247,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4096
4247
|
return { status: "failed", sessionName: "", error: err.message };
|
|
4097
4248
|
}
|
|
4098
4249
|
}
|
|
4099
|
-
if (
|
|
4100
|
-
const bare = employeeName.
|
|
4250
|
+
if (employeeName.includes("-")) {
|
|
4251
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
4101
4252
|
return {
|
|
4102
4253
|
status: "failed",
|
|
4103
4254
|
sessionName: "",
|
|
@@ -4116,7 +4267,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4116
4267
|
return {
|
|
4117
4268
|
status: "failed",
|
|
4118
4269
|
sessionName: "",
|
|
4119
|
-
error: `Invalid
|
|
4270
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
4120
4271
|
};
|
|
4121
4272
|
}
|
|
4122
4273
|
}
|
|
@@ -4273,8 +4424,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4273
4424
|
const ctxContent = [
|
|
4274
4425
|
`## Session Context`,
|
|
4275
4426
|
`You are running in tmux session: ${sessionName}.`,
|
|
4276
|
-
`Your parent
|
|
4277
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
4427
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
4428
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4278
4429
|
].join("\n");
|
|
4279
4430
|
writeFileSync6(ctxFile, ctxContent);
|
|
4280
4431
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -4378,6 +4529,7 @@ var init_tmux_routing = __esm({
|
|
|
4378
4529
|
init_provider_table();
|
|
4379
4530
|
init_intercom_queue();
|
|
4380
4531
|
init_plan_limits();
|
|
4532
|
+
init_employees();
|
|
4381
4533
|
SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
4382
4534
|
SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
|
|
4383
4535
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -4674,7 +4826,7 @@ import os7 from "os";
|
|
|
4674
4826
|
import path14 from "path";
|
|
4675
4827
|
async function hasOpenTasks(client, agentId) {
|
|
4676
4828
|
try {
|
|
4677
|
-
const scope = sessionScopeFilter();
|
|
4829
|
+
const scope = sessionScopeFilter(null);
|
|
4678
4830
|
const result = await client.execute({
|
|
4679
4831
|
sql: `SELECT 1 FROM tasks
|
|
4680
4832
|
WHERE assigned_to = ? AND status IN ('open', 'in_progress')${scope.sql}
|
|
@@ -4688,7 +4840,7 @@ async function hasOpenTasks(client, agentId) {
|
|
|
4688
4840
|
}
|
|
4689
4841
|
async function hasNeedsReview(client, agentId) {
|
|
4690
4842
|
try {
|
|
4691
|
-
const scope = sessionScopeFilter();
|
|
4843
|
+
const scope = sessionScopeFilter(null);
|
|
4692
4844
|
const result = await client.execute({
|
|
4693
4845
|
sql: `SELECT 1 FROM tasks
|
|
4694
4846
|
WHERE assigned_to = ? AND status = 'needs_review'${scope.sql}
|
|
@@ -4760,6 +4912,8 @@ var daemon_orchestration_exports = {};
|
|
|
4760
4912
|
__export(daemon_orchestration_exports, {
|
|
4761
4913
|
IDLE_KILL_INTERCOM_ACK_WINDOW_MS: () => IDLE_KILL_INTERCOM_ACK_WINDOW_MS,
|
|
4762
4914
|
IDLE_NUDGE_DEDUP_MS: () => IDLE_NUDGE_DEDUP_MS,
|
|
4915
|
+
ORPHAN_PATTERNS: () => ORPHAN_PATTERNS,
|
|
4916
|
+
ORPHAN_SIGKILL_DELAY_MS: () => ORPHAN_SIGKILL_DELAY_MS,
|
|
4763
4917
|
REVIEW_NUDGE_COOLDOWN_MS: () => REVIEW_NUDGE_COOLDOWN_MS,
|
|
4764
4918
|
SESSION_CONTEXT_THRESHOLD_PCT: () => SESSION_CONTEXT_THRESHOLD_PCT,
|
|
4765
4919
|
SESSION_TTL_HOURS: () => SESSION_TTL_HOURS,
|
|
@@ -4767,12 +4921,14 @@ __export(daemon_orchestration_exports, {
|
|
|
4767
4921
|
classifyTtlKillReason: () => classifyTtlKillReason,
|
|
4768
4922
|
createIdleKillRealDeps: () => createIdleKillRealDeps,
|
|
4769
4923
|
createIdleNudgeRealDeps: () => createIdleNudgeRealDeps,
|
|
4924
|
+
createOrphanReaperRealDeps: () => createOrphanReaperRealDeps,
|
|
4770
4925
|
createReviewNudgeRealDeps: () => createReviewNudgeRealDeps,
|
|
4771
4926
|
createSessionTTLRealDeps: () => createSessionTTLRealDeps,
|
|
4772
4927
|
loadNudgeState: () => loadNudgeState,
|
|
4773
4928
|
pollIdleEmployees: () => pollIdleEmployees,
|
|
4774
4929
|
pollIdleKill: () => pollIdleKill,
|
|
4775
4930
|
pollReviewNudge: () => pollReviewNudge,
|
|
4931
|
+
reapOrphanedMcpProcesses: () => reapOrphanedMcpProcesses,
|
|
4776
4932
|
saveNudgeState: () => saveNudgeState,
|
|
4777
4933
|
shouldKillIdleSession: () => shouldKillIdleSession,
|
|
4778
4934
|
shouldKillSession: () => shouldKillSession,
|
|
@@ -4808,7 +4964,9 @@ function shouldKillIdleSession(input) {
|
|
|
4808
4964
|
return true;
|
|
4809
4965
|
}
|
|
4810
4966
|
async function pollIdleEmployees(deps, lastNudge) {
|
|
4811
|
-
const registered = deps.listRegisteredSessions().filter(
|
|
4967
|
+
const registered = deps.listRegisteredSessions().filter(
|
|
4968
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
4969
|
+
);
|
|
4812
4970
|
if (registered.length === 0) return [];
|
|
4813
4971
|
let liveSessions;
|
|
4814
4972
|
try {
|
|
@@ -4835,7 +4993,9 @@ async function pollIdleEmployees(deps, lastNudge) {
|
|
|
4835
4993
|
return nudged;
|
|
4836
4994
|
}
|
|
4837
4995
|
function checkSessionTTL(deps) {
|
|
4838
|
-
const registered = deps.listRegisteredSessions().filter(
|
|
4996
|
+
const registered = deps.listRegisteredSessions().filter(
|
|
4997
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
4998
|
+
);
|
|
4839
4999
|
if (registered.length === 0) return [];
|
|
4840
5000
|
let liveSessions;
|
|
4841
5001
|
try {
|
|
@@ -4870,7 +5030,7 @@ function checkSessionTTL(deps) {
|
|
|
4870
5030
|
async function pollIdleKill(deps, idleTickCounts, opts) {
|
|
4871
5031
|
if (!opts.enabled) return [];
|
|
4872
5032
|
const registered = deps.listRegisteredSessions().filter(
|
|
4873
|
-
(s) => s.agentId !== "exe" && !isExeSession(s.windowName)
|
|
5033
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId) && !isExeSession(s.windowName)
|
|
4874
5034
|
);
|
|
4875
5035
|
if (registered.length === 0) return [];
|
|
4876
5036
|
let liveSessions;
|
|
@@ -4928,7 +5088,7 @@ async function pollIdleKill(deps, idleTickCounts, opts) {
|
|
|
4928
5088
|
async function pollReviewNudge(deps, state) {
|
|
4929
5089
|
let sessions;
|
|
4930
5090
|
try {
|
|
4931
|
-
sessions = deps.listTmuxSessions().filter((s) =>
|
|
5091
|
+
sessions = deps.listTmuxSessions().filter((s) => isExeSession(s));
|
|
4932
5092
|
} catch {
|
|
4933
5093
|
return [];
|
|
4934
5094
|
}
|
|
@@ -5026,7 +5186,7 @@ function createIdleNudgeRealDeps(getClient2) {
|
|
|
5026
5186
|
},
|
|
5027
5187
|
queryOpenTask: async (agentId) => {
|
|
5028
5188
|
const client = getClient2();
|
|
5029
|
-
const doScope = sessionScopeFilter();
|
|
5189
|
+
const doScope = sessionScopeFilter(null);
|
|
5030
5190
|
const result = await client.execute({
|
|
5031
5191
|
sql: `SELECT id, title, priority FROM tasks
|
|
5032
5192
|
WHERE assigned_to = ? AND status IN ('open', 'in_progress', 'needs_review')${doScope.sql}
|
|
@@ -5108,18 +5268,76 @@ function createIdleKillRealDeps(getClient2, intercomAckWindowMs) {
|
|
|
5108
5268
|
}
|
|
5109
5269
|
};
|
|
5110
5270
|
}
|
|
5111
|
-
|
|
5271
|
+
function reapOrphanedMcpProcesses(deps) {
|
|
5272
|
+
const lines = deps.listProcesses();
|
|
5273
|
+
const reaped = [];
|
|
5274
|
+
for (const line of lines) {
|
|
5275
|
+
const trimmed = line.trim();
|
|
5276
|
+
const match = trimmed.match(/^(\d+)\s+1\s+(.+)$/);
|
|
5277
|
+
if (!match) continue;
|
|
5278
|
+
const pid = parseInt(match[1], 10);
|
|
5279
|
+
const args = match[2];
|
|
5280
|
+
if (pid === deps.selfPid) continue;
|
|
5281
|
+
if (!ORPHAN_PATTERNS.some((pat) => args.includes(pat))) continue;
|
|
5282
|
+
try {
|
|
5283
|
+
deps.killProcess(pid, "SIGTERM");
|
|
5284
|
+
} catch {
|
|
5285
|
+
continue;
|
|
5286
|
+
}
|
|
5287
|
+
const desc = `PID ${pid} (${args.slice(0, 100)})`;
|
|
5288
|
+
reaped.push(desc);
|
|
5289
|
+
deps.scheduleKill(pid, ORPHAN_SIGKILL_DELAY_MS, () => {
|
|
5290
|
+
try {
|
|
5291
|
+
deps.killProcess(pid, "SIGKILL");
|
|
5292
|
+
} catch {
|
|
5293
|
+
}
|
|
5294
|
+
});
|
|
5295
|
+
}
|
|
5296
|
+
return reaped;
|
|
5297
|
+
}
|
|
5298
|
+
function createOrphanReaperRealDeps() {
|
|
5299
|
+
return {
|
|
5300
|
+
listProcesses: () => {
|
|
5301
|
+
const output = execSync9("ps -eo pid,ppid,args", {
|
|
5302
|
+
encoding: "utf8",
|
|
5303
|
+
timeout: 5e3
|
|
5304
|
+
});
|
|
5305
|
+
return output.split("\n");
|
|
5306
|
+
},
|
|
5307
|
+
killProcess: (pid, signal) => {
|
|
5308
|
+
process.kill(pid, signal);
|
|
5309
|
+
},
|
|
5310
|
+
scheduleKill: (_pid, delayMs, cb) => {
|
|
5311
|
+
setTimeout(() => {
|
|
5312
|
+
try {
|
|
5313
|
+
cb();
|
|
5314
|
+
} catch {
|
|
5315
|
+
}
|
|
5316
|
+
}, delayMs).unref();
|
|
5317
|
+
},
|
|
5318
|
+
selfPid: process.pid
|
|
5319
|
+
};
|
|
5320
|
+
}
|
|
5321
|
+
var IDLE_NUDGE_DEDUP_MS, SESSION_TTL_HOURS, SESSION_CONTEXT_THRESHOLD_PCT, IDLE_KILL_INTERCOM_ACK_WINDOW_MS, REVIEW_NUDGE_COOLDOWN_MS, NUDGE_STATE_PATH, ORPHAN_SIGKILL_DELAY_MS, ORPHAN_PATTERNS;
|
|
5112
5322
|
var init_daemon_orchestration = __esm({
|
|
5113
5323
|
"src/lib/daemon-orchestration.ts"() {
|
|
5114
5324
|
"use strict";
|
|
5115
5325
|
init_tmux_routing();
|
|
5116
5326
|
init_task_scope();
|
|
5327
|
+
init_employees();
|
|
5117
5328
|
IDLE_NUDGE_DEDUP_MS = 6e4;
|
|
5118
5329
|
SESSION_TTL_HOURS = 4;
|
|
5119
5330
|
SESSION_CONTEXT_THRESHOLD_PCT = 50;
|
|
5120
5331
|
IDLE_KILL_INTERCOM_ACK_WINDOW_MS = 1e4;
|
|
5121
5332
|
REVIEW_NUDGE_COOLDOWN_MS = 3e5;
|
|
5122
5333
|
NUDGE_STATE_PATH = join(homedir(), ".exe-os", "review-nudge-state.json");
|
|
5334
|
+
ORPHAN_SIGKILL_DELAY_MS = 5e3;
|
|
5335
|
+
ORPHAN_PATTERNS = [
|
|
5336
|
+
"exe-os/dist/mcp/server.js",
|
|
5337
|
+
"exe-mem/dist/mcp/server.js",
|
|
5338
|
+
"exe-os/dist/hooks/ingest-worker.js",
|
|
5339
|
+
"exe-mem/dist/hooks/ingest-worker.js"
|
|
5340
|
+
];
|
|
5123
5341
|
}
|
|
5124
5342
|
});
|
|
5125
5343
|
|
|
@@ -5360,7 +5578,11 @@ async function ensureShardSchema(client) {
|
|
|
5360
5578
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
5361
5579
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
5362
5580
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
5363
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
5581
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
5582
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
5583
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
5584
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
5585
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
5364
5586
|
]) {
|
|
5365
5587
|
try {
|
|
5366
5588
|
await client.execute(col);
|
|
@@ -5490,26 +5712,26 @@ var init_platform_procedures = __esm({
|
|
|
5490
5712
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
5491
5713
|
domain: "architecture",
|
|
5492
5714
|
priority: "p0",
|
|
5493
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
5715
|
+
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
|
|
5494
5716
|
},
|
|
5495
5717
|
{
|
|
5496
5718
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
5497
5719
|
domain: "architecture",
|
|
5498
5720
|
priority: "p0",
|
|
5499
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
5721
|
+
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
|
|
5500
5722
|
},
|
|
5501
5723
|
{
|
|
5502
|
-
title: "Sessions explained \u2014
|
|
5724
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
5503
5725
|
domain: "architecture",
|
|
5504
5726
|
priority: "p0",
|
|
5505
|
-
content: "Each
|
|
5727
|
+
content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
|
|
5506
5728
|
},
|
|
5507
5729
|
// --- Hierarchy and dispatch ---
|
|
5508
5730
|
{
|
|
5509
5731
|
title: "Chain of command \u2014 who talks to whom",
|
|
5510
5732
|
domain: "workflow",
|
|
5511
5733
|
priority: "p0",
|
|
5512
|
-
content: "Founder
|
|
5734
|
+
content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
|
|
5513
5735
|
},
|
|
5514
5736
|
{
|
|
5515
5737
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -5519,30 +5741,30 @@ var init_platform_procedures = __esm({
|
|
|
5519
5741
|
},
|
|
5520
5742
|
// --- Session isolation ---
|
|
5521
5743
|
{
|
|
5522
|
-
title: "Session scoping \u2014 stay in your
|
|
5744
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
5523
5745
|
domain: "security",
|
|
5524
5746
|
priority: "p0",
|
|
5525
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
5747
|
+
content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
|
|
5526
5748
|
},
|
|
5527
5749
|
{
|
|
5528
5750
|
title: "Session isolation \u2014 never touch another session's work",
|
|
5529
5751
|
domain: "workflow",
|
|
5530
5752
|
priority: "p0",
|
|
5531
|
-
content:
|
|
5753
|
+
content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
|
|
5532
5754
|
},
|
|
5533
5755
|
// --- Engineering: session scoping in code ---
|
|
5534
5756
|
{
|
|
5535
5757
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
5536
5758
|
domain: "architecture",
|
|
5537
5759
|
priority: "p0",
|
|
5538
|
-
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current
|
|
5760
|
+
content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
|
|
5539
5761
|
},
|
|
5540
5762
|
// --- Hard constraints ---
|
|
5541
5763
|
{
|
|
5542
5764
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
5543
5765
|
domain: "security",
|
|
5544
5766
|
priority: "p0",
|
|
5545
|
-
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014
|
|
5767
|
+
content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
|
|
5546
5768
|
},
|
|
5547
5769
|
// --- Operations ---
|
|
5548
5770
|
{
|
|
@@ -5782,7 +6004,10 @@ async function writeMemory(record) {
|
|
|
5782
6004
|
source_path: record.source_path ?? null,
|
|
5783
6005
|
source_type: record.source_type ?? null,
|
|
5784
6006
|
tier: record.tier ?? classifyTier(record),
|
|
5785
|
-
supersedes_id: record.supersedes_id ?? null
|
|
6007
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
6008
|
+
draft: record.draft ? 1 : 0,
|
|
6009
|
+
memory_type: record.memory_type ?? "raw",
|
|
6010
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
5786
6011
|
};
|
|
5787
6012
|
_pendingRecords.push(dbRow);
|
|
5788
6013
|
orgBus.emit({
|
|
@@ -5837,6 +6062,9 @@ async function flushBatch() {
|
|
|
5837
6062
|
const sourceType = row.source_type ?? null;
|
|
5838
6063
|
const tier = row.tier ?? 3;
|
|
5839
6064
|
const supersedesId = row.supersedes_id ?? null;
|
|
6065
|
+
const draft = row.draft ? 1 : 0;
|
|
6066
|
+
const memoryType = row.memory_type ?? "raw";
|
|
6067
|
+
const trajectory = row.trajectory ?? null;
|
|
5840
6068
|
return {
|
|
5841
6069
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
5842
6070
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -5844,15 +6072,15 @@ async function flushBatch() {
|
|
|
5844
6072
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5845
6073
|
confidence, last_accessed,
|
|
5846
6074
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5847
|
-
source_path, source_type, tier, supersedes_id)
|
|
5848
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
6075
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
6076
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
5849
6077
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
5850
6078
|
tool_name, project_name,
|
|
5851
6079
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
5852
6080
|
confidence, last_accessed,
|
|
5853
6081
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
5854
|
-
source_path, source_type, tier, supersedes_id)
|
|
5855
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
6082
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
6083
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
5856
6084
|
args: hasVector ? [
|
|
5857
6085
|
row.id,
|
|
5858
6086
|
row.agent_id,
|
|
@@ -5878,7 +6106,10 @@ async function flushBatch() {
|
|
|
5878
6106
|
sourcePath,
|
|
5879
6107
|
sourceType,
|
|
5880
6108
|
tier,
|
|
5881
|
-
supersedesId
|
|
6109
|
+
supersedesId,
|
|
6110
|
+
draft,
|
|
6111
|
+
memoryType,
|
|
6112
|
+
trajectory
|
|
5882
6113
|
] : [
|
|
5883
6114
|
row.id,
|
|
5884
6115
|
row.agent_id,
|
|
@@ -5903,7 +6134,10 @@ async function flushBatch() {
|
|
|
5903
6134
|
sourcePath,
|
|
5904
6135
|
sourceType,
|
|
5905
6136
|
tier,
|
|
5906
|
-
supersedesId
|
|
6137
|
+
supersedesId,
|
|
6138
|
+
draft,
|
|
6139
|
+
memoryType,
|
|
6140
|
+
trajectory
|
|
5907
6141
|
]
|
|
5908
6142
|
};
|
|
5909
6143
|
};
|
|
@@ -5972,6 +6206,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
5972
6206
|
const limit = options?.limit ?? 10;
|
|
5973
6207
|
const statusFilter = options?.includeArchived ? "" : `
|
|
5974
6208
|
AND COALESCE(status, 'active') = 'active'`;
|
|
6209
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
6210
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
5975
6211
|
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
5976
6212
|
tool_name, project_name,
|
|
5977
6213
|
has_error, raw_text, vector, importance, status,
|
|
@@ -5981,7 +6217,7 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
5981
6217
|
source_path, source_type
|
|
5982
6218
|
FROM memories
|
|
5983
6219
|
WHERE agent_id = ?
|
|
5984
|
-
AND vector IS NOT NULL${statusFilter}
|
|
6220
|
+
AND vector IS NOT NULL${statusFilter}${draftFilter}
|
|
5985
6221
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
5986
6222
|
const args = [agentId];
|
|
5987
6223
|
const scope = buildWikiScopeFilter(options, "");
|
|
@@ -6003,6 +6239,10 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
6003
6239
|
sql += ` AND timestamp >= ?`;
|
|
6004
6240
|
args.push(options.since);
|
|
6005
6241
|
}
|
|
6242
|
+
if (options?.memoryType) {
|
|
6243
|
+
sql += ` AND memory_type = ?`;
|
|
6244
|
+
args.push(options.memoryType);
|
|
6245
|
+
}
|
|
6006
6246
|
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
6007
6247
|
args.push(vectorToBlob(queryVector));
|
|
6008
6248
|
sql += ` LIMIT ?`;
|
|
@@ -6158,7 +6398,7 @@ import { execSync as execSync10 } from "child_process";
|
|
|
6158
6398
|
async function pollPendingReviews(deps, state) {
|
|
6159
6399
|
let sessions;
|
|
6160
6400
|
try {
|
|
6161
|
-
sessions = deps.listTmuxSessions().filter((s) =>
|
|
6401
|
+
sessions = deps.listTmuxSessions().filter((s) => isExeSession(s));
|
|
6162
6402
|
} catch {
|
|
6163
6403
|
return [];
|
|
6164
6404
|
}
|
|
@@ -6265,10 +6505,11 @@ function createRealDeps(getClient2) {
|
|
|
6265
6505
|
},
|
|
6266
6506
|
countPendingReviews: async () => {
|
|
6267
6507
|
const client = getClient2();
|
|
6508
|
+
const coordinatorName = getCoordinatorName();
|
|
6268
6509
|
const rpScope = sessionScopeFilter(void 0, "r");
|
|
6269
6510
|
const result = await client.execute({
|
|
6270
6511
|
sql: `SELECT COUNT(*) as count FROM tasks r
|
|
6271
|
-
WHERE r.assigned_to = 'exe'
|
|
6512
|
+
WHERE (r.assigned_to = ? OR r.assigned_to = 'exe')
|
|
6272
6513
|
AND r.status IN ('open', 'in_progress')
|
|
6273
6514
|
AND r.title LIKE 'Review:%'${rpScope.sql}
|
|
6274
6515
|
AND NOT EXISTS (
|
|
@@ -6277,7 +6518,7 @@ function createRealDeps(getClient2) {
|
|
|
6277
6518
|
AND t.status = 'done'
|
|
6278
6519
|
AND t.updated_at > r.created_at
|
|
6279
6520
|
)`,
|
|
6280
|
-
args: [...rpScope.args]
|
|
6521
|
+
args: [coordinatorName, ...rpScope.args]
|
|
6281
6522
|
});
|
|
6282
6523
|
return Number(result.rows[0]?.count ?? 0);
|
|
6283
6524
|
},
|
|
@@ -6287,15 +6528,17 @@ function createRealDeps(getClient2) {
|
|
|
6287
6528
|
},
|
|
6288
6529
|
findOrphanedDoneTasks: async () => {
|
|
6289
6530
|
const client = getClient2();
|
|
6531
|
+
const coordinatorName = getCoordinatorName();
|
|
6290
6532
|
const odScope = sessionScopeFilter(void 0, "t");
|
|
6291
6533
|
const result = await client.execute({
|
|
6292
6534
|
sql: `SELECT t.id, t.title, t.assigned_to, t.assigned_by,
|
|
6293
6535
|
t.project_name, t.task_file, t.result, t.status
|
|
6294
6536
|
FROM tasks t
|
|
6295
6537
|
WHERE t.status = 'needs_review'
|
|
6538
|
+
AND t.assigned_to != ?
|
|
6296
6539
|
AND t.assigned_to != 'exe'${odScope.sql}
|
|
6297
6540
|
AND (t.result IS NULL OR t.result NOT LIKE '%## Review notes%')`,
|
|
6298
|
-
args: [...odScope.args]
|
|
6541
|
+
args: [coordinatorName, ...odScope.args]
|
|
6299
6542
|
});
|
|
6300
6543
|
return result.rows;
|
|
6301
6544
|
},
|
|
@@ -6327,18 +6570,20 @@ function createRealDeps(getClient2) {
|
|
|
6327
6570
|
},
|
|
6328
6571
|
findUnstartedTasks: async () => {
|
|
6329
6572
|
const client = getClient2();
|
|
6573
|
+
const coordinatorName = getCoordinatorName();
|
|
6330
6574
|
const usScope = sessionScopeFilter();
|
|
6331
6575
|
const result = await client.execute({
|
|
6332
6576
|
sql: `SELECT id, title, assigned_to, created_at
|
|
6333
6577
|
FROM tasks
|
|
6334
6578
|
WHERE status = 'open'
|
|
6579
|
+
AND assigned_to != ?
|
|
6335
6580
|
AND assigned_to != 'exe'
|
|
6336
6581
|
AND created_at <= datetime('now', '-5 minutes')${usScope.sql}
|
|
6337
6582
|
ORDER BY
|
|
6338
6583
|
CASE priority WHEN 'p0' THEN 0 WHEN 'p1' THEN 1 ELSE 2 END,
|
|
6339
6584
|
created_at ASC
|
|
6340
6585
|
LIMIT 20`,
|
|
6341
|
-
args: [...usScope.args]
|
|
6586
|
+
args: [coordinatorName, ...usScope.args]
|
|
6342
6587
|
});
|
|
6343
6588
|
return result.rows;
|
|
6344
6589
|
}
|
|
@@ -6348,6 +6593,8 @@ var init_review_polling = __esm({
|
|
|
6348
6593
|
"src/lib/review-polling.ts"() {
|
|
6349
6594
|
"use strict";
|
|
6350
6595
|
init_task_scope();
|
|
6596
|
+
init_tmux_routing();
|
|
6597
|
+
init_employees();
|
|
6351
6598
|
}
|
|
6352
6599
|
});
|
|
6353
6600
|
|
|
@@ -6600,8 +6847,8 @@ async function runConsolidation(client, options) {
|
|
|
6600
6847
|
if (clustersProcessed >= options.maxCalls) break;
|
|
6601
6848
|
if (cluster.memories.length < 3) continue;
|
|
6602
6849
|
try {
|
|
6603
|
-
const
|
|
6604
|
-
if (
|
|
6850
|
+
const isCoordinator = cluster.agentId === "exe" || isCoordinatorName(cluster.agentId);
|
|
6851
|
+
if (isCoordinator) {
|
|
6605
6852
|
const synthesis = await consolidateCluster(cluster, options.model);
|
|
6606
6853
|
if (!synthesis.trim()) continue;
|
|
6607
6854
|
const result = await storeConsolidation(client, cluster, synthesis, options.embedFn);
|
|
@@ -6630,7 +6877,7 @@ async function runConsolidation(client, options) {
|
|
|
6630
6877
|
if (dedupCount === 0) continue;
|
|
6631
6878
|
}
|
|
6632
6879
|
clustersProcessed++;
|
|
6633
|
-
memoriesConsolidated +=
|
|
6880
|
+
memoriesConsolidated += isCoordinator ? cluster.memories.length : 0;
|
|
6634
6881
|
} catch (err) {
|
|
6635
6882
|
process.stderr.write(
|
|
6636
6883
|
`[consolidation] Cluster failed (${cluster.projectName}/${cluster.dateRange}): ${err instanceof Error ? err.message : String(err)}
|
|
@@ -6717,6 +6964,7 @@ var init_consolidation = __esm({
|
|
|
6717
6964
|
"src/lib/consolidation.ts"() {
|
|
6718
6965
|
"use strict";
|
|
6719
6966
|
init_store();
|
|
6967
|
+
init_employees();
|
|
6720
6968
|
WIKI_FETCH_TIMEOUT_MS = 1e4;
|
|
6721
6969
|
}
|
|
6722
6970
|
});
|
|
@@ -6809,6 +7057,10 @@ function spawnDaemon() {
|
|
|
6809
7057
|
stdio: ["ignore", "ignore", stderrFd],
|
|
6810
7058
|
env: {
|
|
6811
7059
|
...process.env,
|
|
7060
|
+
TMUX: void 0,
|
|
7061
|
+
// Daemon is global — must not inherit session scope
|
|
7062
|
+
TMUX_PANE: void 0,
|
|
7063
|
+
// Prevents resolveExeSession() from scoping to one session
|
|
6812
7064
|
EXE_DAEMON_SOCK: SOCKET_PATH,
|
|
6813
7065
|
EXE_DAEMON_PID: PID_PATH
|
|
6814
7066
|
}
|
|
@@ -7313,6 +7565,7 @@ var init_code_chunker = __esm({
|
|
|
7313
7565
|
// src/lib/graph-rag.ts
|
|
7314
7566
|
var graph_rag_exports = {};
|
|
7315
7567
|
__export(graph_rag_exports, {
|
|
7568
|
+
EXTRACT_TOOL: () => EXTRACT_TOOL,
|
|
7316
7569
|
entityId: () => entityId,
|
|
7317
7570
|
extractBatch: () => extractBatch,
|
|
7318
7571
|
extractFromCode: () => extractFromCode,
|
|
@@ -7537,17 +7790,32 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
7537
7790
|
const id = aliasTarget ?? entityId(e.name, e.type);
|
|
7538
7791
|
const normalizedName = normalizeEntityName(e.name);
|
|
7539
7792
|
try {
|
|
7793
|
+
const newProps = e.properties ?? {};
|
|
7794
|
+
let mergedPropsJson = JSON.stringify(newProps);
|
|
7795
|
+
if (newProps.provenance) {
|
|
7796
|
+
const existing = await client.execute({
|
|
7797
|
+
sql: "SELECT properties FROM entities WHERE id = ?",
|
|
7798
|
+
args: [aliasTarget ?? id]
|
|
7799
|
+
});
|
|
7800
|
+
const existingProps = existing.rows.length > 0 ? JSON.parse(String(existing.rows[0].properties ?? "{}")) : {};
|
|
7801
|
+
const existingProvenance = Array.isArray(existingProps.provenance) ? existingProps.provenance : existingProps.provenance ? [existingProps.provenance] : [];
|
|
7802
|
+
mergedPropsJson = JSON.stringify({
|
|
7803
|
+
...existingProps,
|
|
7804
|
+
...newProps,
|
|
7805
|
+
provenance: [...existingProvenance, newProps.provenance]
|
|
7806
|
+
});
|
|
7807
|
+
}
|
|
7540
7808
|
if (aliasTarget) {
|
|
7541
7809
|
await client.execute({
|
|
7542
|
-
sql: `UPDATE entities SET last_seen = ? WHERE id = ?`,
|
|
7543
|
-
args: [timestamp, aliasTarget]
|
|
7810
|
+
sql: `UPDATE entities SET last_seen = ?, properties = ? WHERE id = ?`,
|
|
7811
|
+
args: [timestamp, mergedPropsJson, aliasTarget]
|
|
7544
7812
|
});
|
|
7545
7813
|
} else {
|
|
7546
7814
|
await client.execute({
|
|
7547
|
-
sql: `INSERT INTO entities (id, name, type, first_seen, last_seen)
|
|
7548
|
-
VALUES (?, ?, ?, ?, ?)
|
|
7549
|
-
ON CONFLICT(name, type) DO UPDATE SET last_seen = ?`,
|
|
7550
|
-
args: [id, normalizedName, e.type, timestamp, timestamp, timestamp]
|
|
7815
|
+
sql: `INSERT INTO entities (id, name, type, first_seen, last_seen, properties)
|
|
7816
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
7817
|
+
ON CONFLICT(name, type) DO UPDATE SET last_seen = ?, properties = ?`,
|
|
7818
|
+
args: [id, normalizedName, e.type, timestamp, timestamp, mergedPropsJson, timestamp, mergedPropsJson]
|
|
7551
7819
|
});
|
|
7552
7820
|
}
|
|
7553
7821
|
await client.execute({
|
|
@@ -7576,12 +7844,27 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
7576
7844
|
VALUES (?, ?, ?, ?, ?)`,
|
|
7577
7845
|
args: [targetId, r.target, r.targetType, timestamp, timestamp]
|
|
7578
7846
|
});
|
|
7847
|
+
const relProps = r.properties ?? {};
|
|
7848
|
+
let relPropsJson = JSON.stringify(relProps);
|
|
7849
|
+
if (relProps.provenance) {
|
|
7850
|
+
const existingRels = await client.execute({
|
|
7851
|
+
sql: "SELECT properties FROM relationships WHERE source_entity_id = ? AND target_entity_id = ? AND type = ?",
|
|
7852
|
+
args: [sourceId, targetId, r.relationship]
|
|
7853
|
+
});
|
|
7854
|
+
const existingRelProps = existingRels.rows.length > 0 ? JSON.parse(String(existingRels.rows[0].properties ?? "{}")) : {};
|
|
7855
|
+
const existingProvenance = Array.isArray(existingRelProps.provenance) ? existingRelProps.provenance : existingRelProps.provenance ? [existingRelProps.provenance] : [];
|
|
7856
|
+
relPropsJson = JSON.stringify({
|
|
7857
|
+
...existingRelProps,
|
|
7858
|
+
...relProps,
|
|
7859
|
+
provenance: [...existingProvenance, relProps.provenance]
|
|
7860
|
+
});
|
|
7861
|
+
}
|
|
7579
7862
|
await client.execute({
|
|
7580
|
-
sql: `INSERT INTO relationships (id, source_entity_id, target_entity_id, type, weight, timestamp, confidence, confidence_label)
|
|
7581
|
-
VALUES (?, ?, ?, ?, 1.0, ?, ?, ?)
|
|
7863
|
+
sql: `INSERT INTO relationships (id, source_entity_id, target_entity_id, type, weight, timestamp, confidence, confidence_label, properties)
|
|
7864
|
+
VALUES (?, ?, ?, ?, 1.0, ?, ?, ?, ?)
|
|
7582
7865
|
ON CONFLICT(source_entity_id, target_entity_id, type)
|
|
7583
7866
|
DO UPDATE SET weight = MIN(weight + 0.1, 2.0), timestamp = ?,
|
|
7584
|
-
confidence = MAX(confidence, ?), confidence_label = ?`,
|
|
7867
|
+
confidence = MAX(confidence, ?), confidence_label = ?, properties = ?`,
|
|
7585
7868
|
args: [
|
|
7586
7869
|
relId,
|
|
7587
7870
|
sourceId,
|
|
@@ -7590,9 +7873,11 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
7590
7873
|
timestamp,
|
|
7591
7874
|
r.confidence,
|
|
7592
7875
|
r.confidenceLabel,
|
|
7876
|
+
relPropsJson,
|
|
7593
7877
|
timestamp,
|
|
7594
7878
|
r.confidence,
|
|
7595
|
-
r.confidenceLabel
|
|
7879
|
+
r.confidenceLabel,
|
|
7880
|
+
relPropsJson
|
|
7596
7881
|
]
|
|
7597
7882
|
});
|
|
7598
7883
|
const existingRel = await client.execute({
|
|
@@ -8150,16 +8435,25 @@ function createWsClient(config, handlers) {
|
|
|
8150
8435
|
} catch {
|
|
8151
8436
|
}
|
|
8152
8437
|
};
|
|
8153
|
-
ws.onclose = () => {
|
|
8438
|
+
ws.onclose = (event) => {
|
|
8154
8439
|
connected = false;
|
|
8155
8440
|
if (heartbeatTimer) {
|
|
8156
8441
|
clearInterval(heartbeatTimer);
|
|
8157
8442
|
heartbeatTimer = null;
|
|
8158
8443
|
}
|
|
8444
|
+
const code = event.code ?? "unknown";
|
|
8445
|
+
const reason = event.reason || "(none)";
|
|
8446
|
+
process.stderr.write(
|
|
8447
|
+
`[ws-client] Disconnected: code=${code} reason=${reason}
|
|
8448
|
+
`
|
|
8449
|
+
);
|
|
8159
8450
|
handlers.onDisconnect();
|
|
8160
8451
|
scheduleReconnect();
|
|
8161
8452
|
};
|
|
8162
|
-
ws.onerror = () => {
|
|
8453
|
+
ws.onerror = (event) => {
|
|
8454
|
+
const errMsg = event.message ?? "unknown error";
|
|
8455
|
+
process.stderr.write(`[ws-client] Error: ${errMsg}
|
|
8456
|
+
`);
|
|
8163
8457
|
};
|
|
8164
8458
|
} catch {
|
|
8165
8459
|
connected = false;
|
|
@@ -8176,11 +8470,12 @@ function createWsClient(config, handlers) {
|
|
|
8176
8470
|
}
|
|
8177
8471
|
function scheduleReconnect() {
|
|
8178
8472
|
if (closed || reconnectTimer) return;
|
|
8473
|
+
const jitter = reconnectDelay * (0.75 + Math.random() * 0.5);
|
|
8179
8474
|
reconnectTimer = setTimeout(() => {
|
|
8180
8475
|
reconnectTimer = null;
|
|
8181
8476
|
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_MS);
|
|
8182
8477
|
connect();
|
|
8183
|
-
},
|
|
8478
|
+
}, jitter);
|
|
8184
8479
|
}
|
|
8185
8480
|
connect();
|
|
8186
8481
|
return {
|
|
@@ -8362,7 +8657,7 @@ async function deliverLocalMessage(messageId) {
|
|
|
8362
8657
|
try {
|
|
8363
8658
|
const exeSession = resolveExeSession();
|
|
8364
8659
|
if (!exeSession) {
|
|
8365
|
-
throw new Error("No
|
|
8660
|
+
throw new Error("No coordinator session found");
|
|
8366
8661
|
}
|
|
8367
8662
|
const sessionName = employeeSessionName(targetAgent, exeSession);
|
|
8368
8663
|
if (!isEmployeeAlive(sessionName)) {
|
|
@@ -9017,6 +9312,49 @@ function startWikiSync() {
|
|
|
9017
9312
|
}).catch(() => {
|
|
9018
9313
|
});
|
|
9019
9314
|
}
|
|
9315
|
+
var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
|
|
9316
|
+
var AGENT_STATS_PATH = path20.join(EXE_AI_DIR, "agent-stats.json");
|
|
9317
|
+
async function writeAgentStats() {
|
|
9318
|
+
if (!await ensureStoreForPolling()) return;
|
|
9319
|
+
try {
|
|
9320
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
9321
|
+
const client = getClient2();
|
|
9322
|
+
const result = await client.execute({
|
|
9323
|
+
sql: `SELECT agent_id,
|
|
9324
|
+
COUNT(*) as total,
|
|
9325
|
+
SUM(CASE WHEN timestamp > datetime('now', '-7 days') THEN 1 ELSE 0 END) as growth_7d
|
|
9326
|
+
FROM memories
|
|
9327
|
+
WHERE agent_id != 'default'
|
|
9328
|
+
GROUP BY agent_id
|
|
9329
|
+
ORDER BY total DESC`,
|
|
9330
|
+
args: []
|
|
9331
|
+
});
|
|
9332
|
+
const agents = result.rows.map((row) => ({
|
|
9333
|
+
id: row.agent_id,
|
|
9334
|
+
total: Number(row.total),
|
|
9335
|
+
growth7d: Number(row.growth_7d)
|
|
9336
|
+
}));
|
|
9337
|
+
const stats = {
|
|
9338
|
+
generated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9339
|
+
agents,
|
|
9340
|
+
daemon: {
|
|
9341
|
+
uptime: Math.floor((Date.now() - _startedAt) / 1e3),
|
|
9342
|
+
pid: process.pid
|
|
9343
|
+
}
|
|
9344
|
+
};
|
|
9345
|
+
writeFileSync9(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
|
|
9346
|
+
} catch (err) {
|
|
9347
|
+
process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
|
|
9348
|
+
`);
|
|
9349
|
+
}
|
|
9350
|
+
}
|
|
9351
|
+
function startAgentStats() {
|
|
9352
|
+
void writeAgentStats();
|
|
9353
|
+
const timer = setInterval(() => void writeAgentStats(), AGENT_STATS_INTERVAL_MS);
|
|
9354
|
+
timer.unref();
|
|
9355
|
+
process.stderr.write(`[exed] Agent stats started (every ${AGENT_STATS_INTERVAL_MS / 1e3}s)
|
|
9356
|
+
`);
|
|
9357
|
+
}
|
|
9020
9358
|
function startCapacityMonitoring() {
|
|
9021
9359
|
const tick = async () => {
|
|
9022
9360
|
if (!await ensureStoreForPolling()) return;
|
|
@@ -9099,6 +9437,28 @@ function startIntercomQueueDrain() {
|
|
|
9099
9437
|
process.stderr.write(`[exed] Intercom queue drain started (every ${QUEUE_DRAIN_INTERVAL_MS / 1e3}s)
|
|
9100
9438
|
`);
|
|
9101
9439
|
}
|
|
9440
|
+
var ORPHAN_REAP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
9441
|
+
function startOrphanReaper() {
|
|
9442
|
+
const tick = async () => {
|
|
9443
|
+
try {
|
|
9444
|
+
const { reapOrphanedMcpProcesses: reapOrphanedMcpProcesses2, createOrphanReaperRealDeps: createOrphanReaperRealDeps2 } = await Promise.resolve().then(() => (init_daemon_orchestration(), daemon_orchestration_exports));
|
|
9445
|
+
const deps = createOrphanReaperRealDeps2();
|
|
9446
|
+
const reaped = await reapOrphanedMcpProcesses2(deps);
|
|
9447
|
+
for (const entry of reaped) {
|
|
9448
|
+
process.stderr.write(`[exed] Orphan reaper: killed ${entry}
|
|
9449
|
+
`);
|
|
9450
|
+
}
|
|
9451
|
+
} catch (err) {
|
|
9452
|
+
process.stderr.write(`[exed] Orphan reaper error: ${err instanceof Error ? err.message : String(err)}
|
|
9453
|
+
`);
|
|
9454
|
+
}
|
|
9455
|
+
};
|
|
9456
|
+
void tick();
|
|
9457
|
+
const timer = setInterval(() => void tick(), ORPHAN_REAP_INTERVAL_MS);
|
|
9458
|
+
timer.unref();
|
|
9459
|
+
process.stderr.write(`[exed] Orphan reaper started (every ${ORPHAN_REAP_INTERVAL_MS / 6e4}m)
|
|
9460
|
+
`);
|
|
9461
|
+
}
|
|
9102
9462
|
process.on("SIGINT", () => void shutdown());
|
|
9103
9463
|
process.on("SIGTERM", () => void shutdown());
|
|
9104
9464
|
function checkExistingDaemon() {
|
|
@@ -9182,6 +9542,8 @@ try {
|
|
|
9182
9542
|
startIdleKill();
|
|
9183
9543
|
startConsolidation();
|
|
9184
9544
|
startSkillSweep();
|
|
9545
|
+
startOrphanReaper();
|
|
9546
|
+
startAgentStats();
|
|
9185
9547
|
startCapacityMonitoring();
|
|
9186
9548
|
startGraphExtraction();
|
|
9187
9549
|
startWikiSync();
|