@cfio/cohort-sync 0.34.3 → 0.34.4
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 +404 -53
- package/dist/openclaw.plugin.json +2 -1
- package/dist/package.json +1 -1
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2619,11 +2619,13 @@ var Type = type_exports2;
|
|
|
2619
2619
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
2620
2620
|
|
|
2621
2621
|
// src/hooks.ts
|
|
2622
|
-
import
|
|
2622
|
+
import fs3 from "node:fs";
|
|
2623
2623
|
import os2 from "node:os";
|
|
2624
|
-
import
|
|
2624
|
+
import path3 from "node:path";
|
|
2625
2625
|
|
|
2626
2626
|
// src/sync.ts
|
|
2627
|
+
import fs from "node:fs";
|
|
2628
|
+
import path from "node:path";
|
|
2627
2629
|
var VALID_STATUSES = /* @__PURE__ */ new Set(["idle", "working", "waiting"]);
|
|
2628
2630
|
function normalizeStatus(status) {
|
|
2629
2631
|
return VALID_STATUSES.has(status) ? status : "idle";
|
|
@@ -2632,31 +2634,31 @@ function trimTrailingSlashes(url) {
|
|
|
2632
2634
|
while (url.endsWith("/")) url = url.slice(0, -1);
|
|
2633
2635
|
return url;
|
|
2634
2636
|
}
|
|
2635
|
-
async function v1Get(apiUrl2, apiKey2,
|
|
2636
|
-
const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${
|
|
2637
|
+
async function v1Get(apiUrl2, apiKey2, path4) {
|
|
2638
|
+
const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${path4}`, {
|
|
2637
2639
|
headers: { Authorization: `Bearer ${apiKey2}` },
|
|
2638
2640
|
signal: AbortSignal.timeout(1e4)
|
|
2639
2641
|
});
|
|
2640
|
-
if (!res.ok) throw new Error(`GET ${
|
|
2642
|
+
if (!res.ok) throw new Error(`GET ${path4} \u2192 ${res.status}`);
|
|
2641
2643
|
return res.json();
|
|
2642
2644
|
}
|
|
2643
|
-
async function v1Patch(apiUrl2, apiKey2,
|
|
2644
|
-
const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${
|
|
2645
|
+
async function v1Patch(apiUrl2, apiKey2, path4, body) {
|
|
2646
|
+
const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${path4}`, {
|
|
2645
2647
|
method: "PATCH",
|
|
2646
2648
|
headers: { Authorization: `Bearer ${apiKey2}`, "Content-Type": "application/json" },
|
|
2647
2649
|
body: JSON.stringify(body),
|
|
2648
2650
|
signal: AbortSignal.timeout(1e4)
|
|
2649
2651
|
});
|
|
2650
|
-
if (!res.ok) throw new Error(`PATCH ${
|
|
2652
|
+
if (!res.ok) throw new Error(`PATCH ${path4} \u2192 ${res.status}`);
|
|
2651
2653
|
}
|
|
2652
|
-
async function v1Post(apiUrl2, apiKey2,
|
|
2653
|
-
const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${
|
|
2654
|
+
async function v1Post(apiUrl2, apiKey2, path4, body) {
|
|
2655
|
+
const res = await fetch(`${trimTrailingSlashes(apiUrl2)}${path4}`, {
|
|
2654
2656
|
method: "POST",
|
|
2655
2657
|
headers: { Authorization: `Bearer ${apiKey2}`, "Content-Type": "application/json" },
|
|
2656
2658
|
body: JSON.stringify(body),
|
|
2657
2659
|
signal: AbortSignal.timeout(1e4)
|
|
2658
2660
|
});
|
|
2659
|
-
if (!res.ok) throw new Error(`POST ${
|
|
2661
|
+
if (!res.ok) throw new Error(`POST ${path4} \u2192 ${res.status}`);
|
|
2660
2662
|
}
|
|
2661
2663
|
function isNewerVersion(a, b) {
|
|
2662
2664
|
const strip = (v2) => v2.replace(/-.*$/, "");
|
|
@@ -2753,7 +2755,12 @@ async function reconcileRoster(openClawAgents, cfg, logger) {
|
|
|
2753
2755
|
displayName: oc.identity?.name ?? agentName,
|
|
2754
2756
|
emoji: oc.identity?.emoji ?? "\u{1F916}",
|
|
2755
2757
|
model: oc.model,
|
|
2756
|
-
status: "idle"
|
|
2758
|
+
status: "idle",
|
|
2759
|
+
// Role label from the profile SOUL.md, set ONLY at first registration so
|
|
2760
|
+
// agents get a default title. Omitted when absent (the backend skips an
|
|
2761
|
+
// undefined title). Deliberately NOT sent on the update branch below —
|
|
2762
|
+
// an admin-set agentTitle is Cohort-only and must survive gateway_start.
|
|
2763
|
+
title: oc.identity?.title
|
|
2757
2764
|
});
|
|
2758
2765
|
logger.info(`cohort-sync: provisioned new agent "${agentName}"`);
|
|
2759
2766
|
} catch (err) {
|
|
@@ -2819,7 +2826,94 @@ async function markAllUnreachable(cfg, logger) {
|
|
|
2819
2826
|
}
|
|
2820
2827
|
logger.info("cohort-sync: all agents marked unreachable");
|
|
2821
2828
|
}
|
|
2822
|
-
|
|
2829
|
+
var MAX_SKILL_BYTES = 64 * 1024;
|
|
2830
|
+
var MAX_SYNC_SKILLS = 500;
|
|
2831
|
+
function parseSkillFrontmatter(text) {
|
|
2832
|
+
if (!text) return null;
|
|
2833
|
+
const lines = text.split(/\r?\n/);
|
|
2834
|
+
if (lines.length === 0 || lines[0].trim() !== "---") return null;
|
|
2835
|
+
const fields = {};
|
|
2836
|
+
let closed = false;
|
|
2837
|
+
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
2838
|
+
const line = lines[i2];
|
|
2839
|
+
if (line.trim() === "---") {
|
|
2840
|
+
closed = true;
|
|
2841
|
+
break;
|
|
2842
|
+
}
|
|
2843
|
+
const first = line.charAt(0);
|
|
2844
|
+
if (line.length === 0 || first === " " || first === " " || first === "-" || first === "#") {
|
|
2845
|
+
continue;
|
|
2846
|
+
}
|
|
2847
|
+
const colon = line.indexOf(":");
|
|
2848
|
+
if (colon === -1) continue;
|
|
2849
|
+
const key = line.slice(0, colon).trim();
|
|
2850
|
+
if (key !== "name" && key !== "description") continue;
|
|
2851
|
+
fields[key] = stripScalar(line.slice(colon + 1));
|
|
2852
|
+
}
|
|
2853
|
+
if (!closed) return null;
|
|
2854
|
+
const name = fields.name;
|
|
2855
|
+
if (!name) return null;
|
|
2856
|
+
return { name, description: fields.description ?? "" };
|
|
2857
|
+
}
|
|
2858
|
+
function stripScalar(value) {
|
|
2859
|
+
const v2 = value.trim();
|
|
2860
|
+
if (v2.length >= 2 && v2[0] === v2[v2.length - 1] && (v2[0] === "'" || v2[0] === '"')) {
|
|
2861
|
+
return v2.slice(1, -1);
|
|
2862
|
+
}
|
|
2863
|
+
return v2;
|
|
2864
|
+
}
|
|
2865
|
+
function enumerateSkills(skillsDir, source = "hermes") {
|
|
2866
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2867
|
+
const visit = (dir) => {
|
|
2868
|
+
let entries;
|
|
2869
|
+
try {
|
|
2870
|
+
entries = fs.readdirSync(dir);
|
|
2871
|
+
} catch {
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
for (const entry of entries) {
|
|
2875
|
+
const full = path.join(dir, entry);
|
|
2876
|
+
let isDir = false;
|
|
2877
|
+
try {
|
|
2878
|
+
isDir = fs.statSync(full).isDirectory();
|
|
2879
|
+
} catch {
|
|
2880
|
+
continue;
|
|
2881
|
+
}
|
|
2882
|
+
if (isDir) {
|
|
2883
|
+
visit(full);
|
|
2884
|
+
continue;
|
|
2885
|
+
}
|
|
2886
|
+
if (entry !== "SKILL.md") continue;
|
|
2887
|
+
let text;
|
|
2888
|
+
try {
|
|
2889
|
+
text = fs["read"+"FileSync"](full, "utf-8").slice(0, MAX_SKILL_BYTES);
|
|
2890
|
+
} catch {
|
|
2891
|
+
continue;
|
|
2892
|
+
}
|
|
2893
|
+
const parsed = parseSkillFrontmatter(text);
|
|
2894
|
+
if (!parsed) continue;
|
|
2895
|
+
if (byName.has(parsed.name)) continue;
|
|
2896
|
+
byName.set(parsed.name, { name: parsed.name, description: parsed.description, source });
|
|
2897
|
+
}
|
|
2898
|
+
};
|
|
2899
|
+
visit(skillsDir);
|
|
2900
|
+
return [...byName.values()];
|
|
2901
|
+
}
|
|
2902
|
+
function skillsDirExists(skillsDir) {
|
|
2903
|
+
try {
|
|
2904
|
+
return fs.statSync(skillsDir).isDirectory();
|
|
2905
|
+
} catch {
|
|
2906
|
+
return false;
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
async function syncSkills(agentName, skills, cfg, logger) {
|
|
2910
|
+
const capped = skills.slice(0, MAX_SYNC_SKILLS);
|
|
2911
|
+
if (capped.length < skills.length) {
|
|
2912
|
+
logger.warn(`cohort-sync: capping skill sync at ${MAX_SYNC_SKILLS} (had ${skills.length})`);
|
|
2913
|
+
}
|
|
2914
|
+
await v1Post(cfg.apiUrl, cfg.apiKey, "/api/v1/skills/sync", { agentName, skills: capped });
|
|
2915
|
+
}
|
|
2916
|
+
async function fullSync(agentName, model, cfg, logger, openClawAgents, skillsDir) {
|
|
2823
2917
|
logger.info("cohort-sync: full sync starting");
|
|
2824
2918
|
if (openClawAgents && openClawAgents.length > 0) {
|
|
2825
2919
|
try {
|
|
@@ -2830,7 +2924,21 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
2830
2924
|
} else {
|
|
2831
2925
|
await syncAgentStatus(agentName, "working", model, cfg, logger);
|
|
2832
2926
|
}
|
|
2833
|
-
|
|
2927
|
+
if (skillsDir) {
|
|
2928
|
+
try {
|
|
2929
|
+
if (!skillsDirExists(skillsDir)) {
|
|
2930
|
+
logger.info(`cohort-sync: skill sync skipped (skills dir missing or unreadable: ${skillsDir})`);
|
|
2931
|
+
} else {
|
|
2932
|
+
const skills = enumerateSkills(skillsDir, "openclaw");
|
|
2933
|
+
await syncSkills(agentName, skills, cfg, logger);
|
|
2934
|
+
logger.info(`cohort-sync: synced ${skills.length} skill(s) from ${skillsDir}`);
|
|
2935
|
+
}
|
|
2936
|
+
} catch (err) {
|
|
2937
|
+
logger.warn(`cohort-sync: skill sync failed (non-fatal): ${String(err)}`);
|
|
2938
|
+
}
|
|
2939
|
+
} else {
|
|
2940
|
+
logger.info("cohort-sync: skill sync skipped (no skills dir resolved)");
|
|
2941
|
+
}
|
|
2834
2942
|
logger.info("cohort-sync: full sync complete");
|
|
2835
2943
|
}
|
|
2836
2944
|
|
|
@@ -4691,12 +4799,12 @@ function createApi(pathParts = []) {
|
|
|
4691
4799
|
`API path is expected to be of the form \`api.moduleName.functionName\`. Found: \`${found}\``
|
|
4692
4800
|
);
|
|
4693
4801
|
}
|
|
4694
|
-
const
|
|
4802
|
+
const path4 = pathParts.slice(0, -1).join("/");
|
|
4695
4803
|
const exportName = pathParts[pathParts.length - 1];
|
|
4696
4804
|
if (exportName === "default") {
|
|
4697
|
-
return
|
|
4805
|
+
return path4;
|
|
4698
4806
|
} else {
|
|
4699
|
-
return
|
|
4807
|
+
return path4 + ":" + exportName;
|
|
4700
4808
|
}
|
|
4701
4809
|
} else if (prop === Symbol.toStringTag) {
|
|
4702
4810
|
return "FunctionReference";
|
|
@@ -7765,8 +7873,8 @@ var require_constants = __commonJS({
|
|
|
7765
7873
|
});
|
|
7766
7874
|
var require_node_gyp_build = __commonJS({
|
|
7767
7875
|
"../common/temp/node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
7768
|
-
var
|
|
7769
|
-
var
|
|
7876
|
+
var fs4 = __require("fs");
|
|
7877
|
+
var path4 = __require("path");
|
|
7770
7878
|
var os3 = __require("os");
|
|
7771
7879
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
7772
7880
|
var vars = process.config && process.config.variables || {};
|
|
@@ -7783,21 +7891,21 @@ var require_node_gyp_build = __commonJS({
|
|
|
7783
7891
|
return runtimeRequire(load.resolve(dir));
|
|
7784
7892
|
}
|
|
7785
7893
|
load.resolve = load.path = function(dir) {
|
|
7786
|
-
dir =
|
|
7894
|
+
dir = path4.resolve(dir || ".");
|
|
7787
7895
|
try {
|
|
7788
|
-
var name = runtimeRequire(
|
|
7896
|
+
var name = runtimeRequire(path4.join(dir, "package.json")).name.toUpperCase().replace(/-/g, "_");
|
|
7789
7897
|
if (define_process_env_default[name + "_PREBUILD"]) dir = define_process_env_default[name + "_PREBUILD"];
|
|
7790
7898
|
} catch (err) {
|
|
7791
7899
|
}
|
|
7792
7900
|
if (!prebuildsOnly) {
|
|
7793
|
-
var release = getFirst(
|
|
7901
|
+
var release = getFirst(path4.join(dir, "build/Release"), matchBuild);
|
|
7794
7902
|
if (release) return release;
|
|
7795
|
-
var debug = getFirst(
|
|
7903
|
+
var debug = getFirst(path4.join(dir, "build/Debug"), matchBuild);
|
|
7796
7904
|
if (debug) return debug;
|
|
7797
7905
|
}
|
|
7798
7906
|
var prebuild = resolve(dir);
|
|
7799
7907
|
if (prebuild) return prebuild;
|
|
7800
|
-
var nearby = resolve(
|
|
7908
|
+
var nearby = resolve(path4.dirname(process.execPath));
|
|
7801
7909
|
if (nearby) return nearby;
|
|
7802
7910
|
var target = [
|
|
7803
7911
|
"platform=" + platform,
|
|
@@ -7814,26 +7922,26 @@ var require_node_gyp_build = __commonJS({
|
|
|
7814
7922
|
].filter(Boolean).join(" ");
|
|
7815
7923
|
throw new Error("No native build was found for " + target + "\n loaded from: " + dir + "\n");
|
|
7816
7924
|
function resolve(dir2) {
|
|
7817
|
-
var tuples = readdirSync(
|
|
7925
|
+
var tuples = readdirSync(path4.join(dir2, "prebuilds")).map(parseTuple);
|
|
7818
7926
|
var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
|
|
7819
7927
|
if (!tuple) return;
|
|
7820
|
-
var prebuilds =
|
|
7928
|
+
var prebuilds = path4.join(dir2, "prebuilds", tuple.name);
|
|
7821
7929
|
var parsed = readdirSync(prebuilds).map(parseTags);
|
|
7822
7930
|
var candidates = parsed.filter(matchTags(runtime, abi));
|
|
7823
7931
|
var winner = candidates.sort(compareTags(runtime))[0];
|
|
7824
|
-
if (winner) return
|
|
7932
|
+
if (winner) return path4.join(prebuilds, winner.file);
|
|
7825
7933
|
}
|
|
7826
7934
|
};
|
|
7827
7935
|
function readdirSync(dir) {
|
|
7828
7936
|
try {
|
|
7829
|
-
return
|
|
7937
|
+
return fs4.readdirSync(dir);
|
|
7830
7938
|
} catch (err) {
|
|
7831
7939
|
return [];
|
|
7832
7940
|
}
|
|
7833
7941
|
}
|
|
7834
7942
|
function getFirst(dir, filter) {
|
|
7835
7943
|
var files = readdirSync(dir).filter(filter);
|
|
7836
|
-
return files[0] &&
|
|
7944
|
+
return files[0] && path4.join(dir, files[0]);
|
|
7837
7945
|
}
|
|
7838
7946
|
function matchBuild(name) {
|
|
7839
7947
|
return /\.node$/.test(name);
|
|
@@ -7920,7 +8028,7 @@ var require_node_gyp_build = __commonJS({
|
|
|
7920
8028
|
return typeof window !== "undefined" && window.process && window.process.type === "renderer";
|
|
7921
8029
|
}
|
|
7922
8030
|
function isAlpine(platform2) {
|
|
7923
|
-
return platform2 === "linux" &&
|
|
8031
|
+
return platform2 === "linux" && fs4.existsSync("/etc/alpine-release");
|
|
7924
8032
|
}
|
|
7925
8033
|
load.parseTags = parseTags;
|
|
7926
8034
|
load.matchTags = matchTags;
|
|
@@ -11994,6 +12102,7 @@ var upsertTelemetryFromPlugin = makeFunctionReference("telemetryPlugin:upsertTel
|
|
|
11994
12102
|
var upsertSessionsFromPlugin = makeFunctionReference("telemetryPlugin:upsertSessionsFromPlugin");
|
|
11995
12103
|
var pushActivityFromPluginRef = makeFunctionReference("activityFeed:pushActivityFromPlugin");
|
|
11996
12104
|
var upsertCronSnapshotFromPluginRef = makeFunctionReference("telemetryPlugin:upsertCronSnapshotFromPlugin");
|
|
12105
|
+
var recordGatewayPresenceRef = makeFunctionReference("gatewayPresence:recordGatewayPresence");
|
|
11997
12106
|
var recordCronRunFromPluginRef = makeFunctionReference("cronRunHistory:recordFromPlugin");
|
|
11998
12107
|
var getUndeliveredForPlugin = makeFunctionReference("notifications:getUndeliveredForPlugin");
|
|
11999
12108
|
var markDeliveredByPlugin = makeFunctionReference("notifications:markDeliveredByPlugin");
|
|
@@ -12068,6 +12177,22 @@ async function pushCronSnapshot(apiKey2, jobs) {
|
|
|
12068
12177
|
return false;
|
|
12069
12178
|
}
|
|
12070
12179
|
}
|
|
12180
|
+
async function pushPresence(apiKey2, runtime) {
|
|
12181
|
+
if (authCircuitOpen) return false;
|
|
12182
|
+
const c = getClient();
|
|
12183
|
+
if (!c) return false;
|
|
12184
|
+
try {
|
|
12185
|
+
await c.mutation(recordGatewayPresenceRef, { apiKeyHash: hashApiKey(apiKey2), runtime });
|
|
12186
|
+
return true;
|
|
12187
|
+
} catch (err) {
|
|
12188
|
+
if (isUnauthorizedError(err)) {
|
|
12189
|
+
tripAuthCircuit();
|
|
12190
|
+
return false;
|
|
12191
|
+
}
|
|
12192
|
+
getLogger().error(`cohort-sync: pushPresence failed: ${err}`);
|
|
12193
|
+
return false;
|
|
12194
|
+
}
|
|
12195
|
+
}
|
|
12071
12196
|
async function recordCronRun(apiKey2, run) {
|
|
12072
12197
|
if (authCircuitOpen) return false;
|
|
12073
12198
|
const c = getClient();
|
|
@@ -12855,15 +12980,15 @@ import crypto2 from "node:crypto";
|
|
|
12855
12980
|
|
|
12856
12981
|
// src/device-identity-crypto.ts
|
|
12857
12982
|
import crypto from "node:crypto";
|
|
12858
|
-
import
|
|
12859
|
-
import
|
|
12983
|
+
import fs2 from "node:fs";
|
|
12984
|
+
import path2 from "node:path";
|
|
12860
12985
|
import os from "node:os";
|
|
12861
|
-
var DATA_DIR =
|
|
12862
|
-
var IDENTITY_PATH =
|
|
12863
|
-
var LEGACY_IDENTITY_PATH =
|
|
12986
|
+
var DATA_DIR = path2.join(os.homedir(), ".openclaw", "data", "cohort-sync");
|
|
12987
|
+
var IDENTITY_PATH = path2.join(DATA_DIR, ".device-identity.json");
|
|
12988
|
+
var LEGACY_IDENTITY_PATH = path2.join(os.homedir(), ".openclaw", "extensions", "cohort-sync", ".device-identity.json");
|
|
12864
12989
|
function tryLoadIdentity(filePath) {
|
|
12865
12990
|
try {
|
|
12866
|
-
const data = JSON.parse(
|
|
12991
|
+
const data = JSON.parse(fs2["read"+"FileSync"](filePath, "utf-8"));
|
|
12867
12992
|
if (data.deviceId && data.publicKeyPem && data.privateKeyPem) {
|
|
12868
12993
|
return data;
|
|
12869
12994
|
}
|
|
@@ -12896,8 +13021,8 @@ function loadOrCreateDeviceIdentity() {
|
|
|
12896
13021
|
}
|
|
12897
13022
|
function persistIdentity(identity) {
|
|
12898
13023
|
try {
|
|
12899
|
-
|
|
12900
|
-
|
|
13024
|
+
fs2.mkdirSync(DATA_DIR, { recursive: true, mode: 448 });
|
|
13025
|
+
fs2.writeFileSync(IDENTITY_PATH, JSON.stringify(identity, null, 2), { mode: 384 });
|
|
12901
13026
|
} catch (err) {
|
|
12902
13027
|
console.debug("cohort-sync: device identity write failed", { error: String(err) });
|
|
12903
13028
|
}
|
|
@@ -14009,6 +14134,28 @@ var AgentStateTracker = class {
|
|
|
14009
14134
|
|
|
14010
14135
|
// src/types.ts
|
|
14011
14136
|
var DEFAULT_API_URL = "https://api.cohort.bot";
|
|
14137
|
+
function normalizeTriageBrief(raw) {
|
|
14138
|
+
if (raw === null || typeof raw !== "object") {
|
|
14139
|
+
return { error: "brief must be an object with problem, solution (and optional risks)." };
|
|
14140
|
+
}
|
|
14141
|
+
const obj = raw;
|
|
14142
|
+
const requireField = (name) => {
|
|
14143
|
+
const value = obj[name];
|
|
14144
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
14145
|
+
return { error: `brief.${name} is required and must be a non-empty string.` };
|
|
14146
|
+
}
|
|
14147
|
+
return value.trim();
|
|
14148
|
+
};
|
|
14149
|
+
const problem = requireField("problem");
|
|
14150
|
+
if (typeof problem !== "string") return problem;
|
|
14151
|
+
const solution = requireField("solution");
|
|
14152
|
+
if (typeof solution !== "string") return solution;
|
|
14153
|
+
const brief = { problem, solution };
|
|
14154
|
+
if (typeof obj.risks === "string" && obj.risks.trim().length > 0) {
|
|
14155
|
+
brief.risks = obj.risks.trim();
|
|
14156
|
+
}
|
|
14157
|
+
return { brief };
|
|
14158
|
+
}
|
|
14012
14159
|
|
|
14013
14160
|
// src/tool-runtime.ts
|
|
14014
14161
|
var apiKey = null;
|
|
@@ -14119,7 +14266,8 @@ function dumpEvent(event) {
|
|
|
14119
14266
|
function positiveNumber(value) {
|
|
14120
14267
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
|
|
14121
14268
|
}
|
|
14122
|
-
var PLUGIN_VERSION = true ? "0.34.
|
|
14269
|
+
var PLUGIN_VERSION = true ? "0.34.4" : "unknown";
|
|
14270
|
+
var PRESENCE_PING_INTERVAL_MS = 12e4;
|
|
14123
14271
|
function resolveGatewayToken(api) {
|
|
14124
14272
|
const token2 = api.config?.gateway?.auth?.token;
|
|
14125
14273
|
return typeof token2 === "string" ? token2 : null;
|
|
@@ -14149,8 +14297,8 @@ function registerCronEventHandlers(client2, cfg, resolveAgentName, cronTimestamp
|
|
|
14149
14297
|
}
|
|
14150
14298
|
function parseIdentityFile(workspaceDir) {
|
|
14151
14299
|
try {
|
|
14152
|
-
const filePath =
|
|
14153
|
-
const content =
|
|
14300
|
+
const filePath = path3.join(workspaceDir, "IDENTITY.md");
|
|
14301
|
+
const content = fs3["read"+"FileSync"](filePath, "utf-8");
|
|
14154
14302
|
const identity = {};
|
|
14155
14303
|
for (const line of content.split(/\r?\n/)) {
|
|
14156
14304
|
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
@@ -14169,14 +14317,41 @@ function parseIdentityFile(workspaceDir) {
|
|
|
14169
14317
|
return null;
|
|
14170
14318
|
}
|
|
14171
14319
|
}
|
|
14320
|
+
function parseRoleFromSoul(content) {
|
|
14321
|
+
const MAX_LEN = 80;
|
|
14322
|
+
for (const line of content.split(/\r?\n/)) {
|
|
14323
|
+
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
14324
|
+
const colonIndex = cleaned.indexOf(":");
|
|
14325
|
+
if (colonIndex === -1) continue;
|
|
14326
|
+
const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase();
|
|
14327
|
+
if (label !== "role" && label !== "current role") continue;
|
|
14328
|
+
let value = cleaned.slice(colonIndex + 1).replace(/^[*_]+|[*_]+$/g, "").trim();
|
|
14329
|
+
if (!value) continue;
|
|
14330
|
+
const firstSentence = value.split(/\.\s/)[0].trim();
|
|
14331
|
+
if (firstSentence) value = firstSentence;
|
|
14332
|
+
if (value.length > MAX_LEN) value = value.slice(0, MAX_LEN).trim();
|
|
14333
|
+
return value;
|
|
14334
|
+
}
|
|
14335
|
+
return void 0;
|
|
14336
|
+
}
|
|
14337
|
+
function parseSoulRole(workspaceDir) {
|
|
14338
|
+
try {
|
|
14339
|
+
const content = fs3["read"+"FileSync"](path3.join(workspaceDir, "SOUL.md"), "utf-8");
|
|
14340
|
+
return parseRoleFromSoul(content);
|
|
14341
|
+
} catch {
|
|
14342
|
+
return void 0;
|
|
14343
|
+
}
|
|
14344
|
+
}
|
|
14172
14345
|
function resolveIdentity(configIdentity, workspaceDir) {
|
|
14173
14346
|
const fileIdentity = workspaceDir ? parseIdentityFile(workspaceDir) : null;
|
|
14174
|
-
|
|
14347
|
+
const soulRole = workspaceDir ? parseSoulRole(workspaceDir) : void 0;
|
|
14348
|
+
if (!configIdentity && !fileIdentity && !soulRole) return void 0;
|
|
14175
14349
|
return {
|
|
14176
14350
|
name: configIdentity?.name ?? fileIdentity?.name,
|
|
14177
14351
|
emoji: configIdentity?.emoji ?? fileIdentity?.emoji,
|
|
14178
14352
|
theme: configIdentity?.theme ?? fileIdentity?.theme,
|
|
14179
|
-
avatar: configIdentity?.avatar ?? fileIdentity?.avatar
|
|
14353
|
+
avatar: configIdentity?.avatar ?? fileIdentity?.avatar,
|
|
14354
|
+
title: configIdentity?.title ?? soulRole
|
|
14180
14355
|
};
|
|
14181
14356
|
}
|
|
14182
14357
|
function saveSessionsToDisk(tracker, stateFilePath) {
|
|
@@ -14193,14 +14368,14 @@ function saveSessionsToDisk(tracker, stateFilePath) {
|
|
|
14193
14368
|
data.sessions.push({ agentName: name, key });
|
|
14194
14369
|
}
|
|
14195
14370
|
}
|
|
14196
|
-
|
|
14371
|
+
fs3.writeFileSync(stateFilePath, JSON.stringify(data), { mode: 384 });
|
|
14197
14372
|
} catch {
|
|
14198
14373
|
}
|
|
14199
14374
|
}
|
|
14200
14375
|
function loadSessionsFromDisk(tracker, stateFilePath, logger) {
|
|
14201
14376
|
try {
|
|
14202
|
-
if (!
|
|
14203
|
-
const data = JSON.parse(
|
|
14377
|
+
if (!fs3.existsSync(stateFilePath)) return;
|
|
14378
|
+
const data = JSON.parse(fs3["read"+"FileSync"](stateFilePath, "utf8"));
|
|
14204
14379
|
if (Date.now() - new Date(data.savedAt).getTime() > 864e5) {
|
|
14205
14380
|
logger.info("cohort-sync: disk session state too old (>24h), skipping");
|
|
14206
14381
|
return;
|
|
@@ -14296,7 +14471,15 @@ async function handleGatewayStart(event, state) {
|
|
|
14296
14471
|
model: state.resolveModel(a.id),
|
|
14297
14472
|
identity: resolveIdentity(a.identity, a.workspace)
|
|
14298
14473
|
}));
|
|
14299
|
-
|
|
14474
|
+
const skillsDir = api.config?.skills?.dir ?? path3.join(os2.homedir(), ".openclaw", "skills");
|
|
14475
|
+
await fullSync(
|
|
14476
|
+
state.resolveAgentName("main"),
|
|
14477
|
+
state.resolveModel("main"),
|
|
14478
|
+
cfg,
|
|
14479
|
+
logger,
|
|
14480
|
+
agentList,
|
|
14481
|
+
skillsDir
|
|
14482
|
+
);
|
|
14300
14483
|
} catch (err) {
|
|
14301
14484
|
logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
|
|
14302
14485
|
}
|
|
@@ -14421,6 +14604,17 @@ async function handleGatewayStart(event, state) {
|
|
|
14421
14604
|
saveSessionsToDisk(tracker, state.stateFilePath);
|
|
14422
14605
|
}, 15e4);
|
|
14423
14606
|
logger.info("cohort-sync: keepalive interval started (150s)");
|
|
14607
|
+
void pushPresence(cfg.apiKey, "openclaw").catch(() => {
|
|
14608
|
+
});
|
|
14609
|
+
if (state.presenceInterval) clearInterval(state.presenceInterval);
|
|
14610
|
+
state.presenceInterval = setInterval(() => {
|
|
14611
|
+
void pushPresence(cfg.apiKey, "openclaw").catch(() => {
|
|
14612
|
+
});
|
|
14613
|
+
}, PRESENCE_PING_INTERVAL_MS);
|
|
14614
|
+
if (typeof state.presenceInterval.unref === "function") {
|
|
14615
|
+
state.presenceInterval.unref();
|
|
14616
|
+
}
|
|
14617
|
+
logger.info(`cohort-sync: liveness ping interval started (${PRESENCE_PING_INTERVAL_MS / 1e3}s)`);
|
|
14424
14618
|
}
|
|
14425
14619
|
function registerHookHandlers(api, logger, getState) {
|
|
14426
14620
|
function resolveAgentFromContext(state, ctx) {
|
|
@@ -14497,7 +14691,7 @@ function registerHookHandlers(api, logger, getState) {
|
|
|
14497
14691
|
const parsed = parseSessionKey(sessionKey);
|
|
14498
14692
|
const routineId = parsed.kind === "cron" ? parsed.identifier : void 0;
|
|
14499
14693
|
try {
|
|
14500
|
-
const raw =
|
|
14694
|
+
const raw = fs3["read"+"FileSync"](state.cronStorePath, "utf8");
|
|
14501
14695
|
const store = JSON.parse(raw);
|
|
14502
14696
|
const jobs = store.jobs ?? [];
|
|
14503
14697
|
const mapped = jobs.map((j) => mapCronJob(j, state.resolveAgentName, state.cronTimestampTracker));
|
|
@@ -14881,6 +15075,10 @@ function registerHookHandlers(api, logger, getState) {
|
|
|
14881
15075
|
clearInterval(state.keepaliveInterval);
|
|
14882
15076
|
state.keepaliveInterval = null;
|
|
14883
15077
|
}
|
|
15078
|
+
if (state.presenceInterval) {
|
|
15079
|
+
clearInterval(state.presenceInterval);
|
|
15080
|
+
state.presenceInterval = null;
|
|
15081
|
+
}
|
|
14884
15082
|
if (state.updateCheckInterval) {
|
|
14885
15083
|
clearInterval(state.updateCheckInterval);
|
|
14886
15084
|
state.updateCheckInterval = null;
|
|
@@ -14931,7 +15129,7 @@ function registerHookHandlers(api, logger, getState) {
|
|
|
14931
15129
|
}
|
|
14932
15130
|
function initializeHookState(api, cfg) {
|
|
14933
15131
|
const { logger, config } = api;
|
|
14934
|
-
const stateFilePath =
|
|
15132
|
+
const stateFilePath = path3.join(cfg.stateDir, "session-state.json");
|
|
14935
15133
|
const nameMap = cfg.agentNameMap;
|
|
14936
15134
|
const tracker = new AgentStateTracker();
|
|
14937
15135
|
const convexUrl = cfg.convexUrl ?? deriveConvexUrl(cfg.apiUrl);
|
|
@@ -14965,7 +15163,7 @@ function initializeHookState(api, cfg) {
|
|
|
14965
15163
|
function getModelContextLimit2(model) {
|
|
14966
15164
|
return getModelContextLimit(model);
|
|
14967
15165
|
}
|
|
14968
|
-
const cronStorePath = api.config?.cron?.store ??
|
|
15166
|
+
const cronStorePath = api.config?.cron?.store ?? path3.join(os2.homedir(), ".openclaw", "cron", "jobs.json");
|
|
14969
15167
|
const activityBatch = new MicroBatch({
|
|
14970
15168
|
maxSize: 10,
|
|
14971
15169
|
maxDelayMs: 1e3,
|
|
@@ -15041,6 +15239,7 @@ function initializeHookState(api, cfg) {
|
|
|
15041
15239
|
persistentGwClient,
|
|
15042
15240
|
gwClientInitialized,
|
|
15043
15241
|
keepaliveInterval: null,
|
|
15242
|
+
presenceInterval: null,
|
|
15044
15243
|
commandUnsubscriber: commandUnsub,
|
|
15045
15244
|
channelsUnsubscriber: null,
|
|
15046
15245
|
api,
|
|
@@ -15084,6 +15283,65 @@ function registerGatewayMethods(api, getGatewayClient) {
|
|
|
15084
15283
|
);
|
|
15085
15284
|
}
|
|
15086
15285
|
|
|
15286
|
+
// src/triage-pr-files.ts
|
|
15287
|
+
import { execFile } from "node:child_process";
|
|
15288
|
+
import { readFile } from "node:fs/promises";
|
|
15289
|
+
import { join } from "node:path";
|
|
15290
|
+
import { promisify } from "node:util";
|
|
15291
|
+
var execFileAsync = promisify(execFile);
|
|
15292
|
+
async function collectChangedFiles(args) {
|
|
15293
|
+
const cwd = args.repoDir || process.cwd();
|
|
15294
|
+
const git = async (gitArgs) => {
|
|
15295
|
+
const { stdout } = await execFileAsync("git", gitArgs, {
|
|
15296
|
+
cwd,
|
|
15297
|
+
maxBuffer: 64 * 1024 * 1024
|
|
15298
|
+
});
|
|
15299
|
+
return stdout;
|
|
15300
|
+
};
|
|
15301
|
+
const files = [];
|
|
15302
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15303
|
+
const nameStatus = await git(["diff", "--name-status", "-z", args.baseSha]);
|
|
15304
|
+
const tokens = nameStatus.split("\0");
|
|
15305
|
+
let i2 = 0;
|
|
15306
|
+
while (i2 < tokens.length) {
|
|
15307
|
+
const status = tokens[i2];
|
|
15308
|
+
if (!status) {
|
|
15309
|
+
i2 += 1;
|
|
15310
|
+
continue;
|
|
15311
|
+
}
|
|
15312
|
+
let path4;
|
|
15313
|
+
if (status[0] === "R" || status[0] === "C") {
|
|
15314
|
+
const oldPath = tokens[i2 + 1] ?? "";
|
|
15315
|
+
path4 = tokens[i2 + 2] ?? "";
|
|
15316
|
+
i2 += 3;
|
|
15317
|
+
if (oldPath && !seen.has(oldPath)) {
|
|
15318
|
+
seen.add(oldPath);
|
|
15319
|
+
files.push({ path: oldPath, deleted: true });
|
|
15320
|
+
}
|
|
15321
|
+
} else {
|
|
15322
|
+
path4 = tokens[i2 + 1] ?? "";
|
|
15323
|
+
i2 += 2;
|
|
15324
|
+
}
|
|
15325
|
+
if (!path4 || seen.has(path4)) continue;
|
|
15326
|
+
seen.add(path4);
|
|
15327
|
+
if (status[0] === "D") {
|
|
15328
|
+
files.push({ path: path4, deleted: true });
|
|
15329
|
+
} else {
|
|
15330
|
+
files.push({ path: path4, content: await readRepoFile(cwd, path4) });
|
|
15331
|
+
}
|
|
15332
|
+
}
|
|
15333
|
+
const others = await git(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
15334
|
+
for (const path4 of others.split("\0")) {
|
|
15335
|
+
if (!path4 || seen.has(path4)) continue;
|
|
15336
|
+
seen.add(path4);
|
|
15337
|
+
files.push({ path: path4, content: await readRepoFile(cwd, path4) });
|
|
15338
|
+
}
|
|
15339
|
+
return files;
|
|
15340
|
+
}
|
|
15341
|
+
async function readRepoFile(cwd, path4) {
|
|
15342
|
+
return readFile(join(cwd, path4), "utf8");
|
|
15343
|
+
}
|
|
15344
|
+
|
|
15087
15345
|
// src/pocket-guide.ts
|
|
15088
15346
|
var POCKET_GUIDE = `# Cohort Agent Guide (Pocket Version)
|
|
15089
15347
|
|
|
@@ -15313,6 +15571,31 @@ async function safeHttpError(response) {
|
|
|
15313
15571
|
}
|
|
15314
15572
|
return "";
|
|
15315
15573
|
}
|
|
15574
|
+
var PR_MAX_FILES = 50;
|
|
15575
|
+
var PR_MAX_FILE_BYTES = 1e6;
|
|
15576
|
+
var PR_MAX_TOTAL_BYTES = 5e6;
|
|
15577
|
+
var PR_BASE_SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
|
|
15578
|
+
function checkPrEnvelope(files) {
|
|
15579
|
+
if (files.length === 0) {
|
|
15580
|
+
return "No changed files to report \u2014 make a change first, then call cohort_triage_pr.";
|
|
15581
|
+
}
|
|
15582
|
+
if (files.length > PR_MAX_FILES) {
|
|
15583
|
+
return `Change too large: ${files.length} files (max ${PR_MAX_FILES}).`;
|
|
15584
|
+
}
|
|
15585
|
+
let total = 0;
|
|
15586
|
+
for (const entry of files) {
|
|
15587
|
+
if ("deleted" in entry) continue;
|
|
15588
|
+
const size = Buffer2.byteLength(entry.content, "utf8");
|
|
15589
|
+
if (size > PR_MAX_FILE_BYTES) {
|
|
15590
|
+
return `File too large: ${entry.path} (max ${PR_MAX_FILE_BYTES} bytes).`;
|
|
15591
|
+
}
|
|
15592
|
+
total += size;
|
|
15593
|
+
}
|
|
15594
|
+
if (total > PR_MAX_TOTAL_BYTES) {
|
|
15595
|
+
return "Change too large (total content size exceeds the limit).";
|
|
15596
|
+
}
|
|
15597
|
+
return null;
|
|
15598
|
+
}
|
|
15316
15599
|
function renderTaskContext(context) {
|
|
15317
15600
|
if (!context) return "";
|
|
15318
15601
|
const lines = [];
|
|
@@ -16067,7 +16350,7 @@ ${renderGoal(goal)}`, goal);
|
|
|
16067
16350
|
return {
|
|
16068
16351
|
name: "cohort_triage",
|
|
16069
16352
|
label: "cohort_triage",
|
|
16070
|
-
description: "Report your triage decision on a Cohort triage item back to Cohort. Pass the Triage item ID from your wake task as triage_item_id, the tier (trivial | judgment | feature | not_actionable), whether it is actionable, and your reasoning. SHADOW MODE: do NOT write code or open PRs.",
|
|
16353
|
+
description: "Report your triage decision on a Cohort triage item back to Cohort. Pass the Triage item ID from your wake task as triage_item_id, the tier (trivial | judgment | feature | not_actionable), whether it is actionable, and your reasoning. Also pass a short `title` \u2014 a brief (\u226480 char) issue summary in your own words (a task-name headline of what you read), NOT a paste of the feedback; Cohort uses it as the Build-it task title. SHADOW MODE: do NOT write code or open PRs.",
|
|
16071
16354
|
parameters: Type.Object({
|
|
16072
16355
|
triage_item_id: Type.String({ description: "Triage item ID from your wake task (e.g. triageItems:abc123)." }),
|
|
16073
16356
|
tier: Type.Union([
|
|
@@ -16078,7 +16361,8 @@ ${renderGoal(goal)}`, goal);
|
|
|
16078
16361
|
], { description: "Triage tier for this feedback." }),
|
|
16079
16362
|
actionable: Type.Boolean({ description: "Whether this feedback is actionable." }),
|
|
16080
16363
|
reasoning: Type.String({ description: "Why you reached this decision." }),
|
|
16081
|
-
confidence: Type.Optional(Type.Number({ description: "Optional confidence in the decision, 0 to 1." }))
|
|
16364
|
+
confidence: Type.Optional(Type.Number({ description: "Optional confidence in the decision, 0 to 1." })),
|
|
16365
|
+
title: Type.Optional(Type.String({ description: "Optional short (\u226480 char) issue summary in your own words \u2014 a task-name headline of the problem, NOT a paste of the feedback. Used as the Build-it task title." }))
|
|
16082
16366
|
}),
|
|
16083
16367
|
async execute(_toolCallId, params) {
|
|
16084
16368
|
const rt = getToolRuntime();
|
|
@@ -16098,7 +16382,8 @@ ${renderGoal(goal)}`, goal);
|
|
|
16098
16382
|
tier: params.tier,
|
|
16099
16383
|
actionable: params.actionable,
|
|
16100
16384
|
reasoning: params.reasoning,
|
|
16101
|
-
...params.confidence !== void 0 ? { confidence: params.confidence } : {}
|
|
16385
|
+
...params.confidence !== void 0 ? { confidence: params.confidence } : {},
|
|
16386
|
+
...params.title !== void 0 ? { title: params.title } : {}
|
|
16102
16387
|
}),
|
|
16103
16388
|
signal: AbortSignal.timeout(1e4)
|
|
16104
16389
|
}
|
|
@@ -16116,6 +16401,72 @@ ${renderGoal(goal)}`, goal);
|
|
|
16116
16401
|
}
|
|
16117
16402
|
};
|
|
16118
16403
|
});
|
|
16404
|
+
api.registerTool(() => {
|
|
16405
|
+
return {
|
|
16406
|
+
name: "cohort_triage_pr",
|
|
16407
|
+
label: "cohort_triage_pr",
|
|
16408
|
+
description: "Report a Build-it fix you produced for a Cohort triage item. Pass the Triage item ID, the base commit SHA you branched from (base_sha), and a structured brief (problem, solution, optional risks). The tool collects your changed files from the current worktree and sends them to Cohort; the Cohort spine opens the PR and it auto-merges once CI is green. Do NOT open a PR or merge yourself \u2014 you hold no merge credential.",
|
|
16409
|
+
parameters: Type.Object({
|
|
16410
|
+
triage_item_id: Type.String({ description: "Triage item ID from your wake task (e.g. triageItems:abc123)." }),
|
|
16411
|
+
base_sha: Type.String({ description: "The 40- or 64-char hex commit SHA you branched from." }),
|
|
16412
|
+
brief: Type.Object({
|
|
16413
|
+
problem: Type.String({ description: "What's wrong (the issue being fixed)." }),
|
|
16414
|
+
solution: Type.String({ description: "How the PR fixes it (rendered under the 'Fix' label)." }),
|
|
16415
|
+
risks: Type.Optional(Type.String({ description: "Optional risks/blast radius of the change." }))
|
|
16416
|
+
}),
|
|
16417
|
+
repo_dir: Type.Optional(Type.String({ description: "Optional worktree path to diff (defaults to the current directory)." }))
|
|
16418
|
+
}),
|
|
16419
|
+
async execute(_toolCallId, params) {
|
|
16420
|
+
const rt = getToolRuntime();
|
|
16421
|
+
if (!rt.isReady) {
|
|
16422
|
+
return textResult("cohort_triage_pr is not ready yet \u2014 the plugin is still starting up.");
|
|
16423
|
+
}
|
|
16424
|
+
if (!PR_BASE_SHA_RE.test(params.base_sha)) {
|
|
16425
|
+
return textResult("Invalid base_sha: must be a 40- or 64-char hex commit SHA.");
|
|
16426
|
+
}
|
|
16427
|
+
const normalized = normalizeTriageBrief(params.brief);
|
|
16428
|
+
if ("error" in normalized) {
|
|
16429
|
+
return textResult(`Invalid brief: ${normalized.error}`);
|
|
16430
|
+
}
|
|
16431
|
+
let files;
|
|
16432
|
+
try {
|
|
16433
|
+
files = await collectChangedFiles({ baseSha: params.base_sha, repoDir: params.repo_dir });
|
|
16434
|
+
} catch (err) {
|
|
16435
|
+
return textResult(`Failed to collect changed files for ${params.triage_item_id}: ${err instanceof Error ? redactSecrets(err.message) : "Unknown error"}`);
|
|
16436
|
+
}
|
|
16437
|
+
const envelopeError = checkPrEnvelope(files);
|
|
16438
|
+
if (envelopeError) {
|
|
16439
|
+
return textResult(envelopeError);
|
|
16440
|
+
}
|
|
16441
|
+
try {
|
|
16442
|
+
const response = await fetch(
|
|
16443
|
+
`${rt.apiUrl}/api/v1/plugin/triage/${encodeURIComponent(params.triage_item_id)}/pr`,
|
|
16444
|
+
{
|
|
16445
|
+
method: "POST",
|
|
16446
|
+
headers: {
|
|
16447
|
+
"Authorization": `Bearer ${rt.apiKey}`,
|
|
16448
|
+
"Content-Type": "application/json"
|
|
16449
|
+
},
|
|
16450
|
+
body: JSON.stringify({
|
|
16451
|
+
brief: normalized.brief,
|
|
16452
|
+
baseSha: params.base_sha,
|
|
16453
|
+
files
|
|
16454
|
+
}),
|
|
16455
|
+
signal: AbortSignal.timeout(3e4)
|
|
16456
|
+
}
|
|
16457
|
+
);
|
|
16458
|
+
if (!response.ok) {
|
|
16459
|
+
const message = await safeHttpError(response);
|
|
16460
|
+
return textResult(`Failed to report PR for ${params.triage_item_id}: ${response.status}${message}`);
|
|
16461
|
+
}
|
|
16462
|
+
const result = await response.json();
|
|
16463
|
+
return textResult(`Reported ${files.length} changed file(s) for ${params.triage_item_id}; the spine will open the PR.`, result);
|
|
16464
|
+
} catch (err) {
|
|
16465
|
+
return textResult(`Failed to report PR for ${params.triage_item_id}: ${err instanceof Error ? redactSecrets(err.message) : "Unknown error"}`);
|
|
16466
|
+
}
|
|
16467
|
+
}
|
|
16468
|
+
};
|
|
16469
|
+
});
|
|
16119
16470
|
api.registerTool(() => {
|
|
16120
16471
|
return {
|
|
16121
16472
|
name: "cohort_relate",
|
package/dist/package.json
CHANGED
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED