@cfio/cohort-sync 0.29.0 → 0.30.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 +123 -20
- package/dist/openclaw.plugin.json +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11655,8 +11655,8 @@ function humanizeMs(ms) {
|
|
|
11655
11655
|
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
11656
11656
|
return `${Math.round(ms / 1e3)}s`;
|
|
11657
11657
|
}
|
|
11658
|
-
function mapCronJob(job, resolveAgentName) {
|
|
11659
|
-
|
|
11658
|
+
function mapCronJob(job, resolveAgentName, tracker) {
|
|
11659
|
+
const base = {
|
|
11660
11660
|
id: job.id,
|
|
11661
11661
|
text: job.name,
|
|
11662
11662
|
schedule: formatSchedule(job.schedule),
|
|
@@ -11665,9 +11665,58 @@ function mapCronJob(job, resolveAgentName) {
|
|
|
11665
11665
|
nextRun: job.state?.nextRunAtMs,
|
|
11666
11666
|
lastRun: job.state?.lastRunAtMs,
|
|
11667
11667
|
lastStatus: job.state?.lastRunStatus,
|
|
11668
|
-
lastError: job.state?.lastError ?? null
|
|
11668
|
+
lastError: job.state?.lastError ?? null,
|
|
11669
|
+
message: job.payload?.message
|
|
11669
11670
|
};
|
|
11671
|
+
if (tracker) {
|
|
11672
|
+
const stamps = tracker.observe(job.id, base);
|
|
11673
|
+
base.createdAt = stamps.createdAt;
|
|
11674
|
+
base.updatedAt = stamps.updatedAt;
|
|
11675
|
+
}
|
|
11676
|
+
return base;
|
|
11670
11677
|
}
|
|
11678
|
+
function fingerprintJob(job) {
|
|
11679
|
+
return [
|
|
11680
|
+
job.text,
|
|
11681
|
+
job.schedule,
|
|
11682
|
+
job.agentId,
|
|
11683
|
+
job.enabled ? "1" : "0",
|
|
11684
|
+
job.message ?? ""
|
|
11685
|
+
].join("");
|
|
11686
|
+
}
|
|
11687
|
+
var CronTimestampTracker = class {
|
|
11688
|
+
records = /* @__PURE__ */ new Map();
|
|
11689
|
+
/**
|
|
11690
|
+
* Observe a job. Returns the current `{ createdAt, updatedAt }` for the job.
|
|
11691
|
+
* Mutates internal state. Idempotent for unchanged jobs (returns the prior
|
|
11692
|
+
* `updatedAt` when the fingerprint hasn't changed).
|
|
11693
|
+
*
|
|
11694
|
+
* `now` is injectable for tests.
|
|
11695
|
+
*/
|
|
11696
|
+
observe(jobId, job, now = Date.now()) {
|
|
11697
|
+
const fingerprint = fingerprintJob(job);
|
|
11698
|
+
const existing = this.records.get(jobId);
|
|
11699
|
+
if (!existing) {
|
|
11700
|
+
const record = { createdAt: now, updatedAt: now, fingerprint };
|
|
11701
|
+
this.records.set(jobId, record);
|
|
11702
|
+
return { createdAt: record.createdAt, updatedAt: record.updatedAt };
|
|
11703
|
+
}
|
|
11704
|
+
if (existing.fingerprint !== fingerprint) {
|
|
11705
|
+
existing.fingerprint = fingerprint;
|
|
11706
|
+
existing.updatedAt = now;
|
|
11707
|
+
}
|
|
11708
|
+
return { createdAt: existing.createdAt, updatedAt: existing.updatedAt };
|
|
11709
|
+
}
|
|
11710
|
+
/** Drop a jobId (e.g. after cronDelete). Safe to call for unknown ids. */
|
|
11711
|
+
forget(jobId) {
|
|
11712
|
+
this.records.delete(jobId);
|
|
11713
|
+
}
|
|
11714
|
+
/** Test helper — read internal state without mutating it. */
|
|
11715
|
+
peek(jobId) {
|
|
11716
|
+
const r = this.records.get(jobId);
|
|
11717
|
+
return r ? { createdAt: r.createdAt, updatedAt: r.updatedAt } : void 0;
|
|
11718
|
+
}
|
|
11719
|
+
};
|
|
11671
11720
|
function reverseResolveAgentName(cohortName, forwardMap) {
|
|
11672
11721
|
const lower = cohortName.toLowerCase();
|
|
11673
11722
|
for (const [openclawId, name] of Object.entries(forwardMap)) {
|
|
@@ -11692,7 +11741,7 @@ function checkRateLimit() {
|
|
|
11692
11741
|
return true;
|
|
11693
11742
|
}
|
|
11694
11743
|
var MAX_CRON_MESSAGE_LENGTH = 1e3;
|
|
11695
|
-
async function executeCommand(cmd, gwClient, cfg, resolveAgentName, logger) {
|
|
11744
|
+
async function executeCommand(cmd, gwClient, cfg, resolveAgentName, logger, cronTimestampTracker) {
|
|
11696
11745
|
logger.info(`cohort-sync: executing command type=${cmd.type} id=${cmd._id}`);
|
|
11697
11746
|
if (!checkRateLimit()) {
|
|
11698
11747
|
logger.warn(`cohort-sync: rate limit exceeded (>${RATE_LIMIT_MAX} commands/min), rejecting command ${cmd._id}`);
|
|
@@ -11773,11 +11822,14 @@ async function executeCommand(cmd, gwClient, cfg, resolveAgentName, logger) {
|
|
|
11773
11822
|
default:
|
|
11774
11823
|
logger.warn(`cohort-sync: unknown cron command type: ${cmd.type}`);
|
|
11775
11824
|
}
|
|
11825
|
+
if (cmd.type === "cronDelete" && cmd.payload?.jobId && cronTimestampTracker) {
|
|
11826
|
+
cronTimestampTracker.forget(cmd.payload.jobId);
|
|
11827
|
+
}
|
|
11776
11828
|
if (gwClient.isAlive()) {
|
|
11777
11829
|
try {
|
|
11778
11830
|
const snapResult = await gwClient.request("cron.list", { includeDisabled: true });
|
|
11779
11831
|
const freshJobs = Array.isArray(snapResult) ? snapResult : snapResult?.jobs ?? [];
|
|
11780
|
-
const mapped = freshJobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
11832
|
+
const mapped = freshJobs.map((j) => mapCronJob(j, resolveAgentName, cronTimestampTracker));
|
|
11781
11833
|
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
11782
11834
|
} catch (snapErr) {
|
|
11783
11835
|
logger.warn(`cohort-sync: post-command snapshot push failed: ${String(snapErr)}`);
|
|
@@ -11881,6 +11933,7 @@ var upsertTelemetryFromPlugin = makeFunctionReference("telemetryPlugin:upsertTel
|
|
|
11881
11933
|
var upsertSessionsFromPlugin = makeFunctionReference("telemetryPlugin:upsertSessionsFromPlugin");
|
|
11882
11934
|
var pushActivityFromPluginRef = makeFunctionReference("activityFeed:pushActivityFromPlugin");
|
|
11883
11935
|
var upsertCronSnapshotFromPluginRef = makeFunctionReference("telemetryPlugin:upsertCronSnapshotFromPlugin");
|
|
11936
|
+
var recordCronRunFromPluginRef = makeFunctionReference("cronRunHistory:recordFromPlugin");
|
|
11884
11937
|
var getUndeliveredForPlugin = makeFunctionReference("notifications:getUndeliveredForPlugin");
|
|
11885
11938
|
var markDeliveredByPlugin = makeFunctionReference("notifications:markDeliveredByPlugin");
|
|
11886
11939
|
var getPendingCommandsForPlugin = makeFunctionReference("gatewayCommands:getPendingForPlugin");
|
|
@@ -11946,6 +11999,25 @@ async function pushCronSnapshot(apiKey2, jobs) {
|
|
|
11946
11999
|
return false;
|
|
11947
12000
|
}
|
|
11948
12001
|
}
|
|
12002
|
+
async function recordCronRun(apiKey2, run) {
|
|
12003
|
+
if (authCircuitOpen) return false;
|
|
12004
|
+
const c = getClient();
|
|
12005
|
+
if (!c) return false;
|
|
12006
|
+
try {
|
|
12007
|
+
await c.mutation(recordCronRunFromPluginRef, {
|
|
12008
|
+
apiKeyHash: hashApiKey(apiKey2),
|
|
12009
|
+
...run
|
|
12010
|
+
});
|
|
12011
|
+
return true;
|
|
12012
|
+
} catch (err) {
|
|
12013
|
+
if (isUnauthorizedError(err)) {
|
|
12014
|
+
tripAuthCircuit();
|
|
12015
|
+
return false;
|
|
12016
|
+
}
|
|
12017
|
+
getLogger().error(`cohort-sync: recordCronRun failed: ${err}`);
|
|
12018
|
+
return false;
|
|
12019
|
+
}
|
|
12020
|
+
}
|
|
11949
12021
|
async function callAddCommentFromPlugin(apiKey2, args) {
|
|
11950
12022
|
if (authCircuitOpen) {
|
|
11951
12023
|
throw new Error(
|
|
@@ -12169,7 +12241,7 @@ async function startNotificationSubscription(port, cfg, hooksToken, logger, gwCl
|
|
|
12169
12241
|
unsubscribers.push(unsubscribe);
|
|
12170
12242
|
}
|
|
12171
12243
|
}
|
|
12172
|
-
function startCommandSubscription(cfg, logger, resolveAgentName, gwClient) {
|
|
12244
|
+
function startCommandSubscription(cfg, logger, resolveAgentName, gwClient, cronTimestampTracker) {
|
|
12173
12245
|
const c = getClient();
|
|
12174
12246
|
if (!c) {
|
|
12175
12247
|
logger.warn("cohort-sync: no ConvexClient \u2014 command subscription skipped");
|
|
@@ -12192,7 +12264,7 @@ function startCommandSubscription(cfg, logger, resolveAgentName, gwClient) {
|
|
|
12192
12264
|
commandId: cmd._id,
|
|
12193
12265
|
apiKeyHash
|
|
12194
12266
|
});
|
|
12195
|
-
await executeCommand(cmd, gwClient, cfg, resolveAgentName, logger);
|
|
12267
|
+
await executeCommand(cmd, gwClient, cfg, resolveAgentName, logger, cronTimestampTracker);
|
|
12196
12268
|
if (cmd.type === "restart") return;
|
|
12197
12269
|
} catch (err) {
|
|
12198
12270
|
if (isUnauthorizedError(err)) {
|
|
@@ -13487,12 +13559,12 @@ function dumpCtx(ctx) {
|
|
|
13487
13559
|
function dumpEvent(event) {
|
|
13488
13560
|
return dumpCtx(event);
|
|
13489
13561
|
}
|
|
13490
|
-
var PLUGIN_VERSION = true ? "0.
|
|
13562
|
+
var PLUGIN_VERSION = true ? "0.30.0" : "unknown";
|
|
13491
13563
|
function resolveGatewayToken(api) {
|
|
13492
13564
|
const token = api.config?.gateway?.auth?.token;
|
|
13493
13565
|
return typeof token === "string" ? token : null;
|
|
13494
13566
|
}
|
|
13495
|
-
function registerCronEventHandlers(client2, cfg, resolveAgentName, logger) {
|
|
13567
|
+
function registerCronEventHandlers(client2, cfg, resolveAgentName, cronTimestampTracker, logger) {
|
|
13496
13568
|
if (client2.availableEvents.has("cron")) {
|
|
13497
13569
|
let debounceTimer = null;
|
|
13498
13570
|
client2.on("cron", () => {
|
|
@@ -13501,7 +13573,7 @@ function registerCronEventHandlers(client2, cfg, resolveAgentName, logger) {
|
|
|
13501
13573
|
try {
|
|
13502
13574
|
const cronResult = await client2.request("cron.list", { includeDisabled: true });
|
|
13503
13575
|
const jobs = Array.isArray(cronResult) ? cronResult : cronResult?.jobs ?? [];
|
|
13504
|
-
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
13576
|
+
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName, cronTimestampTracker));
|
|
13505
13577
|
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13506
13578
|
logger.debug("cohort-sync: cron event pushed", { count: mapped.length });
|
|
13507
13579
|
} catch (err) {
|
|
@@ -13588,12 +13660,12 @@ function loadSessionsFromDisk(tracker, stateFilePath, logger) {
|
|
|
13588
13660
|
} catch {
|
|
13589
13661
|
}
|
|
13590
13662
|
}
|
|
13591
|
-
function initGatewayClient(port, token, cfg, resolveAgentName, logger) {
|
|
13663
|
+
function initGatewayClient(port, token, cfg, resolveAgentName, cronTimestampTracker, logger) {
|
|
13592
13664
|
const client2 = new GatewayClient(port, token, logger, PLUGIN_VERSION);
|
|
13593
13665
|
const onConnected = async () => {
|
|
13594
13666
|
logger.debug(`cohort-sync: gateway client connected (methods=${client2.availableMethods.size}, events=${client2.availableEvents.size})`);
|
|
13595
13667
|
logger.info(`cohort-sync: gateway client connected (methods=${client2.availableMethods.size}, events=${client2.availableEvents.size})`);
|
|
13596
|
-
registerCronEventHandlers(client2, cfg, resolveAgentName, logger);
|
|
13668
|
+
registerCronEventHandlers(client2, cfg, resolveAgentName, cronTimestampTracker, logger);
|
|
13597
13669
|
if (client2.availableEvents.has("shutdown")) {
|
|
13598
13670
|
client2.on("shutdown", () => {
|
|
13599
13671
|
logger.info("cohort-sync: gateway shutdown event received");
|
|
@@ -13602,7 +13674,7 @@ function initGatewayClient(port, token, cfg, resolveAgentName, logger) {
|
|
|
13602
13674
|
try {
|
|
13603
13675
|
const cronResult = await client2.request("cron.list", { includeDisabled: true });
|
|
13604
13676
|
const jobs = Array.isArray(cronResult) ? cronResult : cronResult?.jobs ?? [];
|
|
13605
|
-
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName));
|
|
13677
|
+
const mapped = jobs.map((j) => mapCronJob(j, resolveAgentName, cronTimestampTracker));
|
|
13606
13678
|
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13607
13679
|
logger.debug("cohort-sync: cron snapshot pushed", { count: mapped.length });
|
|
13608
13680
|
} catch (err) {
|
|
@@ -13666,14 +13738,14 @@ async function handleGatewayStart(event, state) {
|
|
|
13666
13738
|
state.gatewayToken = token;
|
|
13667
13739
|
logger.debug("cohort-sync: gateway client connecting", { port: event.port, hasToken: true });
|
|
13668
13740
|
try {
|
|
13669
|
-
const client2 = initGatewayClient(event.port, token, cfg, state.resolveAgentName, logger);
|
|
13741
|
+
const client2 = initGatewayClient(event.port, token, cfg, state.resolveAgentName, state.cronTimestampTracker, logger);
|
|
13670
13742
|
state.persistentGwClient = client2;
|
|
13671
13743
|
state.gwClientInitialized = true;
|
|
13672
13744
|
if (state.commandUnsubscriber) {
|
|
13673
13745
|
state.commandUnsubscriber();
|
|
13674
13746
|
state.commandUnsubscriber = null;
|
|
13675
13747
|
}
|
|
13676
|
-
const unsub = startCommandSubscription(cfg, logger, state.resolveAgentName, state.persistentGwClient);
|
|
13748
|
+
const unsub = startCommandSubscription(cfg, logger, state.resolveAgentName, state.persistentGwClient, state.cronTimestampTracker);
|
|
13677
13749
|
state.commandUnsubscriber = unsub;
|
|
13678
13750
|
} catch (err) {
|
|
13679
13751
|
logger.debug("cohort-sync: gateway client connect failed", { error: String(err) });
|
|
@@ -13837,16 +13909,40 @@ function registerHookHandlers(api, logger, getState) {
|
|
|
13837
13909
|
}
|
|
13838
13910
|
const sessionKey = ctx.sessionKey;
|
|
13839
13911
|
if (sessionKey && sessionKey.includes(":cron:")) {
|
|
13912
|
+
const parsed = parseSessionKey(sessionKey);
|
|
13913
|
+
const routineId = parsed.kind === "cron" ? parsed.identifier : void 0;
|
|
13840
13914
|
try {
|
|
13841
13915
|
const raw = fs2["read"+"FileSync"](state.cronStorePath, "utf8");
|
|
13842
13916
|
const store = JSON.parse(raw);
|
|
13843
13917
|
const jobs = store.jobs ?? [];
|
|
13844
|
-
const mapped = jobs.map((j) => mapCronJob(j, state.resolveAgentName));
|
|
13918
|
+
const mapped = jobs.map((j) => mapCronJob(j, state.resolveAgentName, state.cronTimestampTracker));
|
|
13845
13919
|
await pushCronSnapshot(cfg.apiKey, mapped);
|
|
13846
13920
|
log.debug("cohort-sync: cron agent end push", { count: mapped.length, sessionKey });
|
|
13847
13921
|
} catch (err) {
|
|
13848
13922
|
log.debug("cohort-sync: cron agent end push failed", { error: String(err) });
|
|
13849
13923
|
}
|
|
13924
|
+
if (routineId) {
|
|
13925
|
+
const finishedAt = Date.now();
|
|
13926
|
+
const startedAtFromTracker = state.cronRunStarts.get(sessionKey);
|
|
13927
|
+
state.cronRunStarts.delete(sessionKey);
|
|
13928
|
+
const durationMs = typeof event?.durationMs === "number" && event.durationMs >= 0 ? event.durationMs : startedAtFromTracker != null ? Math.max(0, finishedAt - startedAtFromTracker) : void 0;
|
|
13929
|
+
const startedAt = startedAtFromTracker ?? (typeof durationMs === "number" ? finishedAt - durationMs : finishedAt);
|
|
13930
|
+
const status = event?.success === false ? "error" : "ok";
|
|
13931
|
+
const errorMessage = status === "error" && typeof event?.error === "string" ? event.error : void 0;
|
|
13932
|
+
try {
|
|
13933
|
+
await recordCronRun(cfg.apiKey, {
|
|
13934
|
+
routineId,
|
|
13935
|
+
startedAt,
|
|
13936
|
+
finishedAt,
|
|
13937
|
+
status,
|
|
13938
|
+
durationMs,
|
|
13939
|
+
...errorMessage ? { errorMessage } : {}
|
|
13940
|
+
});
|
|
13941
|
+
log.debug("cohort-sync: cron run recorded", { routineId, status, durationMs });
|
|
13942
|
+
} catch (err) {
|
|
13943
|
+
log.debug("cohort-sync: cron run record failed", { error: String(err) });
|
|
13944
|
+
}
|
|
13945
|
+
}
|
|
13850
13946
|
}
|
|
13851
13947
|
if (event.success === false) {
|
|
13852
13948
|
const entry = buildActivityEntry(agentName, "agent_end", {
|
|
@@ -13973,14 +14069,14 @@ function registerHookHandlers(api, logger, getState) {
|
|
|
13973
14069
|
try {
|
|
13974
14070
|
if (!state.gwClientInitialized && state.gatewayPort && state.gatewayToken) {
|
|
13975
14071
|
try {
|
|
13976
|
-
const client2 = initGatewayClient(state.gatewayPort, state.gatewayToken, cfg, state.resolveAgentName, log);
|
|
14072
|
+
const client2 = initGatewayClient(state.gatewayPort, state.gatewayToken, cfg, state.resolveAgentName, state.cronTimestampTracker, log);
|
|
13977
14073
|
state.persistentGwClient = client2;
|
|
13978
14074
|
state.gwClientInitialized = true;
|
|
13979
14075
|
if (state.commandUnsubscriber) {
|
|
13980
14076
|
state.commandUnsubscriber();
|
|
13981
14077
|
state.commandUnsubscriber = null;
|
|
13982
14078
|
}
|
|
13983
|
-
const unsub = startCommandSubscription(cfg, log, state.resolveAgentName, state.persistentGwClient);
|
|
14079
|
+
const unsub = startCommandSubscription(cfg, log, state.resolveAgentName, state.persistentGwClient, state.cronTimestampTracker);
|
|
13984
14080
|
state.commandUnsubscriber = unsub;
|
|
13985
14081
|
} catch (err) {
|
|
13986
14082
|
log.debug("cohort-sync: gateway client lazy init failed", { error: String(err) });
|
|
@@ -14007,6 +14103,9 @@ function registerHookHandlers(api, logger, getState) {
|
|
|
14007
14103
|
} else if (sessionKey) {
|
|
14008
14104
|
tracker.setSessionAgent(sessionKey, agentName);
|
|
14009
14105
|
}
|
|
14106
|
+
if (sessionKey && sessionKey.includes(":cron:")) {
|
|
14107
|
+
state.cronRunStarts.set(sessionKey, Date.now());
|
|
14108
|
+
}
|
|
14010
14109
|
const ctxChannelId = ctx.channelId;
|
|
14011
14110
|
if (ctxChannelId) {
|
|
14012
14111
|
setChannelAgent(ctxChannelId, agentName);
|
|
@@ -14289,14 +14388,16 @@ function initializeHookState(api, cfg) {
|
|
|
14289
14388
|
});
|
|
14290
14389
|
const gatewayPort = api.config?.gateway?.port ?? null;
|
|
14291
14390
|
const gatewayToken = resolveGatewayToken(api);
|
|
14391
|
+
const cronTimestampTracker = new CronTimestampTracker();
|
|
14392
|
+
const cronRunStarts = /* @__PURE__ */ new Map();
|
|
14292
14393
|
let persistentGwClient = null;
|
|
14293
14394
|
let gwClientInitialized = false;
|
|
14294
14395
|
if (gatewayPort && gatewayToken) {
|
|
14295
|
-
const client2 = initGatewayClient(gatewayPort, gatewayToken, cfg, resolveAgentName, logger);
|
|
14396
|
+
const client2 = initGatewayClient(gatewayPort, gatewayToken, cfg, resolveAgentName, cronTimestampTracker, logger);
|
|
14296
14397
|
persistentGwClient = client2;
|
|
14297
14398
|
gwClientInitialized = true;
|
|
14298
14399
|
}
|
|
14299
|
-
const commandUnsub = startCommandSubscription(cfg, logger, resolveAgentName, persistentGwClient);
|
|
14400
|
+
const commandUnsub = startCommandSubscription(cfg, logger, resolveAgentName, persistentGwClient, cronTimestampTracker);
|
|
14300
14401
|
setToolRuntime({
|
|
14301
14402
|
apiKey: cfg.apiKey,
|
|
14302
14403
|
apiUrl: cfg.apiUrl,
|
|
@@ -14331,6 +14432,8 @@ function initializeHookState(api, cfg) {
|
|
|
14331
14432
|
return {
|
|
14332
14433
|
cfg,
|
|
14333
14434
|
tracker,
|
|
14435
|
+
cronTimestampTracker,
|
|
14436
|
+
cronRunStarts,
|
|
14334
14437
|
logger,
|
|
14335
14438
|
config,
|
|
14336
14439
|
resolveAgentName,
|
package/dist/package.json
CHANGED
package/package.json
CHANGED