@cfio/cohort-sync 0.4.8 → 0.5.0
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/index.js +380 -141
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -88,9 +88,8 @@ var init_keychain = __esm({
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
// src/hooks.ts
|
|
91
|
-
import
|
|
92
|
-
import
|
|
93
|
-
import path2 from "node:path";
|
|
91
|
+
import fs from "node:fs";
|
|
92
|
+
import path from "node:path";
|
|
94
93
|
|
|
95
94
|
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.48/node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
|
|
96
95
|
var value_exports = {};
|
|
@@ -2700,9 +2699,6 @@ var Type = type_exports2;
|
|
|
2700
2699
|
|
|
2701
2700
|
// src/sync.ts
|
|
2702
2701
|
import { execSync } from "node:child_process";
|
|
2703
|
-
import fs from "node:fs";
|
|
2704
|
-
import os from "node:os";
|
|
2705
|
-
import path from "node:path";
|
|
2706
2702
|
|
|
2707
2703
|
// ../../node_modules/.pnpm/convex@1.33.0_patch_hash=l43bztwr6e2lbmpd6ao6hmcg24_react@19.2.1/node_modules/convex/dist/esm/index.js
|
|
2708
2704
|
var version = "1.33.0";
|
|
@@ -4558,12 +4554,12 @@ function createApi(pathParts = []) {
|
|
|
4558
4554
|
`API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
|
|
4559
4555
|
);
|
|
4560
4556
|
}
|
|
4561
|
-
const
|
|
4557
|
+
const path2 = pathParts.slice(0, -1).join("/");
|
|
4562
4558
|
const exportName = pathParts[pathParts.length - 1];
|
|
4563
4559
|
if (exportName === "default") {
|
|
4564
|
-
return
|
|
4560
|
+
return path2;
|
|
4565
4561
|
} else {
|
|
4566
|
-
return
|
|
4562
|
+
return path2 + ":" + exportName;
|
|
4567
4563
|
}
|
|
4568
4564
|
} else if (prop === Symbol.toStringTag) {
|
|
4569
4565
|
return "FunctionReference";
|
|
@@ -7632,16 +7628,16 @@ var require_constants = __commonJS({
|
|
|
7632
7628
|
});
|
|
7633
7629
|
var require_node_gyp_build = __commonJS({
|
|
7634
7630
|
"../common/temp/node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
7635
|
-
var
|
|
7636
|
-
var
|
|
7637
|
-
var
|
|
7631
|
+
var fs2 = __require("fs");
|
|
7632
|
+
var path2 = __require("path");
|
|
7633
|
+
var os = __require("os");
|
|
7638
7634
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
7639
7635
|
var vars = process.config && process.config.variables || {};
|
|
7640
7636
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
7641
7637
|
var abi = process.versions.modules;
|
|
7642
7638
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
7643
|
-
var arch = process.env.npm_config_arch ||
|
|
7644
|
-
var platform = process.env.npm_config_platform ||
|
|
7639
|
+
var arch = process.env.npm_config_arch || os.arch();
|
|
7640
|
+
var platform = process.env.npm_config_platform || os.platform();
|
|
7645
7641
|
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
7646
7642
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
7647
7643
|
var uv = (process.versions.uv || "").split(".")[0];
|
|
@@ -7650,21 +7646,21 @@ var require_node_gyp_build = __commonJS({
|
|
|
7650
7646
|
return runtimeRequire(load.resolve(dir));
|
|
7651
7647
|
}
|
|
7652
7648
|
load.resolve = load.path = function(dir) {
|
|
7653
|
-
dir =
|
|
7649
|
+
dir = path2.resolve(dir || ".");
|
|
7654
7650
|
try {
|
|
7655
|
-
var name = runtimeRequire(
|
|
7651
|
+
var name = runtimeRequire(path2.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
7656
7652
|
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
7657
7653
|
} catch (err) {
|
|
7658
7654
|
}
|
|
7659
7655
|
if (!prebuildsOnly) {
|
|
7660
|
-
var release = getFirst(
|
|
7656
|
+
var release = getFirst(path2.join(dir, "build/Release"), matchBuild);
|
|
7661
7657
|
if (release) return release;
|
|
7662
|
-
var debug = getFirst(
|
|
7658
|
+
var debug = getFirst(path2.join(dir, "build/Debug"), matchBuild);
|
|
7663
7659
|
if (debug) return debug;
|
|
7664
7660
|
}
|
|
7665
7661
|
var prebuild = resolve(dir);
|
|
7666
7662
|
if (prebuild) return prebuild;
|
|
7667
|
-
var nearby = resolve(
|
|
7663
|
+
var nearby = resolve(path2.dirname(process.execPath));
|
|
7668
7664
|
if (nearby) return nearby;
|
|
7669
7665
|
var target = [
|
|
7670
7666
|
"platform=" + platform,
|
|
@@ -7681,26 +7677,26 @@ var require_node_gyp_build = __commonJS({
|
|
|
7681
7677
|
].filter(Boolean).join(" ");
|
|
7682
7678
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
7683
7679
|
function resolve(dir2) {
|
|
7684
|
-
var tuples = readdirSync(
|
|
7680
|
+
var tuples = readdirSync(path2.join(dir2, "prebuilds")).map(parseTuple);
|
|
7685
7681
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
7686
7682
|
if (!tuple) return;
|
|
7687
|
-
var prebuilds =
|
|
7683
|
+
var prebuilds = path2.join(dir2, "prebuilds", tuple.name);
|
|
7688
7684
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
7689
7685
|
var candidates = parsed.filter(matchTags(runtime, abi));
|
|
7690
7686
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
7691
|
-
if (winner) return
|
|
7687
|
+
if (winner) return path2.join(prebuilds, winner.file);
|
|
7692
7688
|
}
|
|
7693
7689
|
};
|
|
7694
7690
|
function readdirSync(dir) {
|
|
7695
7691
|
try {
|
|
7696
|
-
return
|
|
7692
|
+
return fs2.readdirSync(dir);
|
|
7697
7693
|
} catch (err) {
|
|
7698
7694
|
return [];
|
|
7699
7695
|
}
|
|
7700
7696
|
}
|
|
7701
7697
|
function getFirst(dir, filter) {
|
|
7702
7698
|
var files = readdirSync(dir).filter(filter);
|
|
7703
|
-
return files[0] &&
|
|
7699
|
+
return files[0] && path2.join(dir, files[0]);
|
|
7704
7700
|
}
|
|
7705
7701
|
function matchBuild(name) {
|
|
7706
7702
|
return /\.node$/.test(name);
|
|
@@ -7787,7 +7783,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
7787
7783
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
7788
7784
|
}
|
|
7789
7785
|
function isAlpine(platform2) {
|
|
7790
|
-
return platform2 === "linux" &&
|
|
7786
|
+
return platform2 === "linux" && fs2.existsSync("/etc/alpine-release");
|
|
7791
7787
|
}
|
|
7792
7788
|
load.parseTags = parseTags;
|
|
7793
7789
|
load.matchTags = matchTags;
|
|
@@ -11506,8 +11502,151 @@ var _systemSchema = defineSchema({
|
|
|
11506
11502
|
})
|
|
11507
11503
|
});
|
|
11508
11504
|
|
|
11505
|
+
// src/gateway-rpc.ts
|
|
11506
|
+
import crypto from "node:crypto";
|
|
11507
|
+
function buildRequestFrame(id, method, params) {
|
|
11508
|
+
return { id, type: "req", method, params };
|
|
11509
|
+
}
|
|
11510
|
+
function getPendingRequests() {
|
|
11511
|
+
const g = globalThis;
|
|
11512
|
+
const hot = g.__cohort_sync__ ?? (g.__cohort_sync__ = {});
|
|
11513
|
+
if (!hot.pendingGatewayRequests) hot.pendingGatewayRequests = /* @__PURE__ */ new Map();
|
|
11514
|
+
return hot.pendingGatewayRequests;
|
|
11515
|
+
}
|
|
11516
|
+
var authReady = null;
|
|
11517
|
+
function openGatewayConnection(port, token, logger) {
|
|
11518
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
11519
|
+
let resolveAuth;
|
|
11520
|
+
let rejectAuth;
|
|
11521
|
+
let authSettled = false;
|
|
11522
|
+
authReady = new Promise((res, rej) => {
|
|
11523
|
+
resolveAuth = res;
|
|
11524
|
+
rejectAuth = rej;
|
|
11525
|
+
});
|
|
11526
|
+
ws.addEventListener("open", () => {
|
|
11527
|
+
ws.send(JSON.stringify({
|
|
11528
|
+
minProtocol: 1,
|
|
11529
|
+
maxProtocol: 1,
|
|
11530
|
+
client: {
|
|
11531
|
+
id: "gateway-client",
|
|
11532
|
+
version: "1.0.0",
|
|
11533
|
+
platform: process.platform,
|
|
11534
|
+
mode: "backend"
|
|
11535
|
+
},
|
|
11536
|
+
auth: { token }
|
|
11537
|
+
}));
|
|
11538
|
+
logger.info(`cohort-sync: gateway WS connected to port ${port}, awaiting hello-ok`);
|
|
11539
|
+
});
|
|
11540
|
+
ws.addEventListener("message", (event) => {
|
|
11541
|
+
try {
|
|
11542
|
+
const data = JSON.parse(String(event.data));
|
|
11543
|
+
if (data.type === "hello-ok") {
|
|
11544
|
+
authSettled = true;
|
|
11545
|
+
resolveAuth();
|
|
11546
|
+
logger.info("cohort-sync: gateway WS authenticated");
|
|
11547
|
+
return;
|
|
11548
|
+
}
|
|
11549
|
+
if (data.type === "hello-error" || data.type === "error") {
|
|
11550
|
+
authSettled = true;
|
|
11551
|
+
rejectAuth(new Error(`Gateway auth failed: ${data.message ?? data.error ?? "unknown"}`));
|
|
11552
|
+
ws.close();
|
|
11553
|
+
return;
|
|
11554
|
+
}
|
|
11555
|
+
const pending = getPendingRequests();
|
|
11556
|
+
if (data.type === "res" && data.id && pending.has(data.id)) {
|
|
11557
|
+
const entry = pending.get(data.id);
|
|
11558
|
+
pending.delete(data.id);
|
|
11559
|
+
clearTimeout(entry.timer);
|
|
11560
|
+
if (data.ok) {
|
|
11561
|
+
entry.resolve(data.payload);
|
|
11562
|
+
} else {
|
|
11563
|
+
entry.reject(new Error(`Gateway method failed: ${data.error?.message ?? "unknown"}`));
|
|
11564
|
+
}
|
|
11565
|
+
}
|
|
11566
|
+
} catch {
|
|
11567
|
+
}
|
|
11568
|
+
});
|
|
11569
|
+
ws.addEventListener("close", () => {
|
|
11570
|
+
logger.warn("cohort-sync: gateway WS closed");
|
|
11571
|
+
if (!authSettled) {
|
|
11572
|
+
authSettled = true;
|
|
11573
|
+
rejectAuth(new Error("Gateway WS closed during auth"));
|
|
11574
|
+
}
|
|
11575
|
+
const pending = getPendingRequests();
|
|
11576
|
+
for (const [, { reject, timer }] of pending) {
|
|
11577
|
+
clearTimeout(timer);
|
|
11578
|
+
reject(new Error("Gateway WS closed"));
|
|
11579
|
+
}
|
|
11580
|
+
pending.clear();
|
|
11581
|
+
});
|
|
11582
|
+
ws.addEventListener("error", (err) => {
|
|
11583
|
+
logger.error(`cohort-sync: gateway WS error: ${String(err)}`);
|
|
11584
|
+
});
|
|
11585
|
+
return ws;
|
|
11586
|
+
}
|
|
11587
|
+
async function callGatewayMethod(ws, method, params, timeoutMs = 1e4) {
|
|
11588
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
11589
|
+
throw new Error("Gateway WS not connected");
|
|
11590
|
+
}
|
|
11591
|
+
if (authReady) await authReady;
|
|
11592
|
+
const id = crypto.randomUUID();
|
|
11593
|
+
const frame = buildRequestFrame(id, method, params);
|
|
11594
|
+
const pending = getPendingRequests();
|
|
11595
|
+
return new Promise((resolve, reject) => {
|
|
11596
|
+
const timer = setTimeout(() => {
|
|
11597
|
+
pending.delete(id);
|
|
11598
|
+
reject(new Error(`Gateway method ${method} timed out after ${timeoutMs}ms`));
|
|
11599
|
+
}, timeoutMs);
|
|
11600
|
+
pending.set(id, {
|
|
11601
|
+
resolve,
|
|
11602
|
+
reject,
|
|
11603
|
+
timer
|
|
11604
|
+
});
|
|
11605
|
+
ws.send(JSON.stringify(frame));
|
|
11606
|
+
});
|
|
11607
|
+
}
|
|
11608
|
+
|
|
11609
|
+
// src/cron-mapping.ts
|
|
11610
|
+
function formatSchedule(s) {
|
|
11611
|
+
switch (s.kind) {
|
|
11612
|
+
case "cron":
|
|
11613
|
+
return s.expr ?? "";
|
|
11614
|
+
case "every":
|
|
11615
|
+
return `every ${humanizeMs(s.everyMs ?? 0)}`;
|
|
11616
|
+
case "at":
|
|
11617
|
+
return `once at ${s.at ?? ""}`;
|
|
11618
|
+
default:
|
|
11619
|
+
return s.kind;
|
|
11620
|
+
}
|
|
11621
|
+
}
|
|
11622
|
+
function humanizeMs(ms) {
|
|
11623
|
+
if (ms >= 864e5) return `${Math.round(ms / 864e5)}d`;
|
|
11624
|
+
if (ms >= 36e5) return `${Math.round(ms / 36e5)}h`;
|
|
11625
|
+
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
11626
|
+
return `${Math.round(ms / 1e3)}s`;
|
|
11627
|
+
}
|
|
11628
|
+
function mapCronJob(job, resolveAgentName) {
|
|
11629
|
+
return {
|
|
11630
|
+
id: job.id,
|
|
11631
|
+
text: job.name,
|
|
11632
|
+
schedule: formatSchedule(job.schedule),
|
|
11633
|
+
enabled: job.enabled,
|
|
11634
|
+
agentId: resolveAgentName(job.agentId ?? "main"),
|
|
11635
|
+
nextRun: job.state?.nextRunAtMs,
|
|
11636
|
+
lastRun: job.state?.lastRunAtMs,
|
|
11637
|
+
lastStatus: job.state?.lastRunStatus,
|
|
11638
|
+
lastError: job.state?.lastError ?? null
|
|
11639
|
+
};
|
|
11640
|
+
}
|
|
11641
|
+
function reverseResolveAgentName(cohortName, forwardMap) {
|
|
11642
|
+
const lower = cohortName.toLowerCase();
|
|
11643
|
+
for (const [openclawId, name] of Object.entries(forwardMap)) {
|
|
11644
|
+
if (name.toLowerCase() === lower) return openclawId;
|
|
11645
|
+
}
|
|
11646
|
+
return cohortName;
|
|
11647
|
+
}
|
|
11648
|
+
|
|
11509
11649
|
// src/subscription.ts
|
|
11510
|
-
import { execFileSync } from "node:child_process";
|
|
11511
11650
|
function deriveConvexUrl(apiUrl) {
|
|
11512
11651
|
return apiUrl.replace(/\.convex\.site\/?$/, ".convex.cloud");
|
|
11513
11652
|
}
|
|
@@ -11605,7 +11744,11 @@ function getHotState() {
|
|
|
11605
11744
|
lastKnownRoster: [],
|
|
11606
11745
|
intervals: { heartbeat: null, activityFlush: null },
|
|
11607
11746
|
activityBuffer: [],
|
|
11608
|
-
channelAgentBridge: {}
|
|
11747
|
+
channelAgentBridge: {},
|
|
11748
|
+
gatewayWs: null,
|
|
11749
|
+
gatewayPort: null,
|
|
11750
|
+
gatewayToken: null,
|
|
11751
|
+
commandSubscription: null
|
|
11609
11752
|
};
|
|
11610
11753
|
globalThis[HOT_KEY] = state;
|
|
11611
11754
|
}
|
|
@@ -11736,7 +11879,7 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11736
11879
|
const c = getOrCreateClient();
|
|
11737
11880
|
if (!c) {
|
|
11738
11881
|
logger.warn("cohort-sync: no ConvexClient \u2014 command subscription skipped");
|
|
11739
|
-
return;
|
|
11882
|
+
return null;
|
|
11740
11883
|
}
|
|
11741
11884
|
let processing = false;
|
|
11742
11885
|
const unsubscribe = c.onUpdate(
|
|
@@ -11760,19 +11903,94 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11760
11903
|
process.kill(process.pid, "SIGTERM");
|
|
11761
11904
|
return;
|
|
11762
11905
|
}
|
|
11763
|
-
if (cmd.type.startsWith("cron")
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11906
|
+
if (cmd.type.startsWith("cron")) {
|
|
11907
|
+
const ws = getHotState().gatewayWs;
|
|
11908
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
11909
|
+
logger.warn(`cohort-sync: gateway WS not connected, cannot execute ${cmd.type}`);
|
|
11910
|
+
continue;
|
|
11911
|
+
}
|
|
11912
|
+
const nameMap = cfg.agentNameMap ?? {};
|
|
11913
|
+
if (cmd.type === "cronEnable") {
|
|
11914
|
+
await callGatewayMethod(ws, "cohort-sync/cron-update", {
|
|
11915
|
+
jobId: cmd.payload?.jobId,
|
|
11916
|
+
patch: { enabled: true }
|
|
11917
|
+
});
|
|
11918
|
+
} else if (cmd.type === "cronDisable") {
|
|
11919
|
+
await callGatewayMethod(ws, "cohort-sync/cron-update", {
|
|
11920
|
+
jobId: cmd.payload?.jobId,
|
|
11921
|
+
patch: { enabled: false }
|
|
11922
|
+
});
|
|
11923
|
+
} else if (cmd.type === "cronDelete") {
|
|
11924
|
+
await callGatewayMethod(ws, "cohort-sync/cron-remove", {
|
|
11925
|
+
jobId: cmd.payload?.jobId
|
|
11926
|
+
});
|
|
11927
|
+
} else if (cmd.type === "cronRunNow") {
|
|
11928
|
+
const runResult = await callGatewayMethod(
|
|
11929
|
+
ws,
|
|
11930
|
+
"cohort-sync/cron-run",
|
|
11931
|
+
{ jobId: cmd.payload?.jobId }
|
|
11932
|
+
);
|
|
11933
|
+
if (runResult?.ok && runResult?.ran) {
|
|
11934
|
+
const jobId = cmd.payload?.jobId;
|
|
11935
|
+
let polls = 0;
|
|
11936
|
+
const pollInterval = setInterval(async () => {
|
|
11937
|
+
polls++;
|
|
11938
|
+
if (polls >= 15) {
|
|
11939
|
+
clearInterval(pollInterval);
|
|
11940
|
+
return;
|
|
11941
|
+
}
|
|
11942
|
+
try {
|
|
11943
|
+
const pollWs = getHotState().gatewayWs;
|
|
11944
|
+
if (!pollWs || pollWs.readyState !== WebSocket.OPEN) {
|
|
11945
|
+
clearInterval(pollInterval);
|
|
11946
|
+
return;
|
|
11947
|
+
}
|
|
11948
|
+
const freshJobs = await callGatewayMethod(pollWs, "cohort-sync/cron-list");
|
|
11949
|
+
const job = freshJobs.find((j) => j.id === jobId);
|
|
11950
|
+
if (job && !job.state?.runningAtMs) {
|
|
11951
|
+
clearInterval(pollInterval);
|
|
11952
|
+
const mapped = freshJobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
11953
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
11954
|
+
}
|
|
11955
|
+
} catch {
|
|
11956
|
+
}
|
|
11957
|
+
}, 2e3);
|
|
11958
|
+
}
|
|
11959
|
+
} else if (cmd.type === "cronCreate") {
|
|
11960
|
+
const agentId = reverseResolveAgentName(cmd.payload?.agentId ?? "main", nameMap);
|
|
11961
|
+
await callGatewayMethod(ws, "cohort-sync/cron-add", {
|
|
11962
|
+
job: {
|
|
11963
|
+
agentId,
|
|
11964
|
+
name: cmd.payload?.name,
|
|
11965
|
+
enabled: true,
|
|
11966
|
+
schedule: cmd.payload?.schedule,
|
|
11967
|
+
payload: { kind: "agentTurn", message: cmd.payload?.message },
|
|
11968
|
+
sessionTarget: "isolated",
|
|
11969
|
+
wakeMode: "now"
|
|
11970
|
+
}
|
|
11971
|
+
});
|
|
11972
|
+
} else if (cmd.type === "cronUpdate") {
|
|
11973
|
+
const patch = {};
|
|
11974
|
+
if (cmd.payload?.name) patch.name = cmd.payload.name;
|
|
11975
|
+
if (cmd.payload?.schedule) patch.schedule = cmd.payload.schedule;
|
|
11976
|
+
if (cmd.payload?.message) patch.payload = { kind: "agentTurn", message: cmd.payload.message };
|
|
11977
|
+
if (cmd.payload?.agentId) patch.agentId = reverseResolveAgentName(cmd.payload.agentId, nameMap);
|
|
11978
|
+
await callGatewayMethod(ws, "cohort-sync/cron-update", {
|
|
11979
|
+
jobId: cmd.payload?.jobId,
|
|
11980
|
+
patch
|
|
11981
|
+
});
|
|
11982
|
+
} else {
|
|
11983
|
+
logger.warn(`cohort-sync: unknown cron command type: ${cmd.type}`);
|
|
11984
|
+
}
|
|
11985
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
11986
|
+
try {
|
|
11987
|
+
const freshJobs = await callGatewayMethod(ws, "cohort-sync/cron-list");
|
|
11988
|
+
const mapped = freshJobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
11989
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
11990
|
+
} catch (snapErr) {
|
|
11991
|
+
logger.warn(`cohort-sync: post-command snapshot push failed: ${String(snapErr)}`);
|
|
11770
11992
|
}
|
|
11771
|
-
} catch (snapErr) {
|
|
11772
|
-
logger.warn(`cohort-sync: post-command snapshot push failed: ${String(snapErr)}`);
|
|
11773
11993
|
}
|
|
11774
|
-
} else if (cmd.type.startsWith("cron")) {
|
|
11775
|
-
logger.warn(`cohort-sync: cron command missing jobId: ${cmd.type}`);
|
|
11776
11994
|
} else {
|
|
11777
11995
|
logger.warn(`cohort-sync: unknown command type: ${cmd.type}`);
|
|
11778
11996
|
}
|
|
@@ -11788,34 +12006,8 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11788
12006
|
logger.error(`cohort-sync: command subscription error: ${String(err)}`);
|
|
11789
12007
|
}
|
|
11790
12008
|
);
|
|
11791
|
-
unsubscribers.push(unsubscribe);
|
|
11792
|
-
getHotState().unsubscribers = [...unsubscribers];
|
|
11793
12009
|
logger.info("cohort-sync: command subscription active");
|
|
11794
|
-
|
|
11795
|
-
function handleCronCommand(type, jobId, logger) {
|
|
11796
|
-
const subcommandMap = {
|
|
11797
|
-
cronEnable: ["cron", "enable", jobId],
|
|
11798
|
-
cronDisable: ["cron", "disable", jobId],
|
|
11799
|
-
cronRunNow: ["cron", "run", jobId],
|
|
11800
|
-
cronDelete: ["cron", "delete", jobId, "--force"]
|
|
11801
|
-
};
|
|
11802
|
-
const args = subcommandMap[type];
|
|
11803
|
-
if (!args) {
|
|
11804
|
-
logger.warn(`cohort-sync: unknown cron command type: ${type}`);
|
|
11805
|
-
return;
|
|
11806
|
-
}
|
|
11807
|
-
const timeout = type === "cronRunNow" ? 3e4 : 15e3;
|
|
11808
|
-
try {
|
|
11809
|
-
execFileSync("openclaw", args, {
|
|
11810
|
-
encoding: "utf8",
|
|
11811
|
-
timeout,
|
|
11812
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
11813
|
-
});
|
|
11814
|
-
logger.info(`cohort-sync: cron command ${type} executed for job ${jobId}`);
|
|
11815
|
-
} catch (err) {
|
|
11816
|
-
logger.error(`cohort-sync: cron command ${type} failed for job ${jobId}: ${String(err)}`);
|
|
11817
|
-
throw err;
|
|
11818
|
-
}
|
|
12010
|
+
return unsubscribe;
|
|
11819
12011
|
}
|
|
11820
12012
|
async function callAddCommentFromPlugin(apiKey, args) {
|
|
11821
12013
|
const c = getOrCreateClient();
|
|
@@ -11845,6 +12037,13 @@ function closeSubscription() {
|
|
|
11845
12037
|
} catch {
|
|
11846
12038
|
}
|
|
11847
12039
|
}
|
|
12040
|
+
if (state.commandSubscription) {
|
|
12041
|
+
try {
|
|
12042
|
+
state.commandSubscription();
|
|
12043
|
+
} catch {
|
|
12044
|
+
}
|
|
12045
|
+
state.commandSubscription = null;
|
|
12046
|
+
}
|
|
11848
12047
|
client?.close();
|
|
11849
12048
|
client = null;
|
|
11850
12049
|
clearHotState();
|
|
@@ -11918,10 +12117,6 @@ function getChannelAgent(channelId) {
|
|
|
11918
12117
|
}
|
|
11919
12118
|
|
|
11920
12119
|
// src/sync.ts
|
|
11921
|
-
var cronStorePath = null;
|
|
11922
|
-
function setCronStorePath(p) {
|
|
11923
|
-
cronStorePath = p;
|
|
11924
|
-
}
|
|
11925
12120
|
function extractJson(raw) {
|
|
11926
12121
|
const jsonStart = raw.search(/[\[{]/);
|
|
11927
12122
|
const jsonEndBracket = raw.lastIndexOf("]");
|
|
@@ -11953,73 +12148,35 @@ function fetchSkills(logger) {
|
|
|
11953
12148
|
return [];
|
|
11954
12149
|
}
|
|
11955
12150
|
}
|
|
11956
|
-
function fetchCronJobs(logger) {
|
|
11957
|
-
try {
|
|
11958
|
-
const storePath = cronStorePath ?? path.join(os.homedir(), ".openclaw", "cron", "jobs.json");
|
|
11959
|
-
if (!fs.existsSync(storePath)) {
|
|
11960
|
-
logger.warn(`cohort-sync: cron store not found at ${storePath}`);
|
|
11961
|
-
return null;
|
|
11962
|
-
}
|
|
11963
|
-
const raw = fs.readFileSync(storePath, "utf-8");
|
|
11964
|
-
const parsed = JSON.parse(raw);
|
|
11965
|
-
const jobs = Array.isArray(parsed?.jobs) ? parsed.jobs : [];
|
|
11966
|
-
return jobs.map((j) => ({
|
|
11967
|
-
id: String(j.id ?? "unknown"),
|
|
11968
|
-
text: String(j.name ?? ""),
|
|
11969
|
-
schedule: formatSchedule(j.schedule),
|
|
11970
|
-
...j.state?.nextRunAtMs != null ? { nextRun: Number(j.state.nextRunAtMs) } : {},
|
|
11971
|
-
...j.state?.lastRunAtMs != null ? { lastRun: Number(j.state.lastRunAtMs) } : {},
|
|
11972
|
-
...j.state?.lastStatus ? { lastStatus: String(j.state.lastStatus) } : {},
|
|
11973
|
-
enabled: j.enabled !== false,
|
|
11974
|
-
...j.agentId != null ? { agentId: String(j.agentId) } : {}
|
|
11975
|
-
}));
|
|
11976
|
-
} catch (err) {
|
|
11977
|
-
logger.warn(`cohort-sync: failed to read cron store: ${String(err)}`);
|
|
11978
|
-
return null;
|
|
11979
|
-
}
|
|
11980
|
-
}
|
|
11981
|
-
function formatSchedule(schedule) {
|
|
11982
|
-
if (typeof schedule === "string") return schedule;
|
|
11983
|
-
if (schedule && typeof schedule === "object") {
|
|
11984
|
-
const s = schedule;
|
|
11985
|
-
if (s.kind === "cron" && typeof s.expr === "string") return s.expr;
|
|
11986
|
-
if (s.kind === "interval" && typeof s.everyMs === "number") {
|
|
11987
|
-
const mins = Math.floor(Number(s.everyMs) / 6e4);
|
|
11988
|
-
if (mins > 0) return `*/${mins} * * * *`;
|
|
11989
|
-
return `every ${s.everyMs}ms`;
|
|
11990
|
-
}
|
|
11991
|
-
}
|
|
11992
|
-
return String(schedule);
|
|
11993
|
-
}
|
|
11994
12151
|
var VALID_STATUSES = /* @__PURE__ */ new Set(["idle", "working", "waiting"]);
|
|
11995
12152
|
function normalizeStatus(status) {
|
|
11996
12153
|
return VALID_STATUSES.has(status) ? status : "idle";
|
|
11997
12154
|
}
|
|
11998
|
-
async function v1Get(apiUrl, apiKey,
|
|
11999
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12155
|
+
async function v1Get(apiUrl, apiKey, path2) {
|
|
12156
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path2}`, {
|
|
12000
12157
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
12001
12158
|
signal: AbortSignal.timeout(1e4)
|
|
12002
12159
|
});
|
|
12003
|
-
if (!res.ok) throw new Error(`GET ${
|
|
12160
|
+
if (!res.ok) throw new Error(`GET ${path2} \u2192 ${res.status}`);
|
|
12004
12161
|
return res.json();
|
|
12005
12162
|
}
|
|
12006
|
-
async function v1Patch(apiUrl, apiKey,
|
|
12007
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12163
|
+
async function v1Patch(apiUrl, apiKey, path2, body) {
|
|
12164
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path2}`, {
|
|
12008
12165
|
method: "PATCH",
|
|
12009
12166
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
12010
12167
|
body: JSON.stringify(body),
|
|
12011
12168
|
signal: AbortSignal.timeout(1e4)
|
|
12012
12169
|
});
|
|
12013
|
-
if (!res.ok) throw new Error(`PATCH ${
|
|
12170
|
+
if (!res.ok) throw new Error(`PATCH ${path2} \u2192 ${res.status}`);
|
|
12014
12171
|
}
|
|
12015
|
-
async function v1Post(apiUrl, apiKey,
|
|
12016
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12172
|
+
async function v1Post(apiUrl, apiKey, path2, body) {
|
|
12173
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path2}`, {
|
|
12017
12174
|
method: "POST",
|
|
12018
12175
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
12019
12176
|
body: JSON.stringify(body),
|
|
12020
12177
|
signal: AbortSignal.timeout(1e4)
|
|
12021
12178
|
});
|
|
12022
|
-
if (!res.ok) throw new Error(`POST ${
|
|
12179
|
+
if (!res.ok) throw new Error(`POST ${path2} \u2192 ${res.status}`);
|
|
12023
12180
|
}
|
|
12024
12181
|
async function checkForUpdate(currentVersion, logger) {
|
|
12025
12182
|
try {
|
|
@@ -12187,6 +12344,65 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
12187
12344
|
logger.info("cohort-sync: full sync complete");
|
|
12188
12345
|
}
|
|
12189
12346
|
|
|
12347
|
+
// src/gateway-methods.ts
|
|
12348
|
+
function registerCronGatewayMethods(api) {
|
|
12349
|
+
api.registerGatewayMethod("cohort-sync/cron-list", async ({ context, respond }) => {
|
|
12350
|
+
try {
|
|
12351
|
+
const jobs = await context.cron.list({ includeDisabled: true });
|
|
12352
|
+
respond(true, jobs);
|
|
12353
|
+
} catch (err) {
|
|
12354
|
+
respond(false, void 0, {
|
|
12355
|
+
code: "INTERNAL_ERROR",
|
|
12356
|
+
message: (err instanceof Error ? err.message : String(err)).slice(0, 200)
|
|
12357
|
+
});
|
|
12358
|
+
}
|
|
12359
|
+
});
|
|
12360
|
+
api.registerGatewayMethod("cohort-sync/cron-run", async ({ context, params, respond }) => {
|
|
12361
|
+
try {
|
|
12362
|
+
const result = await context.cron.run(params.jobId, "force");
|
|
12363
|
+
respond(true, result);
|
|
12364
|
+
} catch (err) {
|
|
12365
|
+
respond(false, void 0, {
|
|
12366
|
+
code: "CRON_RUN_FAILED",
|
|
12367
|
+
message: (err instanceof Error ? err.message : String(err)).slice(0, 200)
|
|
12368
|
+
});
|
|
12369
|
+
}
|
|
12370
|
+
});
|
|
12371
|
+
api.registerGatewayMethod("cohort-sync/cron-update", async ({ context, params, respond }) => {
|
|
12372
|
+
try {
|
|
12373
|
+
const result = await context.cron.update(params.jobId, params.patch);
|
|
12374
|
+
respond(true, result);
|
|
12375
|
+
} catch (err) {
|
|
12376
|
+
respond(false, void 0, {
|
|
12377
|
+
code: "CRON_UPDATE_FAILED",
|
|
12378
|
+
message: (err instanceof Error ? err.message : String(err)).slice(0, 200)
|
|
12379
|
+
});
|
|
12380
|
+
}
|
|
12381
|
+
});
|
|
12382
|
+
api.registerGatewayMethod("cohort-sync/cron-remove", async ({ context, params, respond }) => {
|
|
12383
|
+
try {
|
|
12384
|
+
const result = await context.cron.remove(params.jobId);
|
|
12385
|
+
respond(true, result);
|
|
12386
|
+
} catch (err) {
|
|
12387
|
+
respond(false, void 0, {
|
|
12388
|
+
code: "CRON_REMOVE_FAILED",
|
|
12389
|
+
message: (err instanceof Error ? err.message : String(err)).slice(0, 200)
|
|
12390
|
+
});
|
|
12391
|
+
}
|
|
12392
|
+
});
|
|
12393
|
+
api.registerGatewayMethod("cohort-sync/cron-add", async ({ context, params, respond }) => {
|
|
12394
|
+
try {
|
|
12395
|
+
const result = await context.cron.add(params.job);
|
|
12396
|
+
respond(true, result);
|
|
12397
|
+
} catch (err) {
|
|
12398
|
+
respond(false, void 0, {
|
|
12399
|
+
code: "CRON_ADD_FAILED",
|
|
12400
|
+
message: (err instanceof Error ? err.message : String(err)).slice(0, 200)
|
|
12401
|
+
});
|
|
12402
|
+
}
|
|
12403
|
+
});
|
|
12404
|
+
}
|
|
12405
|
+
|
|
12190
12406
|
// src/agent-state.ts
|
|
12191
12407
|
import { basename } from "node:path";
|
|
12192
12408
|
|
|
@@ -12696,7 +12912,7 @@ function diag(label, data) {
|
|
|
12696
12912
|
const line = `[${ts}] ${label}${payload}
|
|
12697
12913
|
`;
|
|
12698
12914
|
try {
|
|
12699
|
-
|
|
12915
|
+
fs.appendFileSync(DIAG_LOG_PATH, line);
|
|
12700
12916
|
} catch {
|
|
12701
12917
|
}
|
|
12702
12918
|
}
|
|
@@ -12724,16 +12940,16 @@ function dumpEvent(event) {
|
|
|
12724
12940
|
}
|
|
12725
12941
|
var PLUGIN_VERSION = "unknown";
|
|
12726
12942
|
try {
|
|
12727
|
-
const pkgPath =
|
|
12728
|
-
const pkgJson = JSON.parse(
|
|
12943
|
+
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), "package.json");
|
|
12944
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
12729
12945
|
PLUGIN_VERSION = pkgJson.version ?? "unknown";
|
|
12730
12946
|
} catch {
|
|
12731
12947
|
}
|
|
12732
12948
|
diag("MODULE_LOADED", { BUILD_ID, PLUGIN_VERSION });
|
|
12733
12949
|
function parseIdentityFile(workspaceDir) {
|
|
12734
12950
|
try {
|
|
12735
|
-
const filePath =
|
|
12736
|
-
const content =
|
|
12951
|
+
const filePath = path.join(workspaceDir, "IDENTITY.md");
|
|
12952
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
12737
12953
|
const identity = {};
|
|
12738
12954
|
for (const line of content.split(/\r?\n/)) {
|
|
12739
12955
|
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
@@ -12771,8 +12987,8 @@ function getOrCreateTracker() {
|
|
|
12771
12987
|
state.tracker = fresh;
|
|
12772
12988
|
return fresh;
|
|
12773
12989
|
}
|
|
12774
|
-
var STATE_FILE_PATH =
|
|
12775
|
-
|
|
12990
|
+
var STATE_FILE_PATH = path.join(
|
|
12991
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
12776
12992
|
".session-state.json"
|
|
12777
12993
|
);
|
|
12778
12994
|
function saveSessionsToDisk(tracker) {
|
|
@@ -12789,14 +13005,14 @@ function saveSessionsToDisk(tracker) {
|
|
|
12789
13005
|
data.sessions.push({ agentName: name, key });
|
|
12790
13006
|
}
|
|
12791
13007
|
}
|
|
12792
|
-
|
|
13008
|
+
fs.writeFileSync(STATE_FILE_PATH, JSON.stringify(data));
|
|
12793
13009
|
} catch {
|
|
12794
13010
|
}
|
|
12795
13011
|
}
|
|
12796
13012
|
function loadSessionsFromDisk(tracker, logger) {
|
|
12797
13013
|
try {
|
|
12798
|
-
if (!
|
|
12799
|
-
const data = JSON.parse(
|
|
13014
|
+
if (!fs.existsSync(STATE_FILE_PATH)) return;
|
|
13015
|
+
const data = JSON.parse(fs.readFileSync(STATE_FILE_PATH, "utf8"));
|
|
12800
13016
|
if (Date.now() - new Date(data.savedAt).getTime() > 864e5) {
|
|
12801
13017
|
logger.info("cohort-sync: disk session state too old (>24h), skipping");
|
|
12802
13018
|
return;
|
|
@@ -12843,6 +13059,7 @@ function registerHooks(api, cfg) {
|
|
|
12843
13059
|
const tracker = getOrCreateTracker();
|
|
12844
13060
|
let heartbeatInterval = null;
|
|
12845
13061
|
let activityFlushInterval = null;
|
|
13062
|
+
registerCronGatewayMethods(api);
|
|
12846
13063
|
logger.info(`cohort-sync: registerHooks [${BUILD_ID}]`);
|
|
12847
13064
|
diag("REGISTER_HOOKS", {
|
|
12848
13065
|
BUILD_ID,
|
|
@@ -12854,9 +13071,6 @@ function registerHooks(api, cfg) {
|
|
|
12854
13071
|
agentIds: (config?.agents?.list ?? []).map((a) => a.id),
|
|
12855
13072
|
agentMessageProviders: (config?.agents?.list ?? []).map((a) => ({ id: a.id, mp: a.messageProvider }))
|
|
12856
13073
|
});
|
|
12857
|
-
const cronPath = path2.join(os2.homedir(), ".openclaw", "cron", "jobs.json");
|
|
12858
|
-
setCronStorePath(cronPath);
|
|
12859
|
-
diag("CRON_STORE_PATH", { cronPath, exists: fs2.existsSync(cronPath) });
|
|
12860
13074
|
setConvexUrl(cfg);
|
|
12861
13075
|
setLogger(logger);
|
|
12862
13076
|
restoreFromHotReload(logger);
|
|
@@ -12977,6 +13191,15 @@ function registerHooks(api, cfg) {
|
|
|
12977
13191
|
}, 3e3);
|
|
12978
13192
|
saveIntervalsToHot(heartbeatInterval, activityFlushInterval);
|
|
12979
13193
|
logger.info("cohort-sync: intervals created (heartbeat=2m, activityFlush=3s)");
|
|
13194
|
+
{
|
|
13195
|
+
const hotState = getHotState();
|
|
13196
|
+
if (hotState.commandSubscription) {
|
|
13197
|
+
hotState.commandSubscription();
|
|
13198
|
+
hotState.commandSubscription = null;
|
|
13199
|
+
}
|
|
13200
|
+
const unsub = initCommandSubscription(cfg, logger, resolveAgentName);
|
|
13201
|
+
hotState.commandSubscription = unsub;
|
|
13202
|
+
}
|
|
12980
13203
|
api.registerTool((toolCtx) => {
|
|
12981
13204
|
const agentId = toolCtx.agentId ?? "main";
|
|
12982
13205
|
const agentName = resolveAgentName(agentId);
|
|
@@ -13126,17 +13349,17 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13126
13349
|
}
|
|
13127
13350
|
}
|
|
13128
13351
|
saveSessionsToDisk(tracker);
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13132
|
-
const
|
|
13133
|
-
|
|
13134
|
-
|
|
13135
|
-
|
|
13136
|
-
|
|
13352
|
+
const ws = getHotState().gatewayWs;
|
|
13353
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
13354
|
+
try {
|
|
13355
|
+
const jobs = await callGatewayMethod(ws, "cohort-sync/cron-list");
|
|
13356
|
+
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
13357
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13358
|
+
} catch (err) {
|
|
13359
|
+
logger.warn(`cohort-sync: cron list failed: ${String(err)}`);
|
|
13137
13360
|
}
|
|
13138
|
-
}
|
|
13139
|
-
logger.warn(
|
|
13361
|
+
} else {
|
|
13362
|
+
logger.warn("cohort-sync: gateway WS not connected \u2014 skipping cron snapshot");
|
|
13140
13363
|
}
|
|
13141
13364
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
13142
13365
|
}
|
|
@@ -13175,6 +13398,23 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13175
13398
|
}
|
|
13176
13399
|
}
|
|
13177
13400
|
diag("GATEWAY_START_BRIDGE_AFTER_SEED", { bridge: { ...getHotState().channelAgentBridge } });
|
|
13401
|
+
const hotState = getHotState();
|
|
13402
|
+
hotState.gatewayPort = event.port;
|
|
13403
|
+
const rawToken = api.config?.gateway?.auth?.token;
|
|
13404
|
+
if (typeof rawToken === "string") {
|
|
13405
|
+
hotState.gatewayToken = rawToken;
|
|
13406
|
+
} else if (rawToken && typeof rawToken === "object" && rawToken.source === "env") {
|
|
13407
|
+
hotState.gatewayToken = process.env[rawToken.id] ?? null;
|
|
13408
|
+
} else {
|
|
13409
|
+
hotState.gatewayToken = null;
|
|
13410
|
+
}
|
|
13411
|
+
if (!hotState.gatewayWs || hotState.gatewayWs.readyState !== WebSocket.OPEN) {
|
|
13412
|
+
if (hotState.gatewayToken) {
|
|
13413
|
+
hotState.gatewayWs = openGatewayConnection(event.port, hotState.gatewayToken, logger);
|
|
13414
|
+
} else {
|
|
13415
|
+
logger.warn("cohort-sync: no gateway auth token in config \u2014 cron operations disabled");
|
|
13416
|
+
}
|
|
13417
|
+
}
|
|
13178
13418
|
await initSubscription(
|
|
13179
13419
|
event.port,
|
|
13180
13420
|
cfg,
|
|
@@ -13183,7 +13423,6 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13183
13423
|
).catch((err) => {
|
|
13184
13424
|
logger.error(`cohort-sync: subscription init failed: ${String(err)}`);
|
|
13185
13425
|
});
|
|
13186
|
-
initCommandSubscription(cfg, logger, resolveAgentName);
|
|
13187
13426
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
13188
13427
|
for (const agentId of allAgentIds) {
|
|
13189
13428
|
const agentName = resolveAgentName(agentId);
|
package/dist/package.json
CHANGED