@askexenow/exe-os 0.9.13 → 0.9.14
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 +31 -4
- package/dist/bin/backfill-responses.js +31 -4
- package/dist/bin/backfill-vectors.js +25 -4
- package/dist/bin/cleanup-stale-review-tasks.js +1 -1
- package/dist/bin/cli.js +55 -8
- package/dist/bin/exe-assign.js +31 -4
- package/dist/bin/exe-boot.js +25 -4
- package/dist/bin/exe-dispatch.js +1 -1
- package/dist/bin/exe-doctor.js +1 -1
- package/dist/bin/exe-export-behaviors.js +1 -1
- package/dist/bin/exe-forget.js +1 -1
- package/dist/bin/exe-gateway.js +447 -21
- package/dist/bin/exe-heartbeat.js +1 -1
- package/dist/bin/exe-kill.js +1 -1
- package/dist/bin/exe-launch-agent.js +1 -1
- package/dist/bin/exe-link.js +31 -4
- package/dist/bin/exe-pending-messages.js +1 -1
- package/dist/bin/exe-pending-notifications.js +1 -1
- package/dist/bin/exe-pending-reviews.js +1 -1
- package/dist/bin/exe-rename.js +31 -4
- package/dist/bin/exe-review.js +1 -1
- package/dist/bin/exe-search.js +31 -4
- package/dist/bin/exe-session-cleanup.js +25 -4
- package/dist/bin/exe-start-codex.js +1 -1
- package/dist/bin/exe-start-opencode.js +1 -1
- package/dist/bin/exe-status.js +1 -1
- package/dist/bin/exe-team.js +1 -1
- package/dist/bin/git-sweep.js +25 -4
- package/dist/bin/graph-backfill.js +1 -1
- package/dist/bin/graph-export.js +1 -1
- package/dist/bin/scan-tasks.js +25 -4
- package/dist/bin/setup.js +46 -8
- package/dist/bin/shard-migrate.js +1 -1
- package/dist/bin/wiki-sync.js +1 -1
- package/dist/gateway/index.js +128 -125
- package/dist/hooks/bug-report-worker.js +1 -1
- package/dist/hooks/codex-stop-task-finalizer.js +1 -1
- package/dist/hooks/commit-complete.js +25 -4
- package/dist/hooks/error-recall.js +31 -4
- package/dist/hooks/ingest-worker.js +25 -4
- package/dist/hooks/ingest.js +1 -1
- package/dist/hooks/instructions-loaded.js +31 -4
- package/dist/hooks/notification.js +31 -4
- package/dist/hooks/post-compact.js +31 -4
- package/dist/hooks/pre-compact.js +25 -4
- package/dist/hooks/pre-tool-use.js +31 -4
- package/dist/hooks/prompt-ingest-worker.js +25 -4
- package/dist/hooks/prompt-submit.js +25 -4
- package/dist/hooks/response-ingest-worker.js +25 -4
- package/dist/hooks/session-end.js +25 -4
- package/dist/hooks/session-start.js +31 -4
- package/dist/hooks/stop.js +25 -4
- package/dist/hooks/subagent-stop.js +31 -4
- package/dist/hooks/summary-worker.js +25 -4
- package/dist/index.js +128 -125
- package/dist/lib/cloud-sync.js +31 -4
- package/dist/lib/database.js +31 -4
- package/dist/lib/db-daemon-client.js +31 -3
- package/dist/lib/db.js +31 -4
- package/dist/lib/device-registry.js +31 -4
- package/dist/lib/embedder.js +30 -3
- package/dist/lib/exe-daemon-client.js +31 -3
- package/dist/lib/exe-daemon.js +1958 -153
- package/dist/lib/hybrid-search.js +31 -4
- package/dist/lib/schedules.js +1 -1
- package/dist/lib/store.js +1 -1
- package/dist/mcp/server.js +25 -4
- package/dist/runtime/index.js +25 -4
- package/dist/tui/App.js +34 -4
- package/package.json +1 -1
package/dist/lib/exe-daemon.js
CHANGED
|
@@ -400,7 +400,7 @@ var init_daemon_protocol = __esm({
|
|
|
400
400
|
});
|
|
401
401
|
|
|
402
402
|
// src/lib/daemon-auth.ts
|
|
403
|
-
import
|
|
403
|
+
import crypto2 from "crypto";
|
|
404
404
|
import path2 from "path";
|
|
405
405
|
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
406
406
|
function normalizeToken(token) {
|
|
@@ -419,7 +419,7 @@ function readDaemonToken() {
|
|
|
419
419
|
function ensureDaemonToken(seed) {
|
|
420
420
|
const existing = readDaemonToken();
|
|
421
421
|
if (existing) return existing;
|
|
422
|
-
const token = normalizeToken(seed) ??
|
|
422
|
+
const token = normalizeToken(seed) ?? crypto2.randomBytes(32).toString("hex");
|
|
423
423
|
ensurePrivateDirSync(EXE_AI_DIR);
|
|
424
424
|
writeFileSync(DAEMON_TOKEN_PATH, `${token}
|
|
425
425
|
`, "utf8");
|
|
@@ -976,8 +976,8 @@ function logQueue(msg) {
|
|
|
976
976
|
process.stderr.write(`[intercom-queue] ${msg}
|
|
977
977
|
`);
|
|
978
978
|
try {
|
|
979
|
-
const { appendFileSync:
|
|
980
|
-
|
|
979
|
+
const { appendFileSync: appendFileSync3 } = __require("fs");
|
|
980
|
+
appendFileSync3(INTERCOM_LOG, line);
|
|
981
981
|
} catch {
|
|
982
982
|
}
|
|
983
983
|
}
|
|
@@ -1635,7 +1635,7 @@ async function ensureCompatibilityViews(prisma) {
|
|
|
1635
1635
|
for (const mapping of VIEW_MAPPINGS) {
|
|
1636
1636
|
const relation = mapping.source.replace(/"/g, "");
|
|
1637
1637
|
const rows = await prisma.$queryRawUnsafe(
|
|
1638
|
-
"SELECT to_regclass($1) AS regclass",
|
|
1638
|
+
"SELECT to_regclass($1)::text AS regclass",
|
|
1639
1639
|
relation
|
|
1640
1640
|
);
|
|
1641
1641
|
if (!rows[0]?.regclass) {
|
|
@@ -1952,8 +1952,29 @@ function findPackageRoot() {
|
|
|
1952
1952
|
}
|
|
1953
1953
|
return null;
|
|
1954
1954
|
}
|
|
1955
|
+
function getAvailableMemoryGB() {
|
|
1956
|
+
if (process.platform === "darwin") {
|
|
1957
|
+
try {
|
|
1958
|
+
const { execSync: execSync12 } = __require("child_process");
|
|
1959
|
+
const vmstat = execSync12("vm_stat", { encoding: "utf8" });
|
|
1960
|
+
const pageSize = 16384;
|
|
1961
|
+
const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
|
|
1962
|
+
const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
|
|
1963
|
+
const free = vmstat.match(/Pages free:\s+(\d+)/);
|
|
1964
|
+
const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
|
|
1965
|
+
const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
|
|
1966
|
+
const freePages = free ? parseInt(free[1], 10) : 0;
|
|
1967
|
+
const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
|
|
1968
|
+
const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
|
|
1969
|
+
return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
|
|
1970
|
+
} catch {
|
|
1971
|
+
return os6.freemem() / (1024 * 1024 * 1024);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return os6.freemem() / (1024 * 1024 * 1024);
|
|
1975
|
+
}
|
|
1955
1976
|
function spawnDaemon() {
|
|
1956
|
-
const freeGB =
|
|
1977
|
+
const freeGB = getAvailableMemoryGB();
|
|
1957
1978
|
const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
|
|
1958
1979
|
if (totalGB <= 8) {
|
|
1959
1980
|
process.stderr.write(
|
|
@@ -1962,9 +1983,9 @@ function spawnDaemon() {
|
|
|
1962
1983
|
);
|
|
1963
1984
|
return;
|
|
1964
1985
|
}
|
|
1965
|
-
if (totalGB <= 16 && freeGB <
|
|
1986
|
+
if (totalGB <= 16 && freeGB < 2) {
|
|
1966
1987
|
process.stderr.write(
|
|
1967
|
-
`[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB
|
|
1988
|
+
`[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
|
|
1968
1989
|
`
|
|
1969
1990
|
);
|
|
1970
1991
|
return;
|
|
@@ -3546,6 +3567,27 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
3546
3567
|
import os7 from "os";
|
|
3547
3568
|
import path9 from "path";
|
|
3548
3569
|
import { jwtVerify, importSPKI } from "jose";
|
|
3570
|
+
function loadDeviceId() {
|
|
3571
|
+
const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
|
|
3572
|
+
try {
|
|
3573
|
+
if (existsSync9(deviceJsonPath)) {
|
|
3574
|
+
const data = JSON.parse(readFileSync8(deviceJsonPath, "utf8"));
|
|
3575
|
+
if (data.deviceId) return data.deviceId;
|
|
3576
|
+
}
|
|
3577
|
+
} catch {
|
|
3578
|
+
}
|
|
3579
|
+
try {
|
|
3580
|
+
if (existsSync9(DEVICE_ID_PATH)) {
|
|
3581
|
+
const id2 = readFileSync8(DEVICE_ID_PATH, "utf8").trim();
|
|
3582
|
+
if (id2) return id2;
|
|
3583
|
+
}
|
|
3584
|
+
} catch {
|
|
3585
|
+
}
|
|
3586
|
+
const id = randomUUID2();
|
|
3587
|
+
mkdirSync4(EXE_AI_DIR, { recursive: true });
|
|
3588
|
+
writeFileSync6(DEVICE_ID_PATH, id, "utf8");
|
|
3589
|
+
return id;
|
|
3590
|
+
}
|
|
3549
3591
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
3550
3592
|
var init_license = __esm({
|
|
3551
3593
|
"src/lib/license.ts"() {
|
|
@@ -3676,7 +3718,7 @@ var init_task_scope = __esm({
|
|
|
3676
3718
|
});
|
|
3677
3719
|
|
|
3678
3720
|
// src/lib/notifications.ts
|
|
3679
|
-
import
|
|
3721
|
+
import crypto3 from "crypto";
|
|
3680
3722
|
import path11 from "path";
|
|
3681
3723
|
import os8 from "os";
|
|
3682
3724
|
import {
|
|
@@ -3689,7 +3731,7 @@ import {
|
|
|
3689
3731
|
async function writeNotification(notification) {
|
|
3690
3732
|
try {
|
|
3691
3733
|
const client = getClient();
|
|
3692
|
-
const id =
|
|
3734
|
+
const id = crypto3.randomUUID();
|
|
3693
3735
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3694
3736
|
const sessionScope = notification.sessionScope === void 0 ? getCurrentSessionScope() : notification.sessionScope;
|
|
3695
3737
|
await client.execute({
|
|
@@ -3745,7 +3787,7 @@ __export(session_kill_telemetry_exports, {
|
|
|
3745
3787
|
recordSessionKill: () => recordSessionKill,
|
|
3746
3788
|
sumTokensSavedSince: () => sumTokensSavedSince
|
|
3747
3789
|
});
|
|
3748
|
-
import
|
|
3790
|
+
import crypto4 from "crypto";
|
|
3749
3791
|
async function recordSessionKill(input) {
|
|
3750
3792
|
try {
|
|
3751
3793
|
const client = getClient();
|
|
@@ -3755,7 +3797,7 @@ async function recordSessionKill(input) {
|
|
|
3755
3797
|
ticks_idle, estimated_tokens_saved)
|
|
3756
3798
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
3757
3799
|
args: [
|
|
3758
|
-
|
|
3800
|
+
crypto4.randomUUID(),
|
|
3759
3801
|
input.sessionName,
|
|
3760
3802
|
input.agentId,
|
|
3761
3803
|
(/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3996,7 +4038,7 @@ var init_session_scope = __esm({
|
|
|
3996
4038
|
});
|
|
3997
4039
|
|
|
3998
4040
|
// src/lib/tasks-crud.ts
|
|
3999
|
-
import
|
|
4041
|
+
import crypto5 from "crypto";
|
|
4000
4042
|
import path13 from "path";
|
|
4001
4043
|
import os9 from "os";
|
|
4002
4044
|
import { execSync as execSync6 } from "child_process";
|
|
@@ -4117,7 +4159,7 @@ async function resolveTask(client, identifier, scopeSession) {
|
|
|
4117
4159
|
}
|
|
4118
4160
|
async function createTaskCore(input) {
|
|
4119
4161
|
const client = getClient();
|
|
4120
|
-
const id =
|
|
4162
|
+
const id = crypto5.randomUUID();
|
|
4121
4163
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4122
4164
|
const slug = slugify(input.title);
|
|
4123
4165
|
let earlySessionScope = null;
|
|
@@ -5063,10 +5105,10 @@ var init_tasks_notify = __esm({
|
|
|
5063
5105
|
});
|
|
5064
5106
|
|
|
5065
5107
|
// src/lib/behaviors.ts
|
|
5066
|
-
import
|
|
5108
|
+
import crypto6 from "crypto";
|
|
5067
5109
|
async function storeBehavior(opts) {
|
|
5068
5110
|
const client = getClient();
|
|
5069
|
-
const id =
|
|
5111
|
+
const id = crypto6.randomUUID();
|
|
5070
5112
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5071
5113
|
await client.execute({
|
|
5072
5114
|
sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, priority, content, active, created_at, updated_at)
|
|
@@ -5095,7 +5137,7 @@ __export(skill_learning_exports, {
|
|
|
5095
5137
|
storeTrajectory: () => storeTrajectory,
|
|
5096
5138
|
sweepTrajectories: () => sweepTrajectories
|
|
5097
5139
|
});
|
|
5098
|
-
import
|
|
5140
|
+
import crypto7 from "crypto";
|
|
5099
5141
|
async function extractTrajectory(taskId, agentId) {
|
|
5100
5142
|
const client = getClient();
|
|
5101
5143
|
const result = await client.execute({
|
|
@@ -5124,11 +5166,11 @@ async function extractTrajectory(taskId, agentId) {
|
|
|
5124
5166
|
return signature;
|
|
5125
5167
|
}
|
|
5126
5168
|
function hashSignature(signature) {
|
|
5127
|
-
return
|
|
5169
|
+
return crypto7.createHash("sha256").update(signature.join("|")).digest("hex").slice(0, 16);
|
|
5128
5170
|
}
|
|
5129
5171
|
async function storeTrajectory(opts) {
|
|
5130
5172
|
const client = getClient();
|
|
5131
|
-
const id =
|
|
5173
|
+
const id = crypto7.randomUUID();
|
|
5132
5174
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5133
5175
|
const signatureHash = hashSignature(opts.signature);
|
|
5134
5176
|
await client.execute({
|
|
@@ -7633,6 +7675,247 @@ var init_daemon_orchestration = __esm({
|
|
|
7633
7675
|
}
|
|
7634
7676
|
});
|
|
7635
7677
|
|
|
7678
|
+
// src/lib/projection-worker.ts
|
|
7679
|
+
var projection_worker_exports = {};
|
|
7680
|
+
__export(projection_worker_exports, {
|
|
7681
|
+
processProjectionBatch: () => processProjectionBatch,
|
|
7682
|
+
projectionHandlersForTests: () => projectionHandlersForTests,
|
|
7683
|
+
resetProjectionWorkerForTests: () => resetProjectionWorkerForTests,
|
|
7684
|
+
setProjectionWorkerPrismaClientForTests: () => setProjectionWorkerPrismaClientForTests,
|
|
7685
|
+
startProjectionWorker: () => startProjectionWorker,
|
|
7686
|
+
stopProjectionWorker: () => stopProjectionWorker
|
|
7687
|
+
});
|
|
7688
|
+
import os12 from "os";
|
|
7689
|
+
import path19 from "path";
|
|
7690
|
+
import { createRequire as createRequire3 } from "module";
|
|
7691
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
7692
|
+
function loadPrisma() {
|
|
7693
|
+
if (!prismaPromise) {
|
|
7694
|
+
prismaPromise = (async () => {
|
|
7695
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
7696
|
+
if (explicitPath) {
|
|
7697
|
+
const mod2 = await import(pathToFileURL3(explicitPath).href);
|
|
7698
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
7699
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
7700
|
+
return new Ctor2();
|
|
7701
|
+
}
|
|
7702
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path19.join(os12.homedir(), "exe-db");
|
|
7703
|
+
const req = createRequire3(path19.join(exeDbRoot, "package.json"));
|
|
7704
|
+
const entry = req.resolve("@prisma/client");
|
|
7705
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
7706
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
7707
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
7708
|
+
return new Ctor();
|
|
7709
|
+
})();
|
|
7710
|
+
}
|
|
7711
|
+
return prismaPromise;
|
|
7712
|
+
}
|
|
7713
|
+
function setProjectionWorkerPrismaClientForTests(client) {
|
|
7714
|
+
prismaPromise = client ? Promise.resolve(client) : null;
|
|
7715
|
+
}
|
|
7716
|
+
function resetProjectionWorkerForTests() {
|
|
7717
|
+
running = false;
|
|
7718
|
+
if (pollTimer) {
|
|
7719
|
+
clearTimeout(pollTimer);
|
|
7720
|
+
pollTimer = null;
|
|
7721
|
+
}
|
|
7722
|
+
prismaPromise = null;
|
|
7723
|
+
}
|
|
7724
|
+
async function processBatch() {
|
|
7725
|
+
const prisma = await loadPrisma();
|
|
7726
|
+
const events = await prisma.$queryRawUnsafe(
|
|
7727
|
+
`SELECT "id", "source", "source_id", "event_type", "payload", "metadata", "timestamp"
|
|
7728
|
+
FROM "raw"."raw_events"
|
|
7729
|
+
WHERE "processed_at" IS NULL
|
|
7730
|
+
ORDER BY "timestamp" ASC
|
|
7731
|
+
LIMIT $1`,
|
|
7732
|
+
BATCH_SIZE
|
|
7733
|
+
);
|
|
7734
|
+
if (events.length === 0) return 0;
|
|
7735
|
+
let processed = 0;
|
|
7736
|
+
for (const event of events) {
|
|
7737
|
+
try {
|
|
7738
|
+
const handler = projectionHandlers[event.source] ?? defaultHandler;
|
|
7739
|
+
const result = await handler(event, prisma);
|
|
7740
|
+
await prisma.$executeRawUnsafe(
|
|
7741
|
+
`UPDATE "raw"."raw_events"
|
|
7742
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
7743
|
+
WHERE "id" = $2`,
|
|
7744
|
+
JSON.stringify({ targets: result.targets, skipped: result.skipped }),
|
|
7745
|
+
event.id
|
|
7746
|
+
);
|
|
7747
|
+
processed++;
|
|
7748
|
+
} catch (err) {
|
|
7749
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7750
|
+
process.stderr.write(
|
|
7751
|
+
`[projection-worker] Error processing event ${event.id}: ${message}
|
|
7752
|
+
`
|
|
7753
|
+
);
|
|
7754
|
+
await prisma.$executeRawUnsafe(
|
|
7755
|
+
`UPDATE "raw"."raw_events"
|
|
7756
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
7757
|
+
WHERE "id" = $2`,
|
|
7758
|
+
JSON.stringify({ error: message }),
|
|
7759
|
+
event.id
|
|
7760
|
+
);
|
|
7761
|
+
}
|
|
7762
|
+
}
|
|
7763
|
+
return processed;
|
|
7764
|
+
}
|
|
7765
|
+
function startProjectionWorker() {
|
|
7766
|
+
if (running) return;
|
|
7767
|
+
running = true;
|
|
7768
|
+
process.stderr.write("[projection-worker] Starting...\n");
|
|
7769
|
+
const tick = async () => {
|
|
7770
|
+
if (!running) return;
|
|
7771
|
+
try {
|
|
7772
|
+
const count = await processBatch();
|
|
7773
|
+
if (count > 0) {
|
|
7774
|
+
process.stderr.write(`[projection-worker] Processed ${count} events.
|
|
7775
|
+
`);
|
|
7776
|
+
}
|
|
7777
|
+
} catch (err) {
|
|
7778
|
+
process.stderr.write(
|
|
7779
|
+
`[projection-worker] Poll error: ${err instanceof Error ? err.message : String(err)}
|
|
7780
|
+
`
|
|
7781
|
+
);
|
|
7782
|
+
}
|
|
7783
|
+
if (running) {
|
|
7784
|
+
pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
|
|
7785
|
+
}
|
|
7786
|
+
};
|
|
7787
|
+
void tick();
|
|
7788
|
+
}
|
|
7789
|
+
function stopProjectionWorker() {
|
|
7790
|
+
running = false;
|
|
7791
|
+
if (pollTimer) {
|
|
7792
|
+
clearTimeout(pollTimer);
|
|
7793
|
+
pollTimer = null;
|
|
7794
|
+
}
|
|
7795
|
+
process.stderr.write("[projection-worker] Stopped.\n");
|
|
7796
|
+
}
|
|
7797
|
+
async function processProjectionBatch() {
|
|
7798
|
+
return processBatch();
|
|
7799
|
+
}
|
|
7800
|
+
var prismaPromise, projectionHandlers, projectionHandlersForTests, defaultHandler, BATCH_SIZE, POLL_INTERVAL_MS, running, pollTimer;
|
|
7801
|
+
var init_projection_worker = __esm({
|
|
7802
|
+
"src/lib/projection-worker.ts"() {
|
|
7803
|
+
"use strict";
|
|
7804
|
+
prismaPromise = null;
|
|
7805
|
+
projectionHandlers = {
|
|
7806
|
+
async whatsapp(event, prisma) {
|
|
7807
|
+
const targets = [];
|
|
7808
|
+
const payload = event.payload;
|
|
7809
|
+
await prisma.$executeRawUnsafe(
|
|
7810
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
7811
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
7812
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7813
|
+
crypto.randomUUID(),
|
|
7814
|
+
"system",
|
|
7815
|
+
"ingest",
|
|
7816
|
+
"projection-worker",
|
|
7817
|
+
"ingest_raw",
|
|
7818
|
+
"exe-os",
|
|
7819
|
+
JSON.stringify(payload),
|
|
7820
|
+
"raw",
|
|
7821
|
+
"whatsapp",
|
|
7822
|
+
event.event_type
|
|
7823
|
+
);
|
|
7824
|
+
targets.push("memory");
|
|
7825
|
+
return { targets };
|
|
7826
|
+
},
|
|
7827
|
+
async shopify(event, prisma) {
|
|
7828
|
+
const targets = [];
|
|
7829
|
+
const payload = event.payload;
|
|
7830
|
+
await prisma.$executeRawUnsafe(
|
|
7831
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
7832
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
7833
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7834
|
+
crypto.randomUUID(),
|
|
7835
|
+
"system",
|
|
7836
|
+
"ingest",
|
|
7837
|
+
"projection-worker",
|
|
7838
|
+
"ingest_raw",
|
|
7839
|
+
"exe-os",
|
|
7840
|
+
JSON.stringify(payload),
|
|
7841
|
+
"raw",
|
|
7842
|
+
"shopify",
|
|
7843
|
+
event.event_type
|
|
7844
|
+
);
|
|
7845
|
+
targets.push("memory");
|
|
7846
|
+
return { targets };
|
|
7847
|
+
},
|
|
7848
|
+
async cloud_sync(event, prisma) {
|
|
7849
|
+
const targets = [];
|
|
7850
|
+
const payload = event.payload;
|
|
7851
|
+
await prisma.$executeRawUnsafe(
|
|
7852
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
7853
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
7854
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7855
|
+
payload.id ?? crypto.randomUUID(),
|
|
7856
|
+
payload.agent_id ?? "system",
|
|
7857
|
+
payload.agent_role ?? "ingest",
|
|
7858
|
+
payload.session_id ?? "projection-worker",
|
|
7859
|
+
payload.tool_name ?? "cloud_sync",
|
|
7860
|
+
payload.project_name ?? "exe-os",
|
|
7861
|
+
payload.raw_text ?? JSON.stringify(payload),
|
|
7862
|
+
payload.memory_type ?? "raw",
|
|
7863
|
+
"cloud_sync",
|
|
7864
|
+
event.event_type,
|
|
7865
|
+
payload.domain ?? null
|
|
7866
|
+
);
|
|
7867
|
+
targets.push("memory");
|
|
7868
|
+
return { targets };
|
|
7869
|
+
},
|
|
7870
|
+
async external_agent(event, prisma) {
|
|
7871
|
+
const targets = [];
|
|
7872
|
+
const payload = event.payload;
|
|
7873
|
+
await prisma.$executeRawUnsafe(
|
|
7874
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
7875
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
7876
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7877
|
+
crypto.randomUUID(),
|
|
7878
|
+
"system",
|
|
7879
|
+
"ingest",
|
|
7880
|
+
"projection-worker",
|
|
7881
|
+
"ingest_raw",
|
|
7882
|
+
"exe-os",
|
|
7883
|
+
JSON.stringify(payload),
|
|
7884
|
+
"raw",
|
|
7885
|
+
"external_agent",
|
|
7886
|
+
event.event_type,
|
|
7887
|
+
payload.domain ?? null
|
|
7888
|
+
);
|
|
7889
|
+
targets.push("memory");
|
|
7890
|
+
return { targets };
|
|
7891
|
+
}
|
|
7892
|
+
};
|
|
7893
|
+
projectionHandlersForTests = projectionHandlers;
|
|
7894
|
+
defaultHandler = async (event, prisma) => {
|
|
7895
|
+
await prisma.$executeRawUnsafe(
|
|
7896
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
7897
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
7898
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7899
|
+
crypto.randomUUID(),
|
|
7900
|
+
"system",
|
|
7901
|
+
"ingest",
|
|
7902
|
+
"projection-worker",
|
|
7903
|
+
"ingest_raw",
|
|
7904
|
+
"exe-os",
|
|
7905
|
+
JSON.stringify(event.payload),
|
|
7906
|
+
"raw",
|
|
7907
|
+
event.source,
|
|
7908
|
+
event.event_type
|
|
7909
|
+
);
|
|
7910
|
+
return { targets: ["memory"] };
|
|
7911
|
+
};
|
|
7912
|
+
BATCH_SIZE = 50;
|
|
7913
|
+
POLL_INTERVAL_MS = 1e4;
|
|
7914
|
+
running = false;
|
|
7915
|
+
pollTimer = null;
|
|
7916
|
+
}
|
|
7917
|
+
});
|
|
7918
|
+
|
|
7636
7919
|
// src/lib/shard-manager.ts
|
|
7637
7920
|
var shard_manager_exports = {};
|
|
7638
7921
|
__export(shard_manager_exports, {
|
|
@@ -7647,7 +7930,7 @@ __export(shard_manager_exports, {
|
|
|
7647
7930
|
listShards: () => listShards,
|
|
7648
7931
|
shardExists: () => shardExists
|
|
7649
7932
|
});
|
|
7650
|
-
import
|
|
7933
|
+
import path20 from "path";
|
|
7651
7934
|
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
7652
7935
|
import { createClient as createClient2 } from "@libsql/client";
|
|
7653
7936
|
function initShardManager(encryptionKey) {
|
|
@@ -7682,7 +7965,7 @@ function getShardClient(projectName) {
|
|
|
7682
7965
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
7683
7966
|
evictLRU();
|
|
7684
7967
|
}
|
|
7685
|
-
const dbPath =
|
|
7968
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
7686
7969
|
const client = createClient2({
|
|
7687
7970
|
url: `file:${dbPath}`,
|
|
7688
7971
|
encryptionKey: _encryptionKey
|
|
@@ -7693,7 +7976,7 @@ function getShardClient(projectName) {
|
|
|
7693
7976
|
}
|
|
7694
7977
|
function shardExists(projectName) {
|
|
7695
7978
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
7696
|
-
return existsSync17(
|
|
7979
|
+
return existsSync17(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
7697
7980
|
}
|
|
7698
7981
|
function listShards() {
|
|
7699
7982
|
if (!existsSync17(SHARDS_DIR)) return [];
|
|
@@ -7943,7 +8226,7 @@ var init_shard_manager = __esm({
|
|
|
7943
8226
|
"src/lib/shard-manager.ts"() {
|
|
7944
8227
|
"use strict";
|
|
7945
8228
|
init_config();
|
|
7946
|
-
SHARDS_DIR =
|
|
8229
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
7947
8230
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
7948
8231
|
MAX_OPEN_SHARDS = 10;
|
|
7949
8232
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -7966,13 +8249,13 @@ __export(keychain_exports, {
|
|
|
7966
8249
|
});
|
|
7967
8250
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
7968
8251
|
import { existsSync as existsSync18 } from "fs";
|
|
7969
|
-
import
|
|
7970
|
-
import
|
|
8252
|
+
import path21 from "path";
|
|
8253
|
+
import os13 from "os";
|
|
7971
8254
|
function getKeyDir() {
|
|
7972
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
8255
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path21.join(os13.homedir(), ".exe-os");
|
|
7973
8256
|
}
|
|
7974
8257
|
function getKeyPath() {
|
|
7975
|
-
return
|
|
8258
|
+
return path21.join(getKeyDir(), "master.key");
|
|
7976
8259
|
}
|
|
7977
8260
|
async function tryKeytar() {
|
|
7978
8261
|
try {
|
|
@@ -7995,7 +8278,7 @@ async function getMasterKey() {
|
|
|
7995
8278
|
const keyPath = getKeyPath();
|
|
7996
8279
|
if (!existsSync18(keyPath)) {
|
|
7997
8280
|
process.stderr.write(
|
|
7998
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
8281
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os13.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
7999
8282
|
`
|
|
8000
8283
|
);
|
|
8001
8284
|
return null;
|
|
@@ -9215,16 +9498,16 @@ async function pushToWiki(consolidation, config) {
|
|
|
9215
9498
|
const contentLines = consolidation.rawText.split("\n").filter((l) => l.trim() && !l.startsWith("CONSOLIDATION") && !l.match(/^[A-Z\s]+:$/)).join(" ");
|
|
9216
9499
|
const keywords = contentLines.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
|
|
9217
9500
|
let bestMatch = null;
|
|
9218
|
-
for (const
|
|
9219
|
-
if (!
|
|
9220
|
-
const titleWords =
|
|
9501
|
+
for (const doc2 of docs) {
|
|
9502
|
+
if (!doc2.id || !doc2.title) continue;
|
|
9503
|
+
const titleWords = doc2.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
|
|
9221
9504
|
if (titleWords.length === 0) continue;
|
|
9222
9505
|
const matchCount = titleWords.filter(
|
|
9223
9506
|
(tw) => keywords.some((k) => k.includes(tw) || tw.includes(k))
|
|
9224
9507
|
).length;
|
|
9225
9508
|
const score = matchCount / titleWords.length;
|
|
9226
9509
|
if (score > (bestMatch?.score ?? 0)) {
|
|
9227
|
-
bestMatch = { id:
|
|
9510
|
+
bestMatch = { id: doc2.id, title: doc2.title, score };
|
|
9228
9511
|
}
|
|
9229
9512
|
}
|
|
9230
9513
|
if (bestMatch && bestMatch.score >= config.wikiAutoUpdateThreshold) {
|
|
@@ -9453,10 +9736,10 @@ async function disposeEmbedder() {
|
|
|
9453
9736
|
async function embedDirect(text) {
|
|
9454
9737
|
const llamaCpp = await import("node-llama-cpp");
|
|
9455
9738
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
9456
|
-
const { existsSync:
|
|
9457
|
-
const
|
|
9458
|
-
const modelPath =
|
|
9459
|
-
if (!
|
|
9739
|
+
const { existsSync: existsSync23 } = await import("fs");
|
|
9740
|
+
const path28 = await import("path");
|
|
9741
|
+
const modelPath = path28.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
9742
|
+
if (!existsSync23(modelPath)) {
|
|
9460
9743
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
9461
9744
|
}
|
|
9462
9745
|
const llama = await llamaCpp.getLlama();
|
|
@@ -9484,69 +9767,1554 @@ var init_embedder = __esm({
|
|
|
9484
9767
|
}
|
|
9485
9768
|
});
|
|
9486
9769
|
|
|
9487
|
-
// src/lib/
|
|
9488
|
-
import
|
|
9489
|
-
function
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
// setParentNodes
|
|
9496
|
-
fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
9770
|
+
// src/lib/crypto.ts
|
|
9771
|
+
import crypto8 from "crypto";
|
|
9772
|
+
function initSyncCrypto(masterKey) {
|
|
9773
|
+
if (masterKey.length !== 32) {
|
|
9774
|
+
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
9775
|
+
}
|
|
9776
|
+
_syncKey = Buffer.from(
|
|
9777
|
+
crypto8.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
9497
9778
|
);
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9779
|
+
}
|
|
9780
|
+
function isSyncCryptoInitialized() {
|
|
9781
|
+
return _syncKey !== null;
|
|
9782
|
+
}
|
|
9783
|
+
function requireSyncKey() {
|
|
9784
|
+
if (!_syncKey) {
|
|
9785
|
+
throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
|
|
9786
|
+
}
|
|
9787
|
+
return _syncKey;
|
|
9788
|
+
}
|
|
9789
|
+
function encryptSyncBlob(data) {
|
|
9790
|
+
const key = requireSyncKey();
|
|
9791
|
+
const iv = crypto8.randomBytes(IV_LENGTH);
|
|
9792
|
+
const cipher = crypto8.createCipheriv(ALGORITHM, key, iv);
|
|
9793
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
9794
|
+
const tag = cipher.getAuthTag();
|
|
9795
|
+
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
9796
|
+
}
|
|
9797
|
+
function decryptSyncBlob(ciphertext) {
|
|
9798
|
+
const key = requireSyncKey();
|
|
9799
|
+
const combined = Buffer.from(ciphertext, "base64");
|
|
9800
|
+
if (combined.length < IV_LENGTH + TAG_LENGTH) {
|
|
9801
|
+
throw new Error("Sync blob too short to contain IV + tag");
|
|
9802
|
+
}
|
|
9803
|
+
const iv = combined.subarray(0, IV_LENGTH);
|
|
9804
|
+
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
9805
|
+
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
9806
|
+
const decipher = crypto8.createDecipheriv(ALGORITHM, key, iv);
|
|
9807
|
+
decipher.setAuthTag(tag);
|
|
9808
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
9809
|
+
}
|
|
9810
|
+
var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
|
|
9811
|
+
var init_crypto = __esm({
|
|
9812
|
+
"src/lib/crypto.ts"() {
|
|
9813
|
+
"use strict";
|
|
9814
|
+
ALGORITHM = "aes-256-gcm";
|
|
9815
|
+
IV_LENGTH = 12;
|
|
9816
|
+
TAG_LENGTH = 16;
|
|
9817
|
+
SYNC_HKDF_INFO = "exe-mem-sync-v2";
|
|
9818
|
+
_syncKey = null;
|
|
9503
9819
|
}
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9820
|
+
});
|
|
9821
|
+
|
|
9822
|
+
// src/lib/compress.ts
|
|
9823
|
+
import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
|
|
9824
|
+
function compress(input) {
|
|
9825
|
+
if (input.length === 0) return Buffer.alloc(0);
|
|
9826
|
+
return brotliCompressSync(input, {
|
|
9827
|
+
params: {
|
|
9828
|
+
[constants.BROTLI_PARAM_QUALITY]: 4
|
|
9829
|
+
}
|
|
9830
|
+
});
|
|
9831
|
+
}
|
|
9832
|
+
function decompress(input) {
|
|
9833
|
+
if (input.length === 0) return Buffer.alloc(0);
|
|
9834
|
+
return brotliDecompressSync(input);
|
|
9835
|
+
}
|
|
9836
|
+
var init_compress = __esm({
|
|
9837
|
+
"src/lib/compress.ts"() {
|
|
9838
|
+
"use strict";
|
|
9509
9839
|
}
|
|
9510
|
-
|
|
9511
|
-
|
|
9840
|
+
});
|
|
9841
|
+
|
|
9842
|
+
// src/lib/crdt-sync.ts
|
|
9843
|
+
var crdt_sync_exports = {};
|
|
9844
|
+
__export(crdt_sync_exports, {
|
|
9845
|
+
_setStatePath: () => _setStatePath,
|
|
9846
|
+
applyRemoteUpdate: () => applyRemoteUpdate,
|
|
9847
|
+
destroyCrdtDoc: () => destroyCrdtDoc,
|
|
9848
|
+
getDiffUpdate: () => getDiffUpdate,
|
|
9849
|
+
getFullState: () => getFullState,
|
|
9850
|
+
getStateVector: () => getStateVector,
|
|
9851
|
+
importExistingBehaviors: () => importExistingBehaviors,
|
|
9852
|
+
importExistingMemories: () => importExistingMemories,
|
|
9853
|
+
initCrdtDoc: () => initCrdtDoc,
|
|
9854
|
+
isCrdtSyncEnabled: () => isCrdtSyncEnabled,
|
|
9855
|
+
onUpdate: () => onUpdate,
|
|
9856
|
+
readAllBehaviors: () => readAllBehaviors,
|
|
9857
|
+
readAllMemories: () => readAllMemories,
|
|
9858
|
+
rebuildFromDb: () => rebuildFromDb
|
|
9859
|
+
});
|
|
9860
|
+
import * as Y from "yjs";
|
|
9861
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync19, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7 } from "fs";
|
|
9862
|
+
import path22 from "path";
|
|
9863
|
+
import { homedir as homedir2 } from "os";
|
|
9864
|
+
function getStatePath() {
|
|
9865
|
+
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
9866
|
+
}
|
|
9867
|
+
function _setStatePath(p) {
|
|
9868
|
+
_statePathOverride = p;
|
|
9869
|
+
}
|
|
9870
|
+
function initCrdtDoc() {
|
|
9871
|
+
if (doc) return doc;
|
|
9872
|
+
doc = new Y.Doc();
|
|
9873
|
+
const sp = getStatePath();
|
|
9874
|
+
if (existsSync19(sp)) {
|
|
9875
|
+
try {
|
|
9876
|
+
const state = readFileSync15(sp);
|
|
9877
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
9878
|
+
} catch {
|
|
9879
|
+
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
9880
|
+
try {
|
|
9881
|
+
unlinkSync7(sp);
|
|
9882
|
+
} catch {
|
|
9883
|
+
}
|
|
9884
|
+
rebuildFromDb().catch((err) => {
|
|
9885
|
+
console.warn("[crdt-sync] rebuild from DB failed:", err);
|
|
9886
|
+
});
|
|
9887
|
+
}
|
|
9512
9888
|
}
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9889
|
+
doc.on("update", () => {
|
|
9890
|
+
persistState();
|
|
9891
|
+
});
|
|
9892
|
+
return doc;
|
|
9893
|
+
}
|
|
9894
|
+
function getMemoriesMap() {
|
|
9895
|
+
const d = initCrdtDoc();
|
|
9896
|
+
return d.getMap("memories");
|
|
9897
|
+
}
|
|
9898
|
+
function getBehaviorsMap() {
|
|
9899
|
+
const d = initCrdtDoc();
|
|
9900
|
+
return d.getMap("behaviors");
|
|
9901
|
+
}
|
|
9902
|
+
function applyRemoteUpdate(update) {
|
|
9903
|
+
const d = initCrdtDoc();
|
|
9904
|
+
Y.applyUpdate(d, update);
|
|
9905
|
+
}
|
|
9906
|
+
function getFullState() {
|
|
9907
|
+
const d = initCrdtDoc();
|
|
9908
|
+
return Y.encodeStateAsUpdate(d);
|
|
9909
|
+
}
|
|
9910
|
+
function getDiffUpdate(remoteStateVector) {
|
|
9911
|
+
const d = initCrdtDoc();
|
|
9912
|
+
return Y.encodeStateAsUpdate(d, remoteStateVector);
|
|
9913
|
+
}
|
|
9914
|
+
function getStateVector() {
|
|
9915
|
+
const d = initCrdtDoc();
|
|
9916
|
+
return Y.encodeStateVector(d);
|
|
9917
|
+
}
|
|
9918
|
+
function importExistingMemories(memories) {
|
|
9919
|
+
const map = getMemoriesMap();
|
|
9920
|
+
const d = initCrdtDoc();
|
|
9921
|
+
let imported = 0;
|
|
9922
|
+
d.transact(() => {
|
|
9923
|
+
for (const mem of memories) {
|
|
9924
|
+
if (!mem.id) continue;
|
|
9925
|
+
if (map.has(mem.id)) continue;
|
|
9926
|
+
const entry = new Y.Map();
|
|
9927
|
+
entry.set("id", mem.id);
|
|
9928
|
+
entry.set("agent_id", mem.agent_id ?? null);
|
|
9929
|
+
entry.set("agent_role", mem.agent_role ?? null);
|
|
9930
|
+
entry.set("session_id", mem.session_id ?? null);
|
|
9931
|
+
entry.set("timestamp", mem.timestamp ?? null);
|
|
9932
|
+
entry.set("tool_name", mem.tool_name ?? null);
|
|
9933
|
+
entry.set("project_name", mem.project_name ?? null);
|
|
9934
|
+
entry.set("has_error", mem.has_error ?? 0);
|
|
9935
|
+
entry.set("raw_text", mem.raw_text ?? "");
|
|
9936
|
+
entry.set("version", mem.version ?? 0);
|
|
9937
|
+
entry.set("author_device_id", mem.author_device_id ?? null);
|
|
9938
|
+
entry.set("scope", mem.scope ?? "business");
|
|
9939
|
+
map.set(mem.id, entry);
|
|
9940
|
+
imported++;
|
|
9516
9941
|
}
|
|
9517
|
-
|
|
9518
|
-
|
|
9942
|
+
});
|
|
9943
|
+
return imported;
|
|
9944
|
+
}
|
|
9945
|
+
function importExistingBehaviors(behaviors) {
|
|
9946
|
+
const map = getBehaviorsMap();
|
|
9947
|
+
const d = initCrdtDoc();
|
|
9948
|
+
let imported = 0;
|
|
9949
|
+
d.transact(() => {
|
|
9950
|
+
for (const beh of behaviors) {
|
|
9951
|
+
if (!beh.id) continue;
|
|
9952
|
+
if (map.has(beh.id)) continue;
|
|
9953
|
+
const entry = new Y.Map();
|
|
9954
|
+
entry.set("id", beh.id);
|
|
9955
|
+
entry.set("agent_id", beh.agent_id ?? null);
|
|
9956
|
+
entry.set("project_name", beh.project_name ?? null);
|
|
9957
|
+
entry.set("domain", beh.domain ?? null);
|
|
9958
|
+
entry.set("content", beh.content ?? null);
|
|
9959
|
+
entry.set("active", beh.active ?? 1);
|
|
9960
|
+
entry.set("priority", beh.priority ?? "p1");
|
|
9961
|
+
entry.set("created_at", beh.created_at ?? null);
|
|
9962
|
+
entry.set("updated_at", beh.updated_at ?? null);
|
|
9963
|
+
map.set(beh.id, entry);
|
|
9964
|
+
imported++;
|
|
9519
9965
|
}
|
|
9520
|
-
|
|
9521
|
-
|
|
9966
|
+
});
|
|
9967
|
+
return imported;
|
|
9968
|
+
}
|
|
9969
|
+
function readAllMemories() {
|
|
9970
|
+
const map = getMemoriesMap();
|
|
9971
|
+
const records = [];
|
|
9972
|
+
map.forEach((entry, id) => {
|
|
9973
|
+
records.push({
|
|
9974
|
+
id,
|
|
9975
|
+
agent_id: entry.get("agent_id"),
|
|
9976
|
+
agent_role: entry.get("agent_role"),
|
|
9977
|
+
session_id: entry.get("session_id"),
|
|
9978
|
+
timestamp: entry.get("timestamp"),
|
|
9979
|
+
tool_name: entry.get("tool_name"),
|
|
9980
|
+
project_name: entry.get("project_name"),
|
|
9981
|
+
has_error: entry.get("has_error"),
|
|
9982
|
+
raw_text: entry.get("raw_text"),
|
|
9983
|
+
version: entry.get("version"),
|
|
9984
|
+
author_device_id: entry.get("author_device_id"),
|
|
9985
|
+
scope: entry.get("scope")
|
|
9986
|
+
});
|
|
9987
|
+
});
|
|
9988
|
+
return records;
|
|
9989
|
+
}
|
|
9990
|
+
function readAllBehaviors() {
|
|
9991
|
+
const map = getBehaviorsMap();
|
|
9992
|
+
const records = [];
|
|
9993
|
+
map.forEach((entry, id) => {
|
|
9994
|
+
records.push({
|
|
9995
|
+
id,
|
|
9996
|
+
agent_id: entry.get("agent_id"),
|
|
9997
|
+
project_name: entry.get("project_name"),
|
|
9998
|
+
domain: entry.get("domain"),
|
|
9999
|
+
content: entry.get("content"),
|
|
10000
|
+
active: entry.get("active"),
|
|
10001
|
+
priority: entry.get("priority"),
|
|
10002
|
+
created_at: entry.get("created_at"),
|
|
10003
|
+
updated_at: entry.get("updated_at")
|
|
10004
|
+
});
|
|
10005
|
+
});
|
|
10006
|
+
return records;
|
|
10007
|
+
}
|
|
10008
|
+
function onUpdate(callback) {
|
|
10009
|
+
const d = initCrdtDoc();
|
|
10010
|
+
const handler = (update) => callback(update);
|
|
10011
|
+
d.on("update", handler);
|
|
10012
|
+
return () => d.off("update", handler);
|
|
10013
|
+
}
|
|
10014
|
+
function persistState() {
|
|
10015
|
+
if (!doc) return;
|
|
10016
|
+
try {
|
|
10017
|
+
const sp = getStatePath();
|
|
10018
|
+
const dir = path22.dirname(sp);
|
|
10019
|
+
if (!existsSync19(dir)) mkdirSync8(dir, { recursive: true });
|
|
10020
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
10021
|
+
writeFileSync10(sp, Buffer.from(state));
|
|
10022
|
+
} catch {
|
|
10023
|
+
}
|
|
10024
|
+
}
|
|
10025
|
+
function isCrdtSyncEnabled() {
|
|
10026
|
+
return process.env.EXE_CRDT_SYNC !== "0";
|
|
10027
|
+
}
|
|
10028
|
+
async function rebuildFromDb() {
|
|
10029
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
10030
|
+
const client = getClient2();
|
|
10031
|
+
const result = await client.execute(
|
|
10032
|
+
"SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
|
|
10033
|
+
);
|
|
10034
|
+
const memories = result.rows.map((row) => ({
|
|
10035
|
+
id: String(row.id),
|
|
10036
|
+
agent_id: row.agent_id,
|
|
10037
|
+
agent_role: row.agent_role,
|
|
10038
|
+
session_id: row.session_id,
|
|
10039
|
+
timestamp: row.timestamp,
|
|
10040
|
+
tool_name: row.tool_name,
|
|
10041
|
+
project_name: row.project_name,
|
|
10042
|
+
has_error: row.has_error,
|
|
10043
|
+
raw_text: row.raw_text,
|
|
10044
|
+
version: row.version,
|
|
10045
|
+
author_device_id: row.author_device_id,
|
|
10046
|
+
scope: row.scope
|
|
10047
|
+
}));
|
|
10048
|
+
const count = importExistingMemories(memories);
|
|
10049
|
+
persistState();
|
|
10050
|
+
return count;
|
|
10051
|
+
}
|
|
10052
|
+
function destroyCrdtDoc() {
|
|
10053
|
+
if (doc) {
|
|
10054
|
+
doc.destroy();
|
|
10055
|
+
doc = null;
|
|
10056
|
+
}
|
|
10057
|
+
}
|
|
10058
|
+
var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
10059
|
+
var init_crdt_sync = __esm({
|
|
10060
|
+
"src/lib/crdt-sync.ts"() {
|
|
10061
|
+
"use strict";
|
|
10062
|
+
DEFAULT_STATE_PATH = path22.join(homedir2(), ".exe-os", "crdt-state.bin");
|
|
10063
|
+
_statePathOverride = null;
|
|
10064
|
+
doc = null;
|
|
10065
|
+
}
|
|
10066
|
+
});
|
|
10067
|
+
|
|
10068
|
+
// src/lib/cloud-sync.ts
|
|
10069
|
+
var cloud_sync_exports = {};
|
|
10070
|
+
__export(cloud_sync_exports, {
|
|
10071
|
+
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
10072
|
+
buildRosterBlob: () => buildRosterBlob,
|
|
10073
|
+
cloudPull: () => cloudPull,
|
|
10074
|
+
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
10075
|
+
cloudPullBlob: () => cloudPullBlob,
|
|
10076
|
+
cloudPullConversations: () => cloudPullConversations,
|
|
10077
|
+
cloudPullDocuments: () => cloudPullDocuments,
|
|
10078
|
+
cloudPullGlobalProcedures: () => cloudPullGlobalProcedures,
|
|
10079
|
+
cloudPullGraphRAG: () => cloudPullGraphRAG,
|
|
10080
|
+
cloudPullRoster: () => cloudPullRoster,
|
|
10081
|
+
cloudPullTasks: () => cloudPullTasks,
|
|
10082
|
+
cloudPush: () => cloudPush,
|
|
10083
|
+
cloudPushBehaviors: () => cloudPushBehaviors,
|
|
10084
|
+
cloudPushBlob: () => cloudPushBlob,
|
|
10085
|
+
cloudPushConversations: () => cloudPushConversations,
|
|
10086
|
+
cloudPushDocuments: () => cloudPushDocuments,
|
|
10087
|
+
cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
|
|
10088
|
+
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
10089
|
+
cloudPushRoster: () => cloudPushRoster,
|
|
10090
|
+
cloudPushTasks: () => cloudPushTasks,
|
|
10091
|
+
cloudSync: () => cloudSync,
|
|
10092
|
+
mergeConfig: () => mergeConfig,
|
|
10093
|
+
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
10094
|
+
pushToPostgres: () => pushToPostgres,
|
|
10095
|
+
recordRosterDeletion: () => recordRosterDeletion
|
|
10096
|
+
});
|
|
10097
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync11, existsSync as existsSync20, readdirSync as readdirSync5, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
10098
|
+
import crypto9 from "crypto";
|
|
10099
|
+
import path23 from "path";
|
|
10100
|
+
import { homedir as homedir3 } from "os";
|
|
10101
|
+
function sqlSafe(v) {
|
|
10102
|
+
return v === void 0 ? null : v;
|
|
10103
|
+
}
|
|
10104
|
+
function logError(msg) {
|
|
10105
|
+
try {
|
|
10106
|
+
const logPath = path23.join(homedir3(), ".exe-os", "workers.log");
|
|
10107
|
+
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
10108
|
+
`);
|
|
10109
|
+
} catch {
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
10112
|
+
function loadPgClient() {
|
|
10113
|
+
if (_pgFailed) return null;
|
|
10114
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
10115
|
+
const configPath = path23.join(EXE_AI_DIR, "config.json");
|
|
10116
|
+
let cloudPostgresUrl;
|
|
10117
|
+
try {
|
|
10118
|
+
if (existsSync20(configPath)) {
|
|
10119
|
+
const cfg = JSON.parse(readFileSync16(configPath, "utf8"));
|
|
10120
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
10121
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
10122
|
+
_pgFailed = true;
|
|
10123
|
+
return null;
|
|
10124
|
+
}
|
|
9522
10125
|
}
|
|
9523
|
-
|
|
9524
|
-
|
|
10126
|
+
} catch {
|
|
10127
|
+
}
|
|
10128
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
10129
|
+
if (!url) {
|
|
10130
|
+
_pgFailed = true;
|
|
10131
|
+
return null;
|
|
10132
|
+
}
|
|
10133
|
+
if (!_pgPromise) {
|
|
10134
|
+
_pgPromise = (async () => {
|
|
10135
|
+
const { createRequire: createRequire4 } = await import("module");
|
|
10136
|
+
const { pathToFileURL: pathToFileURL4 } = await import("url");
|
|
10137
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path23.join(homedir3(), "exe-db");
|
|
10138
|
+
const req = createRequire4(path23.join(exeDbRoot, "package.json"));
|
|
10139
|
+
const entry = req.resolve("@prisma/client");
|
|
10140
|
+
const mod = await import(pathToFileURL4(entry).href);
|
|
10141
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
10142
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
10143
|
+
return new Ctor();
|
|
10144
|
+
})().catch(() => {
|
|
10145
|
+
_pgFailed = true;
|
|
10146
|
+
_pgPromise = null;
|
|
10147
|
+
throw new Error("pg_unavailable");
|
|
10148
|
+
});
|
|
10149
|
+
}
|
|
10150
|
+
return _pgPromise;
|
|
10151
|
+
}
|
|
10152
|
+
async function pushToPostgres(records) {
|
|
10153
|
+
const loader = loadPgClient();
|
|
10154
|
+
if (!loader) return 0;
|
|
10155
|
+
let prisma;
|
|
10156
|
+
try {
|
|
10157
|
+
prisma = await loader;
|
|
10158
|
+
} catch {
|
|
10159
|
+
return 0;
|
|
10160
|
+
}
|
|
10161
|
+
let inserted = 0;
|
|
10162
|
+
for (const rec of records) {
|
|
10163
|
+
try {
|
|
10164
|
+
await prisma.$executeRawUnsafe(
|
|
10165
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
10166
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
10167
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
10168
|
+
String(rec.id ?? ""),
|
|
10169
|
+
JSON.stringify(rec),
|
|
10170
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
10171
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
10172
|
+
);
|
|
10173
|
+
inserted++;
|
|
10174
|
+
} catch {
|
|
9525
10175
|
}
|
|
9526
|
-
|
|
9527
|
-
|
|
10176
|
+
}
|
|
10177
|
+
return inserted;
|
|
10178
|
+
}
|
|
10179
|
+
async function withRosterLock(fn) {
|
|
10180
|
+
try {
|
|
10181
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
10182
|
+
closeSync2(fd);
|
|
10183
|
+
writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
|
|
10184
|
+
} catch (err) {
|
|
10185
|
+
if (err.code === "EEXIST") {
|
|
10186
|
+
try {
|
|
10187
|
+
const ts2 = parseInt(readFileSync16(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
10188
|
+
if (Date.now() - ts2 < LOCK_STALE_MS) {
|
|
10189
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
10190
|
+
}
|
|
10191
|
+
unlinkSync8(ROSTER_LOCK_PATH);
|
|
10192
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
10193
|
+
closeSync2(fd);
|
|
10194
|
+
writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
|
|
10195
|
+
} catch (retryErr) {
|
|
10196
|
+
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
10197
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
10198
|
+
}
|
|
10199
|
+
} else {
|
|
10200
|
+
throw err;
|
|
9528
10201
|
}
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
10202
|
+
}
|
|
10203
|
+
try {
|
|
10204
|
+
return await fn();
|
|
10205
|
+
} finally {
|
|
10206
|
+
try {
|
|
10207
|
+
unlinkSync8(ROSTER_LOCK_PATH);
|
|
10208
|
+
} catch {
|
|
9532
10209
|
}
|
|
9533
|
-
|
|
9534
|
-
|
|
10210
|
+
}
|
|
10211
|
+
}
|
|
10212
|
+
async function fetchWithRetry(url, init) {
|
|
10213
|
+
const MAX_RETRIES4 = 3;
|
|
10214
|
+
const BASE_DELAY_MS2 = 200;
|
|
10215
|
+
let lastError;
|
|
10216
|
+
for (let attempt = 0; attempt <= MAX_RETRIES4; attempt++) {
|
|
10217
|
+
try {
|
|
10218
|
+
const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
10219
|
+
const resp = await fetch(url, { ...init, signal });
|
|
10220
|
+
if (resp && resp.status >= 500 && attempt < MAX_RETRIES4) {
|
|
10221
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
10222
|
+
continue;
|
|
10223
|
+
}
|
|
10224
|
+
return resp;
|
|
10225
|
+
} catch (err) {
|
|
10226
|
+
lastError = err;
|
|
10227
|
+
if (attempt === MAX_RETRIES4) throw err;
|
|
10228
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
9535
10229
|
}
|
|
9536
|
-
return "(unknown)";
|
|
9537
10230
|
}
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
10231
|
+
throw lastError;
|
|
10232
|
+
}
|
|
10233
|
+
function assertSecureEndpoint(endpoint) {
|
|
10234
|
+
if (endpoint.startsWith("https://")) return;
|
|
10235
|
+
if (endpoint.startsWith("http://")) {
|
|
10236
|
+
try {
|
|
10237
|
+
const parsed = new URL(endpoint);
|
|
10238
|
+
if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
|
|
10239
|
+
} catch {
|
|
9544
10240
|
return;
|
|
9545
10241
|
}
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
10242
|
+
throw new Error(
|
|
10243
|
+
`Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
|
|
10244
|
+
);
|
|
10245
|
+
}
|
|
10246
|
+
}
|
|
10247
|
+
async function cloudPush(records, maxVersion, config) {
|
|
10248
|
+
if (records.length === 0) return true;
|
|
10249
|
+
assertSecureEndpoint(config.endpoint);
|
|
10250
|
+
try {
|
|
10251
|
+
const json = JSON.stringify(records);
|
|
10252
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
10253
|
+
const blob = encryptSyncBlob(compressed);
|
|
10254
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
|
|
10255
|
+
method: "POST",
|
|
10256
|
+
headers: {
|
|
10257
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10258
|
+
"Content-Type": "application/json",
|
|
10259
|
+
"X-Device-Id": loadDeviceId(),
|
|
10260
|
+
"X-Expected-Version": String(maxVersion)
|
|
10261
|
+
},
|
|
10262
|
+
body: JSON.stringify({ version: maxVersion, blob })
|
|
10263
|
+
});
|
|
10264
|
+
if (resp == null) {
|
|
10265
|
+
logError("[cloud-sync] PUSH FAILED: no response from server");
|
|
10266
|
+
return false;
|
|
10267
|
+
}
|
|
10268
|
+
if (resp.status === 409) {
|
|
10269
|
+
logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
|
|
10270
|
+
return false;
|
|
10271
|
+
}
|
|
10272
|
+
return resp.ok;
|
|
10273
|
+
} catch (err) {
|
|
10274
|
+
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
10275
|
+
return false;
|
|
10276
|
+
}
|
|
10277
|
+
}
|
|
10278
|
+
async function cloudPull(sinceVersion, config) {
|
|
10279
|
+
assertSecureEndpoint(config.endpoint);
|
|
10280
|
+
try {
|
|
10281
|
+
const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
|
|
10282
|
+
method: "POST",
|
|
10283
|
+
headers: {
|
|
10284
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10285
|
+
"Content-Type": "application/json",
|
|
10286
|
+
"X-Device-Id": loadDeviceId()
|
|
10287
|
+
},
|
|
10288
|
+
body: JSON.stringify({ since_version: sinceVersion })
|
|
10289
|
+
});
|
|
10290
|
+
if (response == null) {
|
|
10291
|
+
logError("[cloud-sync] PULL FAILED: no response from server");
|
|
10292
|
+
return { records: [], maxVersion: sinceVersion };
|
|
10293
|
+
}
|
|
10294
|
+
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
10295
|
+
const data = await response.json();
|
|
10296
|
+
const allRecords = [];
|
|
10297
|
+
for (const { blob } of data.blobs ?? []) {
|
|
10298
|
+
try {
|
|
10299
|
+
const compressed = decryptSyncBlob(blob);
|
|
10300
|
+
const json = decompress(compressed).toString("utf8");
|
|
10301
|
+
const records = JSON.parse(json);
|
|
10302
|
+
allRecords.push(...records);
|
|
10303
|
+
} catch {
|
|
10304
|
+
continue;
|
|
10305
|
+
}
|
|
10306
|
+
}
|
|
10307
|
+
return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
|
|
10308
|
+
} catch (err) {
|
|
10309
|
+
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
10310
|
+
return { records: [], maxVersion: sinceVersion };
|
|
10311
|
+
}
|
|
10312
|
+
}
|
|
10313
|
+
async function cloudSync(config) {
|
|
10314
|
+
if (!isSyncCryptoInitialized()) {
|
|
10315
|
+
try {
|
|
10316
|
+
const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
|
|
10317
|
+
const masterKey = await getMasterKey2();
|
|
10318
|
+
if (masterKey) {
|
|
10319
|
+
initSyncCrypto(masterKey);
|
|
10320
|
+
} else {
|
|
10321
|
+
throw new Error("No master key found");
|
|
10322
|
+
}
|
|
10323
|
+
} catch (err) {
|
|
10324
|
+
throw new Error(`[cloud-sync] Cannot initialize encryption: ${err instanceof Error ? err.message : String(err)}`);
|
|
10325
|
+
}
|
|
10326
|
+
}
|
|
10327
|
+
let client;
|
|
10328
|
+
try {
|
|
10329
|
+
client = getClient();
|
|
10330
|
+
} catch {
|
|
10331
|
+
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
10332
|
+
}
|
|
10333
|
+
try {
|
|
10334
|
+
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
10335
|
+
await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
10336
|
+
} catch {
|
|
10337
|
+
}
|
|
10338
|
+
try {
|
|
10339
|
+
await client.execute(
|
|
10340
|
+
"CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
|
|
10341
|
+
);
|
|
10342
|
+
} catch (e) {
|
|
10343
|
+
logError(`[cloud-sync] sync_meta CREATE failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
10344
|
+
}
|
|
10345
|
+
const pullMeta = await client.execute(
|
|
10346
|
+
"SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
|
|
10347
|
+
);
|
|
10348
|
+
const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
|
|
10349
|
+
const pullResult = await cloudPull(lastPullVersion, config);
|
|
10350
|
+
let pulled = 0;
|
|
10351
|
+
if (pullResult.records.length > 0) {
|
|
10352
|
+
if (isCrdtSyncEnabled()) {
|
|
10353
|
+
const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
|
|
10354
|
+
initCrdtDoc2();
|
|
10355
|
+
importExistingMemories2(
|
|
10356
|
+
pullResult.records.map((rec) => ({
|
|
10357
|
+
id: String(rec.id ?? ""),
|
|
10358
|
+
agent_id: rec.agent_id,
|
|
10359
|
+
agent_role: rec.agent_role,
|
|
10360
|
+
session_id: rec.session_id,
|
|
10361
|
+
timestamp: rec.timestamp,
|
|
10362
|
+
tool_name: rec.tool_name,
|
|
10363
|
+
project_name: rec.project_name,
|
|
10364
|
+
has_error: rec.has_error ?? 0,
|
|
10365
|
+
raw_text: rec.raw_text ?? "",
|
|
10366
|
+
version: rec.version ?? 0,
|
|
10367
|
+
author_device_id: rec.author_device_id,
|
|
10368
|
+
scope: rec.scope ?? "business"
|
|
10369
|
+
}))
|
|
10370
|
+
);
|
|
10371
|
+
const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
|
|
10372
|
+
const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
|
|
10373
|
+
const stmts = merged.map((rec) => ({
|
|
10374
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
10375
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
10376
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
10377
|
+
author_device_id, scope)
|
|
10378
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
10379
|
+
args: [
|
|
10380
|
+
sqlSafe(rec.id),
|
|
10381
|
+
sqlSafe(rec.agent_id),
|
|
10382
|
+
sqlSafe(rec.agent_role),
|
|
10383
|
+
sqlSafe(rec.session_id),
|
|
10384
|
+
sqlSafe(rec.timestamp),
|
|
10385
|
+
sqlSafe(rec.tool_name),
|
|
10386
|
+
sqlSafe(rec.project_name),
|
|
10387
|
+
sqlSafe(rec.has_error ?? 0),
|
|
10388
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
10389
|
+
sqlSafe(rec.version ?? 0),
|
|
10390
|
+
sqlSafe(rec.author_device_id),
|
|
10391
|
+
sqlSafe(rec.scope ?? "business")
|
|
10392
|
+
]
|
|
10393
|
+
}));
|
|
10394
|
+
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
10395
|
+
pulled = pullResult.records.length;
|
|
10396
|
+
} else {
|
|
10397
|
+
const stmts = pullResult.records.map((rec) => ({
|
|
10398
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
10399
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
10400
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
10401
|
+
author_device_id, scope)
|
|
10402
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
10403
|
+
args: [
|
|
10404
|
+
sqlSafe(rec.id),
|
|
10405
|
+
sqlSafe(rec.agent_id),
|
|
10406
|
+
sqlSafe(rec.agent_role),
|
|
10407
|
+
sqlSafe(rec.session_id),
|
|
10408
|
+
sqlSafe(rec.timestamp),
|
|
10409
|
+
sqlSafe(rec.tool_name),
|
|
10410
|
+
sqlSafe(rec.project_name),
|
|
10411
|
+
sqlSafe(rec.has_error ?? 0),
|
|
10412
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
10413
|
+
sqlSafe(rec.version ?? 0),
|
|
10414
|
+
sqlSafe(rec.author_device_id),
|
|
10415
|
+
sqlSafe(rec.scope ?? "business")
|
|
10416
|
+
]
|
|
10417
|
+
}));
|
|
10418
|
+
await client.batch(stmts, "write");
|
|
10419
|
+
pulled = pullResult.records.length;
|
|
10420
|
+
}
|
|
10421
|
+
}
|
|
10422
|
+
if (pulled > 0) {
|
|
10423
|
+
try {
|
|
10424
|
+
await pushToPostgres(pullResult.records);
|
|
10425
|
+
} catch {
|
|
10426
|
+
}
|
|
10427
|
+
}
|
|
10428
|
+
if (pullResult.maxVersion > lastPullVersion) {
|
|
10429
|
+
await client.execute({
|
|
10430
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
|
|
10431
|
+
args: [String(pullResult.maxVersion)]
|
|
10432
|
+
});
|
|
10433
|
+
}
|
|
10434
|
+
const pushMeta = await client.execute(
|
|
10435
|
+
"SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
|
|
10436
|
+
);
|
|
10437
|
+
const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
|
|
10438
|
+
let pushed = 0;
|
|
10439
|
+
let batchCursor = lastPushVersion;
|
|
10440
|
+
while (true) {
|
|
10441
|
+
const recordsResult = await client.execute({
|
|
10442
|
+
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
10443
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
10444
|
+
author_device_id, scope
|
|
10445
|
+
FROM memories
|
|
10446
|
+
WHERE version > ?
|
|
10447
|
+
AND (scope IS NULL OR scope != 'personal')
|
|
10448
|
+
ORDER BY version ASC
|
|
10449
|
+
LIMIT ?`,
|
|
10450
|
+
args: [batchCursor, PUSH_BATCH_SIZE]
|
|
10451
|
+
});
|
|
10452
|
+
if (recordsResult.rows.length === 0) break;
|
|
10453
|
+
const records = recordsResult.rows.map((row) => ({
|
|
10454
|
+
id: row.id,
|
|
10455
|
+
agent_id: row.agent_id,
|
|
10456
|
+
agent_role: row.agent_role,
|
|
10457
|
+
session_id: row.session_id,
|
|
10458
|
+
timestamp: row.timestamp,
|
|
10459
|
+
tool_name: row.tool_name,
|
|
10460
|
+
project_name: row.project_name,
|
|
10461
|
+
has_error: row.has_error,
|
|
10462
|
+
raw_text: row.raw_text,
|
|
10463
|
+
version: row.version,
|
|
10464
|
+
author_device_id: row.author_device_id,
|
|
10465
|
+
scope: row.scope
|
|
10466
|
+
}));
|
|
10467
|
+
const maxVersion = Number(records[records.length - 1].version);
|
|
10468
|
+
const pushOk = await cloudPush(records, maxVersion, config);
|
|
10469
|
+
if (!pushOk) break;
|
|
10470
|
+
try {
|
|
10471
|
+
await pushToPostgres(records);
|
|
10472
|
+
} catch {
|
|
10473
|
+
}
|
|
10474
|
+
await client.execute({
|
|
10475
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
10476
|
+
args: [String(maxVersion)]
|
|
10477
|
+
});
|
|
10478
|
+
pushed += records.length;
|
|
10479
|
+
batchCursor = maxVersion;
|
|
10480
|
+
if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
|
|
10481
|
+
}
|
|
10482
|
+
try {
|
|
10483
|
+
await cloudPushRoster(config);
|
|
10484
|
+
} catch (err) {
|
|
10485
|
+
logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10486
|
+
}
|
|
10487
|
+
try {
|
|
10488
|
+
await cloudPullRoster(config);
|
|
10489
|
+
} catch (err) {
|
|
10490
|
+
logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10491
|
+
}
|
|
10492
|
+
try {
|
|
10493
|
+
await cloudPushGlobalProcedures(config);
|
|
10494
|
+
} catch (err) {
|
|
10495
|
+
logError(`[cloud-sync] Global procedures push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10496
|
+
}
|
|
10497
|
+
try {
|
|
10498
|
+
await cloudPullGlobalProcedures(config);
|
|
10499
|
+
} catch (err) {
|
|
10500
|
+
logError(`[cloud-sync] Global procedures pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10501
|
+
}
|
|
10502
|
+
const countRows = async (sql) => {
|
|
10503
|
+
try {
|
|
10504
|
+
return Number((await client.execute(sql)).rows[0]?.cnt ?? 0);
|
|
10505
|
+
} catch {
|
|
10506
|
+
return 0;
|
|
10507
|
+
}
|
|
10508
|
+
};
|
|
10509
|
+
let behaviorsResult = { pushed: 0, pulled: 0 };
|
|
10510
|
+
try {
|
|
10511
|
+
await cloudPushBehaviors(config);
|
|
10512
|
+
behaviorsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM behaviors WHERE active = 1");
|
|
10513
|
+
} catch (err) {
|
|
10514
|
+
logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10515
|
+
}
|
|
10516
|
+
try {
|
|
10517
|
+
const pullResult2 = await cloudPullBehaviors(config);
|
|
10518
|
+
behaviorsResult.pulled = pullResult2.pulled;
|
|
10519
|
+
} catch (err) {
|
|
10520
|
+
logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10521
|
+
}
|
|
10522
|
+
let graphragResult = { pushed: 0, pulled: 0 };
|
|
10523
|
+
try {
|
|
10524
|
+
await cloudPushGraphRAG(config);
|
|
10525
|
+
graphragResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM entities");
|
|
10526
|
+
} catch (err) {
|
|
10527
|
+
logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10528
|
+
}
|
|
10529
|
+
try {
|
|
10530
|
+
const pullResult2 = await cloudPullGraphRAG(config);
|
|
10531
|
+
graphragResult.pulled = pullResult2.pulled;
|
|
10532
|
+
} catch (err) {
|
|
10533
|
+
logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10534
|
+
}
|
|
10535
|
+
let tasksResult = { pushed: 0, pulled: 0 };
|
|
10536
|
+
try {
|
|
10537
|
+
await cloudPushTasks(config);
|
|
10538
|
+
tasksResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM tasks");
|
|
10539
|
+
} catch (err) {
|
|
10540
|
+
logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10541
|
+
}
|
|
10542
|
+
try {
|
|
10543
|
+
const pullResult2 = await cloudPullTasks(config);
|
|
10544
|
+
tasksResult.pulled = pullResult2.pulled;
|
|
10545
|
+
} catch (err) {
|
|
10546
|
+
logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10547
|
+
}
|
|
10548
|
+
let conversationsResult = { pushed: 0, pulled: 0 };
|
|
10549
|
+
try {
|
|
10550
|
+
await cloudPushConversations(config);
|
|
10551
|
+
conversationsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM conversations");
|
|
10552
|
+
} catch (err) {
|
|
10553
|
+
logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10554
|
+
}
|
|
10555
|
+
try {
|
|
10556
|
+
const pullResult2 = await cloudPullConversations(config);
|
|
10557
|
+
conversationsResult.pulled = pullResult2.pulled;
|
|
10558
|
+
} catch (err) {
|
|
10559
|
+
logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10560
|
+
}
|
|
10561
|
+
let documentsResult = { pushed: 0, pulled: 0 };
|
|
10562
|
+
try {
|
|
10563
|
+
await cloudPushDocuments(config);
|
|
10564
|
+
documentsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM documents");
|
|
10565
|
+
} catch (err) {
|
|
10566
|
+
logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10567
|
+
}
|
|
10568
|
+
try {
|
|
10569
|
+
const pullResult2 = await cloudPullDocuments(config);
|
|
10570
|
+
documentsResult.pulled = pullResult2.pulled;
|
|
10571
|
+
} catch (err) {
|
|
10572
|
+
logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10573
|
+
}
|
|
10574
|
+
let rosterResult = { employees: 0, identities: 0 };
|
|
10575
|
+
try {
|
|
10576
|
+
const employees = await loadEmployees();
|
|
10577
|
+
rosterResult.employees = employees.length;
|
|
10578
|
+
const idDir = path23.join(EXE_AI_DIR, "identity");
|
|
10579
|
+
if (existsSync20(idDir)) {
|
|
10580
|
+
rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
|
|
10581
|
+
}
|
|
10582
|
+
} catch {
|
|
10583
|
+
}
|
|
10584
|
+
const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
|
|
10585
|
+
return {
|
|
10586
|
+
pushed,
|
|
10587
|
+
pulled,
|
|
10588
|
+
totalMemories,
|
|
10589
|
+
behaviors: behaviorsResult,
|
|
10590
|
+
graphrag: graphragResult,
|
|
10591
|
+
tasks: tasksResult,
|
|
10592
|
+
conversations: conversationsResult,
|
|
10593
|
+
documents: documentsResult,
|
|
10594
|
+
roster: rosterResult
|
|
10595
|
+
};
|
|
10596
|
+
}
|
|
10597
|
+
function recordRosterDeletion(name) {
|
|
10598
|
+
let deletions = [];
|
|
10599
|
+
try {
|
|
10600
|
+
if (existsSync20(ROSTER_DELETIONS_PATH)) {
|
|
10601
|
+
deletions = JSON.parse(readFileSync16(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
10602
|
+
}
|
|
10603
|
+
} catch {
|
|
10604
|
+
}
|
|
10605
|
+
if (!deletions.includes(name)) deletions.push(name);
|
|
10606
|
+
writeFileSync11(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
10607
|
+
}
|
|
10608
|
+
function consumeRosterDeletions() {
|
|
10609
|
+
try {
|
|
10610
|
+
if (!existsSync20(ROSTER_DELETIONS_PATH)) return [];
|
|
10611
|
+
const deletions = JSON.parse(readFileSync16(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
10612
|
+
writeFileSync11(ROSTER_DELETIONS_PATH, "[]");
|
|
10613
|
+
return deletions;
|
|
10614
|
+
} catch {
|
|
10615
|
+
return [];
|
|
10616
|
+
}
|
|
10617
|
+
}
|
|
10618
|
+
function buildRosterBlob(paths) {
|
|
10619
|
+
const rosterPath = paths?.rosterPath ?? path23.join(EXE_AI_DIR, "exe-employees.json");
|
|
10620
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
10621
|
+
const configPath = paths?.configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
10622
|
+
let roster = [];
|
|
10623
|
+
if (existsSync20(rosterPath)) {
|
|
10624
|
+
try {
|
|
10625
|
+
roster = JSON.parse(readFileSync16(rosterPath, "utf-8"));
|
|
10626
|
+
} catch {
|
|
10627
|
+
}
|
|
10628
|
+
}
|
|
10629
|
+
const identities = {};
|
|
10630
|
+
if (existsSync20(identityDir)) {
|
|
10631
|
+
for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
10632
|
+
try {
|
|
10633
|
+
identities[file] = readFileSync16(path23.join(identityDir, file), "utf-8");
|
|
10634
|
+
} catch {
|
|
10635
|
+
}
|
|
10636
|
+
}
|
|
10637
|
+
}
|
|
10638
|
+
let config;
|
|
10639
|
+
if (existsSync20(configPath)) {
|
|
10640
|
+
try {
|
|
10641
|
+
config = JSON.parse(readFileSync16(configPath, "utf-8"));
|
|
10642
|
+
} catch {
|
|
10643
|
+
}
|
|
10644
|
+
}
|
|
10645
|
+
let agentConfig;
|
|
10646
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
10647
|
+
if (existsSync20(agentConfigPath)) {
|
|
10648
|
+
try {
|
|
10649
|
+
agentConfig = JSON.parse(readFileSync16(agentConfigPath, "utf-8"));
|
|
10650
|
+
} catch {
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10653
|
+
const deletedNames = consumeRosterDeletions();
|
|
10654
|
+
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
10655
|
+
const hash = crypto9.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
10656
|
+
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
10657
|
+
}
|
|
10658
|
+
async function cloudPushRoster(config) {
|
|
10659
|
+
assertSecureEndpoint(config.endpoint);
|
|
10660
|
+
const blob = buildRosterBlob();
|
|
10661
|
+
if (blob.roster.length === 0) return true;
|
|
10662
|
+
try {
|
|
10663
|
+
const client = getClient();
|
|
10664
|
+
const meta = await client.execute(
|
|
10665
|
+
"SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
|
|
10666
|
+
);
|
|
10667
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
10668
|
+
if (blob.version === lastVersion) return true;
|
|
10669
|
+
} catch {
|
|
10670
|
+
}
|
|
10671
|
+
try {
|
|
10672
|
+
const json = JSON.stringify(blob);
|
|
10673
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
10674
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
10675
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
|
|
10676
|
+
method: "POST",
|
|
10677
|
+
headers: {
|
|
10678
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10679
|
+
"Content-Type": "application/json",
|
|
10680
|
+
"X-Device-Id": loadDeviceId()
|
|
10681
|
+
},
|
|
10682
|
+
body: JSON.stringify({ blob: encrypted })
|
|
10683
|
+
});
|
|
10684
|
+
if (resp.ok) {
|
|
10685
|
+
try {
|
|
10686
|
+
const client = getClient();
|
|
10687
|
+
await client.execute({
|
|
10688
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
|
|
10689
|
+
args: [String(blob.version)]
|
|
10690
|
+
});
|
|
10691
|
+
} catch {
|
|
10692
|
+
}
|
|
10693
|
+
}
|
|
10694
|
+
return resp.ok;
|
|
10695
|
+
} catch (err) {
|
|
10696
|
+
process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
10697
|
+
`);
|
|
10698
|
+
return false;
|
|
10699
|
+
}
|
|
10700
|
+
}
|
|
10701
|
+
async function cloudPullRoster(config) {
|
|
10702
|
+
assertSecureEndpoint(config.endpoint);
|
|
10703
|
+
try {
|
|
10704
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
|
|
10705
|
+
method: "GET",
|
|
10706
|
+
headers: {
|
|
10707
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10708
|
+
"X-Device-Id": loadDeviceId()
|
|
10709
|
+
}
|
|
10710
|
+
});
|
|
10711
|
+
if (!resp.ok) return { added: 0 };
|
|
10712
|
+
const data = await resp.json();
|
|
10713
|
+
if (!data.blob) return { added: 0 };
|
|
10714
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
10715
|
+
const json = decompress(compressed).toString("utf8");
|
|
10716
|
+
const remote = JSON.parse(json);
|
|
10717
|
+
return mergeRosterFromRemote(remote);
|
|
10718
|
+
} catch (err) {
|
|
10719
|
+
process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
10720
|
+
`);
|
|
10721
|
+
return { added: 0 };
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10724
|
+
function mergeConfig(remoteConfig, configPath) {
|
|
10725
|
+
const cfgPath = configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
10726
|
+
let local = {};
|
|
10727
|
+
if (existsSync20(cfgPath)) {
|
|
10728
|
+
try {
|
|
10729
|
+
local = JSON.parse(readFileSync16(cfgPath, "utf-8"));
|
|
10730
|
+
} catch {
|
|
10731
|
+
}
|
|
10732
|
+
}
|
|
10733
|
+
const merged = { ...remoteConfig, ...local };
|
|
10734
|
+
const dir = path23.dirname(cfgPath);
|
|
10735
|
+
ensurePrivateDirSync(dir);
|
|
10736
|
+
writeFileSync11(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
10737
|
+
enforcePrivateFileSync(cfgPath);
|
|
10738
|
+
}
|
|
10739
|
+
async function mergeRosterFromRemote(remote, paths) {
|
|
10740
|
+
return withRosterLock(async () => {
|
|
10741
|
+
const rosterPath = paths?.rosterPath ?? void 0;
|
|
10742
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
10743
|
+
const localEmployees = await loadEmployees(rosterPath);
|
|
10744
|
+
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
10745
|
+
let added = 0;
|
|
10746
|
+
let identitiesUpdated = 0;
|
|
10747
|
+
for (const remoteEmp of remote.roster) {
|
|
10748
|
+
if (!localNames.has(remoteEmp.name)) {
|
|
10749
|
+
localEmployees.push(remoteEmp);
|
|
10750
|
+
localNames.add(remoteEmp.name);
|
|
10751
|
+
added++;
|
|
10752
|
+
try {
|
|
10753
|
+
registerBinSymlinks(remoteEmp.name);
|
|
10754
|
+
} catch {
|
|
10755
|
+
}
|
|
10756
|
+
}
|
|
10757
|
+
const lookupKey = `${remoteEmp.name}.md`;
|
|
10758
|
+
const matchedKey = Object.keys(remote.identities).find(
|
|
10759
|
+
(k) => k.toLowerCase() === lookupKey.toLowerCase()
|
|
10760
|
+
) ?? lookupKey;
|
|
10761
|
+
const remoteIdentity = remote.identities[matchedKey];
|
|
10762
|
+
if (remoteIdentity) {
|
|
10763
|
+
if (!existsSync20(identityDir)) mkdirSync9(identityDir, { recursive: true });
|
|
10764
|
+
const idPath = path23.join(identityDir, `${remoteEmp.name}.md`);
|
|
10765
|
+
let localIdentity = null;
|
|
10766
|
+
try {
|
|
10767
|
+
localIdentity = existsSync20(idPath) ? readFileSync16(idPath, "utf-8") : null;
|
|
10768
|
+
} catch {
|
|
10769
|
+
}
|
|
10770
|
+
if (localIdentity !== remoteIdentity) {
|
|
10771
|
+
writeFileSync11(idPath, remoteIdentity, "utf-8");
|
|
10772
|
+
identitiesUpdated++;
|
|
10773
|
+
}
|
|
10774
|
+
}
|
|
10775
|
+
}
|
|
10776
|
+
let removed = 0;
|
|
10777
|
+
if (remote.deletedNames && remote.deletedNames.length > 0) {
|
|
10778
|
+
const toRemove = new Set(remote.deletedNames);
|
|
10779
|
+
const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
|
|
10780
|
+
removed = localEmployees.length - filtered.length;
|
|
10781
|
+
if (removed > 0) {
|
|
10782
|
+
localEmployees.length = 0;
|
|
10783
|
+
localEmployees.push(...filtered);
|
|
10784
|
+
}
|
|
10785
|
+
}
|
|
10786
|
+
if (added > 0 || removed > 0) {
|
|
10787
|
+
await saveEmployees(localEmployees, rosterPath);
|
|
10788
|
+
}
|
|
10789
|
+
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
10790
|
+
try {
|
|
10791
|
+
mergeConfig(remote.config, paths?.configPath);
|
|
10792
|
+
} catch {
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
10795
|
+
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
10796
|
+
try {
|
|
10797
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
10798
|
+
let local = {};
|
|
10799
|
+
if (existsSync20(agentConfigPath)) {
|
|
10800
|
+
try {
|
|
10801
|
+
local = JSON.parse(readFileSync16(agentConfigPath, "utf-8"));
|
|
10802
|
+
} catch {
|
|
10803
|
+
}
|
|
10804
|
+
}
|
|
10805
|
+
const merged = { ...remote.agentConfig, ...local };
|
|
10806
|
+
ensurePrivateDirSync(path23.dirname(agentConfigPath));
|
|
10807
|
+
writeFileSync11(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
10808
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
10809
|
+
} catch {
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
return { added, identitiesUpdated };
|
|
10813
|
+
});
|
|
10814
|
+
}
|
|
10815
|
+
async function cloudPushBlob(route, data, metaKey, config) {
|
|
10816
|
+
if (data.length === 0) return { ok: true };
|
|
10817
|
+
assertSecureEndpoint(config.endpoint);
|
|
10818
|
+
const json = JSON.stringify(data);
|
|
10819
|
+
const version = Buffer.from(json).length;
|
|
10820
|
+
try {
|
|
10821
|
+
const client = getClient();
|
|
10822
|
+
const meta = await client.execute({
|
|
10823
|
+
sql: "SELECT value FROM sync_meta WHERE key = ?",
|
|
10824
|
+
args: [metaKey]
|
|
10825
|
+
});
|
|
10826
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
10827
|
+
if (version === lastVersion) return { ok: true };
|
|
10828
|
+
} catch {
|
|
10829
|
+
}
|
|
10830
|
+
try {
|
|
10831
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
10832
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
10833
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
10834
|
+
method: "POST",
|
|
10835
|
+
headers: {
|
|
10836
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10837
|
+
"Content-Type": "application/json",
|
|
10838
|
+
"X-Device-Id": loadDeviceId()
|
|
10839
|
+
},
|
|
10840
|
+
body: JSON.stringify({ blob: encrypted })
|
|
10841
|
+
});
|
|
10842
|
+
if (resp.ok) {
|
|
10843
|
+
try {
|
|
10844
|
+
const client = getClient();
|
|
10845
|
+
await client.execute({
|
|
10846
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
|
|
10847
|
+
args: [metaKey, String(version)]
|
|
10848
|
+
});
|
|
10849
|
+
} catch {
|
|
10850
|
+
}
|
|
10851
|
+
}
|
|
10852
|
+
return { ok: resp.ok };
|
|
10853
|
+
} catch (err) {
|
|
10854
|
+
logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10855
|
+
return { ok: false };
|
|
10856
|
+
}
|
|
10857
|
+
}
|
|
10858
|
+
async function cloudPullBlob(route, config) {
|
|
10859
|
+
assertSecureEndpoint(config.endpoint);
|
|
10860
|
+
try {
|
|
10861
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
10862
|
+
method: "GET",
|
|
10863
|
+
headers: {
|
|
10864
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10865
|
+
"X-Device-Id": loadDeviceId()
|
|
10866
|
+
}
|
|
10867
|
+
});
|
|
10868
|
+
if (!resp.ok) return null;
|
|
10869
|
+
const data = await resp.json();
|
|
10870
|
+
if (!data.blob) return null;
|
|
10871
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
10872
|
+
const json = decompress(compressed).toString("utf8");
|
|
10873
|
+
return JSON.parse(json);
|
|
10874
|
+
} catch (err) {
|
|
10875
|
+
logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10876
|
+
return null;
|
|
10877
|
+
}
|
|
10878
|
+
}
|
|
10879
|
+
async function cloudPushGlobalProcedures(config) {
|
|
10880
|
+
const client = getClient();
|
|
10881
|
+
const result = await client.execute("SELECT * FROM global_procedures LIMIT 1000");
|
|
10882
|
+
const rows = result.rows;
|
|
10883
|
+
const { ok } = await cloudPushBlob(
|
|
10884
|
+
"/sync/push-global-procedures",
|
|
10885
|
+
rows,
|
|
10886
|
+
"last_global_procedures_push_version",
|
|
10887
|
+
config
|
|
10888
|
+
);
|
|
10889
|
+
return ok;
|
|
10890
|
+
}
|
|
10891
|
+
async function cloudPullGlobalProcedures(config) {
|
|
10892
|
+
const remoteProcs = await cloudPullBlob(
|
|
10893
|
+
"/sync/pull-global-procedures",
|
|
10894
|
+
config
|
|
10895
|
+
);
|
|
10896
|
+
if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
|
|
10897
|
+
const client = getClient();
|
|
10898
|
+
const stmts = remoteProcs.map((p) => ({
|
|
10899
|
+
sql: `INSERT INTO global_procedures
|
|
10900
|
+
(id, title, content, priority, domain, active, created_at, updated_at)
|
|
10901
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
10902
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
10903
|
+
title = excluded.title,
|
|
10904
|
+
content = excluded.content,
|
|
10905
|
+
priority = excluded.priority,
|
|
10906
|
+
domain = excluded.domain,
|
|
10907
|
+
active = excluded.active,
|
|
10908
|
+
updated_at = excluded.updated_at
|
|
10909
|
+
WHERE excluded.updated_at > global_procedures.updated_at`,
|
|
10910
|
+
args: [
|
|
10911
|
+
sqlSafe(p.id),
|
|
10912
|
+
sqlSafe(p.title),
|
|
10913
|
+
sqlSafe(p.content),
|
|
10914
|
+
sqlSafe(p.priority ?? "p0"),
|
|
10915
|
+
sqlSafe(p.domain),
|
|
10916
|
+
sqlSafe(p.active ?? 1),
|
|
10917
|
+
sqlSafe(p.created_at),
|
|
10918
|
+
sqlSafe(p.updated_at)
|
|
10919
|
+
]
|
|
10920
|
+
}));
|
|
10921
|
+
await client.batch(stmts, "write");
|
|
10922
|
+
return { pulled: remoteProcs.length };
|
|
10923
|
+
}
|
|
10924
|
+
async function cloudPushBehaviors(config) {
|
|
10925
|
+
const client = getClient();
|
|
10926
|
+
const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
|
|
10927
|
+
const rows = result.rows;
|
|
10928
|
+
const { ok } = await cloudPushBlob(
|
|
10929
|
+
"/sync/push-behaviors",
|
|
10930
|
+
rows,
|
|
10931
|
+
"last_behaviors_push_version",
|
|
10932
|
+
config
|
|
10933
|
+
);
|
|
10934
|
+
return ok;
|
|
10935
|
+
}
|
|
10936
|
+
async function cloudPullBehaviors(config) {
|
|
10937
|
+
const remoteBehaviors = await cloudPullBlob(
|
|
10938
|
+
"/sync/pull-behaviors",
|
|
10939
|
+
config
|
|
10940
|
+
);
|
|
10941
|
+
if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
|
|
10942
|
+
const client = getClient();
|
|
10943
|
+
let pulled = 0;
|
|
10944
|
+
for (const behavior of remoteBehaviors) {
|
|
10945
|
+
const existing = await client.execute({
|
|
10946
|
+
sql: `SELECT COUNT(*) as cnt FROM behaviors
|
|
10947
|
+
WHERE agent_id = ? AND content = ?`,
|
|
10948
|
+
args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
|
|
10949
|
+
});
|
|
10950
|
+
if (Number(existing.rows[0]?.cnt) > 0) continue;
|
|
10951
|
+
await client.execute({
|
|
10952
|
+
sql: `INSERT OR IGNORE INTO behaviors
|
|
10953
|
+
(id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
|
|
10954
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
10955
|
+
args: [
|
|
10956
|
+
sqlSafe(behavior.id),
|
|
10957
|
+
sqlSafe(behavior.agent_id),
|
|
10958
|
+
sqlSafe(behavior.project_name),
|
|
10959
|
+
sqlSafe(behavior.domain),
|
|
10960
|
+
sqlSafe(behavior.content),
|
|
10961
|
+
sqlSafe(behavior.active ?? 1),
|
|
10962
|
+
sqlSafe(behavior.priority ?? "p1"),
|
|
10963
|
+
sqlSafe(behavior.created_at),
|
|
10964
|
+
sqlSafe(behavior.updated_at)
|
|
10965
|
+
]
|
|
10966
|
+
});
|
|
10967
|
+
pulled++;
|
|
10968
|
+
}
|
|
10969
|
+
return { pulled };
|
|
10970
|
+
}
|
|
10971
|
+
async function cloudPushGraphRAG(config) {
|
|
10972
|
+
const client = getClient();
|
|
10973
|
+
const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
|
|
10974
|
+
client.execute("SELECT * FROM entities LIMIT 50000"),
|
|
10975
|
+
client.execute("SELECT * FROM relationships LIMIT 50000"),
|
|
10976
|
+
client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
|
|
10977
|
+
client.execute("SELECT * FROM entity_memories LIMIT 50000"),
|
|
10978
|
+
client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
|
|
10979
|
+
client.execute("SELECT * FROM hyperedges LIMIT 50000"),
|
|
10980
|
+
client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
|
|
10981
|
+
]);
|
|
10982
|
+
const blob = {
|
|
10983
|
+
entities: entities.rows,
|
|
10984
|
+
relationships: relationships.rows,
|
|
10985
|
+
entity_aliases: aliases.rows,
|
|
10986
|
+
entity_memories: entityMems.rows,
|
|
10987
|
+
relationship_memories: relMems.rows,
|
|
10988
|
+
hyperedges: hyperedges.rows,
|
|
10989
|
+
hyperedge_nodes: hyperedgeNodes.rows
|
|
10990
|
+
};
|
|
10991
|
+
const { ok } = await cloudPushBlob(
|
|
10992
|
+
"/sync/push-graphrag",
|
|
10993
|
+
[blob],
|
|
10994
|
+
"last_graphrag_push_version",
|
|
10995
|
+
config
|
|
10996
|
+
);
|
|
10997
|
+
return ok;
|
|
10998
|
+
}
|
|
10999
|
+
async function cloudPullGraphRAG(config) {
|
|
11000
|
+
const data = await cloudPullBlob(
|
|
11001
|
+
"/sync/pull-graphrag",
|
|
11002
|
+
config
|
|
11003
|
+
);
|
|
11004
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
11005
|
+
const blob = data[0];
|
|
11006
|
+
const client = getClient();
|
|
11007
|
+
let pulled = 0;
|
|
11008
|
+
if (blob.entities.length > 0) {
|
|
11009
|
+
const stmts = blob.entities.map((e) => ({
|
|
11010
|
+
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
|
|
11011
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
11012
|
+
args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
|
|
11013
|
+
}));
|
|
11014
|
+
await client.batch(stmts, "write");
|
|
11015
|
+
pulled += stmts.length;
|
|
11016
|
+
}
|
|
11017
|
+
if (blob.relationships.length > 0) {
|
|
11018
|
+
const stmts = blob.relationships.map((r) => ({
|
|
11019
|
+
sql: `INSERT OR IGNORE INTO relationships
|
|
11020
|
+
(id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
|
|
11021
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11022
|
+
args: [
|
|
11023
|
+
sqlSafe(r.id),
|
|
11024
|
+
sqlSafe(r.source_entity_id),
|
|
11025
|
+
sqlSafe(r.target_entity_id),
|
|
11026
|
+
sqlSafe(r.type),
|
|
11027
|
+
sqlSafe(r.weight ?? 1),
|
|
11028
|
+
sqlSafe(r.timestamp),
|
|
11029
|
+
sqlSafe(r.properties ?? "{}"),
|
|
11030
|
+
sqlSafe(r.confidence ?? 1),
|
|
11031
|
+
sqlSafe(r.confidence_label ?? "extracted")
|
|
11032
|
+
]
|
|
11033
|
+
}));
|
|
11034
|
+
await client.batch(stmts, "write");
|
|
11035
|
+
pulled += stmts.length;
|
|
11036
|
+
}
|
|
11037
|
+
if (blob.entity_aliases.length > 0) {
|
|
11038
|
+
const stmts = blob.entity_aliases.map((a) => ({
|
|
11039
|
+
sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
|
|
11040
|
+
args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
|
|
11041
|
+
}));
|
|
11042
|
+
await client.batch(stmts, "write");
|
|
11043
|
+
pulled += stmts.length;
|
|
11044
|
+
}
|
|
11045
|
+
if (blob.entity_memories.length > 0) {
|
|
11046
|
+
const stmts = blob.entity_memories.map((em) => ({
|
|
11047
|
+
sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
|
|
11048
|
+
args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
|
|
11049
|
+
}));
|
|
11050
|
+
await client.batch(stmts, "write");
|
|
11051
|
+
pulled += stmts.length;
|
|
11052
|
+
}
|
|
11053
|
+
if (blob.relationship_memories.length > 0) {
|
|
11054
|
+
const stmts = blob.relationship_memories.map((rm) => ({
|
|
11055
|
+
sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
|
|
11056
|
+
args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
|
|
11057
|
+
}));
|
|
11058
|
+
await client.batch(stmts, "write");
|
|
11059
|
+
pulled += stmts.length;
|
|
11060
|
+
}
|
|
11061
|
+
if (blob.hyperedges.length > 0) {
|
|
11062
|
+
const stmts = blob.hyperedges.map((h) => ({
|
|
11063
|
+
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
11064
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
11065
|
+
args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
|
|
11066
|
+
}));
|
|
11067
|
+
await client.batch(stmts, "write");
|
|
11068
|
+
pulled += stmts.length;
|
|
11069
|
+
}
|
|
11070
|
+
if (blob.hyperedge_nodes.length > 0) {
|
|
11071
|
+
const stmts = blob.hyperedge_nodes.map((hn) => ({
|
|
11072
|
+
sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
|
|
11073
|
+
args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
|
|
11074
|
+
}));
|
|
11075
|
+
await client.batch(stmts, "write");
|
|
11076
|
+
pulled += stmts.length;
|
|
11077
|
+
}
|
|
11078
|
+
return { pulled };
|
|
11079
|
+
}
|
|
11080
|
+
async function cloudPushTasks(config) {
|
|
11081
|
+
const client = getClient();
|
|
11082
|
+
const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
|
|
11083
|
+
const rows = result.rows;
|
|
11084
|
+
const { ok } = await cloudPushBlob(
|
|
11085
|
+
"/sync/push-tasks",
|
|
11086
|
+
rows,
|
|
11087
|
+
"last_tasks_push_version",
|
|
11088
|
+
config
|
|
11089
|
+
);
|
|
11090
|
+
return ok;
|
|
11091
|
+
}
|
|
11092
|
+
async function cloudPullTasks(config) {
|
|
11093
|
+
const remoteTasks = await cloudPullBlob(
|
|
11094
|
+
"/sync/pull-tasks",
|
|
11095
|
+
config
|
|
11096
|
+
);
|
|
11097
|
+
if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
|
|
11098
|
+
const client = getClient();
|
|
11099
|
+
const stmts = remoteTasks.map((t) => ({
|
|
11100
|
+
sql: `INSERT OR IGNORE INTO tasks
|
|
11101
|
+
(id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
|
|
11102
|
+
blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
|
|
11103
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11104
|
+
args: [
|
|
11105
|
+
sqlSafe(t.id),
|
|
11106
|
+
sqlSafe(t.title),
|
|
11107
|
+
sqlSafe(t.assigned_to),
|
|
11108
|
+
sqlSafe(t.assigned_by),
|
|
11109
|
+
sqlSafe(t.project_name),
|
|
11110
|
+
sqlSafe(t.priority ?? "p1"),
|
|
11111
|
+
sqlSafe(t.status ?? "open"),
|
|
11112
|
+
sqlSafe(t.task_file),
|
|
11113
|
+
sqlSafe(t.created_at),
|
|
11114
|
+
sqlSafe(t.updated_at),
|
|
11115
|
+
sqlSafe(t.blocked_by),
|
|
11116
|
+
sqlSafe(t.parent_task_id),
|
|
11117
|
+
sqlSafe(t.budget_tokens),
|
|
11118
|
+
sqlSafe(t.budget_fallback_model),
|
|
11119
|
+
sqlSafe(t.tokens_used ?? 0),
|
|
11120
|
+
sqlSafe(t.tokens_warned_at)
|
|
11121
|
+
]
|
|
11122
|
+
}));
|
|
11123
|
+
await client.batch(stmts, "write");
|
|
11124
|
+
return { pulled: remoteTasks.length };
|
|
11125
|
+
}
|
|
11126
|
+
async function cloudPushConversations(config) {
|
|
11127
|
+
const client = getClient();
|
|
11128
|
+
const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
|
|
11129
|
+
const rows = result.rows;
|
|
11130
|
+
const { ok } = await cloudPushBlob(
|
|
11131
|
+
"/sync/push-conversations",
|
|
11132
|
+
rows,
|
|
11133
|
+
"last_conversations_push_version",
|
|
11134
|
+
config
|
|
11135
|
+
);
|
|
11136
|
+
return ok;
|
|
11137
|
+
}
|
|
11138
|
+
async function cloudPullConversations(config) {
|
|
11139
|
+
const remoteConvos = await cloudPullBlob(
|
|
11140
|
+
"/sync/pull-conversations",
|
|
11141
|
+
config
|
|
11142
|
+
);
|
|
11143
|
+
if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
|
|
11144
|
+
const client = getClient();
|
|
11145
|
+
const stmts = remoteConvos.map((c) => ({
|
|
11146
|
+
sql: `INSERT OR IGNORE INTO conversations
|
|
11147
|
+
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
11148
|
+
recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
|
|
11149
|
+
content_metadata, agent_response, agent_name, timestamp, ingested_at)
|
|
11150
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11151
|
+
args: [
|
|
11152
|
+
sqlSafe(c.id),
|
|
11153
|
+
sqlSafe(c.platform),
|
|
11154
|
+
sqlSafe(c.external_id),
|
|
11155
|
+
sqlSafe(c.sender_id),
|
|
11156
|
+
sqlSafe(c.sender_name),
|
|
11157
|
+
sqlSafe(c.sender_phone),
|
|
11158
|
+
sqlSafe(c.sender_email),
|
|
11159
|
+
sqlSafe(c.recipient_id),
|
|
11160
|
+
sqlSafe(c.channel_id),
|
|
11161
|
+
sqlSafe(c.thread_id),
|
|
11162
|
+
sqlSafe(c.reply_to_id),
|
|
11163
|
+
sqlSafe(c.content_text),
|
|
11164
|
+
sqlSafe(c.content_media),
|
|
11165
|
+
sqlSafe(c.content_metadata),
|
|
11166
|
+
sqlSafe(c.agent_response),
|
|
11167
|
+
sqlSafe(c.agent_name),
|
|
11168
|
+
sqlSafe(c.timestamp),
|
|
11169
|
+
sqlSafe(c.ingested_at)
|
|
11170
|
+
]
|
|
11171
|
+
}));
|
|
11172
|
+
await client.batch(stmts, "write");
|
|
11173
|
+
return { pulled: remoteConvos.length };
|
|
11174
|
+
}
|
|
11175
|
+
async function cloudPushDocuments(config) {
|
|
11176
|
+
const client = getClient();
|
|
11177
|
+
const [workspaces, documents] = await Promise.all([
|
|
11178
|
+
client.execute("SELECT * FROM workspaces LIMIT 1000"),
|
|
11179
|
+
client.execute("SELECT * FROM documents LIMIT 10000")
|
|
11180
|
+
]);
|
|
11181
|
+
const blob = {
|
|
11182
|
+
workspaces: workspaces.rows,
|
|
11183
|
+
documents: documents.rows
|
|
11184
|
+
};
|
|
11185
|
+
const { ok } = await cloudPushBlob(
|
|
11186
|
+
"/sync/push-documents",
|
|
11187
|
+
[blob],
|
|
11188
|
+
"last_documents_push_version",
|
|
11189
|
+
config
|
|
11190
|
+
);
|
|
11191
|
+
return ok;
|
|
11192
|
+
}
|
|
11193
|
+
async function cloudPullDocuments(config) {
|
|
11194
|
+
const data = await cloudPullBlob(
|
|
11195
|
+
"/sync/pull-documents",
|
|
11196
|
+
config
|
|
11197
|
+
);
|
|
11198
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
11199
|
+
const blob = data[0];
|
|
11200
|
+
const client = getClient();
|
|
11201
|
+
let pulled = 0;
|
|
11202
|
+
if (blob.workspaces.length > 0) {
|
|
11203
|
+
const stmts = blob.workspaces.map((w) => ({
|
|
11204
|
+
sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
|
|
11205
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
11206
|
+
args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
|
|
11207
|
+
}));
|
|
11208
|
+
await client.batch(stmts, "write");
|
|
11209
|
+
pulled += stmts.length;
|
|
11210
|
+
}
|
|
11211
|
+
if (blob.documents.length > 0) {
|
|
11212
|
+
const stmts = blob.documents.map((d) => ({
|
|
11213
|
+
sql: `INSERT OR IGNORE INTO documents
|
|
11214
|
+
(id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
|
|
11215
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11216
|
+
args: [
|
|
11217
|
+
sqlSafe(d.id),
|
|
11218
|
+
sqlSafe(d.workspace_id),
|
|
11219
|
+
sqlSafe(d.filename),
|
|
11220
|
+
sqlSafe(d.mime),
|
|
11221
|
+
sqlSafe(d.source_type),
|
|
11222
|
+
sqlSafe(d.user_id),
|
|
11223
|
+
sqlSafe(d.uploaded_at),
|
|
11224
|
+
sqlSafe(d.metadata)
|
|
11225
|
+
]
|
|
11226
|
+
}));
|
|
11227
|
+
await client.batch(stmts, "write");
|
|
11228
|
+
pulled += stmts.length;
|
|
11229
|
+
}
|
|
11230
|
+
return { pulled };
|
|
11231
|
+
}
|
|
11232
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
11233
|
+
var init_cloud_sync = __esm({
|
|
11234
|
+
"src/lib/cloud-sync.ts"() {
|
|
11235
|
+
"use strict";
|
|
11236
|
+
init_database();
|
|
11237
|
+
init_crypto();
|
|
11238
|
+
init_compress();
|
|
11239
|
+
init_license();
|
|
11240
|
+
init_config();
|
|
11241
|
+
init_crdt_sync();
|
|
11242
|
+
init_employees();
|
|
11243
|
+
init_secure_files();
|
|
11244
|
+
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
11245
|
+
FETCH_TIMEOUT_MS = 3e4;
|
|
11246
|
+
PUSH_BATCH_SIZE = 5e3;
|
|
11247
|
+
ROSTER_LOCK_PATH = path23.join(EXE_AI_DIR, "roster-merge.lock");
|
|
11248
|
+
LOCK_STALE_MS = 3e4;
|
|
11249
|
+
_pgPromise = null;
|
|
11250
|
+
_pgFailed = false;
|
|
11251
|
+
ROSTER_DELETIONS_PATH = path23.join(EXE_AI_DIR, "roster-deletions.json");
|
|
11252
|
+
}
|
|
11253
|
+
});
|
|
11254
|
+
|
|
11255
|
+
// src/lib/code-chunker.ts
|
|
11256
|
+
import ts from "typescript";
|
|
11257
|
+
function chunkSourceFile(source, fileName = "file.ts") {
|
|
11258
|
+
const sourceFile = ts.createSourceFile(
|
|
11259
|
+
fileName,
|
|
11260
|
+
source,
|
|
11261
|
+
ts.ScriptTarget.Latest,
|
|
11262
|
+
true,
|
|
11263
|
+
// setParentNodes
|
|
11264
|
+
fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
11265
|
+
);
|
|
11266
|
+
const chunks = [];
|
|
11267
|
+
const lines = source.split("\n");
|
|
11268
|
+
const importLines = [];
|
|
11269
|
+
function getLineNumber(pos) {
|
|
11270
|
+
return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
|
|
11271
|
+
}
|
|
11272
|
+
function getLeadingComment(node) {
|
|
11273
|
+
const fullText = sourceFile.getFullText();
|
|
11274
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
11275
|
+
if (!ranges || ranges.length === 0) return void 0;
|
|
11276
|
+
return ranges.map((r) => fullText.slice(r.pos, r.end)).join("\n");
|
|
11277
|
+
}
|
|
11278
|
+
function getNodeText(node) {
|
|
11279
|
+
return node.getText(sourceFile);
|
|
11280
|
+
}
|
|
11281
|
+
function getName(node) {
|
|
11282
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
|
|
11283
|
+
return node.name?.getText(sourceFile) ?? "(anonymous)";
|
|
11284
|
+
}
|
|
11285
|
+
if (ts.isClassDeclaration(node)) {
|
|
11286
|
+
return node.name?.getText(sourceFile) ?? "(anonymous class)";
|
|
11287
|
+
}
|
|
11288
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
11289
|
+
return node.name.getText(sourceFile);
|
|
11290
|
+
}
|
|
11291
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
11292
|
+
return node.name.getText(sourceFile);
|
|
11293
|
+
}
|
|
11294
|
+
if (ts.isEnumDeclaration(node)) {
|
|
11295
|
+
return node.name.getText(sourceFile);
|
|
11296
|
+
}
|
|
11297
|
+
if (ts.isVariableStatement(node)) {
|
|
11298
|
+
const decls = node.declarationList.declarations;
|
|
11299
|
+
return decls.map((d) => d.name.getText(sourceFile)).join(", ");
|
|
11300
|
+
}
|
|
11301
|
+
if (ts.isExportAssignment(node)) {
|
|
11302
|
+
return "default export";
|
|
11303
|
+
}
|
|
11304
|
+
return "(unknown)";
|
|
11305
|
+
}
|
|
11306
|
+
function visitTopLevel(node) {
|
|
11307
|
+
if (ts.isImportDeclaration(node)) {
|
|
11308
|
+
importLines.push({
|
|
11309
|
+
start: getLineNumber(node.getStart(sourceFile)),
|
|
11310
|
+
end: getLineNumber(node.getEnd())
|
|
11311
|
+
});
|
|
11312
|
+
return;
|
|
11313
|
+
}
|
|
11314
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
11315
|
+
chunks.push({
|
|
11316
|
+
kind: "function",
|
|
11317
|
+
name: getName(node),
|
|
9550
11318
|
text: getNodeText(node),
|
|
9551
11319
|
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
9552
11320
|
endLine: getLineNumber(node.getEnd()),
|
|
@@ -9678,13 +11446,13 @@ __export(graph_rag_exports, {
|
|
|
9678
11446
|
resolveAlias: () => resolveAlias,
|
|
9679
11447
|
storeExtraction: () => storeExtraction
|
|
9680
11448
|
});
|
|
9681
|
-
import
|
|
11449
|
+
import crypto10 from "crypto";
|
|
9682
11450
|
function normalizeEntityName(name) {
|
|
9683
11451
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
9684
11452
|
}
|
|
9685
11453
|
function entityId(name, type) {
|
|
9686
11454
|
const normalized = normalizeEntityName(name);
|
|
9687
|
-
return
|
|
11455
|
+
return crypto10.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
9688
11456
|
}
|
|
9689
11457
|
async function resolveAlias(client, name) {
|
|
9690
11458
|
const normalized = normalizeEntityName(name);
|
|
@@ -9934,7 +11702,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9934
11702
|
const targetAlias = await resolveAlias(client, r.target);
|
|
9935
11703
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
9936
11704
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
9937
|
-
const relId =
|
|
11705
|
+
const relId = crypto10.randomUUID().slice(0, 16);
|
|
9938
11706
|
try {
|
|
9939
11707
|
await client.execute({
|
|
9940
11708
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -9997,7 +11765,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9997
11765
|
}
|
|
9998
11766
|
}
|
|
9999
11767
|
for (const h of extraction.hyperedges) {
|
|
10000
|
-
const hId =
|
|
11768
|
+
const hId = crypto10.randomUUID().slice(0, 16);
|
|
10001
11769
|
try {
|
|
10002
11770
|
await client.execute({
|
|
10003
11771
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -10061,7 +11829,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
10061
11829
|
totalEntities += stored.entitiesStored;
|
|
10062
11830
|
totalRelationships += stored.relationshipsStored;
|
|
10063
11831
|
}
|
|
10064
|
-
const contentHash =
|
|
11832
|
+
const contentHash = crypto10.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
10065
11833
|
await client.execute({
|
|
10066
11834
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
10067
11835
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -10178,8 +11946,8 @@ __export(wiki_sync_exports, {
|
|
|
10178
11946
|
listWorkspaces: () => listWorkspaces,
|
|
10179
11947
|
syncMemories: () => syncMemories
|
|
10180
11948
|
});
|
|
10181
|
-
async function wikiRequest(config,
|
|
10182
|
-
const url = `${config.wikiUrl}/api/v1${
|
|
11949
|
+
async function wikiRequest(config, path28, method = "GET", body) {
|
|
11950
|
+
const url = `${config.wikiUrl}/api/v1${path28}`;
|
|
10183
11951
|
const headers = {
|
|
10184
11952
|
"Authorization": `Bearer ${config.wikiApiKey}`,
|
|
10185
11953
|
"Content-Type": "application/json"
|
|
@@ -10191,7 +11959,7 @@ async function wikiRequest(config, path25, method = "GET", body) {
|
|
|
10191
11959
|
signal: AbortSignal.timeout(3e4)
|
|
10192
11960
|
});
|
|
10193
11961
|
if (!response.ok) {
|
|
10194
|
-
throw new Error(`Wiki API ${method} ${
|
|
11962
|
+
throw new Error(`Wiki API ${method} ${path28}: ${response.status} ${response.statusText}`);
|
|
10195
11963
|
}
|
|
10196
11964
|
return response.json();
|
|
10197
11965
|
}
|
|
@@ -10303,8 +12071,8 @@ __export(token_spend_exports, {
|
|
|
10303
12071
|
import { readdir } from "fs/promises";
|
|
10304
12072
|
import { createReadStream } from "fs";
|
|
10305
12073
|
import { createInterface } from "readline";
|
|
10306
|
-
import
|
|
10307
|
-
import
|
|
12074
|
+
import path24 from "path";
|
|
12075
|
+
import os14 from "os";
|
|
10308
12076
|
function getPricing(model) {
|
|
10309
12077
|
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
10310
12078
|
const stripped = model.replace(/-\d{8}$/, "");
|
|
@@ -10331,18 +12099,18 @@ async function getAgentSpend(period = "7d") {
|
|
|
10331
12099
|
for (const row of dbResult.rows) {
|
|
10332
12100
|
sessionAgent.set(row.session_uuid, row.agent_id);
|
|
10333
12101
|
}
|
|
10334
|
-
const claudeDir =
|
|
12102
|
+
const claudeDir = path24.join(os14.homedir(), ".claude", "projects");
|
|
10335
12103
|
let projectDirs = [];
|
|
10336
12104
|
try {
|
|
10337
12105
|
const entries = await readdir(claudeDir);
|
|
10338
|
-
projectDirs = entries.map((e) =>
|
|
12106
|
+
projectDirs = entries.map((e) => path24.join(claudeDir, e));
|
|
10339
12107
|
} catch {
|
|
10340
12108
|
return [];
|
|
10341
12109
|
}
|
|
10342
12110
|
const agentTotals = /* @__PURE__ */ new Map();
|
|
10343
12111
|
for (const [sessionUuid, agentId] of sessionAgent) {
|
|
10344
12112
|
for (const dir of projectDirs) {
|
|
10345
|
-
const jsonlPath =
|
|
12113
|
+
const jsonlPath = path24.join(dir, `${sessionUuid}.jsonl`);
|
|
10346
12114
|
try {
|
|
10347
12115
|
const usage = await extractSessionUsage(jsonlPath);
|
|
10348
12116
|
if (usage.input === 0 && usage.output === 0) continue;
|
|
@@ -10467,11 +12235,11 @@ __export(update_check_exports, {
|
|
|
10467
12235
|
getRemoteVersion: () => getRemoteVersion
|
|
10468
12236
|
});
|
|
10469
12237
|
import { execSync as execSync11 } from "child_process";
|
|
10470
|
-
import { readFileSync as
|
|
10471
|
-
import
|
|
12238
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
12239
|
+
import path25 from "path";
|
|
10472
12240
|
function getLocalVersion(packageRoot) {
|
|
10473
|
-
const pkgPath =
|
|
10474
|
-
const pkg = JSON.parse(
|
|
12241
|
+
const pkgPath = path25.join(packageRoot, "package.json");
|
|
12242
|
+
const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
10475
12243
|
return pkg.version;
|
|
10476
12244
|
}
|
|
10477
12245
|
function getRemoteVersion() {
|
|
@@ -10514,16 +12282,16 @@ __export(ws_auth_exports, {
|
|
|
10514
12282
|
deriveWsAuthToken: () => deriveWsAuthToken,
|
|
10515
12283
|
hashAuthToken: () => hashAuthToken
|
|
10516
12284
|
});
|
|
10517
|
-
import
|
|
12285
|
+
import crypto11 from "crypto";
|
|
10518
12286
|
function deriveWsAuthToken(masterKey) {
|
|
10519
|
-
return Buffer.from(
|
|
12287
|
+
return Buffer.from(crypto11.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
|
|
10520
12288
|
}
|
|
10521
12289
|
function deriveOrgId(masterKey) {
|
|
10522
|
-
const raw = Buffer.from(
|
|
10523
|
-
return
|
|
12290
|
+
const raw = Buffer.from(crypto11.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
|
|
12291
|
+
return crypto11.createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
10524
12292
|
}
|
|
10525
12293
|
function hashAuthToken(token) {
|
|
10526
|
-
return
|
|
12294
|
+
return crypto11.createHash("sha256").update(token).digest("hex");
|
|
10527
12295
|
}
|
|
10528
12296
|
var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
|
|
10529
12297
|
var init_ws_auth = __esm({
|
|
@@ -10541,14 +12309,14 @@ __export(device_registry_exports, {
|
|
|
10541
12309
|
resolveTargetDevice: () => resolveTargetDevice,
|
|
10542
12310
|
setFriendlyName: () => setFriendlyName
|
|
10543
12311
|
});
|
|
10544
|
-
import
|
|
10545
|
-
import
|
|
10546
|
-
import { readFileSync as
|
|
10547
|
-
import
|
|
12312
|
+
import crypto12 from "crypto";
|
|
12313
|
+
import os15 from "os";
|
|
12314
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync10, existsSync as existsSync21 } from "fs";
|
|
12315
|
+
import path26 from "path";
|
|
10548
12316
|
function getDeviceInfo() {
|
|
10549
|
-
if (
|
|
12317
|
+
if (existsSync21(DEVICE_JSON_PATH)) {
|
|
10550
12318
|
try {
|
|
10551
|
-
const raw =
|
|
12319
|
+
const raw = readFileSync18(DEVICE_JSON_PATH, "utf8");
|
|
10552
12320
|
const data = JSON.parse(raw);
|
|
10553
12321
|
if (data.deviceId && data.friendlyName && data.hostname) {
|
|
10554
12322
|
return data;
|
|
@@ -10556,20 +12324,20 @@ function getDeviceInfo() {
|
|
|
10556
12324
|
} catch {
|
|
10557
12325
|
}
|
|
10558
12326
|
}
|
|
10559
|
-
const hostname =
|
|
12327
|
+
const hostname = os15.hostname();
|
|
10560
12328
|
const info = {
|
|
10561
|
-
deviceId:
|
|
12329
|
+
deviceId: crypto12.randomUUID(),
|
|
10562
12330
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
10563
12331
|
hostname
|
|
10564
12332
|
};
|
|
10565
|
-
|
|
10566
|
-
|
|
12333
|
+
mkdirSync10(path26.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
12334
|
+
writeFileSync12(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10567
12335
|
return info;
|
|
10568
12336
|
}
|
|
10569
12337
|
function setFriendlyName(name) {
|
|
10570
12338
|
const info = getDeviceInfo();
|
|
10571
12339
|
info.friendlyName = name;
|
|
10572
|
-
|
|
12340
|
+
writeFileSync12(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10573
12341
|
}
|
|
10574
12342
|
async function resolveTargetDevice(targetAgent, targetProject) {
|
|
10575
12343
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -10603,7 +12371,7 @@ var init_device_registry = __esm({
|
|
|
10603
12371
|
"src/lib/device-registry.ts"() {
|
|
10604
12372
|
"use strict";
|
|
10605
12373
|
init_config();
|
|
10606
|
-
DEVICE_JSON_PATH =
|
|
12374
|
+
DEVICE_JSON_PATH = path26.join(EXE_AI_DIR, "device.json");
|
|
10607
12375
|
}
|
|
10608
12376
|
});
|
|
10609
12377
|
|
|
@@ -10628,18 +12396,18 @@ function assertSecureWsUrl(url) {
|
|
|
10628
12396
|
`Malformed WebSocket URL rejected: "${url}".`
|
|
10629
12397
|
);
|
|
10630
12398
|
}
|
|
10631
|
-
if (
|
|
12399
|
+
if (LOCALHOST_PATTERNS2.test(parsed.hostname)) return;
|
|
10632
12400
|
throw new Error(
|
|
10633
12401
|
`Insecure WebSocket URL rejected: "${url}". Use wss:// for remote hosts. Plain ws:// is only allowed for localhost.`
|
|
10634
12402
|
);
|
|
10635
12403
|
}
|
|
10636
12404
|
}
|
|
10637
|
-
var
|
|
12405
|
+
var LOCALHOST_PATTERNS2;
|
|
10638
12406
|
var init_gateway_client = __esm({
|
|
10639
12407
|
"src/lib/gateway-client.ts"() {
|
|
10640
12408
|
"use strict";
|
|
10641
12409
|
init_message_queue();
|
|
10642
|
-
|
|
12410
|
+
LOCALHOST_PATTERNS2 = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
10643
12411
|
}
|
|
10644
12412
|
});
|
|
10645
12413
|
|
|
@@ -10827,10 +12595,10 @@ __export(messaging_exports, {
|
|
|
10827
12595
|
sendMessage: () => sendMessage,
|
|
10828
12596
|
setWsClientSend: () => setWsClientSend
|
|
10829
12597
|
});
|
|
10830
|
-
import
|
|
12598
|
+
import crypto13 from "crypto";
|
|
10831
12599
|
function generateUlid() {
|
|
10832
12600
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
10833
|
-
const random =
|
|
12601
|
+
const random = crypto13.randomBytes(10).toString("hex").slice(0, 16);
|
|
10834
12602
|
return (timestamp + random).toUpperCase();
|
|
10835
12603
|
}
|
|
10836
12604
|
function rowToMessage(row) {
|
|
@@ -11089,13 +12857,13 @@ init_memory();
|
|
|
11089
12857
|
init_daemon_protocol();
|
|
11090
12858
|
init_daemon_auth();
|
|
11091
12859
|
init_daemon_orchestration();
|
|
11092
|
-
import
|
|
12860
|
+
import os16 from "os";
|
|
11093
12861
|
import net2 from "net";
|
|
11094
|
-
import { writeFileSync as
|
|
11095
|
-
import
|
|
12862
|
+
import { writeFileSync as writeFileSync13, unlinkSync as unlinkSync9, mkdirSync as mkdirSync11, existsSync as existsSync22, readFileSync as readFileSync19, chmodSync as chmodSync2 } from "fs";
|
|
12863
|
+
import path27 from "path";
|
|
11096
12864
|
import { getLlama } from "node-llama-cpp";
|
|
11097
|
-
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ??
|
|
11098
|
-
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ??
|
|
12865
|
+
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path27.join(EXE_AI_DIR, "exed.sock");
|
|
12866
|
+
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path27.join(EXE_AI_DIR, "exed.pid");
|
|
11099
12867
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
11100
12868
|
var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
11101
12869
|
var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
|
|
@@ -11123,8 +12891,8 @@ function enqueue(queue, entry) {
|
|
|
11123
12891
|
queue.push(entry);
|
|
11124
12892
|
}
|
|
11125
12893
|
async function loadModel() {
|
|
11126
|
-
const modelPath =
|
|
11127
|
-
if (!
|
|
12894
|
+
const modelPath = path27.join(MODELS_DIR, MODEL_FILE);
|
|
12895
|
+
if (!existsSync22(modelPath)) {
|
|
11128
12896
|
process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
|
|
11129
12897
|
`);
|
|
11130
12898
|
process.exit(1);
|
|
@@ -11194,6 +12962,11 @@ function checkIdle() {
|
|
|
11194
12962
|
}
|
|
11195
12963
|
async function shutdown() {
|
|
11196
12964
|
resetIdleTimer();
|
|
12965
|
+
try {
|
|
12966
|
+
const { stopProjectionWorker: stopProjectionWorker2 } = await Promise.resolve().then(() => (init_projection_worker(), projection_worker_exports));
|
|
12967
|
+
stopProjectionWorker2();
|
|
12968
|
+
} catch {
|
|
12969
|
+
}
|
|
11197
12970
|
try {
|
|
11198
12971
|
const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
11199
12972
|
disposeShards2();
|
|
@@ -11215,11 +12988,11 @@ async function shutdown() {
|
|
|
11215
12988
|
}
|
|
11216
12989
|
_llama = null;
|
|
11217
12990
|
try {
|
|
11218
|
-
|
|
12991
|
+
unlinkSync9(SOCKET_PATH2);
|
|
11219
12992
|
} catch {
|
|
11220
12993
|
}
|
|
11221
12994
|
try {
|
|
11222
|
-
|
|
12995
|
+
unlinkSync9(PID_PATH2);
|
|
11223
12996
|
} catch {
|
|
11224
12997
|
}
|
|
11225
12998
|
process.stderr.write("[exed] Shutdown complete.\n");
|
|
@@ -11355,28 +13128,28 @@ async function handleIngest(req) {
|
|
|
11355
13128
|
}
|
|
11356
13129
|
}
|
|
11357
13130
|
function startServer() {
|
|
11358
|
-
|
|
13131
|
+
mkdirSync11(path27.dirname(SOCKET_PATH2), { recursive: true });
|
|
11359
13132
|
try {
|
|
11360
|
-
chmodSync2(
|
|
13133
|
+
chmodSync2(path27.dirname(SOCKET_PATH2), 448);
|
|
11361
13134
|
} catch {
|
|
11362
13135
|
}
|
|
11363
13136
|
_daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
|
|
11364
13137
|
for (const oldFile of ["embed.sock", "embed.pid"]) {
|
|
11365
|
-
const oldPath =
|
|
13138
|
+
const oldPath = path27.join(path27.dirname(SOCKET_PATH2), oldFile);
|
|
11366
13139
|
try {
|
|
11367
13140
|
if (oldFile.endsWith(".pid")) {
|
|
11368
|
-
const pid = parseInt(
|
|
13141
|
+
const pid = parseInt(readFileSync19(oldPath, "utf8").trim(), 10);
|
|
11369
13142
|
if (pid > 0) try {
|
|
11370
13143
|
process.kill(pid, "SIGKILL");
|
|
11371
13144
|
} catch {
|
|
11372
13145
|
}
|
|
11373
13146
|
}
|
|
11374
|
-
|
|
13147
|
+
unlinkSync9(oldPath);
|
|
11375
13148
|
} catch {
|
|
11376
13149
|
}
|
|
11377
13150
|
}
|
|
11378
13151
|
try {
|
|
11379
|
-
|
|
13152
|
+
unlinkSync9(SOCKET_PATH2);
|
|
11380
13153
|
} catch {
|
|
11381
13154
|
}
|
|
11382
13155
|
const server = net2.createServer((socket) => {
|
|
@@ -11466,7 +13239,7 @@ function startServer() {
|
|
|
11466
13239
|
chmodSync2(SOCKET_PATH2, 384);
|
|
11467
13240
|
} catch {
|
|
11468
13241
|
}
|
|
11469
|
-
|
|
13242
|
+
writeFileSync13(PID_PATH2, String(process.pid));
|
|
11470
13243
|
try {
|
|
11471
13244
|
chmodSync2(PID_PATH2, 384);
|
|
11472
13245
|
} catch {
|
|
@@ -11666,6 +13439,35 @@ function startConsolidation() {
|
|
|
11666
13439
|
`);
|
|
11667
13440
|
});
|
|
11668
13441
|
}
|
|
13442
|
+
var CLOUD_SYNC_INTERVAL_MS = Number(process.env.EXE_CLOUD_SYNC_INTERVAL_MS) || 5 * 60 * 1e3;
|
|
13443
|
+
function startCloudSyncTimer() {
|
|
13444
|
+
const tick = async () => {
|
|
13445
|
+
try {
|
|
13446
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
13447
|
+
const config = await loadConfig2();
|
|
13448
|
+
if (!config.cloud?.apiKey || !config.cloud?.endpoint) return;
|
|
13449
|
+
const { cloudSync: cloudSync2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
13450
|
+
const result = await cloudSync2({
|
|
13451
|
+
apiKey: config.cloud.apiKey,
|
|
13452
|
+
endpoint: config.cloud.endpoint
|
|
13453
|
+
});
|
|
13454
|
+
if (result.pushed > 0 || result.pulled > 0) {
|
|
13455
|
+
process.stderr.write(
|
|
13456
|
+
`[exed] Cloud sync: pushed=${result.pushed} pulled=${result.pulled} total=${result.totalMemories}
|
|
13457
|
+
`
|
|
13458
|
+
);
|
|
13459
|
+
}
|
|
13460
|
+
} catch (err) {
|
|
13461
|
+
process.stderr.write(`[exed] Cloud sync error (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
13462
|
+
`);
|
|
13463
|
+
}
|
|
13464
|
+
};
|
|
13465
|
+
const timer = setInterval(() => void tick(), CLOUD_SYNC_INTERVAL_MS);
|
|
13466
|
+
timer.unref();
|
|
13467
|
+
setTimeout(() => void tick(), 3e4);
|
|
13468
|
+
process.stderr.write(`[exed] Cloud sync timer started (every ${CLOUD_SYNC_INTERVAL_MS / 6e4}m)
|
|
13469
|
+
`);
|
|
13470
|
+
}
|
|
11669
13471
|
var SKILL_SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
11670
13472
|
function startSkillSweep() {
|
|
11671
13473
|
const tick = async () => {
|
|
@@ -11763,7 +13565,7 @@ function startWikiSync() {
|
|
|
11763
13565
|
});
|
|
11764
13566
|
}
|
|
11765
13567
|
var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
|
|
11766
|
-
var AGENT_STATS_PATH =
|
|
13568
|
+
var AGENT_STATS_PATH = path27.join(EXE_AI_DIR, "agent-stats.json");
|
|
11767
13569
|
async function writeAgentStats() {
|
|
11768
13570
|
if (!await ensureStoreForPolling()) return;
|
|
11769
13571
|
try {
|
|
@@ -11821,7 +13623,7 @@ async function writeAgentStats() {
|
|
|
11821
13623
|
pid: process.pid
|
|
11822
13624
|
}
|
|
11823
13625
|
};
|
|
11824
|
-
|
|
13626
|
+
writeFileSync13(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
|
|
11825
13627
|
} catch (err) {
|
|
11826
13628
|
process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
|
|
11827
13629
|
`);
|
|
@@ -11893,12 +13695,12 @@ function startIntercomQueueDrain() {
|
|
|
11893
13695
|
const hasInProgressTask = (session) => {
|
|
11894
13696
|
try {
|
|
11895
13697
|
const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
|
|
11896
|
-
const
|
|
11897
|
-
const { existsSync:
|
|
11898
|
-
const
|
|
13698
|
+
const path28 = __require("path");
|
|
13699
|
+
const { existsSync: existsSync23 } = __require("fs");
|
|
13700
|
+
const os17 = __require("os");
|
|
11899
13701
|
const agent = ban(session.split("-")[0] ?? session);
|
|
11900
|
-
const markerPath =
|
|
11901
|
-
return
|
|
13702
|
+
const markerPath = path28.join(os17.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
|
|
13703
|
+
return existsSync23(markerPath);
|
|
11902
13704
|
} catch {
|
|
11903
13705
|
return false;
|
|
11904
13706
|
}
|
|
@@ -11987,7 +13789,7 @@ function startAutoWake() {
|
|
|
11987
13789
|
process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
|
|
11988
13790
|
`);
|
|
11989
13791
|
}
|
|
11990
|
-
var TOTAL_MEM_GB =
|
|
13792
|
+
var TOTAL_MEM_GB = os16.totalmem() / 1024 ** 3;
|
|
11991
13793
|
var RSS_WARN_BYTES = Number(process.env.EXE_RSS_WARN_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 6 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 4 * 1024 ** 3 : 1024 ** 3);
|
|
11992
13794
|
var RSS_RESTART_BYTES = Number(process.env.EXE_RSS_RESTART_MB) * 1024 * 1024 || (TOTAL_MEM_GB >= 64 ? 8 * 1024 ** 3 : TOTAL_MEM_GB >= 32 ? 6 * 1024 ** 3 : 2 * 1024 ** 3);
|
|
11993
13795
|
var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
|
|
@@ -12023,8 +13825,8 @@ process.on("SIGINT", () => void shutdown());
|
|
|
12023
13825
|
process.on("SIGTERM", () => void shutdown());
|
|
12024
13826
|
function checkExistingDaemon() {
|
|
12025
13827
|
try {
|
|
12026
|
-
if (!
|
|
12027
|
-
const pid = parseInt(
|
|
13828
|
+
if (!existsSync22(PID_PATH2)) return false;
|
|
13829
|
+
const pid = parseInt(readFileSync19(PID_PATH2, "utf8").trim(), 10);
|
|
12028
13830
|
if (!pid || isNaN(pid)) return false;
|
|
12029
13831
|
process.kill(pid, 0);
|
|
12030
13832
|
process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
|
|
@@ -12037,7 +13839,7 @@ function checkExistingDaemon() {
|
|
|
12037
13839
|
return true;
|
|
12038
13840
|
}
|
|
12039
13841
|
try {
|
|
12040
|
-
|
|
13842
|
+
unlinkSync9(PID_PATH2);
|
|
12041
13843
|
} catch {
|
|
12042
13844
|
}
|
|
12043
13845
|
return false;
|
|
@@ -12103,6 +13905,7 @@ try {
|
|
|
12103
13905
|
startIdleKill();
|
|
12104
13906
|
startConsolidation();
|
|
12105
13907
|
startSkillSweep();
|
|
13908
|
+
startCloudSyncTimer();
|
|
12106
13909
|
startOrphanReaper();
|
|
12107
13910
|
startAgentStats();
|
|
12108
13911
|
startCapacityMonitoring();
|
|
@@ -12113,6 +13916,8 @@ try {
|
|
|
12113
13916
|
startConfidenceDecay();
|
|
12114
13917
|
startAutoUpdateCheck();
|
|
12115
13918
|
startRssWatchdog();
|
|
13919
|
+
const { startProjectionWorker: startProjectionWorker2 } = await Promise.resolve().then(() => (init_projection_worker(), projection_worker_exports));
|
|
13920
|
+
startProjectionWorker2();
|
|
12116
13921
|
try {
|
|
12117
13922
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
12118
13923
|
const config = await loadConfig2();
|
|
@@ -12206,11 +14011,11 @@ try {
|
|
|
12206
14011
|
process.stderr.write(`[exed] FATAL: ${err instanceof Error ? err.message : String(err)}
|
|
12207
14012
|
`);
|
|
12208
14013
|
try {
|
|
12209
|
-
|
|
14014
|
+
unlinkSync9(SOCKET_PATH2);
|
|
12210
14015
|
} catch {
|
|
12211
14016
|
}
|
|
12212
14017
|
try {
|
|
12213
|
-
|
|
14018
|
+
unlinkSync9(PID_PATH2);
|
|
12214
14019
|
} catch {
|
|
12215
14020
|
}
|
|
12216
14021
|
process.exit(1);
|