@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/tui/App.js
CHANGED
|
@@ -410,7 +410,7 @@ function wrapWithRetry(client) {
|
|
|
410
410
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
411
411
|
}
|
|
412
412
|
if (prop === "batch") {
|
|
413
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
413
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
414
414
|
}
|
|
415
415
|
return Reflect.get(target, prop, receiver);
|
|
416
416
|
}
|
|
@@ -426,361 +426,801 @@ var init_db_retry = __esm({
|
|
|
426
426
|
}
|
|
427
427
|
});
|
|
428
428
|
|
|
429
|
-
// src/lib/
|
|
430
|
-
var
|
|
431
|
-
__export(
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
429
|
+
// src/lib/config.ts
|
|
430
|
+
var config_exports = {};
|
|
431
|
+
__export(config_exports, {
|
|
432
|
+
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
433
|
+
CONFIG_PATH: () => CONFIG_PATH,
|
|
434
|
+
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
435
|
+
DB_PATH: () => DB_PATH,
|
|
436
|
+
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
437
|
+
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
438
|
+
MODELS_DIR: () => MODELS_DIR,
|
|
439
|
+
loadConfig: () => loadConfig,
|
|
440
|
+
loadConfigFrom: () => loadConfigFrom,
|
|
441
|
+
loadConfigSync: () => loadConfigSync,
|
|
442
|
+
migrateConfig: () => migrateConfig,
|
|
443
|
+
saveConfig: () => saveConfig
|
|
440
444
|
});
|
|
441
|
-
import {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
445
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
446
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4, renameSync as renameSync2 } from "fs";
|
|
447
|
+
import path3 from "path";
|
|
448
|
+
import os3 from "os";
|
|
449
|
+
function resolveDataDir() {
|
|
450
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
451
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
452
|
+
const newDir = path3.join(os3.homedir(), ".exe-os");
|
|
453
|
+
const legacyDir = path3.join(os3.homedir(), ".exe-mem");
|
|
454
|
+
if (!existsSync4(newDir) && existsSync4(legacyDir)) {
|
|
455
|
+
try {
|
|
456
|
+
renameSync2(legacyDir, newDir);
|
|
457
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
458
|
+
`);
|
|
459
|
+
} catch {
|
|
460
|
+
return legacyDir;
|
|
461
|
+
}
|
|
453
462
|
}
|
|
454
|
-
|
|
455
|
-
_resilientClient = wrapWithRetry(_client);
|
|
463
|
+
return newDir;
|
|
456
464
|
}
|
|
457
|
-
function
|
|
458
|
-
|
|
465
|
+
function migrateLegacyConfig(raw) {
|
|
466
|
+
if ("r2" in raw) {
|
|
467
|
+
process.stderr.write(
|
|
468
|
+
"[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
|
|
469
|
+
);
|
|
470
|
+
delete raw.r2;
|
|
471
|
+
}
|
|
472
|
+
if ("syncIntervalMs" in raw) {
|
|
473
|
+
delete raw.syncIntervalMs;
|
|
474
|
+
}
|
|
475
|
+
return raw;
|
|
459
476
|
}
|
|
460
|
-
function
|
|
461
|
-
|
|
462
|
-
|
|
477
|
+
function migrateConfig(raw) {
|
|
478
|
+
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
479
|
+
let currentVersion = fromVersion;
|
|
480
|
+
let migrated = false;
|
|
481
|
+
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
482
|
+
return { config: raw, migrated: false, fromVersion };
|
|
463
483
|
}
|
|
464
|
-
|
|
484
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
485
|
+
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
486
|
+
raw = migration.migrate(raw);
|
|
487
|
+
currentVersion = migration.to;
|
|
488
|
+
migrated = true;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return { config: raw, migrated, fromVersion };
|
|
465
492
|
}
|
|
466
|
-
function
|
|
467
|
-
|
|
468
|
-
|
|
493
|
+
function normalizeScalingRoadmap(raw) {
|
|
494
|
+
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
495
|
+
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
496
|
+
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
497
|
+
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
498
|
+
userAuto.enabled = raw.rerankerEnabled;
|
|
469
499
|
}
|
|
470
|
-
|
|
500
|
+
raw.scalingRoadmap = {
|
|
501
|
+
...userRoadmap,
|
|
502
|
+
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
503
|
+
};
|
|
471
504
|
}
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
505
|
+
function normalizeSessionLifecycle(raw) {
|
|
506
|
+
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
507
|
+
const userSL = raw.sessionLifecycle ?? {};
|
|
508
|
+
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
509
|
+
}
|
|
510
|
+
function normalizeAutoUpdate(raw) {
|
|
511
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
512
|
+
const userAU = raw.autoUpdate ?? {};
|
|
513
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
514
|
+
}
|
|
515
|
+
async function loadConfig() {
|
|
516
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
517
|
+
await mkdir(dir, { recursive: true });
|
|
518
|
+
const configPath = path3.join(dir, "config.json");
|
|
519
|
+
if (!existsSync4(configPath)) {
|
|
520
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
480
521
|
}
|
|
481
|
-
await
|
|
482
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
483
|
-
id TEXT PRIMARY KEY,
|
|
484
|
-
agent_id TEXT NOT NULL,
|
|
485
|
-
agent_role TEXT NOT NULL,
|
|
486
|
-
session_id TEXT NOT NULL,
|
|
487
|
-
timestamp TEXT NOT NULL,
|
|
488
|
-
tool_name TEXT NOT NULL,
|
|
489
|
-
project_name TEXT NOT NULL,
|
|
490
|
-
has_error INTEGER NOT NULL DEFAULT 0,
|
|
491
|
-
raw_text TEXT NOT NULL,
|
|
492
|
-
vector F32_BLOB(1024),
|
|
493
|
-
version INTEGER NOT NULL DEFAULT 0
|
|
494
|
-
);
|
|
495
|
-
|
|
496
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
497
|
-
ON memories(agent_id);
|
|
498
|
-
|
|
499
|
-
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
500
|
-
ON memories(timestamp);
|
|
501
|
-
|
|
502
|
-
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
503
|
-
ON memories(session_id);
|
|
504
|
-
|
|
505
|
-
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
506
|
-
ON memories(project_name);
|
|
507
|
-
|
|
508
|
-
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
509
|
-
ON memories(tool_name);
|
|
510
|
-
|
|
511
|
-
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
512
|
-
ON memories(version);
|
|
513
|
-
|
|
514
|
-
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
515
|
-
ON memories(agent_id, project_name);
|
|
516
|
-
`);
|
|
517
|
-
await client.executeMultiple(`
|
|
518
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
519
|
-
raw_text,
|
|
520
|
-
content='memories',
|
|
521
|
-
content_rowid='rowid'
|
|
522
|
-
);
|
|
523
|
-
|
|
524
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
525
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
526
|
-
END;
|
|
527
|
-
|
|
528
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
529
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
530
|
-
END;
|
|
531
|
-
|
|
532
|
-
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
533
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
534
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
535
|
-
END;
|
|
536
|
-
`);
|
|
537
|
-
await client.executeMultiple(`
|
|
538
|
-
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
539
|
-
key TEXT PRIMARY KEY,
|
|
540
|
-
value TEXT NOT NULL
|
|
541
|
-
);
|
|
542
|
-
`);
|
|
543
|
-
await client.executeMultiple(`
|
|
544
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
545
|
-
id TEXT PRIMARY KEY,
|
|
546
|
-
title TEXT NOT NULL,
|
|
547
|
-
assigned_to TEXT NOT NULL,
|
|
548
|
-
assigned_by TEXT NOT NULL,
|
|
549
|
-
project_name TEXT NOT NULL,
|
|
550
|
-
priority TEXT NOT NULL DEFAULT 'p1',
|
|
551
|
-
status TEXT NOT NULL DEFAULT 'open',
|
|
552
|
-
task_file TEXT,
|
|
553
|
-
created_at TEXT NOT NULL,
|
|
554
|
-
updated_at TEXT NOT NULL
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
558
|
-
ON tasks(assigned_to, status);
|
|
559
|
-
`);
|
|
560
|
-
await client.executeMultiple(`
|
|
561
|
-
CREATE TABLE IF NOT EXISTS behaviors (
|
|
562
|
-
id TEXT PRIMARY KEY,
|
|
563
|
-
agent_id TEXT NOT NULL,
|
|
564
|
-
project_name TEXT,
|
|
565
|
-
domain TEXT,
|
|
566
|
-
content TEXT NOT NULL,
|
|
567
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
568
|
-
created_at TEXT NOT NULL,
|
|
569
|
-
updated_at TEXT NOT NULL
|
|
570
|
-
);
|
|
571
|
-
|
|
572
|
-
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
573
|
-
ON behaviors(agent_id, active);
|
|
574
|
-
`);
|
|
522
|
+
const raw = await readFile(configPath, "utf-8");
|
|
575
523
|
try {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
524
|
+
let parsed = JSON.parse(raw);
|
|
525
|
+
parsed = migrateLegacyConfig(parsed);
|
|
526
|
+
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
527
|
+
if (migrated) {
|
|
528
|
+
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
529
|
+
`);
|
|
530
|
+
try {
|
|
531
|
+
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
532
|
+
} catch {
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
536
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
537
|
+
normalizeAutoUpdate(migratedCfg);
|
|
538
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
539
|
+
if (config.dbPath.startsWith("~")) {
|
|
540
|
+
config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
|
|
592
541
|
}
|
|
542
|
+
return config;
|
|
593
543
|
} catch {
|
|
544
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function loadConfigSync() {
|
|
548
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
549
|
+
const configPath = path3.join(dir, "config.json");
|
|
550
|
+
if (!existsSync4(configPath)) {
|
|
551
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
594
552
|
}
|
|
595
553
|
try {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
605
|
-
args: []
|
|
606
|
-
});
|
|
554
|
+
const raw = readFileSync4(configPath, "utf-8");
|
|
555
|
+
let parsed = JSON.parse(raw);
|
|
556
|
+
parsed = migrateLegacyConfig(parsed);
|
|
557
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
558
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
559
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
560
|
+
normalizeAutoUpdate(migratedCfg);
|
|
561
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
607
562
|
} catch {
|
|
563
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
608
564
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
565
|
+
}
|
|
566
|
+
async function saveConfig(config) {
|
|
567
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
568
|
+
await mkdir(dir, { recursive: true });
|
|
569
|
+
const configPath = path3.join(dir, "config.json");
|
|
570
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
571
|
+
if (config.cloud?.apiKey) {
|
|
572
|
+
await chmod(configPath, 384);
|
|
615
573
|
}
|
|
574
|
+
}
|
|
575
|
+
async function loadConfigFrom(configPath) {
|
|
576
|
+
const raw = await readFile(configPath, "utf-8");
|
|
616
577
|
try {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
578
|
+
let parsed = JSON.parse(raw);
|
|
579
|
+
parsed = migrateLegacyConfig(parsed);
|
|
580
|
+
const { config: migratedCfg } = migrateConfig(parsed);
|
|
581
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
582
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
583
|
+
normalizeAutoUpdate(migratedCfg);
|
|
584
|
+
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
623
585
|
} catch {
|
|
586
|
+
return { ...DEFAULT_CONFIG };
|
|
624
587
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
588
|
+
}
|
|
589
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
590
|
+
var init_config = __esm({
|
|
591
|
+
"src/lib/config.ts"() {
|
|
592
|
+
"use strict";
|
|
593
|
+
EXE_AI_DIR = resolveDataDir();
|
|
594
|
+
DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
|
|
595
|
+
MODELS_DIR = path3.join(EXE_AI_DIR, "models");
|
|
596
|
+
CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
|
|
597
|
+
LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
|
|
598
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
599
|
+
DEFAULT_CONFIG = {
|
|
600
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
601
|
+
dbPath: DB_PATH,
|
|
602
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
603
|
+
embeddingDim: 1024,
|
|
604
|
+
batchSize: 20,
|
|
605
|
+
flushIntervalMs: 1e4,
|
|
606
|
+
autoIngestion: true,
|
|
607
|
+
autoRetrieval: true,
|
|
608
|
+
searchMode: "hybrid",
|
|
609
|
+
hookSearchMode: "hybrid",
|
|
610
|
+
fileGrepEnabled: true,
|
|
611
|
+
splashEffect: true,
|
|
612
|
+
consolidationEnabled: true,
|
|
613
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
614
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
615
|
+
consolidationMaxCallsPerRun: 20,
|
|
616
|
+
selfQueryRouter: true,
|
|
617
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
618
|
+
rerankerEnabled: true,
|
|
619
|
+
scalingRoadmap: {
|
|
620
|
+
rerankerAutoTrigger: {
|
|
621
|
+
enabled: true,
|
|
622
|
+
broadQueryMinCardinality: 5e4,
|
|
623
|
+
fetchTopK: 150,
|
|
624
|
+
returnTopK: 5
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
graphRagEnabled: true,
|
|
628
|
+
wikiEnabled: false,
|
|
629
|
+
wikiUrl: "",
|
|
630
|
+
wikiApiKey: "",
|
|
631
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
632
|
+
wikiWorkspaceMapping: {},
|
|
633
|
+
wikiAutoUpdate: true,
|
|
634
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
635
|
+
wikiAutoUpdateCreateNew: true,
|
|
636
|
+
skillLearning: true,
|
|
637
|
+
skillThreshold: 3,
|
|
638
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
639
|
+
exeHeartbeat: {
|
|
640
|
+
enabled: true,
|
|
641
|
+
intervalSeconds: 60,
|
|
642
|
+
staleInProgressThresholdHours: 2
|
|
643
|
+
},
|
|
644
|
+
sessionLifecycle: {
|
|
645
|
+
idleKillEnabled: true,
|
|
646
|
+
idleKillTicksRequired: 3,
|
|
647
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
648
|
+
maxAutoInstances: 10
|
|
649
|
+
},
|
|
650
|
+
autoUpdate: {
|
|
651
|
+
checkOnBoot: true,
|
|
652
|
+
autoInstall: false,
|
|
653
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
CONFIG_MIGRATIONS = [
|
|
657
|
+
{
|
|
658
|
+
from: 0,
|
|
659
|
+
to: 1,
|
|
660
|
+
migrate: (cfg) => {
|
|
661
|
+
cfg.config_version = 1;
|
|
662
|
+
return cfg;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
];
|
|
631
666
|
}
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
// src/lib/employees.ts
|
|
670
|
+
var employees_exports = {};
|
|
671
|
+
__export(employees_exports, {
|
|
672
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
673
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
674
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
675
|
+
addEmployee: () => addEmployee,
|
|
676
|
+
canCoordinate: () => canCoordinate,
|
|
677
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
678
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
679
|
+
getEmployee: () => getEmployee,
|
|
680
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
681
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
682
|
+
hasRole: () => hasRole,
|
|
683
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
684
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
685
|
+
isMultiInstance: () => isMultiInstance,
|
|
686
|
+
loadEmployees: () => loadEmployees,
|
|
687
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
688
|
+
normalizeRole: () => normalizeRole,
|
|
689
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
690
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
691
|
+
saveEmployees: () => saveEmployees,
|
|
692
|
+
validateEmployeeName: () => validateEmployeeName
|
|
693
|
+
});
|
|
694
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
695
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
696
|
+
import { execSync as execSync4 } from "child_process";
|
|
697
|
+
import path4 from "path";
|
|
698
|
+
import os4 from "os";
|
|
699
|
+
function normalizeRole(role) {
|
|
700
|
+
return (role ?? "").trim().toLowerCase();
|
|
701
|
+
}
|
|
702
|
+
function isCoordinatorRole(role) {
|
|
703
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
704
|
+
}
|
|
705
|
+
function getCoordinatorEmployee(employees) {
|
|
706
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
707
|
+
}
|
|
708
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
709
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
710
|
+
}
|
|
711
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
712
|
+
if (!agentName) return false;
|
|
713
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
714
|
+
}
|
|
715
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
716
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
717
|
+
}
|
|
718
|
+
function validateEmployeeName(name) {
|
|
719
|
+
if (!name) {
|
|
720
|
+
return { valid: false, error: "Name is required" };
|
|
638
721
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
642
|
-
args: []
|
|
643
|
-
});
|
|
644
|
-
} catch {
|
|
722
|
+
if (name.length > 32) {
|
|
723
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
645
724
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
} catch {
|
|
725
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
726
|
+
return {
|
|
727
|
+
valid: false,
|
|
728
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
729
|
+
};
|
|
652
730
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
} catch {
|
|
731
|
+
return { valid: true };
|
|
732
|
+
}
|
|
733
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
734
|
+
if (!existsSync5(employeesPath)) {
|
|
735
|
+
return [];
|
|
659
736
|
}
|
|
737
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
660
738
|
try {
|
|
661
|
-
|
|
662
|
-
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
663
|
-
args: []
|
|
664
|
-
});
|
|
739
|
+
return JSON.parse(raw);
|
|
665
740
|
} catch {
|
|
741
|
+
return [];
|
|
666
742
|
}
|
|
743
|
+
}
|
|
744
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
745
|
+
await mkdir2(path4.dirname(employeesPath), { recursive: true });
|
|
746
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
747
|
+
}
|
|
748
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
749
|
+
if (!existsSync5(employeesPath)) return [];
|
|
667
750
|
try {
|
|
668
|
-
|
|
669
|
-
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
670
|
-
args: []
|
|
671
|
-
});
|
|
751
|
+
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
672
752
|
} catch {
|
|
753
|
+
return [];
|
|
673
754
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
755
|
+
}
|
|
756
|
+
function getEmployee(employees, name) {
|
|
757
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
758
|
+
}
|
|
759
|
+
function getEmployeeByRole(employees, role) {
|
|
760
|
+
const lower = role.toLowerCase();
|
|
761
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
762
|
+
}
|
|
763
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
764
|
+
const lower = role.toLowerCase();
|
|
765
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
766
|
+
}
|
|
767
|
+
function hasRole(agentName, role) {
|
|
768
|
+
const employees = loadEmployeesSync();
|
|
769
|
+
const emp = getEmployee(employees, agentName);
|
|
770
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
771
|
+
}
|
|
772
|
+
function isMultiInstance(agentName, employees) {
|
|
773
|
+
const roster = employees ?? loadEmployeesSync();
|
|
774
|
+
const emp = getEmployee(roster, agentName);
|
|
775
|
+
if (!emp) return false;
|
|
776
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
777
|
+
}
|
|
778
|
+
function addEmployee(employees, employee) {
|
|
779
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
780
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
781
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
680
782
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
783
|
+
return [...employees, normalized];
|
|
784
|
+
}
|
|
785
|
+
async function normalizeRosterCase(rosterPath) {
|
|
786
|
+
const employees = await loadEmployees(rosterPath);
|
|
787
|
+
let changed = false;
|
|
788
|
+
for (const emp of employees) {
|
|
789
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
790
|
+
const oldName = emp.name;
|
|
791
|
+
emp.name = emp.name.toLowerCase();
|
|
792
|
+
changed = true;
|
|
793
|
+
try {
|
|
794
|
+
const identityDir = path4.join(os4.homedir(), ".exe-os", "identity");
|
|
795
|
+
const oldPath = path4.join(identityDir, `${oldName}.md`);
|
|
796
|
+
const newPath = path4.join(identityDir, `${emp.name}.md`);
|
|
797
|
+
if (existsSync5(oldPath) && !existsSync5(newPath)) {
|
|
798
|
+
renameSync3(oldPath, newPath);
|
|
799
|
+
} else if (existsSync5(oldPath) && oldPath !== newPath) {
|
|
800
|
+
const content = readFileSync5(oldPath, "utf-8");
|
|
801
|
+
writeFileSync3(newPath, content, "utf-8");
|
|
802
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
803
|
+
unlinkSync(oldPath);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} catch {
|
|
807
|
+
}
|
|
808
|
+
}
|
|
687
809
|
}
|
|
688
|
-
|
|
689
|
-
await
|
|
690
|
-
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
691
|
-
args: []
|
|
692
|
-
});
|
|
693
|
-
} catch {
|
|
810
|
+
if (changed) {
|
|
811
|
+
await saveEmployees(employees, rosterPath);
|
|
694
812
|
}
|
|
813
|
+
return changed;
|
|
814
|
+
}
|
|
815
|
+
function findExeBin() {
|
|
695
816
|
try {
|
|
696
|
-
|
|
697
|
-
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
698
|
-
args: []
|
|
699
|
-
});
|
|
817
|
+
return execSync4(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
700
818
|
} catch {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function registerBinSymlinks(name) {
|
|
823
|
+
const created = [];
|
|
824
|
+
const skipped = [];
|
|
825
|
+
const errors = [];
|
|
826
|
+
const exeBinPath = findExeBin();
|
|
827
|
+
if (!exeBinPath) {
|
|
828
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
829
|
+
return { created, skipped, errors };
|
|
701
830
|
}
|
|
831
|
+
const binDir = path4.dirname(exeBinPath);
|
|
832
|
+
let target;
|
|
702
833
|
try {
|
|
703
|
-
|
|
704
|
-
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
705
|
-
args: []
|
|
706
|
-
});
|
|
834
|
+
target = readlinkSync(exeBinPath);
|
|
707
835
|
} catch {
|
|
836
|
+
errors.push("Could not read 'exe' symlink");
|
|
837
|
+
return { created, skipped, errors };
|
|
838
|
+
}
|
|
839
|
+
for (const suffix of ["", "-opencode"]) {
|
|
840
|
+
const linkName = `${name}${suffix}`;
|
|
841
|
+
const linkPath = path4.join(binDir, linkName);
|
|
842
|
+
if (existsSync5(linkPath)) {
|
|
843
|
+
skipped.push(linkName);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
try {
|
|
847
|
+
symlinkSync(target, linkPath);
|
|
848
|
+
created.push(linkName);
|
|
849
|
+
} catch (err) {
|
|
850
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return { created, skipped, errors };
|
|
854
|
+
}
|
|
855
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
856
|
+
var init_employees = __esm({
|
|
857
|
+
"src/lib/employees.ts"() {
|
|
858
|
+
"use strict";
|
|
859
|
+
init_config();
|
|
860
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
861
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
862
|
+
COORDINATOR_ROLE = "COO";
|
|
863
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
// src/lib/database.ts
|
|
868
|
+
var database_exports = {};
|
|
869
|
+
__export(database_exports, {
|
|
870
|
+
disposeDatabase: () => disposeDatabase,
|
|
871
|
+
disposeTurso: () => disposeTurso,
|
|
872
|
+
ensureSchema: () => ensureSchema,
|
|
873
|
+
getClient: () => getClient,
|
|
874
|
+
getRawClient: () => getRawClient,
|
|
875
|
+
initDatabase: () => initDatabase,
|
|
876
|
+
initTurso: () => initTurso,
|
|
877
|
+
isInitialized: () => isInitialized
|
|
878
|
+
});
|
|
879
|
+
import { createClient } from "@libsql/client";
|
|
880
|
+
async function initDatabase(config) {
|
|
881
|
+
if (_client) {
|
|
882
|
+
_client.close();
|
|
883
|
+
_client = null;
|
|
884
|
+
_resilientClient = null;
|
|
885
|
+
}
|
|
886
|
+
const opts = {
|
|
887
|
+
url: `file:${config.dbPath}`
|
|
888
|
+
};
|
|
889
|
+
if (config.encryptionKey) {
|
|
890
|
+
opts.encryptionKey = config.encryptionKey;
|
|
891
|
+
}
|
|
892
|
+
_client = createClient(opts);
|
|
893
|
+
_resilientClient = wrapWithRetry(_client);
|
|
894
|
+
}
|
|
895
|
+
function isInitialized() {
|
|
896
|
+
return _client !== null;
|
|
897
|
+
}
|
|
898
|
+
function getClient() {
|
|
899
|
+
if (!_resilientClient) {
|
|
900
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
901
|
+
}
|
|
902
|
+
return _resilientClient;
|
|
903
|
+
}
|
|
904
|
+
function getRawClient() {
|
|
905
|
+
if (!_client) {
|
|
906
|
+
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
708
907
|
}
|
|
908
|
+
return _client;
|
|
909
|
+
}
|
|
910
|
+
async function ensureSchema() {
|
|
911
|
+
const client = getRawClient();
|
|
912
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
913
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
914
|
+
await client.execute("PRAGMA wal_autocheckpoint = 1000");
|
|
709
915
|
try {
|
|
710
|
-
await client.execute(
|
|
711
|
-
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
712
|
-
args: []
|
|
713
|
-
});
|
|
916
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
714
917
|
} catch {
|
|
715
918
|
}
|
|
716
919
|
await client.executeMultiple(`
|
|
717
|
-
CREATE TABLE IF NOT EXISTS
|
|
718
|
-
id
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
920
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
921
|
+
id TEXT PRIMARY KEY,
|
|
922
|
+
agent_id TEXT NOT NULL,
|
|
923
|
+
agent_role TEXT NOT NULL,
|
|
924
|
+
session_id TEXT NOT NULL,
|
|
925
|
+
timestamp TEXT NOT NULL,
|
|
926
|
+
tool_name TEXT NOT NULL,
|
|
927
|
+
project_name TEXT NOT NULL,
|
|
928
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
929
|
+
raw_text TEXT NOT NULL,
|
|
930
|
+
vector F32_BLOB(1024),
|
|
931
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
722
932
|
);
|
|
723
933
|
|
|
724
|
-
CREATE INDEX IF NOT EXISTS
|
|
725
|
-
ON
|
|
934
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent
|
|
935
|
+
ON memories(agent_id);
|
|
726
936
|
|
|
727
|
-
CREATE INDEX IF NOT EXISTS
|
|
728
|
-
ON
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
937
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp
|
|
938
|
+
ON memories(timestamp);
|
|
939
|
+
|
|
940
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session
|
|
941
|
+
ON memories(session_id);
|
|
942
|
+
|
|
943
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project
|
|
944
|
+
ON memories(project_name);
|
|
945
|
+
|
|
946
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tool
|
|
947
|
+
ON memories(tool_name);
|
|
948
|
+
|
|
949
|
+
CREATE INDEX IF NOT EXISTS idx_memories_version
|
|
950
|
+
ON memories(version);
|
|
951
|
+
|
|
952
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project
|
|
953
|
+
ON memories(agent_id, project_name);
|
|
738
954
|
`);
|
|
739
955
|
await client.executeMultiple(`
|
|
740
|
-
CREATE TABLE IF NOT EXISTS
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
event TEXT NOT NULL,
|
|
745
|
-
project TEXT NOT NULL,
|
|
746
|
-
summary TEXT NOT NULL,
|
|
747
|
-
task_file TEXT,
|
|
748
|
-
read INTEGER NOT NULL DEFAULT 0,
|
|
749
|
-
created_at TEXT NOT NULL
|
|
956
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
957
|
+
raw_text,
|
|
958
|
+
content='memories',
|
|
959
|
+
content_rowid='rowid'
|
|
750
960
|
);
|
|
751
961
|
|
|
752
|
-
CREATE
|
|
753
|
-
|
|
962
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
963
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
964
|
+
END;
|
|
754
965
|
|
|
755
|
-
CREATE
|
|
756
|
-
|
|
966
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
967
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
968
|
+
END;
|
|
757
969
|
|
|
758
|
-
CREATE
|
|
759
|
-
|
|
970
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
971
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
972
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
973
|
+
END;
|
|
760
974
|
`);
|
|
761
975
|
await client.executeMultiple(`
|
|
762
|
-
CREATE TABLE IF NOT EXISTS
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
description TEXT NOT NULL,
|
|
766
|
-
job_type TEXT NOT NULL DEFAULT 'report',
|
|
767
|
-
prompt TEXT,
|
|
768
|
-
assigned_to TEXT,
|
|
769
|
-
project_name TEXT,
|
|
770
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
771
|
-
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
772
|
-
created_at TEXT NOT NULL
|
|
976
|
+
CREATE TABLE IF NOT EXISTS sync_meta (
|
|
977
|
+
key TEXT PRIMARY KEY,
|
|
978
|
+
value TEXT NOT NULL
|
|
773
979
|
);
|
|
774
980
|
`);
|
|
775
981
|
await client.executeMultiple(`
|
|
776
|
-
CREATE TABLE IF NOT EXISTS
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
982
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
983
|
+
id TEXT PRIMARY KEY,
|
|
984
|
+
title TEXT NOT NULL,
|
|
985
|
+
assigned_to TEXT NOT NULL,
|
|
986
|
+
assigned_by TEXT NOT NULL,
|
|
987
|
+
project_name TEXT NOT NULL,
|
|
988
|
+
priority TEXT NOT NULL DEFAULT 'p1',
|
|
989
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
990
|
+
task_file TEXT,
|
|
991
|
+
created_at TEXT NOT NULL,
|
|
992
|
+
updated_at TEXT NOT NULL
|
|
993
|
+
);
|
|
994
|
+
|
|
995
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee_status
|
|
996
|
+
ON tasks(assigned_to, status);
|
|
997
|
+
`);
|
|
998
|
+
await client.executeMultiple(`
|
|
999
|
+
CREATE TABLE IF NOT EXISTS behaviors (
|
|
1000
|
+
id TEXT PRIMARY KEY,
|
|
1001
|
+
agent_id TEXT NOT NULL,
|
|
1002
|
+
project_name TEXT,
|
|
1003
|
+
domain TEXT,
|
|
1004
|
+
content TEXT NOT NULL,
|
|
1005
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1006
|
+
created_at TEXT NOT NULL,
|
|
1007
|
+
updated_at TEXT NOT NULL
|
|
1008
|
+
);
|
|
1009
|
+
|
|
1010
|
+
CREATE INDEX IF NOT EXISTS idx_behaviors_agent
|
|
1011
|
+
ON behaviors(agent_id, active);
|
|
1012
|
+
`);
|
|
1013
|
+
try {
|
|
1014
|
+
const coordinatorName = getCoordinatorName();
|
|
1015
|
+
const existing = await client.execute({
|
|
1016
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
1017
|
+
args: [coordinatorName]
|
|
1018
|
+
});
|
|
1019
|
+
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
1020
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
1021
|
+
for (const [domain, content] of [
|
|
1022
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
1023
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
1024
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
1025
|
+
]) {
|
|
1026
|
+
await client.execute({
|
|
1027
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
1028
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
1029
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
1030
|
+
});
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
try {
|
|
1036
|
+
await client.execute({
|
|
1037
|
+
sql: `ALTER TABLE behaviors ADD COLUMN priority TEXT DEFAULT 'p1'`,
|
|
1038
|
+
args: []
|
|
1039
|
+
});
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
try {
|
|
1043
|
+
await client.execute({
|
|
1044
|
+
sql: `ALTER TABLE tasks ADD COLUMN blocked_by TEXT`,
|
|
1045
|
+
args: []
|
|
1046
|
+
});
|
|
1047
|
+
} catch {
|
|
1048
|
+
}
|
|
1049
|
+
try {
|
|
1050
|
+
await client.execute({
|
|
1051
|
+
sql: `ALTER TABLE tasks ADD COLUMN parent_task_id TEXT`,
|
|
1052
|
+
args: []
|
|
1053
|
+
});
|
|
1054
|
+
} catch {
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
await client.execute({
|
|
1058
|
+
sql: `CREATE INDEX IF NOT EXISTS idx_tasks_parent_task_id
|
|
1059
|
+
ON tasks(parent_task_id)
|
|
1060
|
+
WHERE parent_task_id IS NOT NULL`,
|
|
1061
|
+
args: []
|
|
1062
|
+
});
|
|
1063
|
+
} catch {
|
|
1064
|
+
}
|
|
1065
|
+
try {
|
|
1066
|
+
await client.execute({
|
|
1067
|
+
sql: `UPDATE tasks SET status = 'done' WHERE status = 'completed'`,
|
|
1068
|
+
args: []
|
|
1069
|
+
});
|
|
1070
|
+
} catch {
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
await client.execute({
|
|
1074
|
+
sql: `ALTER TABLE tasks ADD COLUMN reviewer TEXT`,
|
|
1075
|
+
args: []
|
|
1076
|
+
});
|
|
1077
|
+
} catch {
|
|
1078
|
+
}
|
|
1079
|
+
try {
|
|
1080
|
+
await client.execute({
|
|
1081
|
+
sql: `ALTER TABLE tasks ADD COLUMN context TEXT`,
|
|
1082
|
+
args: []
|
|
1083
|
+
});
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
try {
|
|
1087
|
+
await client.execute({
|
|
1088
|
+
sql: `ALTER TABLE tasks ADD COLUMN result TEXT`,
|
|
1089
|
+
args: []
|
|
1090
|
+
});
|
|
1091
|
+
} catch {
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
await client.execute({
|
|
1095
|
+
sql: `ALTER TABLE tasks ADD COLUMN assigned_tmux TEXT`,
|
|
1096
|
+
args: []
|
|
1097
|
+
});
|
|
1098
|
+
} catch {
|
|
1099
|
+
}
|
|
1100
|
+
try {
|
|
1101
|
+
await client.execute({
|
|
1102
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
1103
|
+
args: []
|
|
1104
|
+
});
|
|
1105
|
+
} catch {
|
|
1106
|
+
}
|
|
1107
|
+
try {
|
|
1108
|
+
await client.execute({
|
|
1109
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
1110
|
+
args: []
|
|
1111
|
+
});
|
|
1112
|
+
} catch {
|
|
1113
|
+
}
|
|
1114
|
+
try {
|
|
1115
|
+
await client.execute({
|
|
1116
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
1117
|
+
args: []
|
|
1118
|
+
});
|
|
1119
|
+
} catch {
|
|
1120
|
+
}
|
|
1121
|
+
try {
|
|
1122
|
+
await client.execute({
|
|
1123
|
+
sql: `ALTER TABLE tasks ADD COLUMN session_scope TEXT`,
|
|
1124
|
+
args: []
|
|
1125
|
+
});
|
|
1126
|
+
} catch {
|
|
1127
|
+
}
|
|
1128
|
+
try {
|
|
1129
|
+
await client.execute({
|
|
1130
|
+
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
1131
|
+
args: []
|
|
1132
|
+
});
|
|
1133
|
+
} catch {
|
|
1134
|
+
}
|
|
1135
|
+
try {
|
|
1136
|
+
await client.execute({
|
|
1137
|
+
sql: `ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0`,
|
|
1138
|
+
args: []
|
|
1139
|
+
});
|
|
1140
|
+
} catch {
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
await client.execute({
|
|
1144
|
+
sql: `ALTER TABLE memories ADD COLUMN author_device_id TEXT`,
|
|
1145
|
+
args: []
|
|
1146
|
+
});
|
|
1147
|
+
} catch {
|
|
1148
|
+
}
|
|
1149
|
+
try {
|
|
1150
|
+
await client.execute({
|
|
1151
|
+
sql: `ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'`,
|
|
1152
|
+
args: []
|
|
1153
|
+
});
|
|
1154
|
+
} catch {
|
|
1155
|
+
}
|
|
1156
|
+
await client.executeMultiple(`
|
|
1157
|
+
CREATE TABLE IF NOT EXISTS consolidations (
|
|
1158
|
+
id TEXT PRIMARY KEY,
|
|
1159
|
+
consolidated_memory_id TEXT NOT NULL,
|
|
1160
|
+
source_memory_id TEXT NOT NULL,
|
|
1161
|
+
created_at TEXT NOT NULL
|
|
1162
|
+
);
|
|
1163
|
+
|
|
1164
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_source
|
|
1165
|
+
ON consolidations(source_memory_id);
|
|
1166
|
+
|
|
1167
|
+
CREATE INDEX IF NOT EXISTS idx_consolidations_consolidated
|
|
1168
|
+
ON consolidations(consolidated_memory_id);
|
|
1169
|
+
`);
|
|
1170
|
+
await client.executeMultiple(`
|
|
1171
|
+
CREATE TABLE IF NOT EXISTS reminders (
|
|
1172
|
+
id TEXT PRIMARY KEY,
|
|
1173
|
+
text TEXT NOT NULL,
|
|
1174
|
+
created_at TEXT NOT NULL,
|
|
1175
|
+
due_date TEXT,
|
|
1176
|
+
completed_at TEXT
|
|
1177
|
+
);
|
|
1178
|
+
`);
|
|
1179
|
+
await client.executeMultiple(`
|
|
1180
|
+
CREATE TABLE IF NOT EXISTS notifications (
|
|
1181
|
+
id TEXT PRIMARY KEY,
|
|
1182
|
+
agent_id TEXT NOT NULL,
|
|
1183
|
+
agent_role TEXT NOT NULL,
|
|
1184
|
+
event TEXT NOT NULL,
|
|
1185
|
+
project TEXT NOT NULL,
|
|
1186
|
+
summary TEXT NOT NULL,
|
|
1187
|
+
task_file TEXT,
|
|
1188
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
1189
|
+
created_at TEXT NOT NULL
|
|
1190
|
+
);
|
|
1191
|
+
|
|
1192
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_read
|
|
1193
|
+
ON notifications(read);
|
|
1194
|
+
|
|
1195
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_agent
|
|
1196
|
+
ON notifications(agent_id);
|
|
1197
|
+
|
|
1198
|
+
CREATE INDEX IF NOT EXISTS idx_notifications_task_file
|
|
1199
|
+
ON notifications(task_file);
|
|
1200
|
+
`);
|
|
1201
|
+
await client.executeMultiple(`
|
|
1202
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
1203
|
+
id TEXT PRIMARY KEY,
|
|
1204
|
+
cron TEXT NOT NULL,
|
|
1205
|
+
description TEXT NOT NULL,
|
|
1206
|
+
job_type TEXT NOT NULL DEFAULT 'report',
|
|
1207
|
+
prompt TEXT,
|
|
1208
|
+
assigned_to TEXT,
|
|
1209
|
+
project_name TEXT,
|
|
1210
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
1211
|
+
use_crontab INTEGER NOT NULL DEFAULT 0,
|
|
1212
|
+
created_at TEXT NOT NULL
|
|
1213
|
+
);
|
|
1214
|
+
`);
|
|
1215
|
+
await client.executeMultiple(`
|
|
1216
|
+
CREATE TABLE IF NOT EXISTS device_registry (
|
|
1217
|
+
device_id TEXT PRIMARY KEY,
|
|
1218
|
+
friendly_name TEXT NOT NULL,
|
|
1219
|
+
hostname TEXT NOT NULL,
|
|
1220
|
+
projects TEXT NOT NULL DEFAULT '[]',
|
|
1221
|
+
agents TEXT NOT NULL DEFAULT '[]',
|
|
1222
|
+
connected INTEGER DEFAULT 0,
|
|
1223
|
+
last_seen TEXT NOT NULL
|
|
784
1224
|
);
|
|
785
1225
|
`);
|
|
786
1226
|
await client.executeMultiple(`
|
|
@@ -1222,498 +1662,115 @@ async function ensureSchema() {
|
|
|
1222
1662
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
1223
1663
|
END;
|
|
1224
1664
|
|
|
1225
|
-
CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
|
|
1226
|
-
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
1227
|
-
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
1228
|
-
END;
|
|
1229
|
-
|
|
1230
|
-
CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
|
|
1231
|
-
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
1232
|
-
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
1233
|
-
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
1234
|
-
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
1235
|
-
END;
|
|
1236
|
-
`);
|
|
1237
|
-
try {
|
|
1238
|
-
await client.execute({
|
|
1239
|
-
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1240
|
-
args: []
|
|
1241
|
-
});
|
|
1242
|
-
} catch {
|
|
1243
|
-
}
|
|
1244
|
-
try {
|
|
1245
|
-
await client.execute(
|
|
1246
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1247
|
-
);
|
|
1248
|
-
} catch {
|
|
1249
|
-
}
|
|
1250
|
-
try {
|
|
1251
|
-
await client.execute({
|
|
1252
|
-
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1253
|
-
args: []
|
|
1254
|
-
});
|
|
1255
|
-
await client.execute({
|
|
1256
|
-
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1257
|
-
args: []
|
|
1258
|
-
});
|
|
1259
|
-
} catch {
|
|
1260
|
-
}
|
|
1261
|
-
try {
|
|
1262
|
-
await client.execute({
|
|
1263
|
-
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1264
|
-
args: []
|
|
1265
|
-
});
|
|
1266
|
-
} catch {
|
|
1267
|
-
}
|
|
1268
|
-
try {
|
|
1269
|
-
await client.execute(
|
|
1270
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1271
|
-
);
|
|
1272
|
-
} catch {
|
|
1273
|
-
}
|
|
1274
|
-
for (const col of [
|
|
1275
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1276
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1277
|
-
]) {
|
|
1278
|
-
try {
|
|
1279
|
-
await client.execute(col);
|
|
1280
|
-
} catch {
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
async function disposeDatabase() {
|
|
1285
|
-
if (_client) {
|
|
1286
|
-
_client.close();
|
|
1287
|
-
_client = null;
|
|
1288
|
-
_resilientClient = null;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1292
|
-
var init_database = __esm({
|
|
1293
|
-
"src/lib/database.ts"() {
|
|
1294
|
-
"use strict";
|
|
1295
|
-
init_db_retry();
|
|
1296
|
-
_client = null;
|
|
1297
|
-
_resilientClient = null;
|
|
1298
|
-
initTurso = initDatabase;
|
|
1299
|
-
disposeTurso = disposeDatabase;
|
|
1300
|
-
}
|
|
1301
|
-
});
|
|
1302
|
-
|
|
1303
|
-
// src/lib/config.ts
|
|
1304
|
-
var config_exports = {};
|
|
1305
|
-
__export(config_exports, {
|
|
1306
|
-
CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
|
|
1307
|
-
CONFIG_PATH: () => CONFIG_PATH,
|
|
1308
|
-
COO_AGENT_NAME: () => COO_AGENT_NAME,
|
|
1309
|
-
CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
|
|
1310
|
-
DB_PATH: () => DB_PATH,
|
|
1311
|
-
EXE_AI_DIR: () => EXE_AI_DIR,
|
|
1312
|
-
LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
|
|
1313
|
-
MODELS_DIR: () => MODELS_DIR,
|
|
1314
|
-
loadConfig: () => loadConfig,
|
|
1315
|
-
loadConfigFrom: () => loadConfigFrom,
|
|
1316
|
-
loadConfigSync: () => loadConfigSync,
|
|
1317
|
-
migrateConfig: () => migrateConfig,
|
|
1318
|
-
saveConfig: () => saveConfig
|
|
1319
|
-
});
|
|
1320
|
-
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
1321
|
-
import { readFileSync as readFileSync4, existsSync as existsSync4, renameSync as renameSync2 } from "fs";
|
|
1322
|
-
import path3 from "path";
|
|
1323
|
-
import os3 from "os";
|
|
1324
|
-
function resolveDataDir() {
|
|
1325
|
-
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
1326
|
-
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
1327
|
-
const newDir = path3.join(os3.homedir(), ".exe-os");
|
|
1328
|
-
const legacyDir = path3.join(os3.homedir(), ".exe-mem");
|
|
1329
|
-
if (!existsSync4(newDir) && existsSync4(legacyDir)) {
|
|
1330
|
-
try {
|
|
1331
|
-
renameSync2(legacyDir, newDir);
|
|
1332
|
-
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
1333
|
-
`);
|
|
1334
|
-
} catch {
|
|
1335
|
-
return legacyDir;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
return newDir;
|
|
1339
|
-
}
|
|
1340
|
-
function migrateLegacyConfig(raw) {
|
|
1341
|
-
if ("r2" in raw) {
|
|
1342
|
-
process.stderr.write(
|
|
1343
|
-
"[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
|
|
1344
|
-
);
|
|
1345
|
-
delete raw.r2;
|
|
1346
|
-
}
|
|
1347
|
-
if ("syncIntervalMs" in raw) {
|
|
1348
|
-
delete raw.syncIntervalMs;
|
|
1349
|
-
}
|
|
1350
|
-
return raw;
|
|
1351
|
-
}
|
|
1352
|
-
function migrateConfig(raw) {
|
|
1353
|
-
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
1354
|
-
let currentVersion = fromVersion;
|
|
1355
|
-
let migrated = false;
|
|
1356
|
-
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
1357
|
-
return { config: raw, migrated: false, fromVersion };
|
|
1358
|
-
}
|
|
1359
|
-
for (const migration of CONFIG_MIGRATIONS) {
|
|
1360
|
-
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
1361
|
-
raw = migration.migrate(raw);
|
|
1362
|
-
currentVersion = migration.to;
|
|
1363
|
-
migrated = true;
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
return { config: raw, migrated, fromVersion };
|
|
1367
|
-
}
|
|
1368
|
-
function normalizeScalingRoadmap(raw) {
|
|
1369
|
-
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
1370
|
-
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
1371
|
-
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
1372
|
-
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
1373
|
-
userAuto.enabled = raw.rerankerEnabled;
|
|
1374
|
-
}
|
|
1375
|
-
raw.scalingRoadmap = {
|
|
1376
|
-
...userRoadmap,
|
|
1377
|
-
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1380
|
-
function normalizeSessionLifecycle(raw) {
|
|
1381
|
-
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
1382
|
-
const userSL = raw.sessionLifecycle ?? {};
|
|
1383
|
-
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1384
|
-
}
|
|
1385
|
-
function normalizeAutoUpdate(raw) {
|
|
1386
|
-
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1387
|
-
const userAU = raw.autoUpdate ?? {};
|
|
1388
|
-
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1389
|
-
}
|
|
1390
|
-
async function loadConfig() {
|
|
1391
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1392
|
-
await mkdir(dir, { recursive: true });
|
|
1393
|
-
const configPath = path3.join(dir, "config.json");
|
|
1394
|
-
if (!existsSync4(configPath)) {
|
|
1395
|
-
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
1396
|
-
}
|
|
1397
|
-
const raw = await readFile(configPath, "utf-8");
|
|
1398
|
-
try {
|
|
1399
|
-
let parsed = JSON.parse(raw);
|
|
1400
|
-
parsed = migrateLegacyConfig(parsed);
|
|
1401
|
-
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
1402
|
-
if (migrated) {
|
|
1403
|
-
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
1404
|
-
`);
|
|
1405
|
-
try {
|
|
1406
|
-
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
1407
|
-
} catch {
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1411
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1412
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1413
|
-
const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
1414
|
-
if (config.dbPath.startsWith("~")) {
|
|
1415
|
-
config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
|
|
1416
|
-
}
|
|
1417
|
-
return config;
|
|
1418
|
-
} catch {
|
|
1419
|
-
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
function loadConfigSync() {
|
|
1423
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1424
|
-
const configPath = path3.join(dir, "config.json");
|
|
1425
|
-
if (!existsSync4(configPath)) {
|
|
1426
|
-
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
1427
|
-
}
|
|
1428
|
-
try {
|
|
1429
|
-
const raw = readFileSync4(configPath, "utf-8");
|
|
1430
|
-
let parsed = JSON.parse(raw);
|
|
1431
|
-
parsed = migrateLegacyConfig(parsed);
|
|
1432
|
-
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1433
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1434
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1435
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1436
|
-
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
1437
|
-
} catch {
|
|
1438
|
-
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
async function saveConfig(config) {
|
|
1442
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1443
|
-
await mkdir(dir, { recursive: true });
|
|
1444
|
-
const configPath = path3.join(dir, "config.json");
|
|
1445
|
-
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1446
|
-
if (config.cloud?.apiKey) {
|
|
1447
|
-
await chmod(configPath, 384);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
async function loadConfigFrom(configPath) {
|
|
1451
|
-
const raw = await readFile(configPath, "utf-8");
|
|
1452
|
-
try {
|
|
1453
|
-
let parsed = JSON.parse(raw);
|
|
1454
|
-
parsed = migrateLegacyConfig(parsed);
|
|
1455
|
-
const { config: migratedCfg } = migrateConfig(parsed);
|
|
1456
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1457
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1458
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1459
|
-
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
1460
|
-
} catch {
|
|
1461
|
-
return { ...DEFAULT_CONFIG };
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
1465
|
-
var init_config = __esm({
|
|
1466
|
-
"src/lib/config.ts"() {
|
|
1467
|
-
"use strict";
|
|
1468
|
-
EXE_AI_DIR = resolveDataDir();
|
|
1469
|
-
DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
|
|
1470
|
-
MODELS_DIR = path3.join(EXE_AI_DIR, "models");
|
|
1471
|
-
CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
|
|
1472
|
-
COO_AGENT_NAME = "exe";
|
|
1473
|
-
LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
|
|
1474
|
-
CURRENT_CONFIG_VERSION = 1;
|
|
1475
|
-
DEFAULT_CONFIG = {
|
|
1476
|
-
config_version: CURRENT_CONFIG_VERSION,
|
|
1477
|
-
dbPath: DB_PATH,
|
|
1478
|
-
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
1479
|
-
embeddingDim: 1024,
|
|
1480
|
-
batchSize: 20,
|
|
1481
|
-
flushIntervalMs: 1e4,
|
|
1482
|
-
autoIngestion: true,
|
|
1483
|
-
autoRetrieval: true,
|
|
1484
|
-
searchMode: "hybrid",
|
|
1485
|
-
hookSearchMode: "hybrid",
|
|
1486
|
-
fileGrepEnabled: true,
|
|
1487
|
-
splashEffect: true,
|
|
1488
|
-
consolidationEnabled: true,
|
|
1489
|
-
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
1490
|
-
consolidationModel: "claude-haiku-4-5-20251001",
|
|
1491
|
-
consolidationMaxCallsPerRun: 20,
|
|
1492
|
-
selfQueryRouter: true,
|
|
1493
|
-
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
1494
|
-
rerankerEnabled: true,
|
|
1495
|
-
scalingRoadmap: {
|
|
1496
|
-
rerankerAutoTrigger: {
|
|
1497
|
-
enabled: true,
|
|
1498
|
-
broadQueryMinCardinality: 5e4,
|
|
1499
|
-
fetchTopK: 150,
|
|
1500
|
-
returnTopK: 5
|
|
1501
|
-
}
|
|
1502
|
-
},
|
|
1503
|
-
graphRagEnabled: true,
|
|
1504
|
-
wikiEnabled: false,
|
|
1505
|
-
wikiUrl: "",
|
|
1506
|
-
wikiApiKey: "",
|
|
1507
|
-
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
1508
|
-
wikiWorkspaceMapping: {
|
|
1509
|
-
exe: "Executive",
|
|
1510
|
-
yoshi: "Engineering",
|
|
1511
|
-
mari: "Marketing",
|
|
1512
|
-
tom: "Engineering",
|
|
1513
|
-
sasha: "Production"
|
|
1514
|
-
},
|
|
1515
|
-
wikiAutoUpdate: true,
|
|
1516
|
-
wikiAutoUpdateThreshold: 0.5,
|
|
1517
|
-
wikiAutoUpdateCreateNew: true,
|
|
1518
|
-
skillLearning: true,
|
|
1519
|
-
skillThreshold: 3,
|
|
1520
|
-
skillModel: "claude-haiku-4-5-20251001",
|
|
1521
|
-
exeHeartbeat: {
|
|
1522
|
-
enabled: true,
|
|
1523
|
-
intervalSeconds: 60,
|
|
1524
|
-
staleInProgressThresholdHours: 2
|
|
1525
|
-
},
|
|
1526
|
-
sessionLifecycle: {
|
|
1527
|
-
idleKillEnabled: true,
|
|
1528
|
-
idleKillTicksRequired: 3,
|
|
1529
|
-
idleKillIntercomAckWindowMs: 1e4,
|
|
1530
|
-
maxAutoInstances: 10
|
|
1531
|
-
},
|
|
1532
|
-
autoUpdate: {
|
|
1533
|
-
checkOnBoot: true,
|
|
1534
|
-
autoInstall: false,
|
|
1535
|
-
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1536
|
-
}
|
|
1537
|
-
};
|
|
1538
|
-
CONFIG_MIGRATIONS = [
|
|
1539
|
-
{
|
|
1540
|
-
from: 0,
|
|
1541
|
-
to: 1,
|
|
1542
|
-
migrate: (cfg) => {
|
|
1543
|
-
cfg.config_version = 1;
|
|
1544
|
-
return cfg;
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
];
|
|
1548
|
-
}
|
|
1549
|
-
});
|
|
1550
|
-
|
|
1551
|
-
// src/lib/employees.ts
|
|
1552
|
-
var employees_exports = {};
|
|
1553
|
-
__export(employees_exports, {
|
|
1554
|
-
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
1555
|
-
addEmployee: () => addEmployee,
|
|
1556
|
-
getEmployee: () => getEmployee,
|
|
1557
|
-
getEmployeeByRole: () => getEmployeeByRole,
|
|
1558
|
-
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
1559
|
-
hasRole: () => hasRole,
|
|
1560
|
-
isMultiInstance: () => isMultiInstance,
|
|
1561
|
-
loadEmployees: () => loadEmployees,
|
|
1562
|
-
loadEmployeesSync: () => loadEmployeesSync,
|
|
1563
|
-
normalizeRosterCase: () => normalizeRosterCase,
|
|
1564
|
-
registerBinSymlinks: () => registerBinSymlinks,
|
|
1565
|
-
saveEmployees: () => saveEmployees,
|
|
1566
|
-
validateEmployeeName: () => validateEmployeeName
|
|
1567
|
-
});
|
|
1568
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1569
|
-
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1570
|
-
import { execSync as execSync4 } from "child_process";
|
|
1571
|
-
import path4 from "path";
|
|
1572
|
-
import os4 from "os";
|
|
1573
|
-
function validateEmployeeName(name) {
|
|
1574
|
-
if (!name) {
|
|
1575
|
-
return { valid: false, error: "Name is required" };
|
|
1576
|
-
}
|
|
1577
|
-
if (name.length > 32) {
|
|
1578
|
-
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
1579
|
-
}
|
|
1580
|
-
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
1581
|
-
return {
|
|
1582
|
-
valid: false,
|
|
1583
|
-
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
1584
|
-
};
|
|
1665
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
|
|
1666
|
+
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
1667
|
+
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
1668
|
+
END;
|
|
1669
|
+
|
|
1670
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
|
|
1671
|
+
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
1672
|
+
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
1673
|
+
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
1674
|
+
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
1675
|
+
END;
|
|
1676
|
+
`);
|
|
1677
|
+
try {
|
|
1678
|
+
await client.execute({
|
|
1679
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
1680
|
+
args: []
|
|
1681
|
+
});
|
|
1682
|
+
} catch {
|
|
1585
1683
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1684
|
+
try {
|
|
1685
|
+
await client.execute(
|
|
1686
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
1687
|
+
);
|
|
1688
|
+
} catch {
|
|
1591
1689
|
}
|
|
1592
|
-
const raw = await readFile2(employeesPath, "utf-8");
|
|
1593
1690
|
try {
|
|
1594
|
-
|
|
1691
|
+
await client.execute({
|
|
1692
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
1693
|
+
args: []
|
|
1694
|
+
});
|
|
1695
|
+
await client.execute({
|
|
1696
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
1697
|
+
args: []
|
|
1698
|
+
});
|
|
1595
1699
|
} catch {
|
|
1596
|
-
return [];
|
|
1597
1700
|
}
|
|
1598
|
-
}
|
|
1599
|
-
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
1600
|
-
await mkdir2(path4.dirname(employeesPath), { recursive: true });
|
|
1601
|
-
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
1602
|
-
}
|
|
1603
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1604
|
-
if (!existsSync5(employeesPath)) return [];
|
|
1605
1701
|
try {
|
|
1606
|
-
|
|
1702
|
+
await client.execute({
|
|
1703
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1704
|
+
args: []
|
|
1705
|
+
});
|
|
1607
1706
|
} catch {
|
|
1608
|
-
return [];
|
|
1609
1707
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
const lower = role.toLowerCase();
|
|
1616
|
-
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
1617
|
-
}
|
|
1618
|
-
function getEmployeeNamesByRole(employees, role) {
|
|
1619
|
-
const lower = role.toLowerCase();
|
|
1620
|
-
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
1621
|
-
}
|
|
1622
|
-
function hasRole(agentName, role) {
|
|
1623
|
-
const employees = loadEmployeesSync();
|
|
1624
|
-
const emp = getEmployee(employees, agentName);
|
|
1625
|
-
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
1626
|
-
}
|
|
1627
|
-
function isMultiInstance(agentName, employees) {
|
|
1628
|
-
const roster = employees ?? loadEmployeesSync();
|
|
1629
|
-
const emp = getEmployee(roster, agentName);
|
|
1630
|
-
if (!emp) return false;
|
|
1631
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1632
|
-
}
|
|
1633
|
-
function addEmployee(employees, employee) {
|
|
1634
|
-
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
1635
|
-
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
1636
|
-
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
1708
|
+
try {
|
|
1709
|
+
await client.execute(
|
|
1710
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1711
|
+
);
|
|
1712
|
+
} catch {
|
|
1637
1713
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
const oldName = emp.name;
|
|
1646
|
-
emp.name = emp.name.toLowerCase();
|
|
1647
|
-
changed = true;
|
|
1648
|
-
try {
|
|
1649
|
-
const identityDir = path4.join(os4.homedir(), ".exe-os", "identity");
|
|
1650
|
-
const oldPath = path4.join(identityDir, `${oldName}.md`);
|
|
1651
|
-
const newPath = path4.join(identityDir, `${emp.name}.md`);
|
|
1652
|
-
if (existsSync5(oldPath) && !existsSync5(newPath)) {
|
|
1653
|
-
renameSync3(oldPath, newPath);
|
|
1654
|
-
} else if (existsSync5(oldPath) && oldPath !== newPath) {
|
|
1655
|
-
const content = readFileSync5(oldPath, "utf-8");
|
|
1656
|
-
writeFileSync3(newPath, content, "utf-8");
|
|
1657
|
-
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1658
|
-
unlinkSync(oldPath);
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
} catch {
|
|
1662
|
-
}
|
|
1714
|
+
for (const col of [
|
|
1715
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1716
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1717
|
+
]) {
|
|
1718
|
+
try {
|
|
1719
|
+
await client.execute(col);
|
|
1720
|
+
} catch {
|
|
1663
1721
|
}
|
|
1664
1722
|
}
|
|
1665
|
-
|
|
1666
|
-
await
|
|
1723
|
+
try {
|
|
1724
|
+
await client.execute({
|
|
1725
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1726
|
+
args: []
|
|
1727
|
+
});
|
|
1728
|
+
} catch {
|
|
1667
1729
|
}
|
|
1668
|
-
return changed;
|
|
1669
|
-
}
|
|
1670
|
-
function findExeBin() {
|
|
1671
1730
|
try {
|
|
1672
|
-
|
|
1731
|
+
await client.execute(
|
|
1732
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1733
|
+
);
|
|
1673
1734
|
} catch {
|
|
1674
|
-
return null;
|
|
1675
1735
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
if (!exeBinPath) {
|
|
1683
|
-
errors.push("Could not find 'exe-os' in PATH");
|
|
1684
|
-
return { created, skipped, errors };
|
|
1736
|
+
try {
|
|
1737
|
+
await client.execute({
|
|
1738
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1739
|
+
args: []
|
|
1740
|
+
});
|
|
1741
|
+
} catch {
|
|
1685
1742
|
}
|
|
1686
|
-
const binDir = path4.dirname(exeBinPath);
|
|
1687
|
-
let target;
|
|
1688
1743
|
try {
|
|
1689
|
-
|
|
1744
|
+
await client.execute(
|
|
1745
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1746
|
+
);
|
|
1690
1747
|
} catch {
|
|
1691
|
-
errors.push("Could not read 'exe' symlink");
|
|
1692
|
-
return { created, skipped, errors };
|
|
1693
1748
|
}
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1701
|
-
try {
|
|
1702
|
-
symlinkSync(target, linkPath);
|
|
1703
|
-
created.push(linkName);
|
|
1704
|
-
} catch (err) {
|
|
1705
|
-
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1706
|
-
}
|
|
1749
|
+
try {
|
|
1750
|
+
await client.execute({
|
|
1751
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1752
|
+
args: []
|
|
1753
|
+
});
|
|
1754
|
+
} catch {
|
|
1707
1755
|
}
|
|
1708
|
-
return { created, skipped, errors };
|
|
1709
1756
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1757
|
+
async function disposeDatabase() {
|
|
1758
|
+
if (_client) {
|
|
1759
|
+
_client.close();
|
|
1760
|
+
_client = null;
|
|
1761
|
+
_resilientClient = null;
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1765
|
+
var init_database = __esm({
|
|
1766
|
+
"src/lib/database.ts"() {
|
|
1713
1767
|
"use strict";
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1768
|
+
init_db_retry();
|
|
1769
|
+
init_employees();
|
|
1770
|
+
_client = null;
|
|
1771
|
+
_resilientClient = null;
|
|
1772
|
+
initTurso = initDatabase;
|
|
1773
|
+
disposeTurso = disposeDatabase;
|
|
1717
1774
|
}
|
|
1718
1775
|
});
|
|
1719
1776
|
|
|
@@ -2333,6 +2390,7 @@ __export(tasks_crud_exports, {
|
|
|
2333
2390
|
ensureArchitectureDoc: () => ensureArchitectureDoc,
|
|
2334
2391
|
ensureGitignoreExe: () => ensureGitignoreExe,
|
|
2335
2392
|
extractParentFromContext: () => extractParentFromContext,
|
|
2393
|
+
isTmuxSessionAlive: () => isTmuxSessionAlive,
|
|
2336
2394
|
listTasks: () => listTasks,
|
|
2337
2395
|
resolveTask: () => resolveTask,
|
|
2338
2396
|
slugify: () => slugify,
|
|
@@ -2583,6 +2641,36 @@ async function listTasks(input) {
|
|
|
2583
2641
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
2584
2642
|
}));
|
|
2585
2643
|
}
|
|
2644
|
+
function isTmuxSessionAlive(identifier) {
|
|
2645
|
+
if (!identifier || identifier === "unknown") return true;
|
|
2646
|
+
try {
|
|
2647
|
+
if (identifier.startsWith("%")) {
|
|
2648
|
+
const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
|
|
2649
|
+
timeout: 2e3,
|
|
2650
|
+
encoding: "utf8",
|
|
2651
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2652
|
+
});
|
|
2653
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2654
|
+
} else {
|
|
2655
|
+
execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2656
|
+
timeout: 2e3,
|
|
2657
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2658
|
+
});
|
|
2659
|
+
return true;
|
|
2660
|
+
}
|
|
2661
|
+
} catch {
|
|
2662
|
+
if (identifier.startsWith("%")) return true;
|
|
2663
|
+
try {
|
|
2664
|
+
execSync5("tmux list-sessions", {
|
|
2665
|
+
timeout: 2e3,
|
|
2666
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2667
|
+
});
|
|
2668
|
+
return false;
|
|
2669
|
+
} catch {
|
|
2670
|
+
return true;
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2586
2674
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
2587
2675
|
if (!taskContext) return null;
|
|
2588
2676
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -2645,13 +2733,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2645
2733
|
});
|
|
2646
2734
|
if (claim.rowsAffected === 0) {
|
|
2647
2735
|
const current = await client.execute({
|
|
2648
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
2736
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
2649
2737
|
args: [taskId]
|
|
2650
2738
|
});
|
|
2651
2739
|
const cur = current.rows[0];
|
|
2652
|
-
const
|
|
2653
|
-
const
|
|
2654
|
-
|
|
2740
|
+
const curStatus = cur?.status ?? "unknown";
|
|
2741
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
2742
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
2743
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
2744
|
+
process.stderr.write(
|
|
2745
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
2746
|
+
`
|
|
2747
|
+
);
|
|
2748
|
+
await client.execute({
|
|
2749
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
2750
|
+
args: [now, taskId]
|
|
2751
|
+
});
|
|
2752
|
+
const retried = await client.execute({
|
|
2753
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
2754
|
+
args: [tmuxSession, now, taskId]
|
|
2755
|
+
});
|
|
2756
|
+
if (retried.rowsAffected > 0) {
|
|
2757
|
+
try {
|
|
2758
|
+
await writeCheckpoint({
|
|
2759
|
+
taskId,
|
|
2760
|
+
step: "reclaimed_dead_session",
|
|
2761
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
2762
|
+
});
|
|
2763
|
+
} catch {
|
|
2764
|
+
}
|
|
2765
|
+
return { row, taskFile, now, taskId };
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
2769
|
+
process.stderr.write(
|
|
2770
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2771
|
+
`
|
|
2772
|
+
);
|
|
2773
|
+
await client.execute({
|
|
2774
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
2775
|
+
args: [tmuxSession, now, taskId]
|
|
2776
|
+
});
|
|
2777
|
+
try {
|
|
2778
|
+
await writeCheckpoint({
|
|
2779
|
+
taskId,
|
|
2780
|
+
step: "assigner_override",
|
|
2781
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
2782
|
+
});
|
|
2783
|
+
} catch {
|
|
2784
|
+
}
|
|
2785
|
+
return { row, taskFile, now, taskId };
|
|
2786
|
+
}
|
|
2787
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
2788
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
2655
2789
|
}
|
|
2656
2790
|
try {
|
|
2657
2791
|
await writeCheckpoint({
|
|
@@ -2749,7 +2883,7 @@ var init_tasks_crud = __esm({
|
|
|
2749
2883
|
"use strict";
|
|
2750
2884
|
init_database();
|
|
2751
2885
|
init_task_scope();
|
|
2752
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
2886
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2753
2887
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2754
2888
|
}
|
|
2755
2889
|
});
|
|
@@ -3106,7 +3240,7 @@ function findSessionForProject(projectName) {
|
|
|
3106
3240
|
const sessions = listSessions();
|
|
3107
3241
|
for (const s of sessions) {
|
|
3108
3242
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
3109
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
3243
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
3110
3244
|
}
|
|
3111
3245
|
return null;
|
|
3112
3246
|
}
|
|
@@ -3146,12 +3280,13 @@ var init_session_scope = __esm({
|
|
|
3146
3280
|
init_session_registry();
|
|
3147
3281
|
init_project_name();
|
|
3148
3282
|
init_tmux_routing();
|
|
3283
|
+
init_employees();
|
|
3149
3284
|
}
|
|
3150
3285
|
});
|
|
3151
3286
|
|
|
3152
3287
|
// src/lib/tasks-notify.ts
|
|
3153
3288
|
async function dispatchTaskToEmployee(input) {
|
|
3154
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
3289
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
3155
3290
|
let crossProject = false;
|
|
3156
3291
|
if (input.projectName) {
|
|
3157
3292
|
try {
|
|
@@ -3594,6 +3729,24 @@ async function updateTask(input) {
|
|
|
3594
3729
|
});
|
|
3595
3730
|
} catch {
|
|
3596
3731
|
}
|
|
3732
|
+
const assignedAgent = String(row.assigned_to);
|
|
3733
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
3734
|
+
try {
|
|
3735
|
+
const draftClient = getClient();
|
|
3736
|
+
if (input.status === "done") {
|
|
3737
|
+
await draftClient.execute({
|
|
3738
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3739
|
+
args: [assignedAgent]
|
|
3740
|
+
});
|
|
3741
|
+
} else if (input.status === "cancelled") {
|
|
3742
|
+
await draftClient.execute({
|
|
3743
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
3744
|
+
args: [assignedAgent]
|
|
3745
|
+
});
|
|
3746
|
+
}
|
|
3747
|
+
} catch {
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3597
3750
|
try {
|
|
3598
3751
|
const client = getClient();
|
|
3599
3752
|
const cascaded = await client.execute({
|
|
@@ -3612,8 +3765,8 @@ async function updateTask(input) {
|
|
|
3612
3765
|
}
|
|
3613
3766
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
3614
3767
|
if (isTerminal) {
|
|
3615
|
-
const
|
|
3616
|
-
if (!
|
|
3768
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
3769
|
+
if (!isCoordinator) {
|
|
3617
3770
|
notifyTaskDone();
|
|
3618
3771
|
}
|
|
3619
3772
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -3637,7 +3790,7 @@ async function updateTask(input) {
|
|
|
3637
3790
|
}
|
|
3638
3791
|
}
|
|
3639
3792
|
}
|
|
3640
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
3793
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3641
3794
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3642
3795
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3643
3796
|
taskId,
|
|
@@ -3653,7 +3806,7 @@ async function updateTask(input) {
|
|
|
3653
3806
|
});
|
|
3654
3807
|
}
|
|
3655
3808
|
let nextTask;
|
|
3656
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
3809
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
3657
3810
|
try {
|
|
3658
3811
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3659
3812
|
} catch {
|
|
@@ -3680,12 +3833,14 @@ async function updateTask(input) {
|
|
|
3680
3833
|
async function deleteTask(taskId, baseDir) {
|
|
3681
3834
|
const client = getClient();
|
|
3682
3835
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
3683
|
-
const
|
|
3836
|
+
const coordinatorName = getCoordinatorName();
|
|
3837
|
+
const reviewer = assignedBy || coordinatorName;
|
|
3684
3838
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
3685
3839
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
3840
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
3686
3841
|
await client.execute({
|
|
3687
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
3688
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3842
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
3843
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3689
3844
|
});
|
|
3690
3845
|
await markAsReadByTaskFile(taskFile);
|
|
3691
3846
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -3697,6 +3852,7 @@ var init_tasks = __esm({
|
|
|
3697
3852
|
init_config();
|
|
3698
3853
|
init_notifications();
|
|
3699
3854
|
init_state_bus();
|
|
3855
|
+
init_employees();
|
|
3700
3856
|
init_tasks_crud();
|
|
3701
3857
|
init_tasks_review();
|
|
3702
3858
|
init_tasks_crud();
|
|
@@ -3782,7 +3938,7 @@ function _resetLastRelaunchCache() {
|
|
|
3782
3938
|
}
|
|
3783
3939
|
async function lastResumeCreatedAtMs(agentId) {
|
|
3784
3940
|
const client = getClient();
|
|
3785
|
-
const cmScope = sessionScopeFilter();
|
|
3941
|
+
const cmScope = sessionScopeFilter(null);
|
|
3786
3942
|
const result = await client.execute({
|
|
3787
3943
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
3788
3944
|
FROM tasks
|
|
@@ -3807,7 +3963,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
3807
3963
|
const client = getClient();
|
|
3808
3964
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3809
3965
|
const context = buildResumeContext(agentId, openTasks);
|
|
3810
|
-
const rdScope = sessionScopeFilter();
|
|
3966
|
+
const rdScope = sessionScopeFilter(null);
|
|
3811
3967
|
const existing = await client.execute({
|
|
3812
3968
|
sql: `SELECT id FROM tasks
|
|
3813
3969
|
WHERE assigned_to = ?
|
|
@@ -3841,7 +3997,7 @@ async function pollCapacityDead() {
|
|
|
3841
3997
|
const transport = getTransport();
|
|
3842
3998
|
const relaunched = [];
|
|
3843
3999
|
const registered = listSessions().filter(
|
|
3844
|
-
(s) => s.agentId !== "exe"
|
|
4000
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
3845
4001
|
);
|
|
3846
4002
|
if (registered.length === 0) return [];
|
|
3847
4003
|
let liveSessions;
|
|
@@ -3901,7 +4057,7 @@ async function pollCapacityDead() {
|
|
|
3901
4057
|
reason: "capacity"
|
|
3902
4058
|
});
|
|
3903
4059
|
const client = getClient();
|
|
3904
|
-
const rlScope = sessionScopeFilter();
|
|
4060
|
+
const rlScope = sessionScopeFilter(null);
|
|
3905
4061
|
const openTasks = await client.execute({
|
|
3906
4062
|
sql: `SELECT id, title, priority, task_file, status
|
|
3907
4063
|
FROM tasks
|
|
@@ -3955,6 +4111,7 @@ var init_capacity_monitor = __esm({
|
|
|
3955
4111
|
init_session_kill_telemetry();
|
|
3956
4112
|
init_tmux_routing();
|
|
3957
4113
|
init_task_scope();
|
|
4114
|
+
init_employees();
|
|
3958
4115
|
CAPACITY_PATTERNS = [
|
|
3959
4116
|
/conversation is too long/i,
|
|
3960
4117
|
/maximum context length/i,
|
|
@@ -4093,7 +4250,7 @@ function getMySession() {
|
|
|
4093
4250
|
function isRootSession(name) {
|
|
4094
4251
|
return name.length > 0 && !name.includes("-");
|
|
4095
4252
|
}
|
|
4096
|
-
function employeeSessionName(employee, exeSession,
|
|
4253
|
+
function employeeSessionName(employee, exeSession, instance2) {
|
|
4097
4254
|
if (!isRootSession(exeSession)) {
|
|
4098
4255
|
const root = extractRootExe(exeSession);
|
|
4099
4256
|
if (root) {
|
|
@@ -4104,11 +4261,11 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
4104
4261
|
exeSession = root;
|
|
4105
4262
|
} else {
|
|
4106
4263
|
throw new Error(
|
|
4107
|
-
`Invalid
|
|
4264
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
4108
4265
|
);
|
|
4109
4266
|
}
|
|
4110
4267
|
}
|
|
4111
|
-
const suffix =
|
|
4268
|
+
const suffix = instance2 != null && instance2 > 0 ? String(instance2) : "";
|
|
4112
4269
|
const name = `${employee}${suffix}-${exeSession}`;
|
|
4113
4270
|
if (!VALID_SESSION_NAME.test(name)) {
|
|
4114
4271
|
throw new Error(
|
|
@@ -4124,8 +4281,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
4124
4281
|
return match?.[1] ?? null;
|
|
4125
4282
|
}
|
|
4126
4283
|
function extractRootExe(name) {
|
|
4127
|
-
|
|
4128
|
-
|
|
4284
|
+
if (!name) return null;
|
|
4285
|
+
if (!name.includes("-")) return name;
|
|
4286
|
+
const parts = name.split("-").filter(Boolean);
|
|
4287
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
4129
4288
|
}
|
|
4130
4289
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
4131
4290
|
if (!existsSync11(SESSION_CACHE)) {
|
|
@@ -4270,12 +4429,14 @@ function isSessionBusy(sessionName) {
|
|
|
4270
4429
|
return state === "thinking" || state === "tool";
|
|
4271
4430
|
}
|
|
4272
4431
|
function isExeSession(sessionName) {
|
|
4273
|
-
|
|
4432
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
4433
|
+
const coordinatorName = getCoordinatorName();
|
|
4434
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
4274
4435
|
}
|
|
4275
4436
|
function sendIntercom(targetSession) {
|
|
4276
4437
|
const transport = getTransport();
|
|
4277
4438
|
if (isExeSession(targetSession)) {
|
|
4278
|
-
logIntercom(`
|
|
4439
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
4279
4440
|
return "skipped_exe";
|
|
4280
4441
|
}
|
|
4281
4442
|
if (isDebounced(targetSession)) {
|
|
@@ -4327,7 +4488,7 @@ function notifyParentExe(sessionKey) {
|
|
|
4327
4488
|
if (result === "failed") {
|
|
4328
4489
|
const rootExe = resolveExeSession();
|
|
4329
4490
|
if (rootExe && rootExe !== target) {
|
|
4330
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
4491
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
4331
4492
|
`);
|
|
4332
4493
|
const fallback = sendIntercom(rootExe);
|
|
4333
4494
|
return fallback !== "failed";
|
|
@@ -4337,8 +4498,8 @@ function notifyParentExe(sessionKey) {
|
|
|
4337
4498
|
return true;
|
|
4338
4499
|
}
|
|
4339
4500
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
4340
|
-
if (employeeName === "exe") {
|
|
4341
|
-
return { status: "failed", sessionName: "", error: "
|
|
4501
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
4502
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
4342
4503
|
}
|
|
4343
4504
|
try {
|
|
4344
4505
|
assertEmployeeLimitSync();
|
|
@@ -4347,8 +4508,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4347
4508
|
return { status: "failed", sessionName: "", error: err.message };
|
|
4348
4509
|
}
|
|
4349
4510
|
}
|
|
4350
|
-
if (
|
|
4351
|
-
const bare = employeeName.
|
|
4511
|
+
if (employeeName.includes("-")) {
|
|
4512
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
4352
4513
|
return {
|
|
4353
4514
|
status: "failed",
|
|
4354
4515
|
sessionName: "",
|
|
@@ -4367,7 +4528,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4367
4528
|
return {
|
|
4368
4529
|
status: "failed",
|
|
4369
4530
|
sessionName: "",
|
|
4370
|
-
error: `Invalid
|
|
4531
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
4371
4532
|
};
|
|
4372
4533
|
}
|
|
4373
4534
|
}
|
|
@@ -4524,8 +4685,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
4524
4685
|
const ctxContent = [
|
|
4525
4686
|
`## Session Context`,
|
|
4526
4687
|
`You are running in tmux session: ${sessionName}.`,
|
|
4527
|
-
`Your parent
|
|
4528
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
4688
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
4689
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
4529
4690
|
].join("\n");
|
|
4530
4691
|
writeFileSync6(ctxFile, ctxContent);
|
|
4531
4692
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -4629,6 +4790,7 @@ var init_tmux_routing = __esm({
|
|
|
4629
4790
|
init_provider_table();
|
|
4630
4791
|
init_intercom_queue();
|
|
4631
4792
|
init_plan_limits();
|
|
4793
|
+
init_employees();
|
|
4632
4794
|
SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
4633
4795
|
SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
|
|
4634
4796
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -5922,26 +6084,26 @@ var init_platform_procedures = __esm({
|
|
|
5922
6084
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
5923
6085
|
domain: "architecture",
|
|
5924
6086
|
priority: "p0",
|
|
5925
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
6087
|
+
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."
|
|
5926
6088
|
},
|
|
5927
6089
|
{
|
|
5928
6090
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
5929
6091
|
domain: "architecture",
|
|
5930
6092
|
priority: "p0",
|
|
5931
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
6093
|
+
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."
|
|
5932
6094
|
},
|
|
5933
6095
|
{
|
|
5934
|
-
title: "Sessions explained \u2014
|
|
6096
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
5935
6097
|
domain: "architecture",
|
|
5936
6098
|
priority: "p0",
|
|
5937
|
-
content: "Each
|
|
6099
|
+
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."
|
|
5938
6100
|
},
|
|
5939
6101
|
// --- Hierarchy and dispatch ---
|
|
5940
6102
|
{
|
|
5941
6103
|
title: "Chain of command \u2014 who talks to whom",
|
|
5942
6104
|
domain: "workflow",
|
|
5943
6105
|
priority: "p0",
|
|
5944
|
-
content: "Founder
|
|
6106
|
+
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."
|
|
5945
6107
|
},
|
|
5946
6108
|
{
|
|
5947
6109
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -5951,30 +6113,30 @@ var init_platform_procedures = __esm({
|
|
|
5951
6113
|
},
|
|
5952
6114
|
// --- Session isolation ---
|
|
5953
6115
|
{
|
|
5954
|
-
title: "Session scoping \u2014 stay in your
|
|
6116
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
5955
6117
|
domain: "security",
|
|
5956
6118
|
priority: "p0",
|
|
5957
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
6119
|
+
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."
|
|
5958
6120
|
},
|
|
5959
6121
|
{
|
|
5960
6122
|
title: "Session isolation \u2014 never touch another session's work",
|
|
5961
6123
|
domain: "workflow",
|
|
5962
6124
|
priority: "p0",
|
|
5963
|
-
content:
|
|
6125
|
+
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."
|
|
5964
6126
|
},
|
|
5965
6127
|
// --- Engineering: session scoping in code ---
|
|
5966
6128
|
{
|
|
5967
6129
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
5968
6130
|
domain: "architecture",
|
|
5969
6131
|
priority: "p0",
|
|
5970
|
-
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
|
|
6132
|
+
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."
|
|
5971
6133
|
},
|
|
5972
6134
|
// --- Hard constraints ---
|
|
5973
6135
|
{
|
|
5974
6136
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
5975
6137
|
domain: "security",
|
|
5976
6138
|
priority: "p0",
|
|
5977
|
-
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
|
|
6139
|
+
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."
|
|
5978
6140
|
},
|
|
5979
6141
|
// --- Operations ---
|
|
5980
6142
|
{
|
|
@@ -7225,7 +7387,7 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
|
7225
7387
|
return { agentId, score: results.length / 5 };
|
|
7226
7388
|
}
|
|
7227
7389
|
async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
|
|
7228
|
-
let specialists = employees.filter((e) => e.
|
|
7390
|
+
let specialists = employees.filter((e) => !isCoordinatorRole(e.role));
|
|
7229
7391
|
if (specialists.length === 0) {
|
|
7230
7392
|
throw new Error(
|
|
7231
7393
|
"No specialist employees available. Create one with /exe-new-employee."
|
|
@@ -7261,6 +7423,7 @@ var DEFAULT_BLOOM_CONFIG;
|
|
|
7261
7423
|
var init_task_router = __esm({
|
|
7262
7424
|
"src/lib/task-router.ts"() {
|
|
7263
7425
|
"use strict";
|
|
7426
|
+
init_employees();
|
|
7264
7427
|
DEFAULT_BLOOM_CONFIG = {
|
|
7265
7428
|
complexityToTier: {
|
|
7266
7429
|
routine: "junior",
|
|
@@ -7306,6 +7469,7 @@ var STALE_THRESHOLD_MS, MultiAgentOrchestrator;
|
|
|
7306
7469
|
var init_orchestrator = __esm({
|
|
7307
7470
|
"src/runtime/orchestrator.ts"() {
|
|
7308
7471
|
"use strict";
|
|
7472
|
+
init_employees();
|
|
7309
7473
|
init_task_router();
|
|
7310
7474
|
init_tmux_routing();
|
|
7311
7475
|
init_task_scope();
|
|
@@ -7350,7 +7514,7 @@ ${task.context}`,
|
|
|
7350
7514
|
await createTaskCore({
|
|
7351
7515
|
title: task.title,
|
|
7352
7516
|
assignedTo: targetEmployee.name,
|
|
7353
|
-
assignedBy:
|
|
7517
|
+
assignedBy: getCoordinatorName(),
|
|
7354
7518
|
projectName: task.projectName,
|
|
7355
7519
|
priority: task.priority,
|
|
7356
7520
|
context: task.context,
|
|
@@ -7455,15 +7619,16 @@ ${task.context}`,
|
|
|
7455
7619
|
const client = getClient2();
|
|
7456
7620
|
const autoApproved = [];
|
|
7457
7621
|
const needsReview = [];
|
|
7622
|
+
const coordinatorName = getCoordinatorName();
|
|
7458
7623
|
const rScope = sessionScopeFilter();
|
|
7459
7624
|
const reviews = await client.execute({
|
|
7460
7625
|
sql: `SELECT id, title, assigned_to, priority, result, task_file FROM tasks
|
|
7461
|
-
WHERE assigned_to = 'exe'
|
|
7626
|
+
WHERE (assigned_to = ? OR assigned_to = 'exe')
|
|
7462
7627
|
AND status IN ('open', 'in_progress')
|
|
7463
7628
|
AND task_file LIKE '%review-%'${rScope.sql}
|
|
7464
7629
|
ORDER BY priority ASC
|
|
7465
7630
|
LIMIT 20`,
|
|
7466
|
-
args: [...rScope.args]
|
|
7631
|
+
args: [coordinatorName, ...rScope.args]
|
|
7467
7632
|
});
|
|
7468
7633
|
for (const row of reviews.rows) {
|
|
7469
7634
|
const item = {
|
|
@@ -8250,7 +8415,11 @@ async function ensureShardSchema(client) {
|
|
|
8250
8415
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
8251
8416
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
8252
8417
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
8253
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
8418
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
8419
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
8420
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
8421
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
8422
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
8254
8423
|
]) {
|
|
8255
8424
|
try {
|
|
8256
8425
|
await client.execute(col);
|
|
@@ -8491,7 +8660,10 @@ async function writeMemory(record) {
|
|
|
8491
8660
|
source_path: record.source_path ?? null,
|
|
8492
8661
|
source_type: record.source_type ?? null,
|
|
8493
8662
|
tier: record.tier ?? classifyTier(record),
|
|
8494
|
-
supersedes_id: record.supersedes_id ?? null
|
|
8663
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
8664
|
+
draft: record.draft ? 1 : 0,
|
|
8665
|
+
memory_type: record.memory_type ?? "raw",
|
|
8666
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
8495
8667
|
};
|
|
8496
8668
|
_pendingRecords.push(dbRow);
|
|
8497
8669
|
orgBus.emit({
|
|
@@ -8546,6 +8718,9 @@ async function flushBatch() {
|
|
|
8546
8718
|
const sourceType = row.source_type ?? null;
|
|
8547
8719
|
const tier = row.tier ?? 3;
|
|
8548
8720
|
const supersedesId = row.supersedes_id ?? null;
|
|
8721
|
+
const draft = row.draft ? 1 : 0;
|
|
8722
|
+
const memoryType = row.memory_type ?? "raw";
|
|
8723
|
+
const trajectory = row.trajectory ?? null;
|
|
8549
8724
|
return {
|
|
8550
8725
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
8551
8726
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -8553,15 +8728,15 @@ async function flushBatch() {
|
|
|
8553
8728
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
8554
8729
|
confidence, last_accessed,
|
|
8555
8730
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
8556
|
-
source_path, source_type, tier, supersedes_id)
|
|
8557
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
8731
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
8732
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
8558
8733
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
8559
8734
|
tool_name, project_name,
|
|
8560
8735
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
8561
8736
|
confidence, last_accessed,
|
|
8562
8737
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
8563
|
-
source_path, source_type, tier, supersedes_id)
|
|
8564
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
8738
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
8739
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
8565
8740
|
args: hasVector ? [
|
|
8566
8741
|
row.id,
|
|
8567
8742
|
row.agent_id,
|
|
@@ -8587,7 +8762,10 @@ async function flushBatch() {
|
|
|
8587
8762
|
sourcePath,
|
|
8588
8763
|
sourceType,
|
|
8589
8764
|
tier,
|
|
8590
|
-
supersedesId
|
|
8765
|
+
supersedesId,
|
|
8766
|
+
draft,
|
|
8767
|
+
memoryType,
|
|
8768
|
+
trajectory
|
|
8591
8769
|
] : [
|
|
8592
8770
|
row.id,
|
|
8593
8771
|
row.agent_id,
|
|
@@ -8612,7 +8790,10 @@ async function flushBatch() {
|
|
|
8612
8790
|
sourcePath,
|
|
8613
8791
|
sourceType,
|
|
8614
8792
|
tier,
|
|
8615
|
-
supersedesId
|
|
8793
|
+
supersedesId,
|
|
8794
|
+
draft,
|
|
8795
|
+
memoryType,
|
|
8796
|
+
trajectory
|
|
8616
8797
|
]
|
|
8617
8798
|
};
|
|
8618
8799
|
};
|
|
@@ -8681,6 +8862,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
8681
8862
|
const limit = options?.limit ?? 10;
|
|
8682
8863
|
const statusFilter = options?.includeArchived ? "" : `
|
|
8683
8864
|
AND COALESCE(status, 'active') = 'active'`;
|
|
8865
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
8866
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
8684
8867
|
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
8685
8868
|
tool_name, project_name,
|
|
8686
8869
|
has_error, raw_text, vector, importance, status,
|
|
@@ -8690,7 +8873,7 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
8690
8873
|
source_path, source_type
|
|
8691
8874
|
FROM memories
|
|
8692
8875
|
WHERE agent_id = ?
|
|
8693
|
-
AND vector IS NOT NULL${statusFilter}
|
|
8876
|
+
AND vector IS NOT NULL${statusFilter}${draftFilter}
|
|
8694
8877
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
8695
8878
|
const args = [agentId];
|
|
8696
8879
|
const scope = buildWikiScopeFilter(options, "");
|
|
@@ -8712,6 +8895,10 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
8712
8895
|
sql += ` AND timestamp >= ?`;
|
|
8713
8896
|
args.push(options.since);
|
|
8714
8897
|
}
|
|
8898
|
+
if (options?.memoryType) {
|
|
8899
|
+
sql += ` AND memory_type = ?`;
|
|
8900
|
+
args.push(options.memoryType);
|
|
8901
|
+
}
|
|
8715
8902
|
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
8716
8903
|
args.push(vectorToBlob(queryVector));
|
|
8717
8904
|
sql += ` LIMIT ?`;
|
|
@@ -11608,7 +11795,7 @@ var reconciler_default = createReconciler({
|
|
|
11608
11795
|
unhideTextInstance(node, text) {
|
|
11609
11796
|
setTextNodeValue(node, text);
|
|
11610
11797
|
},
|
|
11611
|
-
getPublicInstance: (
|
|
11798
|
+
getPublicInstance: (instance2) => instance2,
|
|
11612
11799
|
hideInstance(node) {
|
|
11613
11800
|
node.yogaNode?.setDisplay(src_default.DISPLAY_NONE);
|
|
11614
11801
|
},
|
|
@@ -13951,16 +14138,16 @@ var render = (node, options) => {
|
|
|
13951
14138
|
concurrent: false,
|
|
13952
14139
|
...getOptions(options)
|
|
13953
14140
|
};
|
|
13954
|
-
const
|
|
13955
|
-
|
|
14141
|
+
const instance2 = getInstance(inkOptions.stdout, () => new Ink(inkOptions), inkOptions.concurrent ?? false);
|
|
14142
|
+
instance2.render(node);
|
|
13956
14143
|
return {
|
|
13957
|
-
rerender:
|
|
14144
|
+
rerender: instance2.render,
|
|
13958
14145
|
unmount() {
|
|
13959
|
-
|
|
14146
|
+
instance2.unmount();
|
|
13960
14147
|
},
|
|
13961
|
-
waitUntilExit:
|
|
14148
|
+
waitUntilExit: instance2.waitUntilExit,
|
|
13962
14149
|
cleanup: () => instances_default.delete(inkOptions.stdout),
|
|
13963
|
-
clear:
|
|
14150
|
+
clear: instance2.clear
|
|
13964
14151
|
};
|
|
13965
14152
|
};
|
|
13966
14153
|
var render_default = render;
|
|
@@ -13974,14 +14161,14 @@ var getOptions = (stdout = {}) => {
|
|
|
13974
14161
|
return stdout;
|
|
13975
14162
|
};
|
|
13976
14163
|
var getInstance = (stdout, createInstance, concurrent) => {
|
|
13977
|
-
let
|
|
13978
|
-
if (!
|
|
13979
|
-
|
|
13980
|
-
instances_default.set(stdout,
|
|
13981
|
-
} else if (
|
|
13982
|
-
console.warn(`Warning: render() was called with concurrent: ${concurrent}, but the existing instance for this stdout uses concurrent: ${
|
|
13983
|
-
}
|
|
13984
|
-
return
|
|
14164
|
+
let instance2 = instances_default.get(stdout);
|
|
14165
|
+
if (!instance2) {
|
|
14166
|
+
instance2 = createInstance();
|
|
14167
|
+
instances_default.set(stdout, instance2);
|
|
14168
|
+
} else if (instance2.isConcurrent !== concurrent) {
|
|
14169
|
+
console.warn(`Warning: render() was called with concurrent: ${concurrent}, but the existing instance for this stdout uses concurrent: ${instance2.isConcurrent}. The concurrent option only takes effect on the first render. Call unmount() first if you need to change the rendering mode.`);
|
|
14170
|
+
}
|
|
14171
|
+
return instance2;
|
|
13985
14172
|
};
|
|
13986
14173
|
|
|
13987
14174
|
// src/tui/ink/render-to-string.js
|
|
@@ -14896,7 +15083,7 @@ function StatusDot({ status, showLabel = true }) {
|
|
|
14896
15083
|
|
|
14897
15084
|
// src/tui/demo-data.ts
|
|
14898
15085
|
var DEMO_EMPLOYEES = [
|
|
14899
|
-
{ name: "
|
|
15086
|
+
{ name: "atlas", role: "COO", status: "active", activity: "Reviewing nova's task-aware behavior injection PR", memoryCount: 15e3, projects: [
|
|
14900
15087
|
{ name: "exe-os", status: "active" },
|
|
14901
15088
|
{ name: "exe-create", status: "has_tasks" },
|
|
14902
15089
|
{ name: "openclaw", status: "idle" }
|
|
@@ -14905,7 +15092,7 @@ var DEMO_EMPLOYEES = [
|
|
|
14905
15092
|
"Dispatched behavior injection task",
|
|
14906
15093
|
"Approved gateway Phase 4"
|
|
14907
15094
|
] },
|
|
14908
|
-
{ name: "
|
|
15095
|
+
{ name: "nova", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", memoryCount: 8e3, projects: [
|
|
14909
15096
|
{ name: "exe-os", status: "active" },
|
|
14910
15097
|
{ name: "exe-create", status: "idle" }
|
|
14911
15098
|
], recentTasks: [
|
|
@@ -14913,7 +15100,7 @@ var DEMO_EMPLOYEES = [
|
|
|
14913
15100
|
"Fixed TUI mouse listener leak",
|
|
14914
15101
|
"Built task-aware behavior injection"
|
|
14915
15102
|
] },
|
|
14916
|
-
{ name: "
|
|
15103
|
+
{ name: "lina", role: "CMO", status: "idle", activity: "", memoryCount: 2e3, projects: [
|
|
14917
15104
|
{ name: "exe-build-skills", status: "has_tasks" },
|
|
14918
15105
|
{ name: "exe-os", status: "idle" }
|
|
14919
15106
|
], recentTasks: [
|
|
@@ -14921,57 +15108,57 @@ var DEMO_EMPLOYEES = [
|
|
|
14921
15108
|
"Designed exe-os UI system",
|
|
14922
15109
|
"Fixed logo layouts"
|
|
14923
15110
|
] },
|
|
14924
|
-
{ name: "
|
|
15111
|
+
{ name: "devon", role: "Principal Engineer", status: "idle", activity: "", memoryCount: 700, projects: [
|
|
14925
15112
|
{ name: "exe-os", status: "idle" }
|
|
14926
15113
|
], recentTasks: [
|
|
14927
15114
|
"Implemented BashTool sandboxed execution",
|
|
14928
15115
|
"Ported session scoping to exe-agent-memory"
|
|
14929
15116
|
] },
|
|
14930
|
-
{ name: "
|
|
15117
|
+
{ name: "pixel", role: "Content Production", status: "offline", activity: "", memoryCount: 50, projects: [
|
|
14931
15118
|
{ name: "exe-build-skills", status: "idle" }
|
|
14932
15119
|
], recentTasks: [
|
|
14933
15120
|
"Rendered carousel export prototype"
|
|
14934
15121
|
] }
|
|
14935
15122
|
];
|
|
14936
15123
|
var DEMO_ACTIVITY = [
|
|
14937
|
-
{ time: "11:46", agent: "
|
|
14938
|
-
{ time: "11:42", agent: "
|
|
14939
|
-
{ time: "11:38", agent: "
|
|
14940
|
-
{ time: "11:15", agent: "
|
|
14941
|
-
{ time: "10:50", agent: "
|
|
15124
|
+
{ time: "11:46", agent: "nova", action: "Committed skill learning \u2014 trajectory capture + LLM extraction" },
|
|
15125
|
+
{ time: "11:42", agent: "atlas", action: "Dispatched behavior injection task to nova" },
|
|
15126
|
+
{ time: "11:38", agent: "nova", action: "Fixed TUI mouse listener leak + arrow nav" },
|
|
15127
|
+
{ time: "11:15", agent: "lina", action: "Completed exe-os UI design system" },
|
|
15128
|
+
{ time: "10:50", agent: "atlas", action: "Approved gateway Phase 4" }
|
|
14942
15129
|
];
|
|
14943
15130
|
var DEMO_HEALTH = { memories: 25750, daemon: "running", cloud: "disabled" };
|
|
14944
15131
|
var DEMO_PROJECTS = [
|
|
14945
15132
|
{
|
|
14946
15133
|
projectName: "exe-os",
|
|
14947
|
-
exeSession: "
|
|
15134
|
+
exeSession: "atlas1",
|
|
14948
15135
|
employees: [
|
|
14949
|
-
{ name: "
|
|
14950
|
-
{ name: "
|
|
14951
|
-
{ name: "
|
|
14952
|
-
{ name: "
|
|
15136
|
+
{ name: "atlas", role: "COO", status: "active", activity: "Reviewing nova's task-aware behavior injection PR", sessionName: "atlas1" },
|
|
15137
|
+
{ name: "nova", role: "CTO", status: "active", activity: "Implementing skill learning trajectory capture", sessionName: "nova-atlas1" },
|
|
15138
|
+
{ name: "lina", role: "CMO", status: "offline", activity: "", sessionName: "lina-atlas1" },
|
|
15139
|
+
{ name: "devon", role: "PE", status: "offline", activity: "", sessionName: "devon-atlas1" }
|
|
14953
15140
|
]
|
|
14954
15141
|
},
|
|
14955
15142
|
{
|
|
14956
15143
|
projectName: "exe-build-skills",
|
|
14957
|
-
exeSession: "
|
|
15144
|
+
exeSession: "atlas2",
|
|
14958
15145
|
employees: [
|
|
14959
|
-
{ name: "
|
|
14960
|
-
{ name: "
|
|
15146
|
+
{ name: "atlas", role: "COO", status: "active", activity: "Planning carousel pipeline automation", sessionName: "atlas2" },
|
|
15147
|
+
{ name: "lina", role: "CMO", status: "active", activity: "Creating CTO Breakdowns LinkedIn carousel", sessionName: "lina-atlas2" }
|
|
14961
15148
|
]
|
|
14962
15149
|
},
|
|
14963
15150
|
{
|
|
14964
15151
|
projectName: "CMO",
|
|
14965
|
-
exeSession: "
|
|
15152
|
+
exeSession: "atlas3",
|
|
14966
15153
|
employees: [
|
|
14967
|
-
{ name: "
|
|
14968
|
-
{ name: "
|
|
14969
|
-
{ name: "
|
|
15154
|
+
{ name: "atlas", role: "COO", status: "active", activity: "Planning carousel pipeline automation", sessionName: "atlas3" },
|
|
15155
|
+
{ name: "lina", role: "CMO", status: "active", activity: "Creating CTO Breakdowns carousel", sessionName: "lina-atlas3" },
|
|
15156
|
+
{ name: "pixel", role: "Content Production", status: "idle", activity: "", sessionName: "pixel-atlas3" }
|
|
14970
15157
|
]
|
|
14971
15158
|
}
|
|
14972
15159
|
];
|
|
14973
15160
|
var DEMO_PANE_OUTPUT = {
|
|
14974
|
-
"
|
|
15161
|
+
"nova-atlas1": [
|
|
14975
15162
|
"",
|
|
14976
15163
|
"\u23FA Reading src/lib/skill-learning.ts...",
|
|
14977
15164
|
"",
|
|
@@ -15010,9 +15197,9 @@ var DEMO_PANE_OUTPUT = {
|
|
|
15010
15197
|
"",
|
|
15011
15198
|
"\u23FA All 16 tests pass. Committing."
|
|
15012
15199
|
],
|
|
15013
|
-
"
|
|
15200
|
+
"atlas1": [
|
|
15014
15201
|
"",
|
|
15015
|
-
"\u23FA Let me review
|
|
15202
|
+
"\u23FA Let me review nova's latest commits...",
|
|
15016
15203
|
"",
|
|
15017
15204
|
"\u23FA Bash(git log --oneline -5)",
|
|
15018
15205
|
" \u23BF 26abae6 feat: skill learning \u2014 trajectory capture",
|
|
@@ -15025,16 +15212,16 @@ var DEMO_PANE_OUTPUT = {
|
|
|
15025
15212
|
"",
|
|
15026
15213
|
"\u23FA Verified: build passes, zero type errors in changed files.",
|
|
15027
15214
|
"",
|
|
15028
|
-
"\u23FA Dispatching next task to
|
|
15215
|
+
"\u23FA Dispatching next task to nova: TUI demo mode.",
|
|
15029
15216
|
"",
|
|
15030
15217
|
"\u23FA mcp__exe-mem__create_task",
|
|
15031
|
-
' \u23BF Task created: "Build TUI demo mode" assigned to
|
|
15218
|
+
' \u23BF Task created: "Build TUI demo mode" assigned to nova [p0]'
|
|
15032
15219
|
],
|
|
15033
|
-
"
|
|
15220
|
+
"lina-atlas2": [
|
|
15034
15221
|
"",
|
|
15035
15222
|
"\u23FA Starting carousel creation for CTO Breakdowns series...",
|
|
15036
15223
|
"",
|
|
15037
|
-
"\u23FA Read(exe/
|
|
15224
|
+
"\u23FA Read(exe/lina/create-cto-breakdowns-linkedin-carousel.md)",
|
|
15038
15225
|
" \u23BF Task: Create a 10-slide LinkedIn carousel about the",
|
|
15039
15226
|
" CTO Breakdowns podcast launch",
|
|
15040
15227
|
"",
|
|
@@ -15048,11 +15235,11 @@ var DEMO_PANE_OUTPUT = {
|
|
|
15048
15235
|
"\u23FA Write(exe/output/cto-breakdowns-carousel-v1.md)",
|
|
15049
15236
|
" \u23BF 10 slides drafted with hooks, key points, CTAs"
|
|
15050
15237
|
],
|
|
15051
|
-
"
|
|
15238
|
+
"atlas2": [
|
|
15052
15239
|
"",
|
|
15053
15240
|
"\u23FA Planning the carousel pipeline automation...",
|
|
15054
15241
|
"",
|
|
15055
|
-
"\u23FA Current workflow:
|
|
15242
|
+
"\u23FA Current workflow: lina writes content \u2192 pixel produces visuals",
|
|
15056
15243
|
" \u2192 manual export. Need to automate the export step.",
|
|
15057
15244
|
"",
|
|
15058
15245
|
"\u23FA mcp__exe-mem__recall_my_memory",
|
|
@@ -15061,18 +15248,18 @@ var DEMO_PANE_OUTPUT = {
|
|
|
15061
15248
|
"",
|
|
15062
15249
|
"\u23FA Good \u2014 the pipeline exists. Just need to wire it."
|
|
15063
15250
|
],
|
|
15064
|
-
"
|
|
15251
|
+
"lina-atlas3": [
|
|
15065
15252
|
"",
|
|
15066
15253
|
"\u23FA Reading carousel task brief...",
|
|
15067
15254
|
"",
|
|
15068
|
-
"\u23FA Read(exe/
|
|
15255
|
+
"\u23FA Read(exe/lina/create-cto-breakdowns-carousel.md)",
|
|
15069
15256
|
" \u23BF 10-slide carousel for CTO Breakdowns podcast launch",
|
|
15070
15257
|
"",
|
|
15071
15258
|
"\u23FA Applying Exe Foundry Bold: Epilogue headlines, #F5D76E gold CTAs",
|
|
15072
15259
|
"",
|
|
15073
15260
|
"\u23FA Writing slide hooks and copy..."
|
|
15074
15261
|
],
|
|
15075
|
-
"
|
|
15262
|
+
"atlas3": [
|
|
15076
15263
|
"",
|
|
15077
15264
|
"\u23FA Welcome to the exe-os demo!",
|
|
15078
15265
|
"",
|
|
@@ -15091,13 +15278,13 @@ var DEMO_PANE_OUTPUT = {
|
|
|
15091
15278
|
]
|
|
15092
15279
|
};
|
|
15093
15280
|
var DEMO_TASKS = [
|
|
15094
|
-
{ priority: "P0", title: "Build TUI demo mode with mock sessions", assignee: "
|
|
15095
|
-
{ priority: "P0", title: "Wire exe-os hooks into runtime HookPipeline", assignee: "
|
|
15096
|
-
{ priority: "P1", title: "Create CTO Breakdowns LinkedIn carousel", assignee: "
|
|
15097
|
-
{ priority: "P1", title: "Implement session scoping enforcement", assignee: "
|
|
15098
|
-
{ priority: "P1", title: "Automate carousel export pipeline", assignee: "
|
|
15099
|
-
{ priority: "P2", title: "Add Chutes provider to gateway failover", assignee: "
|
|
15100
|
-
{ priority: "P2", title: "Port skill learning to exe-ai-employees", assignee: "
|
|
15281
|
+
{ priority: "P0", title: "Build TUI demo mode with mock sessions", assignee: "nova", status: "in_progress", project: "exe-os" },
|
|
15282
|
+
{ priority: "P0", title: "Wire exe-os hooks into runtime HookPipeline", assignee: "nova", status: "done", project: "exe-os" },
|
|
15283
|
+
{ priority: "P1", title: "Create CTO Breakdowns LinkedIn carousel", assignee: "lina", status: "in_progress", project: "exe-build-skills" },
|
|
15284
|
+
{ priority: "P1", title: "Implement session scoping enforcement", assignee: "nova", status: "done", project: "exe-os" },
|
|
15285
|
+
{ priority: "P1", title: "Automate carousel export pipeline", assignee: "pixel", status: "open", project: "exe-build-skills" },
|
|
15286
|
+
{ priority: "P2", title: "Add Chutes provider to gateway failover", assignee: "devon", status: "open", project: "exe-os" },
|
|
15287
|
+
{ priority: "P2", title: "Port skill learning to exe-ai-employees", assignee: "nova", status: "open", project: "exe-os" }
|
|
15101
15288
|
];
|
|
15102
15289
|
var DEMO_EXTERNAL_AGENTS = [
|
|
15103
15290
|
{ name: "exec-assistant", role: "executive assistant", status: "active" },
|
|
@@ -15510,13 +15697,13 @@ Ethos:
|
|
|
15510
15697
|
- Founder zero-ego. Distributors and customers are the loudest voice.
|
|
15511
15698
|
- Crypto values: big companies should not own consumer/SMB AI.
|
|
15512
15699
|
|
|
15513
|
-
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
|
|
15700
|
+
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.
|
|
15514
15701
|
|
|
15515
15702
|
Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of truth for all architectural and product decisions.
|
|
15516
15703
|
|
|
15517
15704
|
OPERATING PROCEDURES (mandatory for all employees):
|
|
15518
15705
|
|
|
15519
|
-
You report to the COO. All work flows through
|
|
15706
|
+
You report to the COO. All work flows through the COO. These procedures are non-negotiable.
|
|
15520
15707
|
|
|
15521
15708
|
1. BEFORE starting work:
|
|
15522
15709
|
- 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.
|
|
@@ -15541,15 +15728,15 @@ You report to the COO. All work flows through exe. These procedures are non-nego
|
|
|
15541
15728
|
- Include what was done, decisions made, and any issues
|
|
15542
15729
|
- 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.
|
|
15543
15730
|
- NEVER let a failed commit, a loop, or an error prevent you from calling update_task(done).
|
|
15544
|
-
- Do NOT use close_task \u2014 that is reserved for reviewers
|
|
15731
|
+
- Do NOT use close_task \u2014 that is reserved for reviewers to finalize after review.
|
|
15545
15732
|
|
|
15546
15733
|
4. AFTER update_task(done) \u2014 COMMIT (best-effort, do NOT let this block):
|
|
15547
15734
|
- If your task changed system structure, update exe/ARCHITECTURE.md first.
|
|
15548
15735
|
- 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.
|
|
15549
15736
|
- If you are NOT in a git repo, skip entirely. NEVER run \`git init\`.
|
|
15550
15737
|
- If the commit fails, note it but move on \u2014 the work is already marked done via update_task.
|
|
15551
|
-
- Do NOT push \u2014
|
|
15552
|
-
- NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch.
|
|
15738
|
+
- Do NOT push \u2014 the COO reviews commits and decides what to push.
|
|
15739
|
+
- 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.
|
|
15553
15740
|
|
|
15554
15741
|
5. AFTER commit \u2014 REPORT (best-effort):
|
|
15555
15742
|
Use store_memory to write a structured summary. Include: project name, what was done,
|
|
@@ -15563,7 +15750,7 @@ You report to the COO. All work flows through exe. These procedures are non-nego
|
|
|
15563
15750
|
|
|
15564
15751
|
7. AFTER reporting \u2014 CHECK FOR NEXT WORK (mandatory):
|
|
15565
15752
|
- 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.
|
|
15566
|
-
- 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
|
|
15753
|
+
- 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.
|
|
15567
15754
|
- Then: re-read your task folder: exe/<your-name>/
|
|
15568
15755
|
- If there are more open tasks, start the next highest-priority one (go to step 1)
|
|
15569
15756
|
- If no more open tasks AND no pending reviews AND no blocked tasks you can fix, tell the user: "All tasks complete. Anything else?"
|
|
@@ -15580,7 +15767,7 @@ DO NOT keep working degraded. Instead:
|
|
|
15580
15767
|
Format the text as: "CONTEXT CHECKPOINT [<task-id>]: <summary>"
|
|
15581
15768
|
Include: task ID + title, what you completed, what's left, open decisions or blockers, key file paths.
|
|
15582
15769
|
|
|
15583
|
-
2. Send intercom to
|
|
15770
|
+
2. Send intercom to the COO session to trigger kill + relaunch:
|
|
15584
15771
|
MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
|
|
15585
15772
|
EXE_SESSION="\${MY_SESSION#\${AGENT_ID}-}"
|
|
15586
15773
|
tmux send-keys -t "$EXE_SESSION" "/exe-intercom context-full: \${AGENT_ID} hit capacity. Checkpoint saved. Resume task <task-id>." Enter
|
|
@@ -15588,8 +15775,8 @@ DO NOT keep working degraded. Instead:
|
|
|
15588
15775
|
3. Stop working immediately. Do not attempt to continue with degraded context.
|
|
15589
15776
|
|
|
15590
15777
|
COMMUNICATION CHAIN \u2014 who you talk to:
|
|
15591
|
-
- You report to the COO. Your completion reports, status updates, and questions go to
|
|
15592
|
-
- Do NOT address the human user directly for decisions, permissions, or status updates. That's
|
|
15778
|
+
- You report to the COO. Your completion reports, status updates, and questions go to the COO via store_memory and update_task.
|
|
15779
|
+
- 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.
|
|
15593
15780
|
- Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
|
|
15594
15781
|
|
|
15595
15782
|
SKILL CAPTURE (encouraged, not mandatory):
|
|
@@ -15744,6 +15931,14 @@ function CommandCenterView({
|
|
|
15744
15931
|
setPermPrompt(req);
|
|
15745
15932
|
});
|
|
15746
15933
|
};
|
|
15934
|
+
let agentId = "default";
|
|
15935
|
+
if (agentRole === "CTO") {
|
|
15936
|
+
try {
|
|
15937
|
+
const { getEmployeeByRole: getEmployeeByRole2, loadEmployeesSync: loadEmployeesSync2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
15938
|
+
agentId = getEmployeeByRole2(loadEmployeesSync2(), "CTO")?.name ?? "default";
|
|
15939
|
+
} catch {
|
|
15940
|
+
}
|
|
15941
|
+
}
|
|
15747
15942
|
setAgentConfig({
|
|
15748
15943
|
provider,
|
|
15749
15944
|
model,
|
|
@@ -15754,7 +15949,7 @@ function CommandCenterView({
|
|
|
15754
15949
|
permissionHandler,
|
|
15755
15950
|
maxTurns: 20,
|
|
15756
15951
|
cwd: process.cwd(),
|
|
15757
|
-
agentId
|
|
15952
|
+
agentId
|
|
15758
15953
|
});
|
|
15759
15954
|
setChatInitError(null);
|
|
15760
15955
|
} catch (e) {
|
|
@@ -15947,10 +16142,12 @@ function CommandCenterView({
|
|
|
15947
16142
|
const registry = listSessions2();
|
|
15948
16143
|
const tmuxSessions = inTmux2() ? new Set(listTmuxSessions2()) : /* @__PURE__ */ new Set();
|
|
15949
16144
|
const roster = await loadEmployees2();
|
|
15950
|
-
const
|
|
16145
|
+
const { getCoordinatorName: getCoordinatorName2, isCoordinatorRole: isCoordinatorRole2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
16146
|
+
const coordinatorName = getCoordinatorName2(roster);
|
|
16147
|
+
const employeeNames = roster.filter((e) => !isCoordinatorRole2(e.role)).map((e) => e.name);
|
|
15951
16148
|
const projectSessions = /* @__PURE__ */ new Map();
|
|
15952
16149
|
for (const entry of registry) {
|
|
15953
|
-
if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
|
|
16150
|
+
if ((entry.agentId === coordinatorName || entry.agentId === "exe") && tmuxSessions.has(entry.windowName)) {
|
|
15954
16151
|
const projName = entry.projectDir.split("/").filter(Boolean).pop() ?? "";
|
|
15955
16152
|
if (projName) {
|
|
15956
16153
|
projectSessions.set(projName, { exeSession: entry.windowName, projectDir: entry.projectDir });
|
|
@@ -16321,7 +16518,7 @@ function TmuxPane({ sessionName, employeeName, employeeRole, projectName, onDeta
|
|
|
16321
16518
|
/* @__PURE__ */ jsx8(Text, { color: "#3D3660", children: "\u2500".repeat(process.stdout.columns ? process.stdout.columns - 30 : 120) }),
|
|
16322
16519
|
/* @__PURE__ */ jsxs6(Box_default, { paddingX: 1, children: [
|
|
16323
16520
|
/* @__PURE__ */ jsx8(Text, { color: "#6B4C9A", children: "\u276F " }),
|
|
16324
|
-
/* @__PURE__ */ jsx8(Text, { children: inputBuffer || (demo ? "Type a message to
|
|
16521
|
+
/* @__PURE__ */ jsx8(Text, { children: inputBuffer || (demo ? "Type a message to the coordinator..." : "Type to send commands...") })
|
|
16325
16522
|
] })
|
|
16326
16523
|
] });
|
|
16327
16524
|
}
|
|
@@ -16340,22 +16537,26 @@ function useOrchestrator(enabled = true) {
|
|
|
16340
16537
|
const [isLoading, setIsLoading] = useState8(true);
|
|
16341
16538
|
const orchestratorRef = useRef5(null);
|
|
16342
16539
|
const exeSessionRef = useRef5("exe1");
|
|
16540
|
+
const coordinatorNameRef = useRef5("exe");
|
|
16343
16541
|
useEffect10(() => {
|
|
16344
16542
|
if (!enabled) return;
|
|
16345
16543
|
let cancelled = false;
|
|
16346
16544
|
async function init() {
|
|
16347
16545
|
try {
|
|
16348
16546
|
const { MultiAgentOrchestrator: MultiAgentOrchestrator2 } = await Promise.resolve().then(() => (init_orchestrator(), orchestrator_exports));
|
|
16349
|
-
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
16547
|
+
const { getCoordinatorName: getCoordinatorName2, isCoordinatorRole: isCoordinatorRole2, loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
16548
|
+
const { isExeSession: isExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
16350
16549
|
const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
|
|
16351
16550
|
if (!inTmux2()) {
|
|
16352
16551
|
setIsLoading(false);
|
|
16353
16552
|
return;
|
|
16354
16553
|
}
|
|
16355
16554
|
const roster = await loadEmployees2();
|
|
16356
|
-
const
|
|
16555
|
+
const coordinatorName = getCoordinatorName2(roster);
|
|
16556
|
+
coordinatorNameRef.current = coordinatorName;
|
|
16557
|
+
const nonExe = roster.filter((e) => !isCoordinatorRole2(e.role));
|
|
16357
16558
|
const tmuxSessions = listTmuxSessions2();
|
|
16358
|
-
const exeSession = tmuxSessions.find((s) =>
|
|
16559
|
+
const exeSession = tmuxSessions.find((s) => isExeSession2(s)) ?? `${coordinatorName}1`;
|
|
16359
16560
|
exeSessionRef.current = exeSession;
|
|
16360
16561
|
orchestratorRef.current = new MultiAgentOrchestrator2({
|
|
16361
16562
|
employees: nonExe,
|
|
@@ -16388,8 +16589,8 @@ function useOrchestrator(enabled = true) {
|
|
|
16388
16589
|
const uoScope = sessionScopeFilter2();
|
|
16389
16590
|
const result = await client.execute({
|
|
16390
16591
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
16391
|
-
WHERE assigned_to = 'exe' AND status IN ('open', 'in_progress')${uoScope.sql}`,
|
|
16392
|
-
args: [...uoScope.args]
|
|
16592
|
+
WHERE (assigned_to = ? OR assigned_to = 'exe') AND status IN ('open', 'in_progress')${uoScope.sql}`,
|
|
16593
|
+
args: [coordinatorNameRef.current, ...uoScope.args]
|
|
16393
16594
|
});
|
|
16394
16595
|
if (!cancelled) setPendingReviews(Number(result.rows[0]?.cnt ?? 0));
|
|
16395
16596
|
} catch {
|
|
@@ -16417,10 +16618,11 @@ function useOrchestrator(enabled = true) {
|
|
|
16417
16618
|
try {
|
|
16418
16619
|
const { createTaskCore: createTaskCore2 } = await Promise.resolve().then(() => (init_tasks_crud(), tasks_crud_exports));
|
|
16419
16620
|
const { ensureEmployee: ensureEmployee2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
16621
|
+
const { getCoordinatorName: getCoordinatorName2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
16420
16622
|
await createTaskCore2({
|
|
16421
16623
|
title: `Session launched for ${agentId} (TUI)`,
|
|
16422
16624
|
assignedTo: agentId,
|
|
16423
|
-
assignedBy:
|
|
16625
|
+
assignedBy: getCoordinatorName2(),
|
|
16424
16626
|
projectName: "exe-os",
|
|
16425
16627
|
priority: "p2",
|
|
16426
16628
|
context: "Session spawned from TUI Sessions view. Agent will pick up any queued tasks via intercom.",
|
|
@@ -16473,6 +16675,9 @@ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
|
16473
16675
|
var SESSION_REFRESH_MS = 5e3;
|
|
16474
16676
|
var ACTIVE_PANE_PATTERN = /█|░|Calculating|tokens|writing|Reading/;
|
|
16475
16677
|
var ACTIVITY_PREVIEW_MAX = 50;
|
|
16678
|
+
function isCoordinatorEntry(entry) {
|
|
16679
|
+
return entry.role.toLowerCase() === "coo" || entry.name === "exe";
|
|
16680
|
+
}
|
|
16476
16681
|
function SessionsView({
|
|
16477
16682
|
initialProject,
|
|
16478
16683
|
onConsumeInitialProject,
|
|
@@ -16518,13 +16723,13 @@ function SessionsView({
|
|
|
16518
16723
|
const proj = projects.find((p) => p.projectName === projectName);
|
|
16519
16724
|
if (!proj) return;
|
|
16520
16725
|
const active = proj.employees.filter(
|
|
16521
|
-
(e) => e.status === "active" || demo && e
|
|
16726
|
+
(e) => e.status === "active" || demo && isCoordinatorEntry(e)
|
|
16522
16727
|
);
|
|
16523
16728
|
const carousel = demo ? proj.employees.filter((e) => e.status !== "offline") : active;
|
|
16524
16729
|
if (carousel.length === 0) return;
|
|
16525
16730
|
const sorted = [
|
|
16526
|
-
...carousel.filter((e) => e
|
|
16527
|
-
...carousel.filter((e) => e
|
|
16731
|
+
...carousel.filter((e) => isCoordinatorEntry(e)),
|
|
16732
|
+
...carousel.filter((e) => !isCoordinatorEntry(e))
|
|
16528
16733
|
];
|
|
16529
16734
|
setCarouselEmployees(sorted);
|
|
16530
16735
|
setCarouselIdx(0);
|
|
@@ -16550,7 +16755,7 @@ function SessionsView({
|
|
|
16550
16755
|
}
|
|
16551
16756
|
async function launchSession(entry, projectName, projectDir) {
|
|
16552
16757
|
try {
|
|
16553
|
-
if (entry
|
|
16758
|
+
if (!isCoordinatorEntry(entry) && !demo) {
|
|
16554
16759
|
const result = await orch.spawnSession(entry.name);
|
|
16555
16760
|
if (!result || result.status === "failed") {
|
|
16556
16761
|
process.stderr.write(
|
|
@@ -16666,7 +16871,8 @@ function SessionsView({
|
|
|
16666
16871
|
try {
|
|
16667
16872
|
const { listSessions: listSessions2 } = await Promise.resolve().then(() => (init_session_registry(), session_registry_exports));
|
|
16668
16873
|
const { listTmuxSessions: listTmuxSessions2, inTmux: inTmux2, capturePaneLines: capturePaneLines2, parseActivity: parseActivity2 } = await Promise.resolve().then(() => (init_tmux_status(), tmux_status_exports));
|
|
16669
|
-
const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
16874
|
+
const { getCoordinatorName: getCoordinatorName2, isCoordinatorRole: isCoordinatorRole2, loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
|
|
16875
|
+
const { isExeSession: isExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
16670
16876
|
const { execSync: execSync10 } = await import("child_process");
|
|
16671
16877
|
if (!inTmux2()) {
|
|
16672
16878
|
setTmuxAvailable(false);
|
|
@@ -16691,14 +16897,15 @@ function SessionsView({
|
|
|
16691
16897
|
const registry = listSessions2();
|
|
16692
16898
|
const tmuxSessions = new Set(listTmuxSessions2());
|
|
16693
16899
|
const roster = await loadEmployees2();
|
|
16900
|
+
const coordinatorName = getCoordinatorName2(roster);
|
|
16694
16901
|
const exeSessions = /* @__PURE__ */ new Map();
|
|
16695
16902
|
for (const entry of registry) {
|
|
16696
|
-
if (entry.agentId === "exe" && tmuxSessions.has(entry.windowName)) {
|
|
16903
|
+
if ((entry.agentId === coordinatorName || entry.agentId === "exe") && tmuxSessions.has(entry.windowName)) {
|
|
16697
16904
|
exeSessions.set(entry.windowName, entry.projectDir);
|
|
16698
16905
|
}
|
|
16699
16906
|
}
|
|
16700
16907
|
for (const s of tmuxSessions) {
|
|
16701
|
-
if (
|
|
16908
|
+
if (isExeSession2(s) && !exeSessions.has(s)) {
|
|
16702
16909
|
exeSessions.set(s, "");
|
|
16703
16910
|
}
|
|
16704
16911
|
}
|
|
@@ -16713,7 +16920,7 @@ function SessionsView({
|
|
|
16713
16920
|
exeStatus = statusFromPaneLines(exeLines);
|
|
16714
16921
|
exeActivity = exeLines.length > 0 ? parseActivity2(exeLines) : "";
|
|
16715
16922
|
}
|
|
16716
|
-
const employeeEntries = roster.filter((e) => e.
|
|
16923
|
+
const employeeEntries = roster.filter((e) => !isCoordinatorRole2(e.role)).map((emp) => {
|
|
16717
16924
|
const sessionName = `${emp.name}-${exeSession}`;
|
|
16718
16925
|
const hasSession = tmuxSessions.has(sessionName);
|
|
16719
16926
|
const isCrashed = !hasSession && orch.crashedSessions.includes(emp.name);
|
|
@@ -16749,7 +16956,7 @@ function SessionsView({
|
|
|
16749
16956
|
});
|
|
16750
16957
|
const employees = [
|
|
16751
16958
|
{
|
|
16752
|
-
name:
|
|
16959
|
+
name: coordinatorName,
|
|
16753
16960
|
role: "COO",
|
|
16754
16961
|
status: exeStatus,
|
|
16755
16962
|
sessionName: exeSession,
|
|
@@ -16989,7 +17196,7 @@ function TasksView({ onBack }) {
|
|
|
16989
17196
|
setAllTasks(DEMO_TASKS.map((t, i) => ({
|
|
16990
17197
|
id: `demo-${i}`,
|
|
16991
17198
|
...t,
|
|
16992
|
-
assignedBy: "
|
|
17199
|
+
assignedBy: "coordinator",
|
|
16993
17200
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16994
17201
|
result: t.status === "done" ? "Completed successfully" : ""
|
|
16995
17202
|
})));
|
|
@@ -18003,7 +18210,7 @@ function TeamView({ onBack, onViewSessions }) {
|
|
|
18003
18210
|
showAddHint && /* @__PURE__ */ jsx12(Text, { color: "#F5D76E", children: "Run /exe-new-employee <name> from CLI to add an employee." }),
|
|
18004
18211
|
!demo && orch.pendingReviews > 0 && /* @__PURE__ */ jsxs10(Text, { color: "#6B4C9A", children: [
|
|
18005
18212
|
orch.pendingReviews,
|
|
18006
|
-
" review(s) pending
|
|
18213
|
+
" review(s) pending coordinator attention"
|
|
18007
18214
|
] }),
|
|
18008
18215
|
/* @__PURE__ */ jsx12(Text, { children: " " }),
|
|
18009
18216
|
/* @__PURE__ */ jsx12(Text, { bold: true, children: "INTERNAL" }),
|
|
@@ -18101,19 +18308,19 @@ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-r
|
|
|
18101
18308
|
var PANELS = ["Workspaces", "Documents", "Chat"];
|
|
18102
18309
|
var MAX_VISIBLE_MESSAGES = 12;
|
|
18103
18310
|
var DEMO_SEARCH_RESULTS = [
|
|
18104
|
-
{ id: "1", agentId: "
|
|
18105
|
-
{ id: "2", agentId: "
|
|
18106
|
-
{ id: "3", agentId: "
|
|
18107
|
-
{ id: "4", agentId: "
|
|
18108
|
-
{ id: "5", agentId: "
|
|
18311
|
+
{ id: "1", agentId: "nova", rawText: "Reviewed PR #42 \u2014 approved with minor comment on error handling pattern", timestamp: new Date(Date.now() - 2 * 36e5).toISOString(), projectName: "exe-os" },
|
|
18312
|
+
{ id: "2", agentId: "devon", rawText: "Implemented session routing with deterministic naming: employee-coordinator convention", timestamp: new Date(Date.now() - 5 * 36e5).toISOString(), projectName: "exe-os" },
|
|
18313
|
+
{ id: "3", agentId: "atlas", rawText: "Status brief: 3 tasks completed, 1 blocked on wiki integration", timestamp: new Date(Date.now() - 8 * 36e5).toISOString(), projectName: "exe-os" },
|
|
18314
|
+
{ id: "4", agentId: "lina", rawText: "Created brand guidelines document \u2014 Exe Foundry Bold design system", timestamp: new Date(Date.now() - 24 * 36e5).toISOString(), projectName: "exe-os" },
|
|
18315
|
+
{ id: "5", agentId: "devon", rawText: "Fixed CommandCenter project filtering \u2014 DB-first, no random directories", timestamp: new Date(Date.now() - 48 * 36e5).toISOString(), projectName: "exe-os" }
|
|
18109
18316
|
];
|
|
18110
18317
|
function agentColor(agentId) {
|
|
18111
18318
|
switch (agentId.toLowerCase()) {
|
|
18112
|
-
case "
|
|
18319
|
+
case "atlas":
|
|
18113
18320
|
return "#F5D76E";
|
|
18114
|
-
case "
|
|
18321
|
+
case "nova":
|
|
18115
18322
|
return "#3B82F6";
|
|
18116
|
-
case "
|
|
18323
|
+
case "lina":
|
|
18117
18324
|
return "#6B4C9A";
|
|
18118
18325
|
default:
|
|
18119
18326
|
return "#F0EDE8";
|
|
@@ -19158,7 +19365,9 @@ function App2() {
|
|
|
19158
19365
|
stdin.unref = () => stdin;
|
|
19159
19366
|
}
|
|
19160
19367
|
}
|
|
19161
|
-
render_default(/* @__PURE__ */ jsx17(App2, {}));
|
|
19368
|
+
var instance = render_default(/* @__PURE__ */ jsx17(App2, {}));
|
|
19369
|
+
instance.waitUntilExit().catch(() => {
|
|
19370
|
+
});
|
|
19162
19371
|
{
|
|
19163
19372
|
const CLEANUP_SEQ = "\x1B[?1006l\x1B[?1002l\x1B[?1000l\x1B[?25h\x1B[?1049l";
|
|
19164
19373
|
const terminalCleanup = () => {
|