@cfio/cohort-sync 0.9.4 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -20
- package/dist/index.js +188 -576
- package/dist/openclaw.plugin.json +1 -1
- package/dist/package.json +2 -2
- package/package.json +5 -5
- package/scripts/postinstall.mjs +1 -1
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
|
-
|
|
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
|
|
2709
|
-
import
|
|
2710
|
-
import
|
|
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
|
-
|
|
2771
|
-
|
|
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 ${
|
|
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,
|
|
2779
|
-
const res = await fetch(`${apiUrl2
|
|
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 ${
|
|
2644
|
+
if (!res.ok) throw new Error(`PATCH ${path3} \u2192 ${res.status}`);
|
|
2786
2645
|
}
|
|
2787
|
-
async function v1Post(apiUrl2, apiKey2,
|
|
2788
|
-
const res = await fetch(`${apiUrl2
|
|
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 ${
|
|
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
|
-
|
|
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
|
|
4669
|
+
const path3 = pathParts.slice(0, -1).join("/");
|
|
4846
4670
|
const exportName = pathParts[pathParts.length - 1];
|
|
4847
4671
|
if (exportName === "default") {
|
|
4848
|
-
return
|
|
4672
|
+
return path3;
|
|
4849
4673
|
} else {
|
|
4850
|
-
return
|
|
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
|
|
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[
|
|
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
|
|
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
|
|
7920
|
-
var
|
|
7921
|
-
var
|
|
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 = !!
|
|
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 =
|
|
7928
|
-
var platform =
|
|
7929
|
-
var libc =
|
|
7930
|
-
var armv =
|
|
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 =
|
|
7761
|
+
dir = path3.resolve(dir || ".");
|
|
7938
7762
|
try {
|
|
7939
|
-
var name = runtimeRequire(
|
|
7940
|
-
if (
|
|
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(
|
|
7768
|
+
var release = getFirst(path3.join(dir, "build/Release"), matchBuild);
|
|
7945
7769
|
if (release) return release;
|
|
7946
|
-
var debug = getFirst(
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
7799
|
+
if (winner) return path3.join(prebuilds, winner.file);
|
|
7976
7800
|
}
|
|
7977
7801
|
};
|
|
7978
7802
|
function readdirSync(dir) {
|
|
7979
7803
|
try {
|
|
7980
|
-
return
|
|
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] &&
|
|
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 (
|
|
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" &&
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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/
|
|
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
|
|
12359
|
-
var
|
|
12360
|
-
var
|
|
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(
|
|
12200
|
+
const data = JSON.parse(fs.readFileSync(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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12440
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12695
|
+
this.logger.debug("cohort-sync: reconnecting", { attempt: this.reconnectAttempts });
|
|
12902
12696
|
await this.connect();
|
|
12903
|
-
|
|
12697
|
+
this.logger.debug("cohort-sync: reconnected", { attempt: this.reconnectAttempts });
|
|
12904
12698
|
this.onReconnect?.();
|
|
12905
12699
|
} catch (err) {
|
|
12906
|
-
|
|
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.0" : "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
|
|
13606
|
-
|
|
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
|
-
|
|
13406
|
+
logger.debug("cohort-sync: cron event pushed", { count: mapped.length });
|
|
13624
13407
|
} catch (err) {
|
|
13625
|
-
|
|
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 =
|
|
13634
|
-
const content =
|
|
13416
|
+
const filePath = path2.join(workspaceDir, "IDENTITY.md");
|
|
13417
|
+
const content = fs2.readFileSync(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
|
-
|
|
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 (!
|
|
13696
|
-
const data = JSON.parse(
|
|
13478
|
+
if (!fs2.existsSync(STATE_FILE_PATH)) return;
|
|
13479
|
+
const data = JSON.parse(fs2.readFileSync(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
|
-
|
|
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
|
-
|
|
13521
|
+
logger.debug("cohort-sync: cron snapshot pushed", { count: mapped.length });
|
|
13740
13522
|
} catch (err) {
|
|
13741
|
-
|
|
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
|
-
|
|
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 =
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13634
|
+
logger.debug("cohort-sync: resolve agent: result", { method: "channelId_bridge", channelId, channelAgent, bridgeState: Object.fromEntries(getChannelAgentBridge()) });
|
|
13853
13635
|
return channelAgent;
|
|
13854
13636
|
}
|
|
13855
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
13799
|
+
const raw = fs2.readFileSync(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
|
-
|
|
13804
|
+
logger.debug("cohort-sync: cron agent end push", { count: mapped.length, sessionKey });
|
|
14023
13805
|
} catch (err) {
|
|
14024
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: " +
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
14241
|
+
if (!response.ok) return textResult(POCKET_GUIDE);
|
|
14621
14242
|
const data = await response.json();
|
|
14622
|
-
return
|
|
14243
|
+
return textResult(data.briefing || POCKET_GUIDE);
|
|
14623
14244
|
} catch {
|
|
14624
|
-
return
|
|
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
|
|
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
|
|
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
|
|
14306
|
+
return textResult(lines.join("\n"));
|
|
14686
14307
|
} catch (err) {
|
|
14687
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
14346
|
+
return textResult(`Task #${params.task_number} transitioned to "${params.status}".`);
|
|
14726
14347
|
} catch (err) {
|
|
14727
|
-
return
|
|
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
|
|
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
|
|
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
|
|
14388
|
+
return textResult(msg);
|
|
14768
14389
|
} catch (err) {
|
|
14769
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|