@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 CHANGED
@@ -88,9 +88,8 @@ var init_keychain = __esm({
88
88
  });
89
89
 
90
90
  // src/hooks.ts
91
- import fs2 from "node:fs";
92
- import os2 from "node:os";
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 path3 = pathParts.slice(0, -1).join("/");
4557
+ const path2 = pathParts.slice(0, -1).join("/");
4562
4558
  const exportName = pathParts[pathParts.length - 1];
4563
4559
  if (exportName === "default") {
4564
- return path3;
4560
+ return path2;
4565
4561
  } else {
4566
- return path3 + ":" + exportName;
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 fs3 = __require("fs");
7636
- var path3 = __require("path");
7637
- var os3 = __require("os");
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 || os3.arch();
7644
- var platform = process.env.npm_config_platform || os3.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 = path3.resolve(dir || ".");
7649
+ dir = path2.resolve(dir || ".");
7654
7650
  try {
7655
- var name = runtimeRequire(path3.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
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(path3.join(dir, "build/Release"), matchBuild);
7656
+ var release = getFirst(path2.join(dir, "build/Release"), matchBuild);
7661
7657
  if (release) return release;
7662
- var debug = getFirst(path3.join(dir, "build/Debug"), matchBuild);
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(path3.dirname(process.execPath));
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(path3.join(dir2, "prebuilds")).map(parseTuple);
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 = path3.join(dir2, "prebuilds", tuple.name);
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 path3.join(prebuilds, winner.file);
7687
+ if (winner) return path2.join(prebuilds, winner.file);
7692
7688
  }
7693
7689
  };
7694
7690
  function readdirSync(dir) {
7695
7691
  try {
7696
- return fs3.readdirSync(dir);
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] && path3.join(dir, 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" && fs3.existsSync("/etc/alpine-release");
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") && cmd.payload?.jobId) {
11764
- handleCronCommand(cmd.type, cmd.payload.jobId, logger);
11765
- try {
11766
- const freshJobs = fetchCronJobs(logger);
11767
- if (freshJobs !== null) {
11768
- const resolvedJobs = resolveAgentName ? freshJobs.map((j) => ({ ...j, agentId: j.agentId ? resolveAgentName(j.agentId) : j.agentId })) : freshJobs;
11769
- await pushCronSnapshot(cfg.apiKey, resolvedJobs);
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, path3) {
11999
- const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path3}`, {
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 ${path3} \u2192 ${res.status}`);
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, path3, body) {
12007
- const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path3}`, {
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 ${path3} \u2192 ${res.status}`);
12170
+ if (!res.ok) throw new Error(`PATCH ${path2} \u2192 ${res.status}`);
12014
12171
  }
12015
- async function v1Post(apiUrl, apiKey, path3, body) {
12016
- const res = await fetch(`${apiUrl.replace(/\/+$/, "")}${path3}`, {
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 ${path3} \u2192 ${res.status}`);
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
- fs2.appendFileSync(DIAG_LOG_PATH, line);
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 = path2.join(path2.dirname(new URL(import.meta.url).pathname), "package.json");
12728
- const pkgJson = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
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 = path2.join(workspaceDir, "IDENTITY.md");
12736
- const content = fs2.readFileSync(filePath, "utf-8");
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 = path2.join(
12775
- path2.dirname(new URL(import.meta.url).pathname),
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
- fs2.writeFileSync(STATE_FILE_PATH, JSON.stringify(data));
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 (!fs2.existsSync(STATE_FILE_PATH)) return;
12799
- const data = JSON.parse(fs2.readFileSync(STATE_FILE_PATH, "utf8"));
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
- 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);
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
- } catch (err) {
13139
- logger.warn(`cohort-sync: heartbeat cron push failed: ${String(err)}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "Syncs agent status and skills to Cohort dashboard",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "Syncs agent status and skills to Cohort dashboard",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.cohort.bot/gateway",