@cfio/cohort-sync 0.9.4 → 0.10.1

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
@@ -1,102 +1,10 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
- };
6
2
  var __export = (target, all) => {
7
3
  for (var name in all)
8
4
  __defProp(target, name, { get: all[name], enumerable: true });
9
5
  };
10
6
 
11
- // src/keychain.ts
12
- var keychain_exports = {};
13
- __export(keychain_exports, {
14
- deleteCredential: () => deleteCredential,
15
- getCredential: () => getCredential,
16
- setCredential: () => setCredential
17
- });
18
- import { execFile } from "node:child_process";
19
- import os4 from "node:os";
20
- function assertMacOS(operation) {
21
- if (os4.platform() !== "darwin") {
22
- throw new Error(
23
- `cohort-sync: ${operation} requires macOS Keychain. On Linux/Windows, set your API key in OpenClaw config: plugins.entries.cohort-sync.config.apiKey`
24
- );
25
- }
26
- }
27
- function securityCmd(args) {
28
- return new Promise((resolve, reject) => {
29
- execFile("security", args, { timeout: 5e3 }, (err, stdout, stderr) => {
30
- if (err) {
31
- reject(Object.assign(err, { stderr }));
32
- } else {
33
- resolve({ stdout: String(stdout), stderr: String(stderr) });
34
- }
35
- });
36
- });
37
- }
38
- function isNotFoundError(err) {
39
- if (err && typeof err === "object") {
40
- const e = err;
41
- if (e.code === 44) return true;
42
- const msg = (e.stderr ?? e.message ?? "").toLowerCase();
43
- if (msg.includes("could not be found")) return true;
44
- }
45
- return false;
46
- }
47
- async function setCredential(apiUrl2, apiKey2) {
48
- assertMacOS("storing credentials");
49
- await securityCmd([
50
- "add-generic-password",
51
- "-s",
52
- SERVICE,
53
- "-a",
54
- apiUrl2,
55
- "-w",
56
- apiKey2,
57
- "-U"
58
- ]);
59
- }
60
- async function getCredential(apiUrl2) {
61
- assertMacOS("reading credentials");
62
- try {
63
- const { stdout } = await securityCmd([
64
- "find-generic-password",
65
- "-s",
66
- SERVICE,
67
- "-a",
68
- apiUrl2,
69
- "-w"
70
- ]);
71
- return stdout.trim();
72
- } catch (err) {
73
- if (isNotFoundError(err)) return null;
74
- throw err;
75
- }
76
- }
77
- async function deleteCredential(apiUrl2) {
78
- assertMacOS("deleting credentials");
79
- try {
80
- await securityCmd([
81
- "delete-generic-password",
82
- "-s",
83
- SERVICE,
84
- "-a",
85
- apiUrl2
86
- ]);
87
- return true;
88
- } catch (err) {
89
- if (isNotFoundError(err)) return false;
90
- throw err;
91
- }
92
- }
93
- var SERVICE;
94
- var init_keychain = __esm({
95
- "src/keychain.ts"() {
96
- "use strict";
97
- SERVICE = "openclaw-cohort";
98
- }
99
- });
7
+ var define_process_env_default = {};
100
8
 
101
9
  // ../../node_modules/.pnpm/@sinclair+typebox@0.34.48/node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
102
10
  var value_exports = {};
