@cfio/cohort-sync 0.2.4 → 0.3.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
@@ -11503,6 +11503,7 @@ var _systemSchema = defineSchema({
11503
11503
  });
11504
11504
 
11505
11505
  // src/subscription.ts
11506
+ import { execFileSync } from "node:child_process";
11506
11507
  function deriveConvexUrl(apiUrl) {
11507
11508
  return apiUrl.replace(/\.convex\.site\/?$/, ".convex.cloud");
11508
11509
  }
@@ -11584,6 +11585,7 @@ var markDeliveredByPlugin = makeFunctionReference("notifications:markDeliveredBy
11584
11585
  var upsertTelemetryFromPlugin = makeFunctionReference("telemetryPlugin:upsertTelemetryFromPlugin");
11585
11586
  var upsertSessionsFromPlugin = makeFunctionReference("telemetryPlugin:upsertSessionsFromPlugin");
11586
11587
  var pushActivityFromPluginRef = makeFunctionReference("activityFeed:pushActivityFromPlugin");
11588
+ var upsertCronSnapshotFromPluginRef = makeFunctionReference("telemetryPlugin:upsertCronSnapshotFromPlugin");
11587
11589
  var getPendingCommandsForPlugin = makeFunctionReference("gatewayCommands:getPendingForPlugin");
11588
11590
  var acknowledgeCommandRef = makeFunctionReference("gatewayCommands:acknowledgeCommand");
11589
11591
  var addCommentFromPluginRef = makeFunctionReference("comments:addCommentFromPlugin");
@@ -11740,23 +11742,38 @@ function initCommandSubscription(cfg, logger) {
11740
11742
  if (processing) return;
11741
11743
  if (commands.length === 0) return;
11742
11744
  processing = true;
11743
- const cmd = commands[commands.length - 1];
11744
- logger.info(`cohort-sync: received gateway command: ${cmd.type} (${cmd._id})`);
11745
- if (cmd.type === "restart") {
11746
- try {
11747
- await c.mutation(acknowledgeCommandRef, {
11748
- commandId: cmd._id,
11749
- apiKey: cfg.apiKey
11750
- });
11751
- logger.info("cohort-sync: restart command acknowledged, terminating in 500ms");
11752
- await new Promise((r) => setTimeout(r, 500));
11753
- process.kill(process.pid, "SIGTERM");
11754
- } catch (err) {
11755
- logger.error(`cohort-sync: failed to process restart command: ${String(err)}`);
11756
- processing = false;
11745
+ try {
11746
+ for (const cmd of commands) {
11747
+ logger.info(`cohort-sync: processing command: ${cmd.type} (${cmd._id})`);
11748
+ try {
11749
+ await c.mutation(acknowledgeCommandRef, {
11750
+ commandId: cmd._id,
11751
+ apiKey: cfg.apiKey
11752
+ });
11753
+ if (cmd.type === "restart") {
11754
+ logger.info("cohort-sync: restart acknowledged, terminating in 500ms");
11755
+ await new Promise((r) => setTimeout(r, 500));
11756
+ process.kill(process.pid, "SIGTERM");
11757
+ return;
11758
+ }
11759
+ if (cmd.type.startsWith("cron") && cmd.payload?.jobId) {
11760
+ handleCronCommand(cmd.type, cmd.payload.jobId, logger);
11761
+ try {
11762
+ const freshJobs = fetchCronJobs(logger);
11763
+ await pushCronSnapshot(cfg.apiKey, freshJobs);
11764
+ } catch (snapErr) {
11765
+ logger.warn(`cohort-sync: post-command snapshot push failed: ${String(snapErr)}`);
11766
+ }
11767
+ } else if (cmd.type.startsWith("cron")) {
11768
+ logger.warn(`cohort-sync: cron command missing jobId: ${cmd.type}`);
11769
+ } else {
11770
+ logger.warn(`cohort-sync: unknown command type: ${cmd.type}`);
11771
+ }
11772
+ } catch (err) {
11773
+ logger.error(`cohort-sync: failed to process command ${cmd._id}: ${String(err)}`);
11774
+ }
11757
11775
  }
11758
- } else {
11759
- logger.warn(`cohort-sync: unknown command type: ${cmd.type}`);
11776
+ } finally {
11760
11777
  processing = false;
11761
11778
  }
11762
11779
  },
@@ -11768,6 +11785,31 @@ function initCommandSubscription(cfg, logger) {
11768
11785
  getHotState().unsubscribers = [...unsubscribers];
11769
11786
  logger.info("cohort-sync: command subscription active");
11770
11787
  }
11788
+ function handleCronCommand(type, jobId, logger) {
11789
+ const subcommandMap = {
11790
+ cronEnable: ["cron", "enable", jobId],
11791
+ cronDisable: ["cron", "disable", jobId],
11792
+ cronRunNow: ["cron", "run", jobId],
11793
+ cronDelete: ["cron", "delete", jobId, "--force"]
11794
+ };
11795
+ const args = subcommandMap[type];
11796
+ if (!args) {
11797
+ logger.warn(`cohort-sync: unknown cron command type: ${type}`);
11798
+ return;
11799
+ }
11800
+ const timeout = type === "cronRunNow" ? 3e4 : 15e3;
11801
+ try {
11802
+ execFileSync("openclaw", args, {
11803
+ encoding: "utf8",
11804
+ timeout,
11805
+ stdio: ["ignore", "pipe", "ignore"]
11806
+ });
11807
+ logger.info(`cohort-sync: cron command ${type} executed for job ${jobId}`);
11808
+ } catch (err) {
11809
+ logger.error(`cohort-sync: cron command ${type} failed for job ${jobId}: ${String(err)}`);
11810
+ throw err;
11811
+ }
11812
+ }
11771
11813
  async function callAddCommentFromPlugin(apiKey, args) {
11772
11814
  const c = getOrCreateClient();
11773
11815
  if (!c) {
@@ -11828,6 +11870,15 @@ async function pushActivity(apiKey, entries) {
11828
11870
  getLogger().error(`cohort-sync: pushActivity failed: ${err}`);
11829
11871
  }
11830
11872
  }
11873
+ async function pushCronSnapshot(apiKey, jobs) {
11874
+ const c = getOrCreateClient();
11875
+ if (!c) return;
11876
+ try {
11877
+ await c.mutation(upsertCronSnapshotFromPluginRef, { apiKey, jobs });
11878
+ } catch (err) {
11879
+ getLogger().error(`cohort-sync: pushCronSnapshot failed: ${err}`);
11880
+ }
11881
+ }
11831
11882
  function saveIntervalsToHot(heartbeat, activityFlush) {
11832
11883
  const state = getHotState();
11833
11884
  state.intervals = { heartbeat, activityFlush };
@@ -11891,6 +11942,44 @@ function fetchSkills(logger) {
11891
11942
  return [];
11892
11943
  }
11893
11944
  }
11945
+ function fetchCronJobs(logger) {
11946
+ try {
11947
+ const raw = execSync("openclaw cron list --all --json", {
11948
+ encoding: "utf8",
11949
+ timeout: 1e4,
11950
+ stdio: ["ignore", "pipe", "ignore"],
11951
+ env: { ...process.env, NO_COLOR: "1" }
11952
+ });
11953
+ const parsed = JSON.parse(extractJson(raw));
11954
+ const list = Array.isArray(parsed) ? parsed : parsed?.jobs ?? [];
11955
+ return list.map((j) => ({
11956
+ id: String(j.jobId ?? j.id ?? "unknown"),
11957
+ text: String(j.name ?? j.text ?? ""),
11958
+ schedule: formatSchedule(j.schedule),
11959
+ ...j.nextRunAt != null ? { nextRun: Number(j.nextRunAt) } : {},
11960
+ ...j.lastRunAt != null ? { lastRun: Number(j.lastRunAt) } : {},
11961
+ ...j.lastStatus ? { lastStatus: String(j.lastStatus) } : {},
11962
+ enabled: j.enabled !== false,
11963
+ ...j.agentId != null ? { agentId: String(j.agentId) } : {}
11964
+ }));
11965
+ } catch (err) {
11966
+ logger.warn(`cohort-sync: failed to fetch cron jobs: ${String(err)}`);
11967
+ return [];
11968
+ }
11969
+ }
11970
+ function formatSchedule(schedule) {
11971
+ if (typeof schedule === "string") return schedule;
11972
+ if (schedule && typeof schedule === "object") {
11973
+ const s = schedule;
11974
+ if (s.kind === "cron" && typeof s.expr === "string") return s.expr;
11975
+ if (s.kind === "interval" && typeof s.everyMs === "number") {
11976
+ const mins = Math.floor(Number(s.everyMs) / 6e4);
11977
+ if (mins > 0) return `*/${mins} * * * *`;
11978
+ return `every ${s.everyMs}ms`;
11979
+ }
11980
+ }
11981
+ return String(schedule);
11982
+ }
11894
11983
  var VALID_STATUSES = /* @__PURE__ */ new Set(["idle", "working", "waiting"]);
11895
11984
  function normalizeStatus(status) {
11896
11985
  return VALID_STATUSES.has(status) ? status : "idle";
@@ -12933,6 +13022,16 @@ Do not attempt to make more comments until ${resetAt}.`
12933
13022
  }
12934
13023
  }
12935
13024
  saveSessionsToDisk(tracker);
13025
+ try {
13026
+ const cronJobs2 = fetchCronJobs(logger);
13027
+ const resolvedJobs = cronJobs2.map((job) => ({
13028
+ ...job,
13029
+ agentId: job.agentId ? resolveAgentName(job.agentId) : job.agentId
13030
+ }));
13031
+ await pushCronSnapshot(cfg.apiKey, resolvedJobs);
13032
+ } catch (err) {
13033
+ logger.warn(`cohort-sync: heartbeat cron push failed: ${String(err)}`);
13034
+ }
12936
13035
  logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
12937
13036
  }
12938
13037
  async function flushActivityBuffer() {
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.2.4",
3
+ "version": "0.3.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.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "Syncs agent status and skills to Cohort dashboard",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.cohort.bot/gateway",