@cfio/cohort-sync 0.4.8 → 0.6.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 +833 -152
- 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 fs3 from "node:fs";
|
|
92
|
+
import path3 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 path4 = pathParts.slice(0, -1).join("/");
|
|
4562
4558
|
const exportName = pathParts[pathParts.length - 1];
|
|
4563
4559
|
if (exportName === "default") {
|
|
4564
|
-
return
|
|
4560
|
+
return path4;
|
|
4565
4561
|
} else {
|
|
4566
|
-
return
|
|
4562
|
+
return path4 + ":" + exportName;
|
|
4567
4563
|
}
|
|
4568
4564
|
} else if (prop === Symbol.toStringTag) {
|
|
4569
4565
|
return "FunctionReference";
|
|
@@ -7632,8 +7628,8 @@ 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
|
|
7631
|
+
var fs4 = __require("fs");
|
|
7632
|
+
var path4 = __require("path");
|
|
7637
7633
|
var os3 = __require("os");
|
|
7638
7634
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
7639
7635
|
var vars = process.config && process.config.variables || {};
|
|
@@ -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 = path4.resolve(dir || ".");
|
|
7654
7650
|
try {
|
|
7655
|
-
var name = runtimeRequire(
|
|
7651
|
+
var name = runtimeRequire(path4.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(path4.join(dir, "build/Release"), matchBuild);
|
|
7661
7657
|
if (release) return release;
|
|
7662
|
-
var debug = getFirst(
|
|
7658
|
+
var debug = getFirst(path4.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(path4.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(path4.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 = path4.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 path4.join(prebuilds, winner.file);
|
|
7692
7688
|
}
|
|
7693
7689
|
};
|
|
7694
7690
|
function readdirSync(dir) {
|
|
7695
7691
|
try {
|
|
7696
|
-
return
|
|
7692
|
+
return fs4.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] && path4.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" && fs4.existsSync("/etc/alpine-release");
|
|
7791
7787
|
}
|
|
7792
7788
|
load.parseTags = parseTags;
|
|
7793
7789
|
load.matchTags = matchTags;
|
|
@@ -11506,8 +11502,47 @@ var _systemSchema = defineSchema({
|
|
|
11506
11502
|
})
|
|
11507
11503
|
});
|
|
11508
11504
|
|
|
11505
|
+
// src/cron-mapping.ts
|
|
11506
|
+
function formatSchedule(s) {
|
|
11507
|
+
switch (s.kind) {
|
|
11508
|
+
case "cron":
|
|
11509
|
+
return s.expr ?? "";
|
|
11510
|
+
case "every":
|
|
11511
|
+
return `every ${humanizeMs(s.everyMs ?? 0)}`;
|
|
11512
|
+
case "at":
|
|
11513
|
+
return `once at ${s.at ?? ""}`;
|
|
11514
|
+
default:
|
|
11515
|
+
return s.kind;
|
|
11516
|
+
}
|
|
11517
|
+
}
|
|
11518
|
+
function humanizeMs(ms) {
|
|
11519
|
+
if (ms >= 864e5) return `${Math.round(ms / 864e5)}d`;
|
|
11520
|
+
if (ms >= 36e5) return `${Math.round(ms / 36e5)}h`;
|
|
11521
|
+
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
11522
|
+
return `${Math.round(ms / 1e3)}s`;
|
|
11523
|
+
}
|
|
11524
|
+
function mapCronJob(job, resolveAgentName) {
|
|
11525
|
+
return {
|
|
11526
|
+
id: job.id,
|
|
11527
|
+
text: job.name,
|
|
11528
|
+
schedule: formatSchedule(job.schedule),
|
|
11529
|
+
enabled: job.enabled,
|
|
11530
|
+
agentId: resolveAgentName(job.agentId ?? "main"),
|
|
11531
|
+
nextRun: job.state?.nextRunAtMs,
|
|
11532
|
+
lastRun: job.state?.lastRunAtMs,
|
|
11533
|
+
lastStatus: job.state?.lastRunStatus,
|
|
11534
|
+
lastError: job.state?.lastError ?? null
|
|
11535
|
+
};
|
|
11536
|
+
}
|
|
11537
|
+
function reverseResolveAgentName(cohortName, forwardMap) {
|
|
11538
|
+
const lower = cohortName.toLowerCase();
|
|
11539
|
+
for (const [openclawId, name] of Object.entries(forwardMap)) {
|
|
11540
|
+
if (name.toLowerCase() === lower) return openclawId;
|
|
11541
|
+
}
|
|
11542
|
+
return cohortName;
|
|
11543
|
+
}
|
|
11544
|
+
|
|
11509
11545
|
// src/subscription.ts
|
|
11510
|
-
import { execFileSync } from "node:child_process";
|
|
11511
11546
|
function deriveConvexUrl(apiUrl) {
|
|
11512
11547
|
return apiUrl.replace(/\.convex\.site\/?$/, ".convex.cloud");
|
|
11513
11548
|
}
|
|
@@ -11605,7 +11640,11 @@ function getHotState() {
|
|
|
11605
11640
|
lastKnownRoster: [],
|
|
11606
11641
|
intervals: { heartbeat: null, activityFlush: null },
|
|
11607
11642
|
activityBuffer: [],
|
|
11608
|
-
channelAgentBridge: {}
|
|
11643
|
+
channelAgentBridge: {},
|
|
11644
|
+
gatewayPort: null,
|
|
11645
|
+
gatewayToken: null,
|
|
11646
|
+
gatewayProtocolClient: null,
|
|
11647
|
+
commandSubscription: null
|
|
11609
11648
|
};
|
|
11610
11649
|
globalThis[HOT_KEY] = state;
|
|
11611
11650
|
}
|
|
@@ -11736,7 +11775,7 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11736
11775
|
const c = getOrCreateClient();
|
|
11737
11776
|
if (!c) {
|
|
11738
11777
|
logger.warn("cohort-sync: no ConvexClient \u2014 command subscription skipped");
|
|
11739
|
-
return;
|
|
11778
|
+
return null;
|
|
11740
11779
|
}
|
|
11741
11780
|
let processing = false;
|
|
11742
11781
|
const unsubscribe = c.onUpdate(
|
|
@@ -11760,19 +11799,113 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11760
11799
|
process.kill(process.pid, "SIGTERM");
|
|
11761
11800
|
return;
|
|
11762
11801
|
}
|
|
11763
|
-
if (cmd.type.startsWith("cron")
|
|
11764
|
-
|
|
11802
|
+
if (cmd.type.startsWith("cron")) {
|
|
11803
|
+
const hotState = getHotState();
|
|
11804
|
+
const port = hotState.gatewayPort;
|
|
11805
|
+
const token = hotState.gatewayToken;
|
|
11806
|
+
if (!port || !token) {
|
|
11807
|
+
logger.warn(`cohort-sync: no gateway port/token, cannot execute ${cmd.type}`);
|
|
11808
|
+
continue;
|
|
11809
|
+
}
|
|
11810
|
+
const gwClient = new GatewayClient(port, token, logger);
|
|
11765
11811
|
try {
|
|
11766
|
-
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
|
|
11812
|
+
await gwClient.connect();
|
|
11813
|
+
const nameMap = cfg.agentNameMap ?? {};
|
|
11814
|
+
switch (cmd.type) {
|
|
11815
|
+
case "cronEnable":
|
|
11816
|
+
await gwClient.request("cron.update", {
|
|
11817
|
+
jobId: cmd.payload?.jobId,
|
|
11818
|
+
patch: { enabled: true }
|
|
11819
|
+
});
|
|
11820
|
+
break;
|
|
11821
|
+
case "cronDisable":
|
|
11822
|
+
await gwClient.request("cron.update", {
|
|
11823
|
+
jobId: cmd.payload?.jobId,
|
|
11824
|
+
patch: { enabled: false }
|
|
11825
|
+
});
|
|
11826
|
+
break;
|
|
11827
|
+
case "cronDelete":
|
|
11828
|
+
await gwClient.request("cron.remove", {
|
|
11829
|
+
jobId: cmd.payload?.jobId
|
|
11830
|
+
});
|
|
11831
|
+
break;
|
|
11832
|
+
case "cronRunNow": {
|
|
11833
|
+
const runResult = await gwClient.request(
|
|
11834
|
+
"cron.run",
|
|
11835
|
+
{ jobId: cmd.payload?.jobId }
|
|
11836
|
+
);
|
|
11837
|
+
if (runResult?.ok && runResult?.ran) {
|
|
11838
|
+
const jobId = cmd.payload?.jobId;
|
|
11839
|
+
let polls = 0;
|
|
11840
|
+
const pollInterval = setInterval(async () => {
|
|
11841
|
+
polls++;
|
|
11842
|
+
if (polls >= 15) {
|
|
11843
|
+
clearInterval(pollInterval);
|
|
11844
|
+
return;
|
|
11845
|
+
}
|
|
11846
|
+
try {
|
|
11847
|
+
const pollClient = getHotState().gatewayProtocolClient;
|
|
11848
|
+
if (!pollClient || !pollClient.isAlive()) {
|
|
11849
|
+
clearInterval(pollInterval);
|
|
11850
|
+
return;
|
|
11851
|
+
}
|
|
11852
|
+
const pollResult = await pollClient.request("cron.list");
|
|
11853
|
+
const freshJobs = Array.isArray(pollResult) ? pollResult : pollResult?.jobs ?? [];
|
|
11854
|
+
const job = freshJobs.find((j) => j.id === jobId);
|
|
11855
|
+
if (job && !job.state?.runningAtMs) {
|
|
11856
|
+
clearInterval(pollInterval);
|
|
11857
|
+
const mapped = freshJobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
11858
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
11859
|
+
}
|
|
11860
|
+
} catch {
|
|
11861
|
+
}
|
|
11862
|
+
}, 2e3);
|
|
11863
|
+
}
|
|
11864
|
+
break;
|
|
11865
|
+
}
|
|
11866
|
+
case "cronCreate": {
|
|
11867
|
+
const agentId = reverseResolveAgentName(cmd.payload?.agentId ?? "main", nameMap);
|
|
11868
|
+
await gwClient.request("cron.add", {
|
|
11869
|
+
job: {
|
|
11870
|
+
agentId,
|
|
11871
|
+
name: cmd.payload?.name,
|
|
11872
|
+
enabled: true,
|
|
11873
|
+
schedule: cmd.payload?.schedule,
|
|
11874
|
+
payload: { kind: "agentTurn", message: cmd.payload?.message },
|
|
11875
|
+
sessionTarget: "isolated",
|
|
11876
|
+
wakeMode: "now"
|
|
11877
|
+
}
|
|
11878
|
+
});
|
|
11879
|
+
break;
|
|
11880
|
+
}
|
|
11881
|
+
case "cronUpdate": {
|
|
11882
|
+
const patch = {};
|
|
11883
|
+
if (cmd.payload?.name) patch.name = cmd.payload.name;
|
|
11884
|
+
if (cmd.payload?.schedule) patch.schedule = cmd.payload.schedule;
|
|
11885
|
+
if (cmd.payload?.message) patch.payload = { kind: "agentTurn", message: cmd.payload.message };
|
|
11886
|
+
if (cmd.payload?.agentId) patch.agentId = reverseResolveAgentName(cmd.payload.agentId, nameMap);
|
|
11887
|
+
await gwClient.request("cron.update", {
|
|
11888
|
+
jobId: cmd.payload?.jobId,
|
|
11889
|
+
patch
|
|
11890
|
+
});
|
|
11891
|
+
break;
|
|
11892
|
+
}
|
|
11893
|
+
default:
|
|
11894
|
+
logger.warn(`cohort-sync: unknown cron command type: ${cmd.type}`);
|
|
11895
|
+
}
|
|
11896
|
+
if (gwClient.isAlive()) {
|
|
11897
|
+
try {
|
|
11898
|
+
const snapResult = await gwClient.request("cron.list");
|
|
11899
|
+
const freshJobs = Array.isArray(snapResult) ? snapResult : snapResult?.jobs ?? [];
|
|
11900
|
+
const mapped = freshJobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
11901
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
11902
|
+
} catch (snapErr) {
|
|
11903
|
+
logger.warn(`cohort-sync: post-command snapshot push failed: ${String(snapErr)}`);
|
|
11904
|
+
}
|
|
11770
11905
|
}
|
|
11771
|
-
}
|
|
11772
|
-
|
|
11906
|
+
} finally {
|
|
11907
|
+
gwClient.close();
|
|
11773
11908
|
}
|
|
11774
|
-
} else if (cmd.type.startsWith("cron")) {
|
|
11775
|
-
logger.warn(`cohort-sync: cron command missing jobId: ${cmd.type}`);
|
|
11776
11909
|
} else {
|
|
11777
11910
|
logger.warn(`cohort-sync: unknown command type: ${cmd.type}`);
|
|
11778
11911
|
}
|
|
@@ -11788,34 +11921,8 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11788
11921
|
logger.error(`cohort-sync: command subscription error: ${String(err)}`);
|
|
11789
11922
|
}
|
|
11790
11923
|
);
|
|
11791
|
-
unsubscribers.push(unsubscribe);
|
|
11792
|
-
getHotState().unsubscribers = [...unsubscribers];
|
|
11793
11924
|
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
|
-
}
|
|
11925
|
+
return unsubscribe;
|
|
11819
11926
|
}
|
|
11820
11927
|
async function callAddCommentFromPlugin(apiKey, args) {
|
|
11821
11928
|
const c = getOrCreateClient();
|
|
@@ -11845,6 +11952,20 @@ function closeSubscription() {
|
|
|
11845
11952
|
} catch {
|
|
11846
11953
|
}
|
|
11847
11954
|
}
|
|
11955
|
+
if (state.commandSubscription) {
|
|
11956
|
+
try {
|
|
11957
|
+
state.commandSubscription();
|
|
11958
|
+
} catch {
|
|
11959
|
+
}
|
|
11960
|
+
state.commandSubscription = null;
|
|
11961
|
+
}
|
|
11962
|
+
if (state.gatewayProtocolClient) {
|
|
11963
|
+
try {
|
|
11964
|
+
state.gatewayProtocolClient.close();
|
|
11965
|
+
} catch {
|
|
11966
|
+
}
|
|
11967
|
+
state.gatewayProtocolClient = null;
|
|
11968
|
+
}
|
|
11848
11969
|
client?.close();
|
|
11849
11970
|
client = null;
|
|
11850
11971
|
clearHotState();
|
|
@@ -11918,10 +12039,6 @@ function getChannelAgent(channelId) {
|
|
|
11918
12039
|
}
|
|
11919
12040
|
|
|
11920
12041
|
// src/sync.ts
|
|
11921
|
-
var cronStorePath = null;
|
|
11922
|
-
function setCronStorePath(p) {
|
|
11923
|
-
cronStorePath = p;
|
|
11924
|
-
}
|
|
11925
12042
|
function extractJson(raw) {
|
|
11926
12043
|
const jsonStart = raw.search(/[\[{]/);
|
|
11927
12044
|
const jsonEndBracket = raw.lastIndexOf("]");
|
|
@@ -11953,73 +12070,35 @@ function fetchSkills(logger) {
|
|
|
11953
12070
|
return [];
|
|
11954
12071
|
}
|
|
11955
12072
|
}
|
|
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
12073
|
var VALID_STATUSES = /* @__PURE__ */ new Set(["idle", "working", "waiting"]);
|
|
11995
12074
|
function normalizeStatus(status) {
|
|
11996
12075
|
return VALID_STATUSES.has(status) ? status : "idle";
|
|
11997
12076
|
}
|
|
11998
|
-
async function v1Get(apiUrl, apiKey,
|
|
11999
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12077
|
+
async function v1Get(apiUrl, apiKey, path4) {
|
|
12078
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path4}`, {
|
|
12000
12079
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
12001
12080
|
signal: AbortSignal.timeout(1e4)
|
|
12002
12081
|
});
|
|
12003
|
-
if (!res.ok) throw new Error(`GET ${
|
|
12082
|
+
if (!res.ok) throw new Error(`GET ${path4} \u2192 ${res.status}`);
|
|
12004
12083
|
return res.json();
|
|
12005
12084
|
}
|
|
12006
|
-
async function v1Patch(apiUrl, apiKey,
|
|
12007
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12085
|
+
async function v1Patch(apiUrl, apiKey, path4, body) {
|
|
12086
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path4}`, {
|
|
12008
12087
|
method: "PATCH",
|
|
12009
12088
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
12010
12089
|
body: JSON.stringify(body),
|
|
12011
12090
|
signal: AbortSignal.timeout(1e4)
|
|
12012
12091
|
});
|
|
12013
|
-
if (!res.ok) throw new Error(`PATCH ${
|
|
12092
|
+
if (!res.ok) throw new Error(`PATCH ${path4} \u2192 ${res.status}`);
|
|
12014
12093
|
}
|
|
12015
|
-
async function v1Post(apiUrl, apiKey,
|
|
12016
|
-
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${
|
|
12094
|
+
async function v1Post(apiUrl, apiKey, path4, body) {
|
|
12095
|
+
const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path4}`, {
|
|
12017
12096
|
method: "POST",
|
|
12018
12097
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
12019
12098
|
body: JSON.stringify(body),
|
|
12020
12099
|
signal: AbortSignal.timeout(1e4)
|
|
12021
12100
|
});
|
|
12022
|
-
if (!res.ok) throw new Error(`POST ${
|
|
12101
|
+
if (!res.ok) throw new Error(`POST ${path4} \u2192 ${res.status}`);
|
|
12023
12102
|
}
|
|
12024
12103
|
async function checkForUpdate(currentVersion, logger) {
|
|
12025
12104
|
try {
|
|
@@ -12187,6 +12266,543 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
12187
12266
|
logger.info("cohort-sync: full sync complete");
|
|
12188
12267
|
}
|
|
12189
12268
|
|
|
12269
|
+
// src/gateway-client.ts
|
|
12270
|
+
import crypto2 from "node:crypto";
|
|
12271
|
+
|
|
12272
|
+
// src/diag.ts
|
|
12273
|
+
import fs from "node:fs";
|
|
12274
|
+
import path from "node:path";
|
|
12275
|
+
import os from "node:os";
|
|
12276
|
+
var LOG_DIR = path.join(os.homedir(), ".openclaw", "logs", "cohort-sync");
|
|
12277
|
+
var LOG_PATH = path.join(LOG_DIR, "diag.log");
|
|
12278
|
+
var LOG_PATH_ROTATED = path.join(LOG_DIR, "diag.log.1");
|
|
12279
|
+
var MAX_LOG_SIZE = 5 * 1024 * 1024;
|
|
12280
|
+
var isDebug = process.env.COHORT_SYNC_DEBUG === "1";
|
|
12281
|
+
try {
|
|
12282
|
+
fs.mkdirSync(LOG_DIR, { recursive: true, mode: 448 });
|
|
12283
|
+
} catch {
|
|
12284
|
+
}
|
|
12285
|
+
var writesSinceCheck = 0;
|
|
12286
|
+
function diag(label, data) {
|
|
12287
|
+
if (!isDebug && !label.startsWith("HOOK_") && !label.startsWith("MODULE_") && !label.startsWith("REGISTER_") && !label.startsWith("GW_WS_AUTH") && !label.startsWith("GW_WS_CLOSED") && !label.startsWith("GW_WS_ERROR") && !label.startsWith("GW_CLIENT_") && !label.startsWith("HEARTBEAT_CRON")) {
|
|
12288
|
+
return;
|
|
12289
|
+
}
|
|
12290
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
12291
|
+
const sanitized = data ? " " + JSON.stringify(data, (_k, v2) => {
|
|
12292
|
+
if (typeof v2 === "string" && v2.length > 200) return v2.slice(0, 200) + "\u2026";
|
|
12293
|
+
return v2;
|
|
12294
|
+
}) : "";
|
|
12295
|
+
const line = `[${ts}] ${label}${sanitized}
|
|
12296
|
+
`;
|
|
12297
|
+
try {
|
|
12298
|
+
fs.appendFileSync(LOG_PATH, line, { mode: 384 });
|
|
12299
|
+
if (++writesSinceCheck >= 100) {
|
|
12300
|
+
writesSinceCheck = 0;
|
|
12301
|
+
const stat = fs.statSync(LOG_PATH);
|
|
12302
|
+
if (stat.size > MAX_LOG_SIZE) {
|
|
12303
|
+
try {
|
|
12304
|
+
fs.unlinkSync(LOG_PATH_ROTATED);
|
|
12305
|
+
} catch {
|
|
12306
|
+
}
|
|
12307
|
+
fs.renameSync(LOG_PATH, LOG_PATH_ROTATED);
|
|
12308
|
+
}
|
|
12309
|
+
}
|
|
12310
|
+
} catch {
|
|
12311
|
+
}
|
|
12312
|
+
}
|
|
12313
|
+
|
|
12314
|
+
// src/device-identity-crypto.ts
|
|
12315
|
+
import crypto from "node:crypto";
|
|
12316
|
+
import fs2 from "node:fs";
|
|
12317
|
+
import path2 from "node:path";
|
|
12318
|
+
import os2 from "node:os";
|
|
12319
|
+
var IDENTITY_PATH = path2.join(os2.homedir(), ".openclaw", "extensions", "cohort-sync", ".device-identity.json");
|
|
12320
|
+
function loadOrCreateDeviceIdentity() {
|
|
12321
|
+
try {
|
|
12322
|
+
const data = JSON.parse(fs2.readFileSync(IDENTITY_PATH, "utf-8"));
|
|
12323
|
+
if (data.deviceId && data.publicKeyPem && data.privateKeyPem) {
|
|
12324
|
+
diag("GW_CLIENT_DEVICE_IDENTITY_LOADED", { deviceId: data.deviceId });
|
|
12325
|
+
return data;
|
|
12326
|
+
}
|
|
12327
|
+
} catch {
|
|
12328
|
+
}
|
|
12329
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
|
|
12330
|
+
const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
|
|
12331
|
+
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
|
|
12332
|
+
const publicKeyDer = publicKey.export({ type: "spki", format: "der" });
|
|
12333
|
+
const rawPublicKey = publicKeyDer.subarray(publicKeyDer.length - 32);
|
|
12334
|
+
const deviceId = crypto.createHash("sha256").update(rawPublicKey).digest("hex");
|
|
12335
|
+
const identity = { deviceId, publicKeyPem, privateKeyPem };
|
|
12336
|
+
try {
|
|
12337
|
+
fs2.writeFileSync(IDENTITY_PATH, JSON.stringify(identity, null, 2), { mode: 384 });
|
|
12338
|
+
diag("GW_CLIENT_DEVICE_IDENTITY_CREATED", { deviceId });
|
|
12339
|
+
} catch (err) {
|
|
12340
|
+
diag("GW_CLIENT_DEVICE_IDENTITY_WRITE_FAILED", { error: String(err) });
|
|
12341
|
+
}
|
|
12342
|
+
return identity;
|
|
12343
|
+
}
|
|
12344
|
+
function normalizeMetadata(value) {
|
|
12345
|
+
if (typeof value !== "string") return "";
|
|
12346
|
+
const trimmed = value.trim();
|
|
12347
|
+
if (!trimmed) return "";
|
|
12348
|
+
return trimmed.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32));
|
|
12349
|
+
}
|
|
12350
|
+
function buildDeviceAuthPayloadV3(params) {
|
|
12351
|
+
return [
|
|
12352
|
+
"v3",
|
|
12353
|
+
params.deviceId,
|
|
12354
|
+
params.clientId,
|
|
12355
|
+
params.clientMode,
|
|
12356
|
+
params.role,
|
|
12357
|
+
params.scopes.join(","),
|
|
12358
|
+
String(params.signedAtMs),
|
|
12359
|
+
params.token ?? "",
|
|
12360
|
+
params.nonce,
|
|
12361
|
+
normalizeMetadata(params.platform),
|
|
12362
|
+
normalizeMetadata(params.deviceFamily)
|
|
12363
|
+
].join("|");
|
|
12364
|
+
}
|
|
12365
|
+
function signPayload(privateKeyPem, payload) {
|
|
12366
|
+
const key = crypto.createPrivateKey(privateKeyPem);
|
|
12367
|
+
const signature = crypto.sign(null, Buffer.from(payload, "utf-8"), key);
|
|
12368
|
+
return signature.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
12369
|
+
}
|
|
12370
|
+
|
|
12371
|
+
// src/gateway-client.ts
|
|
12372
|
+
var ALLOWED_METHODS = /* @__PURE__ */ new Set([
|
|
12373
|
+
"cron.list",
|
|
12374
|
+
"cron.add",
|
|
12375
|
+
"cron.update",
|
|
12376
|
+
"cron.remove",
|
|
12377
|
+
"cron.run",
|
|
12378
|
+
"cron.runs",
|
|
12379
|
+
"cron.status",
|
|
12380
|
+
"channels.status",
|
|
12381
|
+
"sessions.list",
|
|
12382
|
+
"sessions.preview",
|
|
12383
|
+
"agent",
|
|
12384
|
+
"snapshot",
|
|
12385
|
+
"system.presence"
|
|
12386
|
+
]);
|
|
12387
|
+
function buildConnectFrame(id, token, pluginVersion, identity, nonce) {
|
|
12388
|
+
const signedAtMs = Date.now();
|
|
12389
|
+
const payload = buildDeviceAuthPayloadV3({
|
|
12390
|
+
deviceId: identity.deviceId,
|
|
12391
|
+
clientId: "gateway-client",
|
|
12392
|
+
clientMode: "backend",
|
|
12393
|
+
role: "operator",
|
|
12394
|
+
scopes: ["operator.read", "operator.write"],
|
|
12395
|
+
signedAtMs,
|
|
12396
|
+
token,
|
|
12397
|
+
nonce,
|
|
12398
|
+
platform: process.platform,
|
|
12399
|
+
deviceFamily: null
|
|
12400
|
+
});
|
|
12401
|
+
const signature = signPayload(identity.privateKeyPem, payload);
|
|
12402
|
+
return {
|
|
12403
|
+
type: "req",
|
|
12404
|
+
id,
|
|
12405
|
+
method: "connect",
|
|
12406
|
+
params: {
|
|
12407
|
+
minProtocol: 3,
|
|
12408
|
+
maxProtocol: 3,
|
|
12409
|
+
client: {
|
|
12410
|
+
id: "gateway-client",
|
|
12411
|
+
version: pluginVersion,
|
|
12412
|
+
platform: process.platform,
|
|
12413
|
+
mode: "backend"
|
|
12414
|
+
},
|
|
12415
|
+
role: "operator",
|
|
12416
|
+
scopes: ["operator.read", "operator.write"],
|
|
12417
|
+
auth: { token },
|
|
12418
|
+
device: {
|
|
12419
|
+
id: identity.deviceId,
|
|
12420
|
+
publicKey: identity.publicKeyPem,
|
|
12421
|
+
signature,
|
|
12422
|
+
signedAt: signedAtMs,
|
|
12423
|
+
nonce
|
|
12424
|
+
},
|
|
12425
|
+
locale: "en-US",
|
|
12426
|
+
userAgent: `cohort-sync/${pluginVersion}`
|
|
12427
|
+
}
|
|
12428
|
+
};
|
|
12429
|
+
}
|
|
12430
|
+
function parseHelloOk(response) {
|
|
12431
|
+
if (!response.ok) {
|
|
12432
|
+
const msg = response.error?.message ?? "unknown error";
|
|
12433
|
+
throw new Error(`Gateway connect error: ${msg}`);
|
|
12434
|
+
}
|
|
12435
|
+
const payload = response.payload;
|
|
12436
|
+
if (payload?.type === "hello-error") {
|
|
12437
|
+
const msg = payload.message ?? "unknown error";
|
|
12438
|
+
throw new Error(`hello-error: ${msg}`);
|
|
12439
|
+
}
|
|
12440
|
+
if (payload?.type !== "hello-ok") {
|
|
12441
|
+
throw new Error(`Unexpected payload type: ${String(payload?.type ?? "missing")}`);
|
|
12442
|
+
}
|
|
12443
|
+
const policy = payload.policy ?? {};
|
|
12444
|
+
const methods = payload.methods ?? [];
|
|
12445
|
+
const events = payload.events ?? [];
|
|
12446
|
+
return {
|
|
12447
|
+
methods: new Set(methods),
|
|
12448
|
+
events: new Set(events),
|
|
12449
|
+
tickIntervalMs: policy.tickIntervalMs ?? 15e3,
|
|
12450
|
+
snapshot: payload.snapshot
|
|
12451
|
+
};
|
|
12452
|
+
}
|
|
12453
|
+
function getPendingRequests() {
|
|
12454
|
+
const g = globalThis;
|
|
12455
|
+
const hot = g.__cohort_sync__ ?? (g.__cohort_sync__ = {});
|
|
12456
|
+
if (!hot.pendingGatewayRequests) hot.pendingGatewayRequests = /* @__PURE__ */ new Map();
|
|
12457
|
+
return hot.pendingGatewayRequests;
|
|
12458
|
+
}
|
|
12459
|
+
var GatewayClient2 = class {
|
|
12460
|
+
port;
|
|
12461
|
+
logger;
|
|
12462
|
+
ws = null;
|
|
12463
|
+
alive = false;
|
|
12464
|
+
tickWatchdog = null;
|
|
12465
|
+
reconnectTimer = null;
|
|
12466
|
+
reconnectAttempts = 0;
|
|
12467
|
+
closed = false;
|
|
12468
|
+
eventHandlers = /* @__PURE__ */ new Map();
|
|
12469
|
+
pluginVersion;
|
|
12470
|
+
tickIntervalMs = 15e3;
|
|
12471
|
+
// default; overwritten by hello-ok response
|
|
12472
|
+
deviceIdentity;
|
|
12473
|
+
/** Public Sets populated from hello-ok — consumers can inspect gateway capabilities */
|
|
12474
|
+
availableMethods = /* @__PURE__ */ new Set();
|
|
12475
|
+
availableEvents = /* @__PURE__ */ new Set();
|
|
12476
|
+
/**
|
|
12477
|
+
* @param port - Gateway WebSocket port
|
|
12478
|
+
* @param token - Auth token (stored in closure via constructor param, NOT on this or globalThis)
|
|
12479
|
+
* @param logger - RuntimeLogger from plugin SDK
|
|
12480
|
+
* @param pluginVersion - Version string for the connect frame userAgent
|
|
12481
|
+
*/
|
|
12482
|
+
constructor(port, token, logger, pluginVersion = "0.5.0") {
|
|
12483
|
+
this.port = port;
|
|
12484
|
+
this.logger = logger;
|
|
12485
|
+
this.pluginVersion = pluginVersion;
|
|
12486
|
+
this.deviceIdentity = loadOrCreateDeviceIdentity();
|
|
12487
|
+
this._getToken = () => token;
|
|
12488
|
+
}
|
|
12489
|
+
/** Token accessor — closure over constructor param */
|
|
12490
|
+
_getToken;
|
|
12491
|
+
/**
|
|
12492
|
+
* Connect to the gateway, perform the protocol v3 handshake, and start
|
|
12493
|
+
* the tick watchdog.
|
|
12494
|
+
*
|
|
12495
|
+
* Flow:
|
|
12496
|
+
* 1. Open WebSocket to ws://127.0.0.1:{port}
|
|
12497
|
+
* 2. Wait up to 500ms for connect.challenge event (optional)
|
|
12498
|
+
* 3. Send connect request frame
|
|
12499
|
+
* 4. Wait for hello-ok response
|
|
12500
|
+
* 5. Start tick watchdog at 2.5x tickIntervalMs
|
|
12501
|
+
*/
|
|
12502
|
+
async connect() {
|
|
12503
|
+
if (this.closed) throw new Error("GatewayClient has been closed");
|
|
12504
|
+
diag("GW_CLIENT_CONNECTING", { port: this.port });
|
|
12505
|
+
return new Promise((resolve, reject) => {
|
|
12506
|
+
const ws = new WebSocket(`ws://127.0.0.1:${this.port}`);
|
|
12507
|
+
this.ws = ws;
|
|
12508
|
+
let settled = false;
|
|
12509
|
+
const settle = (err) => {
|
|
12510
|
+
if (settled) return;
|
|
12511
|
+
settled = true;
|
|
12512
|
+
if (err) {
|
|
12513
|
+
this.alive = false;
|
|
12514
|
+
reject(err);
|
|
12515
|
+
} else {
|
|
12516
|
+
resolve();
|
|
12517
|
+
}
|
|
12518
|
+
};
|
|
12519
|
+
const handshakeTimeout = setTimeout(() => {
|
|
12520
|
+
settle(new Error("Gateway handshake timed out after 10000ms"));
|
|
12521
|
+
ws.close();
|
|
12522
|
+
}, 1e4);
|
|
12523
|
+
ws.addEventListener("open", () => {
|
|
12524
|
+
diag("GW_CLIENT_WS_OPEN", { port: this.port });
|
|
12525
|
+
let challengeReceived = false;
|
|
12526
|
+
let challengeNonce = "";
|
|
12527
|
+
const challengeTimer = setTimeout(() => {
|
|
12528
|
+
if (!challengeReceived) {
|
|
12529
|
+
diag("GW_CLIENT_NO_CHALLENGE", { waited: 500 });
|
|
12530
|
+
sendConnect();
|
|
12531
|
+
}
|
|
12532
|
+
}, 500);
|
|
12533
|
+
const onChallengeMessage = (event) => {
|
|
12534
|
+
try {
|
|
12535
|
+
const data = JSON.parse(String(event.data));
|
|
12536
|
+
if (data.type === "event" && data.event === "connect.challenge") {
|
|
12537
|
+
challengeReceived = true;
|
|
12538
|
+
clearTimeout(challengeTimer);
|
|
12539
|
+
challengeNonce = data.payload?.nonce ?? "";
|
|
12540
|
+
diag("GW_CLIENT_CHALLENGE_RECEIVED", { hasNonce: !!challengeNonce });
|
|
12541
|
+
sendConnect();
|
|
12542
|
+
}
|
|
12543
|
+
} catch {
|
|
12544
|
+
}
|
|
12545
|
+
};
|
|
12546
|
+
ws.addEventListener("message", onChallengeMessage);
|
|
12547
|
+
const sendConnect = () => {
|
|
12548
|
+
ws.removeEventListener("message", onChallengeMessage);
|
|
12549
|
+
const id = crypto2.randomUUID();
|
|
12550
|
+
const frame = buildConnectFrame(
|
|
12551
|
+
id,
|
|
12552
|
+
this._getToken(),
|
|
12553
|
+
this.pluginVersion,
|
|
12554
|
+
this.deviceIdentity,
|
|
12555
|
+
challengeNonce
|
|
12556
|
+
);
|
|
12557
|
+
diag("GW_CLIENT_SENDING_CONNECT", { id, protocol: 3 });
|
|
12558
|
+
ws.send(JSON.stringify(frame));
|
|
12559
|
+
const onHelloMessage = (event) => {
|
|
12560
|
+
try {
|
|
12561
|
+
const data = JSON.parse(String(event.data));
|
|
12562
|
+
if (data.type === "res" && data.id === id) {
|
|
12563
|
+
ws.removeEventListener("message", onHelloMessage);
|
|
12564
|
+
clearTimeout(handshakeTimeout);
|
|
12565
|
+
try {
|
|
12566
|
+
const result = parseHelloOk(data);
|
|
12567
|
+
this.availableMethods = result.methods;
|
|
12568
|
+
this.availableEvents = result.events;
|
|
12569
|
+
this.tickIntervalMs = result.tickIntervalMs;
|
|
12570
|
+
this.alive = true;
|
|
12571
|
+
this.reconnectAttempts = 0;
|
|
12572
|
+
diag("GW_CLIENT_HELLO_OK", {
|
|
12573
|
+
methods: result.methods.size,
|
|
12574
|
+
events: result.events.size,
|
|
12575
|
+
tickIntervalMs: result.tickIntervalMs
|
|
12576
|
+
});
|
|
12577
|
+
this.startTickWatchdog(result.tickIntervalMs);
|
|
12578
|
+
ws.addEventListener("message", (ev) => this.handleMessage(ev));
|
|
12579
|
+
if (result.snapshot) {
|
|
12580
|
+
this.emit("snapshot", result.snapshot);
|
|
12581
|
+
}
|
|
12582
|
+
this.logger.info("cohort-sync: gateway client connected (protocol v3)");
|
|
12583
|
+
settle();
|
|
12584
|
+
} catch (err) {
|
|
12585
|
+
diag("GW_CLIENT_HELLO_FAILED", { error: String(err) });
|
|
12586
|
+
settle(err instanceof Error ? err : new Error(String(err)));
|
|
12587
|
+
ws.close();
|
|
12588
|
+
}
|
|
12589
|
+
}
|
|
12590
|
+
} catch {
|
|
12591
|
+
}
|
|
12592
|
+
};
|
|
12593
|
+
ws.addEventListener("message", onHelloMessage);
|
|
12594
|
+
};
|
|
12595
|
+
});
|
|
12596
|
+
ws.addEventListener("close", () => {
|
|
12597
|
+
clearTimeout(handshakeTimeout);
|
|
12598
|
+
this.alive = false;
|
|
12599
|
+
this.stopTickWatchdog();
|
|
12600
|
+
diag("GW_CLIENT_WS_CLOSED", { port: this.port });
|
|
12601
|
+
this.logger.warn("cohort-sync: gateway client WebSocket closed");
|
|
12602
|
+
const pending = getPendingRequests();
|
|
12603
|
+
for (const [, entry] of pending) {
|
|
12604
|
+
clearTimeout(entry.timer);
|
|
12605
|
+
entry.reject(new Error("Gateway WebSocket closed"));
|
|
12606
|
+
}
|
|
12607
|
+
pending.clear();
|
|
12608
|
+
if (!settled) {
|
|
12609
|
+
settle(new Error("Gateway WebSocket closed during handshake"));
|
|
12610
|
+
}
|
|
12611
|
+
if (!this.closed) {
|
|
12612
|
+
this.scheduleReconnect();
|
|
12613
|
+
}
|
|
12614
|
+
});
|
|
12615
|
+
ws.addEventListener("error", (err) => {
|
|
12616
|
+
diag("GW_CLIENT_WS_ERROR", { error: String(err) });
|
|
12617
|
+
this.logger.error(`cohort-sync: gateway client WS error: ${String(err)}`);
|
|
12618
|
+
});
|
|
12619
|
+
});
|
|
12620
|
+
}
|
|
12621
|
+
/**
|
|
12622
|
+
* Whether the WebSocket is open and the handshake completed successfully.
|
|
12623
|
+
*/
|
|
12624
|
+
isAlive() {
|
|
12625
|
+
return this.alive && this.ws !== null && this.ws.readyState === WebSocket.OPEN;
|
|
12626
|
+
}
|
|
12627
|
+
/**
|
|
12628
|
+
* Register an event handler for gateway events (tick, cron.changed, etc.).
|
|
12629
|
+
*/
|
|
12630
|
+
on(event, handler) {
|
|
12631
|
+
let handlers = this.eventHandlers.get(event);
|
|
12632
|
+
if (!handlers) {
|
|
12633
|
+
handlers = /* @__PURE__ */ new Set();
|
|
12634
|
+
this.eventHandlers.set(event, handlers);
|
|
12635
|
+
}
|
|
12636
|
+
handlers.add(handler);
|
|
12637
|
+
}
|
|
12638
|
+
/**
|
|
12639
|
+
* Send a request to the gateway and wait for the response.
|
|
12640
|
+
*
|
|
12641
|
+
* Validates the method against ALLOWED_METHODS to prevent accidentally
|
|
12642
|
+
* calling admin or unauthorized methods.
|
|
12643
|
+
*/
|
|
12644
|
+
async request(method, params, timeoutMs = 1e4) {
|
|
12645
|
+
if (!ALLOWED_METHODS.has(method)) {
|
|
12646
|
+
throw new Error(`Method "${method}" is not in ALLOWED_METHODS`);
|
|
12647
|
+
}
|
|
12648
|
+
if (!this.isAlive()) {
|
|
12649
|
+
throw new Error("Gateway client is not connected");
|
|
12650
|
+
}
|
|
12651
|
+
const id = crypto2.randomUUID();
|
|
12652
|
+
const frame = {
|
|
12653
|
+
type: "req",
|
|
12654
|
+
id,
|
|
12655
|
+
method,
|
|
12656
|
+
params
|
|
12657
|
+
};
|
|
12658
|
+
const pending = getPendingRequests();
|
|
12659
|
+
return new Promise((resolve, reject) => {
|
|
12660
|
+
const timer = setTimeout(() => {
|
|
12661
|
+
pending.delete(id);
|
|
12662
|
+
reject(new Error(`Gateway method "${method}" timed out after ${timeoutMs}ms`));
|
|
12663
|
+
}, timeoutMs);
|
|
12664
|
+
pending.set(id, {
|
|
12665
|
+
resolve,
|
|
12666
|
+
reject,
|
|
12667
|
+
timer
|
|
12668
|
+
});
|
|
12669
|
+
this.ws.send(JSON.stringify(frame));
|
|
12670
|
+
});
|
|
12671
|
+
}
|
|
12672
|
+
/**
|
|
12673
|
+
* Clean shutdown — close WebSocket, clear timers, reject pending requests.
|
|
12674
|
+
*/
|
|
12675
|
+
close() {
|
|
12676
|
+
this.closed = true;
|
|
12677
|
+
this.alive = false;
|
|
12678
|
+
this.stopTickWatchdog();
|
|
12679
|
+
if (this.reconnectTimer) {
|
|
12680
|
+
clearTimeout(this.reconnectTimer);
|
|
12681
|
+
this.reconnectTimer = null;
|
|
12682
|
+
}
|
|
12683
|
+
const pending = getPendingRequests();
|
|
12684
|
+
for (const [, entry] of pending) {
|
|
12685
|
+
clearTimeout(entry.timer);
|
|
12686
|
+
entry.reject(new Error("Gateway client closed"));
|
|
12687
|
+
}
|
|
12688
|
+
pending.clear();
|
|
12689
|
+
if (this.ws) {
|
|
12690
|
+
this.ws.close();
|
|
12691
|
+
this.ws = null;
|
|
12692
|
+
}
|
|
12693
|
+
diag("GW_CLIENT_CLOSED", { port: this.port });
|
|
12694
|
+
this.logger.info("cohort-sync: gateway client closed");
|
|
12695
|
+
}
|
|
12696
|
+
// -------------------------------------------------------------------------
|
|
12697
|
+
// Private methods
|
|
12698
|
+
// -------------------------------------------------------------------------
|
|
12699
|
+
/**
|
|
12700
|
+
* Route incoming WebSocket messages to the appropriate handler.
|
|
12701
|
+
*
|
|
12702
|
+
* Frame types:
|
|
12703
|
+
* - "res" → resolve/reject a pending request
|
|
12704
|
+
* - "event" → dispatch to registered event handlers; reset tick watchdog on tick
|
|
12705
|
+
*/
|
|
12706
|
+
handleMessage(event) {
|
|
12707
|
+
try {
|
|
12708
|
+
const data = JSON.parse(String(event.data));
|
|
12709
|
+
if (data.type === "res") {
|
|
12710
|
+
this.handleResponse(data);
|
|
12711
|
+
} else if (data.type === "event") {
|
|
12712
|
+
this.handleEvent(data);
|
|
12713
|
+
}
|
|
12714
|
+
} catch {
|
|
12715
|
+
}
|
|
12716
|
+
}
|
|
12717
|
+
handleResponse(frame) {
|
|
12718
|
+
const pending = getPendingRequests();
|
|
12719
|
+
const entry = pending.get(frame.id);
|
|
12720
|
+
if (!entry) return;
|
|
12721
|
+
pending.delete(frame.id);
|
|
12722
|
+
clearTimeout(entry.timer);
|
|
12723
|
+
if (frame.ok) {
|
|
12724
|
+
entry.resolve(frame.payload);
|
|
12725
|
+
} else {
|
|
12726
|
+
entry.reject(new Error(`Gateway method failed: ${frame.error?.message ?? "unknown error"}`));
|
|
12727
|
+
}
|
|
12728
|
+
}
|
|
12729
|
+
handleEvent(frame) {
|
|
12730
|
+
if (frame.event === "tick") {
|
|
12731
|
+
this.resetTickWatchdog();
|
|
12732
|
+
}
|
|
12733
|
+
this.emit(frame.event, frame.payload);
|
|
12734
|
+
}
|
|
12735
|
+
emit(event, payload) {
|
|
12736
|
+
const handlers = this.eventHandlers.get(event);
|
|
12737
|
+
if (handlers) {
|
|
12738
|
+
for (const handler of handlers) {
|
|
12739
|
+
try {
|
|
12740
|
+
handler(payload);
|
|
12741
|
+
} catch (err) {
|
|
12742
|
+
this.logger.error(`cohort-sync: event handler error for "${event}": ${String(err)}`);
|
|
12743
|
+
}
|
|
12744
|
+
}
|
|
12745
|
+
}
|
|
12746
|
+
}
|
|
12747
|
+
/**
|
|
12748
|
+
* Start the tick watchdog timer.
|
|
12749
|
+
*
|
|
12750
|
+
* The gateway sends tick events at tickIntervalMs. If we don't receive
|
|
12751
|
+
* one within 2.5x that interval, the connection is considered dead.
|
|
12752
|
+
*/
|
|
12753
|
+
startTickWatchdog(tickIntervalMs) {
|
|
12754
|
+
this.stopTickWatchdog();
|
|
12755
|
+
const watchdogMs = Math.round(tickIntervalMs * 2.5);
|
|
12756
|
+
this.tickWatchdog = setTimeout(() => {
|
|
12757
|
+
diag("GW_CLIENT_TICK_TIMEOUT", { watchdogMs });
|
|
12758
|
+
this.logger.warn(`cohort-sync: tick watchdog expired after ${watchdogMs}ms \u2014 closing connection`);
|
|
12759
|
+
this.alive = false;
|
|
12760
|
+
this.ws?.close();
|
|
12761
|
+
}, watchdogMs);
|
|
12762
|
+
}
|
|
12763
|
+
resetTickWatchdog() {
|
|
12764
|
+
if (this.tickWatchdog) {
|
|
12765
|
+
this.startTickWatchdog(this.tickIntervalMs);
|
|
12766
|
+
}
|
|
12767
|
+
}
|
|
12768
|
+
stopTickWatchdog() {
|
|
12769
|
+
if (this.tickWatchdog) {
|
|
12770
|
+
clearTimeout(this.tickWatchdog);
|
|
12771
|
+
this.tickWatchdog = null;
|
|
12772
|
+
}
|
|
12773
|
+
}
|
|
12774
|
+
/**
|
|
12775
|
+
* Schedule a reconnection attempt with exponential backoff and jitter.
|
|
12776
|
+
*
|
|
12777
|
+
* Backoff: 1s base, doubles each attempt, capped at 30s.
|
|
12778
|
+
* Jitter: +/- 25% randomization to avoid thundering herd.
|
|
12779
|
+
*/
|
|
12780
|
+
scheduleReconnect() {
|
|
12781
|
+
if (this.closed) return;
|
|
12782
|
+
const baseMs = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
12783
|
+
const jitter = baseMs * 0.25 * (Math.random() * 2 - 1);
|
|
12784
|
+
const delayMs = Math.round(Math.max(baseMs + jitter, 1e3));
|
|
12785
|
+
this.reconnectAttempts++;
|
|
12786
|
+
diag("GW_CLIENT_RECONNECT_SCHEDULED", {
|
|
12787
|
+
attempt: this.reconnectAttempts,
|
|
12788
|
+
delayMs
|
|
12789
|
+
});
|
|
12790
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
12791
|
+
this.reconnectTimer = null;
|
|
12792
|
+
try {
|
|
12793
|
+
diag("GW_CLIENT_RECONNECTING", { attempt: this.reconnectAttempts });
|
|
12794
|
+
await this.connect();
|
|
12795
|
+
diag("GW_CLIENT_RECONNECTED", { attempt: this.reconnectAttempts });
|
|
12796
|
+
} catch (err) {
|
|
12797
|
+
diag("GW_CLIENT_RECONNECT_FAILED", {
|
|
12798
|
+
attempt: this.reconnectAttempts,
|
|
12799
|
+
error: String(err)
|
|
12800
|
+
});
|
|
12801
|
+
}
|
|
12802
|
+
}, delayMs);
|
|
12803
|
+
}
|
|
12804
|
+
};
|
|
12805
|
+
|
|
12190
12806
|
// src/agent-state.ts
|
|
12191
12807
|
import { basename } from "node:path";
|
|
12192
12808
|
|
|
@@ -12686,20 +13302,6 @@ var POCKET_GUIDE = `# Cohort Agent Guide (Pocket Version)
|
|
|
12686
13302
|
|
|
12687
13303
|
// src/hooks.ts
|
|
12688
13304
|
var BUILD_ID = "B9-ACCOUNTID-20260311";
|
|
12689
|
-
var DIAG_LOG_PATH = "/tmp/cohort-sync-diag.log";
|
|
12690
|
-
function diag(label, data) {
|
|
12691
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
12692
|
-
const payload = data ? " " + JSON.stringify(data, (_k, v2) => {
|
|
12693
|
-
if (typeof v2 === "string" && v2.length > 200) return v2.slice(0, 200) + "\u2026";
|
|
12694
|
-
return v2;
|
|
12695
|
-
}) : "";
|
|
12696
|
-
const line = `[${ts}] ${label}${payload}
|
|
12697
|
-
`;
|
|
12698
|
-
try {
|
|
12699
|
-
fs2.appendFileSync(DIAG_LOG_PATH, line);
|
|
12700
|
-
} catch {
|
|
12701
|
-
}
|
|
12702
|
-
}
|
|
12703
13305
|
function dumpCtx(ctx) {
|
|
12704
13306
|
if (!ctx || typeof ctx !== "object") return { _raw: String(ctx) };
|
|
12705
13307
|
const out = {};
|
|
@@ -12724,16 +13326,64 @@ function dumpEvent(event) {
|
|
|
12724
13326
|
}
|
|
12725
13327
|
var PLUGIN_VERSION = "unknown";
|
|
12726
13328
|
try {
|
|
12727
|
-
const pkgPath =
|
|
12728
|
-
const pkgJson = JSON.parse(
|
|
13329
|
+
const pkgPath = path3.join(path3.dirname(new URL(import.meta.url).pathname), "package.json");
|
|
13330
|
+
const pkgJson = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
|
|
12729
13331
|
PLUGIN_VERSION = pkgJson.version ?? "unknown";
|
|
12730
13332
|
} catch {
|
|
12731
13333
|
}
|
|
12732
13334
|
diag("MODULE_LOADED", { BUILD_ID, PLUGIN_VERSION });
|
|
13335
|
+
var lastCronSnapshotJson = "";
|
|
13336
|
+
function resolveGatewayToken(api) {
|
|
13337
|
+
const rawToken = api.config?.gateway?.auth?.token;
|
|
13338
|
+
if (typeof rawToken === "string") return rawToken;
|
|
13339
|
+
if (rawToken && typeof rawToken === "object" && rawToken.source === "env") {
|
|
13340
|
+
return process.env[rawToken.id] ?? null;
|
|
13341
|
+
}
|
|
13342
|
+
return null;
|
|
13343
|
+
}
|
|
13344
|
+
async function quickCronSync(port, token, cfg, resolveAgentName, logger) {
|
|
13345
|
+
const client2 = new GatewayClient2(port, token, logger, PLUGIN_VERSION);
|
|
13346
|
+
try {
|
|
13347
|
+
await client2.connect();
|
|
13348
|
+
const result = await client2.request("cron.list", { includeDisabled: true });
|
|
13349
|
+
diag("GW_CLIENT_CRON_LIST_RESULT", { type: typeof result, isArray: Array.isArray(result), keys: result && typeof result === "object" ? Object.keys(result) : [], length: Array.isArray(result) ? result.length : void 0 });
|
|
13350
|
+
const jobs = Array.isArray(result) ? result : result?.jobs ?? [];
|
|
13351
|
+
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
13352
|
+
const serialized = JSON.stringify(mapped);
|
|
13353
|
+
if (serialized !== lastCronSnapshotJson) {
|
|
13354
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13355
|
+
lastCronSnapshotJson = serialized;
|
|
13356
|
+
diag("HEARTBEAT_CRON_PUSHED", { count: mapped.length });
|
|
13357
|
+
} else {
|
|
13358
|
+
diag("HEARTBEAT_CRON_UNCHANGED", {});
|
|
13359
|
+
}
|
|
13360
|
+
} finally {
|
|
13361
|
+
client2.close();
|
|
13362
|
+
}
|
|
13363
|
+
}
|
|
13364
|
+
function registerCronEventHandlers(client2, cfg, resolveAgentName) {
|
|
13365
|
+
if (client2.availableEvents.has("cron")) {
|
|
13366
|
+
let debounceTimer = null;
|
|
13367
|
+
client2.on("cron", () => {
|
|
13368
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
13369
|
+
debounceTimer = setTimeout(async () => {
|
|
13370
|
+
try {
|
|
13371
|
+
const cronResult = await client2.request("cron.list", { includeDisabled: true });
|
|
13372
|
+
const jobs = Array.isArray(cronResult) ? cronResult : cronResult?.jobs ?? [];
|
|
13373
|
+
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
13374
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13375
|
+
diag("CRON_EVENT_PUSHED", { count: mapped.length });
|
|
13376
|
+
} catch (err) {
|
|
13377
|
+
diag("CRON_EVENT_PUSH_FAILED", { error: String(err) });
|
|
13378
|
+
}
|
|
13379
|
+
}, 2e3);
|
|
13380
|
+
});
|
|
13381
|
+
}
|
|
13382
|
+
}
|
|
12733
13383
|
function parseIdentityFile(workspaceDir) {
|
|
12734
13384
|
try {
|
|
12735
|
-
const filePath =
|
|
12736
|
-
const content =
|
|
13385
|
+
const filePath = path3.join(workspaceDir, "IDENTITY.md");
|
|
13386
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
12737
13387
|
const identity = {};
|
|
12738
13388
|
for (const line of content.split(/\r?\n/)) {
|
|
12739
13389
|
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
@@ -12771,8 +13421,8 @@ function getOrCreateTracker() {
|
|
|
12771
13421
|
state.tracker = fresh;
|
|
12772
13422
|
return fresh;
|
|
12773
13423
|
}
|
|
12774
|
-
var STATE_FILE_PATH =
|
|
12775
|
-
|
|
13424
|
+
var STATE_FILE_PATH = path3.join(
|
|
13425
|
+
path3.dirname(new URL(import.meta.url).pathname),
|
|
12776
13426
|
".session-state.json"
|
|
12777
13427
|
);
|
|
12778
13428
|
function saveSessionsToDisk(tracker) {
|
|
@@ -12789,14 +13439,14 @@ function saveSessionsToDisk(tracker) {
|
|
|
12789
13439
|
data.sessions.push({ agentName: name, key });
|
|
12790
13440
|
}
|
|
12791
13441
|
}
|
|
12792
|
-
|
|
13442
|
+
fs3.writeFileSync(STATE_FILE_PATH, JSON.stringify(data));
|
|
12793
13443
|
} catch {
|
|
12794
13444
|
}
|
|
12795
13445
|
}
|
|
12796
13446
|
function loadSessionsFromDisk(tracker, logger) {
|
|
12797
13447
|
try {
|
|
12798
|
-
if (!
|
|
12799
|
-
const data = JSON.parse(
|
|
13448
|
+
if (!fs3.existsSync(STATE_FILE_PATH)) return;
|
|
13449
|
+
const data = JSON.parse(fs3.readFileSync(STATE_FILE_PATH, "utf8"));
|
|
12800
13450
|
if (Date.now() - new Date(data.savedAt).getTime() > 864e5) {
|
|
12801
13451
|
logger.info("cohort-sync: disk session state too old (>24h), skipping");
|
|
12802
13452
|
return;
|
|
@@ -12854,9 +13504,6 @@ function registerHooks(api, cfg) {
|
|
|
12854
13504
|
agentIds: (config?.agents?.list ?? []).map((a) => a.id),
|
|
12855
13505
|
agentMessageProviders: (config?.agents?.list ?? []).map((a) => ({ id: a.id, mp: a.messageProvider }))
|
|
12856
13506
|
});
|
|
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
13507
|
setConvexUrl(cfg);
|
|
12861
13508
|
setLogger(logger);
|
|
12862
13509
|
restoreFromHotReload(logger);
|
|
@@ -12977,6 +13624,28 @@ function registerHooks(api, cfg) {
|
|
|
12977
13624
|
}, 3e3);
|
|
12978
13625
|
saveIntervalsToHot(heartbeatInterval, activityFlushInterval);
|
|
12979
13626
|
logger.info("cohort-sync: intervals created (heartbeat=2m, activityFlush=3s)");
|
|
13627
|
+
{
|
|
13628
|
+
const hotState = getHotState();
|
|
13629
|
+
if (hotState.commandSubscription) {
|
|
13630
|
+
hotState.commandSubscription();
|
|
13631
|
+
hotState.commandSubscription = null;
|
|
13632
|
+
}
|
|
13633
|
+
const unsub = initCommandSubscription(cfg, logger, resolveAgentName);
|
|
13634
|
+
hotState.commandSubscription = unsub;
|
|
13635
|
+
}
|
|
13636
|
+
{
|
|
13637
|
+
const port = api.config?.gateway?.port;
|
|
13638
|
+
const token = resolveGatewayToken(api);
|
|
13639
|
+
if (port && token) {
|
|
13640
|
+
const hotState = getHotState();
|
|
13641
|
+
hotState.gatewayPort = port;
|
|
13642
|
+
hotState.gatewayToken = token;
|
|
13643
|
+
diag("REGISTER_HOOKS_CRON_SYNC", { port });
|
|
13644
|
+
quickCronSync(port, token, cfg, resolveAgentName, logger).catch((err) => {
|
|
13645
|
+
diag("REGISTER_HOOKS_CRON_SYNC_FAILED", { error: String(err) });
|
|
13646
|
+
});
|
|
13647
|
+
}
|
|
13648
|
+
}
|
|
12980
13649
|
api.registerTool((toolCtx) => {
|
|
12981
13650
|
const agentId = toolCtx.agentId ?? "main";
|
|
12982
13651
|
const agentName = resolveAgentName(agentId);
|
|
@@ -13126,18 +13795,6 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13126
13795
|
}
|
|
13127
13796
|
}
|
|
13128
13797
|
saveSessionsToDisk(tracker);
|
|
13129
|
-
try {
|
|
13130
|
-
const cronJobs2 = fetchCronJobs(logger);
|
|
13131
|
-
if (cronJobs2 !== null) {
|
|
13132
|
-
const resolvedJobs = cronJobs2.map((job) => ({
|
|
13133
|
-
...job,
|
|
13134
|
-
agentId: job.agentId ? resolveAgentName(job.agentId) : job.agentId
|
|
13135
|
-
}));
|
|
13136
|
-
await pushCronSnapshot(cfg.apiKey, resolvedJobs);
|
|
13137
|
-
}
|
|
13138
|
-
} catch (err) {
|
|
13139
|
-
logger.warn(`cohort-sync: heartbeat cron push failed: ${String(err)}`);
|
|
13140
|
-
}
|
|
13141
13798
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
13142
13799
|
}
|
|
13143
13800
|
async function flushActivityBuffer() {
|
|
@@ -13175,6 +13832,31 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13175
13832
|
}
|
|
13176
13833
|
}
|
|
13177
13834
|
diag("GATEWAY_START_BRIDGE_AFTER_SEED", { bridge: { ...getHotState().channelAgentBridge } });
|
|
13835
|
+
const hotState = getHotState();
|
|
13836
|
+
hotState.gatewayPort = event.port;
|
|
13837
|
+
const token = resolveGatewayToken(api);
|
|
13838
|
+
if (token) {
|
|
13839
|
+
diag("GW_CLIENT_CONNECTING", { port: event.port, hasToken: true });
|
|
13840
|
+
try {
|
|
13841
|
+
const client2 = new GatewayClient2(event.port, token, logger, PLUGIN_VERSION);
|
|
13842
|
+
await client2.connect();
|
|
13843
|
+
hotState.gatewayProtocolClient = client2;
|
|
13844
|
+
registerCronEventHandlers(client2, cfg, resolveAgentName);
|
|
13845
|
+
diag("GW_CLIENT_CONNECTED", { methods: client2.availableMethods.size, events: client2.availableEvents.size });
|
|
13846
|
+
const cronResult = await client2.request("cron.list", { includeDisabled: true });
|
|
13847
|
+
const jobs = Array.isArray(cronResult) ? cronResult : cronResult?.jobs ?? [];
|
|
13848
|
+
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
13849
|
+
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13850
|
+
lastCronSnapshotJson = JSON.stringify(mapped);
|
|
13851
|
+
diag("GW_CLIENT_INITIAL_CRON_PUSH", { count: mapped.length });
|
|
13852
|
+
} catch (err) {
|
|
13853
|
+
diag("GW_CLIENT_CONNECT_FAILED", { error: String(err) });
|
|
13854
|
+
logger.error(`cohort-sync: gateway client connect failed: ${String(err)}`);
|
|
13855
|
+
}
|
|
13856
|
+
} else {
|
|
13857
|
+
diag("GW_CLIENT_NO_TOKEN", {});
|
|
13858
|
+
logger.warn("cohort-sync: no gateway auth token \u2014 cron operations disabled");
|
|
13859
|
+
}
|
|
13178
13860
|
await initSubscription(
|
|
13179
13861
|
event.port,
|
|
13180
13862
|
cfg,
|
|
@@ -13183,7 +13865,6 @@ Do not attempt to make more comments until ${resetAt}.`
|
|
|
13183
13865
|
).catch((err) => {
|
|
13184
13866
|
logger.error(`cohort-sync: subscription init failed: ${String(err)}`);
|
|
13185
13867
|
});
|
|
13186
|
-
initCommandSubscription(cfg, logger, resolveAgentName);
|
|
13187
13868
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
13188
13869
|
for (const agentId of allAgentIds) {
|
|
13189
13870
|
const agentName = resolveAgentName(agentId);
|
package/dist/package.json
CHANGED