@cfio/cohort-sync 0.1.7 → 0.2.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 +444 -94
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11565,6 +11565,7 @@ var getUndeliveredForPlugin = makeFunctionReference("notifications:getUndelivere
|
|
|
11565
11565
|
var markDeliveredByPlugin = makeFunctionReference("notifications:markDeliveredByPlugin");
|
|
11566
11566
|
var upsertTelemetryFromPlugin = makeFunctionReference("telemetryPlugin:upsertTelemetryFromPlugin");
|
|
11567
11567
|
var upsertSessionsFromPlugin = makeFunctionReference("telemetryPlugin:upsertSessionsFromPlugin");
|
|
11568
|
+
var pushActivityFromPluginRef = makeFunctionReference("activityFeed:pushActivityFromPlugin");
|
|
11568
11569
|
var HOT_KEY = "__cohort_sync__";
|
|
11569
11570
|
function getHotState() {
|
|
11570
11571
|
return globalThis[HOT_KEY] ?? null;
|
|
@@ -11728,6 +11729,25 @@ async function pushSessions(apiKey, agentName, sessions) {
|
|
|
11728
11729
|
getLogger().error(`cohort-sync: pushSessions failed: ${err}`);
|
|
11729
11730
|
}
|
|
11730
11731
|
}
|
|
11732
|
+
async function pushActivity(apiKey, entries) {
|
|
11733
|
+
if (entries.length === 0) return;
|
|
11734
|
+
const c = getOrCreateClient();
|
|
11735
|
+
if (!c) return;
|
|
11736
|
+
try {
|
|
11737
|
+
await c.mutation(pushActivityFromPluginRef, { apiKey, entries });
|
|
11738
|
+
} catch (err) {
|
|
11739
|
+
getLogger().error(`cohort-sync: pushActivity failed: ${err}`);
|
|
11740
|
+
}
|
|
11741
|
+
}
|
|
11742
|
+
function saveAgentStatesToHot(states) {
|
|
11743
|
+
const hot = getHotState();
|
|
11744
|
+
if (hot) {
|
|
11745
|
+
hot.agentStates = states;
|
|
11746
|
+
}
|
|
11747
|
+
}
|
|
11748
|
+
function getAgentStatesFromHot() {
|
|
11749
|
+
return getHotState()?.agentStates ?? null;
|
|
11750
|
+
}
|
|
11731
11751
|
|
|
11732
11752
|
// src/sync.ts
|
|
11733
11753
|
function extractJson(raw) {
|
|
@@ -11957,78 +11977,294 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
11957
11977
|
logger.info("cohort-sync: full sync complete");
|
|
11958
11978
|
}
|
|
11959
11979
|
|
|
11960
|
-
// src/
|
|
11961
|
-
|
|
11962
|
-
|
|
11963
|
-
|
|
11964
|
-
|
|
11965
|
-
|
|
11966
|
-
|
|
11967
|
-
|
|
11980
|
+
// src/session-key.ts
|
|
11981
|
+
function parseSessionKey(key) {
|
|
11982
|
+
if (!key || !key.includes(":")) {
|
|
11983
|
+
return { kind: "cli", channel: "cli" };
|
|
11984
|
+
}
|
|
11985
|
+
const parts = key.split(":");
|
|
11986
|
+
if (parts[0] === "hook" && parts[1] === "cohort") {
|
|
11987
|
+
return { kind: "hook", channel: "cohort", identifier: parts.slice(2).join(":") };
|
|
11988
|
+
}
|
|
11989
|
+
if (parts[0] === "agent" && parts.length >= 3) {
|
|
11990
|
+
const rest = parts.slice(2);
|
|
11991
|
+
if (rest[0] === "main") {
|
|
11992
|
+
return { kind: "direct", channel: "signal" };
|
|
11993
|
+
}
|
|
11994
|
+
if (rest[0] === "signal") {
|
|
11995
|
+
if (rest[1] === "group") {
|
|
11996
|
+
return { kind: "group", channel: "signal", identifier: rest.slice(2).join(":") };
|
|
11997
|
+
}
|
|
11998
|
+
if (rest.length >= 3 && rest.some((p) => p === "dm")) {
|
|
11999
|
+
const dmIndex = rest.indexOf("dm");
|
|
12000
|
+
return { kind: "direct", channel: "signal", identifier: rest.slice(dmIndex + 1).join(":") };
|
|
12001
|
+
}
|
|
12002
|
+
return { kind: "direct", channel: "signal" };
|
|
12003
|
+
}
|
|
12004
|
+
if (rest[0] === "slack" && rest[1] === "channel") {
|
|
12005
|
+
return { kind: "group", channel: "slack", identifier: rest[2] };
|
|
12006
|
+
}
|
|
12007
|
+
}
|
|
12008
|
+
return { kind: "cli", channel: "cli" };
|
|
12009
|
+
}
|
|
12010
|
+
|
|
12011
|
+
// src/agent-state.ts
|
|
12012
|
+
var DELTA_THRESHOLD = 0.1;
|
|
12013
|
+
function shouldPushTelemetry(current, lastPushed) {
|
|
12014
|
+
if (!lastPushed) return true;
|
|
12015
|
+
if (current.status !== lastPushed.status) return true;
|
|
12016
|
+
if (current.compactions !== lastPushed.compactions) return true;
|
|
12017
|
+
if (current.contextLimit > 0 && lastPushed.contextLimit > 0) {
|
|
12018
|
+
const currentPct = current.contextTokens / current.contextLimit;
|
|
12019
|
+
const lastPct = lastPushed.contextTokens / lastPushed.contextLimit;
|
|
12020
|
+
if (Math.abs(currentPct - lastPct) >= DELTA_THRESHOLD) return true;
|
|
12021
|
+
} else if (current.contextLimit > 0 && lastPushed.contextLimit === 0) {
|
|
12022
|
+
return true;
|
|
12023
|
+
}
|
|
12024
|
+
const maxIn = Math.max(lastPushed.tokensIn, 1);
|
|
12025
|
+
if (Math.abs(current.tokensIn - lastPushed.tokensIn) / maxIn >= DELTA_THRESHOLD) return true;
|
|
12026
|
+
const maxOut = Math.max(lastPushed.tokensOut, 1);
|
|
12027
|
+
if (Math.abs(current.tokensOut - lastPushed.tokensOut) / maxOut >= DELTA_THRESHOLD) return true;
|
|
12028
|
+
return false;
|
|
12029
|
+
}
|
|
12030
|
+
function shouldPushSessions(current, lastPushed) {
|
|
12031
|
+
if (!lastPushed) return true;
|
|
12032
|
+
if (current.length !== lastPushed.length) return true;
|
|
12033
|
+
for (const sess of current) {
|
|
12034
|
+
const prev = lastPushed.find((s) => s.key === sess.key);
|
|
12035
|
+
if (!prev) return true;
|
|
12036
|
+
if (sess.model && sess.model !== "unknown" && prev.model !== sess.model) return true;
|
|
12037
|
+
if (sess.contextLimit && sess.contextLimit > 0 && prev.contextLimit && prev.contextLimit > 0) {
|
|
12038
|
+
const curPct = (sess.contextTokens ?? 0) / sess.contextLimit;
|
|
12039
|
+
const prevPct = (prev.contextTokens ?? 0) / prev.contextLimit;
|
|
12040
|
+
if (Math.abs(curPct - prevPct) >= DELTA_THRESHOLD) return true;
|
|
12041
|
+
}
|
|
12042
|
+
}
|
|
12043
|
+
return false;
|
|
12044
|
+
}
|
|
12045
|
+
function basename(filePath) {
|
|
12046
|
+
const parts = filePath.split("/");
|
|
12047
|
+
return parts[parts.length - 1] ?? filePath;
|
|
12048
|
+
}
|
|
12049
|
+
function buildActivitySentence(agentName, event, context) {
|
|
12050
|
+
const ts = Date.now();
|
|
12051
|
+
const name = agentName.charAt(0).toUpperCase() + agentName.slice(1);
|
|
12052
|
+
switch (event) {
|
|
12053
|
+
case "before_tool_call": {
|
|
12054
|
+
const toolName = String(context.toolName ?? "a tool");
|
|
12055
|
+
const params = context.params;
|
|
12056
|
+
if (toolName === "Edit" || toolName === "Write") {
|
|
12057
|
+
const file = params?.file_path ? basename(String(params.file_path)) : "a file";
|
|
12058
|
+
return { agentName, text: `${name} is editing ${file}`, category: "tool", timestamp: ts };
|
|
12059
|
+
}
|
|
12060
|
+
if (toolName === "Read") {
|
|
12061
|
+
const file = params?.file_path ? basename(String(params.file_path)) : "a file";
|
|
12062
|
+
return { agentName, text: `${name} is reading ${file}`, category: "tool", timestamp: ts };
|
|
12063
|
+
}
|
|
12064
|
+
if (toolName === "Bash") {
|
|
12065
|
+
return { agentName, text: `${name} is running a command`, category: "tool", timestamp: ts };
|
|
12066
|
+
}
|
|
12067
|
+
if (toolName === "cohort_comment") {
|
|
12068
|
+
const taskNum = params?.task_number ?? "?";
|
|
12069
|
+
return { agentName, text: `${name} is commenting on task #${taskNum}`, category: "tool", timestamp: ts };
|
|
12070
|
+
}
|
|
12071
|
+
return { agentName, text: `${name} used ${toolName}`, category: "tool", timestamp: ts };
|
|
12072
|
+
}
|
|
12073
|
+
case "message_received": {
|
|
12074
|
+
const ch = String(context.channel ?? context.messageProvider ?? "unknown");
|
|
12075
|
+
return { agentName, text: `${name} received a message on ${ch}`, category: "message", timestamp: ts };
|
|
12076
|
+
}
|
|
12077
|
+
case "message_sent": {
|
|
12078
|
+
const ch = String(context.channel ?? context.messageProvider ?? "unknown");
|
|
12079
|
+
return { agentName, text: `${name} sent a reply on ${ch}`, category: "message", timestamp: ts };
|
|
12080
|
+
}
|
|
12081
|
+
case "after_compaction": {
|
|
12082
|
+
const before = context.beforePercent != null ? `${context.beforePercent}%` : "?";
|
|
12083
|
+
const after = context.afterPercent != null ? `${context.afterPercent}%` : "?";
|
|
12084
|
+
return { agentName, text: `${name} context compacted \u2014 ${before} \u2192 ${after}`, category: "system", timestamp: ts };
|
|
12085
|
+
}
|
|
12086
|
+
case "session_start": {
|
|
12087
|
+
const ch = String(context.channel ?? "unknown");
|
|
12088
|
+
return { agentName, text: `${name} started a session on ${ch}`, category: "session", timestamp: ts };
|
|
12089
|
+
}
|
|
12090
|
+
case "session_end": {
|
|
12091
|
+
return { agentName, text: `${name} ended a session`, category: "session", timestamp: ts };
|
|
12092
|
+
}
|
|
12093
|
+
case "before_agent_start": {
|
|
12094
|
+
const ch = String(context.channel ?? "unknown");
|
|
12095
|
+
return { agentName, text: `${name} is working on ${ch}`, category: "session", timestamp: ts };
|
|
12096
|
+
}
|
|
12097
|
+
default:
|
|
12098
|
+
return null;
|
|
12099
|
+
}
|
|
12100
|
+
}
|
|
12101
|
+
var AgentStateTracker = class {
|
|
12102
|
+
agents = /* @__PURE__ */ new Map();
|
|
12103
|
+
activityBuffer = [];
|
|
12104
|
+
getOrCreate(agentName) {
|
|
12105
|
+
let state = this.agents.get(agentName);
|
|
12106
|
+
if (!state) {
|
|
12107
|
+
state = {
|
|
12108
|
+
agentName,
|
|
12109
|
+
status: "idle",
|
|
12110
|
+
model: "unknown",
|
|
12111
|
+
tokensIn: 0,
|
|
12112
|
+
tokensOut: 0,
|
|
12113
|
+
contextTokens: 0,
|
|
12114
|
+
contextLimit: 0,
|
|
12115
|
+
compactions: 0,
|
|
12116
|
+
sessions: /* @__PURE__ */ new Map(),
|
|
12117
|
+
lastPushedTelemetry: null,
|
|
12118
|
+
lastPushedSessions: null
|
|
12119
|
+
};
|
|
12120
|
+
this.agents.set(agentName, state);
|
|
12121
|
+
}
|
|
12122
|
+
return state;
|
|
12123
|
+
}
|
|
12124
|
+
// --- Session lifecycle ---
|
|
12125
|
+
addSession(agentName, sessionKey) {
|
|
12126
|
+
const state = this.getOrCreate(agentName);
|
|
12127
|
+
const parsed = parseSessionKey(sessionKey);
|
|
12128
|
+
state.sessions.set(sessionKey, {
|
|
12129
|
+
key: sessionKey,
|
|
12130
|
+
kind: parsed.kind,
|
|
12131
|
+
channel: parsed.channel,
|
|
11968
12132
|
model: "unknown",
|
|
11969
12133
|
contextTokens: 0,
|
|
11970
12134
|
contextLimit: 0,
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
12135
|
+
lastActivity: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12136
|
+
startedAt: Date.now()
|
|
12137
|
+
});
|
|
12138
|
+
}
|
|
12139
|
+
removeSession(agentName, sessionKey) {
|
|
12140
|
+
const state = this.agents.get(agentName);
|
|
12141
|
+
if (state) {
|
|
12142
|
+
state.sessions.delete(sessionKey);
|
|
12143
|
+
}
|
|
12144
|
+
}
|
|
12145
|
+
// --- Telemetry updates ---
|
|
12146
|
+
updateStatus(agentName, status) {
|
|
12147
|
+
this.getOrCreate(agentName).status = status;
|
|
12148
|
+
}
|
|
12149
|
+
updateFromLlmOutput(agentName, sessionKey, data) {
|
|
12150
|
+
const state = this.getOrCreate(agentName);
|
|
12151
|
+
if (data.model) state.model = data.model;
|
|
12152
|
+
if (data.tokensIn !== void 0) state.tokensIn += data.tokensIn;
|
|
12153
|
+
if (data.tokensOut !== void 0) state.tokensOut += data.tokensOut;
|
|
12154
|
+
if (data.contextTokens !== void 0) state.contextTokens = data.contextTokens;
|
|
12155
|
+
if (data.contextLimit !== void 0) state.contextLimit = data.contextLimit;
|
|
12156
|
+
state.status = "working";
|
|
12157
|
+
if (sessionKey) {
|
|
12158
|
+
const session = state.sessions.get(sessionKey);
|
|
12159
|
+
if (session) {
|
|
12160
|
+
if (data.model) session.model = data.model;
|
|
12161
|
+
if (data.contextTokens !== void 0) session.contextTokens = data.contextTokens;
|
|
12162
|
+
if (data.contextLimit !== void 0) session.contextLimit = data.contextLimit;
|
|
12163
|
+
session.lastActivity = (/* @__PURE__ */ new Date()).toISOString();
|
|
12164
|
+
}
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
12167
|
+
updateFromCompaction(agentName, data) {
|
|
12168
|
+
const state = this.getOrCreate(agentName);
|
|
12169
|
+
state.compactions += 1;
|
|
12170
|
+
if (data.contextTokens !== void 0) state.contextTokens = data.contextTokens;
|
|
12171
|
+
if (data.contextLimit !== void 0) state.contextLimit = data.contextLimit;
|
|
12172
|
+
}
|
|
12173
|
+
setModel(agentName, model) {
|
|
12174
|
+
this.getOrCreate(agentName).model = model;
|
|
12175
|
+
}
|
|
12176
|
+
// --- Snapshot extraction ---
|
|
12177
|
+
getTelemetrySnapshot(agentName) {
|
|
12178
|
+
const state = this.agents.get(agentName);
|
|
12179
|
+
if (!state) return null;
|
|
12180
|
+
return {
|
|
12181
|
+
agentName: state.agentName,
|
|
12182
|
+
status: state.status,
|
|
12183
|
+
model: state.model,
|
|
12184
|
+
contextTokens: state.contextTokens,
|
|
12185
|
+
contextLimit: state.contextLimit,
|
|
12186
|
+
tokensIn: state.tokensIn,
|
|
12187
|
+
tokensOut: state.tokensOut,
|
|
12188
|
+
compactions: state.compactions,
|
|
12189
|
+
activeSessions: state.sessions.size
|
|
11975
12190
|
};
|
|
11976
|
-
if (delta.model) current.model = delta.model;
|
|
11977
|
-
if (delta.status) current.status = delta.status;
|
|
11978
|
-
if (delta.tokensIn !== void 0) current.tokensIn += delta.tokensIn;
|
|
11979
|
-
if (delta.tokensOut !== void 0) current.tokensOut += delta.tokensOut;
|
|
11980
|
-
if (delta.contextTokens !== void 0) current.contextTokens = delta.contextTokens;
|
|
11981
|
-
if (delta.contextLimit !== void 0) current.contextLimit = delta.contextLimit;
|
|
11982
|
-
if (delta.activeSessions !== void 0) current.activeSessions = delta.activeSessions;
|
|
11983
|
-
if (delta.compacted) {
|
|
11984
|
-
current.compactions += 1;
|
|
11985
|
-
}
|
|
11986
|
-
this.state.set(agentName, current);
|
|
11987
|
-
return { ...current };
|
|
11988
|
-
}
|
|
11989
|
-
/** Get current state for an agent. */
|
|
11990
|
-
get(agentName) {
|
|
11991
|
-
const s = this.state.get(agentName);
|
|
11992
|
-
return s ? { ...s } : void 0;
|
|
11993
|
-
}
|
|
11994
|
-
/** Return the last known snapshot for an agent (null if unknown). */
|
|
11995
|
-
getSnapshot(agentName) {
|
|
11996
|
-
return this.state.has(agentName) ? { ...this.state.get(agentName) } : null;
|
|
11997
|
-
}
|
|
11998
|
-
/** Clear all tracked state (e.g., on gateway_stop). */
|
|
11999
|
-
clear() {
|
|
12000
|
-
this.state.clear();
|
|
12001
12191
|
}
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12008
|
-
|
|
12009
|
-
|
|
12010
|
-
|
|
12192
|
+
getSessionsSnapshot(agentName) {
|
|
12193
|
+
const state = this.agents.get(agentName);
|
|
12194
|
+
if (!state) return [];
|
|
12195
|
+
return Array.from(state.sessions.values()).map((s) => ({
|
|
12196
|
+
key: s.key,
|
|
12197
|
+
kind: s.kind,
|
|
12198
|
+
channel: s.channel,
|
|
12199
|
+
model: s.model !== "unknown" ? s.model : void 0,
|
|
12200
|
+
contextTokens: s.contextTokens > 0 ? s.contextTokens : void 0,
|
|
12201
|
+
contextLimit: s.contextLimit > 0 ? s.contextLimit : void 0,
|
|
12202
|
+
lastActivity: s.lastActivity
|
|
12203
|
+
}));
|
|
12204
|
+
}
|
|
12205
|
+
// --- Delta detection ---
|
|
12206
|
+
shouldPushTelemetry(agentName) {
|
|
12207
|
+
const snapshot = this.getTelemetrySnapshot(agentName);
|
|
12208
|
+
if (!snapshot) return false;
|
|
12209
|
+
const state = this.agents.get(agentName);
|
|
12210
|
+
return shouldPushTelemetry(snapshot, state.lastPushedTelemetry);
|
|
12211
|
+
}
|
|
12212
|
+
shouldPushSessions(agentName) {
|
|
12213
|
+
const snapshot = this.getSessionsSnapshot(agentName);
|
|
12214
|
+
const state = this.agents.get(agentName);
|
|
12215
|
+
if (!state) return false;
|
|
12216
|
+
return shouldPushSessions(snapshot, state.lastPushedSessions);
|
|
12011
12217
|
}
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12218
|
+
markTelemetryPushed(agentName) {
|
|
12219
|
+
const state = this.agents.get(agentName);
|
|
12220
|
+
if (state) {
|
|
12221
|
+
state.lastPushedTelemetry = this.getTelemetrySnapshot(agentName);
|
|
12222
|
+
}
|
|
12223
|
+
}
|
|
12224
|
+
markSessionsPushed(agentName) {
|
|
12225
|
+
const state = this.agents.get(agentName);
|
|
12226
|
+
if (state) {
|
|
12227
|
+
state.lastPushedSessions = this.getSessionsSnapshot(agentName);
|
|
12228
|
+
}
|
|
12016
12229
|
}
|
|
12017
|
-
|
|
12018
|
-
|
|
12019
|
-
|
|
12230
|
+
// --- Activity buffer ---
|
|
12231
|
+
addActivity(entry) {
|
|
12232
|
+
this.activityBuffer.push(entry);
|
|
12020
12233
|
}
|
|
12021
|
-
|
|
12022
|
-
|
|
12023
|
-
|
|
12234
|
+
flushActivity() {
|
|
12235
|
+
const entries = [...this.activityBuffer];
|
|
12236
|
+
this.activityBuffer = [];
|
|
12237
|
+
return entries;
|
|
12238
|
+
}
|
|
12239
|
+
// --- Lifecycle ---
|
|
12240
|
+
getAgentNames() {
|
|
12241
|
+
return Array.from(this.agents.keys());
|
|
12024
12242
|
}
|
|
12025
|
-
/** Clear all sessions (e.g., on gateway_stop). */
|
|
12026
12243
|
clear() {
|
|
12027
|
-
this.
|
|
12244
|
+
this.agents.clear();
|
|
12245
|
+
this.activityBuffer = [];
|
|
12246
|
+
}
|
|
12247
|
+
exportState() {
|
|
12248
|
+
return new Map(this.agents);
|
|
12249
|
+
}
|
|
12250
|
+
importState(states) {
|
|
12251
|
+
for (const [name, state] of states) {
|
|
12252
|
+
if (!(state.sessions instanceof Map)) {
|
|
12253
|
+
state.sessions = new Map(Object.entries(state.sessions));
|
|
12254
|
+
}
|
|
12255
|
+
this.agents.set(name, state);
|
|
12256
|
+
}
|
|
12028
12257
|
}
|
|
12029
12258
|
};
|
|
12030
12259
|
|
|
12031
12260
|
// src/hooks.ts
|
|
12261
|
+
var PLUGIN_VERSION = "unknown";
|
|
12262
|
+
try {
|
|
12263
|
+
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), "package.json");
|
|
12264
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
12265
|
+
PLUGIN_VERSION = pkgJson.version ?? "unknown";
|
|
12266
|
+
} catch {
|
|
12267
|
+
}
|
|
12032
12268
|
function parseIdentityFile(workspaceDir) {
|
|
12033
12269
|
try {
|
|
12034
12270
|
const filePath = path.join(workspaceDir, "IDENTITY.md");
|
|
@@ -12064,13 +12300,18 @@ function resolveIdentity(configIdentity, workspaceDir) {
|
|
|
12064
12300
|
function registerHooks(api, cfg) {
|
|
12065
12301
|
const { logger, config } = api;
|
|
12066
12302
|
const nameMap = cfg.agentNameMap;
|
|
12067
|
-
const
|
|
12068
|
-
const sessions = new SessionTracker();
|
|
12303
|
+
const tracker = new AgentStateTracker();
|
|
12069
12304
|
let heartbeatInterval = null;
|
|
12305
|
+
let activityFlushInterval = null;
|
|
12070
12306
|
setConvexUrl(cfg);
|
|
12071
12307
|
setLogger(logger);
|
|
12072
12308
|
restoreFromHotReload(logger);
|
|
12073
12309
|
restoreRosterFromHotReload(getRosterHotState(), logger);
|
|
12310
|
+
const savedStates = getAgentStatesFromHot();
|
|
12311
|
+
if (savedStates) {
|
|
12312
|
+
tracker.importState(savedStates);
|
|
12313
|
+
logger.info("cohort-sync: recovered AgentStateTracker state after hot-reload");
|
|
12314
|
+
}
|
|
12074
12315
|
function resolveAgentName(agentId) {
|
|
12075
12316
|
return (nameMap?.[agentId] ?? agentId).toLowerCase();
|
|
12076
12317
|
}
|
|
@@ -12123,9 +12364,9 @@ function registerHooks(api, cfg) {
|
|
|
12123
12364
|
for (const agentId of allAgentIds) {
|
|
12124
12365
|
const agentName = resolveAgentName(agentId);
|
|
12125
12366
|
try {
|
|
12126
|
-
const snapshot =
|
|
12367
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12127
12368
|
if (snapshot) {
|
|
12128
|
-
await pushTelemetry(cfg.apiKey, snapshot);
|
|
12369
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12129
12370
|
} else {
|
|
12130
12371
|
logger.info(`cohort-sync: heartbeat skipped ${agentName} \u2014 no snapshot in tracker`);
|
|
12131
12372
|
}
|
|
@@ -12135,11 +12376,18 @@ function registerHooks(api, cfg) {
|
|
|
12135
12376
|
}
|
|
12136
12377
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
12137
12378
|
}
|
|
12379
|
+
async function flushActivityBuffer() {
|
|
12380
|
+
const entries = tracker.flushActivity();
|
|
12381
|
+
if (entries.length === 0) return;
|
|
12382
|
+
try {
|
|
12383
|
+
await pushActivity(cfg.apiKey, entries);
|
|
12384
|
+
} catch (err) {
|
|
12385
|
+
logger.warn(`cohort-sync: activity flush failed: ${String(err)}`);
|
|
12386
|
+
}
|
|
12387
|
+
}
|
|
12138
12388
|
api.on("gateway_start", async (event) => {
|
|
12139
12389
|
try {
|
|
12140
|
-
|
|
12141
|
-
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
12142
|
-
checkForUpdate(pkgJson.version, logger).catch(() => {
|
|
12390
|
+
checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
|
|
12143
12391
|
});
|
|
12144
12392
|
} catch {
|
|
12145
12393
|
}
|
|
@@ -12165,11 +12413,13 @@ function registerHooks(api, cfg) {
|
|
|
12165
12413
|
for (const agentId of allAgentIds) {
|
|
12166
12414
|
const agentName = resolveAgentName(agentId);
|
|
12167
12415
|
try {
|
|
12168
|
-
|
|
12169
|
-
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12416
|
+
tracker.setModel(agentName, resolveModel(agentId));
|
|
12417
|
+
tracker.updateStatus(agentName, "idle");
|
|
12418
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12419
|
+
if (snapshot) {
|
|
12420
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12421
|
+
tracker.markTelemetryPushed(agentName);
|
|
12422
|
+
}
|
|
12173
12423
|
} catch (err) {
|
|
12174
12424
|
logger.warn(`cohort-sync: initial telemetry seed failed for ${agentName}: ${String(err)}`);
|
|
12175
12425
|
}
|
|
@@ -12181,14 +12431,26 @@ function registerHooks(api, cfg) {
|
|
|
12181
12431
|
});
|
|
12182
12432
|
}, 12e4);
|
|
12183
12433
|
logger.info("cohort-sync: heartbeat started (2m interval)");
|
|
12434
|
+
activityFlushInterval = setInterval(() => {
|
|
12435
|
+
flushActivityBuffer().catch((err) => {
|
|
12436
|
+
logger.warn(`cohort-sync: activity flush tick failed: ${String(err)}`);
|
|
12437
|
+
});
|
|
12438
|
+
}, 1500);
|
|
12439
|
+
logger.info("cohort-sync: activity flush started (1.5s interval)");
|
|
12184
12440
|
});
|
|
12185
12441
|
api.on("agent_end", async (_event, ctx) => {
|
|
12186
12442
|
const agentId = ctx.agentId ?? "main";
|
|
12187
12443
|
const agentName = resolveAgentName(agentId);
|
|
12188
12444
|
try {
|
|
12445
|
+
tracker.updateStatus(agentName, "idle");
|
|
12189
12446
|
await syncAgentStatus(agentName, "idle", resolveModel(agentId), cfg, logger);
|
|
12190
|
-
|
|
12191
|
-
|
|
12447
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
12448
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12449
|
+
if (snapshot) {
|
|
12450
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12451
|
+
tracker.markTelemetryPushed(agentName);
|
|
12452
|
+
}
|
|
12453
|
+
}
|
|
12192
12454
|
} catch (err) {
|
|
12193
12455
|
logger.warn(`cohort-sync: agent_end sync failed: ${String(err)}`);
|
|
12194
12456
|
}
|
|
@@ -12199,14 +12461,26 @@ function registerHooks(api, cfg) {
|
|
|
12199
12461
|
try {
|
|
12200
12462
|
const usage = event.usage ?? {};
|
|
12201
12463
|
const ext = event;
|
|
12202
|
-
const
|
|
12464
|
+
const sessionKey = ctx.sessionKey;
|
|
12465
|
+
tracker.updateFromLlmOutput(agentName, sessionKey, {
|
|
12203
12466
|
model: event.model ?? resolveModel(agentId),
|
|
12204
12467
|
tokensIn: usage.input ?? 0,
|
|
12205
12468
|
tokensOut: usage.output ?? 0,
|
|
12206
12469
|
contextTokens: ext.contextTokens ?? usage.total ?? 0,
|
|
12207
12470
|
contextLimit: ext.contextLimit ?? 0
|
|
12208
12471
|
});
|
|
12209
|
-
|
|
12472
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
12473
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12474
|
+
if (snapshot) {
|
|
12475
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12476
|
+
tracker.markTelemetryPushed(agentName);
|
|
12477
|
+
}
|
|
12478
|
+
}
|
|
12479
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
12480
|
+
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12481
|
+
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
12482
|
+
tracker.markSessionsPushed(agentName);
|
|
12483
|
+
}
|
|
12210
12484
|
} catch (err) {
|
|
12211
12485
|
logger.warn(`cohort-sync: llm_output telemetry failed: ${String(err)}`);
|
|
12212
12486
|
}
|
|
@@ -12216,12 +12490,22 @@ function registerHooks(api, cfg) {
|
|
|
12216
12490
|
const agentName = resolveAgentName(agentId);
|
|
12217
12491
|
try {
|
|
12218
12492
|
const ext = event;
|
|
12219
|
-
|
|
12220
|
-
compacted: true,
|
|
12493
|
+
tracker.updateFromCompaction(agentName, {
|
|
12221
12494
|
contextTokens: ext.contextTokens ?? event.tokenCount ?? 0,
|
|
12222
12495
|
contextLimit: ext.contextLimit ?? 0
|
|
12223
12496
|
});
|
|
12224
|
-
|
|
12497
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
12498
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12499
|
+
if (snapshot) {
|
|
12500
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12501
|
+
tracker.markTelemetryPushed(agentName);
|
|
12502
|
+
}
|
|
12503
|
+
}
|
|
12504
|
+
const entry = buildActivitySentence(agentName, "after_compaction", {
|
|
12505
|
+
beforePercent: ext.beforePercent,
|
|
12506
|
+
afterPercent: ext.afterPercent
|
|
12507
|
+
});
|
|
12508
|
+
if (entry) tracker.addActivity(entry);
|
|
12225
12509
|
} catch (err) {
|
|
12226
12510
|
logger.warn(`cohort-sync: after_compaction telemetry failed: ${String(err)}`);
|
|
12227
12511
|
}
|
|
@@ -12230,8 +12514,18 @@ function registerHooks(api, cfg) {
|
|
|
12230
12514
|
const agentId = ctx.agentId ?? "main";
|
|
12231
12515
|
const agentName = resolveAgentName(agentId);
|
|
12232
12516
|
try {
|
|
12233
|
-
|
|
12234
|
-
|
|
12517
|
+
tracker.updateStatus(agentName, "working");
|
|
12518
|
+
if (tracker.shouldPushTelemetry(agentName)) {
|
|
12519
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12520
|
+
if (snapshot) {
|
|
12521
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12522
|
+
tracker.markTelemetryPushed(agentName);
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
12525
|
+
const entry = buildActivitySentence(agentName, "before_agent_start", {
|
|
12526
|
+
channel: ctx.messageProvider ?? "unknown"
|
|
12527
|
+
});
|
|
12528
|
+
if (entry) tracker.addActivity(entry);
|
|
12235
12529
|
} catch (err) {
|
|
12236
12530
|
logger.warn(`cohort-sync: before_agent_start telemetry failed: ${String(err)}`);
|
|
12237
12531
|
}
|
|
@@ -12240,15 +12534,18 @@ function registerHooks(api, cfg) {
|
|
|
12240
12534
|
const agentId = ctx.agentId ?? "main";
|
|
12241
12535
|
const agentName = resolveAgentName(agentId);
|
|
12242
12536
|
try {
|
|
12243
|
-
const
|
|
12244
|
-
|
|
12245
|
-
|
|
12246
|
-
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12537
|
+
const sessionKey = ctx.sessionKey ?? event.sessionId ?? String(Date.now());
|
|
12538
|
+
tracker.addSession(agentName, sessionKey);
|
|
12539
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
12540
|
+
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12541
|
+
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
12542
|
+
tracker.markSessionsPushed(agentName);
|
|
12543
|
+
}
|
|
12544
|
+
const parsed = parseSessionKey(sessionKey);
|
|
12545
|
+
const entry = buildActivitySentence(agentName, "session_start", {
|
|
12546
|
+
channel: parsed.channel
|
|
12250
12547
|
});
|
|
12251
|
-
|
|
12548
|
+
if (entry) tracker.addActivity(entry);
|
|
12252
12549
|
} catch (err) {
|
|
12253
12550
|
logger.warn(`cohort-sync: session_start tracking failed: ${String(err)}`);
|
|
12254
12551
|
}
|
|
@@ -12257,35 +12554,88 @@ function registerHooks(api, cfg) {
|
|
|
12257
12554
|
const agentId = ctx.agentId ?? "main";
|
|
12258
12555
|
const agentName = resolveAgentName(agentId);
|
|
12259
12556
|
try {
|
|
12260
|
-
const
|
|
12261
|
-
|
|
12262
|
-
|
|
12557
|
+
const sessionKey = ctx.sessionKey ?? event.sessionId ?? "";
|
|
12558
|
+
tracker.removeSession(agentName, sessionKey);
|
|
12559
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
12560
|
+
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12561
|
+
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
12562
|
+
tracker.markSessionsPushed(agentName);
|
|
12563
|
+
}
|
|
12564
|
+
const entry = buildActivitySentence(agentName, "session_end", {});
|
|
12565
|
+
if (entry) tracker.addActivity(entry);
|
|
12263
12566
|
} catch (err) {
|
|
12264
12567
|
logger.warn(`cohort-sync: session_end tracking failed: ${String(err)}`);
|
|
12265
12568
|
}
|
|
12266
12569
|
});
|
|
12570
|
+
api.on("before_tool_call", async (event, ctx) => {
|
|
12571
|
+
const agentId = ctx.agentId ?? "main";
|
|
12572
|
+
const agentName = resolveAgentName(agentId);
|
|
12573
|
+
try {
|
|
12574
|
+
const entry = buildActivitySentence(agentName, "before_tool_call", {
|
|
12575
|
+
toolName: event.toolName ?? event.name,
|
|
12576
|
+
params: event.params ?? event.input
|
|
12577
|
+
});
|
|
12578
|
+
if (entry) tracker.addActivity(entry);
|
|
12579
|
+
} catch (err) {
|
|
12580
|
+
logger.warn(`cohort-sync: before_tool_call activity failed: ${String(err)}`);
|
|
12581
|
+
}
|
|
12582
|
+
});
|
|
12583
|
+
api.on("message_received", async (_event, ctx) => {
|
|
12584
|
+
const agentId = ctx.agentId ?? "main";
|
|
12585
|
+
const agentName = resolveAgentName(agentId);
|
|
12586
|
+
try {
|
|
12587
|
+
const entry = buildActivitySentence(agentName, "message_received", {
|
|
12588
|
+
channel: ctx.messageProvider ?? "unknown"
|
|
12589
|
+
});
|
|
12590
|
+
if (entry) tracker.addActivity(entry);
|
|
12591
|
+
} catch (err) {
|
|
12592
|
+
logger.warn(`cohort-sync: message_received activity failed: ${String(err)}`);
|
|
12593
|
+
}
|
|
12594
|
+
});
|
|
12595
|
+
api.on("message_sent", async (_event, ctx) => {
|
|
12596
|
+
const agentId = ctx.agentId ?? "main";
|
|
12597
|
+
const agentName = resolveAgentName(agentId);
|
|
12598
|
+
try {
|
|
12599
|
+
const entry = buildActivitySentence(agentName, "message_sent", {
|
|
12600
|
+
channel: ctx.messageProvider ?? "unknown"
|
|
12601
|
+
});
|
|
12602
|
+
if (entry) tracker.addActivity(entry);
|
|
12603
|
+
} catch (err) {
|
|
12604
|
+
logger.warn(`cohort-sync: message_sent activity failed: ${String(err)}`);
|
|
12605
|
+
}
|
|
12606
|
+
});
|
|
12267
12607
|
api.on("gateway_stop", async () => {
|
|
12268
12608
|
if (heartbeatInterval) {
|
|
12269
12609
|
clearInterval(heartbeatInterval);
|
|
12270
12610
|
heartbeatInterval = null;
|
|
12271
12611
|
}
|
|
12612
|
+
if (activityFlushInterval) {
|
|
12613
|
+
clearInterval(activityFlushInterval);
|
|
12614
|
+
activityFlushInterval = null;
|
|
12615
|
+
}
|
|
12616
|
+
await flushActivityBuffer().catch((err) => {
|
|
12617
|
+
logger.warn(`cohort-sync: final activity flush failed: ${String(err)}`);
|
|
12618
|
+
});
|
|
12272
12619
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
12273
12620
|
for (const agentId of allAgentIds) {
|
|
12274
12621
|
const agentName = resolveAgentName(agentId);
|
|
12275
12622
|
try {
|
|
12276
|
-
|
|
12277
|
-
|
|
12623
|
+
tracker.updateStatus(agentName, "unreachable");
|
|
12624
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12625
|
+
if (snapshot) {
|
|
12626
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12627
|
+
}
|
|
12278
12628
|
} catch (err) {
|
|
12279
12629
|
logger.warn(`cohort-sync: final unreachable push failed for ${agentName}: ${String(err)}`);
|
|
12280
12630
|
}
|
|
12281
12631
|
}
|
|
12632
|
+
saveAgentStatesToHot(tracker.exportState());
|
|
12282
12633
|
try {
|
|
12283
12634
|
await markAllUnreachable(cfg, logger);
|
|
12284
12635
|
} catch (err) {
|
|
12285
12636
|
logger.warn(`cohort-sync: markAllUnreachable failed: ${String(err)}`);
|
|
12286
12637
|
}
|
|
12287
|
-
|
|
12288
|
-
sessions.clear();
|
|
12638
|
+
tracker.clear();
|
|
12289
12639
|
closeSubscription();
|
|
12290
12640
|
logger.info("cohort-sync: subscription closed");
|
|
12291
12641
|
});
|
package/dist/package.json
CHANGED