@cfio/cohort-sync 0.34.4 → 0.34.5

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;
@@ -2862,7 +2862,7 @@ function stripScalar(value) {
2862
2862
  }
2863
2863
  return v2;
2864
2864
  }
2865
- function enumerateSkills(skillsDir, source = "hermes") {
2865
+ function enumerateSkills(skillsDir2, source = "hermes") {
2866
2866
  const byName = /* @__PURE__ */ new Map();
2867
2867
  const visit = (dir) => {
2868
2868
  let entries;
@@ -2893,15 +2893,44 @@ function enumerateSkills(skillsDir, source = "hermes") {
2893
2893
  const parsed = parseSkillFrontmatter(text);
2894
2894
  if (!parsed) continue;
2895
2895
  if (byName.has(parsed.name)) continue;
2896
- byName.set(parsed.name, { name: parsed.name, description: parsed.description, source });
2896
+ byName.set(parsed.name, {
2897
+ name: parsed.name,
2898
+ description: parsed.description,
2899
+ source,
2900
+ body: text,
2901
+ location: path.relative(skillsDir2, full)
2902
+ });
2897
2903
  }
2898
2904
  };
2899
- visit(skillsDir);
2905
+ visit(skillsDir2);
2900
2906
  return [...byName.values()];
2901
2907
  }
2902
- function skillsDirExists(skillsDir) {
2908
+ function writeSkillBody(skillsDir2, location, body) {
2909
+ if (!skillsDir2) {
2910
+ throw new Error("skillsDir is required for skill writeback");
2911
+ }
2912
+ if (!location || path.isAbsolute(location) || location.split(/[\\/]+/).includes("..")) {
2913
+ throw new Error("Invalid skill location");
2914
+ }
2915
+ if (path.basename(location) !== "SKILL.md") {
2916
+ throw new Error("Skill location must end with SKILL.md");
2917
+ }
2918
+ if (body.length > MAX_SKILL_BYTES) {
2919
+ throw new Error(`Skill body exceeds maximum length of ${MAX_SKILL_BYTES} characters`);
2920
+ }
2921
+ const root = path.resolve(skillsDir2);
2922
+ const target = path.resolve(root, location);
2923
+ const relative = path.relative(root, target);
2924
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
2925
+ throw new Error("Skill location escapes skills directory");
2926
+ }
2927
+ fs.mkdirSync(path.dirname(target), { recursive: true });
2928
+ fs.writeFileSync(target, body, "utf8");
2929
+ return target;
2930
+ }
2931
+ function skillsDirExists(skillsDir2) {
2903
2932
  try {
2904
- return fs.statSync(skillsDir).isDirectory();
2933
+ return fs.statSync(skillsDir2).isDirectory();
2905
2934
  } catch {
2906
2935
  return false;
2907
2936
  }
@@ -2913,7 +2942,7 @@ async function syncSkills(agentName, skills, cfg, logger) {
2913
2942
  }
2914
2943
  await v1Post(cfg.apiUrl, cfg.apiKey, "/api/v1/skills/sync", { agentName, skills: capped });
2915
2944
  }
2916
- async function fullSync(agentName, model, cfg, logger, openClawAgents, skillsDir) {
2945
+ async function fullSync(agentName, model, cfg, logger, openClawAgents, skillsDir2) {
2917
2946
  logger.info("cohort-sync: full sync starting");
2918
2947
  if (openClawAgents && openClawAgents.length > 0) {
2919
2948
  try {
@@ -2924,14 +2953,14 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents, skillsDir
2924
2953
  } else {
2925
2954
  await syncAgentStatus(agentName, "working", model, cfg, logger);
2926
2955
  }
2927
- if (skillsDir) {
2956
+ if (skillsDir2) {
2928
2957
  try {
2929
- if (!skillsDirExists(skillsDir)) {
2930
- logger.info(`cohort-sync: skill sync skipped (skills dir missing or unreadable: ${skillsDir})`);
2958
+ if (!skillsDirExists(skillsDir2)) {
2959
+ logger.info(`cohort-sync: skill sync skipped (skills dir missing or unreadable: ${skillsDir2})`);
2931
2960
  } else {
2932
- const skills = enumerateSkills(skillsDir, "openclaw");
2961
+ const skills = enumerateSkills(skillsDir2, "openclaw");
2933
2962
  await syncSkills(agentName, skills, cfg, logger);
2934
- logger.info(`cohort-sync: synced ${skills.length} skill(s) from ${skillsDir}`);
2963
+ logger.info(`cohort-sync: synced ${skills.length} skill(s) from ${skillsDir2}`);
2935
2964
  }
2936
2965
  } catch (err) {
2937
2966
  logger.warn(`cohort-sync: skill sync failed (non-fatal): ${String(err)}`);
@@ -11892,6 +11921,27 @@ async function executeCommand(cmd, gwClient, cfg, resolveAgentName, logger, cron
11892
11921
  });
11893
11922
  return;
11894
11923
  }
11924
+ if (cmd.type === "skillWrite") {
11925
+ const skillsDir2 = injection?.skillsDir;
11926
+ const agentName = cmd.payload?.agentId;
11927
+ const location = cmd.payload?.location;
11928
+ const body = cmd.payload?.skillBody;
11929
+ if (!skillsDir2) {
11930
+ throw new Error("skillsDir is required for skillWrite");
11931
+ }
11932
+ if (!agentName) {
11933
+ throw new Error("agentId is required for skillWrite");
11934
+ }
11935
+ if (!location) {
11936
+ throw new Error("location is required for skillWrite");
11937
+ }
11938
+ if (typeof body !== "string") {
11939
+ throw new Error("skillBody is required for skillWrite");
11940
+ }
11941
+ writeSkillBody(skillsDir2, location, body);
11942
+ await syncSkills(agentName, enumerateSkills(skillsDir2, "openclaw"), cfg, logger);
11943
+ return;
11944
+ }
11895
11945
  if (cmd.type.startsWith("cron")) {
11896
11946
  if (!gwClient || !gwClient.isAlive()) {
11897
11947
  logger.warn(`cohort-sync: no gateway client, cannot execute ${cmd.type}`);
@@ -14266,7 +14316,7 @@ function dumpEvent(event) {
14266
14316
  function positiveNumber(value) {
14267
14317
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
14268
14318
  }
14269
- var PLUGIN_VERSION = true ? "0.34.4" : "unknown";
14319
+ var PLUGIN_VERSION = true ? "0.34.5" : "unknown";
14270
14320
  var PRESENCE_PING_INTERVAL_MS = 12e4;
14271
14321
  function resolveGatewayToken(api) {
14272
14322
  const token2 = api.config?.gateway?.auth?.token;
@@ -14444,7 +14494,7 @@ async function handleGatewayStart(event, state) {
14444
14494
  if (!state) {
14445
14495
  return;
14446
14496
  }
14447
- const { cfg, tracker, logger, config, api } = state;
14497
+ const { cfg, tracker, logger, config, api, skillsDir: skillsDir2 } = state;
14448
14498
  try {
14449
14499
  const latestVersion = await checkForUpdate(PLUGIN_VERSION, logger);
14450
14500
  if (latestVersion) {
@@ -14471,14 +14521,13 @@ async function handleGatewayStart(event, state) {
14471
14521
  model: state.resolveModel(a.id),
14472
14522
  identity: resolveIdentity(a.identity, a.workspace)
14473
14523
  }));
14474
- const skillsDir = api.config?.skills?.dir ?? path3.join(os2.homedir(), ".openclaw", "skills");
14475
14524
  await fullSync(
14476
14525
  state.resolveAgentName("main"),
14477
14526
  state.resolveModel("main"),
14478
14527
  cfg,
14479
14528
  logger,
14480
14529
  agentList,
14481
- skillsDir
14530
+ skillsDir2
14482
14531
  );
14483
14532
  } catch (err) {
14484
14533
  logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
@@ -14512,7 +14561,7 @@ async function handleGatewayStart(event, state) {
14512
14561
  state.resolveAgentName,
14513
14562
  state.persistentGwClient,
14514
14563
  state.cronTimestampTracker,
14515
- { port: state.gatewayPort, hooksToken }
14564
+ { port: state.gatewayPort, hooksToken, skillsDir: skillsDir2 }
14516
14565
  );
14517
14566
  state.commandUnsubscriber = unsub;
14518
14567
  } catch (err) {
@@ -14860,7 +14909,7 @@ function registerHookHandlers(api, logger, getState) {
14860
14909
  state.resolveAgentName,
14861
14910
  state.persistentGwClient,
14862
14911
  state.cronTimestampTracker,
14863
- { port: state.gatewayPort, hooksToken: state.gatewayToken }
14912
+ { port: state.gatewayPort, hooksToken: state.gatewayToken, skillsDir }
14864
14913
  );
14865
14914
  state.commandUnsubscriber = unsub;
14866
14915
  } catch (err) {
@@ -15173,6 +15222,7 @@ function initializeHookState(api, cfg) {
15173
15222
  const gatewayPort = api.config?.gateway?.port ?? null;
15174
15223
  const gatewayToken = resolveGatewayToken(api);
15175
15224
  const hooksToken = resolveHooksToken(api);
15225
+ const skillsDir2 = api.config?.skills?.dir ?? path3.join(os2.homedir(), ".openclaw", "skills");
15176
15226
  const cronTimestampTracker = new CronTimestampTracker();
15177
15227
  const cronRunStarts = /* @__PURE__ */ new Map();
15178
15228
  let persistentGwClient = null;
@@ -15188,7 +15238,7 @@ function initializeHookState(api, cfg) {
15188
15238
  resolveAgentName,
15189
15239
  persistentGwClient,
15190
15240
  cronTimestampTracker,
15191
- { port: gatewayPort, hooksToken }
15241
+ { port: gatewayPort, hooksToken, skillsDir: skillsDir2 }
15192
15242
  );
15193
15243
  setToolRuntime({
15194
15244
  apiKey: cfg.apiKey,
@@ -15233,6 +15283,7 @@ function initializeHookState(api, cfg) {
15233
15283
  getModelContextLimit: getModelContextLimit2,
15234
15284
  activityBatch,
15235
15285
  cronStorePath,
15286
+ skillsDir: skillsDir2,
15236
15287
  stateFilePath,
15237
15288
  gatewayPort,
15238
15289
  gatewayToken,
@@ -85,5 +85,5 @@
85
85
  }
86
86
  }
87
87
  },
88
- "version": "0.34.4"
88
+ "version": "0.34.5"
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.5",
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.5",
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",