@askexenow/exe-os 0.9.13 → 0.9.15
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 +1969 -156
- 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,255 @@ 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 { existsSync as existsSync17 } from "fs";
|
|
7691
|
+
import { createRequire as createRequire3 } from "module";
|
|
7692
|
+
import { pathToFileURL as pathToFileURL3 } from "url";
|
|
7693
|
+
function loadPrisma() {
|
|
7694
|
+
if (!prismaPromise) {
|
|
7695
|
+
prismaPromise = (async () => {
|
|
7696
|
+
const explicitPath = process.env.EXE_OS_PRISMA_CLIENT_PATH;
|
|
7697
|
+
if (explicitPath) {
|
|
7698
|
+
const mod2 = await import(pathToFileURL3(explicitPath).href);
|
|
7699
|
+
const Ctor2 = mod2.PrismaClient ?? mod2.default?.PrismaClient;
|
|
7700
|
+
if (!Ctor2) throw new Error(`No PrismaClient at ${explicitPath}`);
|
|
7701
|
+
return new Ctor2();
|
|
7702
|
+
}
|
|
7703
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path19.join(os12.homedir(), "exe-db");
|
|
7704
|
+
const req = createRequire3(path19.join(exeDbRoot, "package.json"));
|
|
7705
|
+
const entry = req.resolve("@prisma/client");
|
|
7706
|
+
const mod = await import(pathToFileURL3(entry).href);
|
|
7707
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
7708
|
+
if (!Ctor) throw new Error(`No PrismaClient in ${entry}`);
|
|
7709
|
+
return new Ctor();
|
|
7710
|
+
})();
|
|
7711
|
+
}
|
|
7712
|
+
return prismaPromise;
|
|
7713
|
+
}
|
|
7714
|
+
function setProjectionWorkerPrismaClientForTests(client) {
|
|
7715
|
+
prismaPromise = client ? Promise.resolve(client) : null;
|
|
7716
|
+
}
|
|
7717
|
+
function resetProjectionWorkerForTests() {
|
|
7718
|
+
running = false;
|
|
7719
|
+
if (pollTimer) {
|
|
7720
|
+
clearTimeout(pollTimer);
|
|
7721
|
+
pollTimer = null;
|
|
7722
|
+
}
|
|
7723
|
+
prismaPromise = null;
|
|
7724
|
+
}
|
|
7725
|
+
async function processBatch() {
|
|
7726
|
+
const prisma = await loadPrisma();
|
|
7727
|
+
const events = await prisma.$queryRawUnsafe(
|
|
7728
|
+
`SELECT "id", "source", "source_id", "event_type", "payload", "metadata", "timestamp"
|
|
7729
|
+
FROM "raw"."raw_events"
|
|
7730
|
+
WHERE "processed_at" IS NULL
|
|
7731
|
+
ORDER BY "timestamp" ASC
|
|
7732
|
+
LIMIT $1`,
|
|
7733
|
+
BATCH_SIZE
|
|
7734
|
+
);
|
|
7735
|
+
if (events.length === 0) return 0;
|
|
7736
|
+
let processed = 0;
|
|
7737
|
+
for (const event of events) {
|
|
7738
|
+
try {
|
|
7739
|
+
const handler = projectionHandlers[event.source] ?? defaultHandler;
|
|
7740
|
+
const result = await handler(event, prisma);
|
|
7741
|
+
await prisma.$executeRawUnsafe(
|
|
7742
|
+
`UPDATE "raw"."raw_events"
|
|
7743
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
7744
|
+
WHERE "id" = $2`,
|
|
7745
|
+
JSON.stringify({ targets: result.targets, skipped: result.skipped }),
|
|
7746
|
+
event.id
|
|
7747
|
+
);
|
|
7748
|
+
processed++;
|
|
7749
|
+
} catch (err) {
|
|
7750
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7751
|
+
process.stderr.write(
|
|
7752
|
+
`[projection-worker] Error processing event ${event.id}: ${message}
|
|
7753
|
+
`
|
|
7754
|
+
);
|
|
7755
|
+
await prisma.$executeRawUnsafe(
|
|
7756
|
+
`UPDATE "raw"."raw_events"
|
|
7757
|
+
SET "processed_at" = NOW(), "projections" = $1::jsonb
|
|
7758
|
+
WHERE "id" = $2`,
|
|
7759
|
+
JSON.stringify({ error: message }),
|
|
7760
|
+
event.id
|
|
7761
|
+
);
|
|
7762
|
+
}
|
|
7763
|
+
}
|
|
7764
|
+
return processed;
|
|
7765
|
+
}
|
|
7766
|
+
function startProjectionWorker() {
|
|
7767
|
+
if (running) return;
|
|
7768
|
+
if (!process.env.DATABASE_URL) {
|
|
7769
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path19.join(os12.homedir(), "exe-db");
|
|
7770
|
+
if (!existsSync17(path19.join(exeDbRoot, "package.json"))) {
|
|
7771
|
+
process.stderr.write("[projection-worker] Skipped \u2014 no exe-db found. Set DATABASE_URL or EXE_DB_ROOT to enable.\n");
|
|
7772
|
+
return;
|
|
7773
|
+
}
|
|
7774
|
+
}
|
|
7775
|
+
running = true;
|
|
7776
|
+
process.stderr.write("[projection-worker] Starting...\n");
|
|
7777
|
+
const tick = async () => {
|
|
7778
|
+
if (!running) return;
|
|
7779
|
+
try {
|
|
7780
|
+
const count = await processBatch();
|
|
7781
|
+
if (count > 0) {
|
|
7782
|
+
process.stderr.write(`[projection-worker] Processed ${count} events.
|
|
7783
|
+
`);
|
|
7784
|
+
}
|
|
7785
|
+
} catch (err) {
|
|
7786
|
+
process.stderr.write(
|
|
7787
|
+
`[projection-worker] Poll error: ${err instanceof Error ? err.message : String(err)}
|
|
7788
|
+
`
|
|
7789
|
+
);
|
|
7790
|
+
}
|
|
7791
|
+
if (running) {
|
|
7792
|
+
pollTimer = setTimeout(tick, POLL_INTERVAL_MS);
|
|
7793
|
+
}
|
|
7794
|
+
};
|
|
7795
|
+
void tick();
|
|
7796
|
+
}
|
|
7797
|
+
function stopProjectionWorker() {
|
|
7798
|
+
running = false;
|
|
7799
|
+
if (pollTimer) {
|
|
7800
|
+
clearTimeout(pollTimer);
|
|
7801
|
+
pollTimer = null;
|
|
7802
|
+
}
|
|
7803
|
+
process.stderr.write("[projection-worker] Stopped.\n");
|
|
7804
|
+
}
|
|
7805
|
+
async function processProjectionBatch() {
|
|
7806
|
+
return processBatch();
|
|
7807
|
+
}
|
|
7808
|
+
var prismaPromise, projectionHandlers, projectionHandlersForTests, defaultHandler, BATCH_SIZE, POLL_INTERVAL_MS, running, pollTimer;
|
|
7809
|
+
var init_projection_worker = __esm({
|
|
7810
|
+
"src/lib/projection-worker.ts"() {
|
|
7811
|
+
"use strict";
|
|
7812
|
+
prismaPromise = null;
|
|
7813
|
+
projectionHandlers = {
|
|
7814
|
+
async whatsapp(event, prisma) {
|
|
7815
|
+
const targets = [];
|
|
7816
|
+
const payload = event.payload;
|
|
7817
|
+
await prisma.$executeRawUnsafe(
|
|
7818
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
7819
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
7820
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7821
|
+
crypto.randomUUID(),
|
|
7822
|
+
"system",
|
|
7823
|
+
"ingest",
|
|
7824
|
+
"projection-worker",
|
|
7825
|
+
"ingest_raw",
|
|
7826
|
+
"exe-os",
|
|
7827
|
+
JSON.stringify(payload),
|
|
7828
|
+
"raw",
|
|
7829
|
+
"whatsapp",
|
|
7830
|
+
event.event_type
|
|
7831
|
+
);
|
|
7832
|
+
targets.push("memory");
|
|
7833
|
+
return { targets };
|
|
7834
|
+
},
|
|
7835
|
+
async shopify(event, prisma) {
|
|
7836
|
+
const targets = [];
|
|
7837
|
+
const payload = event.payload;
|
|
7838
|
+
await prisma.$executeRawUnsafe(
|
|
7839
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
7840
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
7841
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7842
|
+
crypto.randomUUID(),
|
|
7843
|
+
"system",
|
|
7844
|
+
"ingest",
|
|
7845
|
+
"projection-worker",
|
|
7846
|
+
"ingest_raw",
|
|
7847
|
+
"exe-os",
|
|
7848
|
+
JSON.stringify(payload),
|
|
7849
|
+
"raw",
|
|
7850
|
+
"shopify",
|
|
7851
|
+
event.event_type
|
|
7852
|
+
);
|
|
7853
|
+
targets.push("memory");
|
|
7854
|
+
return { targets };
|
|
7855
|
+
},
|
|
7856
|
+
async cloud_sync(event, prisma) {
|
|
7857
|
+
const targets = [];
|
|
7858
|
+
const payload = event.payload;
|
|
7859
|
+
await prisma.$executeRawUnsafe(
|
|
7860
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
7861
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
7862
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7863
|
+
payload.id ?? crypto.randomUUID(),
|
|
7864
|
+
payload.agent_id ?? "system",
|
|
7865
|
+
payload.agent_role ?? "ingest",
|
|
7866
|
+
payload.session_id ?? "projection-worker",
|
|
7867
|
+
payload.tool_name ?? "cloud_sync",
|
|
7868
|
+
payload.project_name ?? "exe-os",
|
|
7869
|
+
payload.raw_text ?? JSON.stringify(payload),
|
|
7870
|
+
payload.memory_type ?? "raw",
|
|
7871
|
+
"cloud_sync",
|
|
7872
|
+
event.event_type,
|
|
7873
|
+
payload.domain ?? null
|
|
7874
|
+
);
|
|
7875
|
+
targets.push("memory");
|
|
7876
|
+
return { targets };
|
|
7877
|
+
},
|
|
7878
|
+
async external_agent(event, prisma) {
|
|
7879
|
+
const targets = [];
|
|
7880
|
+
const payload = event.payload;
|
|
7881
|
+
await prisma.$executeRawUnsafe(
|
|
7882
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent", "domain")
|
|
7883
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
7884
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7885
|
+
crypto.randomUUID(),
|
|
7886
|
+
"system",
|
|
7887
|
+
"ingest",
|
|
7888
|
+
"projection-worker",
|
|
7889
|
+
"ingest_raw",
|
|
7890
|
+
"exe-os",
|
|
7891
|
+
JSON.stringify(payload),
|
|
7892
|
+
"raw",
|
|
7893
|
+
"external_agent",
|
|
7894
|
+
event.event_type,
|
|
7895
|
+
payload.domain ?? null
|
|
7896
|
+
);
|
|
7897
|
+
targets.push("memory");
|
|
7898
|
+
return { targets };
|
|
7899
|
+
}
|
|
7900
|
+
};
|
|
7901
|
+
projectionHandlersForTests = projectionHandlers;
|
|
7902
|
+
defaultHandler = async (event, prisma) => {
|
|
7903
|
+
await prisma.$executeRawUnsafe(
|
|
7904
|
+
`INSERT INTO "memory"."memory_records" ("id", "agent_id", "agent_role", "session_id", "tool_name", "project_name", "raw_text", "memory_type", "source_type", "intent")
|
|
7905
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
7906
|
+
ON CONFLICT ("id") DO NOTHING`,
|
|
7907
|
+
crypto.randomUUID(),
|
|
7908
|
+
"system",
|
|
7909
|
+
"ingest",
|
|
7910
|
+
"projection-worker",
|
|
7911
|
+
"ingest_raw",
|
|
7912
|
+
"exe-os",
|
|
7913
|
+
JSON.stringify(event.payload),
|
|
7914
|
+
"raw",
|
|
7915
|
+
event.source,
|
|
7916
|
+
event.event_type
|
|
7917
|
+
);
|
|
7918
|
+
return { targets: ["memory"] };
|
|
7919
|
+
};
|
|
7920
|
+
BATCH_SIZE = 50;
|
|
7921
|
+
POLL_INTERVAL_MS = 1e4;
|
|
7922
|
+
running = false;
|
|
7923
|
+
pollTimer = null;
|
|
7924
|
+
}
|
|
7925
|
+
});
|
|
7926
|
+
|
|
7636
7927
|
// src/lib/shard-manager.ts
|
|
7637
7928
|
var shard_manager_exports = {};
|
|
7638
7929
|
__export(shard_manager_exports, {
|
|
@@ -7647,12 +7938,12 @@ __export(shard_manager_exports, {
|
|
|
7647
7938
|
listShards: () => listShards,
|
|
7648
7939
|
shardExists: () => shardExists
|
|
7649
7940
|
});
|
|
7650
|
-
import
|
|
7651
|
-
import { existsSync as
|
|
7941
|
+
import path20 from "path";
|
|
7942
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync7, readdirSync as readdirSync4 } from "fs";
|
|
7652
7943
|
import { createClient as createClient2 } from "@libsql/client";
|
|
7653
7944
|
function initShardManager(encryptionKey) {
|
|
7654
7945
|
_encryptionKey = encryptionKey;
|
|
7655
|
-
if (!
|
|
7946
|
+
if (!existsSync18(SHARDS_DIR)) {
|
|
7656
7947
|
mkdirSync7(SHARDS_DIR, { recursive: true });
|
|
7657
7948
|
}
|
|
7658
7949
|
_shardingEnabled = true;
|
|
@@ -7682,7 +7973,7 @@ function getShardClient(projectName) {
|
|
|
7682
7973
|
while (_shards.size >= MAX_OPEN_SHARDS) {
|
|
7683
7974
|
evictLRU();
|
|
7684
7975
|
}
|
|
7685
|
-
const dbPath =
|
|
7976
|
+
const dbPath = path20.join(SHARDS_DIR, `${safeName}.db`);
|
|
7686
7977
|
const client = createClient2({
|
|
7687
7978
|
url: `file:${dbPath}`,
|
|
7688
7979
|
encryptionKey: _encryptionKey
|
|
@@ -7693,10 +7984,10 @@ function getShardClient(projectName) {
|
|
|
7693
7984
|
}
|
|
7694
7985
|
function shardExists(projectName) {
|
|
7695
7986
|
const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
7696
|
-
return
|
|
7987
|
+
return existsSync18(path20.join(SHARDS_DIR, `${safeName}.db`));
|
|
7697
7988
|
}
|
|
7698
7989
|
function listShards() {
|
|
7699
|
-
if (!
|
|
7990
|
+
if (!existsSync18(SHARDS_DIR)) return [];
|
|
7700
7991
|
return readdirSync4(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
|
|
7701
7992
|
}
|
|
7702
7993
|
async function ensureShardSchema(client) {
|
|
@@ -7943,7 +8234,7 @@ var init_shard_manager = __esm({
|
|
|
7943
8234
|
"src/lib/shard-manager.ts"() {
|
|
7944
8235
|
"use strict";
|
|
7945
8236
|
init_config();
|
|
7946
|
-
SHARDS_DIR =
|
|
8237
|
+
SHARDS_DIR = path20.join(EXE_AI_DIR, "shards");
|
|
7947
8238
|
SHARD_IDLE_MS = 5 * 60 * 1e3;
|
|
7948
8239
|
MAX_OPEN_SHARDS = 10;
|
|
7949
8240
|
EVICTION_INTERVAL_MS = 60 * 1e3;
|
|
@@ -7965,14 +8256,14 @@ __export(keychain_exports, {
|
|
|
7965
8256
|
setMasterKey: () => setMasterKey
|
|
7966
8257
|
});
|
|
7967
8258
|
import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
|
|
7968
|
-
import { existsSync as
|
|
7969
|
-
import
|
|
7970
|
-
import
|
|
8259
|
+
import { existsSync as existsSync19 } from "fs";
|
|
8260
|
+
import path21 from "path";
|
|
8261
|
+
import os13 from "os";
|
|
7971
8262
|
function getKeyDir() {
|
|
7972
|
-
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ??
|
|
8263
|
+
return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path21.join(os13.homedir(), ".exe-os");
|
|
7973
8264
|
}
|
|
7974
8265
|
function getKeyPath() {
|
|
7975
|
-
return
|
|
8266
|
+
return path21.join(getKeyDir(), "master.key");
|
|
7976
8267
|
}
|
|
7977
8268
|
async function tryKeytar() {
|
|
7978
8269
|
try {
|
|
@@ -7993,9 +8284,9 @@ async function getMasterKey() {
|
|
|
7993
8284
|
}
|
|
7994
8285
|
}
|
|
7995
8286
|
const keyPath = getKeyPath();
|
|
7996
|
-
if (!
|
|
8287
|
+
if (!existsSync19(keyPath)) {
|
|
7997
8288
|
process.stderr.write(
|
|
7998
|
-
`[keychain] Key not found at ${keyPath} (HOME=${
|
|
8289
|
+
`[keychain] Key not found at ${keyPath} (HOME=${os13.homedir()}, EXE_OS_DIR=${process.env.EXE_OS_DIR ?? "unset"})
|
|
7999
8290
|
`
|
|
8000
8291
|
);
|
|
8001
8292
|
return null;
|
|
@@ -8036,7 +8327,7 @@ async function deleteMasterKey() {
|
|
|
8036
8327
|
}
|
|
8037
8328
|
}
|
|
8038
8329
|
const keyPath = getKeyPath();
|
|
8039
|
-
if (
|
|
8330
|
+
if (existsSync19(keyPath)) {
|
|
8040
8331
|
await unlink(keyPath);
|
|
8041
8332
|
}
|
|
8042
8333
|
}
|
|
@@ -9215,16 +9506,16 @@ async function pushToWiki(consolidation, config) {
|
|
|
9215
9506
|
const contentLines = consolidation.rawText.split("\n").filter((l) => l.trim() && !l.startsWith("CONSOLIDATION") && !l.match(/^[A-Z\s]+:$/)).join(" ");
|
|
9216
9507
|
const keywords = contentLines.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
|
|
9217
9508
|
let bestMatch = null;
|
|
9218
|
-
for (const
|
|
9219
|
-
if (!
|
|
9220
|
-
const titleWords =
|
|
9509
|
+
for (const doc2 of docs) {
|
|
9510
|
+
if (!doc2.id || !doc2.title) continue;
|
|
9511
|
+
const titleWords = doc2.title.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 3);
|
|
9221
9512
|
if (titleWords.length === 0) continue;
|
|
9222
9513
|
const matchCount = titleWords.filter(
|
|
9223
9514
|
(tw) => keywords.some((k) => k.includes(tw) || tw.includes(k))
|
|
9224
9515
|
).length;
|
|
9225
9516
|
const score = matchCount / titleWords.length;
|
|
9226
9517
|
if (score > (bestMatch?.score ?? 0)) {
|
|
9227
|
-
bestMatch = { id:
|
|
9518
|
+
bestMatch = { id: doc2.id, title: doc2.title, score };
|
|
9228
9519
|
}
|
|
9229
9520
|
}
|
|
9230
9521
|
if (bestMatch && bestMatch.score >= config.wikiAutoUpdateThreshold) {
|
|
@@ -9453,10 +9744,10 @@ async function disposeEmbedder() {
|
|
|
9453
9744
|
async function embedDirect(text) {
|
|
9454
9745
|
const llamaCpp = await import("node-llama-cpp");
|
|
9455
9746
|
const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
9456
|
-
const { existsSync:
|
|
9457
|
-
const
|
|
9458
|
-
const modelPath =
|
|
9459
|
-
if (!
|
|
9747
|
+
const { existsSync: existsSync24 } = await import("fs");
|
|
9748
|
+
const path28 = await import("path");
|
|
9749
|
+
const modelPath = path28.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
|
|
9750
|
+
if (!existsSync24(modelPath)) {
|
|
9460
9751
|
throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
|
|
9461
9752
|
}
|
|
9462
9753
|
const llama = await llamaCpp.getLlama();
|
|
@@ -9484,66 +9775,1551 @@ var init_embedder = __esm({
|
|
|
9484
9775
|
}
|
|
9485
9776
|
});
|
|
9486
9777
|
|
|
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
|
|
9778
|
+
// src/lib/crypto.ts
|
|
9779
|
+
import crypto8 from "crypto";
|
|
9780
|
+
function initSyncCrypto(masterKey) {
|
|
9781
|
+
if (masterKey.length !== 32) {
|
|
9782
|
+
throw new Error(`Master key must be 32 bytes, got ${masterKey.length}`);
|
|
9783
|
+
}
|
|
9784
|
+
_syncKey = Buffer.from(
|
|
9785
|
+
crypto8.hkdfSync("sha256", masterKey, "", SYNC_HKDF_INFO, 32)
|
|
9497
9786
|
);
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9787
|
+
}
|
|
9788
|
+
function isSyncCryptoInitialized() {
|
|
9789
|
+
return _syncKey !== null;
|
|
9790
|
+
}
|
|
9791
|
+
function requireSyncKey() {
|
|
9792
|
+
if (!_syncKey) {
|
|
9793
|
+
throw new Error("Sync crypto not initialized. Call initSyncCrypto(masterKey) first.");
|
|
9794
|
+
}
|
|
9795
|
+
return _syncKey;
|
|
9796
|
+
}
|
|
9797
|
+
function encryptSyncBlob(data) {
|
|
9798
|
+
const key = requireSyncKey();
|
|
9799
|
+
const iv = crypto8.randomBytes(IV_LENGTH);
|
|
9800
|
+
const cipher = crypto8.createCipheriv(ALGORITHM, key, iv);
|
|
9801
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
9802
|
+
const tag = cipher.getAuthTag();
|
|
9803
|
+
return Buffer.concat([iv, encrypted, tag]).toString("base64");
|
|
9804
|
+
}
|
|
9805
|
+
function decryptSyncBlob(ciphertext) {
|
|
9806
|
+
const key = requireSyncKey();
|
|
9807
|
+
const combined = Buffer.from(ciphertext, "base64");
|
|
9808
|
+
if (combined.length < IV_LENGTH + TAG_LENGTH) {
|
|
9809
|
+
throw new Error("Sync blob too short to contain IV + tag");
|
|
9810
|
+
}
|
|
9811
|
+
const iv = combined.subarray(0, IV_LENGTH);
|
|
9812
|
+
const tag = combined.subarray(combined.length - TAG_LENGTH);
|
|
9813
|
+
const encrypted = combined.subarray(IV_LENGTH, combined.length - TAG_LENGTH);
|
|
9814
|
+
const decipher = crypto8.createDecipheriv(ALGORITHM, key, iv);
|
|
9815
|
+
decipher.setAuthTag(tag);
|
|
9816
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
9817
|
+
}
|
|
9818
|
+
var ALGORITHM, IV_LENGTH, TAG_LENGTH, SYNC_HKDF_INFO, _syncKey;
|
|
9819
|
+
var init_crypto = __esm({
|
|
9820
|
+
"src/lib/crypto.ts"() {
|
|
9821
|
+
"use strict";
|
|
9822
|
+
ALGORITHM = "aes-256-gcm";
|
|
9823
|
+
IV_LENGTH = 12;
|
|
9824
|
+
TAG_LENGTH = 16;
|
|
9825
|
+
SYNC_HKDF_INFO = "exe-mem-sync-v2";
|
|
9826
|
+
_syncKey = null;
|
|
9503
9827
|
}
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9828
|
+
});
|
|
9829
|
+
|
|
9830
|
+
// src/lib/compress.ts
|
|
9831
|
+
import { brotliCompressSync, brotliDecompressSync, constants } from "zlib";
|
|
9832
|
+
function compress(input) {
|
|
9833
|
+
if (input.length === 0) return Buffer.alloc(0);
|
|
9834
|
+
return brotliCompressSync(input, {
|
|
9835
|
+
params: {
|
|
9836
|
+
[constants.BROTLI_PARAM_QUALITY]: 4
|
|
9837
|
+
}
|
|
9838
|
+
});
|
|
9839
|
+
}
|
|
9840
|
+
function decompress(input) {
|
|
9841
|
+
if (input.length === 0) return Buffer.alloc(0);
|
|
9842
|
+
return brotliDecompressSync(input);
|
|
9843
|
+
}
|
|
9844
|
+
var init_compress = __esm({
|
|
9845
|
+
"src/lib/compress.ts"() {
|
|
9846
|
+
"use strict";
|
|
9509
9847
|
}
|
|
9510
|
-
|
|
9511
|
-
|
|
9848
|
+
});
|
|
9849
|
+
|
|
9850
|
+
// src/lib/crdt-sync.ts
|
|
9851
|
+
var crdt_sync_exports = {};
|
|
9852
|
+
__export(crdt_sync_exports, {
|
|
9853
|
+
_setStatePath: () => _setStatePath,
|
|
9854
|
+
applyRemoteUpdate: () => applyRemoteUpdate,
|
|
9855
|
+
destroyCrdtDoc: () => destroyCrdtDoc,
|
|
9856
|
+
getDiffUpdate: () => getDiffUpdate,
|
|
9857
|
+
getFullState: () => getFullState,
|
|
9858
|
+
getStateVector: () => getStateVector,
|
|
9859
|
+
importExistingBehaviors: () => importExistingBehaviors,
|
|
9860
|
+
importExistingMemories: () => importExistingMemories,
|
|
9861
|
+
initCrdtDoc: () => initCrdtDoc,
|
|
9862
|
+
isCrdtSyncEnabled: () => isCrdtSyncEnabled,
|
|
9863
|
+
onUpdate: () => onUpdate,
|
|
9864
|
+
readAllBehaviors: () => readAllBehaviors,
|
|
9865
|
+
readAllMemories: () => readAllMemories,
|
|
9866
|
+
rebuildFromDb: () => rebuildFromDb
|
|
9867
|
+
});
|
|
9868
|
+
import * as Y from "yjs";
|
|
9869
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync10, existsSync as existsSync20, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7 } from "fs";
|
|
9870
|
+
import path22 from "path";
|
|
9871
|
+
import { homedir as homedir2 } from "os";
|
|
9872
|
+
function getStatePath() {
|
|
9873
|
+
return _statePathOverride ?? DEFAULT_STATE_PATH;
|
|
9874
|
+
}
|
|
9875
|
+
function _setStatePath(p) {
|
|
9876
|
+
_statePathOverride = p;
|
|
9877
|
+
}
|
|
9878
|
+
function initCrdtDoc() {
|
|
9879
|
+
if (doc) return doc;
|
|
9880
|
+
doc = new Y.Doc();
|
|
9881
|
+
const sp = getStatePath();
|
|
9882
|
+
if (existsSync20(sp)) {
|
|
9883
|
+
try {
|
|
9884
|
+
const state = readFileSync15(sp);
|
|
9885
|
+
Y.applyUpdate(doc, new Uint8Array(state));
|
|
9886
|
+
} catch {
|
|
9887
|
+
console.warn("[crdt-sync] WARN: corrupted state file, rebuilding from DB");
|
|
9888
|
+
try {
|
|
9889
|
+
unlinkSync7(sp);
|
|
9890
|
+
} catch {
|
|
9891
|
+
}
|
|
9892
|
+
rebuildFromDb().catch((err) => {
|
|
9893
|
+
console.warn("[crdt-sync] rebuild from DB failed:", err);
|
|
9894
|
+
});
|
|
9895
|
+
}
|
|
9512
9896
|
}
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9897
|
+
doc.on("update", () => {
|
|
9898
|
+
persistState();
|
|
9899
|
+
});
|
|
9900
|
+
return doc;
|
|
9901
|
+
}
|
|
9902
|
+
function getMemoriesMap() {
|
|
9903
|
+
const d = initCrdtDoc();
|
|
9904
|
+
return d.getMap("memories");
|
|
9905
|
+
}
|
|
9906
|
+
function getBehaviorsMap() {
|
|
9907
|
+
const d = initCrdtDoc();
|
|
9908
|
+
return d.getMap("behaviors");
|
|
9909
|
+
}
|
|
9910
|
+
function applyRemoteUpdate(update) {
|
|
9911
|
+
const d = initCrdtDoc();
|
|
9912
|
+
Y.applyUpdate(d, update);
|
|
9913
|
+
}
|
|
9914
|
+
function getFullState() {
|
|
9915
|
+
const d = initCrdtDoc();
|
|
9916
|
+
return Y.encodeStateAsUpdate(d);
|
|
9917
|
+
}
|
|
9918
|
+
function getDiffUpdate(remoteStateVector) {
|
|
9919
|
+
const d = initCrdtDoc();
|
|
9920
|
+
return Y.encodeStateAsUpdate(d, remoteStateVector);
|
|
9921
|
+
}
|
|
9922
|
+
function getStateVector() {
|
|
9923
|
+
const d = initCrdtDoc();
|
|
9924
|
+
return Y.encodeStateVector(d);
|
|
9925
|
+
}
|
|
9926
|
+
function importExistingMemories(memories) {
|
|
9927
|
+
const map = getMemoriesMap();
|
|
9928
|
+
const d = initCrdtDoc();
|
|
9929
|
+
let imported = 0;
|
|
9930
|
+
d.transact(() => {
|
|
9931
|
+
for (const mem of memories) {
|
|
9932
|
+
if (!mem.id) continue;
|
|
9933
|
+
if (map.has(mem.id)) continue;
|
|
9934
|
+
const entry = new Y.Map();
|
|
9935
|
+
entry.set("id", mem.id);
|
|
9936
|
+
entry.set("agent_id", mem.agent_id ?? null);
|
|
9937
|
+
entry.set("agent_role", mem.agent_role ?? null);
|
|
9938
|
+
entry.set("session_id", mem.session_id ?? null);
|
|
9939
|
+
entry.set("timestamp", mem.timestamp ?? null);
|
|
9940
|
+
entry.set("tool_name", mem.tool_name ?? null);
|
|
9941
|
+
entry.set("project_name", mem.project_name ?? null);
|
|
9942
|
+
entry.set("has_error", mem.has_error ?? 0);
|
|
9943
|
+
entry.set("raw_text", mem.raw_text ?? "");
|
|
9944
|
+
entry.set("version", mem.version ?? 0);
|
|
9945
|
+
entry.set("author_device_id", mem.author_device_id ?? null);
|
|
9946
|
+
entry.set("scope", mem.scope ?? "business");
|
|
9947
|
+
map.set(mem.id, entry);
|
|
9948
|
+
imported++;
|
|
9516
9949
|
}
|
|
9517
|
-
|
|
9518
|
-
|
|
9950
|
+
});
|
|
9951
|
+
return imported;
|
|
9952
|
+
}
|
|
9953
|
+
function importExistingBehaviors(behaviors) {
|
|
9954
|
+
const map = getBehaviorsMap();
|
|
9955
|
+
const d = initCrdtDoc();
|
|
9956
|
+
let imported = 0;
|
|
9957
|
+
d.transact(() => {
|
|
9958
|
+
for (const beh of behaviors) {
|
|
9959
|
+
if (!beh.id) continue;
|
|
9960
|
+
if (map.has(beh.id)) continue;
|
|
9961
|
+
const entry = new Y.Map();
|
|
9962
|
+
entry.set("id", beh.id);
|
|
9963
|
+
entry.set("agent_id", beh.agent_id ?? null);
|
|
9964
|
+
entry.set("project_name", beh.project_name ?? null);
|
|
9965
|
+
entry.set("domain", beh.domain ?? null);
|
|
9966
|
+
entry.set("content", beh.content ?? null);
|
|
9967
|
+
entry.set("active", beh.active ?? 1);
|
|
9968
|
+
entry.set("priority", beh.priority ?? "p1");
|
|
9969
|
+
entry.set("created_at", beh.created_at ?? null);
|
|
9970
|
+
entry.set("updated_at", beh.updated_at ?? null);
|
|
9971
|
+
map.set(beh.id, entry);
|
|
9972
|
+
imported++;
|
|
9519
9973
|
}
|
|
9520
|
-
|
|
9521
|
-
|
|
9974
|
+
});
|
|
9975
|
+
return imported;
|
|
9976
|
+
}
|
|
9977
|
+
function readAllMemories() {
|
|
9978
|
+
const map = getMemoriesMap();
|
|
9979
|
+
const records = [];
|
|
9980
|
+
map.forEach((entry, id) => {
|
|
9981
|
+
records.push({
|
|
9982
|
+
id,
|
|
9983
|
+
agent_id: entry.get("agent_id"),
|
|
9984
|
+
agent_role: entry.get("agent_role"),
|
|
9985
|
+
session_id: entry.get("session_id"),
|
|
9986
|
+
timestamp: entry.get("timestamp"),
|
|
9987
|
+
tool_name: entry.get("tool_name"),
|
|
9988
|
+
project_name: entry.get("project_name"),
|
|
9989
|
+
has_error: entry.get("has_error"),
|
|
9990
|
+
raw_text: entry.get("raw_text"),
|
|
9991
|
+
version: entry.get("version"),
|
|
9992
|
+
author_device_id: entry.get("author_device_id"),
|
|
9993
|
+
scope: entry.get("scope")
|
|
9994
|
+
});
|
|
9995
|
+
});
|
|
9996
|
+
return records;
|
|
9997
|
+
}
|
|
9998
|
+
function readAllBehaviors() {
|
|
9999
|
+
const map = getBehaviorsMap();
|
|
10000
|
+
const records = [];
|
|
10001
|
+
map.forEach((entry, id) => {
|
|
10002
|
+
records.push({
|
|
10003
|
+
id,
|
|
10004
|
+
agent_id: entry.get("agent_id"),
|
|
10005
|
+
project_name: entry.get("project_name"),
|
|
10006
|
+
domain: entry.get("domain"),
|
|
10007
|
+
content: entry.get("content"),
|
|
10008
|
+
active: entry.get("active"),
|
|
10009
|
+
priority: entry.get("priority"),
|
|
10010
|
+
created_at: entry.get("created_at"),
|
|
10011
|
+
updated_at: entry.get("updated_at")
|
|
10012
|
+
});
|
|
10013
|
+
});
|
|
10014
|
+
return records;
|
|
10015
|
+
}
|
|
10016
|
+
function onUpdate(callback) {
|
|
10017
|
+
const d = initCrdtDoc();
|
|
10018
|
+
const handler = (update) => callback(update);
|
|
10019
|
+
d.on("update", handler);
|
|
10020
|
+
return () => d.off("update", handler);
|
|
10021
|
+
}
|
|
10022
|
+
function persistState() {
|
|
10023
|
+
if (!doc) return;
|
|
10024
|
+
try {
|
|
10025
|
+
const sp = getStatePath();
|
|
10026
|
+
const dir = path22.dirname(sp);
|
|
10027
|
+
if (!existsSync20(dir)) mkdirSync8(dir, { recursive: true });
|
|
10028
|
+
const state = Y.encodeStateAsUpdate(doc);
|
|
10029
|
+
writeFileSync10(sp, Buffer.from(state));
|
|
10030
|
+
} catch {
|
|
10031
|
+
}
|
|
10032
|
+
}
|
|
10033
|
+
function isCrdtSyncEnabled() {
|
|
10034
|
+
return process.env.EXE_CRDT_SYNC !== "0";
|
|
10035
|
+
}
|
|
10036
|
+
async function rebuildFromDb() {
|
|
10037
|
+
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
10038
|
+
const client = getClient2();
|
|
10039
|
+
const result = await client.execute(
|
|
10040
|
+
"SELECT id, agent_id, agent_role, session_id, timestamp, tool_name, project_name, has_error, raw_text, version, author_device_id, scope FROM memories"
|
|
10041
|
+
);
|
|
10042
|
+
const memories = result.rows.map((row) => ({
|
|
10043
|
+
id: String(row.id),
|
|
10044
|
+
agent_id: row.agent_id,
|
|
10045
|
+
agent_role: row.agent_role,
|
|
10046
|
+
session_id: row.session_id,
|
|
10047
|
+
timestamp: row.timestamp,
|
|
10048
|
+
tool_name: row.tool_name,
|
|
10049
|
+
project_name: row.project_name,
|
|
10050
|
+
has_error: row.has_error,
|
|
10051
|
+
raw_text: row.raw_text,
|
|
10052
|
+
version: row.version,
|
|
10053
|
+
author_device_id: row.author_device_id,
|
|
10054
|
+
scope: row.scope
|
|
10055
|
+
}));
|
|
10056
|
+
const count = importExistingMemories(memories);
|
|
10057
|
+
persistState();
|
|
10058
|
+
return count;
|
|
10059
|
+
}
|
|
10060
|
+
function destroyCrdtDoc() {
|
|
10061
|
+
if (doc) {
|
|
10062
|
+
doc.destroy();
|
|
10063
|
+
doc = null;
|
|
10064
|
+
}
|
|
10065
|
+
}
|
|
10066
|
+
var DEFAULT_STATE_PATH, _statePathOverride, doc;
|
|
10067
|
+
var init_crdt_sync = __esm({
|
|
10068
|
+
"src/lib/crdt-sync.ts"() {
|
|
10069
|
+
"use strict";
|
|
10070
|
+
DEFAULT_STATE_PATH = path22.join(homedir2(), ".exe-os", "crdt-state.bin");
|
|
10071
|
+
_statePathOverride = null;
|
|
10072
|
+
doc = null;
|
|
10073
|
+
}
|
|
10074
|
+
});
|
|
10075
|
+
|
|
10076
|
+
// src/lib/cloud-sync.ts
|
|
10077
|
+
var cloud_sync_exports = {};
|
|
10078
|
+
__export(cloud_sync_exports, {
|
|
10079
|
+
assertSecureEndpoint: () => assertSecureEndpoint,
|
|
10080
|
+
buildRosterBlob: () => buildRosterBlob,
|
|
10081
|
+
cloudPull: () => cloudPull,
|
|
10082
|
+
cloudPullBehaviors: () => cloudPullBehaviors,
|
|
10083
|
+
cloudPullBlob: () => cloudPullBlob,
|
|
10084
|
+
cloudPullConversations: () => cloudPullConversations,
|
|
10085
|
+
cloudPullDocuments: () => cloudPullDocuments,
|
|
10086
|
+
cloudPullGlobalProcedures: () => cloudPullGlobalProcedures,
|
|
10087
|
+
cloudPullGraphRAG: () => cloudPullGraphRAG,
|
|
10088
|
+
cloudPullRoster: () => cloudPullRoster,
|
|
10089
|
+
cloudPullTasks: () => cloudPullTasks,
|
|
10090
|
+
cloudPush: () => cloudPush,
|
|
10091
|
+
cloudPushBehaviors: () => cloudPushBehaviors,
|
|
10092
|
+
cloudPushBlob: () => cloudPushBlob,
|
|
10093
|
+
cloudPushConversations: () => cloudPushConversations,
|
|
10094
|
+
cloudPushDocuments: () => cloudPushDocuments,
|
|
10095
|
+
cloudPushGlobalProcedures: () => cloudPushGlobalProcedures,
|
|
10096
|
+
cloudPushGraphRAG: () => cloudPushGraphRAG,
|
|
10097
|
+
cloudPushRoster: () => cloudPushRoster,
|
|
10098
|
+
cloudPushTasks: () => cloudPushTasks,
|
|
10099
|
+
cloudSync: () => cloudSync,
|
|
10100
|
+
mergeConfig: () => mergeConfig,
|
|
10101
|
+
mergeRosterFromRemote: () => mergeRosterFromRemote,
|
|
10102
|
+
pushToPostgres: () => pushToPostgres,
|
|
10103
|
+
recordRosterDeletion: () => recordRosterDeletion
|
|
10104
|
+
});
|
|
10105
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync11, existsSync as existsSync21, readdirSync as readdirSync5, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2, unlinkSync as unlinkSync8, openSync as openSync2, closeSync as closeSync2 } from "fs";
|
|
10106
|
+
import crypto9 from "crypto";
|
|
10107
|
+
import path23 from "path";
|
|
10108
|
+
import { homedir as homedir3 } from "os";
|
|
10109
|
+
function sqlSafe(v) {
|
|
10110
|
+
return v === void 0 ? null : v;
|
|
10111
|
+
}
|
|
10112
|
+
function logError(msg) {
|
|
10113
|
+
try {
|
|
10114
|
+
const logPath = path23.join(homedir3(), ".exe-os", "workers.log");
|
|
10115
|
+
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
10116
|
+
`);
|
|
10117
|
+
} catch {
|
|
10118
|
+
}
|
|
10119
|
+
}
|
|
10120
|
+
function loadPgClient() {
|
|
10121
|
+
if (_pgFailed) return null;
|
|
10122
|
+
const postgresUrl = process.env.DATABASE_URL;
|
|
10123
|
+
const configPath = path23.join(EXE_AI_DIR, "config.json");
|
|
10124
|
+
let cloudPostgresUrl;
|
|
10125
|
+
try {
|
|
10126
|
+
if (existsSync21(configPath)) {
|
|
10127
|
+
const cfg = JSON.parse(readFileSync16(configPath, "utf8"));
|
|
10128
|
+
cloudPostgresUrl = cfg.cloud?.postgresUrl;
|
|
10129
|
+
if (cfg.cloud?.syncToPostgres === false) {
|
|
10130
|
+
_pgFailed = true;
|
|
10131
|
+
return null;
|
|
10132
|
+
}
|
|
9522
10133
|
}
|
|
9523
|
-
|
|
9524
|
-
|
|
10134
|
+
} catch {
|
|
10135
|
+
}
|
|
10136
|
+
const url = postgresUrl || cloudPostgresUrl;
|
|
10137
|
+
if (!url) {
|
|
10138
|
+
_pgFailed = true;
|
|
10139
|
+
return null;
|
|
10140
|
+
}
|
|
10141
|
+
if (!_pgPromise) {
|
|
10142
|
+
_pgPromise = (async () => {
|
|
10143
|
+
const { createRequire: createRequire4 } = await import("module");
|
|
10144
|
+
const { pathToFileURL: pathToFileURL4 } = await import("url");
|
|
10145
|
+
const exeDbRoot = process.env.EXE_DB_ROOT ?? path23.join(homedir3(), "exe-db");
|
|
10146
|
+
const req = createRequire4(path23.join(exeDbRoot, "package.json"));
|
|
10147
|
+
const entry = req.resolve("@prisma/client");
|
|
10148
|
+
const mod = await import(pathToFileURL4(entry).href);
|
|
10149
|
+
const Ctor = mod.PrismaClient ?? mod.default?.PrismaClient;
|
|
10150
|
+
if (!Ctor) throw new Error("No PrismaClient");
|
|
10151
|
+
return new Ctor();
|
|
10152
|
+
})().catch(() => {
|
|
10153
|
+
_pgFailed = true;
|
|
10154
|
+
_pgPromise = null;
|
|
10155
|
+
throw new Error("pg_unavailable");
|
|
10156
|
+
});
|
|
10157
|
+
}
|
|
10158
|
+
return _pgPromise;
|
|
10159
|
+
}
|
|
10160
|
+
async function pushToPostgres(records) {
|
|
10161
|
+
const loader = loadPgClient();
|
|
10162
|
+
if (!loader) return 0;
|
|
10163
|
+
let prisma;
|
|
10164
|
+
try {
|
|
10165
|
+
prisma = await loader;
|
|
10166
|
+
} catch {
|
|
10167
|
+
return 0;
|
|
10168
|
+
}
|
|
10169
|
+
let inserted = 0;
|
|
10170
|
+
for (const rec of records) {
|
|
10171
|
+
try {
|
|
10172
|
+
await prisma.$executeRawUnsafe(
|
|
10173
|
+
`INSERT INTO raw.raw_events (id, source, source_id, event_type, payload, metadata, timestamp)
|
|
10174
|
+
VALUES (gen_random_uuid(), 'cloud_sync', $1, 'memory', $2::jsonb, $3::jsonb, $4)
|
|
10175
|
+
ON CONFLICT (source, source_id, event_type) DO NOTHING`,
|
|
10176
|
+
String(rec.id ?? ""),
|
|
10177
|
+
JSON.stringify(rec),
|
|
10178
|
+
JSON.stringify({ agent_id: rec.agent_id, project_name: rec.project_name, tool_name: rec.tool_name }),
|
|
10179
|
+
rec.timestamp ? new Date(String(rec.timestamp)) : /* @__PURE__ */ new Date()
|
|
10180
|
+
);
|
|
10181
|
+
inserted++;
|
|
10182
|
+
} catch {
|
|
9525
10183
|
}
|
|
9526
|
-
|
|
9527
|
-
|
|
10184
|
+
}
|
|
10185
|
+
return inserted;
|
|
10186
|
+
}
|
|
10187
|
+
async function withRosterLock(fn) {
|
|
10188
|
+
try {
|
|
10189
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
10190
|
+
closeSync2(fd);
|
|
10191
|
+
writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
|
|
10192
|
+
} catch (err) {
|
|
10193
|
+
if (err.code === "EEXIST") {
|
|
10194
|
+
try {
|
|
10195
|
+
const ts2 = parseInt(readFileSync16(ROSTER_LOCK_PATH, "utf-8"), 10);
|
|
10196
|
+
if (Date.now() - ts2 < LOCK_STALE_MS) {
|
|
10197
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
10198
|
+
}
|
|
10199
|
+
unlinkSync8(ROSTER_LOCK_PATH);
|
|
10200
|
+
const fd = openSync2(ROSTER_LOCK_PATH, "wx");
|
|
10201
|
+
closeSync2(fd);
|
|
10202
|
+
writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
|
|
10203
|
+
} catch (retryErr) {
|
|
10204
|
+
if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
|
|
10205
|
+
throw new Error("Roster merge already in progress \u2014 another sync is running");
|
|
10206
|
+
}
|
|
10207
|
+
} else {
|
|
10208
|
+
throw err;
|
|
9528
10209
|
}
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
10210
|
+
}
|
|
10211
|
+
try {
|
|
10212
|
+
return await fn();
|
|
10213
|
+
} finally {
|
|
10214
|
+
try {
|
|
10215
|
+
unlinkSync8(ROSTER_LOCK_PATH);
|
|
10216
|
+
} catch {
|
|
9532
10217
|
}
|
|
9533
|
-
|
|
9534
|
-
|
|
10218
|
+
}
|
|
10219
|
+
}
|
|
10220
|
+
async function fetchWithRetry(url, init) {
|
|
10221
|
+
const MAX_RETRIES4 = 3;
|
|
10222
|
+
const BASE_DELAY_MS2 = 200;
|
|
10223
|
+
let lastError;
|
|
10224
|
+
for (let attempt = 0; attempt <= MAX_RETRIES4; attempt++) {
|
|
10225
|
+
try {
|
|
10226
|
+
const signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
10227
|
+
const resp = await fetch(url, { ...init, signal });
|
|
10228
|
+
if (resp && resp.status >= 500 && attempt < MAX_RETRIES4) {
|
|
10229
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
10230
|
+
continue;
|
|
10231
|
+
}
|
|
10232
|
+
return resp;
|
|
10233
|
+
} catch (err) {
|
|
10234
|
+
lastError = err;
|
|
10235
|
+
if (attempt === MAX_RETRIES4) throw err;
|
|
10236
|
+
await new Promise((r) => setTimeout(r, BASE_DELAY_MS2 * Math.pow(2, attempt)));
|
|
9535
10237
|
}
|
|
9536
|
-
return "(unknown)";
|
|
9537
10238
|
}
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
10239
|
+
throw lastError;
|
|
10240
|
+
}
|
|
10241
|
+
function assertSecureEndpoint(endpoint) {
|
|
10242
|
+
if (endpoint.startsWith("https://")) return;
|
|
10243
|
+
if (endpoint.startsWith("http://")) {
|
|
10244
|
+
try {
|
|
10245
|
+
const parsed = new URL(endpoint);
|
|
10246
|
+
if (LOCALHOST_PATTERNS.test(parsed.hostname)) return;
|
|
10247
|
+
} catch {
|
|
9544
10248
|
return;
|
|
9545
10249
|
}
|
|
9546
|
-
|
|
10250
|
+
throw new Error(
|
|
10251
|
+
`Insecure cloud endpoint rejected: "${endpoint}". Use https:// for remote hosts. Plain http:// is only allowed for localhost.`
|
|
10252
|
+
);
|
|
10253
|
+
}
|
|
10254
|
+
}
|
|
10255
|
+
async function cloudPush(records, maxVersion, config) {
|
|
10256
|
+
if (records.length === 0) return true;
|
|
10257
|
+
assertSecureEndpoint(config.endpoint);
|
|
10258
|
+
try {
|
|
10259
|
+
const json = JSON.stringify(records);
|
|
10260
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
10261
|
+
const blob = encryptSyncBlob(compressed);
|
|
10262
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push`, {
|
|
10263
|
+
method: "POST",
|
|
10264
|
+
headers: {
|
|
10265
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10266
|
+
"Content-Type": "application/json",
|
|
10267
|
+
"X-Device-Id": loadDeviceId(),
|
|
10268
|
+
"X-Expected-Version": String(maxVersion)
|
|
10269
|
+
},
|
|
10270
|
+
body: JSON.stringify({ version: maxVersion, blob })
|
|
10271
|
+
});
|
|
10272
|
+
if (resp == null) {
|
|
10273
|
+
logError("[cloud-sync] PUSH FAILED: no response from server");
|
|
10274
|
+
return false;
|
|
10275
|
+
}
|
|
10276
|
+
if (resp.status === 409) {
|
|
10277
|
+
logError("[cloud-sync] PUSH VERSION CONFLICT \u2014 re-pull required before next push");
|
|
10278
|
+
return false;
|
|
10279
|
+
}
|
|
10280
|
+
return resp.ok;
|
|
10281
|
+
} catch (err) {
|
|
10282
|
+
logError(`[cloud-sync] PUSH FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
10283
|
+
return false;
|
|
10284
|
+
}
|
|
10285
|
+
}
|
|
10286
|
+
async function cloudPull(sinceVersion, config) {
|
|
10287
|
+
assertSecureEndpoint(config.endpoint);
|
|
10288
|
+
try {
|
|
10289
|
+
const response = await fetchWithRetry(`${config.endpoint}/sync/pull`, {
|
|
10290
|
+
method: "POST",
|
|
10291
|
+
headers: {
|
|
10292
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10293
|
+
"Content-Type": "application/json",
|
|
10294
|
+
"X-Device-Id": loadDeviceId()
|
|
10295
|
+
},
|
|
10296
|
+
body: JSON.stringify({ since_version: sinceVersion })
|
|
10297
|
+
});
|
|
10298
|
+
if (response == null) {
|
|
10299
|
+
logError("[cloud-sync] PULL FAILED: no response from server");
|
|
10300
|
+
return { records: [], maxVersion: sinceVersion };
|
|
10301
|
+
}
|
|
10302
|
+
if (!response.ok) return { records: [], maxVersion: sinceVersion };
|
|
10303
|
+
const data = await response.json();
|
|
10304
|
+
const allRecords = [];
|
|
10305
|
+
for (const { blob } of data.blobs ?? []) {
|
|
10306
|
+
try {
|
|
10307
|
+
const compressed = decryptSyncBlob(blob);
|
|
10308
|
+
const json = decompress(compressed).toString("utf8");
|
|
10309
|
+
const records = JSON.parse(json);
|
|
10310
|
+
allRecords.push(...records);
|
|
10311
|
+
} catch {
|
|
10312
|
+
continue;
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
return { records: allRecords, maxVersion: data.max_version ?? sinceVersion };
|
|
10316
|
+
} catch (err) {
|
|
10317
|
+
logError(`[cloud-sync] PULL FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
10318
|
+
return { records: [], maxVersion: sinceVersion };
|
|
10319
|
+
}
|
|
10320
|
+
}
|
|
10321
|
+
async function cloudSync(config) {
|
|
10322
|
+
if (!isSyncCryptoInitialized()) {
|
|
10323
|
+
try {
|
|
10324
|
+
const { getMasterKey: getMasterKey2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
|
|
10325
|
+
const masterKey = await getMasterKey2();
|
|
10326
|
+
if (masterKey) {
|
|
10327
|
+
initSyncCrypto(masterKey);
|
|
10328
|
+
} else {
|
|
10329
|
+
throw new Error("No master key found");
|
|
10330
|
+
}
|
|
10331
|
+
} catch (err) {
|
|
10332
|
+
throw new Error(`[cloud-sync] Cannot initialize encryption: ${err instanceof Error ? err.message : String(err)}`);
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
10335
|
+
let client;
|
|
10336
|
+
try {
|
|
10337
|
+
client = getClient();
|
|
10338
|
+
} catch {
|
|
10339
|
+
throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
|
|
10340
|
+
}
|
|
10341
|
+
try {
|
|
10342
|
+
const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
10343
|
+
await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
|
|
10344
|
+
} catch {
|
|
10345
|
+
}
|
|
10346
|
+
try {
|
|
10347
|
+
await client.execute(
|
|
10348
|
+
"CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
|
|
10349
|
+
);
|
|
10350
|
+
} catch (e) {
|
|
10351
|
+
logError(`[cloud-sync] sync_meta CREATE failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
10352
|
+
}
|
|
10353
|
+
const pullMeta = await client.execute(
|
|
10354
|
+
"SELECT value FROM sync_meta WHERE key = 'last_cloud_pull_version'"
|
|
10355
|
+
);
|
|
10356
|
+
const lastPullVersion = pullMeta.rows.length > 0 ? Number(pullMeta.rows[0].value) : 0;
|
|
10357
|
+
const pullResult = await cloudPull(lastPullVersion, config);
|
|
10358
|
+
let pulled = 0;
|
|
10359
|
+
if (pullResult.records.length > 0) {
|
|
10360
|
+
if (isCrdtSyncEnabled()) {
|
|
10361
|
+
const { initCrdtDoc: initCrdtDoc2, importExistingMemories: importExistingMemories2, readAllMemories: readAllMemories2 } = await Promise.resolve().then(() => (init_crdt_sync(), crdt_sync_exports));
|
|
10362
|
+
initCrdtDoc2();
|
|
10363
|
+
importExistingMemories2(
|
|
10364
|
+
pullResult.records.map((rec) => ({
|
|
10365
|
+
id: String(rec.id ?? ""),
|
|
10366
|
+
agent_id: rec.agent_id,
|
|
10367
|
+
agent_role: rec.agent_role,
|
|
10368
|
+
session_id: rec.session_id,
|
|
10369
|
+
timestamp: rec.timestamp,
|
|
10370
|
+
tool_name: rec.tool_name,
|
|
10371
|
+
project_name: rec.project_name,
|
|
10372
|
+
has_error: rec.has_error ?? 0,
|
|
10373
|
+
raw_text: rec.raw_text ?? "",
|
|
10374
|
+
version: rec.version ?? 0,
|
|
10375
|
+
author_device_id: rec.author_device_id,
|
|
10376
|
+
scope: rec.scope ?? "business"
|
|
10377
|
+
}))
|
|
10378
|
+
);
|
|
10379
|
+
const pulledIds = new Set(pullResult.records.map((r) => String(r.id ?? "")));
|
|
10380
|
+
const merged = readAllMemories2().filter((rec) => pulledIds.has(rec.id));
|
|
10381
|
+
const stmts = merged.map((rec) => ({
|
|
10382
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
10383
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
10384
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
10385
|
+
author_device_id, scope)
|
|
10386
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
10387
|
+
args: [
|
|
10388
|
+
sqlSafe(rec.id),
|
|
10389
|
+
sqlSafe(rec.agent_id),
|
|
10390
|
+
sqlSafe(rec.agent_role),
|
|
10391
|
+
sqlSafe(rec.session_id),
|
|
10392
|
+
sqlSafe(rec.timestamp),
|
|
10393
|
+
sqlSafe(rec.tool_name),
|
|
10394
|
+
sqlSafe(rec.project_name),
|
|
10395
|
+
sqlSafe(rec.has_error ?? 0),
|
|
10396
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
10397
|
+
sqlSafe(rec.version ?? 0),
|
|
10398
|
+
sqlSafe(rec.author_device_id),
|
|
10399
|
+
sqlSafe(rec.scope ?? "business")
|
|
10400
|
+
]
|
|
10401
|
+
}));
|
|
10402
|
+
if (stmts.length > 0) await client.batch(stmts, "write");
|
|
10403
|
+
pulled = pullResult.records.length;
|
|
10404
|
+
} else {
|
|
10405
|
+
const stmts = pullResult.records.map((rec) => ({
|
|
10406
|
+
sql: `INSERT OR REPLACE INTO memories
|
|
10407
|
+
(id, agent_id, agent_role, session_id, timestamp,
|
|
10408
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
10409
|
+
author_device_id, scope)
|
|
10410
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
10411
|
+
args: [
|
|
10412
|
+
sqlSafe(rec.id),
|
|
10413
|
+
sqlSafe(rec.agent_id),
|
|
10414
|
+
sqlSafe(rec.agent_role),
|
|
10415
|
+
sqlSafe(rec.session_id),
|
|
10416
|
+
sqlSafe(rec.timestamp),
|
|
10417
|
+
sqlSafe(rec.tool_name),
|
|
10418
|
+
sqlSafe(rec.project_name),
|
|
10419
|
+
sqlSafe(rec.has_error ?? 0),
|
|
10420
|
+
sqlSafe(rec.raw_text ?? ""),
|
|
10421
|
+
sqlSafe(rec.version ?? 0),
|
|
10422
|
+
sqlSafe(rec.author_device_id),
|
|
10423
|
+
sqlSafe(rec.scope ?? "business")
|
|
10424
|
+
]
|
|
10425
|
+
}));
|
|
10426
|
+
await client.batch(stmts, "write");
|
|
10427
|
+
pulled = pullResult.records.length;
|
|
10428
|
+
}
|
|
10429
|
+
}
|
|
10430
|
+
if (pulled > 0) {
|
|
10431
|
+
try {
|
|
10432
|
+
await pushToPostgres(pullResult.records);
|
|
10433
|
+
} catch {
|
|
10434
|
+
}
|
|
10435
|
+
}
|
|
10436
|
+
if (pullResult.maxVersion > lastPullVersion) {
|
|
10437
|
+
await client.execute({
|
|
10438
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_pull_version', ?)",
|
|
10439
|
+
args: [String(pullResult.maxVersion)]
|
|
10440
|
+
});
|
|
10441
|
+
}
|
|
10442
|
+
const pushMeta = await client.execute(
|
|
10443
|
+
"SELECT value FROM sync_meta WHERE key = 'last_cloud_push_version'"
|
|
10444
|
+
);
|
|
10445
|
+
const lastPushVersion = pushMeta.rows.length > 0 ? Number(pushMeta.rows[0].value) : 0;
|
|
10446
|
+
let pushed = 0;
|
|
10447
|
+
let batchCursor = lastPushVersion;
|
|
10448
|
+
while (true) {
|
|
10449
|
+
const recordsResult = await client.execute({
|
|
10450
|
+
sql: `SELECT id, agent_id, agent_role, session_id, timestamp,
|
|
10451
|
+
tool_name, project_name, has_error, raw_text, version,
|
|
10452
|
+
author_device_id, scope
|
|
10453
|
+
FROM memories
|
|
10454
|
+
WHERE version > ?
|
|
10455
|
+
AND (scope IS NULL OR scope != 'personal')
|
|
10456
|
+
ORDER BY version ASC
|
|
10457
|
+
LIMIT ?`,
|
|
10458
|
+
args: [batchCursor, PUSH_BATCH_SIZE]
|
|
10459
|
+
});
|
|
10460
|
+
if (recordsResult.rows.length === 0) break;
|
|
10461
|
+
const records = recordsResult.rows.map((row) => ({
|
|
10462
|
+
id: row.id,
|
|
10463
|
+
agent_id: row.agent_id,
|
|
10464
|
+
agent_role: row.agent_role,
|
|
10465
|
+
session_id: row.session_id,
|
|
10466
|
+
timestamp: row.timestamp,
|
|
10467
|
+
tool_name: row.tool_name,
|
|
10468
|
+
project_name: row.project_name,
|
|
10469
|
+
has_error: row.has_error,
|
|
10470
|
+
raw_text: row.raw_text,
|
|
10471
|
+
version: row.version,
|
|
10472
|
+
author_device_id: row.author_device_id,
|
|
10473
|
+
scope: row.scope
|
|
10474
|
+
}));
|
|
10475
|
+
const maxVersion = Number(records[records.length - 1].version);
|
|
10476
|
+
const pushOk = await cloudPush(records, maxVersion, config);
|
|
10477
|
+
if (!pushOk) break;
|
|
10478
|
+
try {
|
|
10479
|
+
await pushToPostgres(records);
|
|
10480
|
+
} catch {
|
|
10481
|
+
}
|
|
10482
|
+
await client.execute({
|
|
10483
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_cloud_push_version', ?)",
|
|
10484
|
+
args: [String(maxVersion)]
|
|
10485
|
+
});
|
|
10486
|
+
pushed += records.length;
|
|
10487
|
+
batchCursor = maxVersion;
|
|
10488
|
+
if (recordsResult.rows.length < PUSH_BATCH_SIZE) break;
|
|
10489
|
+
}
|
|
10490
|
+
try {
|
|
10491
|
+
await cloudPushRoster(config);
|
|
10492
|
+
} catch (err) {
|
|
10493
|
+
logError(`[cloud-sync] Roster push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10494
|
+
}
|
|
10495
|
+
try {
|
|
10496
|
+
await cloudPullRoster(config);
|
|
10497
|
+
} catch (err) {
|
|
10498
|
+
logError(`[cloud-sync] Roster pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10499
|
+
}
|
|
10500
|
+
try {
|
|
10501
|
+
await cloudPushGlobalProcedures(config);
|
|
10502
|
+
} catch (err) {
|
|
10503
|
+
logError(`[cloud-sync] Global procedures push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10504
|
+
}
|
|
10505
|
+
try {
|
|
10506
|
+
await cloudPullGlobalProcedures(config);
|
|
10507
|
+
} catch (err) {
|
|
10508
|
+
logError(`[cloud-sync] Global procedures pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10509
|
+
}
|
|
10510
|
+
const countRows = async (sql) => {
|
|
10511
|
+
try {
|
|
10512
|
+
return Number((await client.execute(sql)).rows[0]?.cnt ?? 0);
|
|
10513
|
+
} catch {
|
|
10514
|
+
return 0;
|
|
10515
|
+
}
|
|
10516
|
+
};
|
|
10517
|
+
let behaviorsResult = { pushed: 0, pulled: 0 };
|
|
10518
|
+
try {
|
|
10519
|
+
await cloudPushBehaviors(config);
|
|
10520
|
+
behaviorsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM behaviors WHERE active = 1");
|
|
10521
|
+
} catch (err) {
|
|
10522
|
+
logError(`[cloud-sync] Behaviors push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10523
|
+
}
|
|
10524
|
+
try {
|
|
10525
|
+
const pullResult2 = await cloudPullBehaviors(config);
|
|
10526
|
+
behaviorsResult.pulled = pullResult2.pulled;
|
|
10527
|
+
} catch (err) {
|
|
10528
|
+
logError(`[cloud-sync] Behaviors pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10529
|
+
}
|
|
10530
|
+
let graphragResult = { pushed: 0, pulled: 0 };
|
|
10531
|
+
try {
|
|
10532
|
+
await cloudPushGraphRAG(config);
|
|
10533
|
+
graphragResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM entities");
|
|
10534
|
+
} catch (err) {
|
|
10535
|
+
logError(`[cloud-sync] GraphRAG push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10536
|
+
}
|
|
10537
|
+
try {
|
|
10538
|
+
const pullResult2 = await cloudPullGraphRAG(config);
|
|
10539
|
+
graphragResult.pulled = pullResult2.pulled;
|
|
10540
|
+
} catch (err) {
|
|
10541
|
+
logError(`[cloud-sync] GraphRAG pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10542
|
+
}
|
|
10543
|
+
let tasksResult = { pushed: 0, pulled: 0 };
|
|
10544
|
+
try {
|
|
10545
|
+
await cloudPushTasks(config);
|
|
10546
|
+
tasksResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM tasks");
|
|
10547
|
+
} catch (err) {
|
|
10548
|
+
logError(`[cloud-sync] Tasks push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10549
|
+
}
|
|
10550
|
+
try {
|
|
10551
|
+
const pullResult2 = await cloudPullTasks(config);
|
|
10552
|
+
tasksResult.pulled = pullResult2.pulled;
|
|
10553
|
+
} catch (err) {
|
|
10554
|
+
logError(`[cloud-sync] Tasks pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10555
|
+
}
|
|
10556
|
+
let conversationsResult = { pushed: 0, pulled: 0 };
|
|
10557
|
+
try {
|
|
10558
|
+
await cloudPushConversations(config);
|
|
10559
|
+
conversationsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM conversations");
|
|
10560
|
+
} catch (err) {
|
|
10561
|
+
logError(`[cloud-sync] Conversations push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10562
|
+
}
|
|
10563
|
+
try {
|
|
10564
|
+
const pullResult2 = await cloudPullConversations(config);
|
|
10565
|
+
conversationsResult.pulled = pullResult2.pulled;
|
|
10566
|
+
} catch (err) {
|
|
10567
|
+
logError(`[cloud-sync] Conversations pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10568
|
+
}
|
|
10569
|
+
let documentsResult = { pushed: 0, pulled: 0 };
|
|
10570
|
+
try {
|
|
10571
|
+
await cloudPushDocuments(config);
|
|
10572
|
+
documentsResult.pushed = await countRows("SELECT COUNT(*) as cnt FROM documents");
|
|
10573
|
+
} catch (err) {
|
|
10574
|
+
logError(`[cloud-sync] Documents push: ${err instanceof Error ? err.message : String(err)}`);
|
|
10575
|
+
}
|
|
10576
|
+
try {
|
|
10577
|
+
const pullResult2 = await cloudPullDocuments(config);
|
|
10578
|
+
documentsResult.pulled = pullResult2.pulled;
|
|
10579
|
+
} catch (err) {
|
|
10580
|
+
logError(`[cloud-sync] Documents pull: ${err instanceof Error ? err.message : String(err)}`);
|
|
10581
|
+
}
|
|
10582
|
+
let rosterResult = { employees: 0, identities: 0 };
|
|
10583
|
+
try {
|
|
10584
|
+
const employees = await loadEmployees();
|
|
10585
|
+
rosterResult.employees = employees.length;
|
|
10586
|
+
const idDir = path23.join(EXE_AI_DIR, "identity");
|
|
10587
|
+
if (existsSync21(idDir)) {
|
|
10588
|
+
rosterResult.identities = readdirSync5(idDir).filter((f) => f.endsWith(".md")).length;
|
|
10589
|
+
}
|
|
10590
|
+
} catch {
|
|
10591
|
+
}
|
|
10592
|
+
const totalMemories = await countRows("SELECT COUNT(*) as cnt FROM memories WHERE status = 'active' OR status IS NULL");
|
|
10593
|
+
return {
|
|
10594
|
+
pushed,
|
|
10595
|
+
pulled,
|
|
10596
|
+
totalMemories,
|
|
10597
|
+
behaviors: behaviorsResult,
|
|
10598
|
+
graphrag: graphragResult,
|
|
10599
|
+
tasks: tasksResult,
|
|
10600
|
+
conversations: conversationsResult,
|
|
10601
|
+
documents: documentsResult,
|
|
10602
|
+
roster: rosterResult
|
|
10603
|
+
};
|
|
10604
|
+
}
|
|
10605
|
+
function recordRosterDeletion(name) {
|
|
10606
|
+
let deletions = [];
|
|
10607
|
+
try {
|
|
10608
|
+
if (existsSync21(ROSTER_DELETIONS_PATH)) {
|
|
10609
|
+
deletions = JSON.parse(readFileSync16(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
10610
|
+
}
|
|
10611
|
+
} catch {
|
|
10612
|
+
}
|
|
10613
|
+
if (!deletions.includes(name)) deletions.push(name);
|
|
10614
|
+
writeFileSync11(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
|
|
10615
|
+
}
|
|
10616
|
+
function consumeRosterDeletions() {
|
|
10617
|
+
try {
|
|
10618
|
+
if (!existsSync21(ROSTER_DELETIONS_PATH)) return [];
|
|
10619
|
+
const deletions = JSON.parse(readFileSync16(ROSTER_DELETIONS_PATH, "utf-8"));
|
|
10620
|
+
writeFileSync11(ROSTER_DELETIONS_PATH, "[]");
|
|
10621
|
+
return deletions;
|
|
10622
|
+
} catch {
|
|
10623
|
+
return [];
|
|
10624
|
+
}
|
|
10625
|
+
}
|
|
10626
|
+
function buildRosterBlob(paths) {
|
|
10627
|
+
const rosterPath = paths?.rosterPath ?? path23.join(EXE_AI_DIR, "exe-employees.json");
|
|
10628
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
10629
|
+
const configPath = paths?.configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
10630
|
+
let roster = [];
|
|
10631
|
+
if (existsSync21(rosterPath)) {
|
|
10632
|
+
try {
|
|
10633
|
+
roster = JSON.parse(readFileSync16(rosterPath, "utf-8"));
|
|
10634
|
+
} catch {
|
|
10635
|
+
}
|
|
10636
|
+
}
|
|
10637
|
+
const identities = {};
|
|
10638
|
+
if (existsSync21(identityDir)) {
|
|
10639
|
+
for (const file of readdirSync5(identityDir).filter((f) => f.endsWith(".md"))) {
|
|
10640
|
+
try {
|
|
10641
|
+
identities[file] = readFileSync16(path23.join(identityDir, file), "utf-8");
|
|
10642
|
+
} catch {
|
|
10643
|
+
}
|
|
10644
|
+
}
|
|
10645
|
+
}
|
|
10646
|
+
let config;
|
|
10647
|
+
if (existsSync21(configPath)) {
|
|
10648
|
+
try {
|
|
10649
|
+
config = JSON.parse(readFileSync16(configPath, "utf-8"));
|
|
10650
|
+
} catch {
|
|
10651
|
+
}
|
|
10652
|
+
}
|
|
10653
|
+
let agentConfig;
|
|
10654
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
10655
|
+
if (existsSync21(agentConfigPath)) {
|
|
10656
|
+
try {
|
|
10657
|
+
agentConfig = JSON.parse(readFileSync16(agentConfigPath, "utf-8"));
|
|
10658
|
+
} catch {
|
|
10659
|
+
}
|
|
10660
|
+
}
|
|
10661
|
+
const deletedNames = consumeRosterDeletions();
|
|
10662
|
+
const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
|
|
10663
|
+
const hash = crypto9.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
10664
|
+
return { roster, identities, config, agentConfig, deletedNames, version: hash };
|
|
10665
|
+
}
|
|
10666
|
+
async function cloudPushRoster(config) {
|
|
10667
|
+
assertSecureEndpoint(config.endpoint);
|
|
10668
|
+
const blob = buildRosterBlob();
|
|
10669
|
+
if (blob.roster.length === 0) return true;
|
|
10670
|
+
try {
|
|
10671
|
+
const client = getClient();
|
|
10672
|
+
const meta = await client.execute(
|
|
10673
|
+
"SELECT value FROM sync_meta WHERE key = 'last_roster_push_version'"
|
|
10674
|
+
);
|
|
10675
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
10676
|
+
if (blob.version === lastVersion) return true;
|
|
10677
|
+
} catch {
|
|
10678
|
+
}
|
|
10679
|
+
try {
|
|
10680
|
+
const json = JSON.stringify(blob);
|
|
10681
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
10682
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
10683
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/push-roster`, {
|
|
10684
|
+
method: "POST",
|
|
10685
|
+
headers: {
|
|
10686
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10687
|
+
"Content-Type": "application/json",
|
|
10688
|
+
"X-Device-Id": loadDeviceId()
|
|
10689
|
+
},
|
|
10690
|
+
body: JSON.stringify({ blob: encrypted })
|
|
10691
|
+
});
|
|
10692
|
+
if (resp.ok) {
|
|
10693
|
+
try {
|
|
10694
|
+
const client = getClient();
|
|
10695
|
+
await client.execute({
|
|
10696
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES ('last_roster_push_version', ?)",
|
|
10697
|
+
args: [String(blob.version)]
|
|
10698
|
+
});
|
|
10699
|
+
} catch {
|
|
10700
|
+
}
|
|
10701
|
+
}
|
|
10702
|
+
return resp.ok;
|
|
10703
|
+
} catch (err) {
|
|
10704
|
+
process.stderr.write(`[cloud-sync] ROSTER PUSH FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
10705
|
+
`);
|
|
10706
|
+
return false;
|
|
10707
|
+
}
|
|
10708
|
+
}
|
|
10709
|
+
async function cloudPullRoster(config) {
|
|
10710
|
+
assertSecureEndpoint(config.endpoint);
|
|
10711
|
+
try {
|
|
10712
|
+
const resp = await fetchWithRetry(`${config.endpoint}/sync/pull-roster`, {
|
|
10713
|
+
method: "GET",
|
|
10714
|
+
headers: {
|
|
10715
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10716
|
+
"X-Device-Id": loadDeviceId()
|
|
10717
|
+
}
|
|
10718
|
+
});
|
|
10719
|
+
if (!resp.ok) return { added: 0 };
|
|
10720
|
+
const data = await resp.json();
|
|
10721
|
+
if (!data.blob) return { added: 0 };
|
|
10722
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
10723
|
+
const json = decompress(compressed).toString("utf8");
|
|
10724
|
+
const remote = JSON.parse(json);
|
|
10725
|
+
return mergeRosterFromRemote(remote);
|
|
10726
|
+
} catch (err) {
|
|
10727
|
+
process.stderr.write(`[cloud-sync] ROSTER PULL FAILED: ${err instanceof Error ? err.message : String(err)}
|
|
10728
|
+
`);
|
|
10729
|
+
return { added: 0 };
|
|
10730
|
+
}
|
|
10731
|
+
}
|
|
10732
|
+
function mergeConfig(remoteConfig, configPath) {
|
|
10733
|
+
const cfgPath = configPath ?? path23.join(EXE_AI_DIR, "config.json");
|
|
10734
|
+
let local = {};
|
|
10735
|
+
if (existsSync21(cfgPath)) {
|
|
10736
|
+
try {
|
|
10737
|
+
local = JSON.parse(readFileSync16(cfgPath, "utf-8"));
|
|
10738
|
+
} catch {
|
|
10739
|
+
}
|
|
10740
|
+
}
|
|
10741
|
+
const merged = { ...remoteConfig, ...local };
|
|
10742
|
+
const dir = path23.dirname(cfgPath);
|
|
10743
|
+
ensurePrivateDirSync(dir);
|
|
10744
|
+
writeFileSync11(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
|
|
10745
|
+
enforcePrivateFileSync(cfgPath);
|
|
10746
|
+
}
|
|
10747
|
+
async function mergeRosterFromRemote(remote, paths) {
|
|
10748
|
+
return withRosterLock(async () => {
|
|
10749
|
+
const rosterPath = paths?.rosterPath ?? void 0;
|
|
10750
|
+
const identityDir = paths?.identityDir ?? path23.join(EXE_AI_DIR, "identity");
|
|
10751
|
+
const localEmployees = await loadEmployees(rosterPath);
|
|
10752
|
+
const localNames = new Set(localEmployees.map((e) => e.name));
|
|
10753
|
+
let added = 0;
|
|
10754
|
+
let identitiesUpdated = 0;
|
|
10755
|
+
for (const remoteEmp of remote.roster) {
|
|
10756
|
+
if (!localNames.has(remoteEmp.name)) {
|
|
10757
|
+
localEmployees.push(remoteEmp);
|
|
10758
|
+
localNames.add(remoteEmp.name);
|
|
10759
|
+
added++;
|
|
10760
|
+
try {
|
|
10761
|
+
registerBinSymlinks(remoteEmp.name);
|
|
10762
|
+
} catch {
|
|
10763
|
+
}
|
|
10764
|
+
}
|
|
10765
|
+
const lookupKey = `${remoteEmp.name}.md`;
|
|
10766
|
+
const matchedKey = Object.keys(remote.identities).find(
|
|
10767
|
+
(k) => k.toLowerCase() === lookupKey.toLowerCase()
|
|
10768
|
+
) ?? lookupKey;
|
|
10769
|
+
const remoteIdentity = remote.identities[matchedKey];
|
|
10770
|
+
if (remoteIdentity) {
|
|
10771
|
+
if (!existsSync21(identityDir)) mkdirSync9(identityDir, { recursive: true });
|
|
10772
|
+
const idPath = path23.join(identityDir, `${remoteEmp.name}.md`);
|
|
10773
|
+
let localIdentity = null;
|
|
10774
|
+
try {
|
|
10775
|
+
localIdentity = existsSync21(idPath) ? readFileSync16(idPath, "utf-8") : null;
|
|
10776
|
+
} catch {
|
|
10777
|
+
}
|
|
10778
|
+
if (localIdentity !== remoteIdentity) {
|
|
10779
|
+
writeFileSync11(idPath, remoteIdentity, "utf-8");
|
|
10780
|
+
identitiesUpdated++;
|
|
10781
|
+
}
|
|
10782
|
+
}
|
|
10783
|
+
}
|
|
10784
|
+
let removed = 0;
|
|
10785
|
+
if (remote.deletedNames && remote.deletedNames.length > 0) {
|
|
10786
|
+
const toRemove = new Set(remote.deletedNames);
|
|
10787
|
+
const filtered = localEmployees.filter((e) => !toRemove.has(e.name));
|
|
10788
|
+
removed = localEmployees.length - filtered.length;
|
|
10789
|
+
if (removed > 0) {
|
|
10790
|
+
localEmployees.length = 0;
|
|
10791
|
+
localEmployees.push(...filtered);
|
|
10792
|
+
}
|
|
10793
|
+
}
|
|
10794
|
+
if (added > 0 || removed > 0) {
|
|
10795
|
+
await saveEmployees(localEmployees, rosterPath);
|
|
10796
|
+
}
|
|
10797
|
+
if (remote.config && Object.keys(remote.config).length > 0) {
|
|
10798
|
+
try {
|
|
10799
|
+
mergeConfig(remote.config, paths?.configPath);
|
|
10800
|
+
} catch {
|
|
10801
|
+
}
|
|
10802
|
+
}
|
|
10803
|
+
if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
|
|
10804
|
+
try {
|
|
10805
|
+
const agentConfigPath = path23.join(EXE_AI_DIR, "agent-config.json");
|
|
10806
|
+
let local = {};
|
|
10807
|
+
if (existsSync21(agentConfigPath)) {
|
|
10808
|
+
try {
|
|
10809
|
+
local = JSON.parse(readFileSync16(agentConfigPath, "utf-8"));
|
|
10810
|
+
} catch {
|
|
10811
|
+
}
|
|
10812
|
+
}
|
|
10813
|
+
const merged = { ...remote.agentConfig, ...local };
|
|
10814
|
+
ensurePrivateDirSync(path23.dirname(agentConfigPath));
|
|
10815
|
+
writeFileSync11(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
10816
|
+
enforcePrivateFileSync(agentConfigPath);
|
|
10817
|
+
} catch {
|
|
10818
|
+
}
|
|
10819
|
+
}
|
|
10820
|
+
return { added, identitiesUpdated };
|
|
10821
|
+
});
|
|
10822
|
+
}
|
|
10823
|
+
async function cloudPushBlob(route, data, metaKey, config) {
|
|
10824
|
+
if (data.length === 0) return { ok: true };
|
|
10825
|
+
assertSecureEndpoint(config.endpoint);
|
|
10826
|
+
const json = JSON.stringify(data);
|
|
10827
|
+
const version = Buffer.from(json).length;
|
|
10828
|
+
try {
|
|
10829
|
+
const client = getClient();
|
|
10830
|
+
const meta = await client.execute({
|
|
10831
|
+
sql: "SELECT value FROM sync_meta WHERE key = ?",
|
|
10832
|
+
args: [metaKey]
|
|
10833
|
+
});
|
|
10834
|
+
const lastVersion = meta.rows.length > 0 ? Number(meta.rows[0].value) : 0;
|
|
10835
|
+
if (version === lastVersion) return { ok: true };
|
|
10836
|
+
} catch {
|
|
10837
|
+
}
|
|
10838
|
+
try {
|
|
10839
|
+
const compressed = compress(Buffer.from(json, "utf8"));
|
|
10840
|
+
const encrypted = encryptSyncBlob(compressed);
|
|
10841
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
10842
|
+
method: "POST",
|
|
10843
|
+
headers: {
|
|
10844
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10845
|
+
"Content-Type": "application/json",
|
|
10846
|
+
"X-Device-Id": loadDeviceId()
|
|
10847
|
+
},
|
|
10848
|
+
body: JSON.stringify({ blob: encrypted })
|
|
10849
|
+
});
|
|
10850
|
+
if (resp.ok) {
|
|
10851
|
+
try {
|
|
10852
|
+
const client = getClient();
|
|
10853
|
+
await client.execute({
|
|
10854
|
+
sql: "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)",
|
|
10855
|
+
args: [metaKey, String(version)]
|
|
10856
|
+
});
|
|
10857
|
+
} catch {
|
|
10858
|
+
}
|
|
10859
|
+
}
|
|
10860
|
+
return { ok: resp.ok };
|
|
10861
|
+
} catch (err) {
|
|
10862
|
+
logError(`[cloud-sync] PUSH ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10863
|
+
return { ok: false };
|
|
10864
|
+
}
|
|
10865
|
+
}
|
|
10866
|
+
async function cloudPullBlob(route, config) {
|
|
10867
|
+
assertSecureEndpoint(config.endpoint);
|
|
10868
|
+
try {
|
|
10869
|
+
const resp = await fetchWithRetry(`${config.endpoint}${route}`, {
|
|
10870
|
+
method: "GET",
|
|
10871
|
+
headers: {
|
|
10872
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
10873
|
+
"X-Device-Id": loadDeviceId()
|
|
10874
|
+
}
|
|
10875
|
+
});
|
|
10876
|
+
if (!resp.ok) return null;
|
|
10877
|
+
const data = await resp.json();
|
|
10878
|
+
if (!data.blob) return null;
|
|
10879
|
+
const compressed = decryptSyncBlob(data.blob);
|
|
10880
|
+
const json = decompress(compressed).toString("utf8");
|
|
10881
|
+
return JSON.parse(json);
|
|
10882
|
+
} catch (err) {
|
|
10883
|
+
logError(`[cloud-sync] PULL ${route}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10884
|
+
return null;
|
|
10885
|
+
}
|
|
10886
|
+
}
|
|
10887
|
+
async function cloudPushGlobalProcedures(config) {
|
|
10888
|
+
const client = getClient();
|
|
10889
|
+
const result = await client.execute("SELECT * FROM global_procedures LIMIT 1000");
|
|
10890
|
+
const rows = result.rows;
|
|
10891
|
+
const { ok } = await cloudPushBlob(
|
|
10892
|
+
"/sync/push-global-procedures",
|
|
10893
|
+
rows,
|
|
10894
|
+
"last_global_procedures_push_version",
|
|
10895
|
+
config
|
|
10896
|
+
);
|
|
10897
|
+
return ok;
|
|
10898
|
+
}
|
|
10899
|
+
async function cloudPullGlobalProcedures(config) {
|
|
10900
|
+
const remoteProcs = await cloudPullBlob(
|
|
10901
|
+
"/sync/pull-global-procedures",
|
|
10902
|
+
config
|
|
10903
|
+
);
|
|
10904
|
+
if (!remoteProcs || remoteProcs.length === 0) return { pulled: 0 };
|
|
10905
|
+
const client = getClient();
|
|
10906
|
+
const stmts = remoteProcs.map((p) => ({
|
|
10907
|
+
sql: `INSERT INTO global_procedures
|
|
10908
|
+
(id, title, content, priority, domain, active, created_at, updated_at)
|
|
10909
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
10910
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
10911
|
+
title = excluded.title,
|
|
10912
|
+
content = excluded.content,
|
|
10913
|
+
priority = excluded.priority,
|
|
10914
|
+
domain = excluded.domain,
|
|
10915
|
+
active = excluded.active,
|
|
10916
|
+
updated_at = excluded.updated_at
|
|
10917
|
+
WHERE excluded.updated_at > global_procedures.updated_at`,
|
|
10918
|
+
args: [
|
|
10919
|
+
sqlSafe(p.id),
|
|
10920
|
+
sqlSafe(p.title),
|
|
10921
|
+
sqlSafe(p.content),
|
|
10922
|
+
sqlSafe(p.priority ?? "p0"),
|
|
10923
|
+
sqlSafe(p.domain),
|
|
10924
|
+
sqlSafe(p.active ?? 1),
|
|
10925
|
+
sqlSafe(p.created_at),
|
|
10926
|
+
sqlSafe(p.updated_at)
|
|
10927
|
+
]
|
|
10928
|
+
}));
|
|
10929
|
+
await client.batch(stmts, "write");
|
|
10930
|
+
return { pulled: remoteProcs.length };
|
|
10931
|
+
}
|
|
10932
|
+
async function cloudPushBehaviors(config) {
|
|
10933
|
+
const client = getClient();
|
|
10934
|
+
const result = await client.execute("SELECT * FROM behaviors LIMIT 10000");
|
|
10935
|
+
const rows = result.rows;
|
|
10936
|
+
const { ok } = await cloudPushBlob(
|
|
10937
|
+
"/sync/push-behaviors",
|
|
10938
|
+
rows,
|
|
10939
|
+
"last_behaviors_push_version",
|
|
10940
|
+
config
|
|
10941
|
+
);
|
|
10942
|
+
return ok;
|
|
10943
|
+
}
|
|
10944
|
+
async function cloudPullBehaviors(config) {
|
|
10945
|
+
const remoteBehaviors = await cloudPullBlob(
|
|
10946
|
+
"/sync/pull-behaviors",
|
|
10947
|
+
config
|
|
10948
|
+
);
|
|
10949
|
+
if (!remoteBehaviors || remoteBehaviors.length === 0) return { pulled: 0 };
|
|
10950
|
+
const client = getClient();
|
|
10951
|
+
let pulled = 0;
|
|
10952
|
+
for (const behavior of remoteBehaviors) {
|
|
10953
|
+
const existing = await client.execute({
|
|
10954
|
+
sql: `SELECT COUNT(*) as cnt FROM behaviors
|
|
10955
|
+
WHERE agent_id = ? AND content = ?`,
|
|
10956
|
+
args: [sqlSafe(behavior.agent_id), sqlSafe(behavior.content)]
|
|
10957
|
+
});
|
|
10958
|
+
if (Number(existing.rows[0]?.cnt) > 0) continue;
|
|
10959
|
+
await client.execute({
|
|
10960
|
+
sql: `INSERT OR IGNORE INTO behaviors
|
|
10961
|
+
(id, agent_id, project_name, domain, content, active, priority, created_at, updated_at)
|
|
10962
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
10963
|
+
args: [
|
|
10964
|
+
sqlSafe(behavior.id),
|
|
10965
|
+
sqlSafe(behavior.agent_id),
|
|
10966
|
+
sqlSafe(behavior.project_name),
|
|
10967
|
+
sqlSafe(behavior.domain),
|
|
10968
|
+
sqlSafe(behavior.content),
|
|
10969
|
+
sqlSafe(behavior.active ?? 1),
|
|
10970
|
+
sqlSafe(behavior.priority ?? "p1"),
|
|
10971
|
+
sqlSafe(behavior.created_at),
|
|
10972
|
+
sqlSafe(behavior.updated_at)
|
|
10973
|
+
]
|
|
10974
|
+
});
|
|
10975
|
+
pulled++;
|
|
10976
|
+
}
|
|
10977
|
+
return { pulled };
|
|
10978
|
+
}
|
|
10979
|
+
async function cloudPushGraphRAG(config) {
|
|
10980
|
+
const client = getClient();
|
|
10981
|
+
const [entities, relationships, aliases, entityMems, relMems, hyperedges, hyperedgeNodes] = await Promise.all([
|
|
10982
|
+
client.execute("SELECT * FROM entities LIMIT 50000"),
|
|
10983
|
+
client.execute("SELECT * FROM relationships LIMIT 50000"),
|
|
10984
|
+
client.execute("SELECT * FROM entity_aliases LIMIT 50000"),
|
|
10985
|
+
client.execute("SELECT * FROM entity_memories LIMIT 50000"),
|
|
10986
|
+
client.execute("SELECT * FROM relationship_memories LIMIT 50000"),
|
|
10987
|
+
client.execute("SELECT * FROM hyperedges LIMIT 50000"),
|
|
10988
|
+
client.execute("SELECT * FROM hyperedge_nodes LIMIT 50000")
|
|
10989
|
+
]);
|
|
10990
|
+
const blob = {
|
|
10991
|
+
entities: entities.rows,
|
|
10992
|
+
relationships: relationships.rows,
|
|
10993
|
+
entity_aliases: aliases.rows,
|
|
10994
|
+
entity_memories: entityMems.rows,
|
|
10995
|
+
relationship_memories: relMems.rows,
|
|
10996
|
+
hyperedges: hyperedges.rows,
|
|
10997
|
+
hyperedge_nodes: hyperedgeNodes.rows
|
|
10998
|
+
};
|
|
10999
|
+
const { ok } = await cloudPushBlob(
|
|
11000
|
+
"/sync/push-graphrag",
|
|
11001
|
+
[blob],
|
|
11002
|
+
"last_graphrag_push_version",
|
|
11003
|
+
config
|
|
11004
|
+
);
|
|
11005
|
+
return ok;
|
|
11006
|
+
}
|
|
11007
|
+
async function cloudPullGraphRAG(config) {
|
|
11008
|
+
const data = await cloudPullBlob(
|
|
11009
|
+
"/sync/pull-graphrag",
|
|
11010
|
+
config
|
|
11011
|
+
);
|
|
11012
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
11013
|
+
const blob = data[0];
|
|
11014
|
+
const client = getClient();
|
|
11015
|
+
let pulled = 0;
|
|
11016
|
+
if (blob.entities.length > 0) {
|
|
11017
|
+
const stmts = blob.entities.map((e) => ({
|
|
11018
|
+
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen, properties)
|
|
11019
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
11020
|
+
args: [sqlSafe(e.id), sqlSafe(e.name), sqlSafe(e.type), sqlSafe(e.first_seen), sqlSafe(e.last_seen), sqlSafe(e.properties ?? "{}")]
|
|
11021
|
+
}));
|
|
11022
|
+
await client.batch(stmts, "write");
|
|
11023
|
+
pulled += stmts.length;
|
|
11024
|
+
}
|
|
11025
|
+
if (blob.relationships.length > 0) {
|
|
11026
|
+
const stmts = blob.relationships.map((r) => ({
|
|
11027
|
+
sql: `INSERT OR IGNORE INTO relationships
|
|
11028
|
+
(id, source_entity_id, target_entity_id, type, weight, timestamp, properties, confidence, confidence_label)
|
|
11029
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11030
|
+
args: [
|
|
11031
|
+
sqlSafe(r.id),
|
|
11032
|
+
sqlSafe(r.source_entity_id),
|
|
11033
|
+
sqlSafe(r.target_entity_id),
|
|
11034
|
+
sqlSafe(r.type),
|
|
11035
|
+
sqlSafe(r.weight ?? 1),
|
|
11036
|
+
sqlSafe(r.timestamp),
|
|
11037
|
+
sqlSafe(r.properties ?? "{}"),
|
|
11038
|
+
sqlSafe(r.confidence ?? 1),
|
|
11039
|
+
sqlSafe(r.confidence_label ?? "extracted")
|
|
11040
|
+
]
|
|
11041
|
+
}));
|
|
11042
|
+
await client.batch(stmts, "write");
|
|
11043
|
+
pulled += stmts.length;
|
|
11044
|
+
}
|
|
11045
|
+
if (blob.entity_aliases.length > 0) {
|
|
11046
|
+
const stmts = blob.entity_aliases.map((a) => ({
|
|
11047
|
+
sql: `INSERT OR IGNORE INTO entity_aliases (alias, canonical_entity_id) VALUES (?, ?)`,
|
|
11048
|
+
args: [sqlSafe(a.alias), sqlSafe(a.canonical_entity_id)]
|
|
11049
|
+
}));
|
|
11050
|
+
await client.batch(stmts, "write");
|
|
11051
|
+
pulled += stmts.length;
|
|
11052
|
+
}
|
|
11053
|
+
if (blob.entity_memories.length > 0) {
|
|
11054
|
+
const stmts = blob.entity_memories.map((em) => ({
|
|
11055
|
+
sql: `INSERT OR IGNORE INTO entity_memories (entity_id, memory_id) VALUES (?, ?)`,
|
|
11056
|
+
args: [sqlSafe(em.entity_id), sqlSafe(em.memory_id)]
|
|
11057
|
+
}));
|
|
11058
|
+
await client.batch(stmts, "write");
|
|
11059
|
+
pulled += stmts.length;
|
|
11060
|
+
}
|
|
11061
|
+
if (blob.relationship_memories.length > 0) {
|
|
11062
|
+
const stmts = blob.relationship_memories.map((rm) => ({
|
|
11063
|
+
sql: `INSERT OR IGNORE INTO relationship_memories (relationship_id, memory_id) VALUES (?, ?)`,
|
|
11064
|
+
args: [sqlSafe(rm.relationship_id), sqlSafe(rm.memory_id)]
|
|
11065
|
+
}));
|
|
11066
|
+
await client.batch(stmts, "write");
|
|
11067
|
+
pulled += stmts.length;
|
|
11068
|
+
}
|
|
11069
|
+
if (blob.hyperedges.length > 0) {
|
|
11070
|
+
const stmts = blob.hyperedges.map((h) => ({
|
|
11071
|
+
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
11072
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
11073
|
+
args: [sqlSafe(h.id), sqlSafe(h.label), sqlSafe(h.relation), sqlSafe(h.confidence ?? 1), sqlSafe(h.timestamp)]
|
|
11074
|
+
}));
|
|
11075
|
+
await client.batch(stmts, "write");
|
|
11076
|
+
pulled += stmts.length;
|
|
11077
|
+
}
|
|
11078
|
+
if (blob.hyperedge_nodes.length > 0) {
|
|
11079
|
+
const stmts = blob.hyperedge_nodes.map((hn) => ({
|
|
11080
|
+
sql: `INSERT OR IGNORE INTO hyperedge_nodes (hyperedge_id, entity_id) VALUES (?, ?)`,
|
|
11081
|
+
args: [sqlSafe(hn.hyperedge_id), sqlSafe(hn.entity_id)]
|
|
11082
|
+
}));
|
|
11083
|
+
await client.batch(stmts, "write");
|
|
11084
|
+
pulled += stmts.length;
|
|
11085
|
+
}
|
|
11086
|
+
return { pulled };
|
|
11087
|
+
}
|
|
11088
|
+
async function cloudPushTasks(config) {
|
|
11089
|
+
const client = getClient();
|
|
11090
|
+
const result = await client.execute("SELECT * FROM tasks LIMIT 10000");
|
|
11091
|
+
const rows = result.rows;
|
|
11092
|
+
const { ok } = await cloudPushBlob(
|
|
11093
|
+
"/sync/push-tasks",
|
|
11094
|
+
rows,
|
|
11095
|
+
"last_tasks_push_version",
|
|
11096
|
+
config
|
|
11097
|
+
);
|
|
11098
|
+
return ok;
|
|
11099
|
+
}
|
|
11100
|
+
async function cloudPullTasks(config) {
|
|
11101
|
+
const remoteTasks = await cloudPullBlob(
|
|
11102
|
+
"/sync/pull-tasks",
|
|
11103
|
+
config
|
|
11104
|
+
);
|
|
11105
|
+
if (!remoteTasks || remoteTasks.length === 0) return { pulled: 0 };
|
|
11106
|
+
const client = getClient();
|
|
11107
|
+
const stmts = remoteTasks.map((t) => ({
|
|
11108
|
+
sql: `INSERT OR IGNORE INTO tasks
|
|
11109
|
+
(id, title, assigned_to, assigned_by, project_name, priority, status, task_file, created_at, updated_at,
|
|
11110
|
+
blocked_by, parent_task_id, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at)
|
|
11111
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11112
|
+
args: [
|
|
11113
|
+
sqlSafe(t.id),
|
|
11114
|
+
sqlSafe(t.title),
|
|
11115
|
+
sqlSafe(t.assigned_to),
|
|
11116
|
+
sqlSafe(t.assigned_by),
|
|
11117
|
+
sqlSafe(t.project_name),
|
|
11118
|
+
sqlSafe(t.priority ?? "p1"),
|
|
11119
|
+
sqlSafe(t.status ?? "open"),
|
|
11120
|
+
sqlSafe(t.task_file),
|
|
11121
|
+
sqlSafe(t.created_at),
|
|
11122
|
+
sqlSafe(t.updated_at),
|
|
11123
|
+
sqlSafe(t.blocked_by),
|
|
11124
|
+
sqlSafe(t.parent_task_id),
|
|
11125
|
+
sqlSafe(t.budget_tokens),
|
|
11126
|
+
sqlSafe(t.budget_fallback_model),
|
|
11127
|
+
sqlSafe(t.tokens_used ?? 0),
|
|
11128
|
+
sqlSafe(t.tokens_warned_at)
|
|
11129
|
+
]
|
|
11130
|
+
}));
|
|
11131
|
+
await client.batch(stmts, "write");
|
|
11132
|
+
return { pulled: remoteTasks.length };
|
|
11133
|
+
}
|
|
11134
|
+
async function cloudPushConversations(config) {
|
|
11135
|
+
const client = getClient();
|
|
11136
|
+
const result = await client.execute("SELECT * FROM conversations LIMIT 50000");
|
|
11137
|
+
const rows = result.rows;
|
|
11138
|
+
const { ok } = await cloudPushBlob(
|
|
11139
|
+
"/sync/push-conversations",
|
|
11140
|
+
rows,
|
|
11141
|
+
"last_conversations_push_version",
|
|
11142
|
+
config
|
|
11143
|
+
);
|
|
11144
|
+
return ok;
|
|
11145
|
+
}
|
|
11146
|
+
async function cloudPullConversations(config) {
|
|
11147
|
+
const remoteConvos = await cloudPullBlob(
|
|
11148
|
+
"/sync/pull-conversations",
|
|
11149
|
+
config
|
|
11150
|
+
);
|
|
11151
|
+
if (!remoteConvos || remoteConvos.length === 0) return { pulled: 0 };
|
|
11152
|
+
const client = getClient();
|
|
11153
|
+
const stmts = remoteConvos.map((c) => ({
|
|
11154
|
+
sql: `INSERT OR IGNORE INTO conversations
|
|
11155
|
+
(id, platform, external_id, sender_id, sender_name, sender_phone, sender_email,
|
|
11156
|
+
recipient_id, channel_id, thread_id, reply_to_id, content_text, content_media,
|
|
11157
|
+
content_metadata, agent_response, agent_name, timestamp, ingested_at)
|
|
11158
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11159
|
+
args: [
|
|
11160
|
+
sqlSafe(c.id),
|
|
11161
|
+
sqlSafe(c.platform),
|
|
11162
|
+
sqlSafe(c.external_id),
|
|
11163
|
+
sqlSafe(c.sender_id),
|
|
11164
|
+
sqlSafe(c.sender_name),
|
|
11165
|
+
sqlSafe(c.sender_phone),
|
|
11166
|
+
sqlSafe(c.sender_email),
|
|
11167
|
+
sqlSafe(c.recipient_id),
|
|
11168
|
+
sqlSafe(c.channel_id),
|
|
11169
|
+
sqlSafe(c.thread_id),
|
|
11170
|
+
sqlSafe(c.reply_to_id),
|
|
11171
|
+
sqlSafe(c.content_text),
|
|
11172
|
+
sqlSafe(c.content_media),
|
|
11173
|
+
sqlSafe(c.content_metadata),
|
|
11174
|
+
sqlSafe(c.agent_response),
|
|
11175
|
+
sqlSafe(c.agent_name),
|
|
11176
|
+
sqlSafe(c.timestamp),
|
|
11177
|
+
sqlSafe(c.ingested_at)
|
|
11178
|
+
]
|
|
11179
|
+
}));
|
|
11180
|
+
await client.batch(stmts, "write");
|
|
11181
|
+
return { pulled: remoteConvos.length };
|
|
11182
|
+
}
|
|
11183
|
+
async function cloudPushDocuments(config) {
|
|
11184
|
+
const client = getClient();
|
|
11185
|
+
const [workspaces, documents] = await Promise.all([
|
|
11186
|
+
client.execute("SELECT * FROM workspaces LIMIT 1000"),
|
|
11187
|
+
client.execute("SELECT * FROM documents LIMIT 10000")
|
|
11188
|
+
]);
|
|
11189
|
+
const blob = {
|
|
11190
|
+
workspaces: workspaces.rows,
|
|
11191
|
+
documents: documents.rows
|
|
11192
|
+
};
|
|
11193
|
+
const { ok } = await cloudPushBlob(
|
|
11194
|
+
"/sync/push-documents",
|
|
11195
|
+
[blob],
|
|
11196
|
+
"last_documents_push_version",
|
|
11197
|
+
config
|
|
11198
|
+
);
|
|
11199
|
+
return ok;
|
|
11200
|
+
}
|
|
11201
|
+
async function cloudPullDocuments(config) {
|
|
11202
|
+
const data = await cloudPullBlob(
|
|
11203
|
+
"/sync/pull-documents",
|
|
11204
|
+
config
|
|
11205
|
+
);
|
|
11206
|
+
if (!data || data.length === 0) return { pulled: 0 };
|
|
11207
|
+
const blob = data[0];
|
|
11208
|
+
const client = getClient();
|
|
11209
|
+
let pulled = 0;
|
|
11210
|
+
if (blob.workspaces.length > 0) {
|
|
11211
|
+
const stmts = blob.workspaces.map((w) => ({
|
|
11212
|
+
sql: `INSERT OR IGNORE INTO workspaces (id, slug, name, owner_agent_id, created_at, metadata)
|
|
11213
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
11214
|
+
args: [sqlSafe(w.id), sqlSafe(w.slug), sqlSafe(w.name), sqlSafe(w.owner_agent_id), sqlSafe(w.created_at), sqlSafe(w.metadata)]
|
|
11215
|
+
}));
|
|
11216
|
+
await client.batch(stmts, "write");
|
|
11217
|
+
pulled += stmts.length;
|
|
11218
|
+
}
|
|
11219
|
+
if (blob.documents.length > 0) {
|
|
11220
|
+
const stmts = blob.documents.map((d) => ({
|
|
11221
|
+
sql: `INSERT OR IGNORE INTO documents
|
|
11222
|
+
(id, workspace_id, filename, mime, source_type, user_id, uploaded_at, metadata)
|
|
11223
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
11224
|
+
args: [
|
|
11225
|
+
sqlSafe(d.id),
|
|
11226
|
+
sqlSafe(d.workspace_id),
|
|
11227
|
+
sqlSafe(d.filename),
|
|
11228
|
+
sqlSafe(d.mime),
|
|
11229
|
+
sqlSafe(d.source_type),
|
|
11230
|
+
sqlSafe(d.user_id),
|
|
11231
|
+
sqlSafe(d.uploaded_at),
|
|
11232
|
+
sqlSafe(d.metadata)
|
|
11233
|
+
]
|
|
11234
|
+
}));
|
|
11235
|
+
await client.batch(stmts, "write");
|
|
11236
|
+
pulled += stmts.length;
|
|
11237
|
+
}
|
|
11238
|
+
return { pulled };
|
|
11239
|
+
}
|
|
11240
|
+
var LOCALHOST_PATTERNS, FETCH_TIMEOUT_MS, PUSH_BATCH_SIZE, ROSTER_LOCK_PATH, LOCK_STALE_MS, _pgPromise, _pgFailed, ROSTER_DELETIONS_PATH;
|
|
11241
|
+
var init_cloud_sync = __esm({
|
|
11242
|
+
"src/lib/cloud-sync.ts"() {
|
|
11243
|
+
"use strict";
|
|
11244
|
+
init_database();
|
|
11245
|
+
init_crypto();
|
|
11246
|
+
init_compress();
|
|
11247
|
+
init_license();
|
|
11248
|
+
init_config();
|
|
11249
|
+
init_crdt_sync();
|
|
11250
|
+
init_employees();
|
|
11251
|
+
init_secure_files();
|
|
11252
|
+
LOCALHOST_PATTERNS = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
11253
|
+
FETCH_TIMEOUT_MS = 3e4;
|
|
11254
|
+
PUSH_BATCH_SIZE = 5e3;
|
|
11255
|
+
ROSTER_LOCK_PATH = path23.join(EXE_AI_DIR, "roster-merge.lock");
|
|
11256
|
+
LOCK_STALE_MS = 3e4;
|
|
11257
|
+
_pgPromise = null;
|
|
11258
|
+
_pgFailed = false;
|
|
11259
|
+
ROSTER_DELETIONS_PATH = path23.join(EXE_AI_DIR, "roster-deletions.json");
|
|
11260
|
+
}
|
|
11261
|
+
});
|
|
11262
|
+
|
|
11263
|
+
// src/lib/code-chunker.ts
|
|
11264
|
+
import ts from "typescript";
|
|
11265
|
+
function chunkSourceFile(source, fileName = "file.ts") {
|
|
11266
|
+
const sourceFile = ts.createSourceFile(
|
|
11267
|
+
fileName,
|
|
11268
|
+
source,
|
|
11269
|
+
ts.ScriptTarget.Latest,
|
|
11270
|
+
true,
|
|
11271
|
+
// setParentNodes
|
|
11272
|
+
fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
11273
|
+
);
|
|
11274
|
+
const chunks = [];
|
|
11275
|
+
const lines = source.split("\n");
|
|
11276
|
+
const importLines = [];
|
|
11277
|
+
function getLineNumber(pos) {
|
|
11278
|
+
return sourceFile.getLineAndCharacterOfPosition(pos).line + 1;
|
|
11279
|
+
}
|
|
11280
|
+
function getLeadingComment(node) {
|
|
11281
|
+
const fullText = sourceFile.getFullText();
|
|
11282
|
+
const ranges = ts.getLeadingCommentRanges(fullText, node.getFullStart());
|
|
11283
|
+
if (!ranges || ranges.length === 0) return void 0;
|
|
11284
|
+
return ranges.map((r) => fullText.slice(r.pos, r.end)).join("\n");
|
|
11285
|
+
}
|
|
11286
|
+
function getNodeText(node) {
|
|
11287
|
+
return node.getText(sourceFile);
|
|
11288
|
+
}
|
|
11289
|
+
function getName(node) {
|
|
11290
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
|
|
11291
|
+
return node.name?.getText(sourceFile) ?? "(anonymous)";
|
|
11292
|
+
}
|
|
11293
|
+
if (ts.isClassDeclaration(node)) {
|
|
11294
|
+
return node.name?.getText(sourceFile) ?? "(anonymous class)";
|
|
11295
|
+
}
|
|
11296
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
11297
|
+
return node.name.getText(sourceFile);
|
|
11298
|
+
}
|
|
11299
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
11300
|
+
return node.name.getText(sourceFile);
|
|
11301
|
+
}
|
|
11302
|
+
if (ts.isEnumDeclaration(node)) {
|
|
11303
|
+
return node.name.getText(sourceFile);
|
|
11304
|
+
}
|
|
11305
|
+
if (ts.isVariableStatement(node)) {
|
|
11306
|
+
const decls = node.declarationList.declarations;
|
|
11307
|
+
return decls.map((d) => d.name.getText(sourceFile)).join(", ");
|
|
11308
|
+
}
|
|
11309
|
+
if (ts.isExportAssignment(node)) {
|
|
11310
|
+
return "default export";
|
|
11311
|
+
}
|
|
11312
|
+
return "(unknown)";
|
|
11313
|
+
}
|
|
11314
|
+
function visitTopLevel(node) {
|
|
11315
|
+
if (ts.isImportDeclaration(node)) {
|
|
11316
|
+
importLines.push({
|
|
11317
|
+
start: getLineNumber(node.getStart(sourceFile)),
|
|
11318
|
+
end: getLineNumber(node.getEnd())
|
|
11319
|
+
});
|
|
11320
|
+
return;
|
|
11321
|
+
}
|
|
11322
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
9547
11323
|
chunks.push({
|
|
9548
11324
|
kind: "function",
|
|
9549
11325
|
name: getName(node),
|
|
@@ -9678,13 +11454,13 @@ __export(graph_rag_exports, {
|
|
|
9678
11454
|
resolveAlias: () => resolveAlias,
|
|
9679
11455
|
storeExtraction: () => storeExtraction
|
|
9680
11456
|
});
|
|
9681
|
-
import
|
|
11457
|
+
import crypto10 from "crypto";
|
|
9682
11458
|
function normalizeEntityName(name) {
|
|
9683
11459
|
return name.replace(/\s*\([^)]*\)\s*/g, "").trim().toLowerCase();
|
|
9684
11460
|
}
|
|
9685
11461
|
function entityId(name, type) {
|
|
9686
11462
|
const normalized = normalizeEntityName(name);
|
|
9687
|
-
return
|
|
11463
|
+
return crypto10.createHash("sha256").update(`${normalized}::${type.toLowerCase()}`).digest("hex").slice(0, 16);
|
|
9688
11464
|
}
|
|
9689
11465
|
async function resolveAlias(client, name) {
|
|
9690
11466
|
const normalized = normalizeEntityName(name);
|
|
@@ -9934,7 +11710,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9934
11710
|
const targetAlias = await resolveAlias(client, r.target);
|
|
9935
11711
|
const sourceId = sourceAlias ?? entityId(r.source, r.sourceType);
|
|
9936
11712
|
const targetId = targetAlias ?? entityId(r.target, r.targetType);
|
|
9937
|
-
const relId =
|
|
11713
|
+
const relId = crypto10.randomUUID().slice(0, 16);
|
|
9938
11714
|
try {
|
|
9939
11715
|
await client.execute({
|
|
9940
11716
|
sql: `INSERT OR IGNORE INTO entities (id, name, type, first_seen, last_seen)
|
|
@@ -9997,7 +11773,7 @@ async function storeExtraction(client, extraction, memoryId, timestamp) {
|
|
|
9997
11773
|
}
|
|
9998
11774
|
}
|
|
9999
11775
|
for (const h of extraction.hyperedges) {
|
|
10000
|
-
const hId =
|
|
11776
|
+
const hId = crypto10.randomUUID().slice(0, 16);
|
|
10001
11777
|
try {
|
|
10002
11778
|
await client.execute({
|
|
10003
11779
|
sql: `INSERT OR IGNORE INTO hyperedges (id, label, relation, confidence, timestamp)
|
|
@@ -10061,7 +11837,7 @@ async function extractBatch(client, batchSize = 50, model = "claude-haiku-4-5-20
|
|
|
10061
11837
|
totalEntities += stored.entitiesStored;
|
|
10062
11838
|
totalRelationships += stored.relationshipsStored;
|
|
10063
11839
|
}
|
|
10064
|
-
const contentHash =
|
|
11840
|
+
const contentHash = crypto10.createHash("sha256").update(rawContent).digest("hex").slice(0, 32);
|
|
10065
11841
|
await client.execute({
|
|
10066
11842
|
sql: "UPDATE memories SET graph_extracted = 1, content_hash = ?, graph_extracted_hash = ? WHERE id = ?",
|
|
10067
11843
|
args: [contentHash, contentHash, memoryId]
|
|
@@ -10178,8 +11954,8 @@ __export(wiki_sync_exports, {
|
|
|
10178
11954
|
listWorkspaces: () => listWorkspaces,
|
|
10179
11955
|
syncMemories: () => syncMemories
|
|
10180
11956
|
});
|
|
10181
|
-
async function wikiRequest(config,
|
|
10182
|
-
const url = `${config.wikiUrl}/api/v1${
|
|
11957
|
+
async function wikiRequest(config, path28, method = "GET", body) {
|
|
11958
|
+
const url = `${config.wikiUrl}/api/v1${path28}`;
|
|
10183
11959
|
const headers = {
|
|
10184
11960
|
"Authorization": `Bearer ${config.wikiApiKey}`,
|
|
10185
11961
|
"Content-Type": "application/json"
|
|
@@ -10191,7 +11967,7 @@ async function wikiRequest(config, path25, method = "GET", body) {
|
|
|
10191
11967
|
signal: AbortSignal.timeout(3e4)
|
|
10192
11968
|
});
|
|
10193
11969
|
if (!response.ok) {
|
|
10194
|
-
throw new Error(`Wiki API ${method} ${
|
|
11970
|
+
throw new Error(`Wiki API ${method} ${path28}: ${response.status} ${response.statusText}`);
|
|
10195
11971
|
}
|
|
10196
11972
|
return response.json();
|
|
10197
11973
|
}
|
|
@@ -10303,8 +12079,8 @@ __export(token_spend_exports, {
|
|
|
10303
12079
|
import { readdir } from "fs/promises";
|
|
10304
12080
|
import { createReadStream } from "fs";
|
|
10305
12081
|
import { createInterface } from "readline";
|
|
10306
|
-
import
|
|
10307
|
-
import
|
|
12082
|
+
import path24 from "path";
|
|
12083
|
+
import os14 from "os";
|
|
10308
12084
|
function getPricing(model) {
|
|
10309
12085
|
if (MODEL_PRICING[model]) return MODEL_PRICING[model];
|
|
10310
12086
|
const stripped = model.replace(/-\d{8}$/, "");
|
|
@@ -10331,18 +12107,18 @@ async function getAgentSpend(period = "7d") {
|
|
|
10331
12107
|
for (const row of dbResult.rows) {
|
|
10332
12108
|
sessionAgent.set(row.session_uuid, row.agent_id);
|
|
10333
12109
|
}
|
|
10334
|
-
const claudeDir =
|
|
12110
|
+
const claudeDir = path24.join(os14.homedir(), ".claude", "projects");
|
|
10335
12111
|
let projectDirs = [];
|
|
10336
12112
|
try {
|
|
10337
12113
|
const entries = await readdir(claudeDir);
|
|
10338
|
-
projectDirs = entries.map((e) =>
|
|
12114
|
+
projectDirs = entries.map((e) => path24.join(claudeDir, e));
|
|
10339
12115
|
} catch {
|
|
10340
12116
|
return [];
|
|
10341
12117
|
}
|
|
10342
12118
|
const agentTotals = /* @__PURE__ */ new Map();
|
|
10343
12119
|
for (const [sessionUuid, agentId] of sessionAgent) {
|
|
10344
12120
|
for (const dir of projectDirs) {
|
|
10345
|
-
const jsonlPath =
|
|
12121
|
+
const jsonlPath = path24.join(dir, `${sessionUuid}.jsonl`);
|
|
10346
12122
|
try {
|
|
10347
12123
|
const usage = await extractSessionUsage(jsonlPath);
|
|
10348
12124
|
if (usage.input === 0 && usage.output === 0) continue;
|
|
@@ -10467,11 +12243,11 @@ __export(update_check_exports, {
|
|
|
10467
12243
|
getRemoteVersion: () => getRemoteVersion
|
|
10468
12244
|
});
|
|
10469
12245
|
import { execSync as execSync11 } from "child_process";
|
|
10470
|
-
import { readFileSync as
|
|
10471
|
-
import
|
|
12246
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
12247
|
+
import path25 from "path";
|
|
10472
12248
|
function getLocalVersion(packageRoot) {
|
|
10473
|
-
const pkgPath =
|
|
10474
|
-
const pkg = JSON.parse(
|
|
12249
|
+
const pkgPath = path25.join(packageRoot, "package.json");
|
|
12250
|
+
const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
10475
12251
|
return pkg.version;
|
|
10476
12252
|
}
|
|
10477
12253
|
function getRemoteVersion() {
|
|
@@ -10514,16 +12290,16 @@ __export(ws_auth_exports, {
|
|
|
10514
12290
|
deriveWsAuthToken: () => deriveWsAuthToken,
|
|
10515
12291
|
hashAuthToken: () => hashAuthToken
|
|
10516
12292
|
});
|
|
10517
|
-
import
|
|
12293
|
+
import crypto11 from "crypto";
|
|
10518
12294
|
function deriveWsAuthToken(masterKey) {
|
|
10519
|
-
return Buffer.from(
|
|
12295
|
+
return Buffer.from(crypto11.hkdfSync("sha256", masterKey, "", WS_AUTH_HKDF_INFO, 32));
|
|
10520
12296
|
}
|
|
10521
12297
|
function deriveOrgId(masterKey) {
|
|
10522
|
-
const raw = Buffer.from(
|
|
10523
|
-
return
|
|
12298
|
+
const raw = Buffer.from(crypto11.hkdfSync("sha256", masterKey, "", ORG_ID_HKDF_INFO, 32));
|
|
12299
|
+
return crypto11.createHash("sha256").update(raw).digest("hex").slice(0, 32);
|
|
10524
12300
|
}
|
|
10525
12301
|
function hashAuthToken(token) {
|
|
10526
|
-
return
|
|
12302
|
+
return crypto11.createHash("sha256").update(token).digest("hex");
|
|
10527
12303
|
}
|
|
10528
12304
|
var WS_AUTH_HKDF_INFO, ORG_ID_HKDF_INFO;
|
|
10529
12305
|
var init_ws_auth = __esm({
|
|
@@ -10541,14 +12317,14 @@ __export(device_registry_exports, {
|
|
|
10541
12317
|
resolveTargetDevice: () => resolveTargetDevice,
|
|
10542
12318
|
setFriendlyName: () => setFriendlyName
|
|
10543
12319
|
});
|
|
10544
|
-
import
|
|
10545
|
-
import
|
|
10546
|
-
import { readFileSync as
|
|
10547
|
-
import
|
|
12320
|
+
import crypto12 from "crypto";
|
|
12321
|
+
import os15 from "os";
|
|
12322
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync10, existsSync as existsSync22 } from "fs";
|
|
12323
|
+
import path26 from "path";
|
|
10548
12324
|
function getDeviceInfo() {
|
|
10549
|
-
if (
|
|
12325
|
+
if (existsSync22(DEVICE_JSON_PATH)) {
|
|
10550
12326
|
try {
|
|
10551
|
-
const raw =
|
|
12327
|
+
const raw = readFileSync18(DEVICE_JSON_PATH, "utf8");
|
|
10552
12328
|
const data = JSON.parse(raw);
|
|
10553
12329
|
if (data.deviceId && data.friendlyName && data.hostname) {
|
|
10554
12330
|
return data;
|
|
@@ -10556,20 +12332,20 @@ function getDeviceInfo() {
|
|
|
10556
12332
|
} catch {
|
|
10557
12333
|
}
|
|
10558
12334
|
}
|
|
10559
|
-
const hostname =
|
|
12335
|
+
const hostname = os15.hostname();
|
|
10560
12336
|
const info = {
|
|
10561
|
-
deviceId:
|
|
12337
|
+
deviceId: crypto12.randomUUID(),
|
|
10562
12338
|
friendlyName: hostname.replace(/\./g, "-").toLowerCase(),
|
|
10563
12339
|
hostname
|
|
10564
12340
|
};
|
|
10565
|
-
|
|
10566
|
-
|
|
12341
|
+
mkdirSync10(path26.dirname(DEVICE_JSON_PATH), { recursive: true });
|
|
12342
|
+
writeFileSync12(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10567
12343
|
return info;
|
|
10568
12344
|
}
|
|
10569
12345
|
function setFriendlyName(name) {
|
|
10570
12346
|
const info = getDeviceInfo();
|
|
10571
12347
|
info.friendlyName = name;
|
|
10572
|
-
|
|
12348
|
+
writeFileSync12(DEVICE_JSON_PATH, JSON.stringify(info, null, 2));
|
|
10573
12349
|
}
|
|
10574
12350
|
async function resolveTargetDevice(targetAgent, targetProject) {
|
|
10575
12351
|
const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
@@ -10603,7 +12379,7 @@ var init_device_registry = __esm({
|
|
|
10603
12379
|
"src/lib/device-registry.ts"() {
|
|
10604
12380
|
"use strict";
|
|
10605
12381
|
init_config();
|
|
10606
|
-
DEVICE_JSON_PATH =
|
|
12382
|
+
DEVICE_JSON_PATH = path26.join(EXE_AI_DIR, "device.json");
|
|
10607
12383
|
}
|
|
10608
12384
|
});
|
|
10609
12385
|
|
|
@@ -10628,18 +12404,18 @@ function assertSecureWsUrl(url) {
|
|
|
10628
12404
|
`Malformed WebSocket URL rejected: "${url}".`
|
|
10629
12405
|
);
|
|
10630
12406
|
}
|
|
10631
|
-
if (
|
|
12407
|
+
if (LOCALHOST_PATTERNS2.test(parsed.hostname)) return;
|
|
10632
12408
|
throw new Error(
|
|
10633
12409
|
`Insecure WebSocket URL rejected: "${url}". Use wss:// for remote hosts. Plain ws:// is only allowed for localhost.`
|
|
10634
12410
|
);
|
|
10635
12411
|
}
|
|
10636
12412
|
}
|
|
10637
|
-
var
|
|
12413
|
+
var LOCALHOST_PATTERNS2;
|
|
10638
12414
|
var init_gateway_client = __esm({
|
|
10639
12415
|
"src/lib/gateway-client.ts"() {
|
|
10640
12416
|
"use strict";
|
|
10641
12417
|
init_message_queue();
|
|
10642
|
-
|
|
12418
|
+
LOCALHOST_PATTERNS2 = /^(localhost|127\.0\.0\.1|\[::1\])$/i;
|
|
10643
12419
|
}
|
|
10644
12420
|
});
|
|
10645
12421
|
|
|
@@ -10827,10 +12603,10 @@ __export(messaging_exports, {
|
|
|
10827
12603
|
sendMessage: () => sendMessage,
|
|
10828
12604
|
setWsClientSend: () => setWsClientSend
|
|
10829
12605
|
});
|
|
10830
|
-
import
|
|
12606
|
+
import crypto13 from "crypto";
|
|
10831
12607
|
function generateUlid() {
|
|
10832
12608
|
const timestamp = Date.now().toString(36).padStart(10, "0");
|
|
10833
|
-
const random =
|
|
12609
|
+
const random = crypto13.randomBytes(10).toString("hex").slice(0, 16);
|
|
10834
12610
|
return (timestamp + random).toUpperCase();
|
|
10835
12611
|
}
|
|
10836
12612
|
function rowToMessage(row) {
|
|
@@ -11089,13 +12865,13 @@ init_memory();
|
|
|
11089
12865
|
init_daemon_protocol();
|
|
11090
12866
|
init_daemon_auth();
|
|
11091
12867
|
init_daemon_orchestration();
|
|
11092
|
-
import
|
|
12868
|
+
import os16 from "os";
|
|
11093
12869
|
import net2 from "net";
|
|
11094
|
-
import { writeFileSync as
|
|
11095
|
-
import
|
|
12870
|
+
import { writeFileSync as writeFileSync13, unlinkSync as unlinkSync9, mkdirSync as mkdirSync11, existsSync as existsSync23, readFileSync as readFileSync19, chmodSync as chmodSync2 } from "fs";
|
|
12871
|
+
import path27 from "path";
|
|
11096
12872
|
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 ??
|
|
12873
|
+
var SOCKET_PATH2 = process.env.EXE_DAEMON_SOCK ?? process.env.EXE_EMBED_SOCK ?? path27.join(EXE_AI_DIR, "exed.sock");
|
|
12874
|
+
var PID_PATH2 = process.env.EXE_DAEMON_PID ?? process.env.EXE_EMBED_PID ?? path27.join(EXE_AI_DIR, "exed.pid");
|
|
11099
12875
|
var MODEL_FILE = "jina-embeddings-v5-small-q4_k_m.gguf";
|
|
11100
12876
|
var IDLE_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
11101
12877
|
var REVIEW_POLL_INTERVAL_MS = 60 * 1e3;
|
|
@@ -11123,8 +12899,8 @@ function enqueue(queue, entry) {
|
|
|
11123
12899
|
queue.push(entry);
|
|
11124
12900
|
}
|
|
11125
12901
|
async function loadModel() {
|
|
11126
|
-
const modelPath =
|
|
11127
|
-
if (!
|
|
12902
|
+
const modelPath = path27.join(MODELS_DIR, MODEL_FILE);
|
|
12903
|
+
if (!existsSync23(modelPath)) {
|
|
11128
12904
|
process.stderr.write(`[exed] FATAL: model not found at ${modelPath}
|
|
11129
12905
|
`);
|
|
11130
12906
|
process.exit(1);
|
|
@@ -11194,6 +12970,11 @@ function checkIdle() {
|
|
|
11194
12970
|
}
|
|
11195
12971
|
async function shutdown() {
|
|
11196
12972
|
resetIdleTimer();
|
|
12973
|
+
try {
|
|
12974
|
+
const { stopProjectionWorker: stopProjectionWorker2 } = await Promise.resolve().then(() => (init_projection_worker(), projection_worker_exports));
|
|
12975
|
+
stopProjectionWorker2();
|
|
12976
|
+
} catch {
|
|
12977
|
+
}
|
|
11197
12978
|
try {
|
|
11198
12979
|
const { disposeShards: disposeShards2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
|
|
11199
12980
|
disposeShards2();
|
|
@@ -11215,11 +12996,11 @@ async function shutdown() {
|
|
|
11215
12996
|
}
|
|
11216
12997
|
_llama = null;
|
|
11217
12998
|
try {
|
|
11218
|
-
|
|
12999
|
+
unlinkSync9(SOCKET_PATH2);
|
|
11219
13000
|
} catch {
|
|
11220
13001
|
}
|
|
11221
13002
|
try {
|
|
11222
|
-
|
|
13003
|
+
unlinkSync9(PID_PATH2);
|
|
11223
13004
|
} catch {
|
|
11224
13005
|
}
|
|
11225
13006
|
process.stderr.write("[exed] Shutdown complete.\n");
|
|
@@ -11355,28 +13136,28 @@ async function handleIngest(req) {
|
|
|
11355
13136
|
}
|
|
11356
13137
|
}
|
|
11357
13138
|
function startServer() {
|
|
11358
|
-
|
|
13139
|
+
mkdirSync11(path27.dirname(SOCKET_PATH2), { recursive: true });
|
|
11359
13140
|
try {
|
|
11360
|
-
chmodSync2(
|
|
13141
|
+
chmodSync2(path27.dirname(SOCKET_PATH2), 448);
|
|
11361
13142
|
} catch {
|
|
11362
13143
|
}
|
|
11363
13144
|
_daemonToken = ensureDaemonToken(process.env[DAEMON_TOKEN_ENV2] ?? null);
|
|
11364
13145
|
for (const oldFile of ["embed.sock", "embed.pid"]) {
|
|
11365
|
-
const oldPath =
|
|
13146
|
+
const oldPath = path27.join(path27.dirname(SOCKET_PATH2), oldFile);
|
|
11366
13147
|
try {
|
|
11367
13148
|
if (oldFile.endsWith(".pid")) {
|
|
11368
|
-
const pid = parseInt(
|
|
13149
|
+
const pid = parseInt(readFileSync19(oldPath, "utf8").trim(), 10);
|
|
11369
13150
|
if (pid > 0) try {
|
|
11370
13151
|
process.kill(pid, "SIGKILL");
|
|
11371
13152
|
} catch {
|
|
11372
13153
|
}
|
|
11373
13154
|
}
|
|
11374
|
-
|
|
13155
|
+
unlinkSync9(oldPath);
|
|
11375
13156
|
} catch {
|
|
11376
13157
|
}
|
|
11377
13158
|
}
|
|
11378
13159
|
try {
|
|
11379
|
-
|
|
13160
|
+
unlinkSync9(SOCKET_PATH2);
|
|
11380
13161
|
} catch {
|
|
11381
13162
|
}
|
|
11382
13163
|
const server = net2.createServer((socket) => {
|
|
@@ -11466,7 +13247,7 @@ function startServer() {
|
|
|
11466
13247
|
chmodSync2(SOCKET_PATH2, 384);
|
|
11467
13248
|
} catch {
|
|
11468
13249
|
}
|
|
11469
|
-
|
|
13250
|
+
writeFileSync13(PID_PATH2, String(process.pid));
|
|
11470
13251
|
try {
|
|
11471
13252
|
chmodSync2(PID_PATH2, 384);
|
|
11472
13253
|
} catch {
|
|
@@ -11666,6 +13447,35 @@ function startConsolidation() {
|
|
|
11666
13447
|
`);
|
|
11667
13448
|
});
|
|
11668
13449
|
}
|
|
13450
|
+
var CLOUD_SYNC_INTERVAL_MS = Number(process.env.EXE_CLOUD_SYNC_INTERVAL_MS) || 5 * 60 * 1e3;
|
|
13451
|
+
function startCloudSyncTimer() {
|
|
13452
|
+
const tick = async () => {
|
|
13453
|
+
try {
|
|
13454
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
13455
|
+
const config = await loadConfig2();
|
|
13456
|
+
if (!config.cloud?.apiKey || !config.cloud?.endpoint) return;
|
|
13457
|
+
const { cloudSync: cloudSync2 } = await Promise.resolve().then(() => (init_cloud_sync(), cloud_sync_exports));
|
|
13458
|
+
const result = await cloudSync2({
|
|
13459
|
+
apiKey: config.cloud.apiKey,
|
|
13460
|
+
endpoint: config.cloud.endpoint
|
|
13461
|
+
});
|
|
13462
|
+
if (result.pushed > 0 || result.pulled > 0) {
|
|
13463
|
+
process.stderr.write(
|
|
13464
|
+
`[exed] Cloud sync: pushed=${result.pushed} pulled=${result.pulled} total=${result.totalMemories}
|
|
13465
|
+
`
|
|
13466
|
+
);
|
|
13467
|
+
}
|
|
13468
|
+
} catch (err) {
|
|
13469
|
+
process.stderr.write(`[exed] Cloud sync error (non-fatal): ${err instanceof Error ? err.message : String(err)}
|
|
13470
|
+
`);
|
|
13471
|
+
}
|
|
13472
|
+
};
|
|
13473
|
+
const timer = setInterval(() => void tick(), CLOUD_SYNC_INTERVAL_MS);
|
|
13474
|
+
timer.unref();
|
|
13475
|
+
setTimeout(() => void tick(), 3e4);
|
|
13476
|
+
process.stderr.write(`[exed] Cloud sync timer started (every ${CLOUD_SYNC_INTERVAL_MS / 6e4}m)
|
|
13477
|
+
`);
|
|
13478
|
+
}
|
|
11669
13479
|
var SKILL_SWEEP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
11670
13480
|
function startSkillSweep() {
|
|
11671
13481
|
const tick = async () => {
|
|
@@ -11763,7 +13573,7 @@ function startWikiSync() {
|
|
|
11763
13573
|
});
|
|
11764
13574
|
}
|
|
11765
13575
|
var AGENT_STATS_INTERVAL_MS = 60 * 1e3;
|
|
11766
|
-
var AGENT_STATS_PATH =
|
|
13576
|
+
var AGENT_STATS_PATH = path27.join(EXE_AI_DIR, "agent-stats.json");
|
|
11767
13577
|
async function writeAgentStats() {
|
|
11768
13578
|
if (!await ensureStoreForPolling()) return;
|
|
11769
13579
|
try {
|
|
@@ -11821,7 +13631,7 @@ async function writeAgentStats() {
|
|
|
11821
13631
|
pid: process.pid
|
|
11822
13632
|
}
|
|
11823
13633
|
};
|
|
11824
|
-
|
|
13634
|
+
writeFileSync13(AGENT_STATS_PATH, JSON.stringify(stats, null, 2), "utf8");
|
|
11825
13635
|
} catch (err) {
|
|
11826
13636
|
process.stderr.write(`[exed] Agent stats error: ${err instanceof Error ? err.message : String(err)}
|
|
11827
13637
|
`);
|
|
@@ -11893,12 +13703,12 @@ function startIntercomQueueDrain() {
|
|
|
11893
13703
|
const hasInProgressTask = (session) => {
|
|
11894
13704
|
try {
|
|
11895
13705
|
const { baseAgentName: ban } = (init_employees(), __toCommonJS(employees_exports));
|
|
11896
|
-
const
|
|
11897
|
-
const { existsSync:
|
|
11898
|
-
const
|
|
13706
|
+
const path28 = __require("path");
|
|
13707
|
+
const { existsSync: existsSync24 } = __require("fs");
|
|
13708
|
+
const os17 = __require("os");
|
|
11899
13709
|
const agent = ban(session.split("-")[0] ?? session);
|
|
11900
|
-
const markerPath =
|
|
11901
|
-
return
|
|
13710
|
+
const markerPath = path28.join(os17.homedir(), ".exe-os", "session-cache", `current-task-${agent}.json`);
|
|
13711
|
+
return existsSync24(markerPath);
|
|
11902
13712
|
} catch {
|
|
11903
13713
|
return false;
|
|
11904
13714
|
}
|
|
@@ -11987,7 +13797,7 @@ function startAutoWake() {
|
|
|
11987
13797
|
process.stderr.write(`[exed] Auto-wake started (every ${AUTO_WAKE_INTERVAL_MS / 1e3}s)
|
|
11988
13798
|
`);
|
|
11989
13799
|
}
|
|
11990
|
-
var TOTAL_MEM_GB =
|
|
13800
|
+
var TOTAL_MEM_GB = os16.totalmem() / 1024 ** 3;
|
|
11991
13801
|
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
13802
|
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
13803
|
var RSS_CHECK_INTERVAL_MS = 30 * 1e3;
|
|
@@ -12023,8 +13833,8 @@ process.on("SIGINT", () => void shutdown());
|
|
|
12023
13833
|
process.on("SIGTERM", () => void shutdown());
|
|
12024
13834
|
function checkExistingDaemon() {
|
|
12025
13835
|
try {
|
|
12026
|
-
if (!
|
|
12027
|
-
const pid = parseInt(
|
|
13836
|
+
if (!existsSync23(PID_PATH2)) return false;
|
|
13837
|
+
const pid = parseInt(readFileSync19(PID_PATH2, "utf8").trim(), 10);
|
|
12028
13838
|
if (!pid || isNaN(pid)) return false;
|
|
12029
13839
|
process.kill(pid, 0);
|
|
12030
13840
|
process.stderr.write(`[exed] Another daemon is already running (PID ${pid}). Exiting.
|
|
@@ -12037,7 +13847,7 @@ function checkExistingDaemon() {
|
|
|
12037
13847
|
return true;
|
|
12038
13848
|
}
|
|
12039
13849
|
try {
|
|
12040
|
-
|
|
13850
|
+
unlinkSync9(PID_PATH2);
|
|
12041
13851
|
} catch {
|
|
12042
13852
|
}
|
|
12043
13853
|
return false;
|
|
@@ -12103,6 +13913,7 @@ try {
|
|
|
12103
13913
|
startIdleKill();
|
|
12104
13914
|
startConsolidation();
|
|
12105
13915
|
startSkillSweep();
|
|
13916
|
+
startCloudSyncTimer();
|
|
12106
13917
|
startOrphanReaper();
|
|
12107
13918
|
startAgentStats();
|
|
12108
13919
|
startCapacityMonitoring();
|
|
@@ -12113,6 +13924,8 @@ try {
|
|
|
12113
13924
|
startConfidenceDecay();
|
|
12114
13925
|
startAutoUpdateCheck();
|
|
12115
13926
|
startRssWatchdog();
|
|
13927
|
+
const { startProjectionWorker: startProjectionWorker2 } = await Promise.resolve().then(() => (init_projection_worker(), projection_worker_exports));
|
|
13928
|
+
startProjectionWorker2();
|
|
12116
13929
|
try {
|
|
12117
13930
|
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
12118
13931
|
const config = await loadConfig2();
|
|
@@ -12206,11 +14019,11 @@ try {
|
|
|
12206
14019
|
process.stderr.write(`[exed] FATAL: ${err instanceof Error ? err.message : String(err)}
|
|
12207
14020
|
`);
|
|
12208
14021
|
try {
|
|
12209
|
-
|
|
14022
|
+
unlinkSync9(SOCKET_PATH2);
|
|
12210
14023
|
} catch {
|
|
12211
14024
|
}
|
|
12212
14025
|
try {
|
|
12213
|
-
|
|
14026
|
+
unlinkSync9(PID_PATH2);
|
|
12214
14027
|
} catch {
|
|
12215
14028
|
}
|
|
12216
14029
|
process.exit(1);
|