@cfio/cohort-sync 0.4.6 → 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 -149
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -88,8 +88,8 @@ var init_keychain = __esm({
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
// src/hooks.ts
|
|
91
|
-
import
|
|
92
|
-
import
|
|
91
|
+
import fs from "node:fs";
|
|
92
|
+
import path from "node:path";
|
|
93
93
|
|
|
94
94
|
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.48/node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
|
|
95
95
|
var value_exports = {};
|
|
@@ -2699,9 +2699,6 @@ var Type = type_exports2;
|
|
|
2699
2699
|
|
|
2700
2700
|
// src/sync.ts
|
|
2701
2701
|
import { execSync } from "node:child_process";
|
|
2702
|
-
import fs from "node:fs";
|
|
2703
|
-
import os from "node:os";
|
|
2704
|
-
import path from "node:path";
|
|
2705
2702
|
|
|
2706
2703
|
// ../../node_modules/.pnpm/convex@1.33.0_patch_hash=l43bztwr6e2lbmpd6ao6hmcg24_react@19.2.1/node_modules/convex/dist/esm/index.js
|
|
2707
2704
|
var version = "1.33.0";
|
|
@@ -4557,12 +4554,12 @@ function createApi(pathParts = []) {
|
|
|
4557
4554
|
`API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
|
|
4558
4555
|
);
|
|
4559
4556
|
}
|
|
4560
|
-
const
|
|
4557
|
+
const path2 = pathParts.slice(0, -1).join("/");
|
|
4561
4558
|
const exportName = pathParts[pathParts.length - 1];
|
|
4562
4559
|
if (exportName === "default") {
|
|
4563
|
-
return
|
|
4560
|
+
return path2;
|
|
4564
4561
|
} else {
|
|
4565
|
-
return
|
|
4562
|
+
return path2 + ":" + exportName;
|
|
4566
4563
|
}
|
|
4567
4564
|
} else if (prop === Symbol.toStringTag) {
|
|
4568
4565
|
return "FunctionReference";
|
|
@@ -7631,16 +7628,16 @@ var require_constants = __commonJS({
|
|
|
7631
7628
|
});
|
|
7632
7629
|
var require_node_gyp_build = __commonJS({
|
|
7633
7630
|
"../common/temp/node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
7634
|
-
var
|
|
7635
|
-
var
|
|
7636
|
-
var
|
|
7631
|
+
var fs2 = __require("fs");
|
|
7632
|
+
var path2 = __require("path");
|
|
7633
|
+
var os = __require("os");
|
|
7637
7634
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
7638
7635
|
var vars = process.config && process.config.variables || {};
|
|
7639
7636
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
7640
7637
|
var abi = process.versions.modules;
|
|
7641
7638
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
7642
|
-
var arch = process.env.npm_config_arch ||
|
|
7643
|
-
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();
|
|
7644
7641
|
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
7645
7642
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
7646
7643
|
var uv = (process.versions.uv || "").split(".")[0];
|
|
@@ -7649,21 +7646,21 @@ var require_node_gyp_build = __commonJS({
|
|
|
7649
7646
|
return runtimeRequire(load.resolve(dir));
|
|
7650
7647
|
}
|
|
7651
7648
|
load.resolve = load.path = function(dir) {
|
|
7652
|
-
dir =
|
|
7649
|
+
dir = path2.resolve(dir || ".");
|
|
7653
7650
|
try {
|
|
7654
|
-
var name = runtimeRequire(
|
|
7651
|
+
var name = runtimeRequire(path2.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
7655
7652
|
if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
|
|
7656
7653
|
} catch (err) {
|
|
7657
7654
|
}
|
|
7658
7655
|
if (!prebuildsOnly) {
|
|
7659
|
-
var release = getFirst(
|
|
7656
|
+
var release = getFirst(path2.join(dir, "build/Release"), matchBuild);
|
|
7660
7657
|
if (release) return release;
|
|
7661
|
-
var debug = getFirst(
|
|
7658
|
+
var debug = getFirst(path2.join(dir, "build/Debug"), matchBuild);
|
|
7662
7659
|
if (debug) return debug;
|
|
7663
7660
|
}
|
|
7664
7661
|
var prebuild = resolve(dir);
|
|
7665
7662
|
if (prebuild) return prebuild;
|
|
7666
|
-
var nearby = resolve(
|
|
7663
|
+
var nearby = resolve(path2.dirname(process.execPath));
|
|
7667
7664
|
if (nearby) return nearby;
|
|
7668
7665
|
var target = [
|
|
7669
7666
|
"platform=" + platform,
|
|
@@ -7680,26 +7677,26 @@ var require_node_gyp_build = __commonJS({
|
|
|
7680
7677
|
].filter(Boolean).join(" ");
|
|
7681
7678
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
7682
7679
|
function resolve(dir2) {
|
|
7683
|
-
var tuples = readdirSync(
|
|
7680
|
+
var tuples = readdirSync(path2.join(dir2, "prebuilds")).map(parseTuple);
|
|
7684
7681
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
7685
7682
|
if (!tuple) return;
|
|
7686
|
-
var prebuilds =
|
|
7683
|
+
var prebuilds = path2.join(dir2, "prebuilds", tuple.name);
|
|
7687
7684
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
7688
7685
|
var candidates = parsed.filter(matchTags(runtime, abi));
|
|
7689
7686
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
7690
|
-
if (winner) return
|
|
7687
|
+
if (winner) return path2.join(prebuilds, winner.file);
|
|
7691
7688
|
}
|
|
7692
7689
|
};
|
|
7693
7690
|
function readdirSync(dir) {
|
|
7694
7691
|
try {
|
|
7695
|
-
return
|
|
7692
|
+
return fs2.readdirSync(dir);
|
|
7696
7693
|
} catch (err) {
|
|
7697
7694
|
return [];
|
|
7698
7695
|
}
|
|
7699
7696
|
}
|
|
7700
7697
|
function getFirst(dir, filter) {
|
|
7701
7698
|
var files = readdirSync(dir).filter(filter);
|
|
7702
|
-
return files[0] &&
|
|
7699
|
+
return files[0] && path2.join(dir, files[0]);
|
|
7703
7700
|
}
|
|
7704
7701
|
function matchBuild(name) {
|
|
7705
7702
|
return /\.node$/.test(name);
|
|
@@ -7786,7 +7783,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
7786
7783
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
7787
7784
|
}
|
|
7788
7785
|
function isAlpine(platform2) {
|
|
7789
|
-
return platform2 === "linux" &&
|
|
7786
|
+
return platform2 === "linux" && fs2.existsSync("/etc/alpine-release");
|
|
7790
7787
|
}
|
|
7791
7788
|
load.parseTags = parseTags;
|
|
7792
7789
|
load.matchTags = matchTags;
|
|
@@ -11505,8 +11502,151 @@ var _systemSchema = defineSchema({
|
|
|
11505
11502
|
})
|
|
11506
11503
|
});
|
|
11507
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
|
+
|
|
11508
11649
|
// src/subscription.ts
|
|
11509
|
-
import { execFileSync } from "node:child_process";
|
|
11510
11650
|
function deriveConvexUrl(apiUrl) {
|
|
11511
11651
|
return apiUrl.replace(/\.convex\.site\/?$/, ".convex.cloud");
|
|
11512
11652
|
}
|
|
@@ -11604,7 +11744,11 @@ function getHotState() {
|
|
|
11604
11744
|
lastKnownRoster: [],
|
|
11605
11745
|
intervals: { heartbeat: null, activityFlush: null },
|
|
11606
11746
|
activityBuffer: [],
|
|
11607
|
-
channelAgentBridge: {}
|
|
11747
|
+
channelAgentBridge: {},
|
|
11748
|
+
gatewayWs: null,
|
|
11749
|
+
gatewayPort: null,
|
|
11750
|
+
gatewayToken: null,
|
|
11751
|
+
commandSubscription: null
|
|
11608
11752
|
};
|
|
11609
11753
|
globalThis[HOT_KEY] = state;
|
|
11610
11754
|
}
|
|
@@ -11735,7 +11879,7 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11735
11879
|
const c = getOrCreateClient();
|
|
11736
11880
|
if (!c) {
|
|
11737
11881
|
logger.warn("cohort-sync: no ConvexClient \u2014 command subscription skipped");
|
|
11738
|
-
return;
|
|
11882
|
+
return null;
|
|
11739
11883
|
}
|
|
11740
11884
|
let processing = false;
|
|
11741
11885
|
const unsubscribe = c.onUpdate(
|
|
@@ -11759,19 +11903,94 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11759
11903
|
process.kill(process.pid, "SIGTERM");
|
|
11760
11904
|
return;
|
|
11761
11905
|
}
|
|
11762
|
-
if (cmd.type.startsWith("cron")
|
|
11763
|
-
|
|
11764
|
-
|
|
11765
|
-
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
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)}`);
|
|
11769
11992
|
}
|
|
11770
|
-
} catch (snapErr) {
|
|
11771
|
-
logger.warn(`cohort-sync: post-command snapshot push failed: ${String(snapErr)}`);
|
|
11772
11993
|
}
|
|
11773
|
-
} else if (cmd.type.startsWith("cron")) {
|
|
11774
|
-
logger.warn(`cohort-sync: cron command missing jobId: ${cmd.type}`);
|
|
11775
11994
|
} else {
|
|
11776
11995
|
logger.warn(`cohort-sync: unknown command type: ${cmd.type}`);
|
|
11777
11996
|
}
|
|
@@ -11787,34 +12006,8 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11787
12006
|
logger.error(`cohort-sync: command subscription error: ${String(err)}`);
|
|
11788
12007
|
}
|
|
11789
12008
|
);
|
|
11790
|
-
unsubscribers.push(unsubscribe);
|
|
11791
|
-
getHotState().unsubscribers = [...unsubscribers];
|
|
11792
12009
|
logger.info("cohort-sync: command subscription active");
|
|
11793
|
-
|
|
11794
|
-
function handleCronCommand(type, jobId, logger) {
|
|
11795
|
-
const subcommandMap = {
|
|
11796
|
-
cronEnable: ["cron", "enable", jobId],
|
|
11797
|
-
cronDisable: ["cron", "disable", jobId],
|
|
11798
|
-
cronRunNow: ["cron", "run", jobId],
|
|
11799
|
-
cronDelete: ["cron", "delete", jobId, "--force"]
|
|
11800
|
-
};
|
|
11801
|
-
const args = subcommandMap[type];
|
|
11802
|
-
if (!args) {
|
|
11803
|
-
logger.warn(`cohort-sync: unknown cron command type: ${type}`);
|
|
11804
|
-
return;
|
|
11805
|
-
}
|
|
11806
|
-
const timeout = type === "cronRunNow" ? 3e4 : 15e3;
|
|
11807
|
-
try {
|
|
11808
|
-
execFileSync("openclaw", args, {
|
|
11809
|
-
encoding: "utf8",
|
|
11810
|
-
timeout,
|
|
11811
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
11812
|
-
});
|
|
11813
|
-
logger.info(`cohort-sync: cron command ${type} executed for job ${jobId}`);
|
|
11814
|
-
} catch (err) {
|
|
11815
|
-
logger.error(`cohort-sync: cron command ${type} failed for job ${jobId}: ${String(err)}`);
|
|
11816
|
-
throw err;
|
|
11817
|
-
}
|
|
12010
|
+
return unsubscribe;
|
|
11818
12011
|
}
|
|
11819
12012
|
async function callAddCommentFromPlugin(apiKey, args) {
|
|
11820
12013
|
const c = getOrCreateClient();
|
|
@@ -11844,6 +12037,13 @@ function closeSubscription() {
|
|
|
11844
12037
|
} catch {
|
|
11845
12038
|
}
|
|
11846
12039
|
}
|
|
12040
|
+
if (state.commandSubscription) {
|
|
12041
|
+
try {
|
|
12042
|
+
state.commandSubscription();
|
|
12043
|
+
} catch {
|
|
12044
|
+
}
|
|
12045
|
+
state.commandSubscription = null;
|
|
12046
|
+
}
|
|
11847
12047
|
client?.close();
|
|
11848
12048
|
client = null;
|
|
11849
12049
|
clearHotState();
|
|
@@ -11917,10 +12117,6 @@ function getChannelAgent(channelId) {
|
|
|
11917
12117
|
}
|
|
11918
12118
|
|
|
11919
12119
|
// src/sync.ts
|
|
11920
|
-
var cronStorePath = null;
|
|
11921
|
-
function setCronStorePath(p) {
|
|
11922
|
-
cronStorePath = p;
|
|
11923
|
-
}
|
|
11924
12120
|
function extractJson(raw) {
|
|
11925
12121
|
const jsonStart = raw.search(/[\[{]/);
|
|
11926
12122
|
const jsonEndBracket = raw.lastIndexOf("]");
|
|
@@ -11952,73 +12148,35 @@ function fetchSkills(logger) {
|
|
|
11952
12148
|
return [];
|
|
11953
12149
|
}
|
|
11954
12150
|
}
|
|
11955
|
-
function fetchCronJobs(logger) {
|
|
11956
|
-
try {
|
|
11957
|
-
const storePath = cronStorePath ?? path.join(os.homedir(), ".openclaw", "cron", "jobs.json");
|
|
11958
|
-
if (!fs.existsSync(storePath)) {
|
|
11959
|
-
logger.warn(`cohort-sync: cron store not found at ${storePath}`);
|
|
11960
|
-
return null;
|
|
11961
|
-
}
|
|
11962
|
-
const raw = fs.readFileSync(storePath, "utf-8");
|
|
11963
|
-
const parsed = JSON.parse(raw);
|
|
11964
|
-
const jobs = Array.isArray(parsed?.jobs) ? parsed.jobs : [];
|
|
11965
|
-
return jobs.map((j) => ({
|
|
11966
|
-
id: String(j.id ?? "unknown"),
|
|
11967
|
-
text: String(j.name ?? ""),
|
|
11968
|
-
schedule: formatSchedule(j.schedule),
|
|
11969
|
-
...j.state?.nextRunAtMs != null ? { nextRun: Number(j.state.nextRunAtMs) } : {},
|
|
11970
|
-
...j.state?.lastRunAtMs != null ? { lastRun: Number(j.state.lastRunAtMs) } : {},
|
|
11971
|
-
...j.state?.lastStatus ? { lastStatus: String(j.state.lastStatus) } : {},
|
|
11972
|
-
enabled: j.enabled !== false,
|
|
11973
|
-
...j.agentId != null ? { agentId: String(j.agentId) } : {}
|
|
11974
|
-
}));
|
|
11975
|
-
} catch (err) {
|
|
11976
|
-
logger.warn(`cohort-sync: failed to read cron store: ${String(err)}`);
|
|
11977
|
-
return null;
|
|
11978
|
-
}
|
|
11979
|
-
}
|
|
11980
|
-
function formatSchedule(schedule) {
|
|
11981
|
-
if (typeof schedule === "string") return schedule;
|
|
11982
|
-
if (schedule && typeof schedule === "object") {
|
|
11983
|
-
const s = schedule;
|
|
11984
|
-
if (s.kind === "cron" && typeof s.expr === "string") return s.expr;
|
|
11985
|
-
if (s.kind === "interval" && typeof s.everyMs === "number") {
|
|
11986
|
-
const mins = Math.floor(Number(s.everyMs) / 6e4);
|
|
11987
|
-
if (mins > 0) return `*/${mins} * * * *`;
|
|
11988
|
-
return `every ${s.everyMs}ms`;
|
|
11989
|
-
}
|
|
11990
|
-
}
|
|
11991
|
-
return String(schedule);
|
|
11992
|
-
}
|
|
11993
12151
|
var VALID_STATUSES = /* @__PURE__ */ new Set(["idle", "working", "waiting"]);
|
|
11994
12152
|
function normalizeStatus(status) {
|
|
11995
12153
|
return VALID_STATUSES.has(status) ? status : "idle";
|
|
11996
12154
|
}
|
|
11997
|
-
async function v1Get(apiUrl, apiKey,
|
|
11998
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12155
|
+
async function v1Get(apiUrl, apiKey, path2) {
|
|
12156
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path2}`, {
|
|
11999
12157
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
12000
12158
|
signal: AbortSignal.timeout(1e4)
|
|
12001
12159
|
});
|
|
12002
|
-
if (!res.ok) throw new Error(`GET ${
|
|
12160
|
+
if (!res.ok) throw new Error(`GET ${path2} \u2192 ${res.status}`);
|
|
12003
12161
|
return res.json();
|
|
12004
12162
|
}
|
|
12005
|
-
async function v1Patch(apiUrl, apiKey,
|
|
12006
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12163
|
+
async function v1Patch(apiUrl, apiKey, path2, body) {
|
|
12164
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path2}`, {
|
|
12007
12165
|
method: "PATCH",
|
|
12008
12166
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
12009
12167
|
body: JSON.stringify(body),
|
|
12010
12168
|
signal: AbortSignal.timeout(1e4)
|
|
12011
12169
|
});
|
|
12012
|
-
if (!res.ok) throw new Error(`PATCH ${
|
|
12170
|
+
if (!res.ok) throw new Error(`PATCH ${path2} \u2192 ${res.status}`);
|
|
12013
12171
|
}
|
|
12014
|
-
async function v1Post(apiUrl, apiKey,
|
|
12015
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12172
|
+
async function v1Post(apiUrl, apiKey, path2, body) {
|
|
12173
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path2}`, {
|
|
12016
12174
|
method: "POST",
|
|
12017
12175
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
12018
12176
|
body: JSON.stringify(body),
|
|
12019
12177
|
signal: AbortSignal.timeout(1e4)
|
|
12020
12178
|
});
|
|
12021
|
-
if (!res.ok) throw new Error(`POST ${
|
|
12179
|
+
if (!res.ok) throw new Error(`POST ${path2} \u2192 ${res.status}`);
|
|
12022
12180
|
}
|
|
12023
12181
|
async function checkForUpdate(currentVersion, logger) {
|
|
12024
12182
|
try {
|
|
@@ -12186,6 +12344,65 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
12186
12344
|
logger.info("cohort-sync: full sync complete");
|
|
12187
12345
|
}
|
|
12188
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
|
+
|
|
12189
12406
|
// src/agent-state.ts
|
|
12190
12407
|
import { basename } from "node:path";
|
|
12191
12408
|
|
|
@@ -12695,7 +12912,7 @@ function diag(label, data) {
|
|
|
12695
12912
|
const line = `[${ts}] ${label}${payload}
|
|
12696
12913
|
`;
|
|
12697
12914
|
try {
|
|
12698
|
-
|
|
12915
|
+
fs.appendFileSync(DIAG_LOG_PATH, line);
|
|
12699
12916
|
} catch {
|
|
12700
12917
|
}
|
|
12701
12918
|
}
|
|
@@ -12723,16 +12940,16 @@ function dumpEvent(event) {
|
|
|
12723
12940
|
}
|
|
12724
12941
|
var PLUGIN_VERSION = "unknown";
|
|
12725
12942
|
try {
|
|
12726
|
-
const pkgPath =
|
|
12727
|
-
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"));
|
|
12728
12945
|
PLUGIN_VERSION = pkgJson.version ?? "unknown";
|
|
12729
12946
|
} catch {
|
|
12730
12947
|
}
|
|
12731
12948
|
diag("MODULE_LOADED", { BUILD_ID, PLUGIN_VERSION });
|
|
12732
12949
|
function parseIdentityFile(workspaceDir) {
|
|
12733
12950
|
try {
|
|
12734
|
-
const filePath =
|
|
12735
|
-
const content =
|
|
12951
|
+
const filePath = path.join(workspaceDir, "IDENTITY.md");
|
|
12952
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
12736
12953
|
const identity = {};
|
|
12737
12954
|
for (const line of content.split(/\r?\n/)) {
|
|
12738
12955
|
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
@@ -12770,8 +12987,8 @@ function getOrCreateTracker() {
|
|
|
12770
12987
|
state.tracker = fresh;
|
|
12771
12988
|
return fresh;
|
|
12772
12989
|
}
|
|
12773
|
-
var STATE_FILE_PATH =
|
|
12774
|
-
|
|
12990
|
+
var STATE_FILE_PATH = path.join(
|
|
12991
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
12775
12992
|
".session-state.json"
|
|
12776
12993
|
);
|
|
12777
12994
|
function saveSessionsToDisk(tracker) {
|
|
@@ -12788,14 +13005,14 @@ function saveSessionsToDisk(tracker) {
|
|
|
12788
13005
|
data.sessions.push({ agentName: name, key });
|
|
12789
13006
|
}
|
|
12790
13007
|
}
|
|
12791
|
-
|
|
13008
|
+
fs.writeFileSync(STATE_FILE_PATH, JSON.stringify(data));
|
|
12792
13009
|
} catch {
|
|
12793
13010
|
}
|
|
12794
13011
|
}
|
|
12795
13012
|
function loadSessionsFromDisk(tracker, logger) {
|
|
12796
13013
|
try {
|
|
12797
|
-
if (!
|
|
12798
|
-
const data = JSON.parse(
|
|
13014
|
+
if (!fs.existsSync(STATE_FILE_PATH)) return;
|
|
13015
|
+
const data = JSON.parse(fs.readFileSync(STATE_FILE_PATH, "utf8"));
|
|
12799
13016
|
if (Date.now() - new Date(data.savedAt).getTime() > 864e5) {
|
|
12800
13017
|
logger.info("cohort-sync: disk session state too old (>24h), skipping");
|
|
12801
13018
|
return;
|
|
@@ -12842,6 +13059,7 @@ function registerHooks(api, cfg) {
|
|
|
12842
13059
|
const tracker = getOrCreateTracker();
|
|
12843
13060
|
let heartbeatInterval = null;
|
|
12844
13061
|
let activityFlushInterval = null;
|
|
13062
|
+
registerCronGatewayMethods(api);
|
|
12845
13063
|
logger.info(`cohort-sync: registerHooks [${BUILD_ID}]`);
|
|
12846
13064
|
diag("REGISTER_HOOKS", {
|
|
12847
13065
|
BUILD_ID,
|
|
@@ -12853,18 +13071,6 @@ function registerHooks(api, cfg) {
|
|
|
12853
13071
|
agentIds: (config?.agents?.list ?? []).map((a) => a.id),
|
|
12854
13072
|
agentMessageProviders: (config?.agents?.list ?? []).map((a) => ({ id: a.id, mp: a.messageProvider }))
|
|
12855
13073
|
});
|
|
12856
|
-
const resolvedCronPath = typeof api.resolvePath === "function" ? api.resolvePath("cron/jobs.json") : null;
|
|
12857
|
-
const runtimeKeys = api.runtime ? Object.keys(api.runtime) : [];
|
|
12858
|
-
const runtimeMethods = api.runtime ? Object.keys(api.runtime).filter((k) => typeof api.runtime[k] === "function") : [];
|
|
12859
|
-
diag("CONFIG_DUMP", {
|
|
12860
|
-
resolvedCronPath,
|
|
12861
|
-
cronPathExists: resolvedCronPath ? fs2.existsSync(resolvedCronPath) : false,
|
|
12862
|
-
runtimeKeys,
|
|
12863
|
-
runtimeMethods
|
|
12864
|
-
});
|
|
12865
|
-
if (resolvedCronPath && typeof resolvedCronPath === "string") {
|
|
12866
|
-
setCronStorePath(resolvedCronPath);
|
|
12867
|
-
}
|
|
12868
13074
|
setConvexUrl(cfg);
|
|
12869
13075
|
setLogger(logger);
|
|
12870
13076
|
restoreFromHotReload(logger);
|
|
@@ -12985,6 +13191,15 @@ function registerHooks(api, cfg) {
|
|
|
12985
13191
|
}, 3e3);
|
|
12986
13192
|
saveIntervalsToHot(heartbeatInterval, activityFlushInterval);
|
|
12987
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
|
+
}
|
|
12988
13203
|
api.registerTool((toolCtx) => {
|
|
12989
13204
|
const agentId = toolCtx.agentId ?? "main";
|
|
12990
13205
|
const agentName = resolveAgentName(agentId);
|
|
@@ -13134,17 +13349,17 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13134
13349
|
}
|
|
13135
13350
|
}
|
|
13136
13351
|
saveSessionsToDisk(tracker);
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
const
|
|
13141
|
-
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
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)}`);
|
|
13145
13360
|
}
|
|
13146
|
-
}
|
|
13147
|
-
logger.warn(
|
|
13361
|
+
} else {
|
|
13362
|
+
logger.warn("cohort-sync: gateway WS not connected \u2014 skipping cron snapshot");
|
|
13148
13363
|
}
|
|
13149
13364
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
13150
13365
|
}
|
|
@@ -13183,6 +13398,23 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13183
13398
|
}
|
|
13184
13399
|
}
|
|
13185
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
|
+
}
|
|
13186
13418
|
await initSubscription(
|
|
13187
13419
|
event.port,
|
|
13188
13420
|
cfg,
|
|
@@ -13191,7 +13423,6 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13191
13423
|
).catch((err) => {
|
|
13192
13424
|
logger.error(`cohort-sync: subscription init failed: ${String(err)}`);
|
|
13193
13425
|
});
|
|
13194
|
-
initCommandSubscription(cfg, logger, resolveAgentName);
|
|
13195
13426
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
13196
13427
|
for (const agentId of allAgentIds) {
|
|
13197
13428
|
const agentName = resolveAgentName(agentId);
|
package/dist/package.json
CHANGED