@askexenow/exe-os 0.8.80 → 0.8.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/backfill-conversations.js +359 -267
- package/dist/bin/backfill-responses.js +357 -265
- package/dist/bin/backfill-vectors.js +339 -264
- package/dist/bin/cleanup-stale-review-tasks.js +315 -256
- package/dist/bin/cli.js +494 -240
- package/dist/bin/exe-agent.js +141 -46
- package/dist/bin/exe-assign.js +151 -63
- package/dist/bin/exe-boot.js +294 -115
- package/dist/bin/exe-call.js +76 -51
- package/dist/bin/exe-cloud.js +58 -45
- package/dist/bin/exe-dispatch.js +434 -277
- package/dist/bin/exe-doctor.js +317 -246
- package/dist/bin/exe-export-behaviors.js +328 -248
- package/dist/bin/exe-forget.js +314 -231
- package/dist/bin/exe-gateway.js +2676 -1402
- package/dist/bin/exe-heartbeat.js +329 -264
- package/dist/bin/exe-kill.js +324 -244
- package/dist/bin/exe-launch-agent.js +574 -463
- package/dist/bin/exe-link.js +1055 -95
- package/dist/bin/exe-new-employee.js +49 -54
- package/dist/bin/exe-pending-messages.js +310 -253
- package/dist/bin/exe-pending-notifications.js +299 -228
- package/dist/bin/exe-pending-reviews.js +314 -245
- package/dist/bin/exe-rename.js +259 -195
- package/dist/bin/exe-review.js +140 -64
- package/dist/bin/exe-search.js +543 -356
- package/dist/bin/exe-session-cleanup.js +463 -382
- package/dist/bin/exe-settings.js +129 -99
- package/dist/bin/exe-start.sh +6 -6
- package/dist/bin/exe-status.js +95 -36
- package/dist/bin/exe-team.js +116 -51
- package/dist/bin/git-sweep.js +482 -307
- package/dist/bin/graph-backfill.js +357 -245
- package/dist/bin/graph-export.js +324 -244
- package/dist/bin/install.js +33 -10
- package/dist/bin/scan-tasks.js +481 -307
- package/dist/bin/setup.js +1147 -140
- package/dist/bin/shard-migrate.js +321 -241
- package/dist/bin/update.js +1 -7
- package/dist/bin/wiki-sync.js +318 -238
- package/dist/gateway/index.js +2656 -1383
- package/dist/hooks/bug-report-worker.js +641 -472
- package/dist/hooks/commit-complete.js +482 -307
- package/dist/hooks/error-recall.js +363 -135
- package/dist/hooks/exe-heartbeat-hook.js +97 -27
- package/dist/hooks/ingest-worker.js +584 -397
- package/dist/hooks/ingest.js +123 -58
- package/dist/hooks/instructions-loaded.js +212 -82
- package/dist/hooks/notification.js +200 -70
- package/dist/hooks/post-compact.js +199 -81
- package/dist/hooks/pre-compact.js +352 -140
- package/dist/hooks/pre-tool-use.js +416 -278
- package/dist/hooks/prompt-ingest-worker.js +376 -299
- package/dist/hooks/prompt-submit.js +414 -188
- package/dist/hooks/response-ingest-worker.js +408 -338
- package/dist/hooks/session-end.js +209 -83
- package/dist/hooks/session-start.js +382 -158
- package/dist/hooks/stop.js +209 -83
- package/dist/hooks/subagent-stop.js +209 -85
- package/dist/hooks/summary-worker.js +606 -510
- package/dist/index.js +2133 -855
- package/dist/lib/cloud-sync.js +1175 -184
- package/dist/lib/config.js +1 -9
- package/dist/lib/consolidation.js +71 -34
- package/dist/lib/database.js +166 -14
- package/dist/lib/device-registry.js +189 -117
- package/dist/lib/embedder.js +6 -10
- package/dist/lib/employee-templates.js +134 -39
- package/dist/lib/employees.js +30 -7
- package/dist/lib/exe-daemon-client.js +5 -7
- package/dist/lib/exe-daemon.js +514 -152
- package/dist/lib/hybrid-search.js +543 -356
- package/dist/lib/identity-templates.js +15 -15
- package/dist/lib/identity.js +19 -15
- package/dist/lib/license.js +1 -7
- package/dist/lib/messaging.js +157 -135
- package/dist/lib/reminders.js +97 -0
- package/dist/lib/schedules.js +302 -231
- package/dist/lib/skill-learning.js +33 -27
- package/dist/lib/status-brief.js +11 -14
- package/dist/lib/store.js +326 -237
- package/dist/lib/task-router.js +105 -1
- package/dist/lib/tasks.js +233 -116
- package/dist/lib/tmux-routing.js +173 -56
- package/dist/lib/ws-client.js +13 -3
- package/dist/mcp/server.js +2009 -1015
- package/dist/mcp/tools/complete-reminder.js +97 -0
- package/dist/mcp/tools/create-reminder.js +97 -0
- package/dist/mcp/tools/create-task.js +426 -262
- package/dist/mcp/tools/deactivate-behavior.js +119 -44
- package/dist/mcp/tools/list-reminders.js +97 -0
- package/dist/mcp/tools/list-tasks.js +56 -57
- package/dist/mcp/tools/send-message.js +206 -143
- package/dist/mcp/tools/update-task.js +259 -85
- package/dist/runtime/index.js +495 -316
- package/dist/tui/App.js +1128 -919
- package/package.json +2 -10
- package/src/commands/exe/afk.md +8 -8
- package/src/commands/exe/assign.md +1 -1
- package/src/commands/exe/build-adv.md +1 -1
- package/src/commands/exe/call.md +10 -10
- package/src/commands/exe/employee-heartbeat.md +9 -6
- package/src/commands/exe/heartbeat.md +5 -5
- package/src/commands/exe/intercom.md +26 -15
- package/src/commands/exe/launch.md +2 -2
- package/src/commands/exe/new-employee.md +1 -1
- package/src/commands/exe/review.md +2 -2
- package/src/commands/exe/schedule.md +1 -1
- package/src/commands/exe/sessions.md +2 -2
- package/src/commands/exe.md +22 -20
package/dist/bin/scan-tasks.js
CHANGED
|
@@ -357,7 +357,7 @@ function wrapWithRetry(client) {
|
|
|
357
357
|
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
358
358
|
}
|
|
359
359
|
if (prop === "batch") {
|
|
360
|
-
return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
|
|
360
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
361
361
|
}
|
|
362
362
|
return Reflect.get(target, prop, receiver);
|
|
363
363
|
}
|
|
@@ -373,6 +373,240 @@ var init_db_retry = __esm({
|
|
|
373
373
|
}
|
|
374
374
|
});
|
|
375
375
|
|
|
376
|
+
// src/lib/config.ts
|
|
377
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
378
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
|
|
379
|
+
import path3 from "path";
|
|
380
|
+
import os3 from "os";
|
|
381
|
+
function resolveDataDir() {
|
|
382
|
+
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
383
|
+
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
384
|
+
const newDir = path3.join(os3.homedir(), ".exe-os");
|
|
385
|
+
const legacyDir = path3.join(os3.homedir(), ".exe-mem");
|
|
386
|
+
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
387
|
+
try {
|
|
388
|
+
renameSync2(legacyDir, newDir);
|
|
389
|
+
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
390
|
+
`);
|
|
391
|
+
} catch {
|
|
392
|
+
return legacyDir;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return newDir;
|
|
396
|
+
}
|
|
397
|
+
function migrateLegacyConfig(raw) {
|
|
398
|
+
if ("r2" in raw) {
|
|
399
|
+
process.stderr.write(
|
|
400
|
+
"[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"
|
|
401
|
+
);
|
|
402
|
+
delete raw.r2;
|
|
403
|
+
}
|
|
404
|
+
if ("syncIntervalMs" in raw) {
|
|
405
|
+
delete raw.syncIntervalMs;
|
|
406
|
+
}
|
|
407
|
+
return raw;
|
|
408
|
+
}
|
|
409
|
+
function migrateConfig(raw) {
|
|
410
|
+
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
411
|
+
let currentVersion = fromVersion;
|
|
412
|
+
let migrated = false;
|
|
413
|
+
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
414
|
+
return { config: raw, migrated: false, fromVersion };
|
|
415
|
+
}
|
|
416
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
417
|
+
if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
|
|
418
|
+
raw = migration.migrate(raw);
|
|
419
|
+
currentVersion = migration.to;
|
|
420
|
+
migrated = true;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return { config: raw, migrated, fromVersion };
|
|
424
|
+
}
|
|
425
|
+
function normalizeScalingRoadmap(raw) {
|
|
426
|
+
const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
|
|
427
|
+
const userRoadmap = raw.scalingRoadmap ?? {};
|
|
428
|
+
const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
|
|
429
|
+
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
430
|
+
userAuto.enabled = raw.rerankerEnabled;
|
|
431
|
+
}
|
|
432
|
+
raw.scalingRoadmap = {
|
|
433
|
+
...userRoadmap,
|
|
434
|
+
rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function normalizeSessionLifecycle(raw) {
|
|
438
|
+
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
439
|
+
const userSL = raw.sessionLifecycle ?? {};
|
|
440
|
+
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
441
|
+
}
|
|
442
|
+
function normalizeAutoUpdate(raw) {
|
|
443
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
444
|
+
const userAU = raw.autoUpdate ?? {};
|
|
445
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
446
|
+
}
|
|
447
|
+
async function loadConfig() {
|
|
448
|
+
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
449
|
+
await mkdir(dir, { recursive: true });
|
|
450
|
+
const configPath = path3.join(dir, "config.json");
|
|
451
|
+
if (!existsSync3(configPath)) {
|
|
452
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
453
|
+
}
|
|
454
|
+
const raw = await readFile(configPath, "utf-8");
|
|
455
|
+
try {
|
|
456
|
+
let parsed = JSON.parse(raw);
|
|
457
|
+
parsed = migrateLegacyConfig(parsed);
|
|
458
|
+
const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
|
|
459
|
+
if (migrated) {
|
|
460
|
+
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
461
|
+
`);
|
|
462
|
+
try {
|
|
463
|
+
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
464
|
+
} catch {
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
normalizeScalingRoadmap(migratedCfg);
|
|
468
|
+
normalizeSessionLifecycle(migratedCfg);
|
|
469
|
+
normalizeAutoUpdate(migratedCfg);
|
|
470
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
471
|
+
if (config.dbPath.startsWith("~")) {
|
|
472
|
+
config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
|
|
473
|
+
}
|
|
474
|
+
return config;
|
|
475
|
+
} catch {
|
|
476
|
+
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
480
|
+
var init_config = __esm({
|
|
481
|
+
"src/lib/config.ts"() {
|
|
482
|
+
"use strict";
|
|
483
|
+
EXE_AI_DIR = resolveDataDir();
|
|
484
|
+
DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
|
|
485
|
+
MODELS_DIR = path3.join(EXE_AI_DIR, "models");
|
|
486
|
+
CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
|
|
487
|
+
LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
|
|
488
|
+
CURRENT_CONFIG_VERSION = 1;
|
|
489
|
+
DEFAULT_CONFIG = {
|
|
490
|
+
config_version: CURRENT_CONFIG_VERSION,
|
|
491
|
+
dbPath: DB_PATH,
|
|
492
|
+
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
493
|
+
embeddingDim: 1024,
|
|
494
|
+
batchSize: 20,
|
|
495
|
+
flushIntervalMs: 1e4,
|
|
496
|
+
autoIngestion: true,
|
|
497
|
+
autoRetrieval: true,
|
|
498
|
+
searchMode: "hybrid",
|
|
499
|
+
hookSearchMode: "hybrid",
|
|
500
|
+
fileGrepEnabled: true,
|
|
501
|
+
splashEffect: true,
|
|
502
|
+
consolidationEnabled: true,
|
|
503
|
+
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
504
|
+
consolidationModel: "claude-haiku-4-5-20251001",
|
|
505
|
+
consolidationMaxCallsPerRun: 20,
|
|
506
|
+
selfQueryRouter: true,
|
|
507
|
+
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
508
|
+
rerankerEnabled: true,
|
|
509
|
+
scalingRoadmap: {
|
|
510
|
+
rerankerAutoTrigger: {
|
|
511
|
+
enabled: true,
|
|
512
|
+
broadQueryMinCardinality: 5e4,
|
|
513
|
+
fetchTopK: 150,
|
|
514
|
+
returnTopK: 5
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
graphRagEnabled: true,
|
|
518
|
+
wikiEnabled: false,
|
|
519
|
+
wikiUrl: "",
|
|
520
|
+
wikiApiKey: "",
|
|
521
|
+
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
522
|
+
wikiWorkspaceMapping: {},
|
|
523
|
+
wikiAutoUpdate: true,
|
|
524
|
+
wikiAutoUpdateThreshold: 0.5,
|
|
525
|
+
wikiAutoUpdateCreateNew: true,
|
|
526
|
+
skillLearning: true,
|
|
527
|
+
skillThreshold: 3,
|
|
528
|
+
skillModel: "claude-haiku-4-5-20251001",
|
|
529
|
+
exeHeartbeat: {
|
|
530
|
+
enabled: true,
|
|
531
|
+
intervalSeconds: 60,
|
|
532
|
+
staleInProgressThresholdHours: 2
|
|
533
|
+
},
|
|
534
|
+
sessionLifecycle: {
|
|
535
|
+
idleKillEnabled: true,
|
|
536
|
+
idleKillTicksRequired: 3,
|
|
537
|
+
idleKillIntercomAckWindowMs: 1e4,
|
|
538
|
+
maxAutoInstances: 10
|
|
539
|
+
},
|
|
540
|
+
autoUpdate: {
|
|
541
|
+
checkOnBoot: true,
|
|
542
|
+
autoInstall: false,
|
|
543
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
CONFIG_MIGRATIONS = [
|
|
547
|
+
{
|
|
548
|
+
from: 0,
|
|
549
|
+
to: 1,
|
|
550
|
+
migrate: (cfg) => {
|
|
551
|
+
cfg.config_version = 1;
|
|
552
|
+
return cfg;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
];
|
|
556
|
+
}
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// src/lib/employees.ts
|
|
560
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
561
|
+
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
562
|
+
import { execSync as execSync3 } from "child_process";
|
|
563
|
+
import path4 from "path";
|
|
564
|
+
import os4 from "os";
|
|
565
|
+
function normalizeRole(role) {
|
|
566
|
+
return (role ?? "").trim().toLowerCase();
|
|
567
|
+
}
|
|
568
|
+
function isCoordinatorRole(role) {
|
|
569
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
570
|
+
}
|
|
571
|
+
function getCoordinatorEmployee(employees) {
|
|
572
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
573
|
+
}
|
|
574
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
575
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
576
|
+
}
|
|
577
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
578
|
+
if (!agentName) return false;
|
|
579
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
580
|
+
}
|
|
581
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
582
|
+
if (!existsSync4(employeesPath)) return [];
|
|
583
|
+
try {
|
|
584
|
+
return JSON.parse(readFileSync4(employeesPath, "utf-8"));
|
|
585
|
+
} catch {
|
|
586
|
+
return [];
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function getEmployee(employees, name) {
|
|
590
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
591
|
+
}
|
|
592
|
+
function isMultiInstance(agentName, employees) {
|
|
593
|
+
const roster = employees ?? loadEmployeesSync();
|
|
594
|
+
const emp = getEmployee(roster, agentName);
|
|
595
|
+
if (!emp) return false;
|
|
596
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
597
|
+
}
|
|
598
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
|
|
599
|
+
var init_employees = __esm({
|
|
600
|
+
"src/lib/employees.ts"() {
|
|
601
|
+
"use strict";
|
|
602
|
+
init_config();
|
|
603
|
+
EMPLOYEES_PATH = path4.join(EXE_AI_DIR, "exe-employees.json");
|
|
604
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
605
|
+
COORDINATOR_ROLE = "COO";
|
|
606
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
376
610
|
// src/lib/database.ts
|
|
377
611
|
var database_exports = {};
|
|
378
612
|
__export(database_exports, {
|
|
@@ -520,22 +754,24 @@ async function ensureSchema() {
|
|
|
520
754
|
ON behaviors(agent_id, active);
|
|
521
755
|
`);
|
|
522
756
|
try {
|
|
757
|
+
const coordinatorName = getCoordinatorName();
|
|
523
758
|
const existing = await client.execute({
|
|
524
|
-
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id =
|
|
525
|
-
args: []
|
|
759
|
+
sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
|
|
760
|
+
args: [coordinatorName]
|
|
526
761
|
});
|
|
527
762
|
if (Number(existing.rows[0]?.cnt) === 0) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
763
|
+
const seededAt = "2026-03-25T00:00:00Z";
|
|
764
|
+
for (const [domain, content] of [
|
|
765
|
+
["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
|
|
766
|
+
["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
|
|
767
|
+
["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
|
|
768
|
+
]) {
|
|
769
|
+
await client.execute({
|
|
770
|
+
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
|
|
771
|
+
VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
|
|
772
|
+
args: [coordinatorName, domain, content, seededAt, seededAt]
|
|
773
|
+
});
|
|
774
|
+
}
|
|
539
775
|
}
|
|
540
776
|
} catch {
|
|
541
777
|
}
|
|
@@ -1208,264 +1444,76 @@ async function ensureSchema() {
|
|
|
1208
1444
|
try {
|
|
1209
1445
|
await client.execute({
|
|
1210
1446
|
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
1211
|
-
args: []
|
|
1212
|
-
});
|
|
1213
|
-
} catch {
|
|
1214
|
-
}
|
|
1215
|
-
try {
|
|
1216
|
-
await client.execute(
|
|
1217
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1218
|
-
);
|
|
1219
|
-
} catch {
|
|
1220
|
-
}
|
|
1221
|
-
for (const col of [
|
|
1222
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1223
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1224
|
-
]) {
|
|
1225
|
-
try {
|
|
1226
|
-
await client.execute(col);
|
|
1227
|
-
} catch {
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
async function disposeDatabase() {
|
|
1232
|
-
if (_client) {
|
|
1233
|
-
_client.close();
|
|
1234
|
-
_client = null;
|
|
1235
|
-
_resilientClient = null;
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1239
|
-
var init_database = __esm({
|
|
1240
|
-
"src/lib/database.ts"() {
|
|
1241
|
-
"use strict";
|
|
1242
|
-
init_db_retry();
|
|
1243
|
-
_client = null;
|
|
1244
|
-
_resilientClient = null;
|
|
1245
|
-
initTurso = initDatabase;
|
|
1246
|
-
disposeTurso = disposeDatabase;
|
|
1247
|
-
}
|
|
1248
|
-
});
|
|
1249
|
-
|
|
1250
|
-
// src/lib/config.ts
|
|
1251
|
-
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
1252
|
-
import { readFileSync as readFileSync3, existsSync as existsSync3, renameSync as renameSync2 } from "fs";
|
|
1253
|
-
import path3 from "path";
|
|
1254
|
-
import os3 from "os";
|
|
1255
|
-
function resolveDataDir() {
|
|
1256
|
-
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
1257
|
-
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
1258
|
-
const newDir = path3.join(os3.homedir(), ".exe-os");
|
|
1259
|
-
const legacyDir = path3.join(os3.homedir(), ".exe-mem");
|
|
1260
|
-
if (!existsSync3(newDir) && existsSync3(legacyDir)) {
|
|
1261
|
-
try {
|
|
1262
|
-
renameSync2(legacyDir, newDir);
|
|
1263
|
-
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
1264
|
-
`);
|
|
1265
|
-
} catch {
|
|
1266
|
-
return legacyDir;
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
return newDir;
|
|
1270
|
-
}
|
|
1271
|
-
function migrateLegacyConfig(raw) {
|
|
1272
|
-
if ("r2" in raw) {
|
|
1273
|
-
process.stderr.write(
|
|
1274
|
-
"[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"
|
|
1275
|
-
);
|
|
1276
|
-
delete raw.r2;
|
|
1277
|
-
}
|
|
1278
|
-
if ("syncIntervalMs" in raw) {
|
|
1279
|
-
delete raw.syncIntervalMs;
|
|
1280
|
-
}
|
|
1281
|
-
return raw;
|
|
1282
|
-
}
|
|
1283
|
-
function migrateConfig(raw) {
|
|
1284
|
-
const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
|
|
1285
|
-
let currentVersion = fromVersion;
|
|
1286
|
-
let migrated = false;
|
|
1287
|
-
if (currentVersion > CURRENT_CONFIG_VERSION) {
|
|
1288
|
-
return { config: raw, migrated: false, fromVersion };
|
|
1447
|
+
args: []
|
|
1448
|
+
});
|
|
1449
|
+
} catch {
|
|
1289
1450
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1451
|
+
try {
|
|
1452
|
+
await client.execute(
|
|
1453
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
1454
|
+
);
|
|
1455
|
+
} catch {
|
|
1456
|
+
}
|
|
1457
|
+
for (const col of [
|
|
1458
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
1459
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
1460
|
+
]) {
|
|
1461
|
+
try {
|
|
1462
|
+
await client.execute(col);
|
|
1463
|
+
} catch {
|
|
1295
1464
|
}
|
|
1296
1465
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
|
|
1304
|
-
userAuto.enabled = raw.rerankerEnabled;
|
|
1466
|
+
try {
|
|
1467
|
+
await client.execute({
|
|
1468
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
1469
|
+
args: []
|
|
1470
|
+
});
|
|
1471
|
+
} catch {
|
|
1305
1472
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1311
|
-
function normalizeSessionLifecycle(raw) {
|
|
1312
|
-
const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
|
|
1313
|
-
const userSL = raw.sessionLifecycle ?? {};
|
|
1314
|
-
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
1315
|
-
}
|
|
1316
|
-
function normalizeAutoUpdate(raw) {
|
|
1317
|
-
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
1318
|
-
const userAU = raw.autoUpdate ?? {};
|
|
1319
|
-
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
1320
|
-
}
|
|
1321
|
-
async function loadConfig() {
|
|
1322
|
-
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
1323
|
-
await mkdir(dir, { recursive: true });
|
|
1324
|
-
const configPath = path3.join(dir, "config.json");
|
|
1325
|
-
if (!existsSync3(configPath)) {
|
|
1326
|
-
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
1473
|
+
try {
|
|
1474
|
+
await client.execute(
|
|
1475
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
1476
|
+
);
|
|
1477
|
+
} catch {
|
|
1327
1478
|
}
|
|
1328
|
-
const raw = await readFile(configPath, "utf-8");
|
|
1329
1479
|
try {
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
|
|
1335
|
-
`);
|
|
1336
|
-
try {
|
|
1337
|
-
await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
|
|
1338
|
-
} catch {
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
normalizeScalingRoadmap(migratedCfg);
|
|
1342
|
-
normalizeSessionLifecycle(migratedCfg);
|
|
1343
|
-
normalizeAutoUpdate(migratedCfg);
|
|
1344
|
-
const config = { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db"), ...migratedCfg };
|
|
1345
|
-
if (config.dbPath.startsWith("~")) {
|
|
1346
|
-
config.dbPath = config.dbPath.replace(/^~/, os3.homedir());
|
|
1347
|
-
}
|
|
1348
|
-
return config;
|
|
1480
|
+
await client.execute({
|
|
1481
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
1482
|
+
args: []
|
|
1483
|
+
});
|
|
1349
1484
|
} catch {
|
|
1350
|
-
return { ...DEFAULT_CONFIG, dbPath: path3.join(dir, "memories.db") };
|
|
1351
1485
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
EXE_AI_DIR = resolveDataDir();
|
|
1358
|
-
DB_PATH = path3.join(EXE_AI_DIR, "memories.db");
|
|
1359
|
-
MODELS_DIR = path3.join(EXE_AI_DIR, "models");
|
|
1360
|
-
CONFIG_PATH = path3.join(EXE_AI_DIR, "config.json");
|
|
1361
|
-
LEGACY_LANCE_PATH = path3.join(EXE_AI_DIR, "local.lance");
|
|
1362
|
-
CURRENT_CONFIG_VERSION = 1;
|
|
1363
|
-
DEFAULT_CONFIG = {
|
|
1364
|
-
config_version: CURRENT_CONFIG_VERSION,
|
|
1365
|
-
dbPath: DB_PATH,
|
|
1366
|
-
modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
|
|
1367
|
-
embeddingDim: 1024,
|
|
1368
|
-
batchSize: 20,
|
|
1369
|
-
flushIntervalMs: 1e4,
|
|
1370
|
-
autoIngestion: true,
|
|
1371
|
-
autoRetrieval: true,
|
|
1372
|
-
searchMode: "hybrid",
|
|
1373
|
-
hookSearchMode: "hybrid",
|
|
1374
|
-
fileGrepEnabled: true,
|
|
1375
|
-
splashEffect: true,
|
|
1376
|
-
consolidationEnabled: true,
|
|
1377
|
-
consolidationIntervalMs: 6 * 60 * 60 * 1e3,
|
|
1378
|
-
consolidationModel: "claude-haiku-4-5-20251001",
|
|
1379
|
-
consolidationMaxCallsPerRun: 20,
|
|
1380
|
-
selfQueryRouter: true,
|
|
1381
|
-
selfQueryModel: "claude-haiku-4-5-20251001",
|
|
1382
|
-
rerankerEnabled: true,
|
|
1383
|
-
scalingRoadmap: {
|
|
1384
|
-
rerankerAutoTrigger: {
|
|
1385
|
-
enabled: true,
|
|
1386
|
-
broadQueryMinCardinality: 5e4,
|
|
1387
|
-
fetchTopK: 150,
|
|
1388
|
-
returnTopK: 5
|
|
1389
|
-
}
|
|
1390
|
-
},
|
|
1391
|
-
graphRagEnabled: true,
|
|
1392
|
-
wikiEnabled: false,
|
|
1393
|
-
wikiUrl: "",
|
|
1394
|
-
wikiApiKey: "",
|
|
1395
|
-
wikiSyncIntervalMs: 30 * 60 * 1e3,
|
|
1396
|
-
wikiWorkspaceMapping: {
|
|
1397
|
-
exe: "Executive",
|
|
1398
|
-
yoshi: "Engineering",
|
|
1399
|
-
mari: "Marketing",
|
|
1400
|
-
tom: "Engineering",
|
|
1401
|
-
sasha: "Production"
|
|
1402
|
-
},
|
|
1403
|
-
wikiAutoUpdate: true,
|
|
1404
|
-
wikiAutoUpdateThreshold: 0.5,
|
|
1405
|
-
wikiAutoUpdateCreateNew: true,
|
|
1406
|
-
skillLearning: true,
|
|
1407
|
-
skillThreshold: 3,
|
|
1408
|
-
skillModel: "claude-haiku-4-5-20251001",
|
|
1409
|
-
exeHeartbeat: {
|
|
1410
|
-
enabled: true,
|
|
1411
|
-
intervalSeconds: 60,
|
|
1412
|
-
staleInProgressThresholdHours: 2
|
|
1413
|
-
},
|
|
1414
|
-
sessionLifecycle: {
|
|
1415
|
-
idleKillEnabled: true,
|
|
1416
|
-
idleKillTicksRequired: 3,
|
|
1417
|
-
idleKillIntercomAckWindowMs: 1e4,
|
|
1418
|
-
maxAutoInstances: 10
|
|
1419
|
-
},
|
|
1420
|
-
autoUpdate: {
|
|
1421
|
-
checkOnBoot: true,
|
|
1422
|
-
autoInstall: false,
|
|
1423
|
-
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
1424
|
-
}
|
|
1425
|
-
};
|
|
1426
|
-
CONFIG_MIGRATIONS = [
|
|
1427
|
-
{
|
|
1428
|
-
from: 0,
|
|
1429
|
-
to: 1,
|
|
1430
|
-
migrate: (cfg) => {
|
|
1431
|
-
cfg.config_version = 1;
|
|
1432
|
-
return cfg;
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
];
|
|
1486
|
+
try {
|
|
1487
|
+
await client.execute(
|
|
1488
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
1489
|
+
);
|
|
1490
|
+
} catch {
|
|
1436
1491
|
}
|
|
1437
|
-
});
|
|
1438
|
-
|
|
1439
|
-
// src/lib/employees.ts
|
|
1440
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1441
|
-
import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1442
|
-
import { execSync as execSync3 } from "child_process";
|
|
1443
|
-
import path4 from "path";
|
|
1444
|
-
import os4 from "os";
|
|
1445
|
-
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
1446
|
-
if (!existsSync4(employeesPath)) return [];
|
|
1447
1492
|
try {
|
|
1448
|
-
|
|
1493
|
+
await client.execute({
|
|
1494
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
1495
|
+
args: []
|
|
1496
|
+
});
|
|
1449
1497
|
} catch {
|
|
1450
|
-
return [];
|
|
1451
1498
|
}
|
|
1452
1499
|
}
|
|
1453
|
-
function
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
if (!emp) return false;
|
|
1460
|
-
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
1500
|
+
async function disposeDatabase() {
|
|
1501
|
+
if (_client) {
|
|
1502
|
+
_client.close();
|
|
1503
|
+
_client = null;
|
|
1504
|
+
_resilientClient = null;
|
|
1505
|
+
}
|
|
1461
1506
|
}
|
|
1462
|
-
var
|
|
1463
|
-
var
|
|
1464
|
-
"src/lib/
|
|
1507
|
+
var _client, _resilientClient, initTurso, disposeTurso;
|
|
1508
|
+
var init_database = __esm({
|
|
1509
|
+
"src/lib/database.ts"() {
|
|
1465
1510
|
"use strict";
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1511
|
+
init_db_retry();
|
|
1512
|
+
init_employees();
|
|
1513
|
+
_client = null;
|
|
1514
|
+
_resilientClient = null;
|
|
1515
|
+
initTurso = initDatabase;
|
|
1516
|
+
disposeTurso = disposeDatabase;
|
|
1469
1517
|
}
|
|
1470
1518
|
});
|
|
1471
1519
|
|
|
@@ -1956,6 +2004,36 @@ async function listTasks(input) {
|
|
|
1956
2004
|
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
1957
2005
|
}));
|
|
1958
2006
|
}
|
|
2007
|
+
function isTmuxSessionAlive(identifier) {
|
|
2008
|
+
if (!identifier || identifier === "unknown") return true;
|
|
2009
|
+
try {
|
|
2010
|
+
if (identifier.startsWith("%")) {
|
|
2011
|
+
const output = execSync4("tmux list-panes -a -F '#{pane_id}'", {
|
|
2012
|
+
timeout: 2e3,
|
|
2013
|
+
encoding: "utf8",
|
|
2014
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2015
|
+
});
|
|
2016
|
+
return output.split("\n").some((l) => l.trim() === identifier);
|
|
2017
|
+
} else {
|
|
2018
|
+
execSync4(`tmux has-session -t ${JSON.stringify(identifier)}`, {
|
|
2019
|
+
timeout: 2e3,
|
|
2020
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2021
|
+
});
|
|
2022
|
+
return true;
|
|
2023
|
+
}
|
|
2024
|
+
} catch {
|
|
2025
|
+
if (identifier.startsWith("%")) return true;
|
|
2026
|
+
try {
|
|
2027
|
+
execSync4("tmux list-sessions", {
|
|
2028
|
+
timeout: 2e3,
|
|
2029
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
2030
|
+
});
|
|
2031
|
+
return false;
|
|
2032
|
+
} catch {
|
|
2033
|
+
return true;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
1959
2037
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
1960
2038
|
if (!taskContext) return null;
|
|
1961
2039
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
@@ -2018,13 +2096,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
2018
2096
|
});
|
|
2019
2097
|
if (claim.rowsAffected === 0) {
|
|
2020
2098
|
const current = await client.execute({
|
|
2021
|
-
sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
|
|
2099
|
+
sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
|
|
2022
2100
|
args: [taskId]
|
|
2023
2101
|
});
|
|
2024
2102
|
const cur = current.rows[0];
|
|
2025
|
-
const
|
|
2026
|
-
const
|
|
2027
|
-
|
|
2103
|
+
const curStatus = cur?.status ?? "unknown";
|
|
2104
|
+
const claimedBySession = cur?.assigned_tmux ?? "";
|
|
2105
|
+
const assignedBy = cur?.assigned_by ?? "";
|
|
2106
|
+
if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
|
|
2107
|
+
process.stderr.write(
|
|
2108
|
+
`[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
|
|
2109
|
+
`
|
|
2110
|
+
);
|
|
2111
|
+
await client.execute({
|
|
2112
|
+
sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
|
|
2113
|
+
args: [now, taskId]
|
|
2114
|
+
});
|
|
2115
|
+
const retried = await client.execute({
|
|
2116
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
|
|
2117
|
+
args: [tmuxSession, now, taskId]
|
|
2118
|
+
});
|
|
2119
|
+
if (retried.rowsAffected > 0) {
|
|
2120
|
+
try {
|
|
2121
|
+
await writeCheckpoint({
|
|
2122
|
+
taskId,
|
|
2123
|
+
step: "reclaimed_dead_session",
|
|
2124
|
+
contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
|
|
2125
|
+
});
|
|
2126
|
+
} catch {
|
|
2127
|
+
}
|
|
2128
|
+
return { row, taskFile, now, taskId };
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
|
|
2132
|
+
process.stderr.write(
|
|
2133
|
+
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
2134
|
+
`
|
|
2135
|
+
);
|
|
2136
|
+
await client.execute({
|
|
2137
|
+
sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
|
|
2138
|
+
args: [tmuxSession, now, taskId]
|
|
2139
|
+
});
|
|
2140
|
+
try {
|
|
2141
|
+
await writeCheckpoint({
|
|
2142
|
+
taskId,
|
|
2143
|
+
step: "assigner_override",
|
|
2144
|
+
contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
|
|
2145
|
+
});
|
|
2146
|
+
} catch {
|
|
2147
|
+
}
|
|
2148
|
+
return { row, taskFile, now, taskId };
|
|
2149
|
+
}
|
|
2150
|
+
const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
|
|
2151
|
+
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
|
|
2028
2152
|
}
|
|
2029
2153
|
try {
|
|
2030
2154
|
await writeCheckpoint({
|
|
@@ -2122,7 +2246,7 @@ var init_tasks_crud = __esm({
|
|
|
2122
2246
|
"use strict";
|
|
2123
2247
|
init_database();
|
|
2124
2248
|
init_task_scope();
|
|
2125
|
-
DELEGATION_KEYWORDS = /parallel|delegate|wave|
|
|
2249
|
+
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
2126
2250
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
2127
2251
|
}
|
|
2128
2252
|
});
|
|
@@ -2488,7 +2612,7 @@ function findSessionForProject(projectName) {
|
|
|
2488
2612
|
const sessions = listSessions();
|
|
2489
2613
|
for (const s of sessions) {
|
|
2490
2614
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
2491
|
-
if (proj === projectName && s.agentId === "exe") return s;
|
|
2615
|
+
if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
|
|
2492
2616
|
}
|
|
2493
2617
|
return null;
|
|
2494
2618
|
}
|
|
@@ -2528,12 +2652,13 @@ var init_session_scope = __esm({
|
|
|
2528
2652
|
init_session_registry();
|
|
2529
2653
|
init_project_name();
|
|
2530
2654
|
init_tmux_routing();
|
|
2655
|
+
init_employees();
|
|
2531
2656
|
}
|
|
2532
2657
|
});
|
|
2533
2658
|
|
|
2534
2659
|
// src/lib/tasks-notify.ts
|
|
2535
2660
|
async function dispatchTaskToEmployee(input) {
|
|
2536
|
-
if (input.assignedTo === "exe") return { dispatched: "skipped" };
|
|
2661
|
+
if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
2537
2662
|
let crossProject = false;
|
|
2538
2663
|
if (input.projectName) {
|
|
2539
2664
|
try {
|
|
@@ -2976,6 +3101,24 @@ async function updateTask(input) {
|
|
|
2976
3101
|
});
|
|
2977
3102
|
} catch {
|
|
2978
3103
|
}
|
|
3104
|
+
const assignedAgent = String(row.assigned_to);
|
|
3105
|
+
if (!isCoordinatorName(assignedAgent)) {
|
|
3106
|
+
try {
|
|
3107
|
+
const draftClient = getClient();
|
|
3108
|
+
if (input.status === "done") {
|
|
3109
|
+
await draftClient.execute({
|
|
3110
|
+
sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
|
|
3111
|
+
args: [assignedAgent]
|
|
3112
|
+
});
|
|
3113
|
+
} else if (input.status === "cancelled") {
|
|
3114
|
+
await draftClient.execute({
|
|
3115
|
+
sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
|
|
3116
|
+
args: [assignedAgent]
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
} catch {
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
2979
3122
|
try {
|
|
2980
3123
|
const client = getClient();
|
|
2981
3124
|
const cascaded = await client.execute({
|
|
@@ -2994,8 +3137,8 @@ async function updateTask(input) {
|
|
|
2994
3137
|
}
|
|
2995
3138
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
2996
3139
|
if (isTerminal) {
|
|
2997
|
-
const
|
|
2998
|
-
if (!
|
|
3140
|
+
const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
|
|
3141
|
+
if (!isCoordinator) {
|
|
2999
3142
|
notifyTaskDone();
|
|
3000
3143
|
}
|
|
3001
3144
|
await markTaskNotificationsRead(taskFile);
|
|
@@ -3019,7 +3162,7 @@ async function updateTask(input) {
|
|
|
3019
3162
|
}
|
|
3020
3163
|
}
|
|
3021
3164
|
}
|
|
3022
|
-
if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
|
|
3165
|
+
if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
3023
3166
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
3024
3167
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
3025
3168
|
taskId,
|
|
@@ -3035,7 +3178,7 @@ async function updateTask(input) {
|
|
|
3035
3178
|
});
|
|
3036
3179
|
}
|
|
3037
3180
|
let nextTask;
|
|
3038
|
-
if (isTerminal && String(row.assigned_to) !== "exe") {
|
|
3181
|
+
if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
|
|
3039
3182
|
try {
|
|
3040
3183
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
3041
3184
|
} catch {
|
|
@@ -3062,12 +3205,14 @@ async function updateTask(input) {
|
|
|
3062
3205
|
async function deleteTask(taskId, baseDir) {
|
|
3063
3206
|
const client = getClient();
|
|
3064
3207
|
const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
|
|
3065
|
-
const
|
|
3208
|
+
const coordinatorName = getCoordinatorName();
|
|
3209
|
+
const reviewer = assignedBy || coordinatorName;
|
|
3066
3210
|
const reviewSlug = `review-${assignedTo}-${taskSlug}`;
|
|
3067
3211
|
const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
|
|
3212
|
+
const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
|
|
3068
3213
|
await client.execute({
|
|
3069
|
-
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
|
|
3070
|
-
args: [reviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3214
|
+
sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
|
|
3215
|
+
args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
|
|
3071
3216
|
});
|
|
3072
3217
|
await markAsReadByTaskFile(taskFile);
|
|
3073
3218
|
await markAsReadByTaskFile(reviewFile);
|
|
@@ -3079,6 +3224,7 @@ var init_tasks = __esm({
|
|
|
3079
3224
|
init_config();
|
|
3080
3225
|
init_notifications();
|
|
3081
3226
|
init_state_bus();
|
|
3227
|
+
init_employees();
|
|
3082
3228
|
init_tasks_crud();
|
|
3083
3229
|
init_tasks_review();
|
|
3084
3230
|
init_tasks_crud();
|
|
@@ -3164,7 +3310,7 @@ function _resetLastRelaunchCache() {
|
|
|
3164
3310
|
}
|
|
3165
3311
|
async function lastResumeCreatedAtMs(agentId) {
|
|
3166
3312
|
const client = getClient();
|
|
3167
|
-
const cmScope = sessionScopeFilter();
|
|
3313
|
+
const cmScope = sessionScopeFilter(null);
|
|
3168
3314
|
const result = await client.execute({
|
|
3169
3315
|
sql: `SELECT MAX(created_at) AS last_created_at
|
|
3170
3316
|
FROM tasks
|
|
@@ -3189,7 +3335,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
|
|
|
3189
3335
|
const client = getClient();
|
|
3190
3336
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3191
3337
|
const context = buildResumeContext(agentId, openTasks);
|
|
3192
|
-
const rdScope = sessionScopeFilter();
|
|
3338
|
+
const rdScope = sessionScopeFilter(null);
|
|
3193
3339
|
const existing = await client.execute({
|
|
3194
3340
|
sql: `SELECT id FROM tasks
|
|
3195
3341
|
WHERE assigned_to = ?
|
|
@@ -3223,7 +3369,7 @@ async function pollCapacityDead() {
|
|
|
3223
3369
|
const transport = getTransport();
|
|
3224
3370
|
const relaunched = [];
|
|
3225
3371
|
const registered = listSessions().filter(
|
|
3226
|
-
(s) => s.agentId !== "exe"
|
|
3372
|
+
(s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
|
|
3227
3373
|
);
|
|
3228
3374
|
if (registered.length === 0) return [];
|
|
3229
3375
|
let liveSessions;
|
|
@@ -3283,7 +3429,7 @@ async function pollCapacityDead() {
|
|
|
3283
3429
|
reason: "capacity"
|
|
3284
3430
|
});
|
|
3285
3431
|
const client = getClient();
|
|
3286
|
-
const rlScope = sessionScopeFilter();
|
|
3432
|
+
const rlScope = sessionScopeFilter(null);
|
|
3287
3433
|
const openTasks = await client.execute({
|
|
3288
3434
|
sql: `SELECT id, title, priority, task_file, status
|
|
3289
3435
|
FROM tasks
|
|
@@ -3337,6 +3483,7 @@ var init_capacity_monitor = __esm({
|
|
|
3337
3483
|
init_session_kill_telemetry();
|
|
3338
3484
|
init_tmux_routing();
|
|
3339
3485
|
init_task_scope();
|
|
3486
|
+
init_employees();
|
|
3340
3487
|
CAPACITY_PATTERNS = [
|
|
3341
3488
|
/conversation is too long/i,
|
|
3342
3489
|
/maximum context length/i,
|
|
@@ -3486,7 +3633,7 @@ function employeeSessionName(employee, exeSession, instance) {
|
|
|
3486
3633
|
exeSession = root;
|
|
3487
3634
|
} else {
|
|
3488
3635
|
throw new Error(
|
|
3489
|
-
`Invalid
|
|
3636
|
+
`Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3490
3637
|
);
|
|
3491
3638
|
}
|
|
3492
3639
|
}
|
|
@@ -3506,8 +3653,10 @@ function parseParentExe(sessionName, agentId) {
|
|
|
3506
3653
|
return match?.[1] ?? null;
|
|
3507
3654
|
}
|
|
3508
3655
|
function extractRootExe(name) {
|
|
3509
|
-
|
|
3510
|
-
|
|
3656
|
+
if (!name) return null;
|
|
3657
|
+
if (!name.includes("-")) return name;
|
|
3658
|
+
const parts = name.split("-").filter(Boolean);
|
|
3659
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
3511
3660
|
}
|
|
3512
3661
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
3513
3662
|
if (!existsSync10(SESSION_CACHE)) {
|
|
@@ -3652,12 +3801,14 @@ function isSessionBusy(sessionName) {
|
|
|
3652
3801
|
return state === "thinking" || state === "tool";
|
|
3653
3802
|
}
|
|
3654
3803
|
function isExeSession(sessionName) {
|
|
3655
|
-
|
|
3804
|
+
const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
|
|
3805
|
+
const coordinatorName = getCoordinatorName();
|
|
3806
|
+
return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
|
|
3656
3807
|
}
|
|
3657
3808
|
function sendIntercom(targetSession) {
|
|
3658
3809
|
const transport = getTransport();
|
|
3659
3810
|
if (isExeSession(targetSession)) {
|
|
3660
|
-
logIntercom(`
|
|
3811
|
+
logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
|
|
3661
3812
|
return "skipped_exe";
|
|
3662
3813
|
}
|
|
3663
3814
|
if (isDebounced(targetSession)) {
|
|
@@ -3709,7 +3860,7 @@ function notifyParentExe(sessionKey) {
|
|
|
3709
3860
|
if (result === "failed") {
|
|
3710
3861
|
const rootExe = resolveExeSession();
|
|
3711
3862
|
if (rootExe && rootExe !== target) {
|
|
3712
|
-
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root
|
|
3863
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
|
|
3713
3864
|
`);
|
|
3714
3865
|
const fallback = sendIntercom(rootExe);
|
|
3715
3866
|
return fallback !== "failed";
|
|
@@ -3719,8 +3870,8 @@ function notifyParentExe(sessionKey) {
|
|
|
3719
3870
|
return true;
|
|
3720
3871
|
}
|
|
3721
3872
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
3722
|
-
if (employeeName === "exe") {
|
|
3723
|
-
return { status: "failed", sessionName: "", error: "
|
|
3873
|
+
if (employeeName === "exe" || isCoordinatorName(employeeName)) {
|
|
3874
|
+
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
3724
3875
|
}
|
|
3725
3876
|
try {
|
|
3726
3877
|
assertEmployeeLimitSync();
|
|
@@ -3729,8 +3880,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3729
3880
|
return { status: "failed", sessionName: "", error: err.message };
|
|
3730
3881
|
}
|
|
3731
3882
|
}
|
|
3732
|
-
if (
|
|
3733
|
-
const bare = employeeName.
|
|
3883
|
+
if (employeeName.includes("-")) {
|
|
3884
|
+
const bare = employeeName.split("-")[0].replace(/\d+$/, "");
|
|
3734
3885
|
return {
|
|
3735
3886
|
status: "failed",
|
|
3736
3887
|
sessionName: "",
|
|
@@ -3749,7 +3900,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3749
3900
|
return {
|
|
3750
3901
|
status: "failed",
|
|
3751
3902
|
sessionName: "",
|
|
3752
|
-
error: `Invalid
|
|
3903
|
+
error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
|
|
3753
3904
|
};
|
|
3754
3905
|
}
|
|
3755
3906
|
}
|
|
@@ -3906,8 +4057,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3906
4057
|
const ctxContent = [
|
|
3907
4058
|
`## Session Context`,
|
|
3908
4059
|
`You are running in tmux session: ${sessionName}.`,
|
|
3909
|
-
`Your parent
|
|
3910
|
-
`Your employees (if any) use the -${exeSession} suffix
|
|
4060
|
+
`Your parent coordinator session is ${exeSession}.`,
|
|
4061
|
+
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3911
4062
|
].join("\n");
|
|
3912
4063
|
writeFileSync6(ctxFile, ctxContent);
|
|
3913
4064
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
@@ -4011,6 +4162,7 @@ var init_tmux_routing = __esm({
|
|
|
4011
4162
|
init_provider_table();
|
|
4012
4163
|
init_intercom_queue();
|
|
4013
4164
|
init_plan_limits();
|
|
4165
|
+
init_employees();
|
|
4014
4166
|
SPAWN_LOCK_DIR = path13.join(os6.homedir(), ".exe-os", "spawn-locks");
|
|
4015
4167
|
SESSION_CACHE = path13.join(os6.homedir(), ".exe-os", "session-cache");
|
|
4016
4168
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
@@ -4228,7 +4380,11 @@ async function ensureShardSchema(client) {
|
|
|
4228
4380
|
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
4229
4381
|
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
4230
4382
|
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
4231
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
4383
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
4384
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
4385
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
4386
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
4387
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT"
|
|
4232
4388
|
]) {
|
|
4233
4389
|
try {
|
|
4234
4390
|
await client.execute(col);
|
|
@@ -4358,26 +4514,26 @@ var init_platform_procedures = __esm({
|
|
|
4358
4514
|
title: "What is exe-os \u2014 the operating model every agent must understand",
|
|
4359
4515
|
domain: "architecture",
|
|
4360
4516
|
priority: "p0",
|
|
4361
|
-
content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO
|
|
4517
|
+
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."
|
|
4362
4518
|
},
|
|
4363
4519
|
{
|
|
4364
4520
|
title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
|
|
4365
4521
|
domain: "architecture",
|
|
4366
4522
|
priority: "p0",
|
|
4367
|
-
content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC
|
|
4523
|
+
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."
|
|
4368
4524
|
},
|
|
4369
4525
|
{
|
|
4370
|
-
title: "Sessions explained \u2014
|
|
4526
|
+
title: "Sessions explained \u2014 coordinator session names and projects",
|
|
4371
4527
|
domain: "architecture",
|
|
4372
4528
|
priority: "p0",
|
|
4373
|
-
content: "Each
|
|
4529
|
+
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."
|
|
4374
4530
|
},
|
|
4375
4531
|
// --- Hierarchy and dispatch ---
|
|
4376
4532
|
{
|
|
4377
4533
|
title: "Chain of command \u2014 who talks to whom",
|
|
4378
4534
|
domain: "workflow",
|
|
4379
4535
|
priority: "p0",
|
|
4380
|
-
content: "Founder
|
|
4536
|
+
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."
|
|
4381
4537
|
},
|
|
4382
4538
|
{
|
|
4383
4539
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -4387,30 +4543,30 @@ var init_platform_procedures = __esm({
|
|
|
4387
4543
|
},
|
|
4388
4544
|
// --- Session isolation ---
|
|
4389
4545
|
{
|
|
4390
|
-
title: "Session scoping \u2014 stay in your
|
|
4546
|
+
title: "Session scoping \u2014 stay in your coordinator boundary",
|
|
4391
4547
|
domain: "security",
|
|
4392
4548
|
priority: "p0",
|
|
4393
|
-
content: "Session scoping is mandatory. Managers dispatch to workers within their own
|
|
4549
|
+
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."
|
|
4394
4550
|
},
|
|
4395
4551
|
{
|
|
4396
4552
|
title: "Session isolation \u2014 never touch another session's work",
|
|
4397
4553
|
domain: "workflow",
|
|
4398
4554
|
priority: "p0",
|
|
4399
|
-
content:
|
|
4555
|
+
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."
|
|
4400
4556
|
},
|
|
4401
4557
|
// --- Engineering: session scoping in code ---
|
|
4402
4558
|
{
|
|
4403
4559
|
title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
|
|
4404
4560
|
domain: "architecture",
|
|
4405
4561
|
priority: "p0",
|
|
4406
|
-
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
|
|
4562
|
+
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."
|
|
4407
4563
|
},
|
|
4408
4564
|
// --- Hard constraints ---
|
|
4409
4565
|
{
|
|
4410
4566
|
title: "What you CANNOT do in exe-os \u2014 hard constraints",
|
|
4411
4567
|
domain: "security",
|
|
4412
4568
|
priority: "p0",
|
|
4413
|
-
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
|
|
4569
|
+
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."
|
|
4414
4570
|
},
|
|
4415
4571
|
// --- Operations ---
|
|
4416
4572
|
{
|
|
@@ -4650,7 +4806,10 @@ async function writeMemory(record) {
|
|
|
4650
4806
|
source_path: record.source_path ?? null,
|
|
4651
4807
|
source_type: record.source_type ?? null,
|
|
4652
4808
|
tier: record.tier ?? classifyTier(record),
|
|
4653
|
-
supersedes_id: record.supersedes_id ?? null
|
|
4809
|
+
supersedes_id: record.supersedes_id ?? null,
|
|
4810
|
+
draft: record.draft ? 1 : 0,
|
|
4811
|
+
memory_type: record.memory_type ?? "raw",
|
|
4812
|
+
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
|
|
4654
4813
|
};
|
|
4655
4814
|
_pendingRecords.push(dbRow);
|
|
4656
4815
|
orgBus.emit({
|
|
@@ -4705,6 +4864,9 @@ async function flushBatch() {
|
|
|
4705
4864
|
const sourceType = row.source_type ?? null;
|
|
4706
4865
|
const tier = row.tier ?? 3;
|
|
4707
4866
|
const supersedesId = row.supersedes_id ?? null;
|
|
4867
|
+
const draft = row.draft ? 1 : 0;
|
|
4868
|
+
const memoryType = row.memory_type ?? "raw";
|
|
4869
|
+
const trajectory = row.trajectory ?? null;
|
|
4708
4870
|
return {
|
|
4709
4871
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
4710
4872
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
@@ -4712,15 +4874,15 @@ async function flushBatch() {
|
|
|
4712
4874
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4713
4875
|
confidence, last_accessed,
|
|
4714
4876
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4715
|
-
source_path, source_type, tier, supersedes_id)
|
|
4716
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4877
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
4878
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
4717
4879
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
4718
4880
|
tool_name, project_name,
|
|
4719
4881
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
4720
4882
|
confidence, last_accessed,
|
|
4721
4883
|
workspace_id, document_id, user_id, char_offset, page_number,
|
|
4722
|
-
source_path, source_type, tier, supersedes_id)
|
|
4723
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4884
|
+
source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
|
|
4885
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4724
4886
|
args: hasVector ? [
|
|
4725
4887
|
row.id,
|
|
4726
4888
|
row.agent_id,
|
|
@@ -4746,7 +4908,10 @@ async function flushBatch() {
|
|
|
4746
4908
|
sourcePath,
|
|
4747
4909
|
sourceType,
|
|
4748
4910
|
tier,
|
|
4749
|
-
supersedesId
|
|
4911
|
+
supersedesId,
|
|
4912
|
+
draft,
|
|
4913
|
+
memoryType,
|
|
4914
|
+
trajectory
|
|
4750
4915
|
] : [
|
|
4751
4916
|
row.id,
|
|
4752
4917
|
row.agent_id,
|
|
@@ -4771,7 +4936,10 @@ async function flushBatch() {
|
|
|
4771
4936
|
sourcePath,
|
|
4772
4937
|
sourceType,
|
|
4773
4938
|
tier,
|
|
4774
|
-
supersedesId
|
|
4939
|
+
supersedesId,
|
|
4940
|
+
draft,
|
|
4941
|
+
memoryType,
|
|
4942
|
+
trajectory
|
|
4775
4943
|
]
|
|
4776
4944
|
};
|
|
4777
4945
|
};
|
|
@@ -4840,6 +5008,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4840
5008
|
const limit = options?.limit ?? 10;
|
|
4841
5009
|
const statusFilter = options?.includeArchived ? "" : `
|
|
4842
5010
|
AND COALESCE(status, 'active') = 'active'`;
|
|
5011
|
+
const draftFilter = options?.includeDrafts ? "" : `
|
|
5012
|
+
AND (draft = 0 OR draft IS NULL)`;
|
|
4843
5013
|
let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
4844
5014
|
tool_name, project_name,
|
|
4845
5015
|
has_error, raw_text, vector, importance, status,
|
|
@@ -4849,7 +5019,7 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4849
5019
|
source_path, source_type
|
|
4850
5020
|
FROM memories
|
|
4851
5021
|
WHERE agent_id = ?
|
|
4852
|
-
AND vector IS NOT NULL${statusFilter}
|
|
5022
|
+
AND vector IS NOT NULL${statusFilter}${draftFilter}
|
|
4853
5023
|
AND COALESCE(confidence, 0.7) >= 0.3`;
|
|
4854
5024
|
const args = [agentId];
|
|
4855
5025
|
const scope = buildWikiScopeFilter(options, "");
|
|
@@ -4871,6 +5041,10 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4871
5041
|
sql += ` AND timestamp >= ?`;
|
|
4872
5042
|
args.push(options.since);
|
|
4873
5043
|
}
|
|
5044
|
+
if (options?.memoryType) {
|
|
5045
|
+
sql += ` AND memory_type = ?`;
|
|
5046
|
+
args.push(options.memoryType);
|
|
5047
|
+
}
|
|
4874
5048
|
sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
|
|
4875
5049
|
args.push(vectorToBlob(queryVector));
|
|
4876
5050
|
sql += ` LIMIT ?`;
|