@askexenow/exe-os 0.9.34 → 0.9.36
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 +212 -11
- package/dist/bin/backfill-responses.js +212 -11
- package/dist/bin/backfill-vectors.js +14 -3
- package/dist/bin/cleanup-stale-review-tasks.js +224 -12
- package/dist/bin/cli.js +265 -23
- package/dist/bin/exe-agent.js +1 -1
- package/dist/bin/exe-assign.js +217 -12
- package/dist/bin/exe-boot.js +25 -4
- package/dist/bin/exe-call.js +7 -5
- package/dist/bin/exe-dispatch.js +229 -13
- package/dist/bin/exe-doctor.js +14 -3
- package/dist/bin/exe-export-behaviors.js +301 -14
- package/dist/bin/exe-forget.js +245 -21
- package/dist/bin/exe-gateway.js +229 -13
- package/dist/bin/exe-heartbeat.js +224 -12
- package/dist/bin/exe-kill.js +224 -12
- package/dist/bin/exe-launch-agent.js +177 -9
- package/dist/bin/exe-link.js +8 -0
- package/dist/bin/exe-new-employee.js +26 -2
- package/dist/bin/exe-pending-messages.js +224 -12
- package/dist/bin/exe-pending-notifications.js +224 -12
- package/dist/bin/exe-pending-reviews.js +224 -12
- package/dist/bin/exe-rename.js +9 -1
- package/dist/bin/exe-review.js +224 -12
- package/dist/bin/exe-search.js +246 -21
- package/dist/bin/exe-session-cleanup.js +229 -13
- package/dist/bin/exe-start-codex.js +161 -5
- package/dist/bin/exe-start-opencode.js +172 -5
- package/dist/bin/exe-status.js +224 -12
- package/dist/bin/exe-team.js +224 -12
- package/dist/bin/git-sweep.js +229 -13
- package/dist/bin/graph-backfill.js +94 -3
- package/dist/bin/graph-export.js +224 -12
- package/dist/bin/install.js +25 -1
- package/dist/bin/intercom-check.js +229 -13
- package/dist/bin/scan-tasks.js +229 -13
- package/dist/bin/setup.js +15 -5
- package/dist/bin/shard-migrate.js +94 -3
- package/dist/gateway/index.js +229 -13
- package/dist/hooks/bug-report-worker.js +229 -13
- package/dist/hooks/codex-stop-task-finalizer.js +224 -12
- package/dist/hooks/commit-complete.js +229 -13
- package/dist/hooks/error-recall.js +246 -21
- package/dist/hooks/ingest.js +224 -12
- package/dist/hooks/instructions-loaded.js +224 -12
- package/dist/hooks/notification.js +224 -12
- package/dist/hooks/post-compact.js +224 -12
- package/dist/hooks/post-tool-combined.js +246 -21
- package/dist/hooks/pre-compact.js +229 -13
- package/dist/hooks/pre-tool-use.js +234 -18
- package/dist/hooks/prompt-submit.js +346 -23
- package/dist/hooks/session-end.js +229 -13
- package/dist/hooks/session-start.js +418 -42
- package/dist/hooks/stop.js +224 -12
- package/dist/hooks/subagent-stop.js +224 -12
- package/dist/hooks/summary-worker.js +15 -2
- package/dist/index.js +229 -13
- package/dist/lib/cloud-sync.js +8 -0
- package/dist/lib/consolidation.js +3 -1
- package/dist/lib/database.js +8 -0
- package/dist/lib/db.js +8 -0
- package/dist/lib/device-registry.js +8 -0
- package/dist/lib/employee-templates.js +7 -5
- package/dist/lib/exe-daemon.js +1776 -1433
- package/dist/lib/hybrid-search.js +246 -21
- package/dist/lib/schedules.js +14 -3
- package/dist/lib/store.js +217 -12
- package/dist/lib/tasks.js +5 -1
- package/dist/lib/tmux-routing.js +5 -1
- package/dist/mcp/server.js +331 -37
- package/dist/mcp/tools/create-task.js +5 -1
- package/dist/mcp/tools/update-task.js +5 -1
- package/dist/runtime/index.js +229 -13
- package/dist/tui/App.js +229 -13
- package/package.json +1 -1
- package/src/commands/exe/save.md +48 -0
package/dist/lib/exe-daemon.js
CHANGED
|
@@ -436,1265 +436,694 @@ var init_daemon_auth = __esm({
|
|
|
436
436
|
}
|
|
437
437
|
});
|
|
438
438
|
|
|
439
|
-
// src/lib/
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
resetProjectionWorkerForTests: () => resetProjectionWorkerForTests,
|
|
445
|
-
setProjectionWorkerPrismaClientForTests: () => setProjectionWorkerPrismaClientForTests,
|
|
446
|
-
startProjectionWorker: () => startProjectionWorker,
|
|
447
|
-
stopProjectionWorker: () => stopProjectionWorker
|
|
448
|
-
});
|
|
449
|
-
import os2 from "os";
|
|
450
|
-
import path4 from "path";
|
|
451
|
-
import { existsSync as existsSync5 } from "fs";
|
|
452
|
-
import { createRequire } from "module";
|
|
453
|
-
import { pathToFileURL } from "url";
|
|
454
|
-
function loadPrisma() {
|
|
455
|
-
if (!prismaPromise) {
|
|
456
|
-
prismaPromise = (async () => {
|
|
457
|
-
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
458
|
-
if (explicitPath) {
|
|
459
|
-
const mod2 = await import(pathToFileURL(explicitPath).href);
|
|
460
|
-
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
461
|
-
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
462
|
-
return new Ctor2();
|
|
463
|
-
}
|
|
464
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ?? path4.join(os2.homedir(), "exe-db");
|
|
465
|
-
const req = createRequire(path4.join(exeDbRoot, "package.json"));
|
|
466
|
-
const entry = req.resolve("@prisma/client");
|
|
467
|
-
const mod = await import(pathToFileURL(entry).href);
|
|
468
|
-
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
469
|
-
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
470
|
-
return new Ctor();
|
|
471
|
-
})();
|
|
439
|
+
// src/lib/db-retry.ts
|
|
440
|
+
function isBusyError(err) {
|
|
441
|
+
if (err instanceof Error) {
|
|
442
|
+
const msg = err.message.toLowerCase();
|
|
443
|
+
return msg.includes("sqlite_busy") || msg.includes("database is locked");
|
|
472
444
|
}
|
|
473
|
-
return
|
|
474
|
-
}
|
|
475
|
-
function setProjectionWorkerPrismaClientForTests(client) {
|
|
476
|
-
prismaPromise = client ? Promise.resolve(client) : null;
|
|
445
|
+
return false;
|
|
477
446
|
}
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
if (pollTimer) {
|
|
481
|
-
clearTimeout(pollTimer);
|
|
482
|
-
pollTimer = null;
|
|
483
|
-
}
|
|
484
|
-
prismaPromise = null;
|
|
447
|
+
function delay(ms) {
|
|
448
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
485
449
|
}
|
|
486
|
-
async function
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
`SELECT "id", "source", "source_id", "event_type", "payload", "metadata", "timestamp"
|
|
490
|
-
FROM "raw"."raw_events"
|
|
491
|
-
WHERE "processed_at" IS NULL
|
|
492
|
-
ORDER BY "timestamp" ASC
|
|
493
|
-
LIMIT $1`,
|
|
494
|
-
BATCH_SIZE
|
|
495
|
-
);
|
|
496
|
-
if (events.length === 0) return 0;
|
|
497
|
-
let processed = 0;
|
|
498
|
-
for (const event of events) {
|
|
450
|
+
async function retryOnBusy(fn, label) {
|
|
451
|
+
let lastError;
|
|
452
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
499
453
|
try {
|
|
500
|
-
|
|
501
|
-
const result = await handler(event, prisma);
|
|
502
|
-
await prisma.$executeRawUnsafe(
|
|
503
|
-
`UPDATE "raw"."raw_events"
|
|
504
|
-
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
505
|
-
WHERE "id" = $2`,
|
|
506
|
-
JSON.stringify({ targets: result.targets, skipped: result.skipped }),
|
|
507
|
-
event.id
|
|
508
|
-
);
|
|
509
|
-
processed++;
|
|
454
|
+
return await fn();
|
|
510
455
|
} catch (err) {
|
|
511
|
-
|
|
456
|
+
lastError = err;
|
|
457
|
+
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
458
|
+
throw err;
|
|
459
|
+
}
|
|
460
|
+
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
461
|
+
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
512
462
|
process.stderr.write(
|
|
513
|
-
`[
|
|
463
|
+
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
514
464
|
`
|
|
515
465
|
);
|
|
516
|
-
await
|
|
517
|
-
`UPDATE "raw"."raw_events"
|
|
518
|
-
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
519
|
-
WHERE "id" = $2`,
|
|
520
|
-
JSON.stringify({ error: message }),
|
|
521
|
-
event.id
|
|
522
|
-
);
|
|
466
|
+
await delay(backoff + jitter);
|
|
523
467
|
}
|
|
524
468
|
}
|
|
525
|
-
|
|
469
|
+
throw lastError;
|
|
526
470
|
}
|
|
527
|
-
function
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
process.stderr.write("[projection-worker] Skipped \u2014 no exe-db found. Set DATABASE_URL or EXE_DB_ROOT to enable.\n");
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
running = true;
|
|
537
|
-
process.stderr.write("[projection-worker] Starting...\n");
|
|
538
|
-
const tick = async () => {
|
|
539
|
-
if (!running) return;
|
|
540
|
-
try {
|
|
541
|
-
const count = await processBatch();
|
|
542
|
-
if (count > 0) {
|
|
543
|
-
process.stderr.write(`[projection-worker] Processed ${count} events.
|
|
544
|
-
`);
|
|
471
|
+
function wrapWithRetry(client) {
|
|
472
|
+
return new Proxy(client, {
|
|
473
|
+
get(target, prop, receiver) {
|
|
474
|
+
if (prop === "execute") {
|
|
475
|
+
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
545
476
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
);
|
|
551
|
-
}
|
|
552
|
-
if (running) {
|
|
553
|
-
pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
|
|
477
|
+
if (prop === "batch") {
|
|
478
|
+
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
479
|
+
}
|
|
480
|
+
return Reflect.get(target, prop, receiver);
|
|
554
481
|
}
|
|
555
|
-
};
|
|
556
|
-
void tick();
|
|
482
|
+
});
|
|
557
483
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
484
|
+
var MAX_RETRIES, BASE_DELAY_MS, MAX_JITTER_MS;
|
|
485
|
+
var init_db_retry = __esm({
|
|
486
|
+
"src/lib/db-retry.ts"() {
|
|
487
|
+
"use strict";
|
|
488
|
+
MAX_RETRIES = 5;
|
|
489
|
+
BASE_DELAY_MS = 250;
|
|
490
|
+
MAX_JITTER_MS = 400;
|
|
563
491
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
var init_projection_worker = __esm({
|
|
571
|
-
"src/lib/projection-worker.ts"() {
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// src/lib/runtime-table.ts
|
|
495
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
496
|
+
var init_runtime_table = __esm({
|
|
497
|
+
"src/lib/runtime-table.ts"() {
|
|
572
498
|
"use strict";
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
ON CONFLICT ("id") DO NOTHING`,
|
|
582
|
-
crypto.randomUUID(),
|
|
583
|
-
"system",
|
|
584
|
-
"ingest",
|
|
585
|
-
"projection-worker",
|
|
586
|
-
"projection_worker",
|
|
587
|
-
"exe-os",
|
|
588
|
-
JSON.stringify(payload),
|
|
589
|
-
"raw",
|
|
590
|
-
"whatsapp",
|
|
591
|
-
event.event_type
|
|
592
|
-
);
|
|
593
|
-
targets.push("memory");
|
|
594
|
-
return { targets };
|
|
595
|
-
},
|
|
596
|
-
async shopify(event, prisma) {
|
|
597
|
-
const targets = [];
|
|
598
|
-
const payload = event.payload;
|
|
599
|
-
await prisma.$executeRawUnsafe(
|
|
600
|
-
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
601
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
602
|
-
ON CONFLICT ("id") DO NOTHING`,
|
|
603
|
-
crypto.randomUUID(),
|
|
604
|
-
"system",
|
|
605
|
-
"ingest",
|
|
606
|
-
"projection-worker",
|
|
607
|
-
"projection_worker",
|
|
608
|
-
"exe-os",
|
|
609
|
-
JSON.stringify(payload),
|
|
610
|
-
"raw",
|
|
611
|
-
"shopify",
|
|
612
|
-
event.event_type
|
|
613
|
-
);
|
|
614
|
-
targets.push("memory");
|
|
615
|
-
return { targets };
|
|
616
|
-
},
|
|
617
|
-
async cloud_sync(event, prisma) {
|
|
618
|
-
const targets = [];
|
|
619
|
-
const payload = event.payload;
|
|
620
|
-
await prisma.$executeRawUnsafe(
|
|
621
|
-
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
622
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
623
|
-
ON CONFLICT ("id") DO NOTHING`,
|
|
624
|
-
payload.id ?? crypto.randomUUID(),
|
|
625
|
-
payload.agent_id ?? "system",
|
|
626
|
-
payload.agent_role ?? "ingest",
|
|
627
|
-
payload.session_id ?? "projection-worker",
|
|
628
|
-
payload.tool_name ?? "cloud_sync",
|
|
629
|
-
payload.project_name ?? "exe-os",
|
|
630
|
-
payload.raw_text ?? JSON.stringify(payload),
|
|
631
|
-
payload.memory_type ?? "raw",
|
|
632
|
-
"cloud_sync",
|
|
633
|
-
event.event_type,
|
|
634
|
-
payload.domain ?? null
|
|
635
|
-
);
|
|
636
|
-
targets.push("memory");
|
|
637
|
-
return { targets };
|
|
499
|
+
RUNTIME_TABLE = {
|
|
500
|
+
codex: {
|
|
501
|
+
binary: "codex",
|
|
502
|
+
launchMode: "interactive",
|
|
503
|
+
autoApproveFlag: "--dangerously-bypass-approvals-and-sandbox",
|
|
504
|
+
inlineFlag: "--no-alt-screen",
|
|
505
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
506
|
+
defaultModel: "gpt-5.5"
|
|
638
507
|
},
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
crypto.randomUUID(),
|
|
647
|
-
"system",
|
|
648
|
-
"ingest",
|
|
649
|
-
"projection-worker",
|
|
650
|
-
"projection_worker",
|
|
651
|
-
"exe-os",
|
|
652
|
-
JSON.stringify(payload),
|
|
653
|
-
"raw",
|
|
654
|
-
"external_agent",
|
|
655
|
-
event.event_type,
|
|
656
|
-
payload.domain ?? null
|
|
657
|
-
);
|
|
658
|
-
targets.push("memory");
|
|
659
|
-
return { targets };
|
|
508
|
+
opencode: {
|
|
509
|
+
binary: "opencode",
|
|
510
|
+
launchMode: "exec",
|
|
511
|
+
autoApproveFlag: "--dangerously-skip-permissions",
|
|
512
|
+
inlineFlag: "",
|
|
513
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
514
|
+
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
660
515
|
}
|
|
661
516
|
};
|
|
662
|
-
|
|
663
|
-
defaultHandler = async (event, prisma) => {
|
|
664
|
-
await prisma.$executeRawUnsafe(
|
|
665
|
-
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
666
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
667
|
-
ON CONFLICT ("id") DO NOTHING`,
|
|
668
|
-
crypto.randomUUID(),
|
|
669
|
-
"system",
|
|
670
|
-
"ingest",
|
|
671
|
-
"projection-worker",
|
|
672
|
-
"projection_worker",
|
|
673
|
-
"exe-os",
|
|
674
|
-
JSON.stringify(event.payload),
|
|
675
|
-
"raw",
|
|
676
|
-
event.source,
|
|
677
|
-
event.event_type
|
|
678
|
-
);
|
|
679
|
-
return { targets: ["memory"] };
|
|
680
|
-
};
|
|
681
|
-
BATCH_SIZE = 50;
|
|
682
|
-
POLL_INTERVAL_MS = 1e4;
|
|
683
|
-
running = false;
|
|
684
|
-
pollTimer = null;
|
|
517
|
+
DEFAULT_RUNTIME = "claude";
|
|
685
518
|
}
|
|
686
519
|
});
|
|
687
520
|
|
|
688
|
-
// src/lib/
|
|
689
|
-
var
|
|
690
|
-
__export(
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
shardExists: () => shardExists
|
|
521
|
+
// src/lib/agent-config.ts
|
|
522
|
+
var agent_config_exports = {};
|
|
523
|
+
__export(agent_config_exports, {
|
|
524
|
+
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
525
|
+
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
526
|
+
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
527
|
+
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
528
|
+
clearAgentRuntime: () => clearAgentRuntime,
|
|
529
|
+
getAgentRuntime: () => getAgentRuntime,
|
|
530
|
+
loadAgentConfig: () => loadAgentConfig,
|
|
531
|
+
saveAgentConfig: () => saveAgentConfig,
|
|
532
|
+
setAgentRuntime: () => setAgentRuntime
|
|
701
533
|
});
|
|
702
|
-
import
|
|
703
|
-
import
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
534
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "fs";
|
|
535
|
+
import path4 from "path";
|
|
536
|
+
function loadAgentConfig() {
|
|
537
|
+
if (!existsSync5(AGENT_CONFIG_PATH)) return {};
|
|
538
|
+
try {
|
|
539
|
+
return JSON.parse(readFileSync4(AGENT_CONFIG_PATH, "utf-8"));
|
|
540
|
+
} catch {
|
|
541
|
+
return {};
|
|
709
542
|
}
|
|
710
|
-
_shardingEnabled = true;
|
|
711
|
-
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
712
|
-
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
713
|
-
_evictionTimer.unref();
|
|
714
543
|
}
|
|
715
|
-
function
|
|
716
|
-
|
|
544
|
+
function saveAgentConfig(config2) {
|
|
545
|
+
const dir = path4.dirname(AGENT_CONFIG_PATH);
|
|
546
|
+
ensurePrivateDirSync(dir);
|
|
547
|
+
writeFileSync3(AGENT_CONFIG_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
548
|
+
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
717
549
|
}
|
|
718
|
-
function
|
|
719
|
-
|
|
550
|
+
function getAgentRuntime(agentId) {
|
|
551
|
+
const config2 = loadAgentConfig();
|
|
552
|
+
const entry = config2[agentId];
|
|
553
|
+
if (entry) return entry;
|
|
554
|
+
const orgDefault = config2["default"];
|
|
555
|
+
if (orgDefault) return orgDefault;
|
|
556
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
720
557
|
}
|
|
721
|
-
function
|
|
722
|
-
|
|
723
|
-
|
|
558
|
+
function setAgentRuntime(agentId, runtime, model, reasoning_effort) {
|
|
559
|
+
const knownModels = KNOWN_RUNTIMES[runtime];
|
|
560
|
+
if (!knownModels) {
|
|
561
|
+
return {
|
|
562
|
+
ok: false,
|
|
563
|
+
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
564
|
+
};
|
|
724
565
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
566
|
+
if (!knownModels.includes(model)) {
|
|
567
|
+
return {
|
|
568
|
+
ok: false,
|
|
569
|
+
error: `Unknown model "${model}" for runtime "${runtime}". Valid: ${knownModels.join(", ")}`
|
|
570
|
+
};
|
|
728
571
|
}
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
572
|
+
const config2 = loadAgentConfig();
|
|
573
|
+
const entry = { runtime, model };
|
|
574
|
+
if (reasoning_effort) entry.reasoning_effort = reasoning_effort;
|
|
575
|
+
config2[agentId] = entry;
|
|
576
|
+
saveAgentConfig(config2);
|
|
577
|
+
return { ok: true };
|
|
578
|
+
}
|
|
579
|
+
function clearAgentRuntime(agentId) {
|
|
580
|
+
const config2 = loadAgentConfig();
|
|
581
|
+
delete config2[agentId];
|
|
582
|
+
saveAgentConfig(config2);
|
|
583
|
+
}
|
|
584
|
+
var AGENT_CONFIG_PATH, KNOWN_RUNTIMES, RUNTIME_LABELS, DEFAULT_MODELS;
|
|
585
|
+
var init_agent_config = __esm({
|
|
586
|
+
"src/lib/agent-config.ts"() {
|
|
587
|
+
"use strict";
|
|
588
|
+
init_config();
|
|
589
|
+
init_runtime_table();
|
|
590
|
+
init_secure_files();
|
|
591
|
+
AGENT_CONFIG_PATH = path4.join(EXE_AI_DIR, "agent-config.json");
|
|
592
|
+
KNOWN_RUNTIMES = {
|
|
593
|
+
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
594
|
+
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
595
|
+
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
596
|
+
};
|
|
597
|
+
RUNTIME_LABELS = {
|
|
598
|
+
claude: "Claude Code (Anthropic)",
|
|
599
|
+
codex: "Codex (OpenAI)",
|
|
600
|
+
opencode: "OpenCode (open source)"
|
|
601
|
+
};
|
|
602
|
+
DEFAULT_MODELS = {
|
|
603
|
+
claude: "claude-opus-4.6",
|
|
604
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
605
|
+
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
606
|
+
};
|
|
733
607
|
}
|
|
734
|
-
|
|
735
|
-
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// src/lib/employees.ts
|
|
611
|
+
var employees_exports = {};
|
|
612
|
+
__export(employees_exports, {
|
|
613
|
+
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
614
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
615
|
+
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
616
|
+
addEmployee: () => addEmployee,
|
|
617
|
+
baseAgentName: () => baseAgentName,
|
|
618
|
+
canCoordinate: () => canCoordinate,
|
|
619
|
+
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
620
|
+
getCoordinatorName: () => getCoordinatorName,
|
|
621
|
+
getEmployee: () => getEmployee,
|
|
622
|
+
getEmployeeByRole: () => getEmployeeByRole,
|
|
623
|
+
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
624
|
+
hasRole: () => hasRole,
|
|
625
|
+
hireEmployee: () => hireEmployee,
|
|
626
|
+
isCoordinatorName: () => isCoordinatorName,
|
|
627
|
+
isCoordinatorRole: () => isCoordinatorRole,
|
|
628
|
+
isMultiInstance: () => isMultiInstance,
|
|
629
|
+
loadEmployees: () => loadEmployees,
|
|
630
|
+
loadEmployeesSync: () => loadEmployeesSync,
|
|
631
|
+
normalizeRole: () => normalizeRole,
|
|
632
|
+
normalizeRosterCase: () => normalizeRosterCase,
|
|
633
|
+
registerBinSymlinks: () => registerBinSymlinks,
|
|
634
|
+
saveEmployees: () => saveEmployees,
|
|
635
|
+
validateEmployeeName: () => validateEmployeeName
|
|
636
|
+
});
|
|
637
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
638
|
+
import { existsSync as existsSync6, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
639
|
+
import { execSync } from "child_process";
|
|
640
|
+
import path5 from "path";
|
|
641
|
+
import os2 from "os";
|
|
642
|
+
function normalizeRole(role) {
|
|
643
|
+
return (role ?? "").trim().toLowerCase();
|
|
644
|
+
}
|
|
645
|
+
function isCoordinatorRole(role) {
|
|
646
|
+
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
647
|
+
}
|
|
648
|
+
function getCoordinatorEmployee(employees) {
|
|
649
|
+
return employees.find((e) => isCoordinatorRole(e.role));
|
|
650
|
+
}
|
|
651
|
+
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
652
|
+
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
653
|
+
}
|
|
654
|
+
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
655
|
+
if (!agentName) return false;
|
|
656
|
+
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
657
|
+
}
|
|
658
|
+
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
659
|
+
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
660
|
+
}
|
|
661
|
+
function validateEmployeeName(name) {
|
|
662
|
+
if (!name) {
|
|
663
|
+
return { valid: false, error: "Name is required" };
|
|
736
664
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
665
|
+
if (name.length > 32) {
|
|
666
|
+
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
667
|
+
}
|
|
668
|
+
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
669
|
+
return {
|
|
670
|
+
valid: false,
|
|
671
|
+
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return { valid: true };
|
|
745
675
|
}
|
|
746
|
-
function
|
|
747
|
-
|
|
748
|
-
|
|
676
|
+
async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
|
|
677
|
+
if (!existsSync6(employeesPath)) {
|
|
678
|
+
return [];
|
|
679
|
+
}
|
|
680
|
+
const raw = await readFile2(employeesPath, "utf-8");
|
|
681
|
+
try {
|
|
682
|
+
return JSON.parse(raw);
|
|
683
|
+
} catch {
|
|
684
|
+
return [];
|
|
685
|
+
}
|
|
749
686
|
}
|
|
750
|
-
function
|
|
751
|
-
|
|
752
|
-
|
|
687
|
+
async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
|
|
688
|
+
await mkdir2(path5.dirname(employeesPath), { recursive: true });
|
|
689
|
+
await writeFile2(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
|
|
753
690
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
await client.execute("PRAGMA busy_timeout = 30000");
|
|
691
|
+
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
692
|
+
if (!existsSync6(employeesPath)) return [];
|
|
757
693
|
try {
|
|
758
|
-
|
|
694
|
+
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
759
695
|
} catch {
|
|
696
|
+
return [];
|
|
760
697
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
797
|
-
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
798
|
-
END;
|
|
799
|
-
`);
|
|
800
|
-
for (const col of [
|
|
801
|
-
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
802
|
-
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
803
|
-
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
804
|
-
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
805
|
-
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
806
|
-
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
807
|
-
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
808
|
-
"ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
|
|
809
|
-
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
810
|
-
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
|
|
811
|
-
"ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
|
|
812
|
-
"ALTER TABLE memories ADD COLUMN last_accessed TEXT",
|
|
813
|
-
// Wiki linkage columns (must match database.ts)
|
|
814
|
-
"ALTER TABLE memories ADD COLUMN workspace_id TEXT",
|
|
815
|
-
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
816
|
-
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
817
|
-
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
818
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
819
|
-
// Source provenance columns (must match database.ts)
|
|
820
|
-
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
821
|
-
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
822
|
-
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
823
|
-
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
824
|
-
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
825
|
-
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
826
|
-
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
827
|
-
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
828
|
-
// Metadata enrichment columns (must match database.ts)
|
|
829
|
-
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
830
|
-
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
831
|
-
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
832
|
-
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
833
|
-
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
834
|
-
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
835
|
-
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
836
|
-
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
837
|
-
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
838
|
-
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
839
|
-
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
840
|
-
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
841
|
-
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
842
|
-
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
843
|
-
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
844
|
-
]) {
|
|
845
|
-
try {
|
|
846
|
-
await client.execute(col);
|
|
847
|
-
} catch {
|
|
848
|
-
}
|
|
698
|
+
}
|
|
699
|
+
function getEmployee(employees, name) {
|
|
700
|
+
return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
|
|
701
|
+
}
|
|
702
|
+
function getEmployeeByRole(employees, role) {
|
|
703
|
+
const lower = role.toLowerCase();
|
|
704
|
+
return employees.find((e) => e.role.toLowerCase() === lower);
|
|
705
|
+
}
|
|
706
|
+
function getEmployeeNamesByRole(employees, role) {
|
|
707
|
+
const lower = role.toLowerCase();
|
|
708
|
+
return employees.filter((e) => e.role.toLowerCase() === lower).map((e) => e.name);
|
|
709
|
+
}
|
|
710
|
+
function hasRole(agentName, role) {
|
|
711
|
+
const employees = loadEmployeesSync();
|
|
712
|
+
const emp = getEmployee(employees, agentName);
|
|
713
|
+
return emp ? emp.role.toLowerCase() === role.toLowerCase() : false;
|
|
714
|
+
}
|
|
715
|
+
function baseAgentName(name, employees) {
|
|
716
|
+
const match = name.match(/^([a-zA-Z]+)\d+$/);
|
|
717
|
+
if (!match) return name;
|
|
718
|
+
const base = match[1];
|
|
719
|
+
const roster = employees ?? loadEmployeesSync();
|
|
720
|
+
if (getEmployee(roster, base)) return base;
|
|
721
|
+
return name;
|
|
722
|
+
}
|
|
723
|
+
function isMultiInstance(agentName, employees) {
|
|
724
|
+
const roster = employees ?? loadEmployeesSync();
|
|
725
|
+
const emp = getEmployee(roster, agentName);
|
|
726
|
+
if (!emp) return false;
|
|
727
|
+
return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
|
|
728
|
+
}
|
|
729
|
+
function addEmployee(employees, employee) {
|
|
730
|
+
const normalized = { ...employee, name: employee.name.toLowerCase() };
|
|
731
|
+
if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
|
|
732
|
+
throw new Error(`Employee '${normalized.name}' already exists`);
|
|
849
733
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
734
|
+
return [...employees, normalized];
|
|
735
|
+
}
|
|
736
|
+
function appendToCoordinatorTeam(employee) {
|
|
737
|
+
const coordinator = getCoordinatorEmployee(loadEmployeesSync());
|
|
738
|
+
if (!coordinator) return;
|
|
739
|
+
const idPath = path5.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
740
|
+
if (!existsSync6(idPath)) return;
|
|
741
|
+
const content = readFileSync5(idPath, "utf-8");
|
|
742
|
+
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
743
|
+
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
744
|
+
if (!teamMatch || teamMatch.index === void 0) return;
|
|
745
|
+
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
746
|
+
const nextHeading = afterTeam.match(/\n## /);
|
|
747
|
+
const entry = `
|
|
748
|
+
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
749
|
+
`;
|
|
750
|
+
let updated;
|
|
751
|
+
if (nextHeading && nextHeading.index !== void 0) {
|
|
752
|
+
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
753
|
+
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
754
|
+
} else {
|
|
755
|
+
updated = content.trimEnd() + "\n" + entry;
|
|
858
756
|
}
|
|
757
|
+
writeFileSync4(idPath, updated, "utf-8");
|
|
758
|
+
}
|
|
759
|
+
function capitalize(s) {
|
|
760
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
761
|
+
}
|
|
762
|
+
async function hireEmployee(employee) {
|
|
763
|
+
const employees = await loadEmployees();
|
|
764
|
+
const updated = addEmployee(employees, employee);
|
|
765
|
+
await saveEmployees(updated);
|
|
859
766
|
try {
|
|
860
|
-
|
|
767
|
+
appendToCoordinatorTeam(employee);
|
|
861
768
|
} catch {
|
|
862
769
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
} catch {
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
await client.executeMultiple(`
|
|
874
|
-
CREATE TABLE IF NOT EXISTS entities (
|
|
875
|
-
id TEXT PRIMARY KEY,
|
|
876
|
-
name TEXT NOT NULL,
|
|
877
|
-
type TEXT NOT NULL,
|
|
878
|
-
first_seen TEXT NOT NULL,
|
|
879
|
-
last_seen TEXT NOT NULL,
|
|
880
|
-
properties TEXT DEFAULT '{}',
|
|
881
|
-
UNIQUE(name, type)
|
|
882
|
-
);
|
|
883
|
-
|
|
884
|
-
CREATE TABLE IF NOT EXISTS relationships (
|
|
885
|
-
id TEXT PRIMARY KEY,
|
|
886
|
-
source_entity_id TEXT NOT NULL,
|
|
887
|
-
target_entity_id TEXT NOT NULL,
|
|
888
|
-
type TEXT NOT NULL,
|
|
889
|
-
weight REAL DEFAULT 1.0,
|
|
890
|
-
timestamp TEXT NOT NULL,
|
|
891
|
-
properties TEXT DEFAULT '{}',
|
|
892
|
-
UNIQUE(source_entity_id, target_entity_id, type)
|
|
893
|
-
);
|
|
894
|
-
|
|
895
|
-
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
896
|
-
entity_id TEXT NOT NULL,
|
|
897
|
-
memory_id TEXT NOT NULL,
|
|
898
|
-
PRIMARY KEY (entity_id, memory_id)
|
|
899
|
-
);
|
|
900
|
-
|
|
901
|
-
CREATE TABLE IF NOT EXISTS relationship_memories (
|
|
902
|
-
relationship_id TEXT NOT NULL,
|
|
903
|
-
memory_id TEXT NOT NULL,
|
|
904
|
-
PRIMARY KEY (relationship_id, memory_id)
|
|
905
|
-
);
|
|
906
|
-
|
|
907
|
-
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
908
|
-
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
909
|
-
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
|
|
910
|
-
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
|
|
911
|
-
CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
|
|
912
|
-
|
|
913
|
-
CREATE TABLE IF NOT EXISTS hyperedges (
|
|
914
|
-
id TEXT PRIMARY KEY,
|
|
915
|
-
label TEXT NOT NULL,
|
|
916
|
-
relation TEXT NOT NULL,
|
|
917
|
-
confidence REAL DEFAULT 1.0,
|
|
918
|
-
timestamp TEXT NOT NULL
|
|
919
|
-
);
|
|
920
|
-
|
|
921
|
-
CREATE TABLE IF NOT EXISTS hyperedge_nodes (
|
|
922
|
-
hyperedge_id TEXT NOT NULL,
|
|
923
|
-
entity_id TEXT NOT NULL,
|
|
924
|
-
PRIMARY KEY (hyperedge_id, entity_id)
|
|
925
|
-
);
|
|
926
|
-
`);
|
|
927
|
-
for (const col of [
|
|
928
|
-
"ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
|
|
929
|
-
"ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
|
|
930
|
-
]) {
|
|
931
|
-
try {
|
|
932
|
-
await client.execute(col);
|
|
933
|
-
} catch {
|
|
770
|
+
try {
|
|
771
|
+
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
772
|
+
const config2 = loadAgentConfig2();
|
|
773
|
+
const name = employee.name.toLowerCase();
|
|
774
|
+
if (!config2[name] && config2["default"]) {
|
|
775
|
+
config2[name] = { ...config2["default"] };
|
|
776
|
+
saveAgentConfig2(config2);
|
|
934
777
|
}
|
|
778
|
+
} catch {
|
|
935
779
|
}
|
|
780
|
+
return updated;
|
|
936
781
|
}
|
|
937
|
-
async function
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
782
|
+
async function normalizeRosterCase(rosterPath) {
|
|
783
|
+
const employees = await loadEmployees(rosterPath);
|
|
784
|
+
let changed = false;
|
|
785
|
+
for (const emp of employees) {
|
|
786
|
+
if (emp.name !== emp.name.toLowerCase()) {
|
|
787
|
+
const oldName = emp.name;
|
|
788
|
+
emp.name = emp.name.toLowerCase();
|
|
789
|
+
changed = true;
|
|
790
|
+
try {
|
|
791
|
+
const identityDir = path5.join(os2.homedir(), ".exe-os", "identity");
|
|
792
|
+
const oldPath = path5.join(identityDir, `${oldName}.md`);
|
|
793
|
+
const newPath = path5.join(identityDir, `${emp.name}.md`);
|
|
794
|
+
if (existsSync6(oldPath) && !existsSync6(newPath)) {
|
|
795
|
+
renameSync2(oldPath, newPath);
|
|
796
|
+
} else if (existsSync6(oldPath) && oldPath !== newPath) {
|
|
797
|
+
const content = readFileSync5(oldPath, "utf-8");
|
|
798
|
+
writeFileSync4(newPath, content, "utf-8");
|
|
799
|
+
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
800
|
+
unlinkSync(oldPath);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
} catch {
|
|
804
|
+
}
|
|
949
805
|
}
|
|
950
806
|
}
|
|
951
|
-
if (
|
|
952
|
-
|
|
953
|
-
if (client) {
|
|
954
|
-
client.close();
|
|
955
|
-
}
|
|
956
|
-
_shards.delete(oldest);
|
|
957
|
-
_shardLastAccess.delete(oldest);
|
|
807
|
+
if (changed) {
|
|
808
|
+
await saveEmployees(employees, rosterPath);
|
|
958
809
|
}
|
|
810
|
+
return changed;
|
|
959
811
|
}
|
|
960
|
-
function
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
toEvict.push(name);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
for (const name of toEvict) {
|
|
969
|
-
const client = _shards.get(name);
|
|
970
|
-
if (client) {
|
|
971
|
-
client.close();
|
|
972
|
-
}
|
|
973
|
-
_shards.delete(name);
|
|
974
|
-
_shardLastAccess.delete(name);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
function getOpenShardCount() {
|
|
978
|
-
return _shards.size;
|
|
979
|
-
}
|
|
980
|
-
function disposeShards() {
|
|
981
|
-
if (_evictionTimer) {
|
|
982
|
-
clearInterval(_evictionTimer);
|
|
983
|
-
_evictionTimer = null;
|
|
984
|
-
}
|
|
985
|
-
for (const [, client] of _shards) {
|
|
986
|
-
client.close();
|
|
812
|
+
function findExeBin() {
|
|
813
|
+
try {
|
|
814
|
+
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
815
|
+
} catch {
|
|
816
|
+
return null;
|
|
987
817
|
}
|
|
988
|
-
_shards.clear();
|
|
989
|
-
_shardLastAccess.clear();
|
|
990
|
-
_shardingEnabled = false;
|
|
991
|
-
_encryptionKey = null;
|
|
992
818
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
1002
|
-
_shards = /* @__PURE__ */ new Map();
|
|
1003
|
-
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
1004
|
-
_evictionTimer = null;
|
|
1005
|
-
_encryptionKey = null;
|
|
1006
|
-
_shardingEnabled = false;
|
|
819
|
+
function registerBinSymlinks(name) {
|
|
820
|
+
const created = [];
|
|
821
|
+
const skipped = [];
|
|
822
|
+
const errors = [];
|
|
823
|
+
const exeBinPath = findExeBin();
|
|
824
|
+
if (!exeBinPath) {
|
|
825
|
+
errors.push("Could not find 'exe-os' in PATH");
|
|
826
|
+
return { created, skipped, errors };
|
|
1007
827
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
return
|
|
828
|
+
const binDir = path5.dirname(exeBinPath);
|
|
829
|
+
let target;
|
|
830
|
+
try {
|
|
831
|
+
target = readlinkSync(exeBinPath);
|
|
832
|
+
} catch {
|
|
833
|
+
errors.push("Could not read 'exe' symlink");
|
|
834
|
+
return { created, skipped, errors };
|
|
1015
835
|
}
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
836
|
+
for (const suffix of ["", "-opencode"]) {
|
|
837
|
+
const linkName = `${name}${suffix}`;
|
|
838
|
+
const linkPath = path5.join(binDir, linkName);
|
|
839
|
+
if (existsSync6(linkPath)) {
|
|
840
|
+
skipped.push(linkName);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
1024
843
|
try {
|
|
1025
|
-
|
|
844
|
+
symlinkSync(target, linkPath);
|
|
845
|
+
created.push(linkName);
|
|
1026
846
|
} catch (err) {
|
|
1027
|
-
|
|
1028
|
-
if (!isBusyError(err) || attempt === MAX_RETRIES) {
|
|
1029
|
-
throw err;
|
|
1030
|
-
}
|
|
1031
|
-
const backoff = BASE_DELAY_MS * Math.pow(2, attempt);
|
|
1032
|
-
const jitter = Math.floor(Math.random() * MAX_JITTER_MS);
|
|
1033
|
-
process.stderr.write(
|
|
1034
|
-
`[exe-os] SQLITE_BUSY ${label} retry ${attempt + 1}/${MAX_RETRIES} \u2014 waiting ${backoff + jitter}ms
|
|
1035
|
-
`
|
|
1036
|
-
);
|
|
1037
|
-
await delay(backoff + jitter);
|
|
847
|
+
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1038
848
|
}
|
|
1039
849
|
}
|
|
1040
|
-
|
|
1041
|
-
}
|
|
1042
|
-
function wrapWithRetry(client) {
|
|
1043
|
-
return new Proxy(client, {
|
|
1044
|
-
get(target, prop, receiver) {
|
|
1045
|
-
if (prop === "execute") {
|
|
1046
|
-
return (sql) => retryOnBusy(() => target.execute(sql), "execute");
|
|
1047
|
-
}
|
|
1048
|
-
if (prop === "batch") {
|
|
1049
|
-
return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
|
|
1050
|
-
}
|
|
1051
|
-
return Reflect.get(target, prop, receiver);
|
|
1052
|
-
}
|
|
1053
|
-
});
|
|
850
|
+
return { created, skipped, errors };
|
|
1054
851
|
}
|
|
1055
|
-
var
|
|
1056
|
-
var
|
|
1057
|
-
"src/lib/
|
|
1058
|
-
"use strict";
|
|
1059
|
-
MAX_RETRIES = 5;
|
|
1060
|
-
BASE_DELAY_MS = 250;
|
|
1061
|
-
MAX_JITTER_MS = 400;
|
|
1062
|
-
}
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
// src/lib/runtime-table.ts
|
|
1066
|
-
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
1067
|
-
var init_runtime_table = __esm({
|
|
1068
|
-
"src/lib/runtime-table.ts"() {
|
|
852
|
+
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
853
|
+
var init_employees = __esm({
|
|
854
|
+
"src/lib/employees.ts"() {
|
|
1069
855
|
"use strict";
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
defaultModel: "gpt-5.5"
|
|
1078
|
-
},
|
|
1079
|
-
opencode: {
|
|
1080
|
-
binary: "opencode",
|
|
1081
|
-
launchMode: "exec",
|
|
1082
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
1083
|
-
inlineFlag: "",
|
|
1084
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
1085
|
-
defaultModel: "anthropic/claude-sonnet-4-6"
|
|
1086
|
-
}
|
|
1087
|
-
};
|
|
1088
|
-
DEFAULT_RUNTIME = "claude";
|
|
856
|
+
init_config();
|
|
857
|
+
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
858
|
+
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
859
|
+
COORDINATOR_ROLE = "COO";
|
|
860
|
+
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
861
|
+
IDENTITY_DIR = path5.join(EXE_AI_DIR, "identity");
|
|
862
|
+
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1089
863
|
}
|
|
1090
864
|
});
|
|
1091
865
|
|
|
1092
|
-
// src/lib/
|
|
1093
|
-
|
|
1094
|
-
__export(agent_config_exports, {
|
|
1095
|
-
AGENT_CONFIG_PATH: () => AGENT_CONFIG_PATH,
|
|
1096
|
-
DEFAULT_MODELS: () => DEFAULT_MODELS,
|
|
1097
|
-
KNOWN_RUNTIMES: () => KNOWN_RUNTIMES,
|
|
1098
|
-
RUNTIME_LABELS: () => RUNTIME_LABELS,
|
|
1099
|
-
clearAgentRuntime: () => clearAgentRuntime,
|
|
1100
|
-
getAgentRuntime: () => getAgentRuntime,
|
|
1101
|
-
loadAgentConfig: () => loadAgentConfig,
|
|
1102
|
-
saveAgentConfig: () => saveAgentConfig,
|
|
1103
|
-
setAgentRuntime: () => setAgentRuntime
|
|
1104
|
-
});
|
|
1105
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync7 } from "fs";
|
|
866
|
+
// src/lib/database-adapter.ts
|
|
867
|
+
import os3 from "os";
|
|
1106
868
|
import path6 from "path";
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
} catch {
|
|
1112
|
-
return {};
|
|
1113
|
-
}
|
|
869
|
+
import { createRequire } from "module";
|
|
870
|
+
import { pathToFileURL } from "url";
|
|
871
|
+
function quotedIdentifier(identifier) {
|
|
872
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1114
873
|
}
|
|
1115
|
-
function
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
enforcePrivateFileSync(AGENT_CONFIG_PATH);
|
|
874
|
+
function unqualifiedTableName(name) {
|
|
875
|
+
const raw = name.trim().replace(/^"|"$/g, "");
|
|
876
|
+
const parts = raw.split(".");
|
|
877
|
+
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
1120
878
|
}
|
|
1121
|
-
function
|
|
1122
|
-
|
|
1123
|
-
const entry = config2[agentId];
|
|
1124
|
-
if (entry) return entry;
|
|
1125
|
-
const orgDefault = config2["default"];
|
|
1126
|
-
if (orgDefault) return orgDefault;
|
|
1127
|
-
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
879
|
+
function stripTrailingSemicolon(sql) {
|
|
880
|
+
return sql.trim().replace(/;+\s*$/u, "");
|
|
1128
881
|
}
|
|
1129
|
-
function
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
error: `Unknown runtime "${runtime}". Valid: ${Object.keys(KNOWN_RUNTIMES).join(", ")}`
|
|
1135
|
-
};
|
|
882
|
+
function appendClause(sql, clause) {
|
|
883
|
+
const trimmed = stripTrailingSemicolon(sql);
|
|
884
|
+
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
885
|
+
if (!returningMatch) {
|
|
886
|
+
return `${trimmed}${clause}`;
|
|
1136
887
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
888
|
+
const idx = returningMatch.index;
|
|
889
|
+
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
890
|
+
}
|
|
891
|
+
function normalizeStatement(stmt) {
|
|
892
|
+
if (typeof stmt === "string") {
|
|
893
|
+
return { kind: "positional", sql: stmt, args: [] };
|
|
1142
894
|
}
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
return { ok: true };
|
|
895
|
+
const sql = stmt.sql;
|
|
896
|
+
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
897
|
+
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
898
|
+
}
|
|
899
|
+
return { kind: "named", sql, args: stmt.args };
|
|
1149
900
|
}
|
|
1150
|
-
function
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
901
|
+
function rewriteBooleanLiterals(sql) {
|
|
902
|
+
let out = sql;
|
|
903
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
904
|
+
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
905
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
906
|
+
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
907
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
908
|
+
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
909
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
910
|
+
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
911
|
+
}
|
|
912
|
+
return out;
|
|
1154
913
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
"use strict";
|
|
1159
|
-
init_config();
|
|
1160
|
-
init_runtime_table();
|
|
1161
|
-
init_secure_files();
|
|
1162
|
-
AGENT_CONFIG_PATH = path6.join(EXE_AI_DIR, "agent-config.json");
|
|
1163
|
-
KNOWN_RUNTIMES = {
|
|
1164
|
-
claude: ["claude-opus-4.6", "claude-opus-4", "claude-sonnet-4.6", "claude-sonnet-4", "claude-haiku-4.5"],
|
|
1165
|
-
codex: ["gpt-5.4", "gpt-5.5", "gpt-5.3-codex-spark", "o3", "o4-mini"],
|
|
1166
|
-
opencode: ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4", "google/gemini-2.5-pro", "deepseek/deepseek-r3", "minimax/minimax-m2.5"]
|
|
1167
|
-
};
|
|
1168
|
-
RUNTIME_LABELS = {
|
|
1169
|
-
claude: "Claude Code (Anthropic)",
|
|
1170
|
-
codex: "Codex (OpenAI)",
|
|
1171
|
-
opencode: "OpenCode (open source)"
|
|
1172
|
-
};
|
|
1173
|
-
DEFAULT_MODELS = {
|
|
1174
|
-
claude: "claude-opus-4.6",
|
|
1175
|
-
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
1176
|
-
opencode: RUNTIME_TABLE.opencode?.defaultModel ?? "anthropic/claude-sonnet-4-6"
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
// src/lib/employees.ts
|
|
1182
|
-
var employees_exports = {};
|
|
1183
|
-
__export(employees_exports, {
|
|
1184
|
-
COORDINATOR_ROLE: () => COORDINATOR_ROLE,
|
|
1185
|
-
DEFAULT_COORDINATOR_TEMPLATE_NAME: () => DEFAULT_COORDINATOR_TEMPLATE_NAME,
|
|
1186
|
-
EMPLOYEES_PATH: () => EMPLOYEES_PATH,
|
|
1187
|
-
addEmployee: () => addEmployee,
|
|
1188
|
-
baseAgentName: () => baseAgentName,
|
|
1189
|
-
canCoordinate: () => canCoordinate,
|
|
1190
|
-
getCoordinatorEmployee: () => getCoordinatorEmployee,
|
|
1191
|
-
getCoordinatorName: () => getCoordinatorName,
|
|
1192
|
-
getEmployee: () => getEmployee,
|
|
1193
|
-
getEmployeeByRole: () => getEmployeeByRole,
|
|
1194
|
-
getEmployeeNamesByRole: () => getEmployeeNamesByRole,
|
|
1195
|
-
hasRole: () => hasRole,
|
|
1196
|
-
hireEmployee: () => hireEmployee,
|
|
1197
|
-
isCoordinatorName: () => isCoordinatorName,
|
|
1198
|
-
isCoordinatorRole: () => isCoordinatorRole,
|
|
1199
|
-
isMultiInstance: () => isMultiInstance,
|
|
1200
|
-
loadEmployees: () => loadEmployees,
|
|
1201
|
-
loadEmployeesSync: () => loadEmployeesSync,
|
|
1202
|
-
normalizeRole: () => normalizeRole,
|
|
1203
|
-
normalizeRosterCase: () => normalizeRosterCase,
|
|
1204
|
-
registerBinSymlinks: () => registerBinSymlinks,
|
|
1205
|
-
saveEmployees: () => saveEmployees,
|
|
1206
|
-
validateEmployeeName: () => validateEmployeeName
|
|
1207
|
-
});
|
|
1208
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1209
|
-
import { existsSync as existsSync8, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
1210
|
-
import { execSync } from "child_process";
|
|
1211
|
-
import path7 from "path";
|
|
1212
|
-
import os3 from "os";
|
|
1213
|
-
function normalizeRole(role) {
|
|
1214
|
-
return (role ?? "").trim().toLowerCase();
|
|
1215
|
-
}
|
|
1216
|
-
function isCoordinatorRole(role) {
|
|
1217
|
-
return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
|
|
1218
|
-
}
|
|
1219
|
-
function getCoordinatorEmployee(employees) {
|
|
1220
|
-
return employees.find((e) => isCoordinatorRole(e.role));
|
|
1221
|
-
}
|
|
1222
|
-
function getCoordinatorName(employees = loadEmployeesSync()) {
|
|
1223
|
-
return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
|
|
1224
|
-
}
|
|
1225
|
-
function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
1226
|
-
if (!agentName) return false;
|
|
1227
|
-
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
1228
|
-
}
|
|
1229
|
-
function canCoordinate(agentName, agentRole, employees = loadEmployeesSync()) {
|
|
1230
|
-
return agentName === "default" || isCoordinatorRole(agentRole) || isCoordinatorName(agentName, employees);
|
|
1231
|
-
}
|
|
1232
|
-
function validateEmployeeName(name) {
|
|
1233
|
-
if (!name) {
|
|
1234
|
-
return { valid: false, error: "Name is required" };
|
|
1235
|
-
}
|
|
1236
|
-
if (name.length > 32) {
|
|
1237
|
-
return { valid: false, error: "Name must be 32 characters or fewer" };
|
|
1238
|
-
}
|
|
1239
|
-
if (!/^[a-z][a-z0-9]*$/.test(name)) {
|
|
1240
|
-
return {
|
|
1241
|
-
valid: false,
|
|
1242
|
-
error: "Name must start with a letter and contain only lowercase alphanumeric characters"
|
|
1243
|
-
};
|
|
914
|
+
function rewriteInsertOrIgnore(sql) {
|
|
915
|
+
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
916
|
+
return sql;
|
|
1244
917
|
}
|
|
1245
|
-
|
|
918
|
+
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
919
|
+
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
1246
920
|
}
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
921
|
+
function rewriteInsertOrReplace(sql) {
|
|
922
|
+
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
923
|
+
if (!match) {
|
|
924
|
+
return sql;
|
|
1250
925
|
}
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
926
|
+
const rawTable = match[1];
|
|
927
|
+
const rawColumns = match[2];
|
|
928
|
+
const remainder = match[3];
|
|
929
|
+
const tableName = unqualifiedTableName(rawTable);
|
|
930
|
+
const conflictKeys = UPSERT_KEYS[tableName];
|
|
931
|
+
if (!conflictKeys?.length) {
|
|
932
|
+
return sql;
|
|
1256
933
|
}
|
|
934
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
935
|
+
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
936
|
+
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
937
|
+
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
938
|
+
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
1257
939
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
940
|
+
function rewriteSql(sql) {
|
|
941
|
+
let out = sql;
|
|
942
|
+
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
943
|
+
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
944
|
+
out = rewriteBooleanLiterals(out);
|
|
945
|
+
out = rewriteInsertOrReplace(out);
|
|
946
|
+
out = rewriteInsertOrIgnore(out);
|
|
947
|
+
return stripTrailingSemicolon(out);
|
|
1261
948
|
}
|
|
1262
|
-
function
|
|
1263
|
-
if (
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
949
|
+
function toBoolean(value) {
|
|
950
|
+
if (value === null || value === void 0) return value;
|
|
951
|
+
if (typeof value === "boolean") return value;
|
|
952
|
+
if (typeof value === "number") return value !== 0;
|
|
953
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
954
|
+
if (typeof value === "string") {
|
|
955
|
+
const normalized = value.trim().toLowerCase();
|
|
956
|
+
if (normalized === "0" || normalized === "false") return false;
|
|
957
|
+
if (normalized === "1" || normalized === "true") return true;
|
|
1268
958
|
}
|
|
959
|
+
return Boolean(value);
|
|
1269
960
|
}
|
|
1270
|
-
function
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
961
|
+
function countQuestionMarks(sql, end) {
|
|
962
|
+
let count = 0;
|
|
963
|
+
let inSingle = false;
|
|
964
|
+
let inDouble = false;
|
|
965
|
+
let inLineComment = false;
|
|
966
|
+
let inBlockComment = false;
|
|
967
|
+
for (let i = 0; i < end; i++) {
|
|
968
|
+
const ch = sql[i];
|
|
969
|
+
const next = sql[i + 1];
|
|
970
|
+
if (inLineComment) {
|
|
971
|
+
if (ch === "\n") inLineComment = false;
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (inBlockComment) {
|
|
975
|
+
if (ch === "*" && next === "/") {
|
|
976
|
+
inBlockComment = false;
|
|
977
|
+
i += 1;
|
|
978
|
+
}
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
982
|
+
inLineComment = true;
|
|
983
|
+
i += 1;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
987
|
+
inBlockComment = true;
|
|
988
|
+
i += 1;
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
992
|
+
inSingle = !inSingle;
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
996
|
+
inDouble = !inDouble;
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (!inSingle && !inDouble && ch === "?") {
|
|
1000
|
+
count += 1;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return count;
|
|
1280
1004
|
}
|
|
1281
|
-
function
|
|
1282
|
-
const
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1005
|
+
function findBooleanPlaceholderIndexes(sql) {
|
|
1006
|
+
const indexes = /* @__PURE__ */ new Set();
|
|
1007
|
+
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1008
|
+
const pattern = new RegExp(`(?:\\b[a-z_][a-z0-9_]*\\.)?${column}\\s*=\\s*\\?`, "giu");
|
|
1009
|
+
for (const match of sql.matchAll(pattern)) {
|
|
1010
|
+
const matchText = match[0];
|
|
1011
|
+
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1012
|
+
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return indexes;
|
|
1285
1016
|
}
|
|
1286
|
-
function
|
|
1287
|
-
const match =
|
|
1288
|
-
if (!match) return
|
|
1289
|
-
const
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
return
|
|
1017
|
+
function coerceInsertBooleanArgs(sql, args) {
|
|
1018
|
+
const match = /^\s*INSERT(?:\s+OR\s+(?:IGNORE|REPLACE))?\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)/iu.exec(sql);
|
|
1019
|
+
if (!match) return;
|
|
1020
|
+
const rawTable = match[1];
|
|
1021
|
+
const rawColumns = match[2];
|
|
1022
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1023
|
+
if (!boolColumns?.size) return;
|
|
1024
|
+
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1025
|
+
for (const [index, column] of columns.entries()) {
|
|
1026
|
+
if (boolColumns.has(column) && index < args.length) {
|
|
1027
|
+
args[index] = toBoolean(args[index]);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1293
1030
|
}
|
|
1294
|
-
function
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1031
|
+
function coerceUpdateBooleanArgs(sql, args) {
|
|
1032
|
+
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1033
|
+
if (!match) return;
|
|
1034
|
+
const rawTable = match[1];
|
|
1035
|
+
const setClause = match[2];
|
|
1036
|
+
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1037
|
+
if (!boolColumns?.size) return;
|
|
1038
|
+
const assignments = setClause.split(",");
|
|
1039
|
+
let placeholderIndex = 0;
|
|
1040
|
+
for (const assignment of assignments) {
|
|
1041
|
+
if (!assignment.includes("?")) continue;
|
|
1042
|
+
placeholderIndex += 1;
|
|
1043
|
+
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1044
|
+
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1045
|
+
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1299
1048
|
}
|
|
1300
|
-
function
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
|
|
1049
|
+
function coerceBooleanArgs(sql, args) {
|
|
1050
|
+
const nextArgs = [...args];
|
|
1051
|
+
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1052
|
+
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1053
|
+
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1054
|
+
for (const index of placeholderIndexes) {
|
|
1055
|
+
if (index > 0 && index <= nextArgs.length) {
|
|
1056
|
+
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1057
|
+
}
|
|
1304
1058
|
}
|
|
1305
|
-
return
|
|
1059
|
+
return nextArgs;
|
|
1306
1060
|
}
|
|
1307
|
-
function
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
const idPath = path7.join(IDENTITY_DIR, `${coordinator.name}.md`);
|
|
1311
|
-
if (!existsSync8(idPath)) return;
|
|
1312
|
-
const content = readFileSync5(idPath, "utf-8");
|
|
1313
|
-
if (content.includes(`**${capitalize(employee.name)}`)) return;
|
|
1314
|
-
const teamMatch = content.match(TEAM_SECTION_RE);
|
|
1315
|
-
if (!teamMatch || teamMatch.index === void 0) return;
|
|
1316
|
-
const afterTeam = content.slice(teamMatch.index + teamMatch[0].length);
|
|
1317
|
-
const nextHeading = afterTeam.match(/\n## /);
|
|
1318
|
-
const entry = `
|
|
1319
|
-
**${capitalize(employee.name)} (${employee.role}):** Newly hired. Update this description as the role develops.
|
|
1320
|
-
`;
|
|
1321
|
-
let updated;
|
|
1322
|
-
if (nextHeading && nextHeading.index !== void 0) {
|
|
1323
|
-
const insertAt = teamMatch.index + teamMatch[0].length + nextHeading.index;
|
|
1324
|
-
updated = content.slice(0, insertAt) + entry + content.slice(insertAt);
|
|
1325
|
-
} else {
|
|
1326
|
-
updated = content.trimEnd() + "\n" + entry;
|
|
1327
|
-
}
|
|
1328
|
-
writeFileSync4(idPath, updated, "utf-8");
|
|
1329
|
-
}
|
|
1330
|
-
function capitalize(s) {
|
|
1331
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1332
|
-
}
|
|
1333
|
-
async function hireEmployee(employee) {
|
|
1334
|
-
const employees = await loadEmployees();
|
|
1335
|
-
const updated = addEmployee(employees, employee);
|
|
1336
|
-
await saveEmployees(updated);
|
|
1337
|
-
try {
|
|
1338
|
-
appendToCoordinatorTeam(employee);
|
|
1339
|
-
} catch {
|
|
1340
|
-
}
|
|
1341
|
-
try {
|
|
1342
|
-
const { loadAgentConfig: loadAgentConfig2, saveAgentConfig: saveAgentConfig2 } = await Promise.resolve().then(() => (init_agent_config(), agent_config_exports));
|
|
1343
|
-
const config2 = loadAgentConfig2();
|
|
1344
|
-
const name = employee.name.toLowerCase();
|
|
1345
|
-
if (!config2[name] && config2["default"]) {
|
|
1346
|
-
config2[name] = { ...config2["default"] };
|
|
1347
|
-
saveAgentConfig2(config2);
|
|
1348
|
-
}
|
|
1349
|
-
} catch {
|
|
1350
|
-
}
|
|
1351
|
-
return updated;
|
|
1352
|
-
}
|
|
1353
|
-
async function normalizeRosterCase(rosterPath) {
|
|
1354
|
-
const employees = await loadEmployees(rosterPath);
|
|
1355
|
-
let changed = false;
|
|
1356
|
-
for (const emp of employees) {
|
|
1357
|
-
if (emp.name !== emp.name.toLowerCase()) {
|
|
1358
|
-
const oldName = emp.name;
|
|
1359
|
-
emp.name = emp.name.toLowerCase();
|
|
1360
|
-
changed = true;
|
|
1361
|
-
try {
|
|
1362
|
-
const identityDir = path7.join(os3.homedir(), ".exe-os", "identity");
|
|
1363
|
-
const oldPath = path7.join(identityDir, `${oldName}.md`);
|
|
1364
|
-
const newPath = path7.join(identityDir, `${emp.name}.md`);
|
|
1365
|
-
if (existsSync8(oldPath) && !existsSync8(newPath)) {
|
|
1366
|
-
renameSync2(oldPath, newPath);
|
|
1367
|
-
} else if (existsSync8(oldPath) && oldPath !== newPath) {
|
|
1368
|
-
const content = readFileSync5(oldPath, "utf-8");
|
|
1369
|
-
writeFileSync4(newPath, content, "utf-8");
|
|
1370
|
-
if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
|
|
1371
|
-
unlinkSync(oldPath);
|
|
1372
|
-
}
|
|
1373
|
-
}
|
|
1374
|
-
} catch {
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
if (changed) {
|
|
1379
|
-
await saveEmployees(employees, rosterPath);
|
|
1380
|
-
}
|
|
1381
|
-
return changed;
|
|
1382
|
-
}
|
|
1383
|
-
function findExeBin() {
|
|
1384
|
-
try {
|
|
1385
|
-
return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
|
|
1386
|
-
} catch {
|
|
1387
|
-
return null;
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
function registerBinSymlinks(name) {
|
|
1391
|
-
const created = [];
|
|
1392
|
-
const skipped = [];
|
|
1393
|
-
const errors = [];
|
|
1394
|
-
const exeBinPath = findExeBin();
|
|
1395
|
-
if (!exeBinPath) {
|
|
1396
|
-
errors.push("Could not find 'exe-os' in PATH");
|
|
1397
|
-
return { created, skipped, errors };
|
|
1398
|
-
}
|
|
1399
|
-
const binDir = path7.dirname(exeBinPath);
|
|
1400
|
-
let target;
|
|
1401
|
-
try {
|
|
1402
|
-
target = readlinkSync(exeBinPath);
|
|
1403
|
-
} catch {
|
|
1404
|
-
errors.push("Could not read 'exe' symlink");
|
|
1405
|
-
return { created, skipped, errors };
|
|
1406
|
-
}
|
|
1407
|
-
for (const suffix of ["", "-opencode"]) {
|
|
1408
|
-
const linkName = `${name}${suffix}`;
|
|
1409
|
-
const linkPath = path7.join(binDir, linkName);
|
|
1410
|
-
if (existsSync8(linkPath)) {
|
|
1411
|
-
skipped.push(linkName);
|
|
1412
|
-
continue;
|
|
1413
|
-
}
|
|
1414
|
-
try {
|
|
1415
|
-
symlinkSync(target, linkPath);
|
|
1416
|
-
created.push(linkName);
|
|
1417
|
-
} catch (err) {
|
|
1418
|
-
errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
return { created, skipped, errors };
|
|
1422
|
-
}
|
|
1423
|
-
var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES, IDENTITY_DIR, TEAM_SECTION_RE;
|
|
1424
|
-
var init_employees = __esm({
|
|
1425
|
-
"src/lib/employees.ts"() {
|
|
1426
|
-
"use strict";
|
|
1427
|
-
init_config();
|
|
1428
|
-
EMPLOYEES_PATH = path7.join(EXE_AI_DIR, "exe-employees.json");
|
|
1429
|
-
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
1430
|
-
COORDINATOR_ROLE = "COO";
|
|
1431
|
-
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
1432
|
-
IDENTITY_DIR = path7.join(EXE_AI_DIR, "identity");
|
|
1433
|
-
TEAM_SECTION_RE = /^## Team\b.*$/m;
|
|
1434
|
-
}
|
|
1435
|
-
});
|
|
1436
|
-
|
|
1437
|
-
// src/lib/database-adapter.ts
|
|
1438
|
-
import os4 from "os";
|
|
1439
|
-
import path8 from "path";
|
|
1440
|
-
import { createRequire as createRequire2 } from "module";
|
|
1441
|
-
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
1442
|
-
function quotedIdentifier(identifier) {
|
|
1443
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
1444
|
-
}
|
|
1445
|
-
function unqualifiedTableName(name) {
|
|
1446
|
-
const raw = name.trim().replace(/^"|"$/g, "");
|
|
1447
|
-
const parts = raw.split(".");
|
|
1448
|
-
return parts[parts.length - 1].replace(/^"|"$/g, "").toLowerCase();
|
|
1449
|
-
}
|
|
1450
|
-
function stripTrailingSemicolon(sql) {
|
|
1451
|
-
return sql.trim().replace(/;+\s*$/u, "");
|
|
1452
|
-
}
|
|
1453
|
-
function appendClause(sql, clause) {
|
|
1454
|
-
const trimmed = stripTrailingSemicolon(sql);
|
|
1455
|
-
const returningMatch = /\sRETURNING\b[\s\S]*$/iu.exec(trimmed);
|
|
1456
|
-
if (!returningMatch) {
|
|
1457
|
-
return `${trimmed}${clause}`;
|
|
1458
|
-
}
|
|
1459
|
-
const idx = returningMatch.index;
|
|
1460
|
-
return `${trimmed.slice(0, idx)}${clause}${trimmed.slice(idx)}`;
|
|
1461
|
-
}
|
|
1462
|
-
function normalizeStatement(stmt) {
|
|
1463
|
-
if (typeof stmt === "string") {
|
|
1464
|
-
return { kind: "positional", sql: stmt, args: [] };
|
|
1465
|
-
}
|
|
1466
|
-
const sql = stmt.sql;
|
|
1467
|
-
if (Array.isArray(stmt.args) || stmt.args === void 0) {
|
|
1468
|
-
return { kind: "positional", sql, args: stmt.args ?? [] };
|
|
1469
|
-
}
|
|
1470
|
-
return { kind: "named", sql, args: stmt.args };
|
|
1471
|
-
}
|
|
1472
|
-
function rewriteBooleanLiterals(sql) {
|
|
1473
|
-
let out = sql;
|
|
1474
|
-
for (const column of BOOLEAN_COLUMN_NAMES) {
|
|
1475
|
-
const scoped = `((?:\\b[a-z_][a-z0-9_]*\\.)?${column})`;
|
|
1476
|
-
out = out.replace(new RegExp(`${scoped}\\s*=\\s*0\\b`, "giu"), "$1 = FALSE");
|
|
1477
|
-
out = out.replace(new RegExp(`${scoped}\\s*=\\s*1\\b`, "giu"), "$1 = TRUE");
|
|
1478
|
-
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*0\\b`, "giu"), "$1 != FALSE");
|
|
1479
|
-
out = out.replace(new RegExp(`${scoped}\\s*!=\\s*1\\b`, "giu"), "$1 != TRUE");
|
|
1480
|
-
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*0\\b`, "giu"), "$1 <> FALSE");
|
|
1481
|
-
out = out.replace(new RegExp(`${scoped}\\s*<>\\s*1\\b`, "giu"), "$1 <> TRUE");
|
|
1482
|
-
}
|
|
1483
|
-
return out;
|
|
1484
|
-
}
|
|
1485
|
-
function rewriteInsertOrIgnore(sql) {
|
|
1486
|
-
if (!/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu.test(sql)) {
|
|
1487
|
-
return sql;
|
|
1488
|
-
}
|
|
1489
|
-
const replaced = sql.replace(/^\s*INSERT\s+OR\s+IGNORE\s+INTO\b/iu, "INSERT INTO");
|
|
1490
|
-
return /\bON\s+CONFLICT\b/iu.test(replaced) ? replaced : appendClause(replaced, " ON CONFLICT DO NOTHING");
|
|
1491
|
-
}
|
|
1492
|
-
function rewriteInsertOrReplace(sql) {
|
|
1493
|
-
const match = /^\s*INSERT\s+OR\s+REPLACE\s+INTO\s+([A-Za-z0-9_."]+)\s*\(([^)]+)\)([\s\S]*)$/iu.exec(sql);
|
|
1494
|
-
if (!match) {
|
|
1495
|
-
return sql;
|
|
1496
|
-
}
|
|
1497
|
-
const rawTable = match[1];
|
|
1498
|
-
const rawColumns = match[2];
|
|
1499
|
-
const remainder = match[3];
|
|
1500
|
-
const tableName = unqualifiedTableName(rawTable);
|
|
1501
|
-
const conflictKeys = UPSERT_KEYS[tableName];
|
|
1502
|
-
if (!conflictKeys?.length) {
|
|
1503
|
-
return sql;
|
|
1504
|
-
}
|
|
1505
|
-
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1506
|
-
const updateColumns = columns.filter((col) => !conflictKeys.includes(col));
|
|
1507
|
-
const conflictTarget = conflictKeys.map(quotedIdentifier).join(", ");
|
|
1508
|
-
const updateClause = updateColumns.length === 0 ? " DO NOTHING" : ` DO UPDATE SET ${updateColumns.map((col) => `${quotedIdentifier(col)} = EXCLUDED.${quotedIdentifier(col)}`).join(", ")}`;
|
|
1509
|
-
return `INSERT INTO ${rawTable} (${rawColumns})${appendClause(remainder, ` ON CONFLICT (${conflictTarget})${updateClause}`)}`;
|
|
1510
|
-
}
|
|
1511
|
-
function rewriteSql(sql) {
|
|
1512
|
-
let out = sql;
|
|
1513
|
-
out = out.replace(/\bdatetime\(\s*['"]now['"]\s*\)/giu, "CURRENT_TIMESTAMP");
|
|
1514
|
-
out = out.replace(/\bvector32\s*\(\s*\?\s*\)/giu, "?");
|
|
1515
|
-
out = rewriteBooleanLiterals(out);
|
|
1516
|
-
out = rewriteInsertOrReplace(out);
|
|
1517
|
-
out = rewriteInsertOrIgnore(out);
|
|
1518
|
-
return stripTrailingSemicolon(out);
|
|
1519
|
-
}
|
|
1520
|
-
function toBoolean(value) {
|
|
1521
|
-
if (value === null || value === void 0) return value;
|
|
1522
|
-
if (typeof value === "boolean") return value;
|
|
1523
|
-
if (typeof value === "number") return value !== 0;
|
|
1524
|
-
if (typeof value === "bigint") return value !== 0n;
|
|
1525
|
-
if (typeof value === "string") {
|
|
1526
|
-
const normalized = value.trim().toLowerCase();
|
|
1527
|
-
if (normalized === "0" || normalized === "false") return false;
|
|
1528
|
-
if (normalized === "1" || normalized === "true") return true;
|
|
1529
|
-
}
|
|
1530
|
-
return Boolean(value);
|
|
1531
|
-
}
|
|
1532
|
-
function countQuestionMarks(sql, end) {
|
|
1533
|
-
let count = 0;
|
|
1061
|
+
function convertQuestionMarksToDollarParams(sql) {
|
|
1062
|
+
let out = "";
|
|
1063
|
+
let placeholder = 0;
|
|
1534
1064
|
let inSingle = false;
|
|
1535
1065
|
let inDouble = false;
|
|
1536
1066
|
let inLineComment = false;
|
|
1537
1067
|
let inBlockComment = false;
|
|
1538
|
-
for (let i = 0; i <
|
|
1068
|
+
for (let i = 0; i < sql.length; i++) {
|
|
1539
1069
|
const ch = sql[i];
|
|
1540
1070
|
const next = sql[i + 1];
|
|
1541
1071
|
if (inLineComment) {
|
|
1072
|
+
out += ch;
|
|
1542
1073
|
if (ch === "\n") inLineComment = false;
|
|
1543
1074
|
continue;
|
|
1544
1075
|
}
|
|
1545
1076
|
if (inBlockComment) {
|
|
1077
|
+
out += ch;
|
|
1546
1078
|
if (ch === "*" && next === "/") {
|
|
1079
|
+
out += next;
|
|
1547
1080
|
inBlockComment = false;
|
|
1548
1081
|
i += 1;
|
|
1549
1082
|
}
|
|
1550
1083
|
continue;
|
|
1551
1084
|
}
|
|
1552
1085
|
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1086
|
+
out += ch + next;
|
|
1553
1087
|
inLineComment = true;
|
|
1554
1088
|
i += 1;
|
|
1555
1089
|
continue;
|
|
1556
1090
|
}
|
|
1557
1091
|
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1092
|
+
out += ch + next;
|
|
1558
1093
|
inBlockComment = true;
|
|
1559
1094
|
i += 1;
|
|
1560
1095
|
continue;
|
|
1561
1096
|
}
|
|
1562
1097
|
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1563
1098
|
inSingle = !inSingle;
|
|
1099
|
+
out += ch;
|
|
1564
1100
|
continue;
|
|
1565
1101
|
}
|
|
1566
1102
|
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1567
1103
|
inDouble = !inDouble;
|
|
1104
|
+
out += ch;
|
|
1568
1105
|
continue;
|
|
1569
1106
|
}
|
|
1570
1107
|
if (!inSingle && !inDouble && ch === "?") {
|
|
1571
|
-
|
|
1108
|
+
placeholder += 1;
|
|
1109
|
+
out += `$${placeholder}`;
|
|
1110
|
+
continue;
|
|
1572
1111
|
}
|
|
1112
|
+
out += ch;
|
|
1573
1113
|
}
|
|
1574
|
-
return
|
|
1114
|
+
return out;
|
|
1575
1115
|
}
|
|
1576
|
-
function
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
for (const match of sql.matchAll(pattern)) {
|
|
1581
|
-
const matchText = match[0];
|
|
1582
|
-
const qIndex = match.index + matchText.lastIndexOf("?");
|
|
1583
|
-
indexes.add(countQuestionMarks(sql, qIndex + 1));
|
|
1584
|
-
}
|
|
1116
|
+
function translateStatementForPostgres(stmt) {
|
|
1117
|
+
const normalized = normalizeStatement(stmt);
|
|
1118
|
+
if (normalized.kind === "named") {
|
|
1119
|
+
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1585
1120
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
const rawColumns = match[2];
|
|
1593
|
-
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1594
|
-
if (!boolColumns?.size) return;
|
|
1595
|
-
const columns = rawColumns.split(",").map((col) => col.trim().replace(/^"|"$/g, ""));
|
|
1596
|
-
for (const [index, column] of columns.entries()) {
|
|
1597
|
-
if (boolColumns.has(column) && index < args.length) {
|
|
1598
|
-
args[index] = toBoolean(args[index]);
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
function coerceUpdateBooleanArgs(sql, args) {
|
|
1603
|
-
const match = /^\s*UPDATE\s+([A-Za-z0-9_."]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE\b|$)/iu.exec(sql);
|
|
1604
|
-
if (!match) return;
|
|
1605
|
-
const rawTable = match[1];
|
|
1606
|
-
const setClause = match[2];
|
|
1607
|
-
const boolColumns = BOOLEAN_COLUMNS_BY_TABLE[unqualifiedTableName(rawTable)];
|
|
1608
|
-
if (!boolColumns?.size) return;
|
|
1609
|
-
const assignments = setClause.split(",");
|
|
1610
|
-
let placeholderIndex = 0;
|
|
1611
|
-
for (const assignment of assignments) {
|
|
1612
|
-
if (!assignment.includes("?")) continue;
|
|
1613
|
-
placeholderIndex += 1;
|
|
1614
|
-
const colMatch = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\.)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*\?/iu.exec(assignment);
|
|
1615
|
-
if (colMatch && boolColumns.has(colMatch[1])) {
|
|
1616
|
-
args[placeholderIndex - 1] = toBoolean(args[placeholderIndex - 1]);
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
function coerceBooleanArgs(sql, args) {
|
|
1621
|
-
const nextArgs = [...args];
|
|
1622
|
-
coerceInsertBooleanArgs(sql, nextArgs);
|
|
1623
|
-
coerceUpdateBooleanArgs(sql, nextArgs);
|
|
1624
|
-
const placeholderIndexes = findBooleanPlaceholderIndexes(sql);
|
|
1625
|
-
for (const index of placeholderIndexes) {
|
|
1626
|
-
if (index > 0 && index <= nextArgs.length) {
|
|
1627
|
-
nextArgs[index - 1] = toBoolean(nextArgs[index - 1]);
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
return nextArgs;
|
|
1631
|
-
}
|
|
1632
|
-
function convertQuestionMarksToDollarParams(sql) {
|
|
1633
|
-
let out = "";
|
|
1634
|
-
let placeholder = 0;
|
|
1635
|
-
let inSingle = false;
|
|
1636
|
-
let inDouble = false;
|
|
1637
|
-
let inLineComment = false;
|
|
1638
|
-
let inBlockComment = false;
|
|
1639
|
-
for (let i = 0; i < sql.length; i++) {
|
|
1640
|
-
const ch = sql[i];
|
|
1641
|
-
const next = sql[i + 1];
|
|
1642
|
-
if (inLineComment) {
|
|
1643
|
-
out += ch;
|
|
1644
|
-
if (ch === "\n") inLineComment = false;
|
|
1645
|
-
continue;
|
|
1646
|
-
}
|
|
1647
|
-
if (inBlockComment) {
|
|
1648
|
-
out += ch;
|
|
1649
|
-
if (ch === "*" && next === "/") {
|
|
1650
|
-
out += next;
|
|
1651
|
-
inBlockComment = false;
|
|
1652
|
-
i += 1;
|
|
1653
|
-
}
|
|
1654
|
-
continue;
|
|
1655
|
-
}
|
|
1656
|
-
if (!inSingle && !inDouble && ch === "-" && next === "-") {
|
|
1657
|
-
out += ch + next;
|
|
1658
|
-
inLineComment = true;
|
|
1659
|
-
i += 1;
|
|
1660
|
-
continue;
|
|
1661
|
-
}
|
|
1662
|
-
if (!inSingle && !inDouble && ch === "/" && next === "*") {
|
|
1663
|
-
out += ch + next;
|
|
1664
|
-
inBlockComment = true;
|
|
1665
|
-
i += 1;
|
|
1666
|
-
continue;
|
|
1667
|
-
}
|
|
1668
|
-
if (!inDouble && ch === "'" && sql[i - 1] !== "\\") {
|
|
1669
|
-
inSingle = !inSingle;
|
|
1670
|
-
out += ch;
|
|
1671
|
-
continue;
|
|
1672
|
-
}
|
|
1673
|
-
if (!inSingle && ch === '"' && sql[i - 1] !== "\\") {
|
|
1674
|
-
inDouble = !inDouble;
|
|
1675
|
-
out += ch;
|
|
1676
|
-
continue;
|
|
1677
|
-
}
|
|
1678
|
-
if (!inSingle && !inDouble && ch === "?") {
|
|
1679
|
-
placeholder += 1;
|
|
1680
|
-
out += `$${placeholder}`;
|
|
1681
|
-
continue;
|
|
1682
|
-
}
|
|
1683
|
-
out += ch;
|
|
1684
|
-
}
|
|
1685
|
-
return out;
|
|
1686
|
-
}
|
|
1687
|
-
function translateStatementForPostgres(stmt) {
|
|
1688
|
-
const normalized = normalizeStatement(stmt);
|
|
1689
|
-
if (normalized.kind === "named") {
|
|
1690
|
-
throw new Error("Named SQL parameters are not supported by the Prisma adapter.");
|
|
1691
|
-
}
|
|
1692
|
-
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1693
|
-
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1694
|
-
return {
|
|
1695
|
-
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1696
|
-
args: coercedArgs
|
|
1697
|
-
};
|
|
1121
|
+
const rewrittenSql = rewriteSql(normalized.sql);
|
|
1122
|
+
const coercedArgs = coerceBooleanArgs(rewrittenSql, normalized.args);
|
|
1123
|
+
return {
|
|
1124
|
+
sql: convertQuestionMarksToDollarParams(rewrittenSql),
|
|
1125
|
+
args: coercedArgs
|
|
1126
|
+
};
|
|
1698
1127
|
}
|
|
1699
1128
|
function shouldBypassPostgres(stmt) {
|
|
1700
1129
|
const normalized = normalizeStatement(stmt);
|
|
@@ -1740,17 +1169,17 @@ async function loadPrismaClient() {
|
|
|
1740
1169
|
prismaClientPromise = (async () => {
|
|
1741
1170
|
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
1742
1171
|
if (explicitPath) {
|
|
1743
|
-
const module2 = await import(
|
|
1172
|
+
const module2 = await import(pathToFileURL(explicitPath).href);
|
|
1744
1173
|
const PrismaClient2 = module2.PrismaClient ?? module2.default?.PrismaClient;
|
|
1745
1174
|
if (!PrismaClient2) {
|
|
1746
1175
|
throw new Error(`No PrismaClient export found at ${explicitPath}`);
|
|
1747
1176
|
}
|
|
1748
1177
|
return new PrismaClient2();
|
|
1749
1178
|
}
|
|
1750
|
-
const exeDbRoot = process.env.EXE_DB_ROOT ??
|
|
1751
|
-
const requireFromExeDb =
|
|
1179
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path6.join(os3.homedir(), "exe-db");
|
|
1180
|
+
const requireFromExeDb = createRequire(path6.join(exeDbRoot, "package.json"));
|
|
1752
1181
|
const prismaEntry = requireFromExeDb.resolve("@prisma/client");
|
|
1753
|
-
const module = await import(
|
|
1182
|
+
const module = await import(pathToFileURL(prismaEntry).href);
|
|
1754
1183
|
const PrismaClient = module.PrismaClient ?? module.default?.PrismaClient;
|
|
1755
1184
|
if (!PrismaClient) {
|
|
1756
1185
|
throw new Error(`No PrismaClient export found in ${prismaEntry}`);
|
|
@@ -2031,11 +1460,11 @@ __export(exe_daemon_client_exports, {
|
|
|
2031
1460
|
sendIngestRequest: () => sendIngestRequest
|
|
2032
1461
|
});
|
|
2033
1462
|
import net from "net";
|
|
2034
|
-
import
|
|
1463
|
+
import os4 from "os";
|
|
2035
1464
|
import { spawn } from "child_process";
|
|
2036
1465
|
import { randomUUID } from "crypto";
|
|
2037
|
-
import { existsSync as
|
|
2038
|
-
import
|
|
1466
|
+
import { existsSync as existsSync7, unlinkSync as unlinkSync2, readFileSync as readFileSync6, openSync, closeSync, statSync } from "fs";
|
|
1467
|
+
import path7 from "path";
|
|
2039
1468
|
import { fileURLToPath } from "url";
|
|
2040
1469
|
function handleData(chunk) {
|
|
2041
1470
|
_buffer += chunk.toString();
|
|
@@ -2063,7 +1492,7 @@ function handleData(chunk) {
|
|
|
2063
1492
|
}
|
|
2064
1493
|
}
|
|
2065
1494
|
function cleanupStaleFiles() {
|
|
2066
|
-
if (
|
|
1495
|
+
if (existsSync7(PID_PATH)) {
|
|
2067
1496
|
try {
|
|
2068
1497
|
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
2069
1498
|
if (pid > 0) {
|
|
@@ -2086,11 +1515,11 @@ function cleanupStaleFiles() {
|
|
|
2086
1515
|
}
|
|
2087
1516
|
}
|
|
2088
1517
|
function findPackageRoot() {
|
|
2089
|
-
let dir =
|
|
2090
|
-
const { root } =
|
|
1518
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
1519
|
+
const { root } = path7.parse(dir);
|
|
2091
1520
|
while (dir !== root) {
|
|
2092
|
-
if (
|
|
2093
|
-
dir =
|
|
1521
|
+
if (existsSync7(path7.join(dir, "package.json"))) return dir;
|
|
1522
|
+
dir = path7.dirname(dir);
|
|
2094
1523
|
}
|
|
2095
1524
|
return null;
|
|
2096
1525
|
}
|
|
@@ -2110,14 +1539,14 @@ function getAvailableMemoryGB() {
|
|
|
2110
1539
|
const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
|
|
2111
1540
|
return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
|
|
2112
1541
|
} catch {
|
|
2113
|
-
return
|
|
1542
|
+
return os4.freemem() / (1024 * 1024 * 1024);
|
|
2114
1543
|
}
|
|
2115
1544
|
}
|
|
2116
|
-
return
|
|
1545
|
+
return os4.freemem() / (1024 * 1024 * 1024);
|
|
2117
1546
|
}
|
|
2118
1547
|
function spawnDaemon() {
|
|
2119
1548
|
const freeGB = getAvailableMemoryGB();
|
|
2120
|
-
const totalGB =
|
|
1549
|
+
const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
|
|
2121
1550
|
if (totalGB <= 8) {
|
|
2122
1551
|
process.stderr.write(
|
|
2123
1552
|
`[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
|
|
@@ -2137,8 +1566,8 @@ function spawnDaemon() {
|
|
|
2137
1566
|
process.stderr.write("[exed-client] WARN: cannot find package root\n");
|
|
2138
1567
|
return;
|
|
2139
1568
|
}
|
|
2140
|
-
const daemonPath =
|
|
2141
|
-
if (!
|
|
1569
|
+
const daemonPath = path7.join(pkgRoot, "dist", "lib", "exe-daemon.js");
|
|
1570
|
+
if (!existsSync7(daemonPath)) {
|
|
2142
1571
|
process.stderr.write(`[exed-client] WARN: daemon script not found at ${daemonPath}
|
|
2143
1572
|
`);
|
|
2144
1573
|
return;
|
|
@@ -2147,7 +1576,7 @@ function spawnDaemon() {
|
|
|
2147
1576
|
const daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV] ?? null);
|
|
2148
1577
|
process.stderr.write(`[exed-client] Spawning daemon: ${resolvedPath}
|
|
2149
1578
|
`);
|
|
2150
|
-
const logPath =
|
|
1579
|
+
const logPath = path7.join(path7.dirname(SOCKET_PATH), "exed.log");
|
|
2151
1580
|
let stderrFd = "ignore";
|
|
2152
1581
|
try {
|
|
2153
1582
|
stderrFd = openSync(logPath, "a");
|
|
@@ -2312,7 +1741,7 @@ function killAndRespawnDaemon() {
|
|
|
2312
1741
|
}
|
|
2313
1742
|
try {
|
|
2314
1743
|
process.stderr.write("[exed-client] Killing daemon for restart...\n");
|
|
2315
|
-
if (
|
|
1744
|
+
if (existsSync7(PID_PATH)) {
|
|
2316
1745
|
try {
|
|
2317
1746
|
const pid = parseInt(readFileSync6(PID_PATH, "utf8").trim(), 10);
|
|
2318
1747
|
if (pid > 0) {
|
|
@@ -2460,9 +1889,9 @@ var init_exe_daemon_client = __esm({
|
|
|
2460
1889
|
"use strict";
|
|
2461
1890
|
init_config();
|
|
2462
1891
|
init_daemon_auth();
|
|
2463
|
-
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
2464
|
-
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
2465
|
-
SPAWN_LOCK_PATH =
|
|
1892
|
+
SOCKET_PATH = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path7.join(EXE_AI_DIR, "exed.sock");
|
|
1893
|
+
PID_PATH = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path7.join(EXE_AI_DIR, "exed.pid");
|
|
1894
|
+
SPAWN_LOCK_PATH = path7.join(EXE_AI_DIR, "exed-spawn.lock");
|
|
2466
1895
|
SPAWN_LOCK_STALE_MS = 3e4;
|
|
2467
1896
|
CONNECT_TIMEOUT_MS = 15e3;
|
|
2468
1897
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
@@ -2631,7 +2060,7 @@ __export(database_exports, {
|
|
|
2631
2060
|
isInitialized: () => isInitialized,
|
|
2632
2061
|
setExternalClient: () => setExternalClient
|
|
2633
2062
|
});
|
|
2634
|
-
import { createClient
|
|
2063
|
+
import { createClient } from "@libsql/client";
|
|
2635
2064
|
async function initDatabase(config2) {
|
|
2636
2065
|
if (_walCheckpointTimer) {
|
|
2637
2066
|
clearInterval(_walCheckpointTimer);
|
|
@@ -2656,7 +2085,7 @@ async function initDatabase(config2) {
|
|
|
2656
2085
|
if (config2.encryptionKey) {
|
|
2657
2086
|
opts.encryptionKey = config2.encryptionKey;
|
|
2658
2087
|
}
|
|
2659
|
-
_client =
|
|
2088
|
+
_client = createClient(opts);
|
|
2660
2089
|
_resilientClient = wrapWithRetry(_client);
|
|
2661
2090
|
_adapterClient = _resilientClient;
|
|
2662
2091
|
_client.execute("PRAGMA busy_timeout = 30000").catch(() => {
|
|
@@ -3246,6 +2675,14 @@ async function ensureSchema() {
|
|
|
3246
2675
|
);
|
|
3247
2676
|
} catch {
|
|
3248
2677
|
}
|
|
2678
|
+
try {
|
|
2679
|
+
await client.execute(
|
|
2680
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
|
|
2681
|
+
ON memories(content_hash, agent_id, project_name, memory_type)
|
|
2682
|
+
WHERE content_hash IS NOT NULL`
|
|
2683
|
+
);
|
|
2684
|
+
} catch {
|
|
2685
|
+
}
|
|
3249
2686
|
await client.executeMultiple(`
|
|
3250
2687
|
CREATE TABLE IF NOT EXISTS entities (
|
|
3251
2688
|
id TEXT PRIMARY KEY,
|
|
@@ -3526,164 +2963,812 @@ async function ensureSchema() {
|
|
|
3526
2963
|
PRIMARY KEY (session_uuid, file_path)
|
|
3527
2964
|
);
|
|
3528
2965
|
|
|
3529
|
-
CREATE INDEX IF NOT EXISTS idx_agent_file_reads_agent_read_at
|
|
3530
|
-
ON agent_file_reads(agent_id, read_at);
|
|
3531
|
-
`);
|
|
3532
|
-
try {
|
|
3533
|
-
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
3534
|
-
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
3535
|
-
await client.execute({
|
|
3536
|
-
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
3537
|
-
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
3538
|
-
FROM memories
|
|
3539
|
-
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
3540
|
-
GROUP BY session_id, agent_id`,
|
|
3541
|
-
args: []
|
|
3542
|
-
});
|
|
3543
|
-
}
|
|
3544
|
-
} catch {
|
|
2966
|
+
CREATE INDEX IF NOT EXISTS idx_agent_file_reads_agent_read_at
|
|
2967
|
+
ON agent_file_reads(agent_id, read_at);
|
|
2968
|
+
`);
|
|
2969
|
+
try {
|
|
2970
|
+
const mapCount = await client.execute({ sql: `SELECT COUNT(*) as cnt FROM session_agent_map`, args: [] });
|
|
2971
|
+
if (Number(mapCount.rows[0]?.cnt ?? 0) === 0) {
|
|
2972
|
+
await client.execute({
|
|
2973
|
+
sql: `INSERT OR IGNORE INTO session_agent_map (session_uuid, agent_id, session_name, started_at)
|
|
2974
|
+
SELECT session_id, agent_id, '', MIN(timestamp)
|
|
2975
|
+
FROM memories
|
|
2976
|
+
WHERE session_id IS NOT NULL AND session_id != '' AND agent_id IS NOT NULL AND agent_id != ''
|
|
2977
|
+
GROUP BY session_id, agent_id`,
|
|
2978
|
+
args: []
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
} catch {
|
|
2982
|
+
}
|
|
2983
|
+
try {
|
|
2984
|
+
await client.execute({
|
|
2985
|
+
sql: `ALTER TABLE session_agent_map ADD COLUMN cache_cold_count INTEGER NOT NULL DEFAULT 0`,
|
|
2986
|
+
args: []
|
|
2987
|
+
});
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
try {
|
|
2991
|
+
await client.execute({
|
|
2992
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
2993
|
+
args: []
|
|
2994
|
+
});
|
|
2995
|
+
} catch {
|
|
2996
|
+
}
|
|
2997
|
+
try {
|
|
2998
|
+
await client.execute({
|
|
2999
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
3000
|
+
args: []
|
|
3001
|
+
});
|
|
3002
|
+
} catch {
|
|
3003
|
+
}
|
|
3004
|
+
try {
|
|
3005
|
+
await client.execute({
|
|
3006
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
3007
|
+
args: []
|
|
3008
|
+
});
|
|
3009
|
+
} catch {
|
|
3010
|
+
}
|
|
3011
|
+
try {
|
|
3012
|
+
await client.execute({
|
|
3013
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
3014
|
+
args: []
|
|
3015
|
+
});
|
|
3016
|
+
} catch {
|
|
3017
|
+
}
|
|
3018
|
+
await client.executeMultiple(`
|
|
3019
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
3020
|
+
content_text,
|
|
3021
|
+
sender_name,
|
|
3022
|
+
agent_response,
|
|
3023
|
+
content='conversations',
|
|
3024
|
+
content_rowid='rowid'
|
|
3025
|
+
);
|
|
3026
|
+
|
|
3027
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_ai AFTER INSERT ON conversations BEGIN
|
|
3028
|
+
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
3029
|
+
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
3030
|
+
END;
|
|
3031
|
+
|
|
3032
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_ad AFTER DELETE ON conversations BEGIN
|
|
3033
|
+
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
3034
|
+
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
3035
|
+
END;
|
|
3036
|
+
|
|
3037
|
+
CREATE TRIGGER IF NOT EXISTS conversations_fts_au AFTER UPDATE ON conversations BEGIN
|
|
3038
|
+
INSERT INTO conversations_fts(conversations_fts, rowid, content_text, sender_name, agent_response)
|
|
3039
|
+
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
3040
|
+
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
3041
|
+
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
3042
|
+
END;
|
|
3043
|
+
`);
|
|
3044
|
+
try {
|
|
3045
|
+
await client.execute({
|
|
3046
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
3047
|
+
args: []
|
|
3048
|
+
});
|
|
3049
|
+
} catch {
|
|
3050
|
+
}
|
|
3051
|
+
try {
|
|
3052
|
+
await client.execute(
|
|
3053
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
3054
|
+
);
|
|
3055
|
+
} catch {
|
|
3056
|
+
}
|
|
3057
|
+
try {
|
|
3058
|
+
await client.execute({
|
|
3059
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
3060
|
+
args: []
|
|
3061
|
+
});
|
|
3062
|
+
await client.execute({
|
|
3063
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
3064
|
+
args: []
|
|
3065
|
+
});
|
|
3066
|
+
} catch {
|
|
3067
|
+
}
|
|
3068
|
+
try {
|
|
3069
|
+
await client.execute({
|
|
3070
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
3071
|
+
args: []
|
|
3072
|
+
});
|
|
3073
|
+
} catch {
|
|
3074
|
+
}
|
|
3075
|
+
try {
|
|
3076
|
+
await client.execute(
|
|
3077
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
3078
|
+
);
|
|
3079
|
+
} catch {
|
|
3080
|
+
}
|
|
3081
|
+
for (const col of [
|
|
3082
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
3083
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
3084
|
+
]) {
|
|
3085
|
+
try {
|
|
3086
|
+
await client.execute(col);
|
|
3087
|
+
} catch {
|
|
3088
|
+
}
|
|
3089
|
+
}
|
|
3090
|
+
try {
|
|
3091
|
+
await client.execute({
|
|
3092
|
+
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
3093
|
+
args: []
|
|
3094
|
+
});
|
|
3095
|
+
} catch {
|
|
3096
|
+
}
|
|
3097
|
+
try {
|
|
3098
|
+
await client.execute(
|
|
3099
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
3100
|
+
);
|
|
3101
|
+
} catch {
|
|
3102
|
+
}
|
|
3103
|
+
try {
|
|
3104
|
+
await client.execute({
|
|
3105
|
+
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
3106
|
+
args: []
|
|
3107
|
+
});
|
|
3108
|
+
} catch {
|
|
3109
|
+
}
|
|
3110
|
+
try {
|
|
3111
|
+
await client.execute(
|
|
3112
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
3113
|
+
);
|
|
3114
|
+
} catch {
|
|
3115
|
+
}
|
|
3116
|
+
try {
|
|
3117
|
+
await client.execute({
|
|
3118
|
+
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
3119
|
+
args: []
|
|
3120
|
+
});
|
|
3121
|
+
} catch {
|
|
3122
|
+
}
|
|
3123
|
+
for (const col of [
|
|
3124
|
+
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3125
|
+
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3126
|
+
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
3127
|
+
"ALTER TABLE memories ADD COLUMN referenced_entities TEXT",
|
|
3128
|
+
"ALTER TABLE memories ADD COLUMN retrieval_count INTEGER DEFAULT 0",
|
|
3129
|
+
"ALTER TABLE memories ADD COLUMN chain_position TEXT",
|
|
3130
|
+
"ALTER TABLE memories ADD COLUMN review_status TEXT",
|
|
3131
|
+
"ALTER TABLE memories ADD COLUMN context_window_pct INTEGER",
|
|
3132
|
+
"ALTER TABLE memories ADD COLUMN file_paths TEXT",
|
|
3133
|
+
"ALTER TABLE memories ADD COLUMN commit_hash TEXT",
|
|
3134
|
+
"ALTER TABLE memories ADD COLUMN duration_ms INTEGER",
|
|
3135
|
+
"ALTER TABLE memories ADD COLUMN token_cost REAL",
|
|
3136
|
+
"ALTER TABLE memories ADD COLUMN audience TEXT",
|
|
3137
|
+
"ALTER TABLE memories ADD COLUMN language_type TEXT",
|
|
3138
|
+
"ALTER TABLE memories ADD COLUMN parent_memory_id TEXT"
|
|
3139
|
+
]) {
|
|
3140
|
+
try {
|
|
3141
|
+
await client.execute(col);
|
|
3142
|
+
} catch {
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
try {
|
|
3146
|
+
await client.execute({
|
|
3147
|
+
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3148
|
+
args: []
|
|
3149
|
+
});
|
|
3150
|
+
} catch {
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
async function disposeDatabase() {
|
|
3154
|
+
if (_walCheckpointTimer) {
|
|
3155
|
+
clearInterval(_walCheckpointTimer);
|
|
3156
|
+
_walCheckpointTimer = null;
|
|
3157
|
+
}
|
|
3158
|
+
if (_daemonClient) {
|
|
3159
|
+
_daemonClient.close();
|
|
3160
|
+
_daemonClient = null;
|
|
3161
|
+
}
|
|
3162
|
+
if (_adapterClient && _adapterClient !== _resilientClient) {
|
|
3163
|
+
_adapterClient.close();
|
|
3164
|
+
}
|
|
3165
|
+
_adapterClient = null;
|
|
3166
|
+
if (_client) {
|
|
3167
|
+
_client.close();
|
|
3168
|
+
_client = null;
|
|
3169
|
+
_resilientClient = null;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
|
|
3173
|
+
var init_database = __esm({
|
|
3174
|
+
"src/lib/database.ts"() {
|
|
3175
|
+
"use strict";
|
|
3176
|
+
init_db_retry();
|
|
3177
|
+
init_employees();
|
|
3178
|
+
init_database_adapter();
|
|
3179
|
+
init_memory();
|
|
3180
|
+
_client = null;
|
|
3181
|
+
_resilientClient = null;
|
|
3182
|
+
_walCheckpointTimer = null;
|
|
3183
|
+
_daemonClient = null;
|
|
3184
|
+
_adapterClient = null;
|
|
3185
|
+
initTurso = initDatabase;
|
|
3186
|
+
SOFT_DELETE_RETENTION_DAYS = 7;
|
|
3187
|
+
disposeTurso = disposeDatabase;
|
|
3188
|
+
}
|
|
3189
|
+
});
|
|
3190
|
+
|
|
3191
|
+
// src/lib/memory-write-governor.ts
|
|
3192
|
+
import { createHash } from "crypto";
|
|
3193
|
+
function normalizeMemoryText(text) {
|
|
3194
|
+
return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
|
|
3195
|
+
}
|
|
3196
|
+
function classifyMemoryType(input) {
|
|
3197
|
+
if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
|
|
3198
|
+
const tool = input.tool_name.toLowerCase();
|
|
3199
|
+
const text = input.raw_text.toLowerCase();
|
|
3200
|
+
if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
|
|
3201
|
+
if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
|
|
3202
|
+
if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
|
|
3203
|
+
if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
|
|
3204
|
+
if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
|
|
3205
|
+
if (tool === "store_memory" || tool === "manual") return "observation";
|
|
3206
|
+
return "raw";
|
|
3207
|
+
}
|
|
3208
|
+
function shouldDropMemory(text) {
|
|
3209
|
+
const normalized = normalizeMemoryText(text);
|
|
3210
|
+
if (normalized.length < 10) return { drop: true, reason: "too_short" };
|
|
3211
|
+
if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
3212
|
+
return { drop: true, reason: "known_boilerplate_noise" };
|
|
3213
|
+
}
|
|
3214
|
+
return { drop: false };
|
|
3215
|
+
}
|
|
3216
|
+
function shouldSkipEmbedding(input) {
|
|
3217
|
+
const type = classifyMemoryType(input);
|
|
3218
|
+
if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
|
|
3219
|
+
if (type === "raw" && input.raw_text.length > 2e4) return true;
|
|
3220
|
+
if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
|
|
3221
|
+
return false;
|
|
3222
|
+
}
|
|
3223
|
+
function hashMemoryContent(text) {
|
|
3224
|
+
return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
|
|
3225
|
+
}
|
|
3226
|
+
function scopedDedupArgs(input) {
|
|
3227
|
+
return [input.contentHash, input.agentId, input.projectName, input.memoryType];
|
|
3228
|
+
}
|
|
3229
|
+
function governMemoryRecord(record) {
|
|
3230
|
+
const normalized = normalizeMemoryText(record.raw_text);
|
|
3231
|
+
const memoryType = classifyMemoryType({
|
|
3232
|
+
raw_text: normalized,
|
|
3233
|
+
agent_id: record.agent_id,
|
|
3234
|
+
project_name: record.project_name,
|
|
3235
|
+
tool_name: record.tool_name,
|
|
3236
|
+
memory_type: record.memory_type
|
|
3237
|
+
});
|
|
3238
|
+
const drop = shouldDropMemory(normalized);
|
|
3239
|
+
const skipEmbedding = shouldSkipEmbedding({
|
|
3240
|
+
raw_text: normalized,
|
|
3241
|
+
agent_id: record.agent_id,
|
|
3242
|
+
project_name: record.project_name,
|
|
3243
|
+
tool_name: record.tool_name,
|
|
3244
|
+
memory_type: memoryType
|
|
3245
|
+
});
|
|
3246
|
+
return {
|
|
3247
|
+
record: {
|
|
3248
|
+
...record,
|
|
3249
|
+
raw_text: normalized,
|
|
3250
|
+
memory_type: memoryType,
|
|
3251
|
+
vector: skipEmbedding ? null : record.vector
|
|
3252
|
+
},
|
|
3253
|
+
contentHash: hashMemoryContent(normalized),
|
|
3254
|
+
shouldDrop: drop.drop,
|
|
3255
|
+
dropReason: drop.reason,
|
|
3256
|
+
skipEmbedding,
|
|
3257
|
+
hygiene: {
|
|
3258
|
+
dedup: true,
|
|
3259
|
+
supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
|
|
3260
|
+
}
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
async function findScopedDuplicate(input) {
|
|
3264
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3265
|
+
const client = getClient2();
|
|
3266
|
+
const args = scopedDedupArgs(input);
|
|
3267
|
+
let sql = `SELECT id FROM memories
|
|
3268
|
+
WHERE content_hash = ?
|
|
3269
|
+
AND agent_id = ?
|
|
3270
|
+
AND project_name = ?
|
|
3271
|
+
AND COALESCE(memory_type, 'raw') = ?
|
|
3272
|
+
AND COALESCE(status, 'active') != 'deleted'`;
|
|
3273
|
+
if (input.excludeId) {
|
|
3274
|
+
sql += " AND id != ?";
|
|
3275
|
+
args.push(input.excludeId);
|
|
3276
|
+
}
|
|
3277
|
+
sql += " ORDER BY timestamp DESC LIMIT 1";
|
|
3278
|
+
const result = await client.execute({ sql, args });
|
|
3279
|
+
return result.rows[0]?.id ? String(result.rows[0].id) : null;
|
|
3280
|
+
}
|
|
3281
|
+
async function runPostWriteMemoryHygiene(memoryId) {
|
|
3282
|
+
try {
|
|
3283
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
3284
|
+
const client = getClient2();
|
|
3285
|
+
const current = await client.execute({
|
|
3286
|
+
sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
|
|
3287
|
+
importance, timestamp
|
|
3288
|
+
FROM memories
|
|
3289
|
+
WHERE id = ?
|
|
3290
|
+
LIMIT 1`,
|
|
3291
|
+
args: [memoryId]
|
|
3292
|
+
});
|
|
3293
|
+
const row = current.rows[0];
|
|
3294
|
+
if (!row) return;
|
|
3295
|
+
const memoryType = String(row.memory_type ?? "raw");
|
|
3296
|
+
const contentHash2 = row.content_hash ? String(row.content_hash) : null;
|
|
3297
|
+
const agentId = String(row.agent_id);
|
|
3298
|
+
const projectName = String(row.project_name);
|
|
3299
|
+
if (contentHash2) {
|
|
3300
|
+
await client.execute({
|
|
3301
|
+
sql: `UPDATE memories
|
|
3302
|
+
SET status = 'deleted',
|
|
3303
|
+
outcome = COALESCE(outcome, 'superseded')
|
|
3304
|
+
WHERE id != ?
|
|
3305
|
+
AND content_hash = ?
|
|
3306
|
+
AND agent_id = ?
|
|
3307
|
+
AND project_name = ?
|
|
3308
|
+
AND COALESCE(memory_type, 'raw') = ?
|
|
3309
|
+
AND COALESCE(status, 'active') = 'active'`,
|
|
3310
|
+
args: [memoryId, contentHash2, agentId, projectName, memoryType]
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
|
|
3314
|
+
if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
|
|
3315
|
+
const old = await client.execute({
|
|
3316
|
+
sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
|
|
3317
|
+
args: [supersedesId]
|
|
3318
|
+
});
|
|
3319
|
+
const oldImportance = Number(old.rows[0]?.importance ?? 0);
|
|
3320
|
+
const newImportance = Number(row.importance ?? 0);
|
|
3321
|
+
await client.batch([
|
|
3322
|
+
{
|
|
3323
|
+
sql: `UPDATE memories
|
|
3324
|
+
SET status = 'archived',
|
|
3325
|
+
outcome = COALESCE(outcome, 'superseded')
|
|
3326
|
+
WHERE id = ?`,
|
|
3327
|
+
args: [supersedesId]
|
|
3328
|
+
},
|
|
3329
|
+
{
|
|
3330
|
+
sql: `UPDATE memories
|
|
3331
|
+
SET importance = MAX(COALESCE(importance, 5), ?),
|
|
3332
|
+
parent_memory_id = COALESCE(parent_memory_id, ?)
|
|
3333
|
+
WHERE id = ?`,
|
|
3334
|
+
args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
|
|
3335
|
+
}
|
|
3336
|
+
], "write");
|
|
3337
|
+
}
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
process.stderr.write(
|
|
3340
|
+
`[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
|
|
3341
|
+
`
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
function schedulePostWriteMemoryHygiene(memoryIds) {
|
|
3346
|
+
if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
|
|
3347
|
+
if (memoryIds.length === 0) return;
|
|
3348
|
+
const run = () => {
|
|
3349
|
+
void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
|
|
3350
|
+
};
|
|
3351
|
+
if (typeof setImmediate === "function") setImmediate(run);
|
|
3352
|
+
else setTimeout(run, 0);
|
|
3353
|
+
}
|
|
3354
|
+
var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
|
|
3355
|
+
var init_memory_write_governor = __esm({
|
|
3356
|
+
"src/lib/memory-write-governor.ts"() {
|
|
3357
|
+
"use strict";
|
|
3358
|
+
HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
|
|
3359
|
+
"decision",
|
|
3360
|
+
"adr",
|
|
3361
|
+
"behavior",
|
|
3362
|
+
"procedure"
|
|
3363
|
+
]);
|
|
3364
|
+
NOISE_DROP_PATTERNS = [
|
|
3365
|
+
/^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
|
|
3366
|
+
/^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
|
|
3367
|
+
/^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
|
|
3368
|
+
/^\s*Intercom is a speedup, not delivery/im,
|
|
3369
|
+
/^\s*Context bar reads as USAGE not remaining/im
|
|
3370
|
+
];
|
|
3371
|
+
SKIP_EMBED_PATTERNS = [
|
|
3372
|
+
/tmux capture-pane\b/i,
|
|
3373
|
+
/docker ps\b/i,
|
|
3374
|
+
/docker images\b/i,
|
|
3375
|
+
/git status\b/i,
|
|
3376
|
+
/grep .*node_modules/i,
|
|
3377
|
+
/npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
|
|
3378
|
+
];
|
|
3379
|
+
}
|
|
3380
|
+
});
|
|
3381
|
+
|
|
3382
|
+
// src/lib/projection-worker.ts
|
|
3383
|
+
var projection_worker_exports = {};
|
|
3384
|
+
__export(projection_worker_exports, {
|
|
3385
|
+
processProjectionBatch: () => processProjectionBatch,
|
|
3386
|
+
projectionHandlersForTests: () => projectionHandlersForTests,
|
|
3387
|
+
resetProjectionWorkerForTests: () => resetProjectionWorkerForTests,
|
|
3388
|
+
setProjectionWorkerPrismaClientForTests: () => setProjectionWorkerPrismaClientForTests,
|
|
3389
|
+
startProjectionWorker: () => startProjectionWorker,
|
|
3390
|
+
stopProjectionWorker: () => stopProjectionWorker
|
|
3391
|
+
});
|
|
3392
|
+
import os5 from "os";
|
|
3393
|
+
import path8 from "path";
|
|
3394
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3395
|
+
import { createRequire as createRequire2 } from "module";
|
|
3396
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3397
|
+
function loadPrisma() {
|
|
3398
|
+
if (!prismaPromise) {
|
|
3399
|
+
prismaPromise = (async () => {
|
|
3400
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3401
|
+
if (explicitPath) {
|
|
3402
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
3403
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3404
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3405
|
+
return new Ctor2();
|
|
3406
|
+
}
|
|
3407
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path8.join(os5.homedir(), "exe-db");
|
|
3408
|
+
const req = createRequire2(path8.join(exeDbRoot, "package.json"));
|
|
3409
|
+
const entry = req.resolve("@prisma/client");
|
|
3410
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
3411
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3412
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
3413
|
+
return new Ctor();
|
|
3414
|
+
})();
|
|
3415
|
+
}
|
|
3416
|
+
return prismaPromise;
|
|
3417
|
+
}
|
|
3418
|
+
function setProjectionWorkerPrismaClientForTests(client) {
|
|
3419
|
+
prismaPromise = client ? Promise.resolve(client) : null;
|
|
3420
|
+
}
|
|
3421
|
+
function resetProjectionWorkerForTests() {
|
|
3422
|
+
running = false;
|
|
3423
|
+
if (pollTimer) {
|
|
3424
|
+
clearTimeout(pollTimer);
|
|
3425
|
+
pollTimer = null;
|
|
3426
|
+
}
|
|
3427
|
+
prismaPromise = null;
|
|
3428
|
+
}
|
|
3429
|
+
async function processBatch() {
|
|
3430
|
+
const prisma = await loadPrisma();
|
|
3431
|
+
const events = await prisma.$queryRawUnsafe(
|
|
3432
|
+
`SELECT "id", "source", "source_id", "event_type", "payload", "metadata", "timestamp"
|
|
3433
|
+
FROM "raw"."raw_events"
|
|
3434
|
+
WHERE "processed_at" IS NULL
|
|
3435
|
+
ORDER BY "timestamp" ASC
|
|
3436
|
+
LIMIT $1`,
|
|
3437
|
+
BATCH_SIZE
|
|
3438
|
+
);
|
|
3439
|
+
if (events.length === 0) return 0;
|
|
3440
|
+
let processed = 0;
|
|
3441
|
+
for (const event of events) {
|
|
3442
|
+
try {
|
|
3443
|
+
const handler = projectionHandlers[event.source] ?? defaultHandler;
|
|
3444
|
+
const result = await handler(event, prisma);
|
|
3445
|
+
await prisma.$executeRawUnsafe(
|
|
3446
|
+
`UPDATE "raw"."raw_events"
|
|
3447
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
3448
|
+
WHERE "id" = $2`,
|
|
3449
|
+
JSON.stringify({ targets: result.targets, skipped: result.skipped }),
|
|
3450
|
+
event.id
|
|
3451
|
+
);
|
|
3452
|
+
processed++;
|
|
3453
|
+
} catch (err) {
|
|
3454
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3455
|
+
process.stderr.write(
|
|
3456
|
+
`[projection-worker] Error processing event ${event.id}: ${message}
|
|
3457
|
+
`
|
|
3458
|
+
);
|
|
3459
|
+
await prisma.$executeRawUnsafe(
|
|
3460
|
+
`UPDATE "raw"."raw_events"
|
|
3461
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
3462
|
+
WHERE "id" = $2`,
|
|
3463
|
+
JSON.stringify({ error: message }),
|
|
3464
|
+
event.id
|
|
3465
|
+
);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
return processed;
|
|
3469
|
+
}
|
|
3470
|
+
function startProjectionWorker() {
|
|
3471
|
+
if (running) return;
|
|
3472
|
+
if (!process.env.DATABASE_URL) {
|
|
3473
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path8.join(os5.homedir(), "exe-db");
|
|
3474
|
+
if (!existsSync8(path8.join(exeDbRoot, "package.json"))) {
|
|
3475
|
+
process.stderr.write("[projection-worker] Skipped \u2014 no exe-db found. Set DATABASE_URL or EXE_DB_ROOT to enable.\n");
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
running = true;
|
|
3480
|
+
process.stderr.write("[projection-worker] Starting...\n");
|
|
3481
|
+
const tick = async () => {
|
|
3482
|
+
if (!running) return;
|
|
3483
|
+
try {
|
|
3484
|
+
const count = await processBatch();
|
|
3485
|
+
if (count > 0) {
|
|
3486
|
+
process.stderr.write(`[projection-worker] Processed ${count} events.
|
|
3487
|
+
`);
|
|
3488
|
+
}
|
|
3489
|
+
} catch (err) {
|
|
3490
|
+
process.stderr.write(
|
|
3491
|
+
`[projection-worker] Poll error: ${err instanceof Error ? err.message : String(err)}
|
|
3492
|
+
`
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
if (running) {
|
|
3496
|
+
pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
|
|
3497
|
+
}
|
|
3498
|
+
};
|
|
3499
|
+
void tick();
|
|
3500
|
+
}
|
|
3501
|
+
function stopProjectionWorker() {
|
|
3502
|
+
running = false;
|
|
3503
|
+
if (pollTimer) {
|
|
3504
|
+
clearTimeout(pollTimer);
|
|
3505
|
+
pollTimer = null;
|
|
3506
|
+
}
|
|
3507
|
+
process.stderr.write("[projection-worker] Stopped.\n");
|
|
3508
|
+
}
|
|
3509
|
+
async function processProjectionBatch() {
|
|
3510
|
+
return processBatch();
|
|
3511
|
+
}
|
|
3512
|
+
var prismaPromise, projectionHandlers, projectionHandlersForTests, defaultHandler, BATCH_SIZE, POLL_INTERVAL_MS, running, pollTimer;
|
|
3513
|
+
var init_projection_worker = __esm({
|
|
3514
|
+
"src/lib/projection-worker.ts"() {
|
|
3515
|
+
"use strict";
|
|
3516
|
+
prismaPromise = null;
|
|
3517
|
+
projectionHandlers = {
|
|
3518
|
+
async whatsapp(event, prisma) {
|
|
3519
|
+
const targets = [];
|
|
3520
|
+
const payload = event.payload;
|
|
3521
|
+
await prisma.$executeRawUnsafe(
|
|
3522
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
3523
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
3524
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3525
|
+
crypto.randomUUID(),
|
|
3526
|
+
"system",
|
|
3527
|
+
"ingest",
|
|
3528
|
+
"projection-worker",
|
|
3529
|
+
"projection_worker",
|
|
3530
|
+
"exe-os",
|
|
3531
|
+
JSON.stringify(payload),
|
|
3532
|
+
"raw",
|
|
3533
|
+
"whatsapp",
|
|
3534
|
+
event.event_type
|
|
3535
|
+
);
|
|
3536
|
+
targets.push("memory");
|
|
3537
|
+
return { targets };
|
|
3538
|
+
},
|
|
3539
|
+
async shopify(event, prisma) {
|
|
3540
|
+
const targets = [];
|
|
3541
|
+
const payload = event.payload;
|
|
3542
|
+
await prisma.$executeRawUnsafe(
|
|
3543
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
3544
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
3545
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3546
|
+
crypto.randomUUID(),
|
|
3547
|
+
"system",
|
|
3548
|
+
"ingest",
|
|
3549
|
+
"projection-worker",
|
|
3550
|
+
"projection_worker",
|
|
3551
|
+
"exe-os",
|
|
3552
|
+
JSON.stringify(payload),
|
|
3553
|
+
"raw",
|
|
3554
|
+
"shopify",
|
|
3555
|
+
event.event_type
|
|
3556
|
+
);
|
|
3557
|
+
targets.push("memory");
|
|
3558
|
+
return { targets };
|
|
3559
|
+
},
|
|
3560
|
+
async cloud_sync(event, prisma) {
|
|
3561
|
+
const targets = [];
|
|
3562
|
+
const payload = event.payload;
|
|
3563
|
+
await prisma.$executeRawUnsafe(
|
|
3564
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
3565
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
3566
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3567
|
+
payload.id ?? crypto.randomUUID(),
|
|
3568
|
+
payload.agent_id ?? "system",
|
|
3569
|
+
payload.agent_role ?? "ingest",
|
|
3570
|
+
payload.session_id ?? "projection-worker",
|
|
3571
|
+
payload.tool_name ?? "cloud_sync",
|
|
3572
|
+
payload.project_name ?? "exe-os",
|
|
3573
|
+
payload.raw_text ?? JSON.stringify(payload),
|
|
3574
|
+
payload.memory_type ?? "raw",
|
|
3575
|
+
"cloud_sync",
|
|
3576
|
+
event.event_type,
|
|
3577
|
+
payload.domain ?? null
|
|
3578
|
+
);
|
|
3579
|
+
targets.push("memory");
|
|
3580
|
+
return { targets };
|
|
3581
|
+
},
|
|
3582
|
+
async external_agent(event, prisma) {
|
|
3583
|
+
const targets = [];
|
|
3584
|
+
const payload = event.payload;
|
|
3585
|
+
await prisma.$executeRawUnsafe(
|
|
3586
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
3587
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
3588
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3589
|
+
crypto.randomUUID(),
|
|
3590
|
+
"system",
|
|
3591
|
+
"ingest",
|
|
3592
|
+
"projection-worker",
|
|
3593
|
+
"projection_worker",
|
|
3594
|
+
"exe-os",
|
|
3595
|
+
JSON.stringify(payload),
|
|
3596
|
+
"raw",
|
|
3597
|
+
"external_agent",
|
|
3598
|
+
event.event_type,
|
|
3599
|
+
payload.domain ?? null
|
|
3600
|
+
);
|
|
3601
|
+
targets.push("memory");
|
|
3602
|
+
return { targets };
|
|
3603
|
+
}
|
|
3604
|
+
};
|
|
3605
|
+
projectionHandlersForTests = projectionHandlers;
|
|
3606
|
+
defaultHandler = async (event, prisma) => {
|
|
3607
|
+
await prisma.$executeRawUnsafe(
|
|
3608
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
3609
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
3610
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3611
|
+
crypto.randomUUID(),
|
|
3612
|
+
"system",
|
|
3613
|
+
"ingest",
|
|
3614
|
+
"projection-worker",
|
|
3615
|
+
"projection_worker",
|
|
3616
|
+
"exe-os",
|
|
3617
|
+
JSON.stringify(event.payload),
|
|
3618
|
+
"raw",
|
|
3619
|
+
event.source,
|
|
3620
|
+
event.event_type
|
|
3621
|
+
);
|
|
3622
|
+
return { targets: ["memory"] };
|
|
3623
|
+
};
|
|
3624
|
+
BATCH_SIZE = 50;
|
|
3625
|
+
POLL_INTERVAL_MS = 1e4;
|
|
3626
|
+
running = false;
|
|
3627
|
+
pollTimer = null;
|
|
3628
|
+
}
|
|
3629
|
+
});
|
|
3630
|
+
|
|
3631
|
+
// src/lib/shard-manager.ts
|
|
3632
|
+
var shard_manager_exports = {};
|
|
3633
|
+
__export(shard_manager_exports, {
|
|
3634
|
+
disposeShards: () => disposeShards,
|
|
3635
|
+
ensureShardSchema: () => ensureShardSchema,
|
|
3636
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
3637
|
+
getReadyShardClient: () => getReadyShardClient,
|
|
3638
|
+
getShardClient: () => getShardClient,
|
|
3639
|
+
getShardsDir: () => getShardsDir,
|
|
3640
|
+
initShardManager: () => initShardManager,
|
|
3641
|
+
isShardingEnabled: () => isShardingEnabled,
|
|
3642
|
+
listShards: () => listShards,
|
|
3643
|
+
shardExists: () => shardExists
|
|
3644
|
+
});
|
|
3645
|
+
import path9 from "path";
|
|
3646
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
3647
|
+
import { createClient as createClient2 } from "@libsql/client";
|
|
3648
|
+
function initShardManager(encryptionKey) {
|
|
3649
|
+
_encryptionKey = encryptionKey;
|
|
3650
|
+
if (!existsSync9(SHARDS_DIR)) {
|
|
3651
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
3545
3652
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3653
|
+
_shardingEnabled = true;
|
|
3654
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3655
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3656
|
+
_evictionTimer.unref();
|
|
3657
|
+
}
|
|
3658
|
+
function isShardingEnabled() {
|
|
3659
|
+
return _shardingEnabled;
|
|
3660
|
+
}
|
|
3661
|
+
function getShardsDir() {
|
|
3662
|
+
return SHARDS_DIR;
|
|
3663
|
+
}
|
|
3664
|
+
function getShardClient(projectName) {
|
|
3665
|
+
if (!_encryptionKey) {
|
|
3666
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
3552
3667
|
}
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
args: []
|
|
3557
|
-
});
|
|
3558
|
-
} catch {
|
|
3668
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3669
|
+
if (!safeName || safeName === "unknown") {
|
|
3670
|
+
throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
|
|
3559
3671
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
});
|
|
3565
|
-
} catch {
|
|
3672
|
+
const cached = _shards.get(safeName);
|
|
3673
|
+
if (cached) {
|
|
3674
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3675
|
+
return cached;
|
|
3566
3676
|
}
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
3570
|
-
args: []
|
|
3571
|
-
});
|
|
3572
|
-
} catch {
|
|
3677
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3678
|
+
evictLRU();
|
|
3573
3679
|
}
|
|
3680
|
+
const dbPath = path9.join(SHARDS_DIR, `${safeName}.db`);
|
|
3681
|
+
const client = createClient2({
|
|
3682
|
+
url: `file:${dbPath}`,
|
|
3683
|
+
encryptionKey: _encryptionKey
|
|
3684
|
+
});
|
|
3685
|
+
_shards.set(safeName, client);
|
|
3686
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3687
|
+
return client;
|
|
3688
|
+
}
|
|
3689
|
+
function shardExists(projectName) {
|
|
3690
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3691
|
+
return existsSync9(path9.join(SHARDS_DIR, `${safeName}.db`));
|
|
3692
|
+
}
|
|
3693
|
+
function listShards() {
|
|
3694
|
+
if (!existsSync9(SHARDS_DIR)) return [];
|
|
3695
|
+
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3696
|
+
}
|
|
3697
|
+
async function ensureShardSchema(client) {
|
|
3698
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
3699
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
3574
3700
|
try {
|
|
3575
|
-
await client.execute(
|
|
3576
|
-
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
3577
|
-
args: []
|
|
3578
|
-
});
|
|
3701
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
3579
3702
|
} catch {
|
|
3580
3703
|
}
|
|
3581
3704
|
await client.executeMultiple(`
|
|
3582
|
-
CREATE
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3705
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
3706
|
+
id TEXT PRIMARY KEY,
|
|
3707
|
+
agent_id TEXT NOT NULL,
|
|
3708
|
+
agent_role TEXT NOT NULL,
|
|
3709
|
+
session_id TEXT NOT NULL,
|
|
3710
|
+
timestamp TEXT NOT NULL,
|
|
3711
|
+
tool_name TEXT NOT NULL,
|
|
3712
|
+
project_name TEXT NOT NULL,
|
|
3713
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
3714
|
+
raw_text TEXT NOT NULL,
|
|
3715
|
+
vector F32_BLOB(1024),
|
|
3716
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
3717
|
+
);
|
|
3718
|
+
|
|
3719
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
|
|
3720
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
|
|
3721
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
|
|
3722
|
+
`);
|
|
3723
|
+
await client.executeMultiple(`
|
|
3724
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
3725
|
+
raw_text,
|
|
3726
|
+
content='memories',
|
|
3587
3727
|
content_rowid='rowid'
|
|
3588
3728
|
);
|
|
3589
3729
|
|
|
3590
|
-
CREATE TRIGGER IF NOT EXISTS
|
|
3591
|
-
INSERT INTO
|
|
3592
|
-
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
3730
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
3731
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
3593
3732
|
END;
|
|
3594
3733
|
|
|
3595
|
-
CREATE TRIGGER IF NOT EXISTS
|
|
3596
|
-
INSERT INTO
|
|
3597
|
-
VALUES('delete', old.rowid, old.content_text, old.sender_name, old.agent_response);
|
|
3734
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
3735
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
3598
3736
|
END;
|
|
3599
3737
|
|
|
3600
|
-
CREATE TRIGGER IF NOT EXISTS
|
|
3601
|
-
INSERT INTO
|
|
3602
|
-
|
|
3603
|
-
INSERT INTO conversations_fts(rowid, content_text, sender_name, agent_response)
|
|
3604
|
-
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
3738
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
3739
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
3740
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
3605
3741
|
END;
|
|
3606
3742
|
`);
|
|
3607
|
-
try {
|
|
3608
|
-
await client.execute({
|
|
3609
|
-
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
3610
|
-
args: []
|
|
3611
|
-
});
|
|
3612
|
-
} catch {
|
|
3613
|
-
}
|
|
3614
|
-
try {
|
|
3615
|
-
await client.execute(
|
|
3616
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
3617
|
-
);
|
|
3618
|
-
} catch {
|
|
3619
|
-
}
|
|
3620
|
-
try {
|
|
3621
|
-
await client.execute({
|
|
3622
|
-
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
3623
|
-
args: []
|
|
3624
|
-
});
|
|
3625
|
-
await client.execute({
|
|
3626
|
-
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
3627
|
-
args: []
|
|
3628
|
-
});
|
|
3629
|
-
} catch {
|
|
3630
|
-
}
|
|
3631
|
-
try {
|
|
3632
|
-
await client.execute({
|
|
3633
|
-
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
3634
|
-
args: []
|
|
3635
|
-
});
|
|
3636
|
-
} catch {
|
|
3637
|
-
}
|
|
3638
|
-
try {
|
|
3639
|
-
await client.execute(
|
|
3640
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
3641
|
-
);
|
|
3642
|
-
} catch {
|
|
3643
|
-
}
|
|
3644
|
-
for (const col of [
|
|
3645
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
3646
|
-
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
3647
|
-
]) {
|
|
3648
|
-
try {
|
|
3649
|
-
await client.execute(col);
|
|
3650
|
-
} catch {
|
|
3651
|
-
}
|
|
3652
|
-
}
|
|
3653
|
-
try {
|
|
3654
|
-
await client.execute({
|
|
3655
|
-
sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
|
|
3656
|
-
args: []
|
|
3657
|
-
});
|
|
3658
|
-
} catch {
|
|
3659
|
-
}
|
|
3660
|
-
try {
|
|
3661
|
-
await client.execute(
|
|
3662
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
|
|
3663
|
-
);
|
|
3664
|
-
} catch {
|
|
3665
|
-
}
|
|
3666
|
-
try {
|
|
3667
|
-
await client.execute({
|
|
3668
|
-
sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
|
|
3669
|
-
args: []
|
|
3670
|
-
});
|
|
3671
|
-
} catch {
|
|
3672
|
-
}
|
|
3673
|
-
try {
|
|
3674
|
-
await client.execute(
|
|
3675
|
-
`CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
|
|
3676
|
-
);
|
|
3677
|
-
} catch {
|
|
3678
|
-
}
|
|
3679
|
-
try {
|
|
3680
|
-
await client.execute({
|
|
3681
|
-
sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
|
|
3682
|
-
args: []
|
|
3683
|
-
});
|
|
3684
|
-
} catch {
|
|
3685
|
-
}
|
|
3686
3743
|
for (const col of [
|
|
3744
|
+
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
3745
|
+
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3746
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3747
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
3748
|
+
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
3749
|
+
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
3750
|
+
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
3751
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
|
|
3752
|
+
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
3753
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
|
|
3754
|
+
"ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
|
|
3755
|
+
"ALTER TABLE memories ADD COLUMN last_accessed TEXT",
|
|
3756
|
+
// Wiki linkage columns (must match database.ts)
|
|
3757
|
+
"ALTER TABLE memories ADD COLUMN workspace_id TEXT",
|
|
3758
|
+
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
3759
|
+
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
3760
|
+
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
3761
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
3762
|
+
// Source provenance columns (must match database.ts)
|
|
3763
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
3764
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
3765
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
3766
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
3767
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
3768
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
3769
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
3770
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3771
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3687
3772
|
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3688
3773
|
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3689
3774
|
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
@@ -3705,49 +3790,164 @@ async function ensureSchema() {
|
|
|
3705
3790
|
} catch {
|
|
3706
3791
|
}
|
|
3707
3792
|
}
|
|
3793
|
+
for (const idx of [
|
|
3794
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
3795
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
|
|
3796
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
|
|
3797
|
+
]) {
|
|
3798
|
+
try {
|
|
3799
|
+
await client.execute(idx);
|
|
3800
|
+
} catch {
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3708
3803
|
try {
|
|
3709
|
-
await client.execute(
|
|
3710
|
-
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3711
|
-
args: []
|
|
3712
|
-
});
|
|
3804
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
3713
3805
|
} catch {
|
|
3714
3806
|
}
|
|
3807
|
+
for (const idx of [
|
|
3808
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
|
|
3809
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
|
|
3810
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
|
|
3811
|
+
]) {
|
|
3812
|
+
try {
|
|
3813
|
+
await client.execute(idx);
|
|
3814
|
+
} catch {
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
await client.executeMultiple(`
|
|
3818
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
3819
|
+
id TEXT PRIMARY KEY,
|
|
3820
|
+
name TEXT NOT NULL,
|
|
3821
|
+
type TEXT NOT NULL,
|
|
3822
|
+
first_seen TEXT NOT NULL,
|
|
3823
|
+
last_seen TEXT NOT NULL,
|
|
3824
|
+
properties TEXT DEFAULT '{}',
|
|
3825
|
+
UNIQUE(name, type)
|
|
3826
|
+
);
|
|
3827
|
+
|
|
3828
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
3829
|
+
id TEXT PRIMARY KEY,
|
|
3830
|
+
source_entity_id TEXT NOT NULL,
|
|
3831
|
+
target_entity_id TEXT NOT NULL,
|
|
3832
|
+
type TEXT NOT NULL,
|
|
3833
|
+
weight REAL DEFAULT 1.0,
|
|
3834
|
+
timestamp TEXT NOT NULL,
|
|
3835
|
+
properties TEXT DEFAULT '{}',
|
|
3836
|
+
UNIQUE(source_entity_id, target_entity_id, type)
|
|
3837
|
+
);
|
|
3838
|
+
|
|
3839
|
+
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
3840
|
+
entity_id TEXT NOT NULL,
|
|
3841
|
+
memory_id TEXT NOT NULL,
|
|
3842
|
+
PRIMARY KEY (entity_id, memory_id)
|
|
3843
|
+
);
|
|
3844
|
+
|
|
3845
|
+
CREATE TABLE IF NOT EXISTS relationship_memories (
|
|
3846
|
+
relationship_id TEXT NOT NULL,
|
|
3847
|
+
memory_id TEXT NOT NULL,
|
|
3848
|
+
PRIMARY KEY (relationship_id, memory_id)
|
|
3849
|
+
);
|
|
3850
|
+
|
|
3851
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
3852
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
3853
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
|
|
3854
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
|
|
3855
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
|
|
3856
|
+
|
|
3857
|
+
CREATE TABLE IF NOT EXISTS hyperedges (
|
|
3858
|
+
id TEXT PRIMARY KEY,
|
|
3859
|
+
label TEXT NOT NULL,
|
|
3860
|
+
relation TEXT NOT NULL,
|
|
3861
|
+
confidence REAL DEFAULT 1.0,
|
|
3862
|
+
timestamp TEXT NOT NULL
|
|
3863
|
+
);
|
|
3864
|
+
|
|
3865
|
+
CREATE TABLE IF NOT EXISTS hyperedge_nodes (
|
|
3866
|
+
hyperedge_id TEXT NOT NULL,
|
|
3867
|
+
entity_id TEXT NOT NULL,
|
|
3868
|
+
PRIMARY KEY (hyperedge_id, entity_id)
|
|
3869
|
+
);
|
|
3870
|
+
`);
|
|
3871
|
+
for (const col of [
|
|
3872
|
+
"ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
|
|
3873
|
+
"ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
|
|
3874
|
+
]) {
|
|
3875
|
+
try {
|
|
3876
|
+
await client.execute(col);
|
|
3877
|
+
} catch {
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3715
3880
|
}
|
|
3716
|
-
async function
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3881
|
+
async function getReadyShardClient(projectName) {
|
|
3882
|
+
const client = getShardClient(projectName);
|
|
3883
|
+
await ensureShardSchema(client);
|
|
3884
|
+
return client;
|
|
3885
|
+
}
|
|
3886
|
+
function evictLRU() {
|
|
3887
|
+
let oldest = null;
|
|
3888
|
+
let oldestTime = Infinity;
|
|
3889
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3890
|
+
if (time < oldestTime) {
|
|
3891
|
+
oldestTime = time;
|
|
3892
|
+
oldest = name;
|
|
3893
|
+
}
|
|
3720
3894
|
}
|
|
3721
|
-
if (
|
|
3722
|
-
|
|
3723
|
-
|
|
3895
|
+
if (oldest) {
|
|
3896
|
+
const client = _shards.get(oldest);
|
|
3897
|
+
if (client) {
|
|
3898
|
+
client.close();
|
|
3899
|
+
}
|
|
3900
|
+
_shards.delete(oldest);
|
|
3901
|
+
_shardLastAccess.delete(oldest);
|
|
3724
3902
|
}
|
|
3725
|
-
|
|
3726
|
-
|
|
3903
|
+
}
|
|
3904
|
+
function evictIdleShards() {
|
|
3905
|
+
const now = Date.now();
|
|
3906
|
+
const toEvict = [];
|
|
3907
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3908
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3909
|
+
toEvict.push(name);
|
|
3910
|
+
}
|
|
3727
3911
|
}
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3912
|
+
for (const name of toEvict) {
|
|
3913
|
+
const client = _shards.get(name);
|
|
3914
|
+
if (client) {
|
|
3915
|
+
client.close();
|
|
3916
|
+
}
|
|
3917
|
+
_shards.delete(name);
|
|
3918
|
+
_shardLastAccess.delete(name);
|
|
3733
3919
|
}
|
|
3734
3920
|
}
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3921
|
+
function getOpenShardCount() {
|
|
3922
|
+
return _shards.size;
|
|
3923
|
+
}
|
|
3924
|
+
function disposeShards() {
|
|
3925
|
+
if (_evictionTimer) {
|
|
3926
|
+
clearInterval(_evictionTimer);
|
|
3927
|
+
_evictionTimer = null;
|
|
3928
|
+
}
|
|
3929
|
+
for (const [, client] of _shards) {
|
|
3930
|
+
client.close();
|
|
3931
|
+
}
|
|
3932
|
+
_shards.clear();
|
|
3933
|
+
_shardLastAccess.clear();
|
|
3934
|
+
_shardingEnabled = false;
|
|
3935
|
+
_encryptionKey = null;
|
|
3936
|
+
}
|
|
3937
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
3938
|
+
var init_shard_manager = __esm({
|
|
3939
|
+
"src/lib/shard-manager.ts"() {
|
|
3738
3940
|
"use strict";
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
SOFT_DELETE_RETENTION_DAYS = 7;
|
|
3750
|
-
disposeTurso = disposeDatabase;
|
|
3941
|
+
init_config();
|
|
3942
|
+
SHARDS_DIR = path9.join(EXE_AI_DIR, "shards");
|
|
3943
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3944
|
+
MAX_OPEN_SHARDS = 10;
|
|
3945
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
3946
|
+
_shards = /* @__PURE__ */ new Map();
|
|
3947
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3948
|
+
_evictionTimer = null;
|
|
3949
|
+
_encryptionKey = null;
|
|
3950
|
+
_shardingEnabled = false;
|
|
3751
3951
|
}
|
|
3752
3952
|
});
|
|
3753
3953
|
|
|
@@ -4142,7 +4342,7 @@ var init_platform_procedures = __esm({
|
|
|
4142
4342
|
title: "Chain of command \u2014 who talks to whom",
|
|
4143
4343
|
domain: "workflow",
|
|
4144
4344
|
priority: "p0",
|
|
4145
|
-
content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the
|
|
4345
|
+
content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator 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."
|
|
4146
4346
|
},
|
|
4147
4347
|
{
|
|
4148
4348
|
title: "Single dispatch path \u2014 create_task only",
|
|
@@ -4373,7 +4573,6 @@ __export(store_exports, {
|
|
|
4373
4573
|
vectorToBlob: () => vectorToBlob,
|
|
4374
4574
|
writeMemory: () => writeMemory
|
|
4375
4575
|
});
|
|
4376
|
-
import { createHash } from "crypto";
|
|
4377
4576
|
function isBusyError2(err) {
|
|
4378
4577
|
if (err instanceof Error) {
|
|
4379
4578
|
const msg = err.message.toLowerCase();
|
|
@@ -4487,17 +4686,24 @@ async function writeMemory(record) {
|
|
|
4487
4686
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4488
4687
|
);
|
|
4489
4688
|
}
|
|
4490
|
-
const
|
|
4491
|
-
if (
|
|
4689
|
+
const governed = governMemoryRecord(record);
|
|
4690
|
+
if (governed.shouldDrop) return;
|
|
4691
|
+
record = governed.record;
|
|
4692
|
+
const contentHash2 = governed.contentHash;
|
|
4693
|
+
const memoryType = record.memory_type ?? "raw";
|
|
4694
|
+
if (_pendingRecords.some(
|
|
4695
|
+
(r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
|
|
4696
|
+
)) {
|
|
4492
4697
|
return;
|
|
4493
4698
|
}
|
|
4494
4699
|
try {
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4700
|
+
const existing = await findScopedDuplicate({
|
|
4701
|
+
contentHash: contentHash2,
|
|
4702
|
+
agentId: record.agent_id,
|
|
4703
|
+
projectName: record.project_name,
|
|
4704
|
+
memoryType
|
|
4499
4705
|
});
|
|
4500
|
-
if (existing
|
|
4706
|
+
if (existing) return;
|
|
4501
4707
|
} catch {
|
|
4502
4708
|
}
|
|
4503
4709
|
const dbRow = {
|
|
@@ -4528,7 +4734,7 @@ async function writeMemory(record) {
|
|
|
4528
4734
|
tier: record.tier ?? classifyTier(record),
|
|
4529
4735
|
supersedes_id: record.supersedes_id ?? null,
|
|
4530
4736
|
draft: record.draft ? 1 : 0,
|
|
4531
|
-
memory_type:
|
|
4737
|
+
memory_type: memoryType,
|
|
4532
4738
|
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
4533
4739
|
content_hash: contentHash2,
|
|
4534
4740
|
intent: record.intent ?? null,
|
|
@@ -4687,6 +4893,7 @@ async function flushBatch() {
|
|
|
4687
4893
|
const globalClient = getClient();
|
|
4688
4894
|
const globalStmts = batch.map(buildStmt);
|
|
4689
4895
|
await globalClient.batch(globalStmts, "write");
|
|
4896
|
+
schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
|
|
4690
4897
|
_pendingRecords.splice(0, batch.length);
|
|
4691
4898
|
try {
|
|
4692
4899
|
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
@@ -4807,7 +5014,11 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
4807
5014
|
sql += ` AND timestamp >= ?`;
|
|
4808
5015
|
args.push(options.since);
|
|
4809
5016
|
}
|
|
4810
|
-
if (options?.
|
|
5017
|
+
if (options?.memoryTypes && options.memoryTypes.length > 0) {
|
|
5018
|
+
const uniqueTypes = [...new Set(options.memoryTypes)];
|
|
5019
|
+
sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
|
|
5020
|
+
args.push(...uniqueTypes);
|
|
5021
|
+
} else if (options?.memoryType) {
|
|
4811
5022
|
sql += ` AND memory_type = ?`;
|
|
4812
5023
|
args.push(options.memoryType);
|
|
4813
5024
|
}
|
|
@@ -4945,6 +5156,7 @@ var init_store = __esm({
|
|
|
4945
5156
|
init_keychain();
|
|
4946
5157
|
init_config();
|
|
4947
5158
|
init_state_bus();
|
|
5159
|
+
init_memory_write_governor();
|
|
4948
5160
|
INIT_MAX_RETRIES = 3;
|
|
4949
5161
|
INIT_RETRY_DELAY_MS = 1e3;
|
|
4950
5162
|
_pendingRecords = [];
|
|
@@ -5170,8 +5382,8 @@ async function ensureLoaded() {
|
|
|
5170
5382
|
);
|
|
5171
5383
|
}
|
|
5172
5384
|
process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
|
|
5173
|
-
const { getLlama
|
|
5174
|
-
const llama = await
|
|
5385
|
+
const { getLlama } = await import("node-llama-cpp");
|
|
5386
|
+
const llama = await getLlama();
|
|
5175
5387
|
_rerankerModel = await llama.loadModel({ modelPath });
|
|
5176
5388
|
_rerankerContext = await _rerankerModel.createEmbeddingContext();
|
|
5177
5389
|
process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
|
|
@@ -6190,6 +6402,17 @@ __export(hybrid_search_exports, {
|
|
|
6190
6402
|
rrfMerge: () => rrfMerge,
|
|
6191
6403
|
rrfMergeMulti: () => rrfMergeMulti
|
|
6192
6404
|
});
|
|
6405
|
+
function appendMemoryTypeFilter(sql, args, column, options) {
|
|
6406
|
+
if (options?.memoryTypes && options.memoryTypes.length > 0) {
|
|
6407
|
+
const uniqueTypes = [...new Set(options.memoryTypes)];
|
|
6408
|
+
sql += ` AND ${column} IN (${uniqueTypes.map(() => "?").join(",")})`;
|
|
6409
|
+
args.push(...uniqueTypes);
|
|
6410
|
+
} else if (options?.memoryType) {
|
|
6411
|
+
sql += ` AND ${column} = ?`;
|
|
6412
|
+
args.push(options.memoryType);
|
|
6413
|
+
}
|
|
6414
|
+
return sql;
|
|
6415
|
+
}
|
|
6193
6416
|
async function hybridSearch(queryText, agentId, options) {
|
|
6194
6417
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6195
6418
|
const config2 = await loadConfig2();
|
|
@@ -6418,6 +6641,7 @@ async function estimateCardinality(agentId, options) {
|
|
|
6418
6641
|
sql += ` AND timestamp >= ?`;
|
|
6419
6642
|
args.push(options.since);
|
|
6420
6643
|
}
|
|
6644
|
+
sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
|
|
6421
6645
|
try {
|
|
6422
6646
|
const result = await client.execute({ sql, args });
|
|
6423
6647
|
return Number(result.rows[0]?.cnt) || 0;
|
|
@@ -6539,10 +6763,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
|
|
|
6539
6763
|
sql += ` AND m.timestamp >= ?`;
|
|
6540
6764
|
args.push(options.since);
|
|
6541
6765
|
}
|
|
6542
|
-
|
|
6543
|
-
sql += ` AND m.memory_type = ?`;
|
|
6544
|
-
args.push(options.memoryType);
|
|
6545
|
-
}
|
|
6766
|
+
sql = appendMemoryTypeFilter(sql, args, "m.memory_type", options);
|
|
6546
6767
|
sql += ` ORDER BY rank LIMIT ?`;
|
|
6547
6768
|
args.push(limit);
|
|
6548
6769
|
const result = await client.execute({ sql, args });
|
|
@@ -6599,9 +6820,16 @@ async function recentRecords(agentId, options, limit, textFilter) {
|
|
|
6599
6820
|
AND timestamp >= ? AND timestamp <= ?
|
|
6600
6821
|
AND COALESCE(status, 'active') = 'active'
|
|
6601
6822
|
AND ${options?.includeRaw === false ? "COALESCE(memory_type, 'raw') != 'raw'" : "1 = 1"}
|
|
6823
|
+
${options?.memoryTypes?.length ? `AND memory_type IN (${options.memoryTypes.map(() => "?").join(",")})` : options?.memoryType ? "AND memory_type = ?" : ""}
|
|
6602
6824
|
AND COALESCE(confidence, 0.7) >= 0.3
|
|
6603
6825
|
ORDER BY timestamp DESC LIMIT ?`,
|
|
6604
|
-
args: [
|
|
6826
|
+
args: [
|
|
6827
|
+
agentId,
|
|
6828
|
+
windowStart,
|
|
6829
|
+
killedAt,
|
|
6830
|
+
...options?.memoryTypes?.length ? [...new Set(options.memoryTypes)] : options?.memoryType ? [options.memoryType] : [],
|
|
6831
|
+
boundarySlots
|
|
6832
|
+
]
|
|
6605
6833
|
});
|
|
6606
6834
|
for (const row of boundaryResult.rows) {
|
|
6607
6835
|
sessionBoundaryMemories.push(rowToMemoryRecord(row));
|
|
@@ -6647,10 +6875,7 @@ async function recentRecords(agentId, options, limit, textFilter) {
|
|
|
6647
6875
|
sql += ` AND timestamp >= ?`;
|
|
6648
6876
|
args.push(options.since);
|
|
6649
6877
|
}
|
|
6650
|
-
|
|
6651
|
-
sql += ` AND memory_type = ?`;
|
|
6652
|
-
args.push(options.memoryType);
|
|
6653
|
-
}
|
|
6878
|
+
sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
|
|
6654
6879
|
if (textFilter) {
|
|
6655
6880
|
sql += ` AND raw_text LIKE '%' || ? || '%'`;
|
|
6656
6881
|
args.push(textFilter);
|
|
@@ -7060,6 +7285,67 @@ var init_active_agent = __esm({
|
|
|
7060
7285
|
}
|
|
7061
7286
|
});
|
|
7062
7287
|
|
|
7288
|
+
// src/lib/memory-retrieval-modes.ts
|
|
7289
|
+
function applyRetrievalMode(base, mode) {
|
|
7290
|
+
if (!mode || mode === "all") return base;
|
|
7291
|
+
const next = { ...base };
|
|
7292
|
+
switch (mode) {
|
|
7293
|
+
case "decisions_only":
|
|
7294
|
+
next.memoryTypes = ["decision", "adr"];
|
|
7295
|
+
next.includeRaw = false;
|
|
7296
|
+
break;
|
|
7297
|
+
case "behaviors_only":
|
|
7298
|
+
next.memoryTypes = ["behavior"];
|
|
7299
|
+
next.includeRaw = false;
|
|
7300
|
+
break;
|
|
7301
|
+
case "procedures_only":
|
|
7302
|
+
next.memoryTypes = ["procedure"];
|
|
7303
|
+
next.includeRaw = false;
|
|
7304
|
+
break;
|
|
7305
|
+
case "operational":
|
|
7306
|
+
next.memoryTypes = ["raw", "observation"];
|
|
7307
|
+
next.includeRaw = true;
|
|
7308
|
+
break;
|
|
7309
|
+
case "no_raw":
|
|
7310
|
+
next.includeRaw = false;
|
|
7311
|
+
break;
|
|
7312
|
+
case "recent_high_value":
|
|
7313
|
+
next.memoryTypes = ["decision", "adr", "behavior", "procedure"];
|
|
7314
|
+
next.includeRaw = false;
|
|
7315
|
+
break;
|
|
7316
|
+
default:
|
|
7317
|
+
return next;
|
|
7318
|
+
}
|
|
7319
|
+
return next;
|
|
7320
|
+
}
|
|
7321
|
+
function formatRetrievalModes() {
|
|
7322
|
+
return RETRIEVAL_MODES.map((mode) => `${mode}: ${RETRIEVAL_MODE_DESCRIPTIONS[mode]}`).join("; ");
|
|
7323
|
+
}
|
|
7324
|
+
var RETRIEVAL_MODES, RETRIEVAL_MODE_DESCRIPTIONS;
|
|
7325
|
+
var init_memory_retrieval_modes = __esm({
|
|
7326
|
+
"src/lib/memory-retrieval-modes.ts"() {
|
|
7327
|
+
"use strict";
|
|
7328
|
+
RETRIEVAL_MODES = [
|
|
7329
|
+
"all",
|
|
7330
|
+
"decisions_only",
|
|
7331
|
+
"behaviors_only",
|
|
7332
|
+
"procedures_only",
|
|
7333
|
+
"operational",
|
|
7334
|
+
"no_raw",
|
|
7335
|
+
"recent_high_value"
|
|
7336
|
+
];
|
|
7337
|
+
RETRIEVAL_MODE_DESCRIPTIONS = {
|
|
7338
|
+
all: "All visible memory types.",
|
|
7339
|
+
decisions_only: "Only decisions and ADRs.",
|
|
7340
|
+
behaviors_only: "Only durable behavior/correction memories.",
|
|
7341
|
+
procedures_only: "Only procedures/runbooks.",
|
|
7342
|
+
operational: "Raw operational/debug/tool output.",
|
|
7343
|
+
no_raw: "All non-raw memory types.",
|
|
7344
|
+
recent_high_value: "High-value durable memory types, intended for recency recovery."
|
|
7345
|
+
};
|
|
7346
|
+
}
|
|
7347
|
+
});
|
|
7348
|
+
|
|
7063
7349
|
// src/mcp/tools/recall-my-memory.ts
|
|
7064
7350
|
import { z } from "zod";
|
|
7065
7351
|
function formatSourceLine(record) {
|
|
@@ -7097,7 +7383,8 @@ function registerRecallMyMemory(server) {
|
|
|
7097
7383
|
),
|
|
7098
7384
|
include_source: z.boolean().optional().default(false).describe(
|
|
7099
7385
|
"When true, attach parent document metadata (filename, mime, source_type) to each result. Default false."
|
|
7100
|
-
)
|
|
7386
|
+
),
|
|
7387
|
+
retrieval_mode: z.enum(RETRIEVAL_MODES).optional().default("all").describe(`Typed retrieval mode. ${formatRetrievalModes()}`)
|
|
7101
7388
|
}
|
|
7102
7389
|
},
|
|
7103
7390
|
async ({
|
|
@@ -7111,7 +7398,8 @@ function registerRecallMyMemory(server) {
|
|
|
7111
7398
|
include_archived,
|
|
7112
7399
|
workspace_id,
|
|
7113
7400
|
user_id,
|
|
7114
|
-
include_source
|
|
7401
|
+
include_source,
|
|
7402
|
+
retrieval_mode
|
|
7115
7403
|
}) => {
|
|
7116
7404
|
try {
|
|
7117
7405
|
if (!recent && !query) {
|
|
@@ -7121,7 +7409,7 @@ function registerRecallMyMemory(server) {
|
|
|
7121
7409
|
};
|
|
7122
7410
|
}
|
|
7123
7411
|
const { agentId } = getActiveAgent();
|
|
7124
|
-
const searchOptions = {
|
|
7412
|
+
const searchOptions = applyRetrievalMode({
|
|
7125
7413
|
projectName: project_name,
|
|
7126
7414
|
hasError: has_error,
|
|
7127
7415
|
toolName: tool_name,
|
|
@@ -7132,7 +7420,7 @@ function registerRecallMyMemory(server) {
|
|
|
7132
7420
|
includeSource: include_source,
|
|
7133
7421
|
includeDrafts: true,
|
|
7134
7422
|
...user_id !== void 0 ? { userId: user_id } : {}
|
|
7135
|
-
};
|
|
7423
|
+
}, retrieval_mode);
|
|
7136
7424
|
let results;
|
|
7137
7425
|
if (recent) {
|
|
7138
7426
|
results = await recentRecords(agentId, searchOptions, limit, query);
|
|
@@ -7190,6 +7478,7 @@ var init_recall_my_memory = __esm({
|
|
|
7190
7478
|
init_hybrid_search();
|
|
7191
7479
|
init_store();
|
|
7192
7480
|
init_active_agent();
|
|
7481
|
+
init_memory_retrieval_modes();
|
|
7193
7482
|
}
|
|
7194
7483
|
});
|
|
7195
7484
|
|
|
@@ -7225,10 +7514,11 @@ function registerAskTeamMemory(server) {
|
|
|
7225
7514
|
),
|
|
7226
7515
|
include_raw: z2.boolean().optional().default(false).describe(
|
|
7227
7516
|
"Include raw technical memories when ACL allows it (default: non-raw memories only for cross-agent reads)"
|
|
7228
|
-
)
|
|
7517
|
+
),
|
|
7518
|
+
retrieval_mode: z2.enum(RETRIEVAL_MODES).optional().default("all").describe(`Typed retrieval mode. Raw visibility is still ACL-gated. ${formatRetrievalModes()}`)
|
|
7229
7519
|
}
|
|
7230
7520
|
},
|
|
7231
|
-
async ({ team_member, query, project_name, limit, since, include_archived, include_raw: _include_raw }) => {
|
|
7521
|
+
async ({ team_member, query, project_name, limit, since, include_archived, include_raw: _include_raw, retrieval_mode }) => {
|
|
7232
7522
|
try {
|
|
7233
7523
|
const { agentId: queryingAgentId, agentRole: queryingAgentRole } = getActiveAgent();
|
|
7234
7524
|
const employees = loadEmployeesSync();
|
|
@@ -7237,16 +7527,18 @@ function registerAskTeamMemory(server) {
|
|
|
7237
7527
|
const targetRole = targetEmployee?.role ?? "";
|
|
7238
7528
|
const hasRawAccess = canSeeRaw(queryingRole, targetRole);
|
|
7239
7529
|
const effectiveIncludeRaw = _include_raw && hasRawAccess;
|
|
7240
|
-
const
|
|
7530
|
+
const requestedMode = retrieval_mode ?? "all";
|
|
7531
|
+
const safeMode = !effectiveIncludeRaw && ["all", "operational"].includes(requestedMode) ? "no_raw" : requestedMode;
|
|
7532
|
+
const searchOptions = applyRetrievalMode({
|
|
7241
7533
|
projectName: project_name,
|
|
7242
7534
|
limit,
|
|
7243
7535
|
since,
|
|
7244
7536
|
includeArchived: include_archived,
|
|
7245
7537
|
includeDrafts: false,
|
|
7246
|
-
includeRaw: effectiveIncludeRaw
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7538
|
+
includeRaw: effectiveIncludeRaw
|
|
7539
|
+
}, safeMode);
|
|
7540
|
+
if (!effectiveIncludeRaw) searchOptions.includeRaw = false;
|
|
7541
|
+
const results = await hybridSearch(query, team_member, searchOptions);
|
|
7250
7542
|
if (results.length === 0) {
|
|
7251
7543
|
return {
|
|
7252
7544
|
content: [
|
|
@@ -7288,6 +7580,7 @@ var init_ask_team_memory = __esm({
|
|
|
7288
7580
|
init_hybrid_search();
|
|
7289
7581
|
init_active_agent();
|
|
7290
7582
|
init_employees();
|
|
7583
|
+
init_memory_retrieval_modes();
|
|
7291
7584
|
}
|
|
7292
7585
|
});
|
|
7293
7586
|
|
|
@@ -9930,7 +10223,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9930
10223
|
}
|
|
9931
10224
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
9932
10225
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
9933
|
-
|
|
10226
|
+
let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
10227
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
10228
|
+
ccModel += "[1m]";
|
|
10229
|
+
}
|
|
10230
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
9934
10231
|
}
|
|
9935
10232
|
}
|
|
9936
10233
|
let spawnCommand;
|
|
@@ -15095,8 +15392,9 @@ var init_starter_packs = __esm({
|
|
|
15095
15392
|
|
|
15096
15393
|
// src/lib/employee-templates.ts
|
|
15097
15394
|
function renderClientCOOTemplate(vars) {
|
|
15395
|
+
const resolved = { ...vars, title: vars.title || "Chief Operating Officer" };
|
|
15098
15396
|
for (const key of CLIENT_COO_PLACEHOLDERS) {
|
|
15099
|
-
const value =
|
|
15397
|
+
const value = resolved[key];
|
|
15100
15398
|
if (typeof value !== "string" || value.length === 0) {
|
|
15101
15399
|
throw new Error(
|
|
15102
15400
|
`renderClientCOOTemplate: missing required variable "${key}"`
|
|
@@ -15105,7 +15403,7 @@ function renderClientCOOTemplate(vars) {
|
|
|
15105
15403
|
}
|
|
15106
15404
|
let out = CLIENT_COO_TEMPLATE;
|
|
15107
15405
|
for (const key of CLIENT_COO_PLACEHOLDERS) {
|
|
15108
|
-
out = out.split(`{{${key}}}`).join(
|
|
15406
|
+
out = out.split(`{{${key}}}`).join(resolved[key]);
|
|
15109
15407
|
}
|
|
15110
15408
|
if (vars.industry_context) {
|
|
15111
15409
|
out += "\n" + vars.industry_context;
|
|
@@ -15126,7 +15424,7 @@ created_by: system
|
|
|
15126
15424
|
---
|
|
15127
15425
|
## Identity
|
|
15128
15426
|
|
|
15129
|
-
You are {{agent_name}}, the
|
|
15427
|
+
You are {{agent_name}}, the {{title}} at {{company_name}}.
|
|
15130
15428
|
|
|
15131
15429
|
You are {{founder_name}}'s most reliable teammate in business \u2014 the knowledgeable older sibling who has been through it all. You have seen projects succeed and fail. You know what matters and what is noise. You do not get anxious about problems; you see them coming, stay calm, and handle them.
|
|
15132
15430
|
|
|
@@ -15213,7 +15511,8 @@ All memory, tasks, behaviors, documents, and wiki content belonging to {{company
|
|
|
15213
15511
|
CLIENT_COO_PLACEHOLDERS = [
|
|
15214
15512
|
"agent_name",
|
|
15215
15513
|
"company_name",
|
|
15216
|
-
"founder_name"
|
|
15514
|
+
"founder_name",
|
|
15515
|
+
"title"
|
|
15217
15516
|
];
|
|
15218
15517
|
}
|
|
15219
15518
|
});
|
|
@@ -15387,6 +15686,7 @@ ${vars}`);
|
|
|
15387
15686
|
agent_name,
|
|
15388
15687
|
company_name,
|
|
15389
15688
|
founder_name,
|
|
15689
|
+
title: "Chief Operating Officer",
|
|
15390
15690
|
industry_context: pack.identityContext ?? void 0
|
|
15391
15691
|
});
|
|
15392
15692
|
cooSummaryLines = [
|
|
@@ -26959,7 +27259,6 @@ import { createServer as createHttpServer } from "http";
|
|
|
26959
27259
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
26960
27260
|
import { writeFileSync as writeFileSync24, unlinkSync as unlinkSync13, mkdirSync as mkdirSync19, existsSync as existsSync39, readFileSync as readFileSync33, chmodSync as chmodSync2 } from "fs";
|
|
26961
27261
|
import path51 from "path";
|
|
26962
|
-
import { getLlama } from "node-llama-cpp";
|
|
26963
27262
|
|
|
26964
27263
|
// src/lib/orchestration-metrics.ts
|
|
26965
27264
|
init_config();
|
|
@@ -27030,6 +27329,7 @@ function initMetrics() {
|
|
|
27030
27329
|
}
|
|
27031
27330
|
|
|
27032
27331
|
// src/lib/exe-daemon.ts
|
|
27332
|
+
init_memory_write_governor();
|
|
27033
27333
|
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path51.join(EXE_AI_DIR, "exed.sock");
|
|
27034
27334
|
var PID_PATH3 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path51.join(EXE_AI_DIR, "exed.pid");
|
|
27035
27335
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
@@ -27061,15 +27361,22 @@ function enqueue(queue, entry) {
|
|
|
27061
27361
|
async function loadModel() {
|
|
27062
27362
|
const modelPath = path51.join(MODELS_DIR, MODEL_FILE);
|
|
27063
27363
|
if (!existsSync39(modelPath)) {
|
|
27064
|
-
process.stderr.write(`[exed]
|
|
27364
|
+
process.stderr.write(`[exed] No model at ${modelPath} \u2014 running without embeddings (VPS mode).
|
|
27065
27365
|
`);
|
|
27066
|
-
|
|
27366
|
+
return;
|
|
27067
27367
|
}
|
|
27068
27368
|
process.stderr.write("[exed] Loading model...\n");
|
|
27069
|
-
|
|
27070
|
-
|
|
27071
|
-
|
|
27072
|
-
|
|
27369
|
+
try {
|
|
27370
|
+
const { getLlama } = await import("node-llama-cpp");
|
|
27371
|
+
_llama = await getLlama();
|
|
27372
|
+
_model = await _llama.loadModel({ modelPath });
|
|
27373
|
+
_context = await _model.createEmbeddingContext();
|
|
27374
|
+
process.stderr.write("[exed] Model loaded and ready.\n");
|
|
27375
|
+
} catch (err) {
|
|
27376
|
+
process.stderr.write(`[exed] Model load failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
27377
|
+
`);
|
|
27378
|
+
process.stderr.write("[exed] Running without embeddings \u2014 memories will have null vectors.\n");
|
|
27379
|
+
}
|
|
27073
27380
|
}
|
|
27074
27381
|
async function processQueue() {
|
|
27075
27382
|
if (_processing) return;
|
|
@@ -27252,43 +27559,19 @@ async function handleIngest(req) {
|
|
|
27252
27559
|
try {
|
|
27253
27560
|
if (!await ensureStoreForPolling()) return;
|
|
27254
27561
|
if (!req.rawText || req.rawText.length < 50) return;
|
|
27255
|
-
|
|
27256
|
-
|
|
27257
|
-
|
|
27258
|
-
|
|
27259
|
-
|
|
27260
|
-
|
|
27261
|
-
|
|
27262
|
-
|
|
27263
|
-
|
|
27264
|
-
|
|
27265
|
-
|
|
27266
|
-
|
|
27267
|
-
|
|
27268
|
-
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
27269
|
-
const client = getClient2();
|
|
27270
|
-
const { randomUUID: randomUUID10 } = await import("crypto");
|
|
27271
|
-
const id = randomUUID10();
|
|
27272
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
27273
|
-
await client.execute({
|
|
27274
|
-
sql: `INSERT INTO memories (id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, vector, task_id, confidence, draft, memory_type, trajectory)
|
|
27275
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'raw', ?)`,
|
|
27276
|
-
args: [
|
|
27277
|
-
id,
|
|
27278
|
-
req.agentId,
|
|
27279
|
-
req.agentRole,
|
|
27280
|
-
req.sessionId,
|
|
27281
|
-
now,
|
|
27282
|
-
req.toolName,
|
|
27283
|
-
req.projectName,
|
|
27284
|
-
req.hasError ? 1 : 0,
|
|
27285
|
-
req.rawText,
|
|
27286
|
-
vectorBlob,
|
|
27287
|
-
req.taskId ?? null,
|
|
27288
|
-
req.confidence ?? 0.7,
|
|
27289
|
-
req.draft ? 1 : 0,
|
|
27290
|
-
req.trajectory ? JSON.stringify(req.trajectory) : null
|
|
27291
|
-
]
|
|
27562
|
+
await writeMemoryRecord({
|
|
27563
|
+
raw_text: req.rawText,
|
|
27564
|
+
agent_id: req.agentId,
|
|
27565
|
+
agent_role: req.agentRole,
|
|
27566
|
+
session_id: req.sessionId,
|
|
27567
|
+
tool_name: req.toolName,
|
|
27568
|
+
project_name: req.projectName,
|
|
27569
|
+
has_error: req.hasError,
|
|
27570
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27571
|
+
task_id: req.taskId ?? void 0,
|
|
27572
|
+
confidence: req.confidence,
|
|
27573
|
+
draft: req.draft,
|
|
27574
|
+
trajectory: req.trajectory ? JSON.stringify(req.trajectory) : void 0
|
|
27292
27575
|
});
|
|
27293
27576
|
_ingestCount++;
|
|
27294
27577
|
} catch (err) {
|
|
@@ -27336,10 +27619,38 @@ async function handleBatchWriteMemory(socket, requestId, entries) {
|
|
|
27336
27619
|
}
|
|
27337
27620
|
}
|
|
27338
27621
|
async function writeMemoryRecord(entry) {
|
|
27622
|
+
const id = randomUUID9();
|
|
27623
|
+
const now = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
27624
|
+
const governed = governMemoryRecord({
|
|
27625
|
+
id,
|
|
27626
|
+
agent_id: entry.agent_id,
|
|
27627
|
+
agent_role: entry.agent_role ?? "",
|
|
27628
|
+
session_id: entry.session_id,
|
|
27629
|
+
timestamp: now,
|
|
27630
|
+
tool_name: entry.tool_name,
|
|
27631
|
+
project_name: entry.project_name,
|
|
27632
|
+
has_error: !!entry.has_error,
|
|
27633
|
+
raw_text: entry.raw_text,
|
|
27634
|
+
vector: null,
|
|
27635
|
+
task_id: entry.task_id ?? null,
|
|
27636
|
+
importance: entry.importance ?? 5,
|
|
27637
|
+
confidence: entry.confidence ?? 0.7,
|
|
27638
|
+
draft: entry.draft ?? false,
|
|
27639
|
+
memory_type: entry.memory_type
|
|
27640
|
+
});
|
|
27641
|
+
if (governed.shouldDrop) return id;
|
|
27642
|
+
const record = governed.record;
|
|
27643
|
+
const duplicate = await findScopedDuplicate({
|
|
27644
|
+
contentHash: governed.contentHash,
|
|
27645
|
+
agentId: record.agent_id,
|
|
27646
|
+
projectName: record.project_name,
|
|
27647
|
+
memoryType: record.memory_type ?? "raw"
|
|
27648
|
+
});
|
|
27649
|
+
if (duplicate) return duplicate;
|
|
27339
27650
|
let vectorBlob = null;
|
|
27340
|
-
if (_context) {
|
|
27651
|
+
if (_context && !governed.skipEmbedding) {
|
|
27341
27652
|
try {
|
|
27342
|
-
const embedding = await _context.getEmbeddingFor(
|
|
27653
|
+
const embedding = await _context.getEmbeddingFor(record.raw_text);
|
|
27343
27654
|
const vector = Array.from(embedding.vector);
|
|
27344
27655
|
embedding.vector = null;
|
|
27345
27656
|
if (vector.length === EMBEDDING_DIM) {
|
|
@@ -27351,29 +27662,61 @@ async function writeMemoryRecord(entry) {
|
|
|
27351
27662
|
}
|
|
27352
27663
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
27353
27664
|
const client = getClient2();
|
|
27354
|
-
const
|
|
27355
|
-
const
|
|
27356
|
-
const
|
|
27665
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
27666
|
+
const version = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
27667
|
+
const hasVector = vectorBlob !== null;
|
|
27357
27668
|
await client.execute({
|
|
27358
|
-
sql: `INSERT INTO memories
|
|
27359
|
-
|
|
27360
|
-
|
|
27669
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
27670
|
+
(id, agent_id, agent_role, session_id, timestamp, tool_name, project_name,
|
|
27671
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
27672
|
+
confidence, last_accessed, draft, memory_type, trajectory, content_hash)
|
|
27673
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
27674
|
+
(id, agent_id, agent_role, session_id, timestamp, tool_name, project_name,
|
|
27675
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
27676
|
+
confidence, last_accessed, draft, memory_type, trajectory, content_hash)
|
|
27677
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)`,
|
|
27678
|
+
args: hasVector ? [
|
|
27361
27679
|
id,
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
|
|
27368
|
-
|
|
27369
|
-
|
|
27680
|
+
record.agent_id,
|
|
27681
|
+
record.agent_role,
|
|
27682
|
+
record.session_id,
|
|
27683
|
+
record.timestamp,
|
|
27684
|
+
record.tool_name,
|
|
27685
|
+
record.project_name,
|
|
27686
|
+
record.has_error ? 1 : 0,
|
|
27687
|
+
record.raw_text,
|
|
27370
27688
|
vectorBlob,
|
|
27371
|
-
|
|
27372
|
-
|
|
27373
|
-
|
|
27374
|
-
|
|
27689
|
+
version,
|
|
27690
|
+
record.task_id ?? null,
|
|
27691
|
+
record.importance ?? 5,
|
|
27692
|
+
record.confidence ?? 0.7,
|
|
27693
|
+
record.timestamp,
|
|
27694
|
+
record.draft ? 1 : 0,
|
|
27695
|
+
record.memory_type ?? "raw",
|
|
27696
|
+
entry.trajectory ?? null,
|
|
27697
|
+
governed.contentHash
|
|
27698
|
+
] : [
|
|
27699
|
+
id,
|
|
27700
|
+
record.agent_id,
|
|
27701
|
+
record.agent_role,
|
|
27702
|
+
record.session_id,
|
|
27703
|
+
record.timestamp,
|
|
27704
|
+
record.tool_name,
|
|
27705
|
+
record.project_name,
|
|
27706
|
+
record.has_error ? 1 : 0,
|
|
27707
|
+
record.raw_text,
|
|
27708
|
+
version,
|
|
27709
|
+
record.task_id ?? null,
|
|
27710
|
+
record.importance ?? 5,
|
|
27711
|
+
record.confidence ?? 0.7,
|
|
27712
|
+
record.timestamp,
|
|
27713
|
+
record.draft ? 1 : 0,
|
|
27714
|
+
record.memory_type ?? "raw",
|
|
27715
|
+
entry.trajectory ?? null,
|
|
27716
|
+
governed.contentHash
|
|
27375
27717
|
]
|
|
27376
27718
|
});
|
|
27719
|
+
schedulePostWriteMemoryHygiene([id]);
|
|
27377
27720
|
return id;
|
|
27378
27721
|
}
|
|
27379
27722
|
var MEMORY_DRAIN_INTERVAL_MS = 30 * 1e3;
|