@cfio/cohort-sync 0.34.4 → 0.34.6

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
@@ -2826,7 +2826,7 @@ async function markAllUnreachable(cfg, logger) {
2826
2826
  }
2827
2827
  logger.info("cohort-sync: all agents marked unreachable");
2828
2828
  }
2829
- var MAX_SKILL_BYTES = 64 * 1024;
2829
+ var MAX_SKILL_BYTES = 256 * 1024;
2830
2830
  var MAX_SYNC_SKILLS = 500;
2831
2831
  function parseSkillFrontmatter(text) {
2832
2832
  if (!text) return null;
@@ -2853,7 +2853,8 @@ function parseSkillFrontmatter(text) {
2853
2853
  if (!closed) return null;
2854
2854
  const name = fields.name;
2855
2855
  if (!name) return null;
2856
- return { name, description: fields.description ?? "" };
2856
+ const description = fields.description ?? "";
2857
+ return { name, description, triggers: extractSkillTriggers(description || text) };
2857
2858
  }
2858
2859
  function stripScalar(value) {
2859
2860
  const v2 = value.trim();
@@ -2862,7 +2863,12 @@ function stripScalar(value) {
2862
2863
  }
2863
2864
  return v2;
2864
2865
  }
2865
- function enumerateSkills(skillsDir, source = "hermes") {
2866
+ function extractSkillTriggers(text) {
2867
+ const match = text.match(/\btriggers?\s+on\s*:\s*([^.\n]+)/i) ?? text.match(/\btriggers?\s*:\s*([^.\n]+)/i);
2868
+ if (!match?.[1]) return [];
2869
+ return match[1].split(/[,;]| or | and /i).map((trigger) => trigger.trim()).filter((trigger) => trigger.length > 0).slice(0, 50);
2870
+ }
2871
+ function enumerateSkills(skillsDir2, source = "hermes") {
2866
2872
  const byName = /* @__PURE__ */ new Map();
2867
2873
  const visit = (dir) => {
2868
2874
  let entries;
@@ -2893,15 +2899,45 @@ function enumerateSkills(skillsDir, source = "hermes") {
2893
2899
  const parsed = parseSkillFrontmatter(text);
2894
2900
  if (!parsed) continue;
2895
2901
  if (byName.has(parsed.name)) continue;
2896
- byName.set(parsed.name, { name: parsed.name, description: parsed.description, source });
2902
+ byName.set(parsed.name, {
2903
+ name: parsed.name,
2904
+ description: parsed.description,
2905
+ source,
2906
+ triggers: parsed.triggers,
2907
+ body: text,
2908
+ location: path.relative(skillsDir2, full)
2909
+ });
2897
2910
  }
2898
2911
  };
2899
- visit(skillsDir);
2912
+ visit(skillsDir2);
2900
2913
  return [...byName.values()];
2901
2914
  }
2902
- function skillsDirExists(skillsDir) {
2915
+ function writeSkillBody(skillsDir2, location, body) {
2916
+ if (!skillsDir2) {
2917
+ throw new Error("skillsDir is required for skill writeback");
2918
+ }
2919
+ if (!location || path.isAbsolute(location) || location.split(/[\\/]+/).includes("..")) {
2920
+ throw new Error("Invalid skill location");
2921
+ }
2922
+ if (path.basename(location) !== "SKILL.md") {
2923
+ throw new Error("Skill location must end with SKILL.md");
2924
+ }
2925
+ if (body.length > MAX_SKILL_BYTES) {
2926
+ throw new Error(`Skill body exceeds maximum length of ${MAX_SKILL_BYTES} characters`);
2927
+ }
2928
+ const root = path.resolve(skillsDir2);
2929
+ const target = path.resolve(root, location);
2930
+ const relative = path.relative(root, target);
2931
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
2932
+ throw new Error("Skill location escapes skills directory");
2933
+ }
2934
+ fs.mkdirSync(path.dirname(target), { recursive: true });
2935
+ fs.writeFileSync(target, body, "utf8");
2936
+ return target;
2937
+ }
2938
+ function skillsDirExists(skillsDir2) {
2903
2939
  try {
2904
- return fs.statSync(skillsDir).isDirectory();
2940
+ return fs.statSync(skillsDir2).isDirectory();
2905
2941
  } catch {
2906
2942
  return false;
2907
2943
  }
@@ -2913,7 +2949,7 @@ async function syncSkills(agentName, skills, cfg, logger) {
2913
2949
  }
2914
2950
  await v1Post(cfg.apiUrl, cfg.apiKey, "/api/v1/skills/sync", { agentName, skills: capped });
2915
2951
  }
2916
- async function fullSync(agentName, model, cfg, logger, openClawAgents, skillsDir) {
2952
+ async function fullSync(agentName, model, cfg, logger, openClawAgents, skillsDir2) {
2917
2953
  logger.info("cohort-sync: full sync starting");
2918
2954
  if (openClawAgents && openClawAgents.length > 0) {
2919
2955
  try {
@@ -2924,14 +2960,14 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents, skillsDir
2924
2960
  } else {
2925
2961
  await syncAgentStatus(agentName, "working", model, cfg, logger);
2926
2962
  }
2927
- if (skillsDir) {
2963
+ if (skillsDir2) {
2928
2964
  try {
2929
- if (!skillsDirExists(skillsDir)) {
2930
- logger.info(`cohort-sync: skill sync skipped (skills dir missing or unreadable: ${skillsDir})`);
2965
+ if (!skillsDirExists(skillsDir2)) {
2966
+ logger.info(`cohort-sync: skill sync skipped (skills dir missing or unreadable: ${skillsDir2})`);
2931
2967
  } else {
2932
- const skills = enumerateSkills(skillsDir, "openclaw");
2968
+ const skills = enumerateSkills(skillsDir2, "openclaw");
2933
2969
  await syncSkills(agentName, skills, cfg, logger);
2934
- logger.info(`cohort-sync: synced ${skills.length} skill(s) from ${skillsDir}`);
2970
+ logger.info(`cohort-sync: synced ${skills.length} skill(s) from ${skillsDir2}`);
2935
2971
  }
2936
2972
  } catch (err) {
2937
2973
  logger.warn(`cohort-sync: skill sync failed (non-fatal): ${String(err)}`);
@@ -11892,6 +11928,27 @@ async function executeCommand(cmd, gwClient, cfg, resolveAgentName, logger, cron
11892
11928
  });
11893
11929
  return;
11894
11930
  }
11931
+ if (cmd.type === "skillWrite") {
11932
+ const skillsDir2 = injection?.skillsDir;
11933
+ const agentName = cmd.payload?.agentId;
11934
+ const location = cmd.payload?.location;
11935
+ const body = cmd.payload?.skillBody;
11936
+ if (!skillsDir2) {
11937
+ throw new Error("skillsDir is required for skillWrite");
11938
+ }
11939
+ if (!agentName) {
11940
+ throw new Error("agentId is required for skillWrite");
11941
+ }
11942
+ if (!location) {
11943
+ throw new Error("location is required for skillWrite");
11944
+ }
11945
+ if (typeof body !== "string") {
11946
+ throw new Error("skillBody is required for skillWrite");
11947
+ }
11948
+ writeSkillBody(skillsDir2, location, body);
11949
+ await syncSkills(agentName, enumerateSkills(skillsDir2, "openclaw"), cfg, logger);
11950
+ return;
11951
+ }
11895
11952
  if (cmd.type.startsWith("cron")) {
11896
11953
  if (!gwClient || !gwClient.isAlive()) {
11897
11954
  logger.warn(`cohort-sync: no gateway client, cannot execute ${cmd.type}`);
@@ -14266,7 +14323,7 @@ function dumpEvent(event) {
14266
14323
  function positiveNumber(value) {
14267
14324
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
14268
14325
  }
14269
- var PLUGIN_VERSION = true ? "0.34.4" : "unknown";
14326
+ var PLUGIN_VERSION = true ? "0.34.6" : "unknown";
14270
14327
  var PRESENCE_PING_INTERVAL_MS = 12e4;
14271
14328
  function resolveGatewayToken(api) {
14272
14329
  const token2 = api.config?.gateway?.auth?.token;
@@ -14444,7 +14501,7 @@ async function handleGatewayStart(event, state) {
14444
14501
  if (!state) {
14445
14502
  return;
14446
14503
  }
14447
- const { cfg, tracker, logger, config, api } = state;
14504
+ const { cfg, tracker, logger, config, api, skillsDir: skillsDir2 } = state;
14448
14505
  try {
14449
14506
  const latestVersion = await checkForUpdate(PLUGIN_VERSION, logger);
14450
14507
  if (latestVersion) {
@@ -14471,14 +14528,13 @@ async function handleGatewayStart(event, state) {
14471
14528
  model: state.resolveModel(a.id),
14472
14529
  identity: resolveIdentity(a.identity, a.workspace)
14473
14530
  }));
14474
- const skillsDir = api.config?.skills?.dir ?? path3.join(os2.homedir(), ".openclaw", "skills");
14475
14531
  await fullSync(
14476
14532
  state.resolveAgentName("main"),
14477
14533
  state.resolveModel("main"),
14478
14534
  cfg,
14479
14535
  logger,
14480
14536
  agentList,
14481
- skillsDir
14537
+ skillsDir2
14482
14538
  );
14483
14539
  } catch (err) {
14484
14540
  logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
@@ -14512,7 +14568,7 @@ async function handleGatewayStart(event, state) {
14512
14568
  state.resolveAgentName,
14513
14569
  state.persistentGwClient,
14514
14570
  state.cronTimestampTracker,
14515
- { port: state.gatewayPort, hooksToken }
14571
+ { port: state.gatewayPort, hooksToken, skillsDir: skillsDir2 }
14516
14572
  );
14517
14573
  state.commandUnsubscriber = unsub;
14518
14574
  } catch (err) {
@@ -14860,7 +14916,7 @@ function registerHookHandlers(api, logger, getState) {
14860
14916
  state.resolveAgentName,
14861
14917
  state.persistentGwClient,
14862
14918
  state.cronTimestampTracker,
14863
- { port: state.gatewayPort, hooksToken: state.gatewayToken }
14919
+ { port: state.gatewayPort, hooksToken: state.gatewayToken, skillsDir }
14864
14920
  );
14865
14921
  state.commandUnsubscriber = unsub;
14866
14922
  } catch (err) {
@@ -15173,6 +15229,7 @@ function initializeHookState(api, cfg) {
15173
15229
  const gatewayPort = api.config?.gateway?.port ?? null;
15174
15230
  const gatewayToken = resolveGatewayToken(api);
15175
15231
  const hooksToken = resolveHooksToken(api);
15232
+ const skillsDir2 = api.config?.skills?.dir ?? path3.join(os2.homedir(), ".openclaw", "skills");
15176
15233
  const cronTimestampTracker = new CronTimestampTracker();
15177
15234
  const cronRunStarts = /* @__PURE__ */ new Map();
15178
15235
  let persistentGwClient = null;
@@ -15188,7 +15245,7 @@ function initializeHookState(api, cfg) {
15188
15245
  resolveAgentName,
15189
15246
  persistentGwClient,
15190
15247
  cronTimestampTracker,
15191
- { port: gatewayPort, hooksToken }
15248
+ { port: gatewayPort, hooksToken, skillsDir: skillsDir2 }
15192
15249
  );
15193
15250
  setToolRuntime({
15194
15251
  apiKey: cfg.apiKey,
@@ -15233,6 +15290,7 @@ function initializeHookState(api, cfg) {
15233
15290
  getModelContextLimit: getModelContextLimit2,
15234
15291
  activityBatch,
15235
15292
  cronStorePath,
15293
+ skillsDir: skillsDir2,
15236
15294
  stateFilePath,
15237
15295
  gatewayPort,
15238
15296
  gatewayToken,
@@ -85,5 +85,5 @@
85
85
  }
86
86
  }
87
87
  },
88
- "version": "0.34.4"
88
+ "version": "0.34.6"
89
89
  }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.34.4",
3
+ "version": "0.34.6",
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.4",
3
+ "version": "0.34.6",
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",