@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/bin/exe-boot.js
CHANGED
|
@@ -31,7 +31,6 @@ var config_exports = {};
|
|
|
31
31
|
__export(config_exports, {
|
|
32
32
|
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
33
33
|
CONFIG_PATH: () => CONFIG_PATH,
|
|
34
|
-
COO_AGENT_NAME: () => COO_AGENT_NAME,
|
|
35
34
|
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
36
35
|
DB_PATH: () => DB_PATH,
|
|
37
36
|
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
@@ -187,7 +186,7 @@ async function loadConfigFrom(configPath) {
|
|
|
187
186
|
return { ...DEFAULT_CONFIG };
|
|
188
187
|
}
|
|
189
188
|
}
|
|
190
|
-
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH,
|
|
189
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
191
190
|
var init_config = __esm({
|
|
192
191
|
"src/lib/config.ts"() {
|
|
193
192
|
"use strict";
|
|
@@ -195,7 +194,6 @@ var init_config = __esm({
|
|
|
195
194
|
DB_PATH = path.join(EXE_AI_DIR, "memories.db");
|
|
196
195
|
MODELS_DIR = path.join(EXE_AI_DIR, "models");
|
|
197
196
|
CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
|
|
198
|
-
COO_AGENT_NAME = "exe";
|
|
199
197
|
LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
|
|
200
198
|
CURRENT_CONFIG_VERSION = 1;
|
|
201
199
|
DEFAULT_CONFIG = {
|
|
@@ -231,13 +229,7 @@ var init_config = __esm({
|
|
|
231
229
|
wikiUrl: "",
|
|
232
230
|
wikiApiKey: "",
|
|
233
231
|
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
234
|
-
wikiWorkspaceMapping: {
|
|
235
|
-
exe: "Executive",
|
|
236
|
-
yoshi: "Engineering",
|
|
237
|
-
mari: "Marketing",
|
|
238
|
-
tom: "Engineering",
|
|
239
|
-
sasha: "Production"
|
|
240
|
-
},
|
|
232
|
+
wikiWorkspaceMapping: {},
|
|
241
233
|
wikiAutoUpdate: true,
|
|
242
234
|
wikiAutoUpdateThreshold: 0.5,
|
|
243
235
|
wikiAutoUpdateCreateNew: true,
|
|
@@ -280,6 +272,22 @@ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as r
|
|
|
280
272
|
import { execSync } from "child_process";
|
|
281
273
|
import path2 from "path";
|
|
282
274
|
import os2 from "os";
|
|
275
|
+
function normalizeRole(role) {
|
|
276
|
+
return (role ?? "").trim().toLowerCase();
|
|
277
|
+
}
|
|
278
|
+
function isCoordinatorRole(role) {
|
|
279
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
280
|
+
}
|
|
281
|
+
function getCoordinatorEmployee(employees) {
|
|
282
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
283
|
+
}
|
|
284
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
285
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
286
|
+
}
|
|
287
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
288
|
+
if (!agentName) return false;
|
|
289
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
290
|
+
}
|
|
283
291
|
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
284
292
|
if (!existsSync2(employeesPath)) {
|
|
285
293
|
return [];
|
|
@@ -352,12 +360,14 @@ function registerBinSymlinks(name) {
|
|
|
352
360
|
}
|
|
353
361
|
return { created, skipped, errors };
|
|
354
362
|
}
|
|
355
|
-
var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
|
|
363
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
356
364
|
var init_employees = __esm({
|
|
357
365
|
"src/lib/employees.ts"() {
|
|
358
366
|
"use strict";
|
|
359
367
|
init_config();
|
|
360
368
|
EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
|
|
369
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
370
|
+
COORDINATOR_ROLE = "COO";
|
|
361
371
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
362
372
|
}
|
|
363
373
|
});
|
|
@@ -401,7 +411,7 @@ function wrapWithRetry(client) {
|
|
|
401
411
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
402
412
|
}
|
|
403
413
|
if (prop === "batch") {
|
|
404
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
414
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
405
415
|
}
|
|
406
416
|
return Reflect.get(target, prop, receiver);
|
|
407
417
|
}
|
|
@@ -418,6 +428,17 @@ var init_db_retry = __esm({
|
|
|
418
428
|
});
|
|
419
429
|
|
|
420
430
|
// src/lib/database.ts
|
|
431
|
+
var database_exports = {};
|
|
432
|
+
__export(database_exports, {
|
|
433
|
+
disposeDatabase: () => disposeDatabase,
|
|
434
|
+
disposeTurso: () => disposeTurso,
|
|
435
|
+
ensureSchema: () => ensureSchema,
|
|
436
|
+
getClient: () => getClient,
|
|
437
|
+
getRawClient: () => getRawClient,
|
|
438
|
+
initDatabase: () => initDatabase,
|
|
439
|
+
initTurso: () => initTurso,
|
|
440
|
+
isInitialized: () => isInitialized
|
|
441
|
+
});
|
|
421
442
|
import { createClient } from "@libsql/client";
|
|
422
443
|
async function initDatabase(config) {
|
|
423
444
|
if (_client) {
|
|
@@ -553,22 +574,24 @@ async function ensureSchema() {
|
|
|
553
574
|
ON behaviors(agent_id, active);
|
|
554
575
|
`);
|
|
555
576
|
try {
|
|
577
|
+
const coordinatorName = getCoordinatorName();
|
|
556
578
|
const existing = await client.execute({
|
|
557
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
558
|
-
args: []
|
|
579
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
580
|
+
args: [coordinatorName]
|
|
559
581
|
});
|
|
560
582
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
583
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
584
|
+
for (const [domain, content] of [
|
|
585
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
586
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
587
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
588
|
+
]) {
|
|
589
|
+
await client.execute({
|
|
590
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
591
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
592
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
593
|
+
});
|
|
594
|
+
}
|
|
572
595
|
}
|
|
573
596
|
} catch {
|
|
574
597
|
}
|
|
@@ -1260,15 +1283,57 @@ async function ensureSchema() {
|
|
|
1260
1283
|
} catch {
|
|
1261
1284
|
}
|
|
1262
1285
|
}
|
|
1286
|
+
try {
|
|
1287
|
+
await client.execute({
|
|
1288
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1289
|
+
args: []
|
|
1290
|
+
});
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
try {
|
|
1294
|
+
await client.execute(
|
|
1295
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1296
|
+
);
|
|
1297
|
+
} catch {
|
|
1298
|
+
}
|
|
1299
|
+
try {
|
|
1300
|
+
await client.execute({
|
|
1301
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1302
|
+
args: []
|
|
1303
|
+
});
|
|
1304
|
+
} catch {
|
|
1305
|
+
}
|
|
1306
|
+
try {
|
|
1307
|
+
await client.execute(
|
|
1308
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1309
|
+
);
|
|
1310
|
+
} catch {
|
|
1311
|
+
}
|
|
1312
|
+
try {
|
|
1313
|
+
await client.execute({
|
|
1314
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1315
|
+
args: []
|
|
1316
|
+
});
|
|
1317
|
+
} catch {
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
async function disposeDatabase() {
|
|
1321
|
+
if (_client) {
|
|
1322
|
+
_client.close();
|
|
1323
|
+
_client = null;
|
|
1324
|
+
_resilientClient = null;
|
|
1325
|
+
}
|
|
1263
1326
|
}
|
|
1264
|
-
var _client, _resilientClient, initTurso;
|
|
1327
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1265
1328
|
var init_database = __esm({
|
|
1266
1329
|
"src/lib/database.ts"() {
|
|
1267
1330
|
"use strict";
|
|
1268
1331
|
init_db_retry();
|
|
1332
|
+
init_employees();
|
|
1269
1333
|
_client = null;
|
|
1270
1334
|
_resilientClient = null;
|
|
1271
1335
|
initTurso = initDatabase;
|
|
1336
|
+
disposeTurso = disposeDatabase;
|
|
1272
1337
|
}
|
|
1273
1338
|
});
|
|
1274
1339
|
|
|
@@ -1283,26 +1348,26 @@ var init_platform_procedures = __esm({
|
|
|
1283
1348
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
1284
1349
|
domain: "architecture",
|
|
1285
1350
|
priority: "p0",
|
|
1286
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
1351
|
+
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."
|
|
1287
1352
|
},
|
|
1288
1353
|
{
|
|
1289
1354
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
1290
1355
|
domain: "architecture",
|
|
1291
1356
|
priority: "p0",
|
|
1292
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
1357
|
+
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."
|
|
1293
1358
|
},
|
|
1294
1359
|
{
|
|
1295
|
-
title: "Sessions explained \u2014
|
|
1360
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
1296
1361
|
domain: "architecture",
|
|
1297
1362
|
priority: "p0",
|
|
1298
|
-
content: "Each
|
|
1363
|
+
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."
|
|
1299
1364
|
},
|
|
1300
1365
|
// --- Hierarchy and dispatch ---
|
|
1301
1366
|
{
|
|
1302
1367
|
title: "Chain of command \u2014 who talks to whom",
|
|
1303
1368
|
domain: "workflow",
|
|
1304
1369
|
priority: "p0",
|
|
1305
|
-
content: "Founder
|
|
1370
|
+
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."
|
|
1306
1371
|
},
|
|
1307
1372
|
{
|
|
1308
1373
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -1312,30 +1377,30 @@ var init_platform_procedures = __esm({
|
|
|
1312
1377
|
},
|
|
1313
1378
|
// --- Session isolation ---
|
|
1314
1379
|
{
|
|
1315
|
-
title: "Session scoping \u2014 stay in your
|
|
1380
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
1316
1381
|
domain: "security",
|
|
1317
1382
|
priority: "p0",
|
|
1318
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
1383
|
+
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."
|
|
1319
1384
|
},
|
|
1320
1385
|
{
|
|
1321
1386
|
title: "Session isolation \u2014 never touch another session's work",
|
|
1322
1387
|
domain: "workflow",
|
|
1323
1388
|
priority: "p0",
|
|
1324
|
-
content:
|
|
1389
|
+
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."
|
|
1325
1390
|
},
|
|
1326
1391
|
// --- Engineering: session scoping in code ---
|
|
1327
1392
|
{
|
|
1328
1393
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
1329
1394
|
domain: "architecture",
|
|
1330
1395
|
priority: "p0",
|
|
1331
|
-
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
|
|
1396
|
+
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."
|
|
1332
1397
|
},
|
|
1333
1398
|
// --- Hard constraints ---
|
|
1334
1399
|
{
|
|
1335
1400
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
1336
1401
|
domain: "security",
|
|
1337
1402
|
priority: "p0",
|
|
1338
|
-
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
|
|
1403
|
+
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."
|
|
1339
1404
|
},
|
|
1340
1405
|
// --- Operations ---
|
|
1341
1406
|
{
|
|
@@ -1752,7 +1817,11 @@ async function ensureShardSchema(client) {
|
|
|
1752
1817
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
1753
1818
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
1754
1819
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
1755
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
1820
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
1821
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
1822
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
1823
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
1824
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
1756
1825
|
]) {
|
|
1757
1826
|
try {
|
|
1758
1827
|
await client.execute(col);
|
|
@@ -3271,6 +3340,36 @@ async function listTasks(input) {
|
|
|
3271
3340
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
3272
3341
|
}));
|
|
3273
3342
|
}
|
|
3343
|
+
function isTmuxSessionAlive(identifier) {
|
|
3344
|
+
if (!identifier || identifier === "unknown") return true;
|
|
3345
|
+
try {
|
|
3346
|
+
if (identifier.startsWith("%")) {
|
|
3347
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
3348
|
+
timeout: 2e3,
|
|
3349
|
+
encoding: "utf8",
|
|
3350
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3351
|
+
});
|
|
3352
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
3353
|
+
} else {
|
|
3354
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
3355
|
+
timeout: 2e3,
|
|
3356
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3357
|
+
});
|
|
3358
|
+
return true;
|
|
3359
|
+
}
|
|
3360
|
+
} catch {
|
|
3361
|
+
if (identifier.startsWith("%")) return true;
|
|
3362
|
+
try {
|
|
3363
|
+
execSync5("tmux list-sessions", {
|
|
3364
|
+
timeout: 2e3,
|
|
3365
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3366
|
+
});
|
|
3367
|
+
return false;
|
|
3368
|
+
} catch {
|
|
3369
|
+
return true;
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3274
3373
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
3275
3374
|
if (!taskContext) return null;
|
|
3276
3375
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -3333,13 +3432,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
3333
3432
|
});
|
|
3334
3433
|
if (claim.rowsAffected === 0) {
|
|
3335
3434
|
const current = await client.execute({
|
|
3336
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
3435
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
3337
3436
|
args: [taskId]
|
|
3338
3437
|
});
|
|
3339
3438
|
const cur = current.rows[0];
|
|
3340
|
-
const
|
|
3341
|
-
const
|
|
3342
|
-
|
|
3439
|
+
const curStatus = cur?.status ?? "unknown";
|
|
3440
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
3441
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
3442
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
3443
|
+
process.stderr.write(
|
|
3444
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
3445
|
+
`
|
|
3446
|
+
);
|
|
3447
|
+
await client.execute({
|
|
3448
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
3449
|
+
args: [now, taskId]
|
|
3450
|
+
});
|
|
3451
|
+
const retried = await client.execute({
|
|
3452
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
3453
|
+
args: [tmuxSession, now, taskId]
|
|
3454
|
+
});
|
|
3455
|
+
if (retried.rowsAffected > 0) {
|
|
3456
|
+
try {
|
|
3457
|
+
await writeCheckpoint({
|
|
3458
|
+
taskId,
|
|
3459
|
+
step: "reclaimed_dead_session",
|
|
3460
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
3461
|
+
});
|
|
3462
|
+
} catch {
|
|
3463
|
+
}
|
|
3464
|
+
return { row, taskFile, now, taskId };
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
3468
|
+
process.stderr.write(
|
|
3469
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
3470
|
+
`
|
|
3471
|
+
);
|
|
3472
|
+
await client.execute({
|
|
3473
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
3474
|
+
args: [tmuxSession, now, taskId]
|
|
3475
|
+
});
|
|
3476
|
+
try {
|
|
3477
|
+
await writeCheckpoint({
|
|
3478
|
+
taskId,
|
|
3479
|
+
step: "assigner_override",
|
|
3480
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
3481
|
+
});
|
|
3482
|
+
} catch {
|
|
3483
|
+
}
|
|
3484
|
+
return { row, taskFile, now, taskId };
|
|
3485
|
+
}
|
|
3486
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
3487
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
3343
3488
|
}
|
|
3344
3489
|
try {
|
|
3345
3490
|
await writeCheckpoint({
|
|
@@ -3437,7 +3582,7 @@ var init_tasks_crud = __esm({
|
|
|
3437
3582
|
"use strict";
|
|
3438
3583
|
init_database();
|
|
3439
3584
|
init_task_scope();
|
|
3440
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
3585
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
3441
3586
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
3442
3587
|
}
|
|
3443
3588
|
});
|
|
@@ -3803,7 +3948,7 @@ function findSessionForProject(projectName) {
|
|
|
3803
3948
|
const sessions = listSessions();
|
|
3804
3949
|
for (const s of sessions) {
|
|
3805
3950
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3806
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
3951
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
3807
3952
|
}
|
|
3808
3953
|
return null;
|
|
3809
3954
|
}
|
|
@@ -3843,12 +3988,13 @@ var init_session_scope = __esm({
|
|
|
3843
3988
|
init_session_registry();
|
|
3844
3989
|
init_project_name();
|
|
3845
3990
|
init_tmux_routing();
|
|
3991
|
+
init_employees();
|
|
3846
3992
|
}
|
|
3847
3993
|
});
|
|
3848
3994
|
|
|
3849
3995
|
// src/lib/tasks-notify.ts
|
|
3850
3996
|
async function dispatchTaskToEmployee(input) {
|
|
3851
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
3997
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
3852
3998
|
let crossProject = false;
|
|
3853
3999
|
if (input.projectName) {
|
|
3854
4000
|
try {
|
|
@@ -4291,6 +4437,24 @@ async function updateTask(input) {
|
|
|
4291
4437
|
});
|
|
4292
4438
|
} catch {
|
|
4293
4439
|
}
|
|
4440
|
+
const assignedAgent = String(row.assigned_to);
|
|
4441
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
4442
|
+
try {
|
|
4443
|
+
const draftClient = getClient();
|
|
4444
|
+
if (input.status === "done") {
|
|
4445
|
+
await draftClient.execute({
|
|
4446
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
4447
|
+
args: [assignedAgent]
|
|
4448
|
+
});
|
|
4449
|
+
} else if (input.status === "cancelled") {
|
|
4450
|
+
await draftClient.execute({
|
|
4451
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
4452
|
+
args: [assignedAgent]
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
} catch {
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4294
4458
|
try {
|
|
4295
4459
|
const client = getClient();
|
|
4296
4460
|
const cascaded = await client.execute({
|
|
@@ -4309,8 +4473,8 @@ async function updateTask(input) {
|
|
|
4309
4473
|
}
|
|
4310
4474
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
4311
4475
|
if (isTerminal) {
|
|
4312
|
-
const
|
|
4313
|
-
if (!
|
|
4476
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
4477
|
+
if (!isCoordinator) {
|
|
4314
4478
|
notifyTaskDone();
|
|
4315
4479
|
}
|
|
4316
4480
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -4334,7 +4498,7 @@ async function updateTask(input) {
|
|
|
4334
4498
|
}
|
|
4335
4499
|
}
|
|
4336
4500
|
}
|
|
4337
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
4501
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
4338
4502
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
4339
4503
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
4340
4504
|
taskId,
|
|
@@ -4350,7 +4514,7 @@ async function updateTask(input) {
|
|
|
4350
4514
|
});
|
|
4351
4515
|
}
|
|
4352
4516
|
let nextTask;
|
|
4353
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
4517
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
4354
4518
|
try {
|
|
4355
4519
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
4356
4520
|
} catch {
|
|
@@ -4377,12 +4541,14 @@ async function updateTask(input) {
|
|
|
4377
4541
|
async function deleteTask(taskId, baseDir) {
|
|
4378
4542
|
const client = getClient();
|
|
4379
4543
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
4380
|
-
const
|
|
4544
|
+
const coordinatorName = getCoordinatorName();
|
|
4545
|
+
const reviewer = assignedBy || coordinatorName;
|
|
4381
4546
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
4382
4547
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
4548
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
4383
4549
|
await client.execute({
|
|
4384
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
4385
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
4550
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
4551
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
4386
4552
|
});
|
|
4387
4553
|
await markAsReadByTaskFile(taskFile);
|
|
4388
4554
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -4394,6 +4560,7 @@ var init_tasks = __esm({
|
|
|
4394
4560
|
init_config();
|
|
4395
4561
|
init_notifications();
|
|
4396
4562
|
init_state_bus();
|
|
4563
|
+
init_employees();
|
|
4397
4564
|
init_tasks_crud();
|
|
4398
4565
|
init_tasks_review();
|
|
4399
4566
|
init_tasks_crud();
|
|
@@ -4479,7 +4646,7 @@ function _resetLastRelaunchCache() {
|
|
|
4479
4646
|
}
|
|
4480
4647
|
async function lastResumeCreatedAtMs(agentId) {
|
|
4481
4648
|
const client = getClient();
|
|
4482
|
-
const cmScope = sessionScopeFilter();
|
|
4649
|
+
const cmScope = sessionScopeFilter(null);
|
|
4483
4650
|
const result = await client.execute({
|
|
4484
4651
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
4485
4652
|
FROM tasks
|
|
@@ -4504,7 +4671,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
4504
4671
|
const client = getClient();
|
|
4505
4672
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4506
4673
|
const context = buildResumeContext(agentId, openTasks);
|
|
4507
|
-
const rdScope = sessionScopeFilter();
|
|
4674
|
+
const rdScope = sessionScopeFilter(null);
|
|
4508
4675
|
const existing = await client.execute({
|
|
4509
4676
|
sql: `SELECT id FROM tasks
|
|
4510
4677
|
WHERE assigned_to = ?
|
|
@@ -4538,7 +4705,7 @@ async function pollCapacityDead() {
|
|
|
4538
4705
|
const transport = getTransport();
|
|
4539
4706
|
const relaunched = [];
|
|
4540
4707
|
const registered = listSessions().filter(
|
|
4541
|
-
(s) => s.agentId !== "exe"
|
|
4708
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
4542
4709
|
);
|
|
4543
4710
|
if (registered.length === 0) return [];
|
|
4544
4711
|
let liveSessions;
|
|
@@ -4598,7 +4765,7 @@ async function pollCapacityDead() {
|
|
|
4598
4765
|
reason: "capacity"
|
|
4599
4766
|
});
|
|
4600
4767
|
const client = getClient();
|
|
4601
|
-
const rlScope = sessionScopeFilter();
|
|
4768
|
+
const rlScope = sessionScopeFilter(null);
|
|
4602
4769
|
const openTasks = await client.execute({
|
|
4603
4770
|
sql: `SELECT id, title, priority, task_file, status
|
|
4604
4771
|
FROM tasks
|
|
@@ -4652,6 +4819,7 @@ var init_capacity_monitor = __esm({
|
|
|
4652
4819
|
init_session_kill_telemetry();
|
|
4653
4820
|
init_tmux_routing();
|
|
4654
4821
|
init_task_scope();
|
|
4822
|
+
init_employees();
|
|
4655
4823
|
CAPACITY_PATTERNS = [
|
|
4656
4824
|
/conversation is too long/i,
|
|
4657
4825
|
/maximum context length/i,
|
|
@@ -4801,7 +4969,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
4801
4969
|
exeSession = root;
|
|
4802
4970
|
} else {
|
|
4803
4971
|
throw new Error(
|
|
4804
|
-
`Invalid
|
|
4972
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
4805
4973
|
);
|
|
4806
4974
|
}
|
|
4807
4975
|
}
|
|
@@ -4821,8 +4989,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
4821
4989
|
return match?.[1] ?? null;
|
|
4822
4990
|
}
|
|
4823
4991
|
function extractRootExe(name) {
|
|
4824
|
-
|
|
4825
|
-
|
|
4992
|
+
if (!name) return null;
|
|
4993
|
+
if (!name.includes("-")) return name;
|
|
4994
|
+
const parts = name.split("-").filter(Boolean);
|
|
4995
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4826
4996
|
}
|
|
4827
4997
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4828
4998
|
if (!existsSync12(SESSION_CACHE)) {
|
|
@@ -4967,12 +5137,14 @@ function isSessionBusy(sessionName) {
|
|
|
4967
5137
|
return state === "thinking" || state === "tool";
|
|
4968
5138
|
}
|
|
4969
5139
|
function isExeSession(sessionName) {
|
|
4970
|
-
|
|
5140
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
5141
|
+
const coordinatorName = getCoordinatorName();
|
|
5142
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
4971
5143
|
}
|
|
4972
5144
|
function sendIntercom(targetSession) {
|
|
4973
5145
|
const transport = getTransport();
|
|
4974
5146
|
if (isExeSession(targetSession)) {
|
|
4975
|
-
logIntercom(`
|
|
5147
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
4976
5148
|
return "skipped_exe";
|
|
4977
5149
|
}
|
|
4978
5150
|
if (isDebounced(targetSession)) {
|
|
@@ -5024,7 +5196,7 @@ function notifyParentExe(sessionKey) {
|
|
|
5024
5196
|
if (result === "failed") {
|
|
5025
5197
|
const rootExe = resolveExeSession();
|
|
5026
5198
|
if (rootExe && rootExe !== target) {
|
|
5027
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
5199
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
5028
5200
|
`);
|
|
5029
5201
|
const fallback = sendIntercom(rootExe);
|
|
5030
5202
|
return fallback !== "failed";
|
|
@@ -5034,8 +5206,8 @@ function notifyParentExe(sessionKey) {
|
|
|
5034
5206
|
return true;
|
|
5035
5207
|
}
|
|
5036
5208
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
5037
|
-
if (employeeName === "exe") {
|
|
5038
|
-
return { status: "failed", sessionName: "", error: "
|
|
5209
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
5210
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
5039
5211
|
}
|
|
5040
5212
|
try {
|
|
5041
5213
|
assertEmployeeLimitSync();
|
|
@@ -5044,8 +5216,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5044
5216
|
return { status: "failed", sessionName: "", error: err.message };
|
|
5045
5217
|
}
|
|
5046
5218
|
}
|
|
5047
|
-
if (
|
|
5048
|
-
const bare = employeeName.
|
|
5219
|
+
if (employeeName.includes("-")) {
|
|
5220
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
5049
5221
|
return {
|
|
5050
5222
|
status: "failed",
|
|
5051
5223
|
sessionName: "",
|
|
@@ -5064,7 +5236,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5064
5236
|
return {
|
|
5065
5237
|
status: "failed",
|
|
5066
5238
|
sessionName: "",
|
|
5067
|
-
error: `Invalid
|
|
5239
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
5068
5240
|
};
|
|
5069
5241
|
}
|
|
5070
5242
|
}
|
|
@@ -5221,8 +5393,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
5221
5393
|
const ctxContent = [
|
|
5222
5394
|
`## Session Context`,
|
|
5223
5395
|
`You are running in tmux session: ${sessionName}.`,
|
|
5224
|
-
`Your parent
|
|
5225
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
5396
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
5397
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
5226
5398
|
].join("\n");
|
|
5227
5399
|
writeFileSync6(ctxFile, ctxContent);
|
|
5228
5400
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -5326,6 +5498,7 @@ var init_tmux_routing = __esm({
|
|
|
5326
5498
|
init_provider_table();
|
|
5327
5499
|
init_intercom_queue();
|
|
5328
5500
|
init_plan_limits();
|
|
5501
|
+
init_employees();
|
|
5329
5502
|
SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
5330
5503
|
SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
|
|
5331
5504
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -5884,6 +6057,11 @@ async function cloudSync(config) {
|
|
|
5884
6057
|
} catch {
|
|
5885
6058
|
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
5886
6059
|
}
|
|
6060
|
+
try {
|
|
6061
|
+
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
6062
|
+
await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
6063
|
+
} catch {
|
|
6064
|
+
}
|
|
5887
6065
|
try {
|
|
5888
6066
|
await client.execute(
|
|
5889
6067
|
"CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
|
|
@@ -6926,13 +7104,13 @@ Ethos:
|
|
|
6926
7104
|
- Founder zero-ego. Distributors and customers are the loudest voice.
|
|
6927
7105
|
- Crypto values: big companies should not own consumer/SMB AI.
|
|
6928
7106
|
|
|
6929
|
-
STOP AND REDIRECT: Any decision that compromises memory sovereignty, 3-layer cognition, MCP boundary, or AGPL boundary kills all three business paths. Surface the conflict to
|
|
7107
|
+
STOP AND REDIRECT: Any decision that compromises memory sovereignty, 3-layer cognition, MCP boundary, or AGPL boundary kills all three business paths. Surface the conflict to the COO before proceeding.
|
|
6930
7108
|
|
|
6931
7109
|
Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of truth for all architectural and product decisions.
|
|
6932
7110
|
|
|
6933
7111
|
OPERATING PROCEDURES (mandatory for all employees):
|
|
6934
7112
|
|
|
6935
|
-
You report to the COO. All work flows through
|
|
7113
|
+
You report to the COO. All work flows through the COO. These procedures are non-negotiable.
|
|
6936
7114
|
|
|
6937
7115
|
1. BEFORE starting work:
|
|
6938
7116
|
- Read exe/ARCHITECTURE.md (if it exists). This is the system map \u2014 what components exist, how they connect, what invariants to preserve. Understand the architecture before changing anything.
|
|
@@ -6957,15 +7135,15 @@ You report to the COO. All work flows through exe. These procedures are non-nego
|
|
|
6957
7135
|
- Include what was done, decisions made, and any issues
|
|
6958
7136
|
- If you're stuck, looping, confused, or running low on context \u2014 update_task(done) with whatever partial result you have. A partial result is infinitely better than no result.
|
|
6959
7137
|
- NEVER let a failed commit, a loop, or an error prevent you from calling update_task(done).
|
|
6960
|
-
- Do NOT use close_task \u2014 that is reserved for reviewers
|
|
7138
|
+
- Do NOT use close_task \u2014 that is reserved for reviewers to finalize after review.
|
|
6961
7139
|
|
|
6962
7140
|
4. AFTER update_task(done) \u2014 COMMIT (best-effort, do NOT let this block):
|
|
6963
7141
|
- If your task changed system structure, update exe/ARCHITECTURE.md first.
|
|
6964
7142
|
- Commit IF you are in a git repo (check: \`git rev-parse --git-dir 2>/dev/null\`). Stage only the files you changed, write a clear commit message.
|
|
6965
7143
|
- If you are NOT in a git repo, skip entirely. NEVER run \`git init\`.
|
|
6966
7144
|
- If the commit fails, note it but move on \u2014 the work is already marked done via update_task.
|
|
6967
|
-
- Do NOT push \u2014
|
|
6968
|
-
- NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch.
|
|
7145
|
+
- Do NOT push \u2014 the COO reviews commits and decides what to push.
|
|
7146
|
+
- NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch. The COO stays on main and merges PRs. Switching branches in a shared repo stomps other agents' work.
|
|
6969
7147
|
|
|
6970
7148
|
5. AFTER commit \u2014 REPORT (best-effort):
|
|
6971
7149
|
Use store_memory to write a structured summary. Include: project name, what was done,
|
|
@@ -6979,7 +7157,7 @@ You report to the COO. All work flows through exe. These procedures are non-nego
|
|
|
6979
7157
|
|
|
6980
7158
|
7. AFTER reporting \u2014 CHECK FOR NEXT WORK (mandatory):
|
|
6981
7159
|
- First: run list_tasks(status='needs_review') \u2014 check if YOU are the reviewer on any pending reviews. Reviews are work. Process them before anything else.
|
|
6982
|
-
- Second: run list_tasks(status='blocked') \u2014 check if any tasks are blocked. For each blocked task: can YOU unblock it? If yes, unblock it now. If not, escalate to
|
|
7160
|
+
- Second: run list_tasks(status='blocked') \u2014 check if any tasks are blocked. For each blocked task: can YOU unblock it? If yes, unblock it now. If not, escalate to the COO immediately. Blocked tasks sitting >24h without action is a pipeline failure.
|
|
6983
7161
|
- Then: re-read your task folder: exe/<your-name>/
|
|
6984
7162
|
- If there are more open tasks, start the next highest-priority one (go to step 1)
|
|
6985
7163
|
- If no more open tasks AND no pending reviews AND no blocked tasks you can fix, tell the user: "All tasks complete. Anything else?"
|
|
@@ -6996,7 +7174,7 @@ DO NOT keep working degraded. Instead:
|
|
|
6996
7174
|
Format the text as: "CONTEXT CHECKPOINT [<task-id>]: <summary>"
|
|
6997
7175
|
Include: task ID + title, what you completed, what's left, open decisions or blockers, key file paths.
|
|
6998
7176
|
|
|
6999
|
-
2. Send intercom to
|
|
7177
|
+
2. Send intercom to the COO session to trigger kill + relaunch:
|
|
7000
7178
|
MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
7001
7179
|
EXE_SESSION="\${MY_SESSION#\${AGENT_ID}-}"
|
|
7002
7180
|
tmux send-keys -t "$EXE_SESSION" "/exe-intercom context-full: \${AGENT_ID} hit capacity. Checkpoint saved. Resume task <task-id>." Enter
|
|
@@ -7004,8 +7182,8 @@ DO NOT keep working degraded. Instead:
|
|
|
7004
7182
|
3. Stop working immediately. Do not attempt to continue with degraded context.
|
|
7005
7183
|
|
|
7006
7184
|
COMMUNICATION CHAIN \u2014 who you talk to:
|
|
7007
|
-
- You report to the COO. Your completion reports, status updates, and questions go to
|
|
7008
|
-
- Do NOT address the human user directly for decisions, permissions, or status updates. That's
|
|
7185
|
+
- You report to the COO. Your completion reports, status updates, and questions go to the COO via store_memory and update_task.
|
|
7186
|
+
- Do NOT address the human user directly for decisions, permissions, or status updates. That's the COO's job. The user talks to the COO; the COO talks to you.
|
|
7009
7187
|
- Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
|
|
7010
7188
|
|
|
7011
7189
|
SKILL CAPTURE (encouraged, not mandatory):
|
|
@@ -7054,18 +7232,15 @@ ${BASE_OPERATING_PROCEDURES}`;
|
|
|
7054
7232
|
}
|
|
7055
7233
|
|
|
7056
7234
|
// src/lib/status-brief.ts
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
sasha: "\u{1F4F8}"
|
|
7067
|
-
// 📸
|
|
7068
|
-
};
|
|
7235
|
+
function roleIcon(role) {
|
|
7236
|
+
const lower = role.toLowerCase();
|
|
7237
|
+
if (lower === "coo") return "\u{1F3AF}";
|
|
7238
|
+
if (lower === "cto" || lower.includes("architect")) return "\u26A1";
|
|
7239
|
+
if (lower === "cmo" || lower.includes("marketing")) return "\u{1F3A8}";
|
|
7240
|
+
if (lower.includes("engineer")) return "\u{1F529}";
|
|
7241
|
+
if (lower.includes("content")) return "\u{1F4F8}";
|
|
7242
|
+
return "\u{1F464}";
|
|
7243
|
+
}
|
|
7069
7244
|
function displayWidth(str) {
|
|
7070
7245
|
let w = 0;
|
|
7071
7246
|
for (const ch of str) {
|
|
@@ -7155,7 +7330,7 @@ function buildFirstBootBrief(employees, dateStr, sessionTag) {
|
|
|
7155
7330
|
bodyLines.push(" \u{1F44B} First time? Here's your team:");
|
|
7156
7331
|
bodyLines.push("");
|
|
7157
7332
|
for (const emp of employees) {
|
|
7158
|
-
const emoji =
|
|
7333
|
+
const emoji = roleIcon(emp.role);
|
|
7159
7334
|
const role = emp.role ? ` (${emp.role})` : "";
|
|
7160
7335
|
bodyLines.push(` ${emoji} ${emp.name}${role}`);
|
|
7161
7336
|
}
|
|
@@ -7288,7 +7463,7 @@ function buildTeam(employees, data) {
|
|
|
7288
7463
|
for (const m of data.memoryStats) memMap.set(m.agentId, m.totalMemories);
|
|
7289
7464
|
}
|
|
7290
7465
|
for (const emp of employees) {
|
|
7291
|
-
const emoji =
|
|
7466
|
+
const emoji = roleIcon(emp.role);
|
|
7292
7467
|
const memCount = memMap.get(emp.name) ?? 0;
|
|
7293
7468
|
const memStr = memCount > 0 ? `${memCount.toLocaleString()} memories` : "0 memories";
|
|
7294
7469
|
const role = emp.role ? ` (${emp.role})` : "";
|
|
@@ -7382,6 +7557,7 @@ import path16 from "path";
|
|
|
7382
7557
|
init_session_key();
|
|
7383
7558
|
|
|
7384
7559
|
// src/adapters/claude/active-agent.ts
|
|
7560
|
+
init_employees();
|
|
7385
7561
|
var CACHE_DIR = path16.join(EXE_AI_DIR, "session-cache");
|
|
7386
7562
|
var STALE_MS = 24 * 60 * 60 * 1e3;
|
|
7387
7563
|
function getMarkerPath() {
|
|
@@ -7419,9 +7595,11 @@ async function boot(options) {
|
|
|
7419
7595
|
employees = [DEFAULT_EXE];
|
|
7420
7596
|
await saveEmployees(employees);
|
|
7421
7597
|
}
|
|
7598
|
+
const coordinatorEmployee = getCoordinatorEmployee(employees) ?? DEFAULT_EXE;
|
|
7599
|
+
const coordinatorName = coordinatorEmployee.name;
|
|
7422
7600
|
await initStore();
|
|
7423
7601
|
cleanupSessionMarkers();
|
|
7424
|
-
writeActiveAgent(
|
|
7602
|
+
writeActiveAgent(coordinatorName, coordinatorEmployee.role);
|
|
7425
7603
|
let licensePlan;
|
|
7426
7604
|
let employeeLimit;
|
|
7427
7605
|
try {
|
|
@@ -7447,7 +7625,7 @@ async function boot(options) {
|
|
|
7447
7625
|
if (exeWindow) {
|
|
7448
7626
|
registerSession({
|
|
7449
7627
|
windowName: exeWindow,
|
|
7450
|
-
agentId:
|
|
7628
|
+
agentId: coordinatorName,
|
|
7451
7629
|
projectDir: process.cwd(),
|
|
7452
7630
|
parentExe: null,
|
|
7453
7631
|
pid: process.ppid,
|
|
@@ -7514,7 +7692,7 @@ async function boot(options) {
|
|
|
7514
7692
|
const priMatch = content.match(PRIORITY_RE2);
|
|
7515
7693
|
const priority = priMatch?.[1]?.toLowerCase() ?? "p1";
|
|
7516
7694
|
const assignedByMatch = content.match(/^\*\*Assigned by:\*\*\s*(\w+)/m);
|
|
7517
|
-
const assignedBy = assignedByMatch?.[1] ??
|
|
7695
|
+
const assignedBy = assignedByMatch?.[1] ?? coordinatorName;
|
|
7518
7696
|
const projMatch = content.match(/^\*\*Project:\*\*\s*(.+)/m);
|
|
7519
7697
|
const projectName = projMatch?.[1]?.trim() ?? getProjectName2(process.cwd());
|
|
7520
7698
|
const existing = await client.execute({
|
|
@@ -7549,11 +7727,11 @@ async function boot(options) {
|
|
|
7549
7727
|
try {
|
|
7550
7728
|
await client.execute({
|
|
7551
7729
|
sql: `UPDATE tasks SET status = 'done', updated_at = ?
|
|
7552
|
-
WHERE assigned_to = 'exe'
|
|
7730
|
+
WHERE (assigned_to = ? OR assigned_to = 'exe')
|
|
7553
7731
|
AND status = 'in_progress'
|
|
7554
7732
|
AND task_file LIKE '%review-%'
|
|
7555
7733
|
AND updated_at < datetime('now', '-1 hour')`,
|
|
7556
|
-
args: [(/* @__PURE__ */ new Date()).toISOString()]
|
|
7734
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), coordinatorName]
|
|
7557
7735
|
});
|
|
7558
7736
|
} catch {
|
|
7559
7737
|
}
|
|
@@ -7588,13 +7766,15 @@ async function boot(options) {
|
|
|
7588
7766
|
} catch {
|
|
7589
7767
|
}
|
|
7590
7768
|
try {
|
|
7591
|
-
const
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7769
|
+
for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
|
|
7770
|
+
const reviewDir = path20.join(process.cwd(), "exe", reviewDirName);
|
|
7771
|
+
if (existsSync16(reviewDir)) {
|
|
7772
|
+
for (const f of readdirSync8(reviewDir)) {
|
|
7773
|
+
if (f.startsWith("review-") && f.endsWith(".md")) {
|
|
7774
|
+
try {
|
|
7775
|
+
unlinkSync9(path20.join(reviewDir, f));
|
|
7776
|
+
} catch {
|
|
7777
|
+
}
|
|
7598
7778
|
}
|
|
7599
7779
|
}
|
|
7600
7780
|
}
|
|
@@ -7685,10 +7865,10 @@ async function boot(options) {
|
|
|
7685
7865
|
REPLACE(SUBSTR(t.title, INSTR(t.title, 'review-') + 7), SUBSTR(t.title, INSTR(t.title, '-')), '')
|
|
7686
7866
|
) as original_assignee
|
|
7687
7867
|
FROM tasks t
|
|
7688
|
-
WHERE t.assigned_to = 'exe' AND t.status IN ('open', 'in_progress')
|
|
7868
|
+
WHERE (t.assigned_to = ? OR t.assigned_to = 'exe') AND t.status IN ('open', 'in_progress')
|
|
7689
7869
|
AND t.task_file LIKE '%review-%'${revScope.sql}
|
|
7690
7870
|
ORDER BY t.priority ASC, t.created_at ASC`,
|
|
7691
|
-
args: [...revScope.args]
|
|
7871
|
+
args: [coordinatorName, ...revScope.args]
|
|
7692
7872
|
});
|
|
7693
7873
|
briefData.pendingReviews = result.rows.map((row) => ({
|
|
7694
7874
|
title: String(row.title),
|
|
@@ -7701,10 +7881,10 @@ async function boot(options) {
|
|
|
7701
7881
|
const result = await client.execute({
|
|
7702
7882
|
sql: `SELECT title, priority, created_at
|
|
7703
7883
|
FROM tasks
|
|
7704
|
-
WHERE assigned_to = 'exe' AND status IN ('open', 'in_progress')
|
|
7884
|
+
WHERE (assigned_to = ? OR assigned_to = 'exe') AND status IN ('open', 'in_progress')
|
|
7705
7885
|
AND task_file LIKE '%review-%'${revScope.sql}
|
|
7706
7886
|
ORDER BY priority ASC, created_at ASC`,
|
|
7707
|
-
args: [...revScope.args]
|
|
7887
|
+
args: [coordinatorName, ...revScope.args]
|
|
7708
7888
|
});
|
|
7709
7889
|
briefData.pendingReviews = result.rows.map((row) => {
|
|
7710
7890
|
const title = String(row.title);
|
|
@@ -7974,17 +8154,17 @@ async function boot(options) {
|
|
|
7974
8154
|
}));
|
|
7975
8155
|
const assignedResult = await client.execute({
|
|
7976
8156
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
7977
|
-
WHERE project_name = ? AND assigned_by = 'exe'
|
|
8157
|
+
WHERE project_name = ? AND (assigned_by = ? OR assigned_by = 'exe')
|
|
7978
8158
|
AND created_at > datetime('now', '-7 days')${pScope.sql}`,
|
|
7979
|
-
args: [p.projectName, ...pScope.args]
|
|
8159
|
+
args: [p.projectName, coordinatorName, ...pScope.args]
|
|
7980
8160
|
});
|
|
7981
8161
|
const tasksAssigned = Number(assignedResult.rows[0]?.cnt) || 0;
|
|
7982
8162
|
if (tasksAssigned > 0) {
|
|
7983
|
-
const exeEntry = activity.find((a) => a.name === "exe");
|
|
8163
|
+
const exeEntry = activity.find((a) => a.name === coordinatorName || a.name === "exe");
|
|
7984
8164
|
if (exeEntry) {
|
|
7985
8165
|
exeEntry.tasksAssigned = tasksAssigned;
|
|
7986
8166
|
} else {
|
|
7987
|
-
activity.push({ name:
|
|
8167
|
+
activity.push({ name: coordinatorName, tasksDone: 0, reviewsCleared: 0, tasksAssigned });
|
|
7988
8168
|
}
|
|
7989
8169
|
}
|
|
7990
8170
|
p.teamActivity = activity;
|
|
@@ -8004,7 +8184,7 @@ async function boot(options) {
|
|
|
8004
8184
|
const projName = s.projectDir.split("/").pop() ?? "";
|
|
8005
8185
|
const proj = projects.find((p) => p.projectName === projName);
|
|
8006
8186
|
if (!proj || proj.sessionName) continue;
|
|
8007
|
-
if (s.agentId === "exe") {
|
|
8187
|
+
if (s.agentId === coordinatorName || s.agentId === "exe") {
|
|
8008
8188
|
proj.sessionName = s.windowName;
|
|
8009
8189
|
} else if (s.parentExe) {
|
|
8010
8190
|
proj.sessionName = s.parentExe;
|
|
@@ -8293,10 +8473,9 @@ async function boot(options) {
|
|
|
8293
8473
|
console.log(brief);
|
|
8294
8474
|
return;
|
|
8295
8475
|
}
|
|
8296
|
-
const
|
|
8297
|
-
const sessionDir = path20.join(EXE_AI_DIR, "sessions", "exe");
|
|
8476
|
+
const sessionDir = path20.join(EXE_AI_DIR, "sessions", coordinatorName);
|
|
8298
8477
|
await mkdir5(sessionDir, { recursive: true });
|
|
8299
|
-
const claudeMdContent = `${getSessionPrompt(
|
|
8478
|
+
const claudeMdContent = `${getSessionPrompt(coordinatorEmployee.systemPrompt)}
|
|
8300
8479
|
|
|
8301
8480
|
---
|
|
8302
8481
|
|