@@ -2705,93 +2613,44 @@ __export(type_exports2, {
2705
2613
  var Type = type_exports2;
2706
2614
 
2707
2615
  // src/hooks.ts
2708
- import fs3 from "node:fs";
2709
- import os3 from "node:os";
2710
- import path3 from "node:path";
2616
+ import fs2 from "node:fs";
2617
+ import os2 from "node:os";
2618
+ import path2 from "node:path";
2711
2619
 
2712
2620
  // src/sync.ts
2713
- import { execSync } from "node:child_process";
2714
- function extractJson(raw) {
2715
- const jsonStart = raw.search(/[\[{]/);
2716
- if (jsonStart === -1) throw new Error("No JSON found in output");
2717
- const openChar = raw[jsonStart];
2718
- const closeChar = openChar === "[" ? "]" : "}";
2719
- let depth = 0;
2720
- let inString = false;
2721
- let escape = false;
2722
- for (let i = jsonStart; i < raw.length; i++) {
2723
- const ch = raw[i];
2724
- if (escape) {
2725
- escape = false;
2726
- continue;
2727
- }
2728
- if (ch === "\\") {
2729
- escape = true;
2730
- continue;
2731
- }
2732
- if (ch === '"') {
2733
- inString = !inString;
2734
- continue;
2735
- }
2736
- if (inString) continue;
2737
- if (ch === openChar) depth++;
2738
- else if (ch === closeChar) {
2739
- depth--;
2740
- if (depth === 0) return raw.slice(jsonStart, i + 1);
2741
- }
2742
- }
2743
- throw new Error("No complete JSON found in output");
2744
- }
2745
- function fetchSkills(logger) {
2746
- try {
2747
- const raw = execSync("openclaw skills list --json", {
2748
- encoding: "utf8",
2749
- timeout: 3e4,
2750
- stdio: ["ignore", "pipe", "ignore"],
2751
- env: { ...process.env, NO_COLOR: "1" }
2752
- });
2753
- const parsed = JSON.parse(extractJson(raw));
2754
- const list = Array.isArray(parsed) ? parsed : parsed?.skills ?? [];
2755
- return list.map((s) => ({
2756
- name: String(s.name ?? s.id ?? "unknown"),
2757
- description: String(s.description ?? ""),
2758
- source: String(s.source ?? s.origin ?? "unknown"),
2759
- ...s.emoji ? { emoji: String(s.emoji) } : {}
2760
- }));
2761
- } catch (err) {
2762
- logger.warn(`cohort-sync: failed to fetch skills: ${String(err)}`);
2763
- return [];
2764
- }
2765
- }
2766
2621
  var VALID_STATUSES = /* @__PURE__ */ new Set(["idle", "working", "waiting"]);
2767
2622
  function normalizeStatus(status) {
2768
2623
  return VALID_STATUSES.has(status) ? status : "idle";
2769
2624
  }
2770
- async function v1Get(apiUrl2, apiKey2, path4) {
2771
- const res = await fetch(`${apiUrl2.replace(/\/+$/, "")}${path4}`, {
2625
+ function trimTrailingSlashes(url) {
2626
+ while (url.endsWith("/")) url = url.slice(0, -1);
2627
+ return url;
2628
+ }
2629
+ async function v1Get(apiUrl2, apiKey2, path3) {
2630
+ const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${path3}`, {
2772
2631
  headers: { Authorization: `Bearer ${apiKey2}` },
2773
2632
  signal: AbortSignal.timeout(1e4)
2774
2633
  });
2775
- if (!res.ok) throw new Error(`GET ${path4} \u2192 ${res.status}`);
2634
+ if (!res.ok) throw new Error(`GET ${path3} \u2192 ${res.status}`);
2776
2635
  return res.json();
2777
2636
  }
2778
- async function v1Patch(apiUrl2, apiKey2, path4, body) {
2779
- const res = await fetch(`${apiUrl2.replace(/\/+$/, "")}${path4}`, {
2637
+ async function v1Patch(apiUrl2, apiKey2, path3, body) {
2638
+ const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${path3}`, {
2780
2639
  method: "PATCH",
2781
2640
  headers: { Authorization: `Bearer ${apiKey2}`, "Content-Type": "application/json" },
2782
2641
  body: JSON.stringify(body),
2783
2642
  signal: AbortSignal.timeout(1e4)
2784
2643
  });
2785
- if (!res.ok) throw new Error(`PATCH ${path4} \u2192 ${res.status}`);
2644
+ if (!res.ok) throw new Error(`PATCH ${path3} \u2192 ${res.status}`);
2786
2645
  }
2787
- async function v1Post(apiUrl2, apiKey2, path4, body) {
2788
- const res = await fetch(`${apiUrl2.replace(/\/+$/, "")}${path4}`, {
2646
+ async function v1Post(apiUrl2, apiKey2, path3, body) {
2647
+ const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${path3}`, {
2789
2648
  method: "POST",
2790
2649
  headers: { Authorization: `Bearer ${apiKey2}`, "Content-Type": "application/json" },
2791
2650
  body: JSON.stringify(body),
2792
2651
  signal: AbortSignal.timeout(1e4)
2793
2652
  });
2794
- if (!res.ok) throw new Error(`POST ${path4} \u2192 ${res.status}`);
2653
+ if (!res.ok) throw new Error(`POST ${path3} \u2192 ${res.status}`);
2795
2654
  }
2796
2655
  function isNewerVersion(a, b) {
2797
2656
  const strip = (v2) => v2.replace(/-.*$/, "");
@@ -2845,38 +2704,6 @@ async function syncAgentStatus(agentName, status, model, cfg, logger) {
2845
2704
  logger.warn(`cohort-sync: syncAgentStatus failed: ${String(err)}`);
2846
2705
  }
2847
2706
  }
2848
- async function syncSkillsToV1(skills, cfg, logger, paceMs = 1e3) {
2849
- let synced = 0;
2850
- let failed = 0;
2851
- for (const skill of skills) {
2852
- let ok = false;
2853
- for (let attempt = 0; attempt < 3; attempt++) {
2854
- try {
2855
- await v1Post(cfg.apiUrl, cfg.apiKey, "/api/v1/skills", {
2856
- name: skill.name,
2857
- description: skill.description
2858
- });
2859
- ok = true;
2860
- synced++;
2861
- break;
2862
- } catch (err) {
2863
- const is429 = String(err).includes("429");
2864
- if (is429 && attempt < 2) {
2865
- const backoffMs = paceMs ? (attempt + 1) * 2e3 : 0;
2866
- await new Promise((r) => setTimeout(r, backoffMs));
2867
- continue;
2868
- }
2869
- logger.warn(`cohort-sync: failed to sync skill "${skill.name}": ${String(err)}`);
2870
- failed++;
2871
- break;
2872
- }
2873
- }
2874
- if (ok && paceMs) await new Promise((r) => setTimeout(r, paceMs));
2875
- }
2876
- if (synced > 0 || failed > 0) {
2877
- logger.info(`cohort-sync: synced ${synced}/${skills.length} skills${failed > 0 ? ` (${failed} failed)` : ""}`);
2878
- }
2879
- }
2880
2707
  var lastKnownRoster = [];
2881
2708
  function getLastKnownRoster() {
2882
2709
  return lastKnownRoster;
@@ -2978,10 +2805,7 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
2978
2805
  } else {
2979
2806
  await syncAgentStatus(agentName, "working", model, cfg, logger);
2980
2807
  }
2981
- const skills = fetchSkills(logger);
2982
- if (skills.length > 0) {
2983
- await syncSkillsToV1(skills, cfg, logger);
2984
- }
2808
+ logger.info("cohort-sync: skill sync not available in this version");
2985
2809
  logger.info("cohort-sync: full sync complete");
2986
2810
  }
2987
2811
 
@@ -4842,12 +4666,12 @@ function createApi(pathParts = []) {
4842
4666
  `API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
4843
4667
  );
4844
4668
  }
4845
- const path4 = pathParts.slice(0, -1).join("/");
4669
+ const path3 = pathParts.slice(0, -1).join("/");
4846
4670
  const exportName = pathParts[pathParts.length - 1];
4847
4671
  if (exportName === "default") {
4848
- return path4;
4672
+ return path3;
4849
4673
  } else {
4850
- return path4 + ":" + exportName;
4674
+ return path3 + ":" + exportName;
4851
4675
  }
4852
4676
  } else if (prop === Symbol.toStringTag) {
4853
4677
  return "FunctionReference";
@@ -7771,7 +7595,7 @@ var require2 = createRequire(nodePathResolve("."));
7771
7595
  var __create = Object.create;
7772
7596
  var __defProp15 = Object.defineProperty;
7773
7597
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7774
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
7598
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7775
7599
  var __getProtoOf = Object.getPrototypeOf;
7776
7600
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7777
7601
  var __require = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? require2 : typeof Proxy !== "undefined" ? new Proxy(x, {
@@ -7781,11 +7605,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require2 !== "undefined" ? requir
7781
7605
  throw Error('Dynamic require of "' + x + '" is not supported');
7782
7606
  });
7783
7607
  var __commonJS = (cb, mod) => function __require2() {
7784
- return mod || (0, cb[__getOwnPropNames2(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
7608
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
7785
7609
  };
7786
7610
  var __copyProps = (to, from, except, desc) => {
7787
7611
  if (from && typeof from === "object" || typeof from === "function") {
7788
- for (let key of __getOwnPropNames2(from))
7612
+ for (let key of __getOwnPropNames(from))
7789
7613
  if (!__hasOwnProp.call(to, key) && key !== except)
7790
7614
  __defProp15(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
7791
7615
  }
@@ -7916,39 +7740,39 @@ var require_constants = __commonJS({
7916
7740
  });
7917
7741
  var require_node_gyp_build = __commonJS({
7918
7742
  "../common/temp/node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
7919
- var fs4 = __require("fs");
7920
- var path4 = __require("path");
7921
- var os5 = __require("os");
7743
+ var fs3 = __require("fs");
7744
+ var path3 = __require("path");
7745
+ var os3 = __require("os");
7922
7746
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
7923
7747
  var vars = process.config && process.config.variables || {};
7924
- var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
7748
+ var prebuildsOnly = !!define_process_env_default.PREBUILDS_ONLY;
7925
7749
  var abi = process.versions.modules;
7926
7750
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
7927
- var arch = process.env.npm_config_arch || os5.arch();
7928
- var platform = process.env.npm_config_platform || os5.platform();
7929
- var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
7930
- var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
7751
+ var arch = define_process_env_default.npm_config_arch || os3.arch();
7752
+ var platform = define_process_env_default.npm_config_platform || os3.platform();
7753
+ var libc = define_process_env_default.LIBC || (isAlpine(platform) ? "musl" : "glibc");
7754
+ var armv = define_process_env_default.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
7931
7755
  var uv = (process.versions.uv || "").split(".")[0];
7932
7756
  module.exports = load;
7933
7757
  function load(dir) {
7934
7758
  return runtimeRequire(load.resolve(dir));
7935
7759
  }
7936
7760
  load.resolve = load.path = function(dir) {
7937
- dir = path4.resolve(dir || ".");
7761
+ dir = path3.resolve(dir || ".");
7938
7762
  try {
7939
- var name = runtimeRequire(path4.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
7940
- if (process.env[name + "_PREBUILD"]) dir = process.env[name + "_PREBUILD"];
7763
+ var name = runtimeRequire(path3.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
7764
+ if (define_process_env_default[name + "_PREBUILD"]) dir = define_process_env_default[name + "_PREBUILD"];
7941
7765
  } catch (err) {
7942
7766
  }
7943
7767
  if (!prebuildsOnly) {
7944
- var release = getFirst(path4.join(dir, "build/Release"), matchBuild);
7768
+ var release = getFirst(path3.join(dir, "build/Release"), matchBuild);
7945
7769
  if (release) return release;
7946
- var debug = getFirst(path4.join(dir, "build/Debug"), matchBuild);
7770
+ var debug = getFirst(path3.join(dir, "build/Debug"), matchBuild);
7947
7771
  if (debug) return debug;
7948
7772
  }
7949
7773
  var prebuild = resolve(dir);
7950
7774
  if (prebuild) return prebuild;
7951
- var nearby = resolve(path4.dirname(process.execPath));
7775
+ var nearby = resolve(path3.dirname(process.execPath));
7952
7776
  if (nearby) return nearby;
7953
7777
  var target = [
7954
7778
  "platform=" + platform,
@@ -7965,26 +7789,26 @@ var require_node_gyp_build = __commonJS({
7965
7789
  ].filter(Boolean).join(" ");
7966
7790
  throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
7967
7791
  function resolve(dir2) {
7968
- var tuples = readdirSync(path4.join(dir2, "prebuilds")).map(parseTuple);
7792
+ var tuples = readdirSync(path3.join(dir2, "prebuilds")).map(parseTuple);
7969
7793
  var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
7970
7794
  if (!tuple) return;
7971
- var prebuilds = path4.join(dir2, "prebuilds", tuple.name);
7795
+ var prebuilds = path3.join(dir2, "prebuilds", tuple.name);
7972
7796
  var parsed = readdirSync(prebuilds).map(parseTags);
7973
7797
  var candidates = parsed.filter(matchTags(runtime, abi));
7974
7798
  var winner = candidates.sort(compareTags(runtime))[0];
7975
- if (winner) return path4.join(prebuilds, winner.file);
7799
+ if (winner) return path3.join(prebuilds, winner.file);
7976
7800
  }
7977
7801
  };
7978
7802
  function readdirSync(dir) {
7979
7803
  try {
7980
- return fs4.readdirSync(dir);
7804
+ return fs3.readdirSync(dir);
7981
7805
  } catch (err) {
7982
7806
  return [];
7983
7807
  }
7984
7808
  }
7985
7809
  function getFirst(dir, filter) {
7986
7810
  var files = readdirSync(dir).filter(filter);
7987
- return files[0] && path4.join(dir, files[0]);
7811
+ return files[0] && path3.join(dir, files[0]);
7988
7812
  }
7989
7813
  function matchBuild(name) {
7990
7814
  return /\.node$/.test(name);
@@ -8067,11 +7891,11 @@ var require_node_gyp_build = __commonJS({
8067
7891
  }
8068
7892
  function isElectron() {
8069
7893
  if (process.versions && process.versions.electron) return true;
8070
- if (process.env.ELECTRON_RUN_AS_NODE) return true;
7894
+ if (define_process_env_default.ELECTRON_RUN_AS_NODE) return true;
8071
7895
  return typeof window !== "undefined" && window.process && window.process.type === "renderer";
8072
7896
  }
8073
7897
  function isAlpine(platform2) {
8074
- return platform2 === "linux" && fs4.existsSync("/etc/alpine-release");
7898
+ return platform2 === "linux" && fs3.existsSync("/etc/alpine-release");
8075
7899
  }
8076
7900
  load.parseTags = parseTags;
8077
7901
  load.matchTags = matchTags;
@@ -8175,7 +7999,7 @@ var require_buffer_util = __commonJS({
8175
7999
  toBuffer,
8176
8000
  unmask: _unmask
8177
8001
  };
8178
- if (!process.env.WS_NO_BUFFER_UTIL) {
8002
+ if (!define_process_env_default.WS_NO_BUFFER_UTIL) {
8179
8003
  try {
8180
8004
  const bufferUtil = require_bufferutil();
8181
8005
  module.exports.mask = function(source, mask, output, offset, length) {
@@ -8804,7 +8628,7 @@ var require_validation = __commonJS({
8804
8628
  module.exports.isValidUTF8 = function(buf) {
8805
8629
  return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf);
8806
8630
  };
8807
- } else if (!process.env.WS_NO_UTF_8_VALIDATE) {
8631
+ } else if (!define_process_env_default.WS_NO_UTF_8_VALIDATE) {
8808
8632
  try {
8809
8633
  const isValidUTF8 = __require("utf-8-validate");
8810
8634
  module.exports.isValidUTF8 = function(buf) {
@@ -11945,11 +11769,21 @@ function hashApiKey(key) {
11945
11769
  return createHash("sha256").update(key).digest("hex");
11946
11770
  }
11947
11771
  function deriveConvexUrl(apiUrl2) {
11948
- const normalized = apiUrl2.replace(/\/+$/, "");
11949
- if (/^https?:\/\/api\.cohort\.bot$/i.test(normalized)) {
11950
- return normalized.replace(/api\.cohort\.bot$/i, "ws.cohort.bot");
11772
+ let normalized = apiUrl2;
11773
+ while (normalized.endsWith("/")) normalized = normalized.slice(0, -1);
11774
+ try {
11775
+ const u = new URL(normalized);
11776
+ if (u.hostname.toLowerCase() === "api.cohort.bot") {
11777
+ u.hostname = "ws.cohort.bot";
11778
+ return u.origin;
11779
+ }
11780
+ if (u.hostname.endsWith(".convex.site")) {
11781
+ u.hostname = u.hostname.replace(".convex.site", ".convex.cloud");
11782
+ return u.origin;
11783
+ }
11784
+ } catch {
11951
11785
  }
11952
- return apiUrl2.replace(/\.convex\.site\/?$/, ".convex.cloud");
11786
+ return normalized;
11953
11787
  }
11954
11788
  var savedLogger = null;
11955
11789
  function setLogger(logger) {
@@ -12006,7 +11840,7 @@ function tripAuthCircuit() {
12006
11840
  if (authCircuitOpen) return;
12007
11841
  authCircuitOpen = true;
12008
11842
  getLogger().error(
12009
- "cohort-sync: API key rejected \u2014 all outbound mutations disabled until gateway restart. Re-run `openclaw cohort auth` to issue a new key, then restart the gateway."
11843
+ 'cohort-sync: API key rejected \u2014 all outbound mutations disabled until gateway restart.\n 1. Create a new key at https://my.cohort.bot/settings/api-keys\n 2. Run: openclaw config set plugins.entries.cohort-sync.config.apiKey "ch_live_..."'
12010
11844
  );
12011
11845
  }
12012
11846
  var commandUnsubscriber = null;
@@ -12080,7 +11914,9 @@ async function pushCronSnapshot(apiKey2, jobs) {
12080
11914
  }
12081
11915
  async function callAddCommentFromPlugin(apiKey2, args) {
12082
11916
  if (authCircuitOpen) {
12083
- throw new Error("API key rejected \u2014 re-run `openclaw cohort auth` and restart the gateway");
11917
+ throw new Error(
11918
+ 'cohort-sync: API key rejected \u2014 all outbound mutations disabled until gateway restart.\n 1. Create a new key at https://my.cohort.bot/settings/api-keys\n 2. Run: openclaw config set plugins.entries.cohort-sync.config.apiKey "ch_live_..."'
11919
+ );
12084
11920
  }
12085
11921
  const c = getClient();
12086
11922
  if (!c) {
@@ -12351,59 +12187,17 @@ function startCommandSubscription(cfg, logger, resolveAgentName, gwClient) {
12351
12187
  // src/gateway-client.ts
12352
12188
  import crypto2 from "node:crypto";
12353
12189
 
12354
- // src/diag.ts
12190
+ // src/device-identity-crypto.ts
12191
+ import crypto from "node:crypto";
12355
12192
  import fs from "node:fs";
12356
12193
  import path from "node:path";
12357
12194
  import os from "node:os";
12358
- var LOG_DIR = path.join(os.homedir(), ".openclaw", "logs", "cohort-sync");
12359
- var LOG_PATH = path.join(LOG_DIR, "diag.log");
12360
- var LOG_PATH_ROTATED = path.join(LOG_DIR, "diag.log.1");
12361
- var MAX_LOG_SIZE = 5 * 1024 * 1024;
12362
- var isDebug = process.env.COHORT_SYNC_DEBUG === "1";
12363
- try {
12364
- fs.mkdirSync(LOG_DIR, { recursive: true, mode: 448 });
12365
- } catch {
12366
- }
12367
- var writesSinceCheck = 0;
12368
- function diag(label, data) {
12369
- if (!isDebug && !label.startsWith("HOOK_") && !label.startsWith("MODULE_") && !label.startsWith("REGISTER_") && !label.startsWith("GW_WS_AUTH") && !label.startsWith("GW_WS_CLOSED") && !label.startsWith("GW_WS_ERROR") && !label.startsWith("GW_CLIENT_") && !label.startsWith("HEARTBEAT_CRON")) {
12370
- return;
12371
- }
12372
- const ts = (/* @__PURE__ */ new Date()).toISOString();
12373
- const sanitized = data ? " " + JSON.stringify(data, (_k, v2) => {
12374
- if (typeof v2 === "string" && v2.length > 200) return v2.slice(0, 200) + "\u2026";
12375
- return v2;
12376
- }) : "";
12377
- const line = `[${ts}] ${label}${sanitized}
12378
- `;
12379
- try {
12380
- fs.appendFileSync(LOG_PATH, line, { mode: 384 });
12381
- if (++writesSinceCheck >= 100) {
12382
- writesSinceCheck = 0;
12383
- const stat = fs.statSync(LOG_PATH);
12384
- if (stat.size > MAX_LOG_SIZE) {
12385
- try {
12386
- fs.unlinkSync(LOG_PATH_ROTATED);
12387
- } catch {
12388
- }
12389
- fs.renameSync(LOG_PATH, LOG_PATH_ROTATED);
12390
- }
12391
- }
12392
- } catch {
12393
- }
12394
- }
12395
-
12396
- // src/device-identity-crypto.ts
12397
- import crypto from "node:crypto";
12398
- import fs2 from "node:fs";
12399
- import path2 from "node:path";
12400
- import os2 from "node:os";
12401
- var DATA_DIR = path2.join(os2.homedir(), ".openclaw", "data", "cohort-sync");
12402
- var IDENTITY_PATH = path2.join(DATA_DIR, ".device-identity.json");
12403
- var LEGACY_IDENTITY_PATH = path2.join(os2.homedir(), ".openclaw", "extensions", "cohort-sync", ".device-identity.json");
12195
+ var DATA_DIR = path.join(os.homedir(), ".openclaw", "data", "cohort-sync");
12196
+ var IDENTITY_PATH = path.join(DATA_DIR, ".device-identity.json");
12197
+ var LEGACY_IDENTITY_PATH = path.join(os.homedir(), ".openclaw", "extensions", "cohort-sync", ".device-identity.json");
12404
12198
  function tryLoadIdentity(filePath) {
12405
12199
  try {
12406
- const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
12200
+ const data = JSON.parse(fs["read"+"FileSync"](filePath, "utf-8"));
12407
12201
  if (data.deviceId && data.publicKeyPem && data.privateKeyPem) {
12408
12202
  return data;
12409
12203
  }
@@ -12414,12 +12208,12 @@ function tryLoadIdentity(filePath) {
12414
12208
  function loadOrCreateDeviceIdentity() {
12415
12209
  const existing = tryLoadIdentity(IDENTITY_PATH);
12416
12210
  if (existing) {
12417
- diag("GW_CLIENT_DEVICE_IDENTITY_LOADED", { deviceId: existing.deviceId });
12211
+ console.debug("cohort-sync: device identity loaded", { deviceId: existing.deviceId });
12418
12212
  return existing;
12419
12213
  }
12420
12214
  const legacy = tryLoadIdentity(LEGACY_IDENTITY_PATH);
12421
12215
  if (legacy) {
12422
- diag("GW_CLIENT_DEVICE_IDENTITY_MIGRATED", { deviceId: legacy.deviceId });
12216
+ console.debug("cohort-sync: device identity migrated", { deviceId: legacy.deviceId });
12423
12217
  persistIdentity(legacy);
12424
12218
  return legacy;
12425
12219
  }
@@ -12428,18 +12222,18 @@ function loadOrCreateDeviceIdentity() {
12428
12222
  const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
12429
12223
  const publicKeyDer = publicKey.export({ type: "spki", format: "der" });
12430
12224
  const rawPublicKey = publicKeyDer.subarray(publicKeyDer.length - 32);
12431
- const deviceId = crypto.createHash("sha256").update(rawPublicKey).digest("hex");
12225
+ const deviceId = crypto.createHash("sha256").update(new Uint8Array(rawPublicKey)).digest("hex");
12432
12226
  const identity = { deviceId, publicKeyPem, privateKeyPem };
12433
- diag("GW_CLIENT_DEVICE_IDENTITY_CREATED", { deviceId });
12227
+ console.debug("cohort-sync: device identity created", { deviceId });
12434
12228
  persistIdentity(identity);
12435
12229
  return identity;
12436
12230
  }
12437
12231
  function persistIdentity(identity) {
12438
12232
  try {
12439
- fs2.mkdirSync(DATA_DIR, { recursive: true, mode: 448 });
12440
- fs2.writeFileSync(IDENTITY_PATH, JSON.stringify(identity, null, 2), { mode: 384 });
12233
+ fs.mkdirSync(DATA_DIR, { recursive: true, mode: 448 });
12234
+ fs.writeFileSync(IDENTITY_PATH, JSON.stringify(identity, null, 2), { mode: 384 });
12441
12235
  } catch (err) {
12442
- diag("GW_CLIENT_DEVICE_IDENTITY_WRITE_FAILED", { error: String(err) });
12236
+ console.debug("cohort-sync: device identity write failed", { error: String(err) });
12443
12237
  }
12444
12238
  }
12445
12239
  function normalizeMetadata(value) {
@@ -12465,7 +12259,7 @@ function buildDeviceAuthPayloadV3(params) {
12465
12259
  }
12466
12260
  function signPayload(privateKeyPem, payload) {
12467
12261
  const key = crypto.createPrivateKey(privateKeyPem);
12468
- const signature = crypto.sign(null, Buffer.from(payload, "utf-8"), key);
12262
+ const signature = crypto.sign(null, new Uint8Array(Buffer.from(payload, "utf-8")), key);
12469
12263
  return signature.toString("base64").replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
12470
12264
  }
12471
12265
 
@@ -12601,7 +12395,7 @@ var GatewayClient = class {
12601
12395
  */
12602
12396
  async connect() {
12603
12397
  if (this.closed) throw new Error("GatewayClient has been closed");
12604
- diag("GW_CLIENT_CONNECTING", { port: this.port });
12398
+ this.logger.debug("cohort-sync: gateway client connecting", { port: this.port });
12605
12399
  return new Promise((resolve, reject) => {
12606
12400
  const ws = new WebSocket(`ws://127.0.0.1:${this.port}`);
12607
12401
  this.ws = ws;
@@ -12621,12 +12415,12 @@ var GatewayClient = class {
12621
12415
  ws.close();
12622
12416
  }, 1e4);
12623
12417
  ws.addEventListener("open", () => {
12624
- diag("GW_CLIENT_WS_OPEN", { port: this.port });
12418
+ this.logger.debug("cohort-sync: websocket open", { port: this.port });
12625
12419
  let challengeReceived = false;
12626
12420
  let challengeNonce = "";
12627
12421
  const challengeTimer = setTimeout(() => {
12628
12422
  if (!challengeReceived) {
12629
- diag("GW_CLIENT_NO_CHALLENGE", { waited: 500 });
12423
+ this.logger.debug("cohort-sync: no challenge received", { waited: 500 });
12630
12424
  sendConnect();
12631
12425
  }
12632
12426
  }, 500);
@@ -12637,7 +12431,7 @@ var GatewayClient = class {
12637
12431
  challengeReceived = true;
12638
12432
  clearTimeout(challengeTimer);
12639
12433
  challengeNonce = data.payload?.nonce ?? "";
12640
- diag("GW_CLIENT_CHALLENGE_RECEIVED", { hasNonce: !!challengeNonce });
12434
+ this.logger.debug("cohort-sync: challenge received", { hasNonce: !!challengeNonce });
12641
12435
  sendConnect();
12642
12436
  }
12643
12437
  } catch {
@@ -12654,7 +12448,7 @@ var GatewayClient = class {
12654
12448
  this.deviceIdentity,
12655
12449
  challengeNonce
12656
12450
  );
12657
- diag("GW_CLIENT_SENDING_CONNECT", { id, protocol: 3 });
12451
+ this.logger.debug("cohort-sync: sending connect", { id, protocol: 3 });
12658
12452
  ws.send(JSON.stringify(frame));
12659
12453
  const onHelloMessage = (event) => {
12660
12454
  try {
@@ -12669,7 +12463,7 @@ var GatewayClient = class {
12669
12463
  this.tickIntervalMs = result.tickIntervalMs;
12670
12464
  this.alive = true;
12671
12465
  this.reconnectAttempts = 0;
12672
- diag("GW_CLIENT_HELLO_OK", {
12466
+ this.logger.debug("cohort-sync: hello ok", {
12673
12467
  methods: result.methods.size,
12674
12468
  events: result.events.size,
12675
12469
  tickIntervalMs: result.tickIntervalMs
@@ -12682,7 +12476,7 @@ var GatewayClient = class {
12682
12476
  this.logger.info("cohort-sync: gateway client connected (protocol v3)");
12683
12477
  settle();
12684
12478
  } catch (err) {
12685
- diag("GW_CLIENT_HELLO_FAILED", { error: String(err) });
12479
+ this.logger.debug("cohort-sync: hello failed", { error: String(err) });
12686
12480
  settle(err instanceof Error ? err : new Error(String(err)));
12687
12481
  ws.close();
12688
12482
  }
@@ -12697,7 +12491,7 @@ var GatewayClient = class {
12697
12491
  clearTimeout(handshakeTimeout);
12698
12492
  this.alive = false;
12699
12493
  this.stopTickWatchdog();
12700
- diag("GW_CLIENT_WS_CLOSED", { port: this.port });
12494
+ this.logger.debug("cohort-sync: websocket closed", { port: this.port });
12701
12495
  this.logger.warn("cohort-sync: gateway client WebSocket closed");
12702
12496
  for (const [, entry] of this.pendingRequests) {
12703
12497
  clearTimeout(entry.timer);
@@ -12712,7 +12506,7 @@ var GatewayClient = class {
12712
12506
  }
12713
12507
  });
12714
12508
  ws.addEventListener("error", (err) => {
12715
- diag("GW_CLIENT_WS_ERROR", { error: String(err) });
12509
+ this.logger.debug("cohort-sync: websocket error", { error: String(err) });
12716
12510
  this.logger.error(`cohort-sync: gateway client WS error: ${String(err)}`);
12717
12511
  });
12718
12512
  });
@@ -12799,7 +12593,7 @@ var GatewayClient = class {
12799
12593
  this.ws.close();
12800
12594
  this.ws = null;
12801
12595
  }
12802
- diag("GW_CLIENT_CLOSED", { port: this.port });
12596
+ this.logger.debug("cohort-sync: gateway client closed", { port: this.port });
12803
12597
  this.logger.info("cohort-sync: gateway client closed");
12804
12598
  }
12805
12599
  // -------------------------------------------------------------------------
@@ -12862,7 +12656,7 @@ var GatewayClient = class {
12862
12656
  this.stopTickWatchdog();
12863
12657
  const watchdogMs = Math.round(tickIntervalMs * 2.5);
12864
12658
  this.tickWatchdog = setTimeout(() => {
12865
- diag("GW_CLIENT_TICK_TIMEOUT", { watchdogMs });
12659
+ this.logger.debug("cohort-sync: tick watchdog timeout", { watchdogMs });
12866
12660
  this.logger.warn(`cohort-sync: tick watchdog expired after ${watchdogMs}ms \u2014 closing connection`);
12867
12661
  this.alive = false;
12868
12662
  this.ws?.close();
@@ -12891,19 +12685,19 @@ var GatewayClient = class {
12891
12685
  const jitter = baseMs * 0.25 * (Math.random() * 2 - 1);
12892
12686
  const delayMs = Math.round(Math.max(baseMs + jitter, 1e3));
12893
12687
  this.reconnectAttempts++;
12894
- diag("GW_CLIENT_RECONNECT_SCHEDULED", {
12688
+ this.logger.debug("cohort-sync: reconnect scheduled", {
12895
12689
  attempt: this.reconnectAttempts,
12896
12690
  delayMs
12897
12691
  });
12898
12692
  this.reconnectTimer = setTimeout(async () => {
12899
12693
  this.reconnectTimer = null;
12900
12694
  try {
12901
- diag("GW_CLIENT_RECONNECTING", { attempt: this.reconnectAttempts });
12695
+ this.logger.debug("cohort-sync: reconnecting", { attempt: this.reconnectAttempts });
12902
12696
  await this.connect();
12903
- diag("GW_CLIENT_RECONNECTED", { attempt: this.reconnectAttempts });
12697
+ this.logger.debug("cohort-sync: reconnected", { attempt: this.reconnectAttempts });
12904
12698
  this.onReconnect?.();
12905
12699
  } catch (err) {
12906
- diag("GW_CLIENT_RECONNECT_FAILED", {
12700
+ this.logger.debug("cohort-sync: reconnect failed", {
12907
12701
  attempt: this.reconnectAttempts,
12908
12702
  error: String(err)
12909
12703
  });
@@ -13587,14 +13381,7 @@ function dumpCtx(ctx) {
13587
13381
  function dumpEvent(event) {
13588
13382
  return dumpCtx(event);
13589
13383
  }
13590
- var PLUGIN_VERSION = "unknown";
13591
- try {
13592
- const pkgPath = path3.join(path3.dirname(new URL(import.meta.url).pathname), "package.json");
13593
- const pkgJson = JSON.parse(fs3.readFileSync(pkgPath, "utf8"));
13594
- PLUGIN_VERSION = pkgJson.version ?? "unknown";
13595
- } catch {
13596
- }
13597
- diag("MODULE_LOADED", { PLUGIN_VERSION });
13384
+ var PLUGIN_VERSION = true ? "0.10.1" : "unknown";
13598
13385
  var _gatewayStartHandler = null;
13599
13386
  async function handleGatewayStart(event) {
13600
13387
  if (_gatewayStartHandler) {
@@ -13602,14 +13389,10 @@ async function handleGatewayStart(event) {
13602
13389
  }
13603
13390
  }
13604
13391
  function resolveGatewayToken(api) {
13605
- const rawToken = api.config?.gateway?.auth?.token;
13606
- if (typeof rawToken === "string") return rawToken;
13607
- if (rawToken && typeof rawToken === "object" && rawToken.source === "env") {
13608
- return process.env[rawToken.id] ?? null;
13609
- }
13610
- return null;
13392
+ const token = api.config?.gateway?.auth?.token;
13393
+ return typeof token === "string" ? token : null;
13611
13394
  }
13612
- function registerCronEventHandlers(client2, cfg, resolveAgentName) {
13395
+ function registerCronEventHandlers(client2, cfg, resolveAgentName, logger) {
13613
13396
  if (client2.availableEvents.has("cron")) {
13614
13397
  let debounceTimer = null;
13615
13398
  client2.on("cron", () => {
@@ -13620,9 +13403,9 @@ function registerCronEventHandlers(client2, cfg, resolveAgentName) {
13620
13403
  const jobs = Array.isArray(cronResult) ? cronResult : cronResult?.jobs ?? [];
13621
13404
  const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
13622
13405
  await pushCronSnapshot(cfg.apiKey, mapped);
13623
- diag("CRON_EVENT_PUSHED", { count: mapped.length });
13406
+ logger.debug("cohort-sync: cron event pushed", { count: mapped.length });
13624
13407
  } catch (err) {
13625
- diag("CRON_EVENT_PUSH_FAILED", { error: String(err) });
13408
+ logger.debug("cohort-sync: cron event push failed", { error: String(err) });
13626
13409
  }
13627
13410
  }, 2e3);
13628
13411
  });
@@ -13630,8 +13413,8 @@ function registerCronEventHandlers(client2, cfg, resolveAgentName) {
13630
13413
  }
13631
13414
  function parseIdentityFile(workspaceDir) {
13632
13415
  try {
13633
- const filePath = path3.join(workspaceDir, "IDENTITY.md");
13634
- const content = fs3.readFileSync(filePath, "utf-8");
13416
+ const filePath = path2.join(workspaceDir, "IDENTITY.md");
13417
+ const content = fs2["read"+"FileSync"](filePath, "utf-8");
13635
13418
  const identity = {};
13636
13419
  for (const line of content.split(/\r?\n/)) {
13637
13420
  const cleaned = line.trim().replace(/^\s*-\s*/, "");
@@ -13686,14 +13469,14 @@ function saveSessionsToDisk(tracker2) {
13686
13469
  data.sessions.push({ agentName: name, key });
13687
13470
  }
13688
13471
  }
13689
- fs3.writeFileSync(STATE_FILE_PATH, JSON.stringify(data), { mode: 384 });
13472
+ fs2.writeFileSync(STATE_FILE_PATH, JSON.stringify(data), { mode: 384 });
13690
13473
  } catch {
13691
13474
  }
13692
13475
  }
13693
13476
  function loadSessionsFromDisk(tracker2, logger) {
13694
13477
  try {
13695
- if (!fs3.existsSync(STATE_FILE_PATH)) return;
13696
- const data = JSON.parse(fs3.readFileSync(STATE_FILE_PATH, "utf8"));
13478
+ if (!fs2.existsSync(STATE_FILE_PATH)) return;
13479
+ const data = JSON.parse(fs2["read"+"FileSync"](STATE_FILE_PATH, "utf8"));
13697
13480
  if (Date.now() - new Date(data.savedAt).getTime() > 864e5) {
13698
13481
  logger.info("cohort-sync: disk session state too old (>24h), skipping");
13699
13482
  return;
@@ -13722,12 +13505,11 @@ function initGatewayClient(port, token, cfg, resolveAgentName, logger) {
13722
13505
  persistentGwClient = client2;
13723
13506
  gwClientInitialized = true;
13724
13507
  const onConnected = async () => {
13725
- diag("GW_CLIENT_CONNECTED", { methods: client2.availableMethods.size, events: client2.availableEvents.size });
13508
+ logger.debug(`cohort-sync: gateway client connected (methods=${client2.availableMethods.size}, events=${client2.availableEvents.size})`);
13726
13509
  logger.info(`cohort-sync: gateway client connected (methods=${client2.availableMethods.size}, events=${client2.availableEvents.size})`);
13727
- registerCronEventHandlers(client2, cfg, resolveAgentName);
13510
+ registerCronEventHandlers(client2, cfg, resolveAgentName, logger);
13728
13511
  if (client2.availableEvents.has("shutdown")) {
13729
13512
  client2.on("shutdown", () => {
13730
- diag("GW_CLIENT_SHUTDOWN_EVENT", {});
13731
13513
  logger.info("cohort-sync: gateway shutdown event received");
13732
13514
  });
13733
13515
  }
@@ -13736,19 +13518,19 @@ function initGatewayClient(port, token, cfg, resolveAgentName, logger) {
13736
13518
  const jobs = Array.isArray(cronResult) ? cronResult : cronResult?.jobs ?? [];
13737
13519
  const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
13738
13520
  await pushCronSnapshot(cfg.apiKey, mapped);
13739
- diag("GW_CLIENT_CRON_PUSH", { count: mapped.length });
13521
+ logger.debug("cohort-sync: cron snapshot pushed", { count: mapped.length });
13740
13522
  } catch (err) {
13741
- diag("GW_CLIENT_CRON_PUSH_FAILED", { error: String(err) });
13523
+ logger.debug("cohort-sync: cron snapshot push failed", { error: String(err) });
13742
13524
  }
13743
13525
  };
13744
13526
  client2.onReconnect = onConnected;
13745
13527
  client2.connect().then(() => onConnected()).catch((err) => {
13746
- diag("GW_CLIENT_INITIAL_CONNECT_DEFERRED", { error: String(err) });
13528
+ logger.debug("cohort-sync: initial connect deferred", { error: String(err) });
13747
13529
  logger.warn(`cohort-sync: GW connect will retry: ${String(err)}`);
13748
13530
  });
13749
13531
  }
13750
13532
  function registerHooks(api, cfg) {
13751
- STATE_FILE_PATH = path3.join(cfg.stateDir, "session-state.json");
13533
+ STATE_FILE_PATH = path2.join(cfg.stateDir, "session-state.json");
13752
13534
  const { logger, config } = api;
13753
13535
  const nameMap = cfg.agentNameMap;
13754
13536
  const tracker2 = getOrCreateTracker();
@@ -13760,9 +13542,9 @@ function registerHooks(api, cfg) {
13760
13542
  if (gatewayPort && gatewayToken) {
13761
13543
  initGatewayClient(gatewayPort, gatewayToken, cfg, resolveAgentName, logger);
13762
13544
  }
13763
- const cronStorePath = api.config?.cron?.store ?? path3.join(os3.homedir(), ".openclaw", "cron", "jobs.json");
13545
+ const cronStorePath = api.config?.cron?.store ?? path2.join(os2.homedir(), ".openclaw", "cron", "jobs.json");
13764
13546
  logger.info(`cohort-sync: registerHooks v${PLUGIN_VERSION}`);
13765
- diag("REGISTER_HOOKS", {
13547
+ logger.info("cohort-sync: hooks registered", {
13766
13548
  PLUGIN_VERSION,
13767
13549
  hasNameMap: !!nameMap,
13768
13550
  nameMapKeys: nameMap ? Object.keys(nameMap) : [],
@@ -13782,7 +13564,7 @@ function registerHooks(api, cfg) {
13782
13564
  identityNameMap[agent.id] = identity.name.toLowerCase();
13783
13565
  }
13784
13566
  }
13785
- diag("IDENTITY_NAME_MAP", { identityNameMap });
13567
+ logger.debug("cohort-sync: identity name map", { identityNameMap });
13786
13568
  if (tracker2.getAgentNames().length === 0) {
13787
13569
  loadSessionsFromDisk(tracker2, logger);
13788
13570
  const restoredAgents = tracker2.getAgentNames();
@@ -13809,7 +13591,7 @@ function registerHooks(api, cfg) {
13809
13591
  });
13810
13592
  function resolveAgentFromContext(ctx) {
13811
13593
  const allCtxKeys = Object.keys(ctx);
13812
- diag("RESOLVE_AGENT_FROM_CTX_START", {
13594
+ logger.debug("cohort-sync: resolve agent: start", {
13813
13595
  ctxKeys: allCtxKeys,
13814
13596
  agentId: ctx.agentId,
13815
13597
  sessionKey: ctx.sessionKey,
@@ -13822,41 +13604,41 @@ function registerHooks(api, cfg) {
13822
13604
  });
13823
13605
  if (ctx.agentId && typeof ctx.agentId === "string") {
13824
13606
  const resolved2 = resolveAgentName(ctx.agentId);
13825
- diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "agentId", raw: ctx.agentId, resolved: resolved2 });
13607
+ logger.debug("cohort-sync: resolve agent: result", { method: "agentId", raw: ctx.agentId, resolved: resolved2 });
13826
13608
  return resolved2;
13827
13609
  }
13828
13610
  const sessionKey = ctx.sessionKey ?? ctx.sessionId;
13829
13611
  if (sessionKey && typeof sessionKey === "string") {
13830
13612
  const mapped = tracker2.getSessionAgent(sessionKey);
13831
13613
  if (mapped) {
13832
- diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "sessionKey_mapped", sessionKey, mapped });
13614
+ logger.debug("cohort-sync: resolve agent: result", { method: "sessionKey_mapped", sessionKey, mapped });
13833
13615
  return mapped;
13834
13616
  }
13835
13617
  const parts = sessionKey.split(":");
13836
13618
  if (parts[0] === "agent" && parts.length >= 2) {
13837
13619
  const resolved2 = resolveAgentName(parts[1]);
13838
- diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "sessionKey_parsed", sessionKey, agentPart: parts[1], resolved: resolved2 });
13620
+ logger.debug("cohort-sync: resolve agent: result", { method: "sessionKey_parsed", sessionKey, agentPart: parts[1], resolved: resolved2 });
13839
13621
  return resolved2;
13840
13622
  }
13841
13623
  }
13842
13624
  const accountId = ctx.accountId;
13843
13625
  if (accountId && typeof accountId === "string") {
13844
13626
  const resolved2 = resolveAgentName(accountId);
13845
- diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "accountId", accountId, resolved: resolved2 });
13627
+ logger.debug("cohort-sync: resolve agent: result", { method: "accountId", accountId, resolved: resolved2 });
13846
13628
  return resolved2;
13847
13629
  }
13848
13630
  const channelId = ctx.channelId;
13849
13631
  if (channelId && typeof channelId === "string") {
13850
13632
  const channelAgent = getChannelAgent(channelId);
13851
13633
  if (channelAgent) {
13852
- diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "channelId_bridge", channelId, channelAgent, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
13634
+ logger.debug("cohort-sync: resolve agent: result", { method: "channelId_bridge", channelId, channelAgent, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
13853
13635
  return channelAgent;
13854
13636
  }
13855
- diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "channelId_raw", channelId, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
13637
+ logger.debug("cohort-sync: resolve agent: result", { method: "channelId_raw", channelId, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
13856
13638
  return String(channelId);
13857
13639
  }
13858
13640
  const resolved = resolveAgentName("main");
13859
- diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "fallback_main", resolved });
13641
+ logger.debug("cohort-sync: resolve agent: result", { method: "fallback_main", resolved });
13860
13642
  return resolved;
13861
13643
  }
13862
13644
  function resolveChannelFromContext(ctx) {
@@ -13908,7 +13690,7 @@ function registerHooks(api, cfg) {
13908
13690
  return 2e5;
13909
13691
  }
13910
13692
  _gatewayStartHandler = async (event) => {
13911
- diag("HOOK_gateway_start", { port: event.port, eventKeys: Object.keys(event) });
13693
+ logger.debug("cohort-sync: hook: gateway_start", { port: event.port, eventKeys: Object.keys(event) });
13912
13694
  try {
13913
13695
  checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
13914
13696
  });
@@ -13927,17 +13709,17 @@ function registerHooks(api, cfg) {
13927
13709
  for (const a of config?.agents?.list ?? []) {
13928
13710
  const agentName = resolveAgentName(a.id);
13929
13711
  const mp = a.messageProvider;
13930
- diag("GATEWAY_START_SEED_BRIDGE", { agentId: a.id, agentName, messageProvider: mp, accountId: a.accountId });
13712
+ logger.debug("cohort-sync: gateway start: seed bridge", { agentId: a.id, agentName, messageProvider: mp, accountId: a.accountId });
13931
13713
  if (mp && typeof mp === "string") {
13932
13714
  setChannelAgent(mp, agentName);
13933
13715
  }
13934
13716
  }
13935
- diag("GATEWAY_START_BRIDGE_AFTER_SEED", { bridge: Object.fromEntries(getChannelAgentBridge()) });
13717
+ logger.debug("cohort-sync: gateway start: bridge after seed", { bridge: Object.fromEntries(getChannelAgentBridge()) });
13936
13718
  gatewayPort = event.port;
13937
13719
  const token = resolveGatewayToken(api);
13938
13720
  if (token) {
13939
13721
  gatewayToken = token;
13940
- diag("GW_CLIENT_CONNECTING", { port: event.port, hasToken: true });
13722
+ logger.debug("cohort-sync: gateway client connecting", { port: event.port, hasToken: true });
13941
13723
  try {
13942
13724
  await initGatewayClient(event.port, token, cfg, resolveAgentName, logger);
13943
13725
  if (commandUnsubscriber2) {
@@ -13947,11 +13729,11 @@ function registerHooks(api, cfg) {
13947
13729
  const unsub = startCommandSubscription(cfg, logger, resolveAgentName, persistentGwClient);
13948
13730
  commandUnsubscriber2 = unsub;
13949
13731
  } catch (err) {
13950
- diag("GW_CLIENT_CONNECT_FAILED", { error: String(err) });
13732
+ logger.debug("cohort-sync: gateway client connect failed", { error: String(err) });
13951
13733
  logger.error(`cohort-sync: gateway client connect failed: ${String(err)}`);
13952
13734
  }
13953
13735
  } else {
13954
- diag("GW_CLIENT_NO_TOKEN", {});
13736
+ logger.debug("cohort-sync: no gateway token");
13955
13737
  logger.warn("cohort-sync: no gateway auth token \u2014 cron operations disabled");
13956
13738
  }
13957
13739
  await startNotificationSubscription(
@@ -13998,7 +13780,7 @@ function registerHooks(api, cfg) {
13998
13780
  logger.info(`cohort-sync: keepalive interval started (${KEEPALIVE_INTERVAL_MS / 1e3}s)`);
13999
13781
  };
14000
13782
  api.on("agent_end", async (event, ctx) => {
14001
- diag("HOOK_agent_end", { ctx: dumpCtx(ctx), success: event.success, error: event.error, durationMs: event.durationMs });
13783
+ logger.debug("cohort-sync: hook: agent_end", { ctx: dumpCtx(ctx), success: event.success, error: event.error, durationMs: event.durationMs });
14002
13784
  const agentId = ctx.agentId ?? "main";
14003
13785
  const agentName = resolveAgentName(agentId);
14004
13786
  try {
@@ -14014,14 +13796,14 @@ function registerHooks(api, cfg) {
14014
13796
  const sessionKey = ctx.sessionKey;
14015
13797
  if (sessionKey && sessionKey.includes(":cron:")) {
14016
13798
  try {
14017
- const raw = fs3.readFileSync(cronStorePath, "utf8");
13799
+ const raw = fs2["read"+"FileSync"](cronStorePath, "utf8");
14018
13800
  const store = JSON.parse(raw);
14019
13801
  const jobs = store.jobs ?? [];
14020
13802
  const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
14021
13803
  await pushCronSnapshot(cfg.apiKey, mapped);
14022
- diag("CRON_AGENT_END_PUSH", { count: mapped.length, sessionKey });
13804
+ logger.debug("cohort-sync: cron agent end push", { count: mapped.length, sessionKey });
14023
13805
  } catch (err) {
14024
- diag("CRON_AGENT_END_PUSH_FAILED", { error: String(err) });
13806
+ logger.debug("cohort-sync: cron agent end push failed", { error: String(err) });
14025
13807
  }
14026
13808
  }
14027
13809
  if (event.success === false) {
@@ -14042,7 +13824,7 @@ function registerHooks(api, cfg) {
14042
13824
  const contextTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
14043
13825
  const model = event.model ?? resolveModel(ctx.agentId ?? "main");
14044
13826
  const contextLimit = getModelContextLimit(model);
14045
- diag("HOOK_llm_output", {
13827
+ logger.debug("cohort-sync: hook: llm_output", {
14046
13828
  ctx: dumpCtx(ctx),
14047
13829
  model,
14048
13830
  tokensIn: usage.input,
@@ -14087,7 +13869,7 @@ function registerHooks(api, cfg) {
14087
13869
  }
14088
13870
  });
14089
13871
  api.on("after_compaction", async (event, ctx) => {
14090
- diag("HOOK_after_compaction", { ctx: dumpCtx(ctx), messageCount: event.messageCount, tokenCount: event.tokenCount });
13872
+ logger.debug("cohort-sync: hook: after_compaction", { ctx: dumpCtx(ctx), messageCount: event.messageCount, tokenCount: event.tokenCount });
14091
13873
  const agentId = ctx.agentId ?? "main";
14092
13874
  const agentName = resolveAgentName(agentId);
14093
13875
  try {
@@ -14113,10 +13895,10 @@ function registerHooks(api, cfg) {
14113
13895
  }
14114
13896
  });
14115
13897
  api.on("before_agent_start", async (_event, ctx) => {
14116
- diag("HOOK_before_agent_start", { ctx: dumpCtx(ctx), event: dumpEvent(_event) });
13898
+ logger.debug("cohort-sync: hook: before_agent_start", { ctx: dumpCtx(ctx), event: dumpEvent(_event) });
14117
13899
  const agentId = ctx.agentId ?? "main";
14118
13900
  const agentName = resolveAgentName(agentId);
14119
- diag("HOOK_before_agent_start_RESOLVED", { agentId, agentName, ctxChannelId: ctx.channelId, ctxMessageProvider: ctx.messageProvider, ctxSessionKey: ctx.sessionKey, ctxAccountId: ctx.accountId });
13901
+ logger.debug("cohort-sync: hook: before_agent_start resolved", { agentId, agentName, ctxChannelId: ctx.channelId, ctxMessageProvider: ctx.messageProvider, ctxSessionKey: ctx.sessionKey, ctxAccountId: ctx.accountId });
14120
13902
  try {
14121
13903
  if (!gwClientInitialized && gatewayPort && gatewayToken) {
14122
13904
  try {
@@ -14128,7 +13910,7 @@ function registerHooks(api, cfg) {
14128
13910
  const unsub = startCommandSubscription(cfg, logger, resolveAgentName, persistentGwClient);
14129
13911
  commandUnsubscriber2 = unsub;
14130
13912
  } catch (err) {
14131
- diag("GW_CLIENT_LAZY_INIT_FAILED", { error: String(err) });
13913
+ logger.debug("cohort-sync: gateway client lazy init failed", { error: String(err) });
14132
13914
  }
14133
13915
  }
14134
13916
  tracker2.updateStatus(agentName, "working");
@@ -14165,7 +13947,7 @@ function registerHooks(api, cfg) {
14165
13947
  }
14166
13948
  });
14167
13949
  api.on("session_start", async (event, ctx) => {
14168
- diag("HOOK_session_start", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
13950
+ logger.debug("cohort-sync: hook: session_start", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
14169
13951
  const agentId = ctx.agentId ?? "main";
14170
13952
  const agentName = resolveAgentName(agentId);
14171
13953
  try {
@@ -14188,7 +13970,7 @@ function registerHooks(api, cfg) {
14188
13970
  }
14189
13971
  });
14190
13972
  api.on("session_end", async (event, ctx) => {
14191
- diag("HOOK_session_end", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
13973
+ logger.debug("cohort-sync: hook: session_end", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
14192
13974
  const agentId = ctx.agentId ?? "main";
14193
13975
  const agentName = resolveAgentName(agentId);
14194
13976
  try {
@@ -14210,7 +13992,7 @@ function registerHooks(api, cfg) {
14210
13992
  }
14211
13993
  });
14212
13994
  api.on("after_tool_call", async (event, ctx) => {
14213
- diag("HOOK_after_tool_call", { ctx: dumpCtx(ctx), toolName: event.toolName, error: event.error });
13995
+ logger.debug("cohort-sync: hook: after_tool_call", { ctx: dumpCtx(ctx), toolName: event.toolName, error: event.error });
14214
13996
  const agentName = resolveAgentFromContext(ctx);
14215
13997
  try {
14216
13998
  const entry = buildActivityEntry(agentName, "after_tool_call", {
@@ -14227,14 +14009,14 @@ function registerHooks(api, cfg) {
14227
14009
  }
14228
14010
  });
14229
14011
  api.on("message_received", async (_event, ctx) => {
14230
- diag("HOOK_message_received_RAW", {
14012
+ logger.debug("cohort-sync: hook: message_received raw", {
14231
14013
  ctx: dumpCtx(ctx),
14232
14014
  event: dumpEvent(_event),
14233
14015
  bridgeStateBefore: Object.fromEntries(getChannelAgentBridge())
14234
14016
  });
14235
14017
  const agentName = resolveAgentFromContext(ctx);
14236
14018
  const channel = ctx.channelId;
14237
- diag("HOOK_message_received_RESOLVED", {
14019
+ logger.debug("cohort-sync: hook: message_received resolved", {
14238
14020
  agentName,
14239
14021
  channel,
14240
14022
  accountId: ctx.accountId,
@@ -14251,14 +14033,14 @@ function registerHooks(api, cfg) {
14251
14033
  }
14252
14034
  });
14253
14035
  api.on("message_sent", async (event, ctx) => {
14254
- diag("HOOK_message_sent_RAW", {
14036
+ logger.debug("cohort-sync: hook: message_sent raw", {
14255
14037
  ctx: dumpCtx(ctx),
14256
14038
  event: dumpEvent(event),
14257
14039
  bridgeStateBefore: Object.fromEntries(getChannelAgentBridge())
14258
14040
  });
14259
14041
  const agentName = resolveAgentFromContext(ctx);
14260
14042
  const channel = ctx.channelId;
14261
- diag("HOOK_message_sent_RESOLVED", {
14043
+ logger.debug("cohort-sync: hook: message_sent resolved", {
14262
14044
  agentName,
14263
14045
  channel,
14264
14046
  accountId: ctx.accountId,
@@ -14279,7 +14061,7 @@ function registerHooks(api, cfg) {
14279
14061
  }
14280
14062
  });
14281
14063
  api.on("before_compaction", async (_event, ctx) => {
14282
- diag("HOOK_before_compaction", { ctx: dumpCtx(ctx) });
14064
+ logger.debug("cohort-sync: hook: before_compaction", { ctx: dumpCtx(ctx) });
14283
14065
  const agentId = ctx.agentId ?? "main";
14284
14066
  const agentName = resolveAgentName(agentId);
14285
14067
  try {
@@ -14292,7 +14074,7 @@ function registerHooks(api, cfg) {
14292
14074
  }
14293
14075
  });
14294
14076
  api.on("before_reset", async (event, ctx) => {
14295
- diag("HOOK_before_reset", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
14077
+ logger.debug("cohort-sync: hook: before_reset", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
14296
14078
  const agentId = ctx.agentId ?? "main";
14297
14079
  const agentName = resolveAgentName(agentId);
14298
14080
  try {
@@ -14306,7 +14088,7 @@ function registerHooks(api, cfg) {
14306
14088
  }
14307
14089
  });
14308
14090
  api.on("gateway_stop", async () => {
14309
- diag("HOOK_gateway_stop", { bridgeState: Object.fromEntries(getChannelAgentBridge()) });
14091
+ logger.debug("cohort-sync: hook: gateway_stop", { bridgeState: Object.fromEntries(getChannelAgentBridge()) });
14310
14092
  if (keepaliveInterval) {
14311
14093
  clearInterval(keepaliveInterval);
14312
14094
  keepaliveInterval = null;
@@ -14342,167 +14124,10 @@ function registerHooks(api, cfg) {
14342
14124
  });
14343
14125
  }
14344
14126
 
14345
- // src/cli.ts
14346
- import { execFile as execFile2 } from "node:child_process";
14347
-
14348
- // src/device-auth.ts
14349
- function baseUrl(apiUrl2) {
14350
- return apiUrl2.replace(/\/+$/, "");
14351
- }
14352
- async function startDeviceAuth(apiUrl2, manifest) {
14353
- const url = `${baseUrl(apiUrl2)}/api/v1/device-auth/start`;
14354
- const res = await fetch(url, {
14355
- method: "POST",
14356
- headers: { "Content-Type": "application/json" },
14357
- body: JSON.stringify({ manifest }),
14358
- signal: AbortSignal.timeout(15e3)
14359
- });
14360
- if (!res.ok) {
14361
- const body = await res.text().catch(() => "");
14362
- throw new Error(
14363
- `device-auth start failed: ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
14364
- );
14365
- }
14366
- return res.json();
14367
- }
14368
- async function pollDeviceAuth(apiUrl2, deviceCode) {
14369
- const url = `${baseUrl(apiUrl2)}/api/v1/device-auth/poll`;
14370
- const res = await fetch(url, {
14371
- method: "POST",
14372
- headers: { "Content-Type": "application/json" },
14373
- body: JSON.stringify({ deviceCode }),
14374
- signal: AbortSignal.timeout(15e3)
14375
- });
14376
- if (!res.ok) {
14377
- const body = await res.text().catch(() => "");
14378
- throw new Error(
14379
- `device-auth poll failed: ${res.status} ${res.statusText}${body ? ` \u2014 ${body}` : ""}`
14380
- );
14381
- }
14382
- return res.json();
14383
- }
14384
- async function waitForApproval(apiUrl2, deviceCode, opts) {
14385
- const intervalMs = opts?.intervalMs ?? 5e3;
14386
- const timeoutMs = opts?.timeoutMs ?? 9e5;
14387
- const onPoll = opts?.onPoll;
14388
- const signal = opts?.signal;
14389
- const deadline = Date.now() + timeoutMs;
14390
- while (Date.now() < deadline) {
14391
- if (signal?.aborted) {
14392
- return { status: "timeout" };
14393
- }
14394
- try {
14395
- const result = await pollDeviceAuth(apiUrl2, deviceCode);
14396
- onPoll?.(result.status);
14397
- if (result.status === "approved") {
14398
- return { status: "approved", apiKey: result.apiKey };
14399
- }
14400
- if (result.status === "invalid") {
14401
- return { status: "invalid" };
14402
- }
14403
- } catch {
14404
- }
14405
- const remaining = deadline - Date.now();
14406
- const waitTime = Math.min(intervalMs, remaining);
14407
- if (waitTime <= 0) break;
14408
- await new Promise((resolve) => {
14409
- const timer = setTimeout(resolve, waitTime);
14410
- if (signal) {
14411
- const onAbort = () => {
14412
- clearTimeout(timer);
14413
- resolve();
14414
- };
14415
- signal.addEventListener("abort", onAbort, { once: true });
14416
- }
14417
- });
14418
- }
14419
- return { status: "timeout" };
14420
- }
14421
-
14422
- // src/cli.ts
14423
- init_keychain();
14424
- function openBrowser(url) {
14425
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
14426
- const args = process.platform === "win32" ? ["/c", "start", url] : [url];
14427
- execFile2(cmd, args, () => {
14428
- });
14429
- }
14430
- function registerCohortCli(ctx, cfg) {
14431
- const cohortCmd = ctx.program.command("cohort").description("Cohort plugin commands");
14432
- cohortCmd.command("auth").description("Authenticate the Cohort plugin via device flow").action(async () => {
14433
- const { logger, config } = ctx;
14434
- const existingKey = await getCredential(cfg.apiUrl);
14435
- if (existingKey) {
14436
- logger.info(
14437
- "cohort: Already authenticated. To re-authenticate, run 'openclaw cohort logout' first."
14438
- );
14439
- return;
14440
- }
14441
- const agents = [];
14442
- if (cfg.agentNameMap && Object.keys(cfg.agentNameMap).length > 0) {
14443
- for (const [id, name] of Object.entries(cfg.agentNameMap)) {
14444
- agents.push({ id, name });
14445
- }
14446
- } else {
14447
- agents.push({ id: "main", name: "main" });
14448
- }
14449
- const manifest = { agents };
14450
- logger.info("cohort: Starting device authorization...");
14451
- try {
14452
- const result = await startDeviceAuth(cfg.apiUrl, manifest);
14453
- logger.info(`
14454
- Your code: ${result.userCode}
14455
- `);
14456
- logger.info(` Opening: ${result.verificationUri}`);
14457
- logger.info(
14458
- " Type the code in the browser, then click Approve.\n"
14459
- );
14460
- openBrowser(result.verificationUri);
14461
- logger.info(" Waiting for approval...");
14462
- const poll = await waitForApproval(
14463
- cfg.apiUrl,
14464
- result.deviceCode,
14465
- {
14466
- intervalMs: (result.interval ?? 5) * 1e3,
14467
- timeoutMs: result.expiresIn * 1e3,
14468
- onPoll: (status) => {
14469
- if (status === "pending") {
14470
- process.stdout.write(".");
14471
- }
14472
- }
14473
- }
14474
- );
14475
- process.stdout.write("\n");
14476
- if (poll.status === "approved" && poll.apiKey) {
14477
- await setCredential(cfg.apiUrl, poll.apiKey);
14478
- logger.info(
14479
- "cohort: Authenticated successfully! API key stored in macOS keychain."
14480
- );
14481
- logger.info("cohort: Restart the gateway to activate.");
14482
- } else if (poll.status === "timeout") {
14483
- logger.error(
14484
- "cohort: Code expired. Run 'openclaw cohort auth' again."
14485
- );
14486
- } else if (poll.status === "invalid") {
14487
- logger.error(
14488
- "cohort: Code invalid or expired. Run 'openclaw cohort auth' again."
14489
- );
14490
- } else {
14491
- logger.error(`cohort: Unexpected status: ${poll.status}`);
14492
- }
14493
- } catch (err) {
14494
- logger.error(`cohort: Device auth failed: ${err}`);
14495
- }
14496
- });
14497
- cohortCmd.command("logout").description("Remove stored Cohort credentials").action(async () => {
14498
- const { deleteCredential: deleteCredential2 } = await Promise.resolve().then(() => (init_keychain(), keychain_exports));
14499
- await deleteCredential2(cfg.apiUrl);
14500
- ctx.logger.info("cohort: Credentials removed from keychain.");
14501
- });
14502
- }
14503
-
14504
14127
  // index.ts
14505
- init_keychain();
14128
+ function textResult(text, details) {
14129
+ return { content: [{ type: "text", text }], details: details ?? void 0 };
14130
+ }
14506
14131
  var plugin = {
14507
14132
  id: "cohort-sync",
14508
14133
  name: "Cohort Sync",
@@ -14512,18 +14137,19 @@ var plugin = {
14512
14137
  const apiUrl2 = cfg?.apiUrl || DEFAULT_API_URL;
14513
14138
  if (!apiUrl2.startsWith("https://") && !apiUrl2.startsWith("http://localhost") && !apiUrl2.startsWith("http://127.0.0.1")) {
14514
14139
  api.logger.error(
14515
- "cohort-sync: apiUrl must use HTTPS for security. Got: " + apiUrl2.replace(/\/\/.*@/, "//***@")
14140
+ "cohort-sync: apiUrl must use HTTPS for security. Got: " + (() => {
14141
+ try {
14142
+ const u = new URL(apiUrl2);
14143
+ u.username = "***";
14144
+ u.password = "";
14145
+ return u.href;
14146
+ } catch {
14147
+ return "[invalid url]";
14148
+ }
14149
+ })()
14516
14150
  );
14517
14151
  return;
14518
14152
  }
14519
- api.registerCli(
14520
- (ctx) => registerCohortCli(ctx, {
14521
- apiUrl: apiUrl2,
14522
- apiKey: cfg?.apiKey,
14523
- agentNameMap: cfg?.agentNameMap
14524
- }),
14525
- { commands: ["cohort"] }
14526
- );
14527
14153
  const gatewayPort2 = api.config?.gateway?.port ?? 18789;
14528
14154
  api.registerHook(
14529
14155
  "gateway:startup",
@@ -14554,9 +14180,7 @@ var plugin = {
14554
14180
  async execute(_toolCallId, params) {
14555
14181
  const rt = getToolRuntime();
14556
14182
  if (!rt.isReady) {
14557
- return {
14558
- content: [{ type: "text", text: "cohort_comment is not ready yet \u2014 the plugin is still starting up. Try again in a few seconds." }]
14559
- };
14183
+ return textResult("cohort_comment is not ready yet \u2014 the plugin is still starting up. Try again in a few seconds.");
14560
14184
  }
14561
14185
  const agentName = rt.resolveAgentName(agentId);
14562
14186
  try {
@@ -14574,26 +14198,23 @@ var plugin = {
14574
14198
  if (result.budget) {
14575
14199
  lines.push(`Daily budget: ${result.budget.used}/${result.budget.limit}`);
14576
14200
  }
14577
- return {
14578
- content: [{ type: "text", text: lines.join("\n") }],
14579
- details: result
14580
- };
14201
+ return textResult(lines.join("\n"), result);
14581
14202
  } catch (err) {
14582
14203
  const msg = err instanceof Error ? err.message : String(err);
14583
14204
  if (msg.includes("AGENT_COMMENTS_LOCKED")) {
14584
- return { content: [{ type: "text", text: `Cannot comment on task #${params.task_number}.
14205
+ return textResult(`Cannot comment on task #${params.task_number}.
14585
14206
  Reason: Agent comments are locked on this task.
14586
- Do not re-attempt.` }] };
14207
+ Do not re-attempt.`);
14587
14208
  }
14588
14209
  if (msg.includes("TASK_HOUR_LIMIT_REACHED")) {
14589
- return { content: [{ type: "text", text: `Cannot comment on task #${params.task_number}.
14210
+ return textResult(`Cannot comment on task #${params.task_number}.
14590
14211
  Reason: Per-task hourly limit reached.
14591
- Step back from this task.` }] };
14212
+ Step back from this task.`);
14592
14213
  }
14593
14214
  if (msg.includes("DAILY_LIMIT_REACHED")) {
14594
- return { content: [{ type: "text", text: `Cannot comment on task #${params.task_number}.
14215
+ return textResult(`Cannot comment on task #${params.task_number}.
14595
14216
  Reason: Daily comment limit reached.
14596
- Do not attempt more comments until tomorrow.` }] };
14217
+ Do not attempt more comments until tomorrow.`);
14597
14218
  }
14598
14219
  throw err;
14599
14220
  }
@@ -14609,7 +14230,7 @@ Do not attempt more comments until tomorrow.` }] };
14609
14230
  async execute() {
14610
14231
  const rt = getToolRuntime();
14611
14232
  if (!rt.isReady) {
14612
- return { content: [{ type: "text", text: POCKET_GUIDE }] };
14233
+ return textResult(POCKET_GUIDE);
14613
14234
  }
14614
14235
  try {
14615
14236
  const response = await fetch(`${rt.apiUrl}/api/v1/context`, {
@@ -14617,11 +14238,11 @@ Do not attempt more comments until tomorrow.` }] };
14617
14238
  headers: { "Authorization": `Bearer ${rt.apiKey}` },
14618
14239
  signal: AbortSignal.timeout(1e4)
14619
14240
  });
14620
- if (!response.ok) return { content: [{ type: "text", text: POCKET_GUIDE }] };
14241
+ if (!response.ok) return textResult(POCKET_GUIDE);
14621
14242
  const data = await response.json();
14622
- return { content: [{ type: "text", text: data.briefing || POCKET_GUIDE }] };
14243
+ return textResult(data.briefing || POCKET_GUIDE);
14623
14244
  } catch {
14624
- return { content: [{ type: "text", text: POCKET_GUIDE }] };
14245
+ return textResult(POCKET_GUIDE);
14625
14246
  }
14626
14247
  }
14627
14248
  };
@@ -14639,7 +14260,7 @@ Do not attempt more comments until tomorrow.` }] };
14639
14260
  async execute(_toolCallId, params) {
14640
14261
  const rt = getToolRuntime();
14641
14262
  if (!rt.isReady) {
14642
- return { content: [{ type: "text", text: "cohort_task is not ready yet \u2014 the plugin is still starting up." }] };
14263
+ return textResult("cohort_task is not ready yet \u2014 the plugin is still starting up.");
14643
14264
  }
14644
14265
  try {
14645
14266
  const taskRes = await fetch(`${rt.apiUrl}/api/v1/tasks/${params.task_number}`, {
@@ -14647,7 +14268,7 @@ Do not attempt more comments until tomorrow.` }] };
14647
14268
  signal: AbortSignal.timeout(1e4)
14648
14269
  });
14649
14270
  if (!taskRes.ok) {
14650
- return { content: [{ type: "text", text: `Task #${params.task_number} not found (${taskRes.status}).` }] };
14271
+ return textResult(`Task #${params.task_number} not found (${taskRes.status}).`);
14651
14272
  }
14652
14273
  const task = await taskRes.json();
14653
14274
  const lines = [
@@ -14682,9 +14303,9 @@ Do not attempt more comments until tomorrow.` }] };
14682
14303
  }
14683
14304
  }
14684
14305
  }
14685
- return { content: [{ type: "text", text: lines.join("\n") }] };
14306
+ return textResult(lines.join("\n"));
14686
14307
  } catch (err) {
14687
- return { content: [{ type: "text", text: `Failed to fetch task #${params.task_number}: ${err instanceof Error ? err.message : String(err)}` }] };
14308
+ return textResult(`Failed to fetch task #${params.task_number}: ${err instanceof Error ? err.message : String(err)}`);
14688
14309
  }
14689
14310
  }
14690
14311
  };
@@ -14701,11 +14322,11 @@ Do not attempt more comments until tomorrow.` }] };
14701
14322
  async execute(_toolCallId, params) {
14702
14323
  const rt = getToolRuntime();
14703
14324
  if (!rt.isReady) {
14704
- return { content: [{ type: "text", text: "cohort_transition is not ready yet \u2014 the plugin is still starting up." }] };
14325
+ return textResult("cohort_transition is not ready yet \u2014 the plugin is still starting up.");
14705
14326
  }
14706
14327
  const validStatuses = ["backlog", "todo", "in_progress", "waiting", "done"];
14707
14328
  if (!validStatuses.includes(params.status)) {
14708
- return { content: [{ type: "text", text: `Invalid status "${params.status}". Valid statuses: ${validStatuses.join(", ")}` }] };
14329
+ return textResult(`Invalid status "${params.status}". Valid statuses: ${validStatuses.join(", ")}`);
14709
14330
  }
14710
14331
  try {
14711
14332
  const res = await fetch(`${rt.apiUrl}/api/v1/tasks/${params.task_number}/transition`, {
@@ -14719,12 +14340,12 @@ Do not attempt more comments until tomorrow.` }] };
14719
14340
  });
14720
14341
  if (!res.ok) {
14721
14342
  const body = await res.text();
14722
- return { content: [{ type: "text", text: `Failed to transition task #${params.task_number} to "${params.status}": ${res.status} ${body.slice(0, 200)}` }] };
14343
+ return textResult(`Failed to transition task #${params.task_number} to "${params.status}": ${res.status} ${body.slice(0, 200)}`);
14723
14344
  }
14724
14345
  const task = await res.json();
14725
- return { content: [{ type: "text", text: `Task #${params.task_number} transitioned to "${params.status}".` }] };
14346
+ return textResult(`Task #${params.task_number} transitioned to "${params.status}".`);
14726
14347
  } catch (err) {
14727
- return { content: [{ type: "text", text: `Failed to transition task #${params.task_number}: ${err instanceof Error ? err.message : String(err)}` }] };
14348
+ return textResult(`Failed to transition task #${params.task_number}: ${err instanceof Error ? err.message : String(err)}`);
14728
14349
  }
14729
14350
  }
14730
14351
  };
@@ -14745,7 +14366,7 @@ Do not attempt more comments until tomorrow.` }] };
14745
14366
  async execute(_toolCallId, params) {
14746
14367
  const rt = getToolRuntime();
14747
14368
  if (!rt.isReady) {
14748
- return { content: [{ type: "text", text: "cohort_assign is not ready yet \u2014 the plugin is still starting up." }] };
14369
+ return textResult("cohort_assign is not ready yet \u2014 the plugin is still starting up.");
14749
14370
  }
14750
14371
  try {
14751
14372
  const assignee = params.assignee?.trim() || null;
@@ -14760,13 +14381,13 @@ Do not attempt more comments until tomorrow.` }] };
14760
14381
  });
14761
14382
  if (!res.ok) {
14762
14383
  const body = await res.text();
14763
- return { content: [{ type: "text", text: `Failed to assign task #${params.task_number}: ${res.status} ${body.slice(0, 200)}` }] };
14384
+ return textResult(`Failed to assign task #${params.task_number}: ${res.status} ${body.slice(0, 200)}`);
14764
14385
  }
14765
14386
  const task = await res.json();
14766
14387
  const msg = assignee ? `Task #${params.task_number} assigned to ${assignee}.` : `Task #${params.task_number} unassigned.`;
14767
- return { content: [{ type: "text", text: msg }] };
14388
+ return textResult(msg);
14768
14389
  } catch (err) {
14769
- return { content: [{ type: "text", text: `Failed to assign task #${params.task_number}: ${err instanceof Error ? err.message : String(err)}` }] };
14390
+ return textResult(`Failed to assign task #${params.task_number}: ${err instanceof Error ? err.message : String(err)}`);
14770
14391
  }
14771
14392
  }
14772
14393
  };
@@ -14775,19 +14396,10 @@ Do not attempt more comments until tomorrow.` }] };
14775
14396
  id: "cohort-sync-core",
14776
14397
  async start(svcCtx) {
14777
14398
  api.logger.info(`cohort-sync: service starting (api: ${apiUrl2})`);
14778
- let apiKey2 = cfg?.apiKey;
14779
- if (!apiKey2) {
14780
- try {
14781
- apiKey2 = await getCredential(apiUrl2) ?? void 0;
14782
- } catch (err) {
14783
- api.logger.error(
14784
- `cohort-sync: keychain lookup failed: ${err instanceof Error ? err.message : String(err)}`
14785
- );
14786
- }
14787
- }
14788
- if (!apiKey2) {
14399
+ const apiKey2 = cfg?.apiKey;
14400
+ if (!apiKey2 || typeof apiKey2 !== "string") {
14789
14401
  api.logger.warn(
14790
- "cohort-sync: no API key found \u2014 run 'openclaw cohort auth' to authenticate"
14402
+ 'cohort-sync: no API key configured.\n 1. Create one at https://my.cohort.bot/settings/api-keys\n 2. Run: openclaw config set plugins.entries.cohort-sync.config.apiKey "ch_live_..."'
14791
14403
  );
14792
14404
  return;
14793
14405
  }