@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
|
@@ -165,13 +165,7 @@ var init_config = __esm({
|
|
|
165
165
|
wikiUrl: "",
|
|
166
166
|
wikiApiKey: "",
|
|
167
167
|
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
168
|
-
wikiWorkspaceMapping: {
|
|
169
|
-
exe: "Executive",
|
|
170
|
-
yoshi: "Engineering",
|
|
171
|
-
mari: "Marketing",
|
|
172
|
-
tom: "Engineering",
|
|
173
|
-
sasha: "Production"
|
|
174
|
-
},
|
|
168
|
+
wikiWorkspaceMapping: {},
|
|
175
169
|
wikiAutoUpdate: true,
|
|
176
170
|
wikiAutoUpdateThreshold: 0.5,
|
|
177
171
|
wikiAutoUpdateCreateNew: true,
|
|
@@ -243,13 +237,64 @@ var init_session_key = __esm({
|
|
|
243
237
|
}
|
|
244
238
|
});
|
|
245
239
|
|
|
246
|
-
// src/lib/
|
|
247
|
-
import {
|
|
248
|
-
import
|
|
240
|
+
// src/lib/employees.ts
|
|
241
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
242
|
+
import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
|
|
243
|
+
import { execSync as execSync2 } from "child_process";
|
|
244
|
+
import path2 from "path";
|
|
249
245
|
import os2 from "os";
|
|
246
|
+
function normalizeRole(role) {
|
|
247
|
+
return (role ?? "").trim().toLowerCase();
|
|
248
|
+
}
|
|
249
|
+
function isCoordinatorRole(role) {
|
|
250
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
251
|
+
}
|
|
252
|
+
function getCoordinatorEmployee(employees) {
|
|
253
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
254
|
+
}
|
|
255
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
256
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
257
|
+
}
|
|
258
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
259
|
+
if (!agentName) return false;
|
|
260
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
261
|
+
}
|
|
262
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
263
|
+
if (!existsSync2(employeesPath)) return [];
|
|
264
|
+
try {
|
|
265
|
+
return JSON.parse(readFileSync2(employeesPath, "utf-8"));
|
|
266
|
+
} catch {
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function getEmployee(employees, name) {
|
|
271
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
272
|
+
}
|
|
273
|
+
function isMultiInstance(agentName, employees) {
|
|
274
|
+
const roster = employees ?? loadEmployeesSync();
|
|
275
|
+
const emp = getEmployee(roster, agentName);
|
|
276
|
+
if (!emp) return false;
|
|
277
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
278
|
+
}
|
|
279
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
280
|
+
var init_employees = __esm({
|
|
281
|
+
"src/lib/employees.ts"() {
|
|
282
|
+
"use strict";
|
|
283
|
+
init_config();
|
|
284
|
+
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
285
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
286
|
+
COORDINATOR_ROLE = "COO";
|
|
287
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// src/lib/session-registry.ts
|
|
292
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
|
|
293
|
+
import path4 from "path";
|
|
294
|
+
import os3 from "os";
|
|
250
295
|
function registerSession(entry) {
|
|
251
|
-
const dir =
|
|
252
|
-
if (!
|
|
296
|
+
const dir = path4.dirname(REGISTRY_PATH);
|
|
297
|
+
if (!existsSync3(dir)) {
|
|
253
298
|
mkdirSync2(dir, { recursive: true });
|
|
254
299
|
}
|
|
255
300
|
const sessions = listSessions();
|
|
@@ -259,11 +304,11 @@ function registerSession(entry) {
|
|
|
259
304
|
} else {
|
|
260
305
|
sessions.push(entry);
|
|
261
306
|
}
|
|
262
|
-
|
|
307
|
+
writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
|
|
263
308
|
}
|
|
264
309
|
function listSessions() {
|
|
265
310
|
try {
|
|
266
|
-
const raw =
|
|
311
|
+
const raw = readFileSync4(REGISTRY_PATH, "utf8");
|
|
267
312
|
return JSON.parse(raw);
|
|
268
313
|
} catch {
|
|
269
314
|
return [];
|
|
@@ -273,7 +318,7 @@ var REGISTRY_PATH;
|
|
|
273
318
|
var init_session_registry = __esm({
|
|
274
319
|
"src/lib/session-registry.ts"() {
|
|
275
320
|
"use strict";
|
|
276
|
-
REGISTRY_PATH =
|
|
321
|
+
REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
|
|
277
322
|
}
|
|
278
323
|
});
|
|
279
324
|
|
|
@@ -385,14 +430,14 @@ var init_transport = __esm({
|
|
|
385
430
|
});
|
|
386
431
|
|
|
387
432
|
// src/lib/cc-agent-support.ts
|
|
388
|
-
import { execSync as
|
|
433
|
+
import { execSync as execSync4 } from "child_process";
|
|
389
434
|
function _resetCcAgentSupportCache() {
|
|
390
435
|
_cachedSupport = null;
|
|
391
436
|
}
|
|
392
437
|
function claudeSupportsAgentFlag() {
|
|
393
438
|
if (_cachedSupport !== null) return _cachedSupport;
|
|
394
439
|
try {
|
|
395
|
-
const helpOutput =
|
|
440
|
+
const helpOutput = execSync4("claude --help 2>&1", {
|
|
396
441
|
encoding: "utf-8",
|
|
397
442
|
timeout: 5e3
|
|
398
443
|
});
|
|
@@ -458,17 +503,17 @@ var init_provider_table = __esm({
|
|
|
458
503
|
});
|
|
459
504
|
|
|
460
505
|
// src/lib/intercom-queue.ts
|
|
461
|
-
import { readFileSync as
|
|
462
|
-
import
|
|
463
|
-
import
|
|
506
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
507
|
+
import path5 from "path";
|
|
508
|
+
import os4 from "os";
|
|
464
509
|
function ensureDir() {
|
|
465
|
-
const dir =
|
|
466
|
-
if (!
|
|
510
|
+
const dir = path5.dirname(QUEUE_PATH);
|
|
511
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
467
512
|
}
|
|
468
513
|
function readQueue() {
|
|
469
514
|
try {
|
|
470
|
-
if (!
|
|
471
|
-
return JSON.parse(
|
|
515
|
+
if (!existsSync4(QUEUE_PATH)) return [];
|
|
516
|
+
return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
|
|
472
517
|
} catch {
|
|
473
518
|
return [];
|
|
474
519
|
}
|
|
@@ -476,8 +521,8 @@ function readQueue() {
|
|
|
476
521
|
function writeQueue(queue) {
|
|
477
522
|
ensureDir();
|
|
478
523
|
const tmp = `${QUEUE_PATH}.tmp`;
|
|
479
|
-
|
|
480
|
-
|
|
524
|
+
writeFileSync4(tmp, JSON.stringify(queue, null, 2));
|
|
525
|
+
renameSync3(tmp, QUEUE_PATH);
|
|
481
526
|
}
|
|
482
527
|
function queueIntercom(targetSession, reason) {
|
|
483
528
|
const queue = readQueue();
|
|
@@ -500,9 +545,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
|
500
545
|
var init_intercom_queue = __esm({
|
|
501
546
|
"src/lib/intercom-queue.ts"() {
|
|
502
547
|
"use strict";
|
|
503
|
-
QUEUE_PATH =
|
|
548
|
+
QUEUE_PATH = path5.join(os4.homedir(), ".exe-os", "intercom-queue.json");
|
|
504
549
|
TTL_MS = 60 * 60 * 1e3;
|
|
505
|
-
INTERCOM_LOG =
|
|
550
|
+
INTERCOM_LOG = path5.join(os4.homedir(), ".exe-os", "intercom.log");
|
|
506
551
|
}
|
|
507
552
|
});
|
|
508
553
|
|
|
@@ -545,7 +590,7 @@ function wrapWithRetry(client) {
|
|
|
545
590
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
546
591
|
}
|
|
547
592
|
if (prop === "batch") {
|
|
548
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
593
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
549
594
|
}
|
|
550
595
|
return Reflect.get(target, prop, receiver);
|
|
551
596
|
}
|
|
@@ -708,22 +753,24 @@ async function ensureSchema() {
|
|
|
708
753
|
ON behaviors(agent_id, active);
|
|
709
754
|
`);
|
|
710
755
|
try {
|
|
756
|
+
const coordinatorName = getCoordinatorName();
|
|
711
757
|
const existing = await client.execute({
|
|
712
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
713
|
-
args: []
|
|
758
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
759
|
+
args: [coordinatorName]
|
|
714
760
|
});
|
|
715
761
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
762
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
763
|
+
for (const [domain, content] of [
|
|
764
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
765
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
766
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
767
|
+
]) {
|
|
768
|
+
await client.execute({
|
|
769
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
770
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
771
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
772
|
+
});
|
|
773
|
+
}
|
|
727
774
|
}
|
|
728
775
|
} catch {
|
|
729
776
|
}
|
|
@@ -1415,6 +1462,39 @@ async function ensureSchema() {
|
|
|
1415
1462
|
} catch {
|
|
1416
1463
|
}
|
|
1417
1464
|
}
|
|
1465
|
+
try {
|
|
1466
|
+
await client.execute({
|
|
1467
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1468
|
+
args: []
|
|
1469
|
+
});
|
|
1470
|
+
} catch {
|
|
1471
|
+
}
|
|
1472
|
+
try {
|
|
1473
|
+
await client.execute(
|
|
1474
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1475
|
+
);
|
|
1476
|
+
} catch {
|
|
1477
|
+
}
|
|
1478
|
+
try {
|
|
1479
|
+
await client.execute({
|
|
1480
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1481
|
+
args: []
|
|
1482
|
+
});
|
|
1483
|
+
} catch {
|
|
1484
|
+
}
|
|
1485
|
+
try {
|
|
1486
|
+
await client.execute(
|
|
1487
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1488
|
+
);
|
|
1489
|
+
} catch {
|
|
1490
|
+
}
|
|
1491
|
+
try {
|
|
1492
|
+
await client.execute({
|
|
1493
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1494
|
+
args: []
|
|
1495
|
+
});
|
|
1496
|
+
} catch {
|
|
1497
|
+
}
|
|
1418
1498
|
}
|
|
1419
1499
|
async function disposeDatabase() {
|
|
1420
1500
|
if (_client) {
|
|
@@ -1428,6 +1508,7 @@ var init_database = __esm({
|
|
|
1428
1508
|
"src/lib/database.ts"() {
|
|
1429
1509
|
"use strict";
|
|
1430
1510
|
init_db_retry();
|
|
1511
|
+
init_employees();
|
|
1431
1512
|
_client = null;
|
|
1432
1513
|
_resilientClient = null;
|
|
1433
1514
|
initTurso = initDatabase;
|
|
@@ -1435,39 +1516,6 @@ var init_database = __esm({
|
|
|
1435
1516
|
}
|
|
1436
1517
|
});
|
|
1437
1518
|
|
|
1438
|
-
// src/lib/employees.ts
|
|
1439
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1440
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
1441
|
-
import { execSync as execSync4 } from "child_process";
|
|
1442
|
-
import path5 from "path";
|
|
1443
|
-
import os4 from "os";
|
|
1444
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1445
|
-
if (!existsSync4(employeesPath)) return [];
|
|
1446
|
-
try {
|
|
1447
|
-
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
1448
|
-
} catch {
|
|
1449
|
-
return [];
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
function getEmployee(employees, name) {
|
|
1453
|
-
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
1454
|
-
}
|
|
1455
|
-
function isMultiInstance(agentName, employees) {
|
|
1456
|
-
const roster = employees ?? loadEmployeesSync();
|
|
1457
|
-
const emp = getEmployee(roster, agentName);
|
|
1458
|
-
if (!emp) return false;
|
|
1459
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1460
|
-
}
|
|
1461
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
1462
|
-
var init_employees = __esm({
|
|
1463
|
-
"src/lib/employees.ts"() {
|
|
1464
|
-
"use strict";
|
|
1465
|
-
init_config();
|
|
1466
|
-
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
1467
|
-
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1468
|
-
}
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
1519
|
// src/lib/license.ts
|
|
1472
1520
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
|
|
1473
1521
|
import { randomUUID } from "crypto";
|
|
@@ -1955,6 +2003,36 @@ async function listTasks(input2) {
|
|
|
1955
2003
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
1956
2004
|
}));
|
|
1957
2005
|
}
|
|
2006
|
+
function isTmuxSessionAlive(identifier) {
|
|
2007
|
+
if (!identifier || identifier === "unknown") return true;
|
|
2008
|
+
try {
|
|
2009
|
+
if (identifier.startsWith("%")) {
|
|
2010
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
2011
|
+
timeout: 2e3,
|
|
2012
|
+
encoding: "utf8",
|
|
2013
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2014
|
+
});
|
|
2015
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2016
|
+
} else {
|
|
2017
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2018
|
+
timeout: 2e3,
|
|
2019
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2020
|
+
});
|
|
2021
|
+
return true;
|
|
2022
|
+
}
|
|
2023
|
+
} catch {
|
|
2024
|
+
if (identifier.startsWith("%")) return true;
|
|
2025
|
+
try {
|
|
2026
|
+
execSync5("tmux list-sessions", {
|
|
2027
|
+
timeout: 2e3,
|
|
2028
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2029
|
+
});
|
|
2030
|
+
return false;
|
|
2031
|
+
} catch {
|
|
2032
|
+
return true;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
1958
2036
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
1959
2037
|
if (!taskContext) return null;
|
|
1960
2038
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -2017,13 +2095,59 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2017
2095
|
});
|
|
2018
2096
|
if (claim.rowsAffected === 0) {
|
|
2019
2097
|
const current = await client.execute({
|
|
2020
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
2098
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
2021
2099
|
args: [taskId]
|
|
2022
2100
|
});
|
|
2023
2101
|
const cur = current.rows[0];
|
|
2024
|
-
const
|
|
2025
|
-
const
|
|
2026
|
-
|
|
2102
|
+
const curStatus = cur?.status ?? "unknown";
|
|
2103
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
2104
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
2105
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
2106
|
+
process.stderr.write(
|
|
2107
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
2108
|
+
`
|
|
2109
|
+
);
|
|
2110
|
+
await client.execute({
|
|
2111
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
2112
|
+
args: [now, taskId]
|
|
2113
|
+
});
|
|
2114
|
+
const retried = await client.execute({
|
|
2115
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
2116
|
+
args: [tmuxSession, now, taskId]
|
|
2117
|
+
});
|
|
2118
|
+
if (retried.rowsAffected > 0) {
|
|
2119
|
+
try {
|
|
2120
|
+
await writeCheckpoint({
|
|
2121
|
+
taskId,
|
|
2122
|
+
step: "reclaimed_dead_session",
|
|
2123
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
2124
|
+
});
|
|
2125
|
+
} catch {
|
|
2126
|
+
}
|
|
2127
|
+
return { row, taskFile, now, taskId };
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || input2.callerAgentId === "exe")) {
|
|
2131
|
+
process.stderr.write(
|
|
2132
|
+
`[tasks] Assigner override: ${input2.callerAgentId} reclaiming ${taskId}
|
|
2133
|
+
`
|
|
2134
|
+
);
|
|
2135
|
+
await client.execute({
|
|
2136
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
2137
|
+
args: [tmuxSession, now, taskId]
|
|
2138
|
+
});
|
|
2139
|
+
try {
|
|
2140
|
+
await writeCheckpoint({
|
|
2141
|
+
taskId,
|
|
2142
|
+
step: "assigner_override",
|
|
2143
|
+
contextSummary: `Task force-reclaimed by assigner ${input2.callerAgentId}.`
|
|
2144
|
+
});
|
|
2145
|
+
} catch {
|
|
2146
|
+
}
|
|
2147
|
+
return { row, taskFile, now, taskId };
|
|
2148
|
+
}
|
|
2149
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
2150
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
2027
2151
|
}
|
|
2028
2152
|
try {
|
|
2029
2153
|
await writeCheckpoint({
|
|
@@ -2121,7 +2245,7 @@ var init_tasks_crud = __esm({
|
|
|
2121
2245
|
"use strict";
|
|
2122
2246
|
init_database();
|
|
2123
2247
|
init_task_scope();
|
|
2124
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
2248
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2125
2249
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2126
2250
|
}
|
|
2127
2251
|
});
|
|
@@ -2478,7 +2602,7 @@ function findSessionForProject(projectName) {
|
|
|
2478
2602
|
const sessions = listSessions();
|
|
2479
2603
|
for (const s of sessions) {
|
|
2480
2604
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2481
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
2605
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
2482
2606
|
}
|
|
2483
2607
|
return null;
|
|
2484
2608
|
}
|
|
@@ -2518,12 +2642,13 @@ var init_session_scope = __esm({
|
|
|
2518
2642
|
init_session_registry();
|
|
2519
2643
|
init_project_name();
|
|
2520
2644
|
init_tmux_routing();
|
|
2645
|
+
init_employees();
|
|
2521
2646
|
}
|
|
2522
2647
|
});
|
|
2523
2648
|
|
|
2524
2649
|
// src/lib/tasks-notify.ts
|
|
2525
2650
|
async function dispatchTaskToEmployee(input2) {
|
|
2526
|
-
if (input2.assignedTo === "exe") return { dispatched: "skipped" };
|
|
2651
|
+
if (input2.assignedTo === "exe" || isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
|
|
2527
2652
|
let crossProject = false;
|
|
2528
2653
|
if (input2.projectName) {
|
|
2529
2654
|
try {
|
|
@@ -2966,6 +3091,24 @@ async function updateTask(input2) {
|
|
|
2966
3091
|
});
|
|
2967
3092
|
} catch {
|
|
2968
3093
|
}
|
|
3094
|
+
const assignedAgent = String(row.assigned_to);
|
|
3095
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
3096
|
+
try {
|
|
3097
|
+
const draftClient = getClient();
|
|
3098
|
+
if (input2.status === "done") {
|
|
3099
|
+
await draftClient.execute({
|
|
3100
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3101
|
+
args: [assignedAgent]
|
|
3102
|
+
});
|
|
3103
|
+
} else if (input2.status === "cancelled") {
|
|
3104
|
+
await draftClient.execute({
|
|
3105
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
3106
|
+
args: [assignedAgent]
|
|
3107
|
+
});
|
|
3108
|
+
}
|
|
3109
|
+
} catch {
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
2969
3112
|
try {
|
|
2970
3113
|
const client = getClient();
|
|
2971
3114
|
const cascaded = await client.execute({
|
|
@@ -2984,8 +3127,8 @@ async function updateTask(input2) {
|
|
|
2984
3127
|
}
|
|
2985
3128
|
const isTerminal = input2.status === "done" || input2.status === "needs_review";
|
|
2986
3129
|
if (isTerminal) {
|
|
2987
|
-
const
|
|
2988
|
-
if (!
|
|
3130
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
3131
|
+
if (!isCoordinator) {
|
|
2989
3132
|
notifyTaskDone();
|
|
2990
3133
|
}
|
|
2991
3134
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -3009,7 +3152,7 @@ async function updateTask(input2) {
|
|
|
3009
3152
|
}
|
|
3010
3153
|
}
|
|
3011
3154
|
}
|
|
3012
|
-
if (input2.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
3155
|
+
if (input2.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3013
3156
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3014
3157
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3015
3158
|
taskId,
|
|
@@ -3025,7 +3168,7 @@ async function updateTask(input2) {
|
|
|
3025
3168
|
});
|
|
3026
3169
|
}
|
|
3027
3170
|
let nextTask;
|
|
3028
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
3171
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
3029
3172
|
try {
|
|
3030
3173
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3031
3174
|
} catch {
|
|
@@ -3052,12 +3195,14 @@ async function updateTask(input2) {
|
|
|
3052
3195
|
async function deleteTask(taskId, baseDir) {
|
|
3053
3196
|
const client = getClient();
|
|
3054
3197
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
3055
|
-
const
|
|
3198
|
+
const coordinatorName = getCoordinatorName();
|
|
3199
|
+
const reviewer = assignedBy || coordinatorName;
|
|
3056
3200
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
3057
3201
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
3202
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
3058
3203
|
await client.execute({
|
|
3059
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
3060
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3204
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
3205
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3061
3206
|
});
|
|
3062
3207
|
await markAsReadByTaskFile(taskFile);
|
|
3063
3208
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -3069,6 +3214,7 @@ var init_tasks = __esm({
|
|
|
3069
3214
|
init_config();
|
|
3070
3215
|
init_notifications();
|
|
3071
3216
|
init_state_bus();
|
|
3217
|
+
init_employees();
|
|
3072
3218
|
init_tasks_crud();
|
|
3073
3219
|
init_tasks_review();
|
|
3074
3220
|
init_tasks_crud();
|
|
@@ -3154,7 +3300,7 @@ function _resetLastRelaunchCache() {
|
|
|
3154
3300
|
}
|
|
3155
3301
|
async function lastResumeCreatedAtMs(agentId) {
|
|
3156
3302
|
const client = getClient();
|
|
3157
|
-
const cmScope = sessionScopeFilter();
|
|
3303
|
+
const cmScope = sessionScopeFilter(null);
|
|
3158
3304
|
const result = await client.execute({
|
|
3159
3305
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
3160
3306
|
FROM tasks
|
|
@@ -3179,7 +3325,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
3179
3325
|
const client = getClient();
|
|
3180
3326
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3181
3327
|
const context = buildResumeContext(agentId, openTasks);
|
|
3182
|
-
const rdScope = sessionScopeFilter();
|
|
3328
|
+
const rdScope = sessionScopeFilter(null);
|
|
3183
3329
|
const existing = await client.execute({
|
|
3184
3330
|
sql: `SELECT id FROM tasks
|
|
3185
3331
|
WHERE assigned_to = ?
|
|
@@ -3213,7 +3359,7 @@ async function pollCapacityDead() {
|
|
|
3213
3359
|
const transport = getTransport();
|
|
3214
3360
|
const relaunched = [];
|
|
3215
3361
|
const registered = listSessions().filter(
|
|
3216
|
-
(s) => s.agentId !== "exe"
|
|
3362
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
3217
3363
|
);
|
|
3218
3364
|
if (registered.length === 0) return [];
|
|
3219
3365
|
let liveSessions;
|
|
@@ -3273,7 +3419,7 @@ async function pollCapacityDead() {
|
|
|
3273
3419
|
reason: "capacity"
|
|
3274
3420
|
});
|
|
3275
3421
|
const client = getClient();
|
|
3276
|
-
const rlScope = sessionScopeFilter();
|
|
3422
|
+
const rlScope = sessionScopeFilter(null);
|
|
3277
3423
|
const openTasks = await client.execute({
|
|
3278
3424
|
sql: `SELECT id, title, priority, task_file, status
|
|
3279
3425
|
FROM tasks
|
|
@@ -3327,6 +3473,7 @@ var init_capacity_monitor = __esm({
|
|
|
3327
3473
|
init_session_kill_telemetry();
|
|
3328
3474
|
init_tmux_routing();
|
|
3329
3475
|
init_task_scope();
|
|
3476
|
+
init_employees();
|
|
3330
3477
|
CAPACITY_PATTERNS = [
|
|
3331
3478
|
/conversation is too long/i,
|
|
3332
3479
|
/maximum context length/i,
|
|
@@ -3476,7 +3623,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
3476
3623
|
exeSession = root;
|
|
3477
3624
|
} else {
|
|
3478
3625
|
throw new Error(
|
|
3479
|
-
`Invalid
|
|
3626
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3480
3627
|
);
|
|
3481
3628
|
}
|
|
3482
3629
|
}
|
|
@@ -3496,8 +3643,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
3496
3643
|
return match?.[1] ?? null;
|
|
3497
3644
|
}
|
|
3498
3645
|
function extractRootExe(name) {
|
|
3499
|
-
|
|
3500
|
-
|
|
3646
|
+
if (!name) return null;
|
|
3647
|
+
if (!name.includes("-")) return name;
|
|
3648
|
+
const parts = name.split("-").filter(Boolean);
|
|
3649
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3501
3650
|
}
|
|
3502
3651
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3503
3652
|
if (!existsSync10(SESSION_CACHE)) {
|
|
@@ -3642,12 +3791,14 @@ function isSessionBusy(sessionName) {
|
|
|
3642
3791
|
return state === "thinking" || state === "tool";
|
|
3643
3792
|
}
|
|
3644
3793
|
function isExeSession(sessionName) {
|
|
3645
|
-
|
|
3794
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
3795
|
+
const coordinatorName = getCoordinatorName();
|
|
3796
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
3646
3797
|
}
|
|
3647
3798
|
function sendIntercom(targetSession) {
|
|
3648
3799
|
const transport = getTransport();
|
|
3649
3800
|
if (isExeSession(targetSession)) {
|
|
3650
|
-
logIntercom(`
|
|
3801
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
3651
3802
|
return "skipped_exe";
|
|
3652
3803
|
}
|
|
3653
3804
|
if (isDebounced(targetSession)) {
|
|
@@ -3699,7 +3850,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3699
3850
|
if (result === "failed") {
|
|
3700
3851
|
const rootExe = resolveExeSession();
|
|
3701
3852
|
if (rootExe && rootExe !== target) {
|
|
3702
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
3853
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
3703
3854
|
`);
|
|
3704
3855
|
const fallback = sendIntercom(rootExe);
|
|
3705
3856
|
return fallback !== "failed";
|
|
@@ -3709,8 +3860,8 @@ function notifyParentExe(sessionKey) {
|
|
|
3709
3860
|
return true;
|
|
3710
3861
|
}
|
|
3711
3862
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3712
|
-
if (employeeName === "exe") {
|
|
3713
|
-
return { status: "failed", sessionName: "", error: "
|
|
3863
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
3864
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3714
3865
|
}
|
|
3715
3866
|
try {
|
|
3716
3867
|
assertEmployeeLimitSync();
|
|
@@ -3719,8 +3870,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3719
3870
|
return { status: "failed", sessionName: "", error: err.message };
|
|
3720
3871
|
}
|
|
3721
3872
|
}
|
|
3722
|
-
if (
|
|
3723
|
-
const bare = employeeName.
|
|
3873
|
+
if (employeeName.includes("-")) {
|
|
3874
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
3724
3875
|
return {
|
|
3725
3876
|
status: "failed",
|
|
3726
3877
|
sessionName: "",
|
|
@@ -3739,7 +3890,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3739
3890
|
return {
|
|
3740
3891
|
status: "failed",
|
|
3741
3892
|
sessionName: "",
|
|
3742
|
-
error: `Invalid
|
|
3893
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3743
3894
|
};
|
|
3744
3895
|
}
|
|
3745
3896
|
}
|
|
@@ -3896,8 +4047,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3896
4047
|
const ctxContent = [
|
|
3897
4048
|
`## Session Context`,
|
|
3898
4049
|
`You are running in tmux session: ${sessionName}.`,
|
|
3899
|
-
`Your parent
|
|
3900
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
4050
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
4051
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3901
4052
|
].join("\n");
|
|
3902
4053
|
writeFileSync7(ctxFile, ctxContent);
|
|
3903
4054
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -4001,6 +4152,7 @@ var init_tmux_routing = __esm({
|
|
|
4001
4152
|
init_provider_table();
|
|
4002
4153
|
init_intercom_queue();
|
|
4003
4154
|
init_plan_limits();
|
|
4155
|
+
init_employees();
|
|
4004
4156
|
SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
4005
4157
|
SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
|
|
4006
4158
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -4218,7 +4370,11 @@ async function ensureShardSchema(client) {
|
|
|
4218
4370
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
4219
4371
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
4220
4372
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
4221
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
4373
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
4374
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
4375
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
4376
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
4377
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
4222
4378
|
]) {
|
|
4223
4379
|
try {
|
|
4224
4380
|
await client.execute(col);
|
|
@@ -4348,26 +4504,26 @@ var init_platform_procedures = __esm({
|
|
|
4348
4504
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
4349
4505
|
domain: "architecture",
|
|
4350
4506
|
priority: "p0",
|
|
4351
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
4507
|
+
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."
|
|
4352
4508
|
},
|
|
4353
4509
|
{
|
|
4354
4510
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
4355
4511
|
domain: "architecture",
|
|
4356
4512
|
priority: "p0",
|
|
4357
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
4513
|
+
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."
|
|
4358
4514
|
},
|
|
4359
4515
|
{
|
|
4360
|
-
title: "Sessions explained \u2014
|
|
4516
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
4361
4517
|
domain: "architecture",
|
|
4362
4518
|
priority: "p0",
|
|
4363
|
-
content: "Each
|
|
4519
|
+
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."
|
|
4364
4520
|
},
|
|
4365
4521
|
// --- Hierarchy and dispatch ---
|
|
4366
4522
|
{
|
|
4367
4523
|
title: "Chain of command \u2014 who talks to whom",
|
|
4368
4524
|
domain: "workflow",
|
|
4369
4525
|
priority: "p0",
|
|
4370
|
-
content: "Founder
|
|
4526
|
+
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."
|
|
4371
4527
|
},
|
|
4372
4528
|
{
|
|
4373
4529
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -4377,30 +4533,30 @@ var init_platform_procedures = __esm({
|
|
|
4377
4533
|
},
|
|
4378
4534
|
// --- Session isolation ---
|
|
4379
4535
|
{
|
|
4380
|
-
title: "Session scoping \u2014 stay in your
|
|
4536
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
4381
4537
|
domain: "security",
|
|
4382
4538
|
priority: "p0",
|
|
4383
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
4539
|
+
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."
|
|
4384
4540
|
},
|
|
4385
4541
|
{
|
|
4386
4542
|
title: "Session isolation \u2014 never touch another session's work",
|
|
4387
4543
|
domain: "workflow",
|
|
4388
4544
|
priority: "p0",
|
|
4389
|
-
content:
|
|
4545
|
+
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."
|
|
4390
4546
|
},
|
|
4391
4547
|
// --- Engineering: session scoping in code ---
|
|
4392
4548
|
{
|
|
4393
4549
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
4394
4550
|
domain: "architecture",
|
|
4395
4551
|
priority: "p0",
|
|
4396
|
-
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
|
|
4552
|
+
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."
|
|
4397
4553
|
},
|
|
4398
4554
|
// --- Hard constraints ---
|
|
4399
4555
|
{
|
|
4400
4556
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
4401
4557
|
domain: "security",
|
|
4402
4558
|
priority: "p0",
|
|
4403
|
-
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
|
|
4559
|
+
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."
|
|
4404
4560
|
},
|
|
4405
4561
|
// --- Operations ---
|
|
4406
4562
|
{
|
|
@@ -4640,7 +4796,10 @@ async function writeMemory(record) {
|
|
|
4640
4796
|
source_path: record.source_path ?? null,
|
|
4641
4797
|
source_type: record.source_type ?? null,
|
|
4642
4798
|
tier: record.tier ?? classifyTier(record),
|
|
4643
|
-
supersedes_id: record.supersedes_id ?? null
|
|
4799
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
4800
|
+
draft: record.draft ? 1 : 0,
|
|
4801
|
+
memory_type: record.memory_type ?? "raw",
|
|
4802
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
4644
4803
|
};
|
|
4645
4804
|
_pendingRecords.push(dbRow);
|
|
4646
4805
|
orgBus.emit({
|
|
@@ -4695,6 +4854,9 @@ async function flushBatch() {
|
|
|
4695
4854
|
const sourceType = row.source_type ?? null;
|
|
4696
4855
|
const tier = row.tier ?? 3;
|
|
4697
4856
|
const supersedesId = row.supersedes_id ?? null;
|
|
4857
|
+
const draft = row.draft ? 1 : 0;
|
|
4858
|
+
const memoryType = row.memory_type ?? "raw";
|
|
4859
|
+
const trajectory = row.trajectory ?? null;
|
|
4698
4860
|
return {
|
|
4699
4861
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
4700
4862
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -4702,15 +4864,15 @@ async function flushBatch() {
|
|
|
4702
4864
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4703
4865
|
confidence, last_accessed,
|
|
4704
4866
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4705
|
-
source_path, source_type, tier, supersedes_id)
|
|
4706
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4867
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
4868
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4707
4869
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
4708
4870
|
tool_name, project_name,
|
|
4709
4871
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4710
4872
|
confidence, last_accessed,
|
|
4711
4873
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4712
|
-
source_path, source_type, tier, supersedes_id)
|
|
4713
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4874
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
4875
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4714
4876
|
args: hasVector ? [
|
|
4715
4877
|
row.id,
|
|
4716
4878
|
row.agent_id,
|
|
@@ -4736,7 +4898,10 @@ async function flushBatch() {
|
|
|
4736
4898
|
sourcePath,
|
|
4737
4899
|
sourceType,
|
|
4738
4900
|
tier,
|
|
4739
|
-
supersedesId
|
|
4901
|
+
supersedesId,
|
|
4902
|
+
draft,
|
|
4903
|
+
memoryType,
|
|
4904
|
+
trajectory
|
|
4740
4905
|
] : [
|
|
4741
4906
|
row.id,
|
|
4742
4907
|
row.agent_id,
|
|
@@ -4761,7 +4926,10 @@ async function flushBatch() {
|
|
|
4761
4926
|
sourcePath,
|
|
4762
4927
|
sourceType,
|
|
4763
4928
|
tier,
|
|
4764
|
-
supersedesId
|
|
4929
|
+
supersedesId,
|
|
4930
|
+
draft,
|
|
4931
|
+
memoryType,
|
|
4932
|
+
trajectory
|
|
4765
4933
|
]
|
|
4766
4934
|
};
|
|
4767
4935
|
};
|
|
@@ -4830,6 +4998,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4830
4998
|
const limit = options?.limit ?? 10;
|
|
4831
4999
|
const statusFilter = options?.includeArchived ? "" : `
|
|
4832
5000
|
AND COALESCE(status, 'active') = 'active'`;
|
|
5001
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
5002
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
4833
5003
|
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
4834
5004
|
tool_name, project_name,
|
|
4835
5005
|
has_error, raw_text, vector, importance, status,
|
|
@@ -4839,7 +5009,7 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4839
5009
|
source_path, source_type
|
|
4840
5010
|
FROM memories
|
|
4841
5011
|
WHERE agent_id = ?
|
|
4842
|
-
AND vector IS NOT NULL${statusFilter}
|
|
5012
|
+
AND vector IS NOT NULL${statusFilter}${draftFilter}
|
|
4843
5013
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
4844
5014
|
const args = [agentId];
|
|
4845
5015
|
const scope = buildWikiScopeFilter(options, "");
|
|
@@ -4861,6 +5031,10 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4861
5031
|
sql += ` AND timestamp >= ?`;
|
|
4862
5032
|
args.push(options.since);
|
|
4863
5033
|
}
|
|
5034
|
+
if (options?.memoryType) {
|
|
5035
|
+
sql += ` AND memory_type = ?`;
|
|
5036
|
+
args.push(options.memoryType);
|
|
5037
|
+
}
|
|
4864
5038
|
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
4865
5039
|
args.push(vectorToBlob(queryVector));
|
|
4866
5040
|
sql += ` LIMIT ?`;
|
|
@@ -5011,30 +5185,73 @@ import crypto6 from "crypto";
|
|
|
5011
5185
|
|
|
5012
5186
|
// src/adapters/claude/active-agent.ts
|
|
5013
5187
|
init_config();
|
|
5014
|
-
import { readFileSync as
|
|
5015
|
-
import { execSync as
|
|
5016
|
-
import
|
|
5188
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
|
|
5189
|
+
import { execSync as execSync3 } from "child_process";
|
|
5190
|
+
import path3 from "path";
|
|
5017
5191
|
|
|
5018
5192
|
// src/adapters/claude/session-key.ts
|
|
5019
5193
|
init_session_key();
|
|
5020
5194
|
|
|
5021
5195
|
// src/adapters/claude/active-agent.ts
|
|
5022
|
-
|
|
5196
|
+
init_employees();
|
|
5197
|
+
var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
|
|
5023
5198
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
5199
|
+
function isNameWithOptionalInstance(candidate, baseName) {
|
|
5200
|
+
if (candidate === baseName) return true;
|
|
5201
|
+
if (!candidate.startsWith(baseName)) return false;
|
|
5202
|
+
return /^\d+$/.test(candidate.slice(baseName.length));
|
|
5203
|
+
}
|
|
5204
|
+
function resolveEmployeeFromSessionPrefix(prefix, employees) {
|
|
5205
|
+
const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
|
|
5206
|
+
for (const employee of sorted) {
|
|
5207
|
+
if (isNameWithOptionalInstance(prefix, employee.name)) {
|
|
5208
|
+
return { agentId: employee.name, agentRole: employee.role };
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
return null;
|
|
5212
|
+
}
|
|
5213
|
+
function resolveActiveAgentFromTmuxSession(sessionName) {
|
|
5214
|
+
const employees = loadEmployeesSync();
|
|
5215
|
+
const coordinator = getCoordinatorEmployee(employees);
|
|
5216
|
+
const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
5217
|
+
if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
|
|
5218
|
+
return {
|
|
5219
|
+
agentId: coordinatorName,
|
|
5220
|
+
agentRole: coordinator?.role ?? "COO"
|
|
5221
|
+
};
|
|
5222
|
+
}
|
|
5223
|
+
if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
|
|
5224
|
+
return {
|
|
5225
|
+
agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
5226
|
+
agentRole: coordinator?.role ?? "COO"
|
|
5227
|
+
};
|
|
5228
|
+
}
|
|
5229
|
+
if (sessionName.includes("-")) {
|
|
5230
|
+
const prefix = sessionName.split("-")[0] ?? "";
|
|
5231
|
+
const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
|
|
5232
|
+
if (employee) return employee;
|
|
5233
|
+
const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
|
|
5234
|
+
if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
|
|
5235
|
+
const emp = getEmployee(employees, legacy[1]);
|
|
5236
|
+
return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
return null;
|
|
5240
|
+
}
|
|
5024
5241
|
function getMarkerPath() {
|
|
5025
|
-
return
|
|
5242
|
+
return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
|
|
5026
5243
|
}
|
|
5027
5244
|
function getActiveAgent() {
|
|
5028
5245
|
try {
|
|
5029
5246
|
const markerPath = getMarkerPath();
|
|
5030
|
-
const raw =
|
|
5247
|
+
const raw = readFileSync3(markerPath, "utf8");
|
|
5031
5248
|
const data = JSON.parse(raw);
|
|
5032
5249
|
if (data.agentId) {
|
|
5033
5250
|
if (data.startedAt) {
|
|
5034
5251
|
const age = Date.now() - new Date(data.startedAt).getTime();
|
|
5035
5252
|
if (age > STALE_MS) {
|
|
5036
5253
|
try {
|
|
5037
|
-
|
|
5254
|
+
unlinkSync2(markerPath);
|
|
5038
5255
|
} catch {
|
|
5039
5256
|
}
|
|
5040
5257
|
} else {
|
|
@@ -5053,17 +5270,12 @@ function getActiveAgent() {
|
|
|
5053
5270
|
} catch {
|
|
5054
5271
|
}
|
|
5055
5272
|
try {
|
|
5056
|
-
const sessionName =
|
|
5273
|
+
const sessionName = execSync3(
|
|
5057
5274
|
"tmux display-message -p '#{session_name}' 2>/dev/null",
|
|
5058
5275
|
{ encoding: "utf8", timeout: 2e3 }
|
|
5059
5276
|
).trim();
|
|
5060
|
-
const
|
|
5061
|
-
if (
|
|
5062
|
-
return { agentId: empMatch[1], agentRole: "employee" };
|
|
5063
|
-
}
|
|
5064
|
-
if (/^exe\d+$/.test(sessionName)) {
|
|
5065
|
-
return { agentId: "exe", agentRole: "COO" };
|
|
5066
|
-
}
|
|
5277
|
+
const resolved = resolveActiveAgentFromTmuxSession(sessionName);
|
|
5278
|
+
if (resolved) return resolved;
|
|
5067
5279
|
} catch {
|
|
5068
5280
|
}
|
|
5069
5281
|
return {
|