@cfio/cohort-sync 0.34.5 → 0.34.7

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
@@ -2625,6 +2625,7 @@ import path3 from "node:path";
2625
2625
 
2626
2626
  // src/sync.ts
2627
2627
  import fs from "node:fs";
2628
+ import crypto from "node:crypto";
2628
2629
  import path from "node:path";
2629
2630
  var VALID_STATUSES = /* @__PURE__ */ new Set(["idle", "working", "waiting"]);
2630
2631
  function normalizeStatus(status) {
@@ -2853,7 +2854,8 @@ function parseSkillFrontmatter(text) {
2853
2854
  if (!closed) return null;
2854
2855
  const name = fields.name;
2855
2856
  if (!name) return null;
2856
- return { name, description: fields.description ?? "" };
2857
+ const description = fields.description ?? "";
2858
+ return { name, description, triggers: extractSkillTriggers(description || text) };
2857
2859
  }
2858
2860
  function stripScalar(value) {
2859
2861
  const v2 = value.trim();
@@ -2862,6 +2864,11 @@ function stripScalar(value) {
2862
2864
  }
2863
2865
  return v2;
2864
2866
  }
2867
+ function extractSkillTriggers(text) {
2868
+ const match = text.match(/\btriggers?\s+on\s*:\s*([^.\n]+)/i) ?? text.match(/\btriggers?\s*:\s*([^.\n]+)/i);
2869
+ if (!match?.[1]) return [];
2870
+ return match[1].split(/[,;]| or | and /i).map((trigger) => trigger.trim()).filter((trigger) => trigger.length > 0).slice(0, 50);
2871
+ }
2865
2872
  function enumerateSkills(skillsDir2, source = "hermes") {
2866
2873
  const byName = /* @__PURE__ */ new Map();
2867
2874
  const visit = (dir) => {
@@ -2897,6 +2904,7 @@ function enumerateSkills(skillsDir2, source = "hermes") {
2897
2904
  name: parsed.name,
2898
2905
  description: parsed.description,
2899
2906
  source,
2907
+ triggers: parsed.triggers,
2900
2908
  body: text,
2901
2909
  location: path.relative(skillsDir2, full)
2902
2910
  });
@@ -2915,8 +2923,8 @@ function writeSkillBody(skillsDir2, location, body) {
2915
2923
  if (path.basename(location) !== "SKILL.md") {
2916
2924
  throw new Error("Skill location must end with SKILL.md");
2917
2925
  }
2918
- if (body.length > MAX_SKILL_BYTES) {
2919
- throw new Error(`Skill body exceeds maximum length of ${MAX_SKILL_BYTES} characters`);
2926
+ if (Buffer.byteLength(body, "utf8") > MAX_SKILL_BYTES) {
2927
+ throw new Error(`Skill body exceeds maximum length of ${MAX_SKILL_BYTES} bytes`);
2920
2928
  }
2921
2929
  const root = path.resolve(skillsDir2);
2922
2930
  const target = path.resolve(root, location);
@@ -2928,6 +2936,26 @@ function writeSkillBody(skillsDir2, location, body) {
2928
2936
  fs.writeFileSync(target, body, "utf8");
2929
2937
  return target;
2930
2938
  }
2939
+ function hashSkillBody(body) {
2940
+ return crypto.createHash("sha256").update(body, "utf8").digest("hex");
2941
+ }
2942
+ function writeSkillBodyVerified(skillsDir2, location, body, expectedHash) {
2943
+ if (!expectedHash) {
2944
+ throw new Error("bodyHash is required for skill writeback");
2945
+ }
2946
+ const target = writeSkillBody(skillsDir2, location, body);
2947
+ const actual = hashSkillBody(fs["read"+"FileSync"](target, "utf-8"));
2948
+ if (actual !== expectedHash) {
2949
+ throw new Error("Skill body hash mismatch after write");
2950
+ }
2951
+ return target;
2952
+ }
2953
+ async function postSkillMaterializationApplied(cfg, payload) {
2954
+ await v1Post(cfg.apiUrl, cfg.apiKey, "/api/v1/skills/materialization-applied", payload);
2955
+ }
2956
+ async function postSkillMaterializationFailed(cfg, payload) {
2957
+ await v1Post(cfg.apiUrl, cfg.apiKey, "/api/v1/skills/materialization-failed", payload);
2958
+ }
2931
2959
  function skillsDirExists(skillsDir2) {
2932
2960
  try {
2933
2961
  return fs.statSync(skillsDir2).isDirectory();
@@ -11924,22 +11952,56 @@ async function executeCommand(cmd, gwClient, cfg, resolveAgentName, logger, cron
11924
11952
  if (cmd.type === "skillWrite") {
11925
11953
  const skillsDir2 = injection?.skillsDir;
11926
11954
  const agentName = cmd.payload?.agentId;
11955
+ const skillId = cmd.payload?.skillId;
11927
11956
  const location = cmd.payload?.location;
11928
11957
  const body = cmd.payload?.skillBody;
11958
+ const targetRevision = cmd.payload?.targetRevision;
11959
+ const bodyHash = cmd.payload?.bodyHash;
11929
11960
  if (!skillsDir2) {
11930
11961
  throw new Error("skillsDir is required for skillWrite");
11931
11962
  }
11932
11963
  if (!agentName) {
11933
11964
  throw new Error("agentId is required for skillWrite");
11934
11965
  }
11966
+ if (!skillId) {
11967
+ throw new Error("skillId is required for skillWrite");
11968
+ }
11935
11969
  if (!location) {
11936
11970
  throw new Error("location is required for skillWrite");
11937
11971
  }
11938
11972
  if (typeof body !== "string") {
11939
11973
  throw new Error("skillBody is required for skillWrite");
11940
11974
  }
11941
- writeSkillBody(skillsDir2, location, body);
11942
- await syncSkills(agentName, enumerateSkills(skillsDir2, "openclaw"), cfg, logger);
11975
+ if (typeof targetRevision !== "number") {
11976
+ throw new Error("targetRevision is required for skillWrite");
11977
+ }
11978
+ if (!bodyHash) {
11979
+ throw new Error("bodyHash is required for skillWrite");
11980
+ }
11981
+ try {
11982
+ writeSkillBodyVerified(skillsDir2, location, body, bodyHash);
11983
+ await postSkillMaterializationApplied(cfg, {
11984
+ skillId,
11985
+ agentName,
11986
+ targetRevision,
11987
+ bodyHash,
11988
+ commandId: cmd._id,
11989
+ location
11990
+ });
11991
+ } catch (err) {
11992
+ await postSkillMaterializationFailed(cfg, {
11993
+ skillId,
11994
+ agentName,
11995
+ targetRevision,
11996
+ bodyHash,
11997
+ commandId: cmd._id,
11998
+ location,
11999
+ error: String(err).slice(0, 500)
12000
+ }).catch((ackErr) => {
12001
+ logger.warn(`cohort-sync: materialization failed ack failed: ${String(ackErr)}`);
12002
+ });
12003
+ throw err;
12004
+ }
11943
12005
  return;
11944
12006
  }
11945
12007
  if (cmd.type.startsWith("cron")) {
@@ -13026,10 +13088,10 @@ function subscribeChannels(apiKey2, onUpdate, logger) {
13026
13088
  }
13027
13089
 
13028
13090
  // src/gateway-client.ts
13029
- import crypto2 from "node:crypto";
13091
+ import crypto3 from "node:crypto";
13030
13092
 
13031
13093
  // src/device-identity-crypto.ts
13032
- import crypto from "node:crypto";
13094
+ import crypto2 from "node:crypto";
13033
13095
  import fs2 from "node:fs";
13034
13096
  import path2 from "node:path";
13035
13097
  import os from "node:os";
@@ -13058,12 +13120,12 @@ function loadOrCreateDeviceIdentity() {
13058
13120
  persistIdentity(legacy);
13059
13121
  return legacy;
13060
13122
  }
13061
- const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519");
13123
+ const { publicKey, privateKey } = crypto2.generateKeyPairSync("ed25519");
13062
13124
  const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
13063
13125
  const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
13064
13126
  const publicKeyDer = publicKey.export({ type: "spki", format: "der" });
13065
13127
  const rawPublicKey = publicKeyDer.subarray(publicKeyDer.length - 32);
13066
- const deviceId = crypto.createHash("sha256").update(new Uint8Array(rawPublicKey)).digest("hex");
13128
+ const deviceId = crypto2.createHash("sha256").update(new Uint8Array(rawPublicKey)).digest("hex");
13067
13129
  const identity = { deviceId, publicKeyPem, privateKeyPem };
13068
13130
  console.debug("cohort-sync: device identity created", { deviceId });
13069
13131
  persistIdentity(identity);
@@ -13099,8 +13161,8 @@ function buildDeviceAuthPayloadV3(params) {
13099
13161
  ].join("|");
13100
13162
  }
13101
13163
  function signPayload(privateKeyPem, payload) {
13102
- const key = crypto.createPrivateKey(privateKeyPem);
13103
- const signature = crypto.sign(null, new Uint8Array(Buffer.from(payload, "utf-8")), key);
13164
+ const key = crypto2.createPrivateKey(privateKeyPem);
13165
+ const signature = crypto2.sign(null, new Uint8Array(Buffer.from(payload, "utf-8")), key);
13104
13166
  return signature.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
13105
13167
  }
13106
13168
 
@@ -13292,7 +13354,7 @@ var GatewayClient = class {
13292
13354
  ws.addEventListener("message", onChallengeMessage);
13293
13355
  const sendConnect = () => {
13294
13356
  ws.removeEventListener("message", onChallengeMessage);
13295
- const id = crypto2.randomUUID();
13357
+ const id = crypto3.randomUUID();
13296
13358
  const frame = buildConnectFrame(
13297
13359
  id,
13298
13360
  this._getToken(),
@@ -13405,7 +13467,7 @@ var GatewayClient = class {
13405
13467
  if (!this.isAlive()) {
13406
13468
  throw new Error("Gateway client is not connected");
13407
13469
  }
13408
- const id = crypto2.randomUUID();
13470
+ const id = crypto3.randomUUID();
13409
13471
  const frame = {
13410
13472
  type: "req",
13411
13473
  id,
@@ -14316,7 +14378,7 @@ function dumpEvent(event) {
14316
14378
  function positiveNumber(value) {
14317
14379
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
14318
14380
  }
14319
- var PLUGIN_VERSION = true ? "0.34.5" : "unknown";
14381
+ var PLUGIN_VERSION = true ? "0.34.7" : "unknown";
14320
14382
  var PRESENCE_PING_INTERVAL_MS = 12e4;
14321
14383
  function resolveGatewayToken(api) {
14322
14384
  const token2 = api.config?.gateway?.auth?.token;
@@ -85,5 +85,5 @@
85
85
  }
86
86
  }
87
87
  },
88
- "version": "0.34.5"
88
+ "version": "0.34.7"
89
89
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.34.5",
3
+ "version": "0.34.7",
4
4
  "description": "OpenClaw plugin — syncs agent telemetry, sessions, and activity to the 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.34.5",
3
+ "version": "0.34.7",
4
4
  "description": "OpenClaw plugin — syncs agent telemetry, sessions, and activity to the Cohort dashboard",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.cohort.bot/gateway",