@cfio/cohort-sync 0.1.2 → 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 +464 -91
- 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) {
|
|
@@ -11791,6 +11811,22 @@ async function v1Post(apiUrl, apiKey, path2, body) {
|
|
|
11791
11811
|
});
|
|
11792
11812
|
if (!res.ok) throw new Error(`POST ${path2} \u2192 ${res.status}`);
|
|
11793
11813
|
}
|
|
11814
|
+
async function checkForUpdate(currentVersion, logger) {
|
|
11815
|
+
try {
|
|
11816
|
+
const res = await fetch("https://registry.npmjs.org/@cfio/cohort-sync/latest", {
|
|
11817
|
+
signal: AbortSignal.timeout(5e3)
|
|
11818
|
+
});
|
|
11819
|
+
if (!res.ok) return;
|
|
11820
|
+
const data = await res.json();
|
|
11821
|
+
const latest = data.version;
|
|
11822
|
+
if (latest && latest !== currentVersion) {
|
|
11823
|
+
logger.warn(
|
|
11824
|
+
`cohort-sync: update available (${currentVersion} \u2192 ${latest}) \u2014 run "npm install -g @cfio/cohort-sync" to update`
|
|
11825
|
+
);
|
|
11826
|
+
}
|
|
11827
|
+
} catch {
|
|
11828
|
+
}
|
|
11829
|
+
}
|
|
11794
11830
|
async function syncAgentStatus(agentName, status, model, cfg, logger) {
|
|
11795
11831
|
try {
|
|
11796
11832
|
const data = await v1Get(cfg.apiUrl, cfg.apiKey, "/api/v1/agents?limit=100");
|
|
@@ -11941,78 +11977,294 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
11941
11977
|
logger.info("cohort-sync: full sync complete");
|
|
11942
11978
|
}
|
|
11943
11979
|
|
|
11944
|
-
// src/
|
|
11945
|
-
|
|
11946
|
-
|
|
11947
|
-
|
|
11948
|
-
|
|
11949
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
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,
|
|
11952
12132
|
model: "unknown",
|
|
11953
12133
|
contextTokens: 0,
|
|
11954
12134
|
contextLimit: 0,
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
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
|
|
11959
12190
|
};
|
|
11960
|
-
if (delta.model) current.model = delta.model;
|
|
11961
|
-
if (delta.status) current.status = delta.status;
|
|
11962
|
-
if (delta.tokensIn !== void 0) current.tokensIn += delta.tokensIn;
|
|
11963
|
-
if (delta.tokensOut !== void 0) current.tokensOut += delta.tokensOut;
|
|
11964
|
-
if (delta.contextTokens !== void 0) current.contextTokens = delta.contextTokens;
|
|
11965
|
-
if (delta.contextLimit !== void 0) current.contextLimit = delta.contextLimit;
|
|
11966
|
-
if (delta.activeSessions !== void 0) current.activeSessions = delta.activeSessions;
|
|
11967
|
-
if (delta.compacted) {
|
|
11968
|
-
current.compactions += 1;
|
|
11969
|
-
}
|
|
11970
|
-
this.state.set(agentName, current);
|
|
11971
|
-
return { ...current };
|
|
11972
|
-
}
|
|
11973
|
-
/** Get current state for an agent. */
|
|
11974
|
-
get(agentName) {
|
|
11975
|
-
const s = this.state.get(agentName);
|
|
11976
|
-
return s ? { ...s } : void 0;
|
|
11977
|
-
}
|
|
11978
|
-
/** Return the last known snapshot for an agent (null if unknown). */
|
|
11979
|
-
getSnapshot(agentName) {
|
|
11980
|
-
return this.state.has(agentName) ? { ...this.state.get(agentName) } : null;
|
|
11981
|
-
}
|
|
11982
|
-
/** Clear all tracked state (e.g., on gateway_stop). */
|
|
11983
|
-
clear() {
|
|
11984
|
-
this.state.clear();
|
|
11985
12191
|
}
|
|
11986
|
-
|
|
11987
|
-
|
|
11988
|
-
|
|
11989
|
-
|
|
11990
|
-
|
|
11991
|
-
|
|
11992
|
-
|
|
11993
|
-
|
|
11994
|
-
|
|
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);
|
|
11995
12211
|
}
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
this.
|
|
11999
|
-
|
|
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);
|
|
12000
12217
|
}
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12218
|
+
markTelemetryPushed(agentName) {
|
|
12219
|
+
const state = this.agents.get(agentName);
|
|
12220
|
+
if (state) {
|
|
12221
|
+
state.lastPushedTelemetry = this.getTelemetrySnapshot(agentName);
|
|
12222
|
+
}
|
|
12004
12223
|
}
|
|
12005
|
-
|
|
12006
|
-
|
|
12007
|
-
|
|
12224
|
+
markSessionsPushed(agentName) {
|
|
12225
|
+
const state = this.agents.get(agentName);
|
|
12226
|
+
if (state) {
|
|
12227
|
+
state.lastPushedSessions = this.getSessionsSnapshot(agentName);
|
|
12228
|
+
}
|
|
12229
|
+
}
|
|
12230
|
+
// --- Activity buffer ---
|
|
12231
|
+
addActivity(entry) {
|
|
12232
|
+
this.activityBuffer.push(entry);
|
|
12233
|
+
}
|
|
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());
|
|
12008
12242
|
}
|
|
12009
|
-
/** Clear all sessions (e.g., on gateway_stop). */
|
|
12010
12243
|
clear() {
|
|
12011
|
-
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
|
+
}
|
|
12012
12257
|
}
|
|
12013
12258
|
};
|
|
12014
12259
|
|
|
12015
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
|
+
}
|
|
12016
12268
|
function parseIdentityFile(workspaceDir) {
|
|
12017
12269
|
try {
|
|
12018
12270
|
const filePath = path.join(workspaceDir, "IDENTITY.md");
|
|
@@ -12048,13 +12300,18 @@ function resolveIdentity(configIdentity, workspaceDir) {
|
|
|
12048
12300
|
function registerHooks(api, cfg) {
|
|
12049
12301
|
const { logger, config } = api;
|
|
12050
12302
|
const nameMap = cfg.agentNameMap;
|
|
12051
|
-
const
|
|
12052
|
-
const sessions = new SessionTracker();
|
|
12303
|
+
const tracker = new AgentStateTracker();
|
|
12053
12304
|
let heartbeatInterval = null;
|
|
12305
|
+
let activityFlushInterval = null;
|
|
12054
12306
|
setConvexUrl(cfg);
|
|
12055
12307
|
setLogger(logger);
|
|
12056
12308
|
restoreFromHotReload(logger);
|
|
12057
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
|
+
}
|
|
12058
12315
|
function resolveAgentName(agentId) {
|
|
12059
12316
|
return (nameMap?.[agentId] ?? agentId).toLowerCase();
|
|
12060
12317
|
}
|
|
@@ -12107,9 +12364,9 @@ function registerHooks(api, cfg) {
|
|
|
12107
12364
|
for (const agentId of allAgentIds) {
|
|
12108
12365
|
const agentName = resolveAgentName(agentId);
|
|
12109
12366
|
try {
|
|
12110
|
-
const snapshot =
|
|
12367
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12111
12368
|
if (snapshot) {
|
|
12112
|
-
await pushTelemetry(cfg.apiKey, snapshot);
|
|
12369
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12113
12370
|
} else {
|
|
12114
12371
|
logger.info(`cohort-sync: heartbeat skipped ${agentName} \u2014 no snapshot in tracker`);
|
|
12115
12372
|
}
|
|
@@ -12119,7 +12376,21 @@ function registerHooks(api, cfg) {
|
|
|
12119
12376
|
}
|
|
12120
12377
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
12121
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
|
+
}
|
|
12122
12388
|
api.on("gateway_start", async (event) => {
|
|
12389
|
+
try {
|
|
12390
|
+
checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
|
|
12391
|
+
});
|
|
12392
|
+
} catch {
|
|
12393
|
+
}
|
|
12123
12394
|
try {
|
|
12124
12395
|
const agentList = (config?.agents?.list ?? []).map((a) => ({
|
|
12125
12396
|
id: a.id,
|
|
@@ -12142,11 +12413,13 @@ function registerHooks(api, cfg) {
|
|
|
12142
12413
|
for (const agentId of allAgentIds) {
|
|
12143
12414
|
const agentName = resolveAgentName(agentId);
|
|
12144
12415
|
try {
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
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
|
+
}
|
|
12150
12423
|
} catch (err) {
|
|
12151
12424
|
logger.warn(`cohort-sync: initial telemetry seed failed for ${agentName}: ${String(err)}`);
|
|
12152
12425
|
}
|
|
@@ -12158,14 +12431,26 @@ function registerHooks(api, cfg) {
|
|
|
12158
12431
|
});
|
|
12159
12432
|
}, 12e4);
|
|
12160
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)");
|
|
12161
12440
|
});
|
|
12162
12441
|
api.on("agent_end", async (_event, ctx) => {
|
|
12163
12442
|
const agentId = ctx.agentId ?? "main";
|
|
12164
12443
|
const agentName = resolveAgentName(agentId);
|
|
12165
12444
|
try {
|
|
12445
|
+
tracker.updateStatus(agentName, "idle");
|
|
12166
12446
|
await syncAgentStatus(agentName, "idle", resolveModel(agentId), cfg, logger);
|
|
12167
|
-
|
|
12168
|
-
|
|
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
|
+
}
|
|
12169
12454
|
} catch (err) {
|
|
12170
12455
|
logger.warn(`cohort-sync: agent_end sync failed: ${String(err)}`);
|
|
12171
12456
|
}
|
|
@@ -12176,14 +12461,26 @@ function registerHooks(api, cfg) {
|
|
|
12176
12461
|
try {
|
|
12177
12462
|
const usage = event.usage ?? {};
|
|
12178
12463
|
const ext = event;
|
|
12179
|
-
const
|
|
12464
|
+
const sessionKey = ctx.sessionKey;
|
|
12465
|
+
tracker.updateFromLlmOutput(agentName, sessionKey, {
|
|
12180
12466
|
model: event.model ?? resolveModel(agentId),
|
|
12181
12467
|
tokensIn: usage.input ?? 0,
|
|
12182
12468
|
tokensOut: usage.output ?? 0,
|
|
12183
12469
|
contextTokens: ext.contextTokens ?? usage.total ?? 0,
|
|
12184
12470
|
contextLimit: ext.contextLimit ?? 0
|
|
12185
12471
|
});
|
|
12186
|
-
|
|
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
|
+
}
|
|
12187
12484
|
} catch (err) {
|
|
12188
12485
|
logger.warn(`cohort-sync: llm_output telemetry failed: ${String(err)}`);
|
|
12189
12486
|
}
|
|
@@ -12193,12 +12490,22 @@ function registerHooks(api, cfg) {
|
|
|
12193
12490
|
const agentName = resolveAgentName(agentId);
|
|
12194
12491
|
try {
|
|
12195
12492
|
const ext = event;
|
|
12196
|
-
|
|
12197
|
-
compacted: true,
|
|
12493
|
+
tracker.updateFromCompaction(agentName, {
|
|
12198
12494
|
contextTokens: ext.contextTokens ?? event.tokenCount ?? 0,
|
|
12199
12495
|
contextLimit: ext.contextLimit ?? 0
|
|
12200
12496
|
});
|
|
12201
|
-
|
|
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);
|
|
12202
12509
|
} catch (err) {
|
|
12203
12510
|
logger.warn(`cohort-sync: after_compaction telemetry failed: ${String(err)}`);
|
|
12204
12511
|
}
|
|
@@ -12207,8 +12514,18 @@ function registerHooks(api, cfg) {
|
|
|
12207
12514
|
const agentId = ctx.agentId ?? "main";
|
|
12208
12515
|
const agentName = resolveAgentName(agentId);
|
|
12209
12516
|
try {
|
|
12210
|
-
|
|
12211
|
-
|
|
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);
|
|
12212
12529
|
} catch (err) {
|
|
12213
12530
|
logger.warn(`cohort-sync: before_agent_start telemetry failed: ${String(err)}`);
|
|
12214
12531
|
}
|
|
@@ -12217,15 +12534,18 @@ function registerHooks(api, cfg) {
|
|
|
12217
12534
|
const agentId = ctx.agentId ?? "main";
|
|
12218
12535
|
const agentName = resolveAgentName(agentId);
|
|
12219
12536
|
try {
|
|
12220
|
-
const
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
12226
|
-
|
|
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
|
|
12227
12547
|
});
|
|
12228
|
-
|
|
12548
|
+
if (entry) tracker.addActivity(entry);
|
|
12229
12549
|
} catch (err) {
|
|
12230
12550
|
logger.warn(`cohort-sync: session_start tracking failed: ${String(err)}`);
|
|
12231
12551
|
}
|
|
@@ -12234,35 +12554,88 @@ function registerHooks(api, cfg) {
|
|
|
12234
12554
|
const agentId = ctx.agentId ?? "main";
|
|
12235
12555
|
const agentName = resolveAgentName(agentId);
|
|
12236
12556
|
try {
|
|
12237
|
-
const
|
|
12238
|
-
|
|
12239
|
-
|
|
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);
|
|
12240
12566
|
} catch (err) {
|
|
12241
12567
|
logger.warn(`cohort-sync: session_end tracking failed: ${String(err)}`);
|
|
12242
12568
|
}
|
|
12243
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
|
+
});
|
|
12244
12607
|
api.on("gateway_stop", async () => {
|
|
12245
12608
|
if (heartbeatInterval) {
|
|
12246
12609
|
clearInterval(heartbeatInterval);
|
|
12247
12610
|
heartbeatInterval = null;
|
|
12248
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
|
+
});
|
|
12249
12619
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
12250
12620
|
for (const agentId of allAgentIds) {
|
|
12251
12621
|
const agentName = resolveAgentName(agentId);
|
|
12252
12622
|
try {
|
|
12253
|
-
|
|
12254
|
-
|
|
12623
|
+
tracker.updateStatus(agentName, "unreachable");
|
|
12624
|
+
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12625
|
+
if (snapshot) {
|
|
12626
|
+
await pushTelemetry(cfg.apiKey, { ...snapshot, pluginVersion: PLUGIN_VERSION });
|
|
12627
|
+
}
|
|
12255
12628
|
} catch (err) {
|
|
12256
12629
|
logger.warn(`cohort-sync: final unreachable push failed for ${agentName}: ${String(err)}`);
|
|
12257
12630
|
}
|
|
12258
12631
|
}
|
|
12632
|
+
saveAgentStatesToHot(tracker.exportState());
|
|
12259
12633
|
try {
|
|
12260
12634
|
await markAllUnreachable(cfg, logger);
|
|
12261
12635
|
} catch (err) {
|
|
12262
12636
|
logger.warn(`cohort-sync: markAllUnreachable failed: ${String(err)}`);
|
|
12263
12637
|
}
|
|
12264
|
-
|
|
12265
|
-
sessions.clear();
|
|
12638
|
+
tracker.clear();
|
|
12266
12639
|
closeSubscription();
|
|
12267
12640
|
logger.info("cohort-sync: subscription closed");
|
|
12268
12641
|
});
|
package/dist/package.json
CHANGED