@askexenow/exe-os 0.9.34 → 0.9.35
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 +210 -10
- package/dist/bin/backfill-responses.js +210 -10
- package/dist/bin/backfill-vectors.js +13 -2
- package/dist/bin/cleanup-stale-review-tasks.js +217 -10
- package/dist/bin/cli.js +222 -11
- package/dist/bin/exe-assign.js +210 -10
- package/dist/bin/exe-boot.js +24 -3
- package/dist/bin/exe-dispatch.js +222 -11
- package/dist/bin/exe-doctor.js +13 -2
- package/dist/bin/exe-export-behaviors.js +217 -10
- package/dist/bin/exe-forget.js +217 -10
- package/dist/bin/exe-gateway.js +222 -11
- package/dist/bin/exe-heartbeat.js +217 -10
- package/dist/bin/exe-kill.js +217 -10
- package/dist/bin/exe-launch-agent.js +92 -2
- package/dist/bin/exe-link.js +8 -0
- package/dist/bin/exe-pending-messages.js +217 -10
- package/dist/bin/exe-pending-notifications.js +217 -10
- package/dist/bin/exe-pending-reviews.js +217 -10
- package/dist/bin/exe-rename.js +8 -0
- package/dist/bin/exe-review.js +217 -10
- package/dist/bin/exe-search.js +217 -10
- package/dist/bin/exe-session-cleanup.js +222 -11
- package/dist/bin/exe-start-codex.js +92 -2
- package/dist/bin/exe-start-opencode.js +92 -2
- package/dist/bin/exe-status.js +217 -10
- package/dist/bin/exe-team.js +217 -10
- package/dist/bin/git-sweep.js +222 -11
- package/dist/bin/graph-backfill.js +92 -2
- package/dist/bin/graph-export.js +217 -10
- package/dist/bin/intercom-check.js +222 -11
- package/dist/bin/scan-tasks.js +222 -11
- package/dist/bin/setup.js +8 -0
- package/dist/bin/shard-migrate.js +92 -2
- package/dist/gateway/index.js +222 -11
- package/dist/hooks/bug-report-worker.js +222 -11
- package/dist/hooks/codex-stop-task-finalizer.js +217 -10
- package/dist/hooks/commit-complete.js +222 -11
- package/dist/hooks/error-recall.js +217 -10
- package/dist/hooks/ingest.js +217 -10
- package/dist/hooks/instructions-loaded.js +217 -10
- package/dist/hooks/notification.js +217 -10
- package/dist/hooks/post-compact.js +217 -10
- package/dist/hooks/post-tool-combined.js +217 -10
- package/dist/hooks/pre-compact.js +222 -11
- package/dist/hooks/pre-tool-use.js +217 -10
- package/dist/hooks/prompt-submit.js +222 -11
- package/dist/hooks/session-end.js +222 -11
- package/dist/hooks/session-start.js +217 -10
- package/dist/hooks/stop.js +217 -10
- package/dist/hooks/subagent-stop.js +217 -10
- package/dist/hooks/summary-worker.js +14 -1
- package/dist/index.js +222 -11
- 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/exe-daemon.js +1667 -1413
- package/dist/lib/hybrid-search.js +217 -10
- package/dist/lib/schedules.js +13 -2
- package/dist/lib/store.js +210 -10
- package/dist/lib/tasks.js +5 -1
- package/dist/lib/tmux-routing.js +5 -1
- package/dist/mcp/server.js +222 -11
- package/dist/mcp/tools/create-task.js +5 -1
- package/dist/mcp/tools/update-task.js +5 -1
- package/dist/runtime/index.js +222 -11
- package/dist/tui/App.js +222 -11
- package/package.json +1 -1
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,811 @@ 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 (memoryIds.length === 0) return;
|
|
3347
|
+
const run = () => {
|
|
3348
|
+
void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
|
|
3349
|
+
};
|
|
3350
|
+
if (typeof setImmediate === "function") setImmediate(run);
|
|
3351
|
+
else setTimeout(run, 0);
|
|
3352
|
+
}
|
|
3353
|
+
var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
|
|
3354
|
+
var init_memory_write_governor = __esm({
|
|
3355
|
+
"src/lib/memory-write-governor.ts"() {
|
|
3356
|
+
"use strict";
|
|
3357
|
+
HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
|
|
3358
|
+
"decision",
|
|
3359
|
+
"adr",
|
|
3360
|
+
"behavior",
|
|
3361
|
+
"procedure"
|
|
3362
|
+
]);
|
|
3363
|
+
NOISE_DROP_PATTERNS = [
|
|
3364
|
+
/^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
|
|
3365
|
+
/^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
|
|
3366
|
+
/^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
|
|
3367
|
+
/^\s*Intercom is a speedup, not delivery/im,
|
|
3368
|
+
/^\s*Context bar reads as USAGE not remaining/im
|
|
3369
|
+
];
|
|
3370
|
+
SKIP_EMBED_PATTERNS = [
|
|
3371
|
+
/tmux capture-pane\b/i,
|
|
3372
|
+
/docker ps\b/i,
|
|
3373
|
+
/docker images\b/i,
|
|
3374
|
+
/git status\b/i,
|
|
3375
|
+
/grep .*node_modules/i,
|
|
3376
|
+
/npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
|
|
3377
|
+
];
|
|
3378
|
+
}
|
|
3379
|
+
});
|
|
3380
|
+
|
|
3381
|
+
// src/lib/projection-worker.ts
|
|
3382
|
+
var projection_worker_exports = {};
|
|
3383
|
+
__export(projection_worker_exports, {
|
|
3384
|
+
processProjectionBatch: () => processProjectionBatch,
|
|
3385
|
+
projectionHandlersForTests: () => projectionHandlersForTests,
|
|
3386
|
+
resetProjectionWorkerForTests: () => resetProjectionWorkerForTests,
|
|
3387
|
+
setProjectionWorkerPrismaClientForTests: () => setProjectionWorkerPrismaClientForTests,
|
|
3388
|
+
startProjectionWorker: () => startProjectionWorker,
|
|
3389
|
+
stopProjectionWorker: () => stopProjectionWorker
|
|
3390
|
+
});
|
|
3391
|
+
import os5 from "os";
|
|
3392
|
+
import path8 from "path";
|
|
3393
|
+
import { existsSync as existsSync8 } from "fs";
|
|
3394
|
+
import { createRequire as createRequire2 } from "module";
|
|
3395
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
3396
|
+
function loadPrisma() {
|
|
3397
|
+
if (!prismaPromise) {
|
|
3398
|
+
prismaPromise = (async () => {
|
|
3399
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
3400
|
+
if (explicitPath) {
|
|
3401
|
+
const mod2 = await import(pathToFileURL2(explicitPath).href);
|
|
3402
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
3403
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
3404
|
+
return new Ctor2();
|
|
3405
|
+
}
|
|
3406
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path8.join(os5.homedir(), "exe-db");
|
|
3407
|
+
const req = createRequire2(path8.join(exeDbRoot, "package.json"));
|
|
3408
|
+
const entry = req.resolve("@prisma/client");
|
|
3409
|
+
const mod = await import(pathToFileURL2(entry).href);
|
|
3410
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
3411
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
3412
|
+
return new Ctor();
|
|
3413
|
+
})();
|
|
3414
|
+
}
|
|
3415
|
+
return prismaPromise;
|
|
3416
|
+
}
|
|
3417
|
+
function setProjectionWorkerPrismaClientForTests(client) {
|
|
3418
|
+
prismaPromise = client ? Promise.resolve(client) : null;
|
|
3419
|
+
}
|
|
3420
|
+
function resetProjectionWorkerForTests() {
|
|
3421
|
+
running = false;
|
|
3422
|
+
if (pollTimer) {
|
|
3423
|
+
clearTimeout(pollTimer);
|
|
3424
|
+
pollTimer = null;
|
|
3425
|
+
}
|
|
3426
|
+
prismaPromise = null;
|
|
3427
|
+
}
|
|
3428
|
+
async function processBatch() {
|
|
3429
|
+
const prisma = await loadPrisma();
|
|
3430
|
+
const events = await prisma.$queryRawUnsafe(
|
|
3431
|
+
`SELECT "id", "source", "source_id", "event_type", "payload", "metadata", "timestamp"
|
|
3432
|
+
FROM "raw"."raw_events"
|
|
3433
|
+
WHERE "processed_at" IS NULL
|
|
3434
|
+
ORDER BY "timestamp" ASC
|
|
3435
|
+
LIMIT $1`,
|
|
3436
|
+
BATCH_SIZE
|
|
3437
|
+
);
|
|
3438
|
+
if (events.length === 0) return 0;
|
|
3439
|
+
let processed = 0;
|
|
3440
|
+
for (const event of events) {
|
|
3441
|
+
try {
|
|
3442
|
+
const handler = projectionHandlers[event.source] ?? defaultHandler;
|
|
3443
|
+
const result = await handler(event, prisma);
|
|
3444
|
+
await prisma.$executeRawUnsafe(
|
|
3445
|
+
`UPDATE "raw"."raw_events"
|
|
3446
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
3447
|
+
WHERE "id" = $2`,
|
|
3448
|
+
JSON.stringify({ targets: result.targets, skipped: result.skipped }),
|
|
3449
|
+
event.id
|
|
3450
|
+
);
|
|
3451
|
+
processed++;
|
|
3452
|
+
} catch (err) {
|
|
3453
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3454
|
+
process.stderr.write(
|
|
3455
|
+
`[projection-worker] Error processing event ${event.id}: ${message}
|
|
3456
|
+
`
|
|
3457
|
+
);
|
|
3458
|
+
await prisma.$executeRawUnsafe(
|
|
3459
|
+
`UPDATE "raw"."raw_events"
|
|
3460
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
3461
|
+
WHERE "id" = $2`,
|
|
3462
|
+
JSON.stringify({ error: message }),
|
|
3463
|
+
event.id
|
|
3464
|
+
);
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
return processed;
|
|
3468
|
+
}
|
|
3469
|
+
function startProjectionWorker() {
|
|
3470
|
+
if (running) return;
|
|
3471
|
+
if (!process.env.DATABASE_URL) {
|
|
3472
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path8.join(os5.homedir(), "exe-db");
|
|
3473
|
+
if (!existsSync8(path8.join(exeDbRoot, "package.json"))) {
|
|
3474
|
+
process.stderr.write("[projection-worker] Skipped \u2014 no exe-db found. Set DATABASE_URL or EXE_DB_ROOT to enable.\n");
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
running = true;
|
|
3479
|
+
process.stderr.write("[projection-worker] Starting...\n");
|
|
3480
|
+
const tick = async () => {
|
|
3481
|
+
if (!running) return;
|
|
3482
|
+
try {
|
|
3483
|
+
const count = await processBatch();
|
|
3484
|
+
if (count > 0) {
|
|
3485
|
+
process.stderr.write(`[projection-worker] Processed ${count} events.
|
|
3486
|
+
`);
|
|
3487
|
+
}
|
|
3488
|
+
} catch (err) {
|
|
3489
|
+
process.stderr.write(
|
|
3490
|
+
`[projection-worker] Poll error: ${err instanceof Error ? err.message : String(err)}
|
|
3491
|
+
`
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
if (running) {
|
|
3495
|
+
pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
|
|
3496
|
+
}
|
|
3497
|
+
};
|
|
3498
|
+
void tick();
|
|
3499
|
+
}
|
|
3500
|
+
function stopProjectionWorker() {
|
|
3501
|
+
running = false;
|
|
3502
|
+
if (pollTimer) {
|
|
3503
|
+
clearTimeout(pollTimer);
|
|
3504
|
+
pollTimer = null;
|
|
3505
|
+
}
|
|
3506
|
+
process.stderr.write("[projection-worker] Stopped.\n");
|
|
3507
|
+
}
|
|
3508
|
+
async function processProjectionBatch() {
|
|
3509
|
+
return processBatch();
|
|
3510
|
+
}
|
|
3511
|
+
var prismaPromise, projectionHandlers, projectionHandlersForTests, defaultHandler, BATCH_SIZE, POLL_INTERVAL_MS, running, pollTimer;
|
|
3512
|
+
var init_projection_worker = __esm({
|
|
3513
|
+
"src/lib/projection-worker.ts"() {
|
|
3514
|
+
"use strict";
|
|
3515
|
+
prismaPromise = null;
|
|
3516
|
+
projectionHandlers = {
|
|
3517
|
+
async whatsapp(event, prisma) {
|
|
3518
|
+
const targets = [];
|
|
3519
|
+
const payload = event.payload;
|
|
3520
|
+
await prisma.$executeRawUnsafe(
|
|
3521
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
3522
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
3523
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3524
|
+
crypto.randomUUID(),
|
|
3525
|
+
"system",
|
|
3526
|
+
"ingest",
|
|
3527
|
+
"projection-worker",
|
|
3528
|
+
"projection_worker",
|
|
3529
|
+
"exe-os",
|
|
3530
|
+
JSON.stringify(payload),
|
|
3531
|
+
"raw",
|
|
3532
|
+
"whatsapp",
|
|
3533
|
+
event.event_type
|
|
3534
|
+
);
|
|
3535
|
+
targets.push("memory");
|
|
3536
|
+
return { targets };
|
|
3537
|
+
},
|
|
3538
|
+
async shopify(event, prisma) {
|
|
3539
|
+
const targets = [];
|
|
3540
|
+
const payload = event.payload;
|
|
3541
|
+
await prisma.$executeRawUnsafe(
|
|
3542
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
3543
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
3544
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3545
|
+
crypto.randomUUID(),
|
|
3546
|
+
"system",
|
|
3547
|
+
"ingest",
|
|
3548
|
+
"projection-worker",
|
|
3549
|
+
"projection_worker",
|
|
3550
|
+
"exe-os",
|
|
3551
|
+
JSON.stringify(payload),
|
|
3552
|
+
"raw",
|
|
3553
|
+
"shopify",
|
|
3554
|
+
event.event_type
|
|
3555
|
+
);
|
|
3556
|
+
targets.push("memory");
|
|
3557
|
+
return { targets };
|
|
3558
|
+
},
|
|
3559
|
+
async cloud_sync(event, prisma) {
|
|
3560
|
+
const targets = [];
|
|
3561
|
+
const payload = event.payload;
|
|
3562
|
+
await prisma.$executeRawUnsafe(
|
|
3563
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
3564
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
3565
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3566
|
+
payload.id ?? crypto.randomUUID(),
|
|
3567
|
+
payload.agent_id ?? "system",
|
|
3568
|
+
payload.agent_role ?? "ingest",
|
|
3569
|
+
payload.session_id ?? "projection-worker",
|
|
3570
|
+
payload.tool_name ?? "cloud_sync",
|
|
3571
|
+
payload.project_name ?? "exe-os",
|
|
3572
|
+
payload.raw_text ?? JSON.stringify(payload),
|
|
3573
|
+
payload.memory_type ?? "raw",
|
|
3574
|
+
"cloud_sync",
|
|
3575
|
+
event.event_type,
|
|
3576
|
+
payload.domain ?? null
|
|
3577
|
+
);
|
|
3578
|
+
targets.push("memory");
|
|
3579
|
+
return { targets };
|
|
3580
|
+
},
|
|
3581
|
+
async external_agent(event, prisma) {
|
|
3582
|
+
const targets = [];
|
|
3583
|
+
const payload = event.payload;
|
|
3584
|
+
await prisma.$executeRawUnsafe(
|
|
3585
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
3586
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
3587
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3588
|
+
crypto.randomUUID(),
|
|
3589
|
+
"system",
|
|
3590
|
+
"ingest",
|
|
3591
|
+
"projection-worker",
|
|
3592
|
+
"projection_worker",
|
|
3593
|
+
"exe-os",
|
|
3594
|
+
JSON.stringify(payload),
|
|
3595
|
+
"raw",
|
|
3596
|
+
"external_agent",
|
|
3597
|
+
event.event_type,
|
|
3598
|
+
payload.domain ?? null
|
|
3599
|
+
);
|
|
3600
|
+
targets.push("memory");
|
|
3601
|
+
return { targets };
|
|
3602
|
+
}
|
|
3603
|
+
};
|
|
3604
|
+
projectionHandlersForTests = projectionHandlers;
|
|
3605
|
+
defaultHandler = async (event, prisma) => {
|
|
3606
|
+
await prisma.$executeRawUnsafe(
|
|
3607
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
3608
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
3609
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
3610
|
+
crypto.randomUUID(),
|
|
3611
|
+
"system",
|
|
3612
|
+
"ingest",
|
|
3613
|
+
"projection-worker",
|
|
3614
|
+
"projection_worker",
|
|
3615
|
+
"exe-os",
|
|
3616
|
+
JSON.stringify(event.payload),
|
|
3617
|
+
"raw",
|
|
3618
|
+
event.source,
|
|
3619
|
+
event.event_type
|
|
3620
|
+
);
|
|
3621
|
+
return { targets: ["memory"] };
|
|
3622
|
+
};
|
|
3623
|
+
BATCH_SIZE = 50;
|
|
3624
|
+
POLL_INTERVAL_MS = 1e4;
|
|
3625
|
+
running = false;
|
|
3626
|
+
pollTimer = null;
|
|
3627
|
+
}
|
|
3628
|
+
});
|
|
3629
|
+
|
|
3630
|
+
// src/lib/shard-manager.ts
|
|
3631
|
+
var shard_manager_exports = {};
|
|
3632
|
+
__export(shard_manager_exports, {
|
|
3633
|
+
disposeShards: () => disposeShards,
|
|
3634
|
+
ensureShardSchema: () => ensureShardSchema,
|
|
3635
|
+
getOpenShardCount: () => getOpenShardCount,
|
|
3636
|
+
getReadyShardClient: () => getReadyShardClient,
|
|
3637
|
+
getShardClient: () => getShardClient,
|
|
3638
|
+
getShardsDir: () => getShardsDir,
|
|
3639
|
+
initShardManager: () => initShardManager,
|
|
3640
|
+
isShardingEnabled: () => isShardingEnabled,
|
|
3641
|
+
listShards: () => listShards,
|
|
3642
|
+
shardExists: () => shardExists
|
|
3643
|
+
});
|
|
3644
|
+
import path9 from "path";
|
|
3645
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readdirSync } from "fs";
|
|
3646
|
+
import { createClient as createClient2 } from "@libsql/client";
|
|
3647
|
+
function initShardManager(encryptionKey) {
|
|
3648
|
+
_encryptionKey = encryptionKey;
|
|
3649
|
+
if (!existsSync9(SHARDS_DIR)) {
|
|
3650
|
+
mkdirSync2(SHARDS_DIR, { recursive: true });
|
|
3545
3651
|
}
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3652
|
+
_shardingEnabled = true;
|
|
3653
|
+
if (_evictionTimer) clearInterval(_evictionTimer);
|
|
3654
|
+
_evictionTimer = setInterval(evictIdleShards, EVICTION_INTERVAL_MS);
|
|
3655
|
+
_evictionTimer.unref();
|
|
3656
|
+
}
|
|
3657
|
+
function isShardingEnabled() {
|
|
3658
|
+
return _shardingEnabled;
|
|
3659
|
+
}
|
|
3660
|
+
function getShardsDir() {
|
|
3661
|
+
return SHARDS_DIR;
|
|
3662
|
+
}
|
|
3663
|
+
function getShardClient(projectName) {
|
|
3664
|
+
if (!_encryptionKey) {
|
|
3665
|
+
throw new Error("Shard manager not initialized. Call initShardManager() first.");
|
|
3552
3666
|
}
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
args: []
|
|
3557
|
-
});
|
|
3558
|
-
} catch {
|
|
3667
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3668
|
+
if (!safeName || safeName === "unknown") {
|
|
3669
|
+
throw new Error(`Invalid project name for shard: "${projectName}" (resolved to "${safeName}")`);
|
|
3559
3670
|
}
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
});
|
|
3565
|
-
} catch {
|
|
3671
|
+
const cached = _shards.get(safeName);
|
|
3672
|
+
if (cached) {
|
|
3673
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3674
|
+
return cached;
|
|
3566
3675
|
}
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
3570
|
-
args: []
|
|
3571
|
-
});
|
|
3572
|
-
} catch {
|
|
3676
|
+
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
3677
|
+
evictLRU();
|
|
3573
3678
|
}
|
|
3679
|
+
const dbPath = path9.join(SHARDS_DIR, `${safeName}.db`);
|
|
3680
|
+
const client = createClient2({
|
|
3681
|
+
url: `file:${dbPath}`,
|
|
3682
|
+
encryptionKey: _encryptionKey
|
|
3683
|
+
});
|
|
3684
|
+
_shards.set(safeName, client);
|
|
3685
|
+
_shardLastAccess.set(safeName, Date.now());
|
|
3686
|
+
return client;
|
|
3687
|
+
}
|
|
3688
|
+
function shardExists(projectName) {
|
|
3689
|
+
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
3690
|
+
return existsSync9(path9.join(SHARDS_DIR, `${safeName}.db`));
|
|
3691
|
+
}
|
|
3692
|
+
function listShards() {
|
|
3693
|
+
if (!existsSync9(SHARDS_DIR)) return [];
|
|
3694
|
+
return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
3695
|
+
}
|
|
3696
|
+
async function ensureShardSchema(client) {
|
|
3697
|
+
await client.execute("PRAGMA journal_mode = WAL");
|
|
3698
|
+
await client.execute("PRAGMA busy_timeout = 30000");
|
|
3574
3699
|
try {
|
|
3575
|
-
await client.execute(
|
|
3576
|
-
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
3577
|
-
args: []
|
|
3578
|
-
});
|
|
3700
|
+
await client.execute("PRAGMA libsql_vector_search_ef = 128");
|
|
3579
3701
|
} catch {
|
|
3580
3702
|
}
|
|
3581
3703
|
await client.executeMultiple(`
|
|
3582
|
-
CREATE
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3704
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
3705
|
+
id TEXT PRIMARY KEY,
|
|
3706
|
+
agent_id TEXT NOT NULL,
|
|
3707
|
+
agent_role TEXT NOT NULL,
|
|
3708
|
+
session_id TEXT NOT NULL,
|
|
3709
|
+
timestamp TEXT NOT NULL,
|
|
3710
|
+
tool_name TEXT NOT NULL,
|
|
3711
|
+
project_name TEXT NOT NULL,
|
|
3712
|
+
has_error INTEGER NOT NULL DEFAULT 0,
|
|
3713
|
+
raw_text TEXT NOT NULL,
|
|
3714
|
+
vector F32_BLOB(1024),
|
|
3715
|
+
version INTEGER NOT NULL DEFAULT 0
|
|
3588
3716
|
);
|
|
3589
3717
|
|
|
3590
|
-
CREATE
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
CREATE
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3718
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent ON memories(agent_id);
|
|
3719
|
+
CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp);
|
|
3720
|
+
CREATE INDEX IF NOT EXISTS idx_memories_agent_project ON memories(agent_id, project_name);
|
|
3721
|
+
`);
|
|
3722
|
+
await client.executeMultiple(`
|
|
3723
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
3724
|
+
raw_text,
|
|
3725
|
+
content='memories',
|
|
3726
|
+
content_rowid='rowid'
|
|
3727
|
+
);
|
|
3728
|
+
|
|
3729
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ai AFTER INSERT ON memories BEGIN
|
|
3730
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
3731
|
+
END;
|
|
3732
|
+
|
|
3733
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_ad AFTER DELETE ON memories BEGIN
|
|
3734
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
3735
|
+
END;
|
|
3736
|
+
|
|
3737
|
+
CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
|
|
3738
|
+
INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
|
|
3739
|
+
INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
|
|
3740
|
+
END;
|
|
3606
3741
|
`);
|
|
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
3742
|
for (const col of [
|
|
3743
|
+
"ALTER TABLE memories ADD COLUMN task_id TEXT",
|
|
3744
|
+
"ALTER TABLE memories ADD COLUMN consolidated INTEGER NOT NULL DEFAULT 0",
|
|
3745
|
+
"ALTER TABLE memories ADD COLUMN author_device_id TEXT",
|
|
3746
|
+
"ALTER TABLE memories ADD COLUMN scope TEXT NOT NULL DEFAULT 'business'",
|
|
3747
|
+
"ALTER TABLE memories ADD COLUMN importance INTEGER DEFAULT 5",
|
|
3748
|
+
"ALTER TABLE memories ADD COLUMN status TEXT DEFAULT 'active'",
|
|
3749
|
+
"ALTER TABLE memories ADD COLUMN wiki_synced INTEGER DEFAULT 0",
|
|
3750
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted INTEGER DEFAULT 0",
|
|
3751
|
+
"ALTER TABLE memories ADD COLUMN content_hash TEXT",
|
|
3752
|
+
"ALTER TABLE memories ADD COLUMN graph_extracted_hash TEXT",
|
|
3753
|
+
"ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7",
|
|
3754
|
+
"ALTER TABLE memories ADD COLUMN last_accessed TEXT",
|
|
3755
|
+
// Wiki linkage columns (must match database.ts)
|
|
3756
|
+
"ALTER TABLE memories ADD COLUMN workspace_id TEXT",
|
|
3757
|
+
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
3758
|
+
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
3759
|
+
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
3760
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
3761
|
+
// Source provenance columns (must match database.ts)
|
|
3762
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
3763
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
3764
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
3765
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
|
|
3766
|
+
// MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
|
|
3767
|
+
"ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
|
|
3768
|
+
"ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
|
|
3769
|
+
"ALTER TABLE memories ADD COLUMN trajectory TEXT",
|
|
3770
|
+
// Metadata enrichment columns (must match database.ts)
|
|
3687
3771
|
"ALTER TABLE memories ADD COLUMN intent TEXT",
|
|
3688
3772
|
"ALTER TABLE memories ADD COLUMN outcome TEXT",
|
|
3689
3773
|
"ALTER TABLE memories ADD COLUMN domain TEXT",
|
|
@@ -3705,49 +3789,164 @@ async function ensureSchema() {
|
|
|
3705
3789
|
} catch {
|
|
3706
3790
|
}
|
|
3707
3791
|
}
|
|
3792
|
+
for (const idx of [
|
|
3793
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
3794
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
|
|
3795
|
+
"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"
|
|
3796
|
+
]) {
|
|
3797
|
+
try {
|
|
3798
|
+
await client.execute(idx);
|
|
3799
|
+
} catch {
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3708
3802
|
try {
|
|
3709
|
-
await client.execute(
|
|
3710
|
-
sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
|
|
3711
|
-
args: []
|
|
3712
|
-
});
|
|
3803
|
+
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
3713
3804
|
} catch {
|
|
3714
3805
|
}
|
|
3806
|
+
for (const idx of [
|
|
3807
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace_id)",
|
|
3808
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_document ON memories(document_id)",
|
|
3809
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_user ON memories(user_id)"
|
|
3810
|
+
]) {
|
|
3811
|
+
try {
|
|
3812
|
+
await client.execute(idx);
|
|
3813
|
+
} catch {
|
|
3814
|
+
}
|
|
3815
|
+
}
|
|
3816
|
+
await client.executeMultiple(`
|
|
3817
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
3818
|
+
id TEXT PRIMARY KEY,
|
|
3819
|
+
name TEXT NOT NULL,
|
|
3820
|
+
type TEXT NOT NULL,
|
|
3821
|
+
first_seen TEXT NOT NULL,
|
|
3822
|
+
last_seen TEXT NOT NULL,
|
|
3823
|
+
properties TEXT DEFAULT '{}',
|
|
3824
|
+
UNIQUE(name, type)
|
|
3825
|
+
);
|
|
3826
|
+
|
|
3827
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
3828
|
+
id TEXT PRIMARY KEY,
|
|
3829
|
+
source_entity_id TEXT NOT NULL,
|
|
3830
|
+
target_entity_id TEXT NOT NULL,
|
|
3831
|
+
type TEXT NOT NULL,
|
|
3832
|
+
weight REAL DEFAULT 1.0,
|
|
3833
|
+
timestamp TEXT NOT NULL,
|
|
3834
|
+
properties TEXT DEFAULT '{}',
|
|
3835
|
+
UNIQUE(source_entity_id, target_entity_id, type)
|
|
3836
|
+
);
|
|
3837
|
+
|
|
3838
|
+
CREATE TABLE IF NOT EXISTS entity_memories (
|
|
3839
|
+
entity_id TEXT NOT NULL,
|
|
3840
|
+
memory_id TEXT NOT NULL,
|
|
3841
|
+
PRIMARY KEY (entity_id, memory_id)
|
|
3842
|
+
);
|
|
3843
|
+
|
|
3844
|
+
CREATE TABLE IF NOT EXISTS relationship_memories (
|
|
3845
|
+
relationship_id TEXT NOT NULL,
|
|
3846
|
+
memory_id TEXT NOT NULL,
|
|
3847
|
+
PRIMARY KEY (relationship_id, memory_id)
|
|
3848
|
+
);
|
|
3849
|
+
|
|
3850
|
+
CREATE INDEX IF NOT EXISTS idx_entities_name ON entities(name);
|
|
3851
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
3852
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
|
|
3853
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
|
|
3854
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
|
|
3855
|
+
|
|
3856
|
+
CREATE TABLE IF NOT EXISTS hyperedges (
|
|
3857
|
+
id TEXT PRIMARY KEY,
|
|
3858
|
+
label TEXT NOT NULL,
|
|
3859
|
+
relation TEXT NOT NULL,
|
|
3860
|
+
confidence REAL DEFAULT 1.0,
|
|
3861
|
+
timestamp TEXT NOT NULL
|
|
3862
|
+
);
|
|
3863
|
+
|
|
3864
|
+
CREATE TABLE IF NOT EXISTS hyperedge_nodes (
|
|
3865
|
+
hyperedge_id TEXT NOT NULL,
|
|
3866
|
+
entity_id TEXT NOT NULL,
|
|
3867
|
+
PRIMARY KEY (hyperedge_id, entity_id)
|
|
3868
|
+
);
|
|
3869
|
+
`);
|
|
3870
|
+
for (const col of [
|
|
3871
|
+
"ALTER TABLE relationships ADD COLUMN confidence REAL DEFAULT 1.0",
|
|
3872
|
+
"ALTER TABLE relationships ADD COLUMN confidence_label TEXT DEFAULT 'extracted'"
|
|
3873
|
+
]) {
|
|
3874
|
+
try {
|
|
3875
|
+
await client.execute(col);
|
|
3876
|
+
} catch {
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3715
3879
|
}
|
|
3716
|
-
async function
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3880
|
+
async function getReadyShardClient(projectName) {
|
|
3881
|
+
const client = getShardClient(projectName);
|
|
3882
|
+
await ensureShardSchema(client);
|
|
3883
|
+
return client;
|
|
3884
|
+
}
|
|
3885
|
+
function evictLRU() {
|
|
3886
|
+
let oldest = null;
|
|
3887
|
+
let oldestTime = Infinity;
|
|
3888
|
+
for (const [name, time] of _shardLastAccess) {
|
|
3889
|
+
if (time < oldestTime) {
|
|
3890
|
+
oldestTime = time;
|
|
3891
|
+
oldest = name;
|
|
3892
|
+
}
|
|
3720
3893
|
}
|
|
3721
|
-
if (
|
|
3722
|
-
|
|
3723
|
-
|
|
3894
|
+
if (oldest) {
|
|
3895
|
+
const client = _shards.get(oldest);
|
|
3896
|
+
if (client) {
|
|
3897
|
+
client.close();
|
|
3898
|
+
}
|
|
3899
|
+
_shards.delete(oldest);
|
|
3900
|
+
_shardLastAccess.delete(oldest);
|
|
3724
3901
|
}
|
|
3725
|
-
|
|
3726
|
-
|
|
3902
|
+
}
|
|
3903
|
+
function evictIdleShards() {
|
|
3904
|
+
const now = Date.now();
|
|
3905
|
+
const toEvict = [];
|
|
3906
|
+
for (const [name, lastAccess] of _shardLastAccess) {
|
|
3907
|
+
if (now - lastAccess > SHARD_IDLE_MS) {
|
|
3908
|
+
toEvict.push(name);
|
|
3909
|
+
}
|
|
3727
3910
|
}
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3911
|
+
for (const name of toEvict) {
|
|
3912
|
+
const client = _shards.get(name);
|
|
3913
|
+
if (client) {
|
|
3914
|
+
client.close();
|
|
3915
|
+
}
|
|
3916
|
+
_shards.delete(name);
|
|
3917
|
+
_shardLastAccess.delete(name);
|
|
3733
3918
|
}
|
|
3734
3919
|
}
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3920
|
+
function getOpenShardCount() {
|
|
3921
|
+
return _shards.size;
|
|
3922
|
+
}
|
|
3923
|
+
function disposeShards() {
|
|
3924
|
+
if (_evictionTimer) {
|
|
3925
|
+
clearInterval(_evictionTimer);
|
|
3926
|
+
_evictionTimer = null;
|
|
3927
|
+
}
|
|
3928
|
+
for (const [, client] of _shards) {
|
|
3929
|
+
client.close();
|
|
3930
|
+
}
|
|
3931
|
+
_shards.clear();
|
|
3932
|
+
_shardLastAccess.clear();
|
|
3933
|
+
_shardingEnabled = false;
|
|
3934
|
+
_encryptionKey = null;
|
|
3935
|
+
}
|
|
3936
|
+
var SHARDS_DIR, SHARD_IDLE_MS, MAX_OPEN_SHARDS, EVICTION_INTERVAL_MS, _shards, _shardLastAccess, _evictionTimer, _encryptionKey, _shardingEnabled;
|
|
3937
|
+
var init_shard_manager = __esm({
|
|
3938
|
+
"src/lib/shard-manager.ts"() {
|
|
3738
3939
|
"use strict";
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
SOFT_DELETE_RETENTION_DAYS = 7;
|
|
3750
|
-
disposeTurso = disposeDatabase;
|
|
3940
|
+
init_config();
|
|
3941
|
+
SHARDS_DIR = path9.join(EXE_AI_DIR, "shards");
|
|
3942
|
+
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
3943
|
+
MAX_OPEN_SHARDS = 10;
|
|
3944
|
+
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
3945
|
+
_shards = /* @__PURE__ */ new Map();
|
|
3946
|
+
_shardLastAccess = /* @__PURE__ */ new Map();
|
|
3947
|
+
_evictionTimer = null;
|
|
3948
|
+
_encryptionKey = null;
|
|
3949
|
+
_shardingEnabled = false;
|
|
3751
3950
|
}
|
|
3752
3951
|
});
|
|
3753
3952
|
|
|
@@ -4373,7 +4572,6 @@ __export(store_exports, {
|
|
|
4373
4572
|
vectorToBlob: () => vectorToBlob,
|
|
4374
4573
|
writeMemory: () => writeMemory
|
|
4375
4574
|
});
|
|
4376
|
-
import { createHash } from "crypto";
|
|
4377
4575
|
function isBusyError2(err) {
|
|
4378
4576
|
if (err instanceof Error) {
|
|
4379
4577
|
const msg = err.message.toLowerCase();
|
|
@@ -4487,17 +4685,24 @@ async function writeMemory(record) {
|
|
|
4487
4685
|
`Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
|
|
4488
4686
|
);
|
|
4489
4687
|
}
|
|
4490
|
-
const
|
|
4491
|
-
if (
|
|
4688
|
+
const governed = governMemoryRecord(record);
|
|
4689
|
+
if (governed.shouldDrop) return;
|
|
4690
|
+
record = governed.record;
|
|
4691
|
+
const contentHash2 = governed.contentHash;
|
|
4692
|
+
const memoryType = record.memory_type ?? "raw";
|
|
4693
|
+
if (_pendingRecords.some(
|
|
4694
|
+
(r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
|
|
4695
|
+
)) {
|
|
4492
4696
|
return;
|
|
4493
4697
|
}
|
|
4494
4698
|
try {
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4699
|
+
const existing = await findScopedDuplicate({
|
|
4700
|
+
contentHash: contentHash2,
|
|
4701
|
+
agentId: record.agent_id,
|
|
4702
|
+
projectName: record.project_name,
|
|
4703
|
+
memoryType
|
|
4499
4704
|
});
|
|
4500
|
-
if (existing
|
|
4705
|
+
if (existing) return;
|
|
4501
4706
|
} catch {
|
|
4502
4707
|
}
|
|
4503
4708
|
const dbRow = {
|
|
@@ -4528,7 +4733,7 @@ async function writeMemory(record) {
|
|
|
4528
4733
|
tier: record.tier ?? classifyTier(record),
|
|
4529
4734
|
supersedes_id: record.supersedes_id ?? null,
|
|
4530
4735
|
draft: record.draft ? 1 : 0,
|
|
4531
|
-
memory_type:
|
|
4736
|
+
memory_type: memoryType,
|
|
4532
4737
|
trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
|
|
4533
4738
|
content_hash: contentHash2,
|
|
4534
4739
|
intent: record.intent ?? null,
|
|
@@ -4687,6 +4892,7 @@ async function flushBatch() {
|
|
|
4687
4892
|
const globalClient = getClient();
|
|
4688
4893
|
const globalStmts = batch.map(buildStmt);
|
|
4689
4894
|
await globalClient.batch(globalStmts, "write");
|
|
4895
|
+
schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
|
|
4690
4896
|
_pendingRecords.splice(0, batch.length);
|
|
4691
4897
|
try {
|
|
4692
4898
|
const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
@@ -4945,6 +5151,7 @@ var init_store = __esm({
|
|
|
4945
5151
|
init_keychain();
|
|
4946
5152
|
init_config();
|
|
4947
5153
|
init_state_bus();
|
|
5154
|
+
init_memory_write_governor();
|
|
4948
5155
|
INIT_MAX_RETRIES = 3;
|
|
4949
5156
|
INIT_RETRY_DELAY_MS = 1e3;
|
|
4950
5157
|
_pendingRecords = [];
|
|
@@ -5170,8 +5377,8 @@ async function ensureLoaded() {
|
|
|
5170
5377
|
);
|
|
5171
5378
|
}
|
|
5172
5379
|
process.stderr.write("[reranker] Loading Jina Reranker v3...\n");
|
|
5173
|
-
const { getLlama
|
|
5174
|
-
const llama = await
|
|
5380
|
+
const { getLlama } = await import("node-llama-cpp");
|
|
5381
|
+
const llama = await getLlama();
|
|
5175
5382
|
_rerankerModel = await llama.loadModel({ modelPath });
|
|
5176
5383
|
_rerankerContext = await _rerankerModel.createEmbeddingContext();
|
|
5177
5384
|
process.stderr.write("[reranker] Jina Reranker v3 loaded.\n");
|
|
@@ -9930,7 +10137,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
9930
10137
|
}
|
|
9931
10138
|
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
9932
10139
|
if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
|
|
9933
|
-
|
|
10140
|
+
let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
|
|
10141
|
+
if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
|
|
10142
|
+
ccModel += "[1m]";
|
|
10143
|
+
}
|
|
10144
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
|
|
9934
10145
|
}
|
|
9935
10146
|
}
|
|
9936
10147
|
let spawnCommand;
|
|
@@ -26959,7 +27170,6 @@ import { createServer as createHttpServer } from "http";
|
|
|
26959
27170
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
26960
27171
|
import { writeFileSync as writeFileSync24, unlinkSync as unlinkSync13, mkdirSync as mkdirSync19, existsSync as existsSync39, readFileSync as readFileSync33, chmodSync as chmodSync2 } from "fs";
|
|
26961
27172
|
import path51 from "path";
|
|
26962
|
-
import { getLlama } from "node-llama-cpp";
|
|
26963
27173
|
|
|
26964
27174
|
// src/lib/orchestration-metrics.ts
|
|
26965
27175
|
init_config();
|
|
@@ -27030,6 +27240,7 @@ function initMetrics() {
|
|
|
27030
27240
|
}
|
|
27031
27241
|
|
|
27032
27242
|
// src/lib/exe-daemon.ts
|
|
27243
|
+
init_memory_write_governor();
|
|
27033
27244
|
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path51.join(EXE_AI_DIR, "exed.sock");
|
|
27034
27245
|
var PID_PATH3 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path51.join(EXE_AI_DIR, "exed.pid");
|
|
27035
27246
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
@@ -27061,15 +27272,22 @@ function enqueue(queue, entry) {
|
|
|
27061
27272
|
async function loadModel() {
|
|
27062
27273
|
const modelPath = path51.join(MODELS_DIR, MODEL_FILE);
|
|
27063
27274
|
if (!existsSync39(modelPath)) {
|
|
27064
|
-
process.stderr.write(`[exed]
|
|
27275
|
+
process.stderr.write(`[exed] No model at ${modelPath} \u2014 running without embeddings (VPS mode).
|
|
27065
27276
|
`);
|
|
27066
|
-
|
|
27277
|
+
return;
|
|
27067
27278
|
}
|
|
27068
27279
|
process.stderr.write("[exed] Loading model...\n");
|
|
27069
|
-
|
|
27070
|
-
|
|
27071
|
-
|
|
27072
|
-
|
|
27280
|
+
try {
|
|
27281
|
+
const { getLlama } = await import("node-llama-cpp");
|
|
27282
|
+
_llama = await getLlama();
|
|
27283
|
+
_model = await _llama.loadModel({ modelPath });
|
|
27284
|
+
_context = await _model.createEmbeddingContext();
|
|
27285
|
+
process.stderr.write("[exed] Model loaded and ready.\n");
|
|
27286
|
+
} catch (err) {
|
|
27287
|
+
process.stderr.write(`[exed] Model load failed (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
27288
|
+
`);
|
|
27289
|
+
process.stderr.write("[exed] Running without embeddings \u2014 memories will have null vectors.\n");
|
|
27290
|
+
}
|
|
27073
27291
|
}
|
|
27074
27292
|
async function processQueue() {
|
|
27075
27293
|
if (_processing) return;
|
|
@@ -27252,43 +27470,19 @@ async function handleIngest(req) {
|
|
|
27252
27470
|
try {
|
|
27253
27471
|
if (!await ensureStoreForPolling()) return;
|
|
27254
27472
|
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
|
-
]
|
|
27473
|
+
await writeMemoryRecord({
|
|
27474
|
+
raw_text: req.rawText,
|
|
27475
|
+
agent_id: req.agentId,
|
|
27476
|
+
agent_role: req.agentRole,
|
|
27477
|
+
session_id: req.sessionId,
|
|
27478
|
+
tool_name: req.toolName,
|
|
27479
|
+
project_name: req.projectName,
|
|
27480
|
+
has_error: req.hasError,
|
|
27481
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27482
|
+
task_id: req.taskId ?? void 0,
|
|
27483
|
+
confidence: req.confidence,
|
|
27484
|
+
draft: req.draft,
|
|
27485
|
+
trajectory: req.trajectory ? JSON.stringify(req.trajectory) : void 0
|
|
27292
27486
|
});
|
|
27293
27487
|
_ingestCount++;
|
|
27294
27488
|
} catch (err) {
|
|
@@ -27336,10 +27530,38 @@ async function handleBatchWriteMemory(socket, requestId, entries) {
|
|
|
27336
27530
|
}
|
|
27337
27531
|
}
|
|
27338
27532
|
async function writeMemoryRecord(entry) {
|
|
27533
|
+
const id = randomUUID9();
|
|
27534
|
+
const now = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
27535
|
+
const governed = governMemoryRecord({
|
|
27536
|
+
id,
|
|
27537
|
+
agent_id: entry.agent_id,
|
|
27538
|
+
agent_role: entry.agent_role ?? "",
|
|
27539
|
+
session_id: entry.session_id,
|
|
27540
|
+
timestamp: now,
|
|
27541
|
+
tool_name: entry.tool_name,
|
|
27542
|
+
project_name: entry.project_name,
|
|
27543
|
+
has_error: !!entry.has_error,
|
|
27544
|
+
raw_text: entry.raw_text,
|
|
27545
|
+
vector: null,
|
|
27546
|
+
task_id: entry.task_id ?? null,
|
|
27547
|
+
importance: entry.importance ?? 5,
|
|
27548
|
+
confidence: entry.confidence ?? 0.7,
|
|
27549
|
+
draft: entry.draft ?? false,
|
|
27550
|
+
memory_type: entry.memory_type
|
|
27551
|
+
});
|
|
27552
|
+
if (governed.shouldDrop) return id;
|
|
27553
|
+
const record = governed.record;
|
|
27554
|
+
const duplicate = await findScopedDuplicate({
|
|
27555
|
+
contentHash: governed.contentHash,
|
|
27556
|
+
agentId: record.agent_id,
|
|
27557
|
+
projectName: record.project_name,
|
|
27558
|
+
memoryType: record.memory_type ?? "raw"
|
|
27559
|
+
});
|
|
27560
|
+
if (duplicate) return duplicate;
|
|
27339
27561
|
let vectorBlob = null;
|
|
27340
|
-
if (_context) {
|
|
27562
|
+
if (_context && !governed.skipEmbedding) {
|
|
27341
27563
|
try {
|
|
27342
|
-
const embedding = await _context.getEmbeddingFor(
|
|
27564
|
+
const embedding = await _context.getEmbeddingFor(record.raw_text);
|
|
27343
27565
|
const vector = Array.from(embedding.vector);
|
|
27344
27566
|
embedding.vector = null;
|
|
27345
27567
|
if (vector.length === EMBEDDING_DIM) {
|
|
@@ -27351,29 +27573,61 @@ async function writeMemoryRecord(entry) {
|
|
|
27351
27573
|
}
|
|
27352
27574
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
27353
27575
|
const client = getClient2();
|
|
27354
|
-
const
|
|
27355
|
-
const
|
|
27356
|
-
const
|
|
27576
|
+
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
27577
|
+
const version = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
27578
|
+
const hasVector = vectorBlob !== null;
|
|
27357
27579
|
await client.execute({
|
|
27358
|
-
sql: `INSERT INTO memories
|
|
27359
|
-
|
|
27360
|
-
|
|
27580
|
+
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
27581
|
+
(id, agent_id, agent_role, session_id, timestamp, tool_name, project_name,
|
|
27582
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
27583
|
+
confidence, last_accessed, draft, memory_type, trajectory, content_hash)
|
|
27584
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
27585
|
+
(id, agent_id, agent_role, session_id, timestamp, tool_name, project_name,
|
|
27586
|
+
has_error, raw_text, vector, version, task_id, importance, status,
|
|
27587
|
+
confidence, last_accessed, draft, memory_type, trajectory, content_hash)
|
|
27588
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)`,
|
|
27589
|
+
args: hasVector ? [
|
|
27361
27590
|
id,
|
|
27362
|
-
|
|
27363
|
-
|
|
27364
|
-
|
|
27365
|
-
|
|
27366
|
-
|
|
27367
|
-
|
|
27368
|
-
|
|
27369
|
-
|
|
27591
|
+
record.agent_id,
|
|
27592
|
+
record.agent_role,
|
|
27593
|
+
record.session_id,
|
|
27594
|
+
record.timestamp,
|
|
27595
|
+
record.tool_name,
|
|
27596
|
+
record.project_name,
|
|
27597
|
+
record.has_error ? 1 : 0,
|
|
27598
|
+
record.raw_text,
|
|
27370
27599
|
vectorBlob,
|
|
27371
|
-
|
|
27372
|
-
|
|
27373
|
-
|
|
27374
|
-
|
|
27600
|
+
version,
|
|
27601
|
+
record.task_id ?? null,
|
|
27602
|
+
record.importance ?? 5,
|
|
27603
|
+
record.confidence ?? 0.7,
|
|
27604
|
+
record.timestamp,
|
|
27605
|
+
record.draft ? 1 : 0,
|
|
27606
|
+
record.memory_type ?? "raw",
|
|
27607
|
+
entry.trajectory ?? null,
|
|
27608
|
+
governed.contentHash
|
|
27609
|
+
] : [
|
|
27610
|
+
id,
|
|
27611
|
+
record.agent_id,
|
|
27612
|
+
record.agent_role,
|
|
27613
|
+
record.session_id,
|
|
27614
|
+
record.timestamp,
|
|
27615
|
+
record.tool_name,
|
|
27616
|
+
record.project_name,
|
|
27617
|
+
record.has_error ? 1 : 0,
|
|
27618
|
+
record.raw_text,
|
|
27619
|
+
version,
|
|
27620
|
+
record.task_id ?? null,
|
|
27621
|
+
record.importance ?? 5,
|
|
27622
|
+
record.confidence ?? 0.7,
|
|
27623
|
+
record.timestamp,
|
|
27624
|
+
record.draft ? 1 : 0,
|
|
27625
|
+
record.memory_type ?? "raw",
|
|
27626
|
+
entry.trajectory ?? null,
|
|
27627
|
+
governed.contentHash
|
|
27375
27628
|
]
|
|
27376
27629
|
});
|
|
27630
|
+
schedulePostWriteMemoryHygiene([id]);
|
|
27377
27631
|
return id;
|
|
27378
27632
|
}
|
|
27379
27633
|
var MEMORY_DRAIN_INTERVAL_MS = 30 * 1e3;
|