@cfio/cohort-sync 0.6.0 → 0.7.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/dist/index.js +81 -50
- package/dist/openclaw.plugin.json +11 -1
- package/dist/package.json +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -16,6 +16,14 @@ __export(keychain_exports, {
|
|
|
16
16
|
setCredential: () => setCredential
|
|
17
17
|
});
|
|
18
18
|
import { execFile } from "node:child_process";
|
|
19
|
+
import os3 from "node:os";
|
|
20
|
+
function assertMacOS(operation) {
|
|
21
|
+
if (os3.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
|
+
}
|
|
19
27
|
function securityCmd(args) {
|
|
20
28
|
return new Promise((resolve, reject) => {
|
|
21
29
|
execFile("security", args, { timeout: 5e3 }, (err, stdout, stderr) => {
|
|
@@ -37,6 +45,7 @@ function isNotFoundError(err) {
|
|
|
37
45
|
return false;
|
|
38
46
|
}
|
|
39
47
|
async function setCredential(apiUrl, apiKey) {
|
|
48
|
+
assertMacOS("storing credentials");
|
|
40
49
|
await securityCmd([
|
|
41
50
|
"add-generic-password",
|
|
42
51
|
"-s",
|
|
@@ -49,6 +58,7 @@ async function setCredential(apiUrl, apiKey) {
|
|
|
49
58
|
]);
|
|
50
59
|
}
|
|
51
60
|
async function getCredential(apiUrl) {
|
|
61
|
+
assertMacOS("reading credentials");
|
|
52
62
|
try {
|
|
53
63
|
const { stdout } = await securityCmd([
|
|
54
64
|
"find-generic-password",
|
|
@@ -65,6 +75,7 @@ async function getCredential(apiUrl) {
|
|
|
65
75
|
}
|
|
66
76
|
}
|
|
67
77
|
async function deleteCredential(apiUrl) {
|
|
78
|
+
assertMacOS("deleting credentials");
|
|
68
79
|
try {
|
|
69
80
|
await securityCmd([
|
|
70
81
|
"delete-generic-password",
|
|
@@ -7630,14 +7641,14 @@ var require_node_gyp_build = __commonJS({
|
|
|
7630
7641
|
"../common/temp/node_modules/.pnpm/node-gyp-build@4.8.4/node_modules/node-gyp-build/node-gyp-build.js"(exports, module) {
|
|
7631
7642
|
var fs4 = __require("fs");
|
|
7632
7643
|
var path4 = __require("path");
|
|
7633
|
-
var
|
|
7644
|
+
var os4 = __require("os");
|
|
7634
7645
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
7635
7646
|
var vars = process.config && process.config.variables || {};
|
|
7636
7647
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
7637
7648
|
var abi = process.versions.modules;
|
|
7638
7649
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
7639
|
-
var arch = process.env.npm_config_arch ||
|
|
7640
|
-
var platform = process.env.npm_config_platform ||
|
|
7650
|
+
var arch = process.env.npm_config_arch || os4.arch();
|
|
7651
|
+
var platform = process.env.npm_config_platform || os4.platform();
|
|
7641
7652
|
var libc = process.env.LIBC || (isAlpine(platform) ? "musl" : "glibc");
|
|
7642
7653
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
7643
7654
|
var uv = (process.versions.uv || "").split(".")[0];
|
|
@@ -11627,6 +11638,7 @@ var pushActivityFromPluginRef = makeFunctionReference("activityFeed:pushActivity
|
|
|
11627
11638
|
var upsertCronSnapshotFromPluginRef = makeFunctionReference("telemetryPlugin:upsertCronSnapshotFromPlugin");
|
|
11628
11639
|
var getPendingCommandsForPlugin = makeFunctionReference("gatewayCommands:getPendingForPlugin");
|
|
11629
11640
|
var acknowledgeCommandRef = makeFunctionReference("gatewayCommands:acknowledgeCommand");
|
|
11641
|
+
var failCommandRef = makeFunctionReference("gatewayCommands:failCommand");
|
|
11630
11642
|
var addCommentFromPluginRef = makeFunctionReference("comments:addCommentFromPlugin");
|
|
11631
11643
|
var HOT_KEY = "__cohort_sync__";
|
|
11632
11644
|
function getHotState() {
|
|
@@ -11644,7 +11656,8 @@ function getHotState() {
|
|
|
11644
11656
|
gatewayPort: null,
|
|
11645
11657
|
gatewayToken: null,
|
|
11646
11658
|
gatewayProtocolClient: null,
|
|
11647
|
-
commandSubscription: null
|
|
11659
|
+
commandSubscription: null,
|
|
11660
|
+
cronRunNowPoll: null
|
|
11648
11661
|
};
|
|
11649
11662
|
globalThis[HOT_KEY] = state;
|
|
11650
11663
|
}
|
|
@@ -11689,6 +11702,13 @@ function restoreFromHotReload(logger) {
|
|
|
11689
11702
|
client = state.client;
|
|
11690
11703
|
savedConvexUrl = state.convexUrl;
|
|
11691
11704
|
logger.info("cohort-sync: recovered ConvexClient after hot-reload");
|
|
11705
|
+
} else if (client && state.client && client !== state.client) {
|
|
11706
|
+
try {
|
|
11707
|
+
state.client.close();
|
|
11708
|
+
} catch {
|
|
11709
|
+
}
|
|
11710
|
+
state.client = client;
|
|
11711
|
+
logger.info("cohort-sync: closed orphaned ConvexClient from hot state");
|
|
11692
11712
|
}
|
|
11693
11713
|
if (unsubscribers.length === 0 && state.unsubscribers.length > 0) {
|
|
11694
11714
|
unsubscribers.push(...state.unsubscribers);
|
|
@@ -11837,16 +11857,20 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11837
11857
|
if (runResult?.ok && runResult?.ran) {
|
|
11838
11858
|
const jobId = cmd.payload?.jobId;
|
|
11839
11859
|
let polls = 0;
|
|
11860
|
+
const hs = getHotState();
|
|
11861
|
+
if (hs.cronRunNowPoll) clearInterval(hs.cronRunNowPoll);
|
|
11840
11862
|
const pollInterval = setInterval(async () => {
|
|
11841
11863
|
polls++;
|
|
11842
11864
|
if (polls >= 15) {
|
|
11843
11865
|
clearInterval(pollInterval);
|
|
11866
|
+
hs.cronRunNowPoll = null;
|
|
11844
11867
|
return;
|
|
11845
11868
|
}
|
|
11846
11869
|
try {
|
|
11847
11870
|
const pollClient = getHotState().gatewayProtocolClient;
|
|
11848
11871
|
if (!pollClient || !pollClient.isAlive()) {
|
|
11849
11872
|
clearInterval(pollInterval);
|
|
11873
|
+
hs.cronRunNowPoll = null;
|
|
11850
11874
|
return;
|
|
11851
11875
|
}
|
|
11852
11876
|
const pollResult = await pollClient.request("cron.list");
|
|
@@ -11854,12 +11878,14 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11854
11878
|
const job = freshJobs.find((j) => j.id === jobId);
|
|
11855
11879
|
if (job && !job.state?.runningAtMs) {
|
|
11856
11880
|
clearInterval(pollInterval);
|
|
11881
|
+
hs.cronRunNowPoll = null;
|
|
11857
11882
|
const mapped = freshJobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
11858
11883
|
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
11859
11884
|
}
|
|
11860
11885
|
} catch {
|
|
11861
11886
|
}
|
|
11862
11887
|
}, 2e3);
|
|
11888
|
+
hs.cronRunNowPoll = pollInterval;
|
|
11863
11889
|
}
|
|
11864
11890
|
break;
|
|
11865
11891
|
}
|
|
@@ -11911,6 +11937,15 @@ function initCommandSubscription(cfg, logger, resolveAgentName) {
|
|
|
11911
11937
|
}
|
|
11912
11938
|
} catch (err) {
|
|
11913
11939
|
logger.error(`cohort-sync: failed to process command ${cmd._id}: ${String(err)}`);
|
|
11940
|
+
try {
|
|
11941
|
+
await c.mutation(failCommandRef, {
|
|
11942
|
+
commandId: cmd._id,
|
|
11943
|
+
apiKey: cfg.apiKey,
|
|
11944
|
+
reason: String(err).slice(0, 500)
|
|
11945
|
+
});
|
|
11946
|
+
} catch (failErr) {
|
|
11947
|
+
logger.error(`cohort-sync: failed to mark command ${cmd._id} as failed: ${String(failErr)}`);
|
|
11948
|
+
}
|
|
11914
11949
|
}
|
|
11915
11950
|
}
|
|
11916
11951
|
} finally {
|
|
@@ -12015,7 +12050,9 @@ function clearIntervalsFromHot() {
|
|
|
12015
12050
|
const state = getHotState();
|
|
12016
12051
|
if (state.intervals.heartbeat) clearInterval(state.intervals.heartbeat);
|
|
12017
12052
|
if (state.intervals.activityFlush) clearInterval(state.intervals.activityFlush);
|
|
12053
|
+
if (state.cronRunNowPoll) clearInterval(state.cronRunNowPoll);
|
|
12018
12054
|
state.intervals = { heartbeat: null, activityFlush: null };
|
|
12055
|
+
state.cronRunNowPoll = null;
|
|
12019
12056
|
}
|
|
12020
12057
|
function addActivityToHot(entry) {
|
|
12021
12058
|
const state = getHotState();
|
|
@@ -13421,10 +13458,7 @@ function getOrCreateTracker() {
|
|
|
13421
13458
|
state.tracker = fresh;
|
|
13422
13459
|
return fresh;
|
|
13423
13460
|
}
|
|
13424
|
-
var STATE_FILE_PATH =
|
|
13425
|
-
path3.dirname(new URL(import.meta.url).pathname),
|
|
13426
|
-
".session-state.json"
|
|
13427
|
-
);
|
|
13461
|
+
var STATE_FILE_PATH = "";
|
|
13428
13462
|
function saveSessionsToDisk(tracker) {
|
|
13429
13463
|
try {
|
|
13430
13464
|
const state = tracker.exportState();
|
|
@@ -13488,6 +13522,7 @@ async function fetchAgentContext(apiKey, apiUrl, logger) {
|
|
|
13488
13522
|
}
|
|
13489
13523
|
}
|
|
13490
13524
|
function registerHooks(api, cfg) {
|
|
13525
|
+
STATE_FILE_PATH = path3.join(cfg.stateDir, "session-state.json");
|
|
13491
13526
|
const { logger, config } = api;
|
|
13492
13527
|
const nameMap = cfg.agentNameMap;
|
|
13493
13528
|
const tracker = getOrCreateTracker();
|
|
@@ -13612,18 +13647,19 @@ function registerHooks(api, cfg) {
|
|
|
13612
13647
|
logger.info(`cohort-sync: flushed ${pendingActivity.length} pending activity entries before hot-reload`);
|
|
13613
13648
|
}
|
|
13614
13649
|
clearIntervalsFromHot();
|
|
13650
|
+
const heartbeatMs = cfg.syncIntervalMs ?? 12e4;
|
|
13615
13651
|
heartbeatInterval = setInterval(() => {
|
|
13616
13652
|
pushHeartbeat().catch((err) => {
|
|
13617
13653
|
logger.warn(`cohort-sync: heartbeat tick failed: ${String(err)}`);
|
|
13618
13654
|
});
|
|
13619
|
-
},
|
|
13655
|
+
}, heartbeatMs);
|
|
13620
13656
|
activityFlushInterval = setInterval(() => {
|
|
13621
13657
|
flushActivityBuffer().catch((err) => {
|
|
13622
13658
|
logger.warn(`cohort-sync: activity flush tick failed: ${String(err)}`);
|
|
13623
13659
|
});
|
|
13624
13660
|
}, 3e3);
|
|
13625
13661
|
saveIntervalsToHot(heartbeatInterval, activityFlushInterval);
|
|
13626
|
-
logger.info(
|
|
13662
|
+
logger.info(`cohort-sync: intervals created (heartbeat=${heartbeatMs / 1e3}s, activityFlush=3s)`);
|
|
13627
13663
|
{
|
|
13628
13664
|
const hotState = getHotState();
|
|
13629
13665
|
if (hotState.commandSubscription) {
|
|
@@ -14299,7 +14335,7 @@ function registerCohortCli(ctx, cfg) {
|
|
|
14299
14335
|
agents.push({ id, name });
|
|
14300
14336
|
}
|
|
14301
14337
|
} else {
|
|
14302
|
-
agents.push({ id: "main", name: "
|
|
14338
|
+
agents.push({ id: "main", name: "main" });
|
|
14303
14339
|
}
|
|
14304
14340
|
const manifest = { agents };
|
|
14305
14341
|
logger.info("cohort: Starting device authorization...");
|
|
@@ -14359,40 +14395,10 @@ function registerCohortCli(ctx, cfg) {
|
|
|
14359
14395
|
// index.ts
|
|
14360
14396
|
init_keychain();
|
|
14361
14397
|
var DEFAULT_API_URL = "https://fortunate-chipmunk-286.convex.site";
|
|
14362
|
-
async function doActivate(api) {
|
|
14363
|
-
const cfg = api.pluginConfig;
|
|
14364
|
-
const apiUrl = cfg?.apiUrl ?? DEFAULT_API_URL;
|
|
14365
|
-
api.logger.info(`cohort-sync: activating (api: ${apiUrl})`);
|
|
14366
|
-
let apiKey = cfg?.apiKey;
|
|
14367
|
-
if (!apiKey) {
|
|
14368
|
-
try {
|
|
14369
|
-
apiKey = await getCredential(apiUrl) ?? void 0;
|
|
14370
|
-
} catch (err) {
|
|
14371
|
-
api.logger.error(
|
|
14372
|
-
`cohort-sync: keychain lookup failed: ${err instanceof Error ? err.message : String(err)}`
|
|
14373
|
-
);
|
|
14374
|
-
}
|
|
14375
|
-
}
|
|
14376
|
-
if (!apiKey) {
|
|
14377
|
-
api.logger.warn(
|
|
14378
|
-
"cohort-sync: no API key found \u2014 run 'openclaw cohort auth' to authenticate"
|
|
14379
|
-
);
|
|
14380
|
-
return;
|
|
14381
|
-
}
|
|
14382
|
-
api.logger.info(`cohort-sync: activated (api: ${apiUrl})`);
|
|
14383
|
-
registerHooks(api, {
|
|
14384
|
-
apiUrl,
|
|
14385
|
-
apiKey,
|
|
14386
|
-
agentNameMap: cfg?.agentNameMap
|
|
14387
|
-
});
|
|
14388
|
-
}
|
|
14389
14398
|
var plugin = {
|
|
14390
14399
|
id: "cohort-sync",
|
|
14391
14400
|
name: "Cohort Sync",
|
|
14392
14401
|
description: "Syncs agent status and skills to Cohort dashboard",
|
|
14393
|
-
// register() is synchronous — the SDK does not await it.
|
|
14394
|
-
// We register CLI here, then self-activate async (the gateway does not
|
|
14395
|
-
// call activate() for extension-directory plugins).
|
|
14396
14402
|
register(api) {
|
|
14397
14403
|
const cfg = api.pluginConfig;
|
|
14398
14404
|
const apiUrl = cfg?.apiUrl ?? DEFAULT_API_URL;
|
|
@@ -14404,15 +14410,40 @@ var plugin = {
|
|
|
14404
14410
|
}),
|
|
14405
14411
|
{ commands: ["cohort"] }
|
|
14406
14412
|
);
|
|
14407
|
-
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14413
|
+
api.registerService({
|
|
14414
|
+
id: "cohort-sync-core",
|
|
14415
|
+
async start(svcCtx) {
|
|
14416
|
+
api.logger.info(`cohort-sync: service starting (api: ${apiUrl})`);
|
|
14417
|
+
let apiKey = cfg?.apiKey;
|
|
14418
|
+
if (!apiKey) {
|
|
14419
|
+
try {
|
|
14420
|
+
apiKey = await getCredential(apiUrl) ?? void 0;
|
|
14421
|
+
} catch (err) {
|
|
14422
|
+
api.logger.error(
|
|
14423
|
+
`cohort-sync: keychain lookup failed: ${err instanceof Error ? err.message : String(err)}`
|
|
14424
|
+
);
|
|
14425
|
+
}
|
|
14426
|
+
}
|
|
14427
|
+
if (!apiKey) {
|
|
14428
|
+
api.logger.warn(
|
|
14429
|
+
"cohort-sync: no API key found \u2014 run 'openclaw cohort auth' to authenticate"
|
|
14430
|
+
);
|
|
14431
|
+
return;
|
|
14432
|
+
}
|
|
14433
|
+
api.logger.info(`cohort-sync: activated (api: ${apiUrl})`);
|
|
14434
|
+
registerHooks(api, {
|
|
14435
|
+
apiUrl,
|
|
14436
|
+
apiKey,
|
|
14437
|
+
stateDir: svcCtx.stateDir,
|
|
14438
|
+
agentNameMap: cfg?.agentNameMap,
|
|
14439
|
+
syncIntervalMs: cfg?.syncIntervalMs
|
|
14440
|
+
});
|
|
14441
|
+
},
|
|
14442
|
+
async stop() {
|
|
14443
|
+
closeSubscription();
|
|
14444
|
+
api.logger.info("cohort-sync: service stopped");
|
|
14445
|
+
}
|
|
14446
|
+
});
|
|
14416
14447
|
}
|
|
14417
14448
|
};
|
|
14418
14449
|
var index_default = plugin;
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
"label": "Agent Name Map",
|
|
23
23
|
"advanced": true,
|
|
24
24
|
"help": "Map OpenClaw agent IDs to Cohort display names (e.g. {\"main\": \"yuki\"})"
|
|
25
|
+
},
|
|
26
|
+
"convexUrl": {
|
|
27
|
+
"label": "Convex WebSocket URL",
|
|
28
|
+
"placeholder": "https://fortunate-chipmunk-286.convex.cloud",
|
|
29
|
+
"advanced": true,
|
|
30
|
+
"help": "Override the auto-derived Convex WebSocket URL (rarely needed)"
|
|
25
31
|
}
|
|
26
32
|
},
|
|
27
33
|
"configSchema": {
|
|
@@ -42,7 +48,11 @@
|
|
|
42
48
|
"additionalProperties": {
|
|
43
49
|
"type": "string"
|
|
44
50
|
}
|
|
51
|
+
},
|
|
52
|
+
"convexUrl": {
|
|
53
|
+
"type": "string"
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
|
-
}
|
|
56
|
+
},
|
|
57
|
+
"version": "0.7.0"
|
|
48
58
|
}
|
package/dist/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cfio/cohort-sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Syncs agent status and skills to Cohort dashboard",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docs.cohort.bot/gateway",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build": "node scripts/build.mjs",
|
|
24
24
|
"postinstall": "node scripts/postinstall.mjs",
|
|
25
25
|
"prepublishOnly": "node scripts/build.mjs",
|
|
26
|
-
"typecheck": "tsc
|
|
26
|
+
"typecheck": "echo 'Skipped: openclaw types loosely pinned, build uses esbuild not tsc'",
|
|
27
27
|
"test": "vitest run"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|