@cfio/cohort-sync 0.2.0 → 0.2.2
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 +663 -136
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11568,23 +11568,35 @@ var upsertSessionsFromPlugin = makeFunctionReference("telemetryPlugin:upsertSess
|
|
|
11568
11568
|
var pushActivityFromPluginRef = makeFunctionReference("activityFeed:pushActivityFromPlugin");
|
|
11569
11569
|
var HOT_KEY = "__cohort_sync__";
|
|
11570
11570
|
function getHotState() {
|
|
11571
|
-
|
|
11572
|
-
|
|
11573
|
-
|
|
11574
|
-
|
|
11571
|
+
let state = globalThis[HOT_KEY];
|
|
11572
|
+
if (!state) {
|
|
11573
|
+
state = {
|
|
11574
|
+
tracker: null,
|
|
11575
|
+
client: null,
|
|
11576
|
+
convexUrl: null,
|
|
11577
|
+
unsubscribers: [],
|
|
11578
|
+
lastKnownRoster: [],
|
|
11579
|
+
intervals: { heartbeat: null, activityFlush: null },
|
|
11580
|
+
activityBuffer: [],
|
|
11581
|
+
channelAgentBridge: {}
|
|
11582
|
+
};
|
|
11583
|
+
globalThis[HOT_KEY] = state;
|
|
11584
|
+
}
|
|
11585
|
+
if (!state.activityBuffer) state.activityBuffer = [];
|
|
11586
|
+
if (!state.intervals) state.intervals = { heartbeat: null, activityFlush: null };
|
|
11587
|
+
if (!state.channelAgentBridge) state.channelAgentBridge = {};
|
|
11588
|
+
if (!state.unsubscribers) state.unsubscribers = [];
|
|
11589
|
+
if (!state.lastKnownRoster) state.lastKnownRoster = [];
|
|
11590
|
+
return state;
|
|
11575
11591
|
}
|
|
11576
11592
|
function clearHotState() {
|
|
11577
11593
|
delete globalThis[HOT_KEY];
|
|
11578
11594
|
}
|
|
11579
11595
|
function setRosterHotState(roster) {
|
|
11580
|
-
|
|
11581
|
-
if (hot) {
|
|
11582
|
-
hot.lastKnownRoster = roster;
|
|
11583
|
-
}
|
|
11596
|
+
getHotState().lastKnownRoster = roster;
|
|
11584
11597
|
}
|
|
11585
11598
|
function getRosterHotState() {
|
|
11586
|
-
|
|
11587
|
-
return hot?.lastKnownRoster ?? null;
|
|
11599
|
+
return getHotState().lastKnownRoster;
|
|
11588
11600
|
}
|
|
11589
11601
|
var savedLogger = null;
|
|
11590
11602
|
function setLogger(logger) {
|
|
@@ -11601,51 +11613,54 @@ var client = null;
|
|
|
11601
11613
|
var savedConvexUrl = null;
|
|
11602
11614
|
var unsubscribers = [];
|
|
11603
11615
|
function setConvexUrl(cfg) {
|
|
11604
|
-
|
|
11616
|
+
const url = cfg.convexUrl ?? deriveConvexUrl(cfg.apiUrl);
|
|
11617
|
+
savedConvexUrl = url;
|
|
11618
|
+
getHotState().convexUrl = url;
|
|
11605
11619
|
}
|
|
11606
11620
|
function restoreFromHotReload(logger) {
|
|
11607
|
-
const
|
|
11608
|
-
if (!
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
savedConvexUrl = hot.convexUrl;
|
|
11621
|
+
const state = getHotState();
|
|
11622
|
+
if (!client && state.client) {
|
|
11623
|
+
client = state.client;
|
|
11624
|
+
savedConvexUrl = state.convexUrl;
|
|
11612
11625
|
logger.info("cohort-sync: recovered ConvexClient after hot-reload");
|
|
11613
11626
|
}
|
|
11614
|
-
if (unsubscribers.length === 0 &&
|
|
11615
|
-
unsubscribers.push(...
|
|
11616
|
-
logger.info(`cohort-sync: recovered ${
|
|
11627
|
+
if (unsubscribers.length === 0 && state.unsubscribers.length > 0) {
|
|
11628
|
+
unsubscribers.push(...state.unsubscribers);
|
|
11629
|
+
logger.info(`cohort-sync: recovered ${state.unsubscribers.length} notification subscriptions after hot-reload`);
|
|
11617
11630
|
}
|
|
11618
11631
|
}
|
|
11619
11632
|
function getOrCreateClient() {
|
|
11620
11633
|
if (client) return client;
|
|
11621
|
-
const
|
|
11622
|
-
if (
|
|
11623
|
-
client =
|
|
11634
|
+
const state = getHotState();
|
|
11635
|
+
if (state.client) {
|
|
11636
|
+
client = state.client;
|
|
11624
11637
|
getLogger().info("cohort-sync: recovered ConvexClient from globalThis");
|
|
11625
11638
|
return client;
|
|
11626
11639
|
}
|
|
11627
11640
|
if (!savedConvexUrl) return null;
|
|
11628
11641
|
client = new ConvexClient(savedConvexUrl);
|
|
11629
11642
|
getLogger().info(`cohort-sync: created fresh ConvexClient (${savedConvexUrl})`);
|
|
11630
|
-
|
|
11643
|
+
state.client = client;
|
|
11644
|
+
state.convexUrl = savedConvexUrl;
|
|
11631
11645
|
return client;
|
|
11632
11646
|
}
|
|
11633
11647
|
async function initSubscription(port, cfg, hooksToken, logger) {
|
|
11634
11648
|
const convexUrl = cfg.convexUrl ?? deriveConvexUrl(cfg.apiUrl);
|
|
11635
11649
|
savedConvexUrl = convexUrl;
|
|
11636
|
-
const
|
|
11637
|
-
if (
|
|
11638
|
-
client =
|
|
11650
|
+
const state = getHotState();
|
|
11651
|
+
if (state.client) {
|
|
11652
|
+
client = state.client;
|
|
11639
11653
|
logger.info("cohort-sync: reusing hot-reload ConvexClient for subscription");
|
|
11640
11654
|
} else {
|
|
11641
11655
|
client = new ConvexClient(convexUrl);
|
|
11642
11656
|
logger.info(`cohort-sync: created new ConvexClient for subscription (${convexUrl})`);
|
|
11643
11657
|
}
|
|
11658
|
+
state.client = client;
|
|
11659
|
+
state.convexUrl = convexUrl;
|
|
11644
11660
|
if (!hooksToken) {
|
|
11645
11661
|
logger.warn(
|
|
11646
11662
|
`cohort-sync: hooks.token not configured \u2014 real-time notifications disabled (telemetry will still be pushed).`
|
|
11647
11663
|
);
|
|
11648
|
-
setHotState({ client, convexUrl, unsubscribers: [], lastKnownRoster: [] });
|
|
11649
11664
|
return;
|
|
11650
11665
|
}
|
|
11651
11666
|
const agentNames = cfg.agentNameMap ? Object.values(cfg.agentNameMap) : ["main"];
|
|
@@ -11688,7 +11703,7 @@ async function initSubscription(port, cfg, hooksToken, logger) {
|
|
|
11688
11703
|
);
|
|
11689
11704
|
unsubscribers.push(unsubscribe);
|
|
11690
11705
|
}
|
|
11691
|
-
|
|
11706
|
+
state.unsubscribers = [...unsubscribers];
|
|
11692
11707
|
}
|
|
11693
11708
|
function closeSubscription() {
|
|
11694
11709
|
for (const unsub of unsubscribers) {
|
|
@@ -11698,13 +11713,11 @@ function closeSubscription() {
|
|
|
11698
11713
|
}
|
|
11699
11714
|
}
|
|
11700
11715
|
unsubscribers.length = 0;
|
|
11701
|
-
const
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
} catch {
|
|
11707
|
-
}
|
|
11716
|
+
const state = getHotState();
|
|
11717
|
+
for (const unsub of state.unsubscribers) {
|
|
11718
|
+
try {
|
|
11719
|
+
unsub();
|
|
11720
|
+
} catch {
|
|
11708
11721
|
}
|
|
11709
11722
|
}
|
|
11710
11723
|
client?.close();
|
|
@@ -11739,14 +11752,35 @@ async function pushActivity(apiKey, entries) {
|
|
|
11739
11752
|
getLogger().error(`cohort-sync: pushActivity failed: ${err}`);
|
|
11740
11753
|
}
|
|
11741
11754
|
}
|
|
11742
|
-
function
|
|
11743
|
-
const
|
|
11744
|
-
|
|
11745
|
-
|
|
11755
|
+
function saveIntervalsToHot(heartbeat, activityFlush) {
|
|
11756
|
+
const state = getHotState();
|
|
11757
|
+
state.intervals = { heartbeat, activityFlush };
|
|
11758
|
+
}
|
|
11759
|
+
function clearIntervalsFromHot() {
|
|
11760
|
+
const state = getHotState();
|
|
11761
|
+
if (state.intervals.heartbeat) clearInterval(state.intervals.heartbeat);
|
|
11762
|
+
if (state.intervals.activityFlush) clearInterval(state.intervals.activityFlush);
|
|
11763
|
+
state.intervals = { heartbeat: null, activityFlush: null };
|
|
11764
|
+
}
|
|
11765
|
+
function addActivityToHot(entry) {
|
|
11766
|
+
const state = getHotState();
|
|
11767
|
+
state.activityBuffer.push(entry);
|
|
11768
|
+
const logger = savedLogger;
|
|
11769
|
+
if (logger) {
|
|
11770
|
+
logger.info(`cohort-sync: +activity [${entry.category}] "${entry.text}"`);
|
|
11746
11771
|
}
|
|
11747
11772
|
}
|
|
11748
|
-
function
|
|
11749
|
-
|
|
11773
|
+
function drainActivityFromHot() {
|
|
11774
|
+
const state = getHotState();
|
|
11775
|
+
const buf = state.activityBuffer;
|
|
11776
|
+
state.activityBuffer = [];
|
|
11777
|
+
return buf;
|
|
11778
|
+
}
|
|
11779
|
+
function setChannelAgent(channelId, agentName) {
|
|
11780
|
+
getHotState().channelAgentBridge[channelId] = agentName;
|
|
11781
|
+
}
|
|
11782
|
+
function getChannelAgent(channelId) {
|
|
11783
|
+
return getHotState().channelAgentBridge[channelId] ?? null;
|
|
11750
11784
|
}
|
|
11751
11785
|
|
|
11752
11786
|
// src/sync.ts
|
|
@@ -11977,6 +12011,9 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
11977
12011
|
logger.info("cohort-sync: full sync complete");
|
|
11978
12012
|
}
|
|
11979
12013
|
|
|
12014
|
+
// src/agent-state.ts
|
|
12015
|
+
import { basename } from "node:path";
|
|
12016
|
+
|
|
11980
12017
|
// src/session-key.ts
|
|
11981
12018
|
function parseSessionKey(key) {
|
|
11982
12019
|
if (!key || !key.includes(":")) {
|
|
@@ -12014,6 +12051,7 @@ function shouldPushTelemetry(current, lastPushed) {
|
|
|
12014
12051
|
if (!lastPushed) return true;
|
|
12015
12052
|
if (current.status !== lastPushed.status) return true;
|
|
12016
12053
|
if (current.compactions !== lastPushed.compactions) return true;
|
|
12054
|
+
if (current.activeSessions !== lastPushed.activeSessions) return true;
|
|
12017
12055
|
if (current.contextLimit > 0 && lastPushed.contextLimit > 0) {
|
|
12018
12056
|
const currentPct = current.contextTokens / current.contextLimit;
|
|
12019
12057
|
const lastPct = lastPushed.contextTokens / lastPushed.contextLimit;
|
|
@@ -12042,57 +12080,180 @@ function shouldPushSessions(current, lastPushed) {
|
|
|
12042
12080
|
}
|
|
12043
12081
|
return false;
|
|
12044
12082
|
}
|
|
12045
|
-
function
|
|
12046
|
-
const
|
|
12047
|
-
|
|
12083
|
+
function summarizeError(error) {
|
|
12084
|
+
const str = String(error ?? "");
|
|
12085
|
+
const firstLine = str.split("\n")[0] ?? str;
|
|
12086
|
+
return firstLine.slice(0, 120);
|
|
12048
12087
|
}
|
|
12049
|
-
function
|
|
12088
|
+
function formatDuration(ms) {
|
|
12089
|
+
const seconds = Math.round(ms / 1e3);
|
|
12090
|
+
if (seconds < 60) return `${seconds}s`;
|
|
12091
|
+
const minutes = Math.round(seconds / 60);
|
|
12092
|
+
return `${minutes}m`;
|
|
12093
|
+
}
|
|
12094
|
+
function buildActivityEntry(agentName, hook, context) {
|
|
12050
12095
|
const ts = Date.now();
|
|
12051
12096
|
const name = agentName.charAt(0).toUpperCase() + agentName.slice(1);
|
|
12052
|
-
|
|
12053
|
-
|
|
12097
|
+
const sessionKey = context.sessionKey;
|
|
12098
|
+
const model = context.model;
|
|
12099
|
+
const channel = context.channel;
|
|
12100
|
+
switch (hook) {
|
|
12101
|
+
case "after_tool_call": {
|
|
12054
12102
|
const toolName = String(context.toolName ?? "a tool");
|
|
12055
12103
|
const params = context.params;
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
|
|
12104
|
+
const error = context.error;
|
|
12105
|
+
const durationMs = context.durationMs;
|
|
12106
|
+
const hasError = error != null && error !== "";
|
|
12107
|
+
let text;
|
|
12060
12108
|
if (toolName === "Read") {
|
|
12061
12109
|
const file = params?.file_path ? basename(String(params.file_path)) : "a file";
|
|
12062
|
-
|
|
12063
|
-
}
|
|
12064
|
-
|
|
12065
|
-
|
|
12066
|
-
}
|
|
12067
|
-
|
|
12110
|
+
text = hasError ? `${name} failed to read ${file}: ${summarizeError(error)}` : `${name} read ${file}`;
|
|
12111
|
+
} else if (toolName === "Edit" || toolName === "Write") {
|
|
12112
|
+
const file = params?.file_path ? basename(String(params.file_path)) : "a file";
|
|
12113
|
+
text = hasError ? `${name} failed to edit ${file}: ${summarizeError(error)}` : `${name} edited ${file}`;
|
|
12114
|
+
} else if (toolName === "Bash") {
|
|
12115
|
+
text = hasError ? `${name} ran a command (failed: ${summarizeError(error)})` : `${name} ran a command`;
|
|
12116
|
+
} else if (toolName === "cohort_comment") {
|
|
12068
12117
|
const taskNum = params?.task_number ?? "?";
|
|
12069
|
-
|
|
12118
|
+
text = hasError ? `${name} failed to comment on task #${taskNum}: ${summarizeError(error)}` : `${name} commented on task #${taskNum}`;
|
|
12119
|
+
} else {
|
|
12120
|
+
text = hasError ? `${name} used ${toolName} (failed: ${summarizeError(error)})` : `${name} used ${toolName}`;
|
|
12070
12121
|
}
|
|
12071
|
-
return {
|
|
12122
|
+
return {
|
|
12123
|
+
agentName,
|
|
12124
|
+
text,
|
|
12125
|
+
category: "tool",
|
|
12126
|
+
timestamp: ts,
|
|
12127
|
+
hook,
|
|
12128
|
+
toolName,
|
|
12129
|
+
sessionKey,
|
|
12130
|
+
model,
|
|
12131
|
+
durationMs,
|
|
12132
|
+
error: hasError ? true : void 0,
|
|
12133
|
+
errorSummary: hasError ? summarizeError(error) : void 0
|
|
12134
|
+
};
|
|
12072
12135
|
}
|
|
12073
12136
|
case "message_received": {
|
|
12074
|
-
const ch = String(
|
|
12075
|
-
return {
|
|
12137
|
+
const ch = String(channel ?? "unknown");
|
|
12138
|
+
return {
|
|
12139
|
+
agentName,
|
|
12140
|
+
text: `${name} received a message on ${ch}`,
|
|
12141
|
+
category: "message",
|
|
12142
|
+
timestamp: ts,
|
|
12143
|
+
hook,
|
|
12144
|
+
channel: ch,
|
|
12145
|
+
sessionKey,
|
|
12146
|
+
model
|
|
12147
|
+
};
|
|
12076
12148
|
}
|
|
12077
12149
|
case "message_sent": {
|
|
12078
|
-
const ch = String(
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12084
|
-
|
|
12150
|
+
const ch = String(channel ?? "unknown");
|
|
12151
|
+
const success = context.success !== false;
|
|
12152
|
+
const error = context.error;
|
|
12153
|
+
const text = success ? `${name} sent a reply on ${ch}` : `${name} failed to send on ${ch}: ${summarizeError(error)}`;
|
|
12154
|
+
return {
|
|
12155
|
+
agentName,
|
|
12156
|
+
text,
|
|
12157
|
+
category: "message",
|
|
12158
|
+
timestamp: ts,
|
|
12159
|
+
hook,
|
|
12160
|
+
channel: ch,
|
|
12161
|
+
sessionKey,
|
|
12162
|
+
model,
|
|
12163
|
+
error: !success ? true : void 0,
|
|
12164
|
+
errorSummary: !success ? summarizeError(error) : void 0
|
|
12165
|
+
};
|
|
12085
12166
|
}
|
|
12086
12167
|
case "session_start": {
|
|
12087
|
-
const ch = String(
|
|
12088
|
-
|
|
12168
|
+
const ch = String(channel ?? "unknown");
|
|
12169
|
+
const resumed = context.resumedFrom != null;
|
|
12170
|
+
const text = resumed ? `${name} resumed a session` : `${name} started a session on ${ch}`;
|
|
12171
|
+
return {
|
|
12172
|
+
agentName,
|
|
12173
|
+
text,
|
|
12174
|
+
category: "session",
|
|
12175
|
+
timestamp: ts,
|
|
12176
|
+
hook,
|
|
12177
|
+
channel: ch,
|
|
12178
|
+
sessionKey,
|
|
12179
|
+
model
|
|
12180
|
+
};
|
|
12089
12181
|
}
|
|
12090
12182
|
case "session_end": {
|
|
12091
|
-
|
|
12183
|
+
const msgCount = context.messageCount;
|
|
12184
|
+
const durationMs = context.durationMs;
|
|
12185
|
+
const parts = [];
|
|
12186
|
+
if (msgCount != null) parts.push(`${msgCount} messages`);
|
|
12187
|
+
if (durationMs != null) parts.push(formatDuration(durationMs));
|
|
12188
|
+
const stats = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
12189
|
+
return {
|
|
12190
|
+
agentName,
|
|
12191
|
+
text: `${name} ended a session${stats}`,
|
|
12192
|
+
category: "session",
|
|
12193
|
+
timestamp: ts,
|
|
12194
|
+
hook,
|
|
12195
|
+
sessionKey,
|
|
12196
|
+
model,
|
|
12197
|
+
durationMs
|
|
12198
|
+
};
|
|
12199
|
+
}
|
|
12200
|
+
case "before_compaction": {
|
|
12201
|
+
return {
|
|
12202
|
+
agentName,
|
|
12203
|
+
text: `${name} context compaction starting`,
|
|
12204
|
+
category: "system",
|
|
12205
|
+
timestamp: ts,
|
|
12206
|
+
hook,
|
|
12207
|
+
sessionKey,
|
|
12208
|
+
model
|
|
12209
|
+
};
|
|
12092
12210
|
}
|
|
12093
|
-
case "
|
|
12094
|
-
const
|
|
12095
|
-
|
|
12211
|
+
case "after_compaction": {
|
|
12212
|
+
const compactedCount = context.compactedCount;
|
|
12213
|
+
const messageCount = context.messageCount;
|
|
12214
|
+
const parts = [];
|
|
12215
|
+
if (compactedCount != null) parts.push(`${compactedCount} messages removed`);
|
|
12216
|
+
if (messageCount != null) parts.push(`${messageCount} remaining`);
|
|
12217
|
+
const stats = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
12218
|
+
return {
|
|
12219
|
+
agentName,
|
|
12220
|
+
text: `${name} context compacted${stats}`,
|
|
12221
|
+
category: "system",
|
|
12222
|
+
timestamp: ts,
|
|
12223
|
+
hook,
|
|
12224
|
+
sessionKey,
|
|
12225
|
+
model
|
|
12226
|
+
};
|
|
12227
|
+
}
|
|
12228
|
+
case "agent_end": {
|
|
12229
|
+
if (context.success !== false) return null;
|
|
12230
|
+
const error = context.error;
|
|
12231
|
+
const durationMs = context.durationMs;
|
|
12232
|
+
return {
|
|
12233
|
+
agentName,
|
|
12234
|
+
text: `${name} processing failed: ${summarizeError(error)}`,
|
|
12235
|
+
category: "system",
|
|
12236
|
+
timestamp: ts,
|
|
12237
|
+
hook,
|
|
12238
|
+
sessionKey,
|
|
12239
|
+
model,
|
|
12240
|
+
durationMs,
|
|
12241
|
+
error: true,
|
|
12242
|
+
errorSummary: summarizeError(error)
|
|
12243
|
+
};
|
|
12244
|
+
}
|
|
12245
|
+
case "before_reset": {
|
|
12246
|
+
const reason = context.reason;
|
|
12247
|
+
const text = reason ? `${name} session was reset (${reason})` : `${name} session was reset`;
|
|
12248
|
+
return {
|
|
12249
|
+
agentName,
|
|
12250
|
+
text,
|
|
12251
|
+
category: "system",
|
|
12252
|
+
timestamp: ts,
|
|
12253
|
+
hook,
|
|
12254
|
+
sessionKey,
|
|
12255
|
+
model
|
|
12256
|
+
};
|
|
12096
12257
|
}
|
|
12097
12258
|
default:
|
|
12098
12259
|
return null;
|
|
@@ -12101,6 +12262,13 @@ function buildActivitySentence(agentName, event, context) {
|
|
|
12101
12262
|
var AgentStateTracker = class {
|
|
12102
12263
|
agents = /* @__PURE__ */ new Map();
|
|
12103
12264
|
activityBuffer = [];
|
|
12265
|
+
sessionKeyToAgent = /* @__PURE__ */ new Map();
|
|
12266
|
+
setSessionAgent(sessionKey, agentName) {
|
|
12267
|
+
this.sessionKeyToAgent.set(sessionKey, agentName);
|
|
12268
|
+
}
|
|
12269
|
+
getSessionAgent(sessionKey) {
|
|
12270
|
+
return this.sessionKeyToAgent.get(sessionKey) ?? null;
|
|
12271
|
+
}
|
|
12104
12272
|
getOrCreate(agentName) {
|
|
12105
12273
|
let state = this.agents.get(agentName);
|
|
12106
12274
|
if (!state) {
|
|
@@ -12142,7 +12310,29 @@ var AgentStateTracker = class {
|
|
|
12142
12310
|
state.sessions.delete(sessionKey);
|
|
12143
12311
|
}
|
|
12144
12312
|
}
|
|
12313
|
+
hasSession(agentName, sessionKey) {
|
|
12314
|
+
const state = this.agents.get(agentName);
|
|
12315
|
+
if (!state) return false;
|
|
12316
|
+
return state.sessions.has(sessionKey);
|
|
12317
|
+
}
|
|
12318
|
+
pruneStaleSessions(agentName, maxAgeMs) {
|
|
12319
|
+
const state = this.agents.get(agentName);
|
|
12320
|
+
if (!state) return [];
|
|
12321
|
+
const now = Date.now();
|
|
12322
|
+
const pruned = [];
|
|
12323
|
+
for (const [key, session] of state.sessions) {
|
|
12324
|
+
const lastMs = new Date(session.lastActivity).getTime();
|
|
12325
|
+
if (now - lastMs > maxAgeMs) {
|
|
12326
|
+
state.sessions.delete(key);
|
|
12327
|
+
pruned.push(key);
|
|
12328
|
+
}
|
|
12329
|
+
}
|
|
12330
|
+
return pruned;
|
|
12331
|
+
}
|
|
12145
12332
|
// --- Telemetry updates ---
|
|
12333
|
+
getStatus(agentName) {
|
|
12334
|
+
return this.agents.get(agentName)?.status ?? null;
|
|
12335
|
+
}
|
|
12146
12336
|
updateStatus(agentName, status) {
|
|
12147
12337
|
this.getOrCreate(agentName).status = status;
|
|
12148
12338
|
}
|
|
@@ -12243,21 +12433,72 @@ var AgentStateTracker = class {
|
|
|
12243
12433
|
clear() {
|
|
12244
12434
|
this.agents.clear();
|
|
12245
12435
|
this.activityBuffer = [];
|
|
12436
|
+
this.sessionKeyToAgent.clear();
|
|
12246
12437
|
}
|
|
12247
12438
|
exportState() {
|
|
12248
|
-
return
|
|
12439
|
+
return {
|
|
12440
|
+
agents: new Map(this.agents),
|
|
12441
|
+
sessionKeyToAgent: new Map(this.sessionKeyToAgent),
|
|
12442
|
+
activityBuffer: [...this.activityBuffer]
|
|
12443
|
+
};
|
|
12249
12444
|
}
|
|
12250
12445
|
importState(states) {
|
|
12251
|
-
|
|
12446
|
+
const agentMap = states instanceof Map ? states : states.agents;
|
|
12447
|
+
for (const [name, state] of agentMap) {
|
|
12252
12448
|
if (!(state.sessions instanceof Map)) {
|
|
12253
12449
|
state.sessions = new Map(Object.entries(state.sessions));
|
|
12254
12450
|
}
|
|
12255
12451
|
this.agents.set(name, state);
|
|
12256
12452
|
}
|
|
12453
|
+
if (!(states instanceof Map) && states.activityBuffer) {
|
|
12454
|
+
this.activityBuffer.push(...states.activityBuffer);
|
|
12455
|
+
}
|
|
12456
|
+
if (!(states instanceof Map) && states.sessionKeyToAgent) {
|
|
12457
|
+
for (const [key, agent] of states.sessionKeyToAgent) {
|
|
12458
|
+
this.sessionKeyToAgent.set(key, agent);
|
|
12459
|
+
}
|
|
12460
|
+
}
|
|
12257
12461
|
}
|
|
12258
12462
|
};
|
|
12259
12463
|
|
|
12260
12464
|
// src/hooks.ts
|
|
12465
|
+
var BUILD_ID = "B9-ACCOUNTID-20260311";
|
|
12466
|
+
var DIAG_LOG_PATH = "/tmp/cohort-sync-diag.log";
|
|
12467
|
+
function diag(label, data) {
|
|
12468
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
12469
|
+
const payload = data ? " " + JSON.stringify(data, (_k, v2) => {
|
|
12470
|
+
if (typeof v2 === "string" && v2.length > 200) return v2.slice(0, 200) + "\u2026";
|
|
12471
|
+
return v2;
|
|
12472
|
+
}) : "";
|
|
12473
|
+
const line = `[${ts}] ${label}${payload}
|
|
12474
|
+
`;
|
|
12475
|
+
try {
|
|
12476
|
+
fs.appendFileSync(DIAG_LOG_PATH, line);
|
|
12477
|
+
} catch {
|
|
12478
|
+
}
|
|
12479
|
+
}
|
|
12480
|
+
function dumpCtx(ctx) {
|
|
12481
|
+
if (!ctx || typeof ctx !== "object") return { _raw: String(ctx) };
|
|
12482
|
+
const out = {};
|
|
12483
|
+
for (const key of Object.keys(ctx)) {
|
|
12484
|
+
const val = ctx[key];
|
|
12485
|
+
if (typeof val === "function") {
|
|
12486
|
+
out[key] = "[Function]";
|
|
12487
|
+
} else if (typeof val === "object" && val !== null) {
|
|
12488
|
+
try {
|
|
12489
|
+
out[key] = JSON.parse(JSON.stringify(val));
|
|
12490
|
+
} catch {
|
|
12491
|
+
out[key] = "[Object]";
|
|
12492
|
+
}
|
|
12493
|
+
} else {
|
|
12494
|
+
out[key] = val;
|
|
12495
|
+
}
|
|
12496
|
+
}
|
|
12497
|
+
return out;
|
|
12498
|
+
}
|
|
12499
|
+
function dumpEvent(event) {
|
|
12500
|
+
return dumpCtx(event);
|
|
12501
|
+
}
|
|
12261
12502
|
var PLUGIN_VERSION = "unknown";
|
|
12262
12503
|
try {
|
|
12263
12504
|
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), "package.json");
|
|
@@ -12265,6 +12506,7 @@ try {
|
|
|
12265
12506
|
PLUGIN_VERSION = pkgJson.version ?? "unknown";
|
|
12266
12507
|
} catch {
|
|
12267
12508
|
}
|
|
12509
|
+
diag("MODULE_LOADED", { BUILD_ID, PLUGIN_VERSION });
|
|
12268
12510
|
function parseIdentityFile(workspaceDir) {
|
|
12269
12511
|
try {
|
|
12270
12512
|
const filePath = path.join(workspaceDir, "IDENTITY.md");
|
|
@@ -12297,24 +12539,189 @@ function resolveIdentity(configIdentity, workspaceDir) {
|
|
|
12297
12539
|
avatar: configIdentity?.avatar ?? fileIdentity?.avatar
|
|
12298
12540
|
};
|
|
12299
12541
|
}
|
|
12542
|
+
function getOrCreateTracker() {
|
|
12543
|
+
const state = getHotState();
|
|
12544
|
+
if (state.tracker instanceof AgentStateTracker) {
|
|
12545
|
+
return state.tracker;
|
|
12546
|
+
}
|
|
12547
|
+
const fresh = new AgentStateTracker();
|
|
12548
|
+
state.tracker = fresh;
|
|
12549
|
+
return fresh;
|
|
12550
|
+
}
|
|
12551
|
+
var STATE_FILE_PATH = path.join(
|
|
12552
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
12553
|
+
".session-state.json"
|
|
12554
|
+
);
|
|
12555
|
+
function saveSessionsToDisk(tracker) {
|
|
12556
|
+
try {
|
|
12557
|
+
const state = tracker.exportState();
|
|
12558
|
+
const data = {
|
|
12559
|
+
sessions: [],
|
|
12560
|
+
sessionKeyToAgent: Object.fromEntries(state.sessionKeyToAgent),
|
|
12561
|
+
channelAgents: { ...globalThis["__cohort_sync_channel_agent__"] ?? {} },
|
|
12562
|
+
savedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12563
|
+
};
|
|
12564
|
+
for (const [name, agent] of state.agents) {
|
|
12565
|
+
for (const key of agent.sessions.keys()) {
|
|
12566
|
+
data.sessions.push({ agentName: name, key });
|
|
12567
|
+
}
|
|
12568
|
+
}
|
|
12569
|
+
fs.writeFileSync(STATE_FILE_PATH, JSON.stringify(data));
|
|
12570
|
+
} catch {
|
|
12571
|
+
}
|
|
12572
|
+
}
|
|
12573
|
+
function loadSessionsFromDisk(tracker, logger) {
|
|
12574
|
+
try {
|
|
12575
|
+
if (!fs.existsSync(STATE_FILE_PATH)) return;
|
|
12576
|
+
const data = JSON.parse(fs.readFileSync(STATE_FILE_PATH, "utf8"));
|
|
12577
|
+
if (Date.now() - new Date(data.savedAt).getTime() > 864e5) {
|
|
12578
|
+
logger.info("cohort-sync: disk session state too old (>24h), skipping");
|
|
12579
|
+
return;
|
|
12580
|
+
}
|
|
12581
|
+
let count = 0;
|
|
12582
|
+
for (const { agentName, key } of data.sessions) {
|
|
12583
|
+
if (!tracker.hasSession(agentName, key)) {
|
|
12584
|
+
tracker.addSession(agentName, key);
|
|
12585
|
+
count++;
|
|
12586
|
+
}
|
|
12587
|
+
}
|
|
12588
|
+
for (const [key, agent] of Object.entries(data.sessionKeyToAgent)) {
|
|
12589
|
+
tracker.setSessionAgent(key, agent);
|
|
12590
|
+
}
|
|
12591
|
+
for (const [channelId, agent] of Object.entries(data.channelAgents ?? {})) {
|
|
12592
|
+
setChannelAgent(channelId, agent);
|
|
12593
|
+
}
|
|
12594
|
+
if (count > 0) {
|
|
12595
|
+
logger.info(`cohort-sync: restored ${count} sessions from disk`);
|
|
12596
|
+
}
|
|
12597
|
+
} catch {
|
|
12598
|
+
}
|
|
12599
|
+
}
|
|
12300
12600
|
function registerHooks(api, cfg) {
|
|
12301
12601
|
const { logger, config } = api;
|
|
12302
12602
|
const nameMap = cfg.agentNameMap;
|
|
12303
|
-
const tracker =
|
|
12603
|
+
const tracker = getOrCreateTracker();
|
|
12304
12604
|
let heartbeatInterval = null;
|
|
12305
12605
|
let activityFlushInterval = null;
|
|
12606
|
+
logger.info(`cohort-sync: registerHooks [${BUILD_ID}]`);
|
|
12607
|
+
diag("REGISTER_HOOKS", {
|
|
12608
|
+
BUILD_ID,
|
|
12609
|
+
PLUGIN_VERSION,
|
|
12610
|
+
hasNameMap: !!nameMap,
|
|
12611
|
+
nameMapKeys: nameMap ? Object.keys(nameMap) : [],
|
|
12612
|
+
nameMapValues: nameMap ? Object.values(nameMap) : [],
|
|
12613
|
+
agentCount: config?.agents?.list?.length ?? 0,
|
|
12614
|
+
agentIds: (config?.agents?.list ?? []).map((a) => a.id),
|
|
12615
|
+
agentMessageProviders: (config?.agents?.list ?? []).map((a) => ({ id: a.id, mp: a.messageProvider }))
|
|
12616
|
+
});
|
|
12306
12617
|
setConvexUrl(cfg);
|
|
12307
12618
|
setLogger(logger);
|
|
12308
12619
|
restoreFromHotReload(logger);
|
|
12309
12620
|
restoreRosterFromHotReload(getRosterHotState(), logger);
|
|
12310
|
-
|
|
12311
|
-
|
|
12312
|
-
tracker.
|
|
12313
|
-
|
|
12621
|
+
if (tracker.getAgentNames().length === 0) {
|
|
12622
|
+
loadSessionsFromDisk(tracker, logger);
|
|
12623
|
+
const restoredAgents = tracker.getAgentNames();
|
|
12624
|
+
for (const agentName of restoredAgents) {
|
|
12625
|
+
const sessSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12626
|
+
if (sessSnapshot.length > 0) {
|
|
12627
|
+
pushSessions(cfg.apiKey, agentName, sessSnapshot).then(() => {
|
|
12628
|
+
tracker.markSessionsPushed(agentName);
|
|
12629
|
+
logger.info(`cohort-sync: pushed ${sessSnapshot.length} restored sessions for ${agentName}`);
|
|
12630
|
+
}).catch((err) => {
|
|
12631
|
+
logger.warn(`cohort-sync: failed to push restored sessions for ${agentName}: ${String(err)}`);
|
|
12632
|
+
});
|
|
12633
|
+
}
|
|
12634
|
+
}
|
|
12314
12635
|
}
|
|
12315
12636
|
function resolveAgentName(agentId) {
|
|
12316
12637
|
return (nameMap?.[agentId] ?? agentId).toLowerCase();
|
|
12317
12638
|
}
|
|
12639
|
+
function resolveAgentFromContext(ctx) {
|
|
12640
|
+
const allCtxKeys = Object.keys(ctx);
|
|
12641
|
+
diag("RESOLVE_AGENT_FROM_CTX_START", {
|
|
12642
|
+
ctxKeys: allCtxKeys,
|
|
12643
|
+
agentId: ctx.agentId,
|
|
12644
|
+
sessionKey: ctx.sessionKey,
|
|
12645
|
+
sessionId: ctx.sessionId,
|
|
12646
|
+
channelId: ctx.channelId,
|
|
12647
|
+
accountId: ctx.accountId,
|
|
12648
|
+
conversationId: ctx.conversationId,
|
|
12649
|
+
messageProvider: ctx.messageProvider,
|
|
12650
|
+
workspaceDir: ctx.workspaceDir
|
|
12651
|
+
});
|
|
12652
|
+
if (ctx.agentId && typeof ctx.agentId === "string") {
|
|
12653
|
+
const resolved2 = resolveAgentName(ctx.agentId);
|
|
12654
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "agentId", raw: ctx.agentId, resolved: resolved2 });
|
|
12655
|
+
return resolved2;
|
|
12656
|
+
}
|
|
12657
|
+
const sessionKey = ctx.sessionKey ?? ctx.sessionId;
|
|
12658
|
+
if (sessionKey && typeof sessionKey === "string") {
|
|
12659
|
+
const mapped = tracker.getSessionAgent(sessionKey);
|
|
12660
|
+
if (mapped) {
|
|
12661
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "sessionKey_mapped", sessionKey, mapped });
|
|
12662
|
+
return mapped;
|
|
12663
|
+
}
|
|
12664
|
+
const parts = sessionKey.split(":");
|
|
12665
|
+
if (parts[0] === "agent" && parts.length >= 2) {
|
|
12666
|
+
const resolved2 = resolveAgentName(parts[1]);
|
|
12667
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "sessionKey_parsed", sessionKey, agentPart: parts[1], resolved: resolved2 });
|
|
12668
|
+
return resolved2;
|
|
12669
|
+
}
|
|
12670
|
+
}
|
|
12671
|
+
const accountId = ctx.accountId;
|
|
12672
|
+
if (accountId && typeof accountId === "string") {
|
|
12673
|
+
const resolved2 = resolveAgentName(accountId);
|
|
12674
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "accountId", accountId, resolved: resolved2 });
|
|
12675
|
+
return resolved2;
|
|
12676
|
+
}
|
|
12677
|
+
const channelId = ctx.channelId;
|
|
12678
|
+
if (channelId && typeof channelId === "string") {
|
|
12679
|
+
const channelAgent = getChannelAgent(channelId);
|
|
12680
|
+
if (channelAgent) {
|
|
12681
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "channelId_bridge", channelId, channelAgent, bridgeState: { ...getHotState().channelAgentBridge } });
|
|
12682
|
+
return channelAgent;
|
|
12683
|
+
}
|
|
12684
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "channelId_raw", channelId, bridgeState: { ...getHotState().channelAgentBridge } });
|
|
12685
|
+
return String(channelId);
|
|
12686
|
+
}
|
|
12687
|
+
const resolved = resolveAgentName("main");
|
|
12688
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "fallback_main", resolved });
|
|
12689
|
+
return resolved;
|
|
12690
|
+
}
|
|
12691
|
+
function resolveChannelFromContext(ctx) {
|
|
12692
|
+
if (ctx.messageProvider && typeof ctx.messageProvider === "string") {
|
|
12693
|
+
return ctx.messageProvider;
|
|
12694
|
+
}
|
|
12695
|
+
if (ctx.channelId && typeof ctx.channelId === "string") {
|
|
12696
|
+
return ctx.channelId;
|
|
12697
|
+
}
|
|
12698
|
+
const sessionKey = ctx.sessionKey ?? ctx.sessionId;
|
|
12699
|
+
if (sessionKey && typeof sessionKey === "string") {
|
|
12700
|
+
const parsed = parseSessionKey(sessionKey);
|
|
12701
|
+
return parsed.channel;
|
|
12702
|
+
}
|
|
12703
|
+
return "unknown";
|
|
12704
|
+
}
|
|
12705
|
+
const pendingActivity = drainActivityFromHot();
|
|
12706
|
+
if (pendingActivity.length > 0) {
|
|
12707
|
+
pushActivity(cfg.apiKey, pendingActivity).catch((err) => {
|
|
12708
|
+
logger.warn(`cohort-sync: pre-reload activity flush failed: ${String(err)}`);
|
|
12709
|
+
});
|
|
12710
|
+
logger.info(`cohort-sync: flushed ${pendingActivity.length} pending activity entries before hot-reload`);
|
|
12711
|
+
}
|
|
12712
|
+
clearIntervalsFromHot();
|
|
12713
|
+
heartbeatInterval = setInterval(() => {
|
|
12714
|
+
pushHeartbeat().catch((err) => {
|
|
12715
|
+
logger.warn(`cohort-sync: heartbeat tick failed: ${String(err)}`);
|
|
12716
|
+
});
|
|
12717
|
+
}, 12e4);
|
|
12718
|
+
activityFlushInterval = setInterval(() => {
|
|
12719
|
+
flushActivityBuffer().catch((err) => {
|
|
12720
|
+
logger.warn(`cohort-sync: activity flush tick failed: ${String(err)}`);
|
|
12721
|
+
});
|
|
12722
|
+
}, 3e3);
|
|
12723
|
+
saveIntervalsToHot(heartbeatInterval, activityFlushInterval);
|
|
12724
|
+
logger.info("cohort-sync: intervals created (heartbeat=2m, activityFlush=3s)");
|
|
12318
12725
|
api.registerTool((toolCtx) => {
|
|
12319
12726
|
const agentId = toolCtx.agentId ?? "main";
|
|
12320
12727
|
const agentName = resolveAgentName(agentId);
|
|
@@ -12361,6 +12768,13 @@ function registerHooks(api, cfg) {
|
|
|
12361
12768
|
}
|
|
12362
12769
|
async function pushHeartbeat() {
|
|
12363
12770
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
12771
|
+
for (const agentId of allAgentIds) {
|
|
12772
|
+
const agentName = resolveAgentName(agentId);
|
|
12773
|
+
const pruned = tracker.pruneStaleSessions(agentName, 864e5);
|
|
12774
|
+
if (pruned.length > 0) {
|
|
12775
|
+
logger.info(`cohort-sync: pruned ${pruned.length} stale sessions for ${agentName}`);
|
|
12776
|
+
}
|
|
12777
|
+
}
|
|
12364
12778
|
for (const agentId of allAgentIds) {
|
|
12365
12779
|
const agentName = resolveAgentName(agentId);
|
|
12366
12780
|
try {
|
|
@@ -12374,10 +12788,23 @@ function registerHooks(api, cfg) {
|
|
|
12374
12788
|
logger.warn(`cohort-sync: heartbeat push failed for ${agentName}: ${String(err)}`);
|
|
12375
12789
|
}
|
|
12376
12790
|
}
|
|
12791
|
+
for (const agentId of allAgentIds) {
|
|
12792
|
+
const agentName = resolveAgentName(agentId);
|
|
12793
|
+
try {
|
|
12794
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
12795
|
+
const sessSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12796
|
+
await pushSessions(cfg.apiKey, agentName, sessSnapshot);
|
|
12797
|
+
tracker.markSessionsPushed(agentName);
|
|
12798
|
+
}
|
|
12799
|
+
} catch (err) {
|
|
12800
|
+
logger.warn(`cohort-sync: heartbeat session push failed for ${agentName}: ${String(err)}`);
|
|
12801
|
+
}
|
|
12802
|
+
}
|
|
12803
|
+
saveSessionsToDisk(tracker);
|
|
12377
12804
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
12378
12805
|
}
|
|
12379
12806
|
async function flushActivityBuffer() {
|
|
12380
|
-
const entries =
|
|
12807
|
+
const entries = drainActivityFromHot();
|
|
12381
12808
|
if (entries.length === 0) return;
|
|
12382
12809
|
try {
|
|
12383
12810
|
await pushActivity(cfg.apiKey, entries);
|
|
@@ -12386,6 +12813,7 @@ function registerHooks(api, cfg) {
|
|
|
12386
12813
|
}
|
|
12387
12814
|
}
|
|
12388
12815
|
api.on("gateway_start", async (event) => {
|
|
12816
|
+
diag("HOOK_gateway_start", { port: event.port, eventKeys: Object.keys(event) });
|
|
12389
12817
|
try {
|
|
12390
12818
|
checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
|
|
12391
12819
|
});
|
|
@@ -12401,6 +12829,15 @@ function registerHooks(api, cfg) {
|
|
|
12401
12829
|
} catch (err) {
|
|
12402
12830
|
logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
|
|
12403
12831
|
}
|
|
12832
|
+
for (const a of config?.agents?.list ?? []) {
|
|
12833
|
+
const agentName = resolveAgentName(a.id);
|
|
12834
|
+
const mp = a.messageProvider;
|
|
12835
|
+
diag("GATEWAY_START_SEED_BRIDGE", { agentId: a.id, agentName, messageProvider: mp, accountId: a.accountId });
|
|
12836
|
+
if (mp && typeof mp === "string") {
|
|
12837
|
+
setChannelAgent(mp, agentName);
|
|
12838
|
+
}
|
|
12839
|
+
}
|
|
12840
|
+
diag("GATEWAY_START_BRIDGE_AFTER_SEED", { bridge: { ...getHotState().channelAgentBridge } });
|
|
12404
12841
|
await initSubscription(
|
|
12405
12842
|
event.port,
|
|
12406
12843
|
cfg,
|
|
@@ -12425,20 +12862,9 @@ function registerHooks(api, cfg) {
|
|
|
12425
12862
|
}
|
|
12426
12863
|
}
|
|
12427
12864
|
logger.info(`cohort-sync: seeded telemetry for ${allAgentIds.length} agents`);
|
|
12428
|
-
heartbeatInterval = setInterval(() => {
|
|
12429
|
-
pushHeartbeat().catch((err) => {
|
|
12430
|
-
logger.warn(`cohort-sync: heartbeat tick failed: ${String(err)}`);
|
|
12431
|
-
});
|
|
12432
|
-
}, 12e4);
|
|
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)");
|
|
12440
12865
|
});
|
|
12441
|
-
api.on("agent_end", async (
|
|
12866
|
+
api.on("agent_end", async (event, ctx) => {
|
|
12867
|
+
diag("HOOK_agent_end", { ctx: dumpCtx(ctx), success: event.success, error: event.error, durationMs: event.durationMs });
|
|
12442
12868
|
const agentId = ctx.agentId ?? "main";
|
|
12443
12869
|
const agentName = resolveAgentName(agentId);
|
|
12444
12870
|
try {
|
|
@@ -12451,11 +12877,21 @@ function registerHooks(api, cfg) {
|
|
|
12451
12877
|
tracker.markTelemetryPushed(agentName);
|
|
12452
12878
|
}
|
|
12453
12879
|
}
|
|
12880
|
+
if (event.success === false) {
|
|
12881
|
+
const entry = buildActivityEntry(agentName, "agent_end", {
|
|
12882
|
+
success: false,
|
|
12883
|
+
error: event.error,
|
|
12884
|
+
durationMs: event.durationMs,
|
|
12885
|
+
sessionKey: ctx.sessionKey
|
|
12886
|
+
});
|
|
12887
|
+
if (entry) addActivityToHot(entry);
|
|
12888
|
+
}
|
|
12454
12889
|
} catch (err) {
|
|
12455
12890
|
logger.warn(`cohort-sync: agent_end sync failed: ${String(err)}`);
|
|
12456
12891
|
}
|
|
12457
12892
|
});
|
|
12458
12893
|
api.on("llm_output", async (event, ctx) => {
|
|
12894
|
+
diag("HOOK_llm_output", { ctx: dumpCtx(ctx), model: event.model, tokensIn: event.usage?.input, tokensOut: event.usage?.output });
|
|
12459
12895
|
const agentId = ctx.agentId ?? "main";
|
|
12460
12896
|
const agentName = resolveAgentName(agentId);
|
|
12461
12897
|
try {
|
|
@@ -12469,6 +12905,13 @@ function registerHooks(api, cfg) {
|
|
|
12469
12905
|
contextTokens: ext.contextTokens ?? usage.total ?? 0,
|
|
12470
12906
|
contextLimit: ext.contextLimit ?? 0
|
|
12471
12907
|
});
|
|
12908
|
+
if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
|
|
12909
|
+
tracker.addSession(agentName, sessionKey);
|
|
12910
|
+
logger.info(`cohort-sync: inferred session for ${agentName} from llm_output (${sessionKey})`);
|
|
12911
|
+
}
|
|
12912
|
+
if (sessionKey) {
|
|
12913
|
+
tracker.setSessionAgent(sessionKey, agentName);
|
|
12914
|
+
}
|
|
12472
12915
|
if (tracker.shouldPushTelemetry(agentName)) {
|
|
12473
12916
|
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
12474
12917
|
if (snapshot) {
|
|
@@ -12486,6 +12929,7 @@ function registerHooks(api, cfg) {
|
|
|
12486
12929
|
}
|
|
12487
12930
|
});
|
|
12488
12931
|
api.on("after_compaction", async (event, ctx) => {
|
|
12932
|
+
diag("HOOK_after_compaction", { ctx: dumpCtx(ctx), messageCount: event.messageCount, tokenCount: event.tokenCount });
|
|
12489
12933
|
const agentId = ctx.agentId ?? "main";
|
|
12490
12934
|
const agentName = resolveAgentName(agentId);
|
|
12491
12935
|
try {
|
|
@@ -12501,18 +12945,21 @@ function registerHooks(api, cfg) {
|
|
|
12501
12945
|
tracker.markTelemetryPushed(agentName);
|
|
12502
12946
|
}
|
|
12503
12947
|
}
|
|
12504
|
-
const entry =
|
|
12505
|
-
|
|
12506
|
-
|
|
12948
|
+
const entry = buildActivityEntry(agentName, "after_compaction", {
|
|
12949
|
+
messageCount: event.messageCount,
|
|
12950
|
+
compactedCount: ext.compactedCount ?? event.compactedCount,
|
|
12951
|
+
sessionKey: ctx.sessionKey
|
|
12507
12952
|
});
|
|
12508
|
-
if (entry)
|
|
12953
|
+
if (entry) addActivityToHot(entry);
|
|
12509
12954
|
} catch (err) {
|
|
12510
12955
|
logger.warn(`cohort-sync: after_compaction telemetry failed: ${String(err)}`);
|
|
12511
12956
|
}
|
|
12512
12957
|
});
|
|
12513
12958
|
api.on("before_agent_start", async (_event, ctx) => {
|
|
12959
|
+
diag("HOOK_before_agent_start", { ctx: dumpCtx(ctx), event: dumpEvent(_event) });
|
|
12514
12960
|
const agentId = ctx.agentId ?? "main";
|
|
12515
12961
|
const agentName = resolveAgentName(agentId);
|
|
12962
|
+
diag("HOOK_before_agent_start_RESOLVED", { agentId, agentName, ctxChannelId: ctx.channelId, ctxMessageProvider: ctx.messageProvider, ctxSessionKey: ctx.sessionKey, ctxAccountId: ctx.accountId });
|
|
12516
12963
|
try {
|
|
12517
12964
|
tracker.updateStatus(agentName, "working");
|
|
12518
12965
|
if (tracker.shouldPushTelemetry(agentName)) {
|
|
@@ -12522,19 +12969,37 @@ function registerHooks(api, cfg) {
|
|
|
12522
12969
|
tracker.markTelemetryPushed(agentName);
|
|
12523
12970
|
}
|
|
12524
12971
|
}
|
|
12525
|
-
const
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12972
|
+
const sessionKey = ctx.sessionKey;
|
|
12973
|
+
if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
|
|
12974
|
+
tracker.addSession(agentName, sessionKey);
|
|
12975
|
+
tracker.setSessionAgent(sessionKey, agentName);
|
|
12976
|
+
logger.info(`cohort-sync: inferred session for ${agentName} (${sessionKey})`);
|
|
12977
|
+
if (tracker.shouldPushSessions(agentName)) {
|
|
12978
|
+
const sessSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12979
|
+
await pushSessions(cfg.apiKey, agentName, sessSnapshot);
|
|
12980
|
+
tracker.markSessionsPushed(agentName);
|
|
12981
|
+
}
|
|
12982
|
+
} else if (sessionKey) {
|
|
12983
|
+
tracker.setSessionAgent(sessionKey, agentName);
|
|
12984
|
+
}
|
|
12985
|
+
const ctxChannelId = ctx.channelId;
|
|
12986
|
+
if (ctxChannelId) {
|
|
12987
|
+
setChannelAgent(ctxChannelId, agentName);
|
|
12988
|
+
}
|
|
12989
|
+
const mp = ctx.messageProvider;
|
|
12990
|
+
if (mp) {
|
|
12991
|
+
setChannelAgent(mp, agentName);
|
|
12992
|
+
}
|
|
12529
12993
|
} catch (err) {
|
|
12530
12994
|
logger.warn(`cohort-sync: before_agent_start telemetry failed: ${String(err)}`);
|
|
12531
12995
|
}
|
|
12532
12996
|
});
|
|
12533
12997
|
api.on("session_start", async (event, ctx) => {
|
|
12998
|
+
diag("HOOK_session_start", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
12534
12999
|
const agentId = ctx.agentId ?? "main";
|
|
12535
13000
|
const agentName = resolveAgentName(agentId);
|
|
12536
13001
|
try {
|
|
12537
|
-
const sessionKey = ctx.
|
|
13002
|
+
const sessionKey = ctx.sessionId ?? String(Date.now());
|
|
12538
13003
|
tracker.addSession(agentName, sessionKey);
|
|
12539
13004
|
if (tracker.shouldPushSessions(agentName)) {
|
|
12540
13005
|
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
@@ -12542,77 +13007,139 @@ function registerHooks(api, cfg) {
|
|
|
12542
13007
|
tracker.markSessionsPushed(agentName);
|
|
12543
13008
|
}
|
|
12544
13009
|
const parsed = parseSessionKey(sessionKey);
|
|
12545
|
-
const entry =
|
|
12546
|
-
channel: parsed.channel
|
|
13010
|
+
const entry = buildActivityEntry(agentName, "session_start", {
|
|
13011
|
+
channel: parsed.channel,
|
|
13012
|
+
sessionKey,
|
|
13013
|
+
resumedFrom: event.resumedFrom
|
|
12547
13014
|
});
|
|
12548
|
-
if (entry)
|
|
13015
|
+
if (entry) addActivityToHot(entry);
|
|
12549
13016
|
} catch (err) {
|
|
12550
13017
|
logger.warn(`cohort-sync: session_start tracking failed: ${String(err)}`);
|
|
12551
13018
|
}
|
|
12552
13019
|
});
|
|
12553
13020
|
api.on("session_end", async (event, ctx) => {
|
|
13021
|
+
diag("HOOK_session_end", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
12554
13022
|
const agentId = ctx.agentId ?? "main";
|
|
12555
13023
|
const agentName = resolveAgentName(agentId);
|
|
12556
13024
|
try {
|
|
12557
|
-
const sessionKey = ctx.
|
|
13025
|
+
const sessionKey = ctx.sessionId ?? "";
|
|
12558
13026
|
tracker.removeSession(agentName, sessionKey);
|
|
12559
13027
|
if (tracker.shouldPushSessions(agentName)) {
|
|
12560
13028
|
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12561
13029
|
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
12562
13030
|
tracker.markSessionsPushed(agentName);
|
|
12563
13031
|
}
|
|
12564
|
-
const entry =
|
|
12565
|
-
|
|
13032
|
+
const entry = buildActivityEntry(agentName, "session_end", {
|
|
13033
|
+
sessionKey,
|
|
13034
|
+
messageCount: event.messageCount,
|
|
13035
|
+
durationMs: event.durationMs
|
|
13036
|
+
});
|
|
13037
|
+
if (entry) addActivityToHot(entry);
|
|
12566
13038
|
} catch (err) {
|
|
12567
13039
|
logger.warn(`cohort-sync: session_end tracking failed: ${String(err)}`);
|
|
12568
13040
|
}
|
|
12569
13041
|
});
|
|
12570
|
-
api.on("
|
|
12571
|
-
|
|
12572
|
-
const agentName =
|
|
13042
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
13043
|
+
diag("HOOK_after_tool_call", { ctx: dumpCtx(ctx), toolName: event.toolName, error: event.error });
|
|
13044
|
+
const agentName = resolveAgentFromContext(ctx);
|
|
12573
13045
|
try {
|
|
12574
|
-
const entry =
|
|
12575
|
-
toolName: event.toolName
|
|
12576
|
-
params: event.params
|
|
13046
|
+
const entry = buildActivityEntry(agentName, "after_tool_call", {
|
|
13047
|
+
toolName: event.toolName,
|
|
13048
|
+
params: event.params,
|
|
13049
|
+
error: event.error,
|
|
13050
|
+
durationMs: event.durationMs,
|
|
13051
|
+
sessionKey: ctx.sessionKey,
|
|
13052
|
+
model: resolveModel(ctx.agentId ?? "main")
|
|
12577
13053
|
});
|
|
12578
|
-
if (entry)
|
|
13054
|
+
if (entry) addActivityToHot(entry);
|
|
12579
13055
|
} catch (err) {
|
|
12580
|
-
logger.warn(`cohort-sync:
|
|
13056
|
+
logger.warn(`cohort-sync: after_tool_call activity failed: ${String(err)}`);
|
|
12581
13057
|
}
|
|
12582
13058
|
});
|
|
12583
13059
|
api.on("message_received", async (_event, ctx) => {
|
|
13060
|
+
diag("HOOK_message_received_RAW", {
|
|
13061
|
+
ctx: dumpCtx(ctx),
|
|
13062
|
+
event: dumpEvent(_event),
|
|
13063
|
+
bridgeStateBefore: { ...getHotState().channelAgentBridge }
|
|
13064
|
+
});
|
|
13065
|
+
const agentName = resolveAgentFromContext(ctx);
|
|
13066
|
+
const channel = ctx.channelId;
|
|
13067
|
+
diag("HOOK_message_received_RESOLVED", {
|
|
13068
|
+
agentName,
|
|
13069
|
+
channel,
|
|
13070
|
+
accountId: ctx.accountId,
|
|
13071
|
+
conversationId: ctx.conversationId,
|
|
13072
|
+
from: _event.from
|
|
13073
|
+
});
|
|
13074
|
+
try {
|
|
13075
|
+
const entry = buildActivityEntry(agentName, "message_received", {
|
|
13076
|
+
channel: channel ?? "unknown"
|
|
13077
|
+
});
|
|
13078
|
+
if (entry) addActivityToHot(entry);
|
|
13079
|
+
} catch (err) {
|
|
13080
|
+
logger.warn(`cohort-sync: message_received activity failed: ${String(err)}`);
|
|
13081
|
+
}
|
|
13082
|
+
});
|
|
13083
|
+
api.on("message_sent", async (event, ctx) => {
|
|
13084
|
+
diag("HOOK_message_sent_RAW", {
|
|
13085
|
+
ctx: dumpCtx(ctx),
|
|
13086
|
+
event: dumpEvent(event),
|
|
13087
|
+
bridgeStateBefore: { ...getHotState().channelAgentBridge }
|
|
13088
|
+
});
|
|
13089
|
+
const agentName = resolveAgentFromContext(ctx);
|
|
13090
|
+
const channel = ctx.channelId;
|
|
13091
|
+
diag("HOOK_message_sent_RESOLVED", {
|
|
13092
|
+
agentName,
|
|
13093
|
+
channel,
|
|
13094
|
+
accountId: ctx.accountId,
|
|
13095
|
+
conversationId: ctx.conversationId,
|
|
13096
|
+
to: event.to,
|
|
13097
|
+
success: event.success,
|
|
13098
|
+
error: event.error
|
|
13099
|
+
});
|
|
13100
|
+
try {
|
|
13101
|
+
const entry = buildActivityEntry(agentName, "message_sent", {
|
|
13102
|
+
channel: channel ?? "unknown",
|
|
13103
|
+
success: event.success,
|
|
13104
|
+
error: event.error
|
|
13105
|
+
});
|
|
13106
|
+
if (entry) addActivityToHot(entry);
|
|
13107
|
+
} catch (err) {
|
|
13108
|
+
logger.warn(`cohort-sync: message_sent activity failed: ${String(err)}`);
|
|
13109
|
+
}
|
|
13110
|
+
});
|
|
13111
|
+
api.on("before_compaction", async (_event, ctx) => {
|
|
13112
|
+
diag("HOOK_before_compaction", { ctx: dumpCtx(ctx) });
|
|
12584
13113
|
const agentId = ctx.agentId ?? "main";
|
|
12585
13114
|
const agentName = resolveAgentName(agentId);
|
|
12586
13115
|
try {
|
|
12587
|
-
const entry =
|
|
12588
|
-
|
|
13116
|
+
const entry = buildActivityEntry(agentName, "before_compaction", {
|
|
13117
|
+
sessionKey: ctx.sessionKey
|
|
12589
13118
|
});
|
|
12590
|
-
if (entry)
|
|
13119
|
+
if (entry) addActivityToHot(entry);
|
|
12591
13120
|
} catch (err) {
|
|
12592
|
-
logger.warn(`cohort-sync:
|
|
13121
|
+
logger.warn(`cohort-sync: before_compaction activity failed: ${String(err)}`);
|
|
12593
13122
|
}
|
|
12594
13123
|
});
|
|
12595
|
-
api.on("
|
|
13124
|
+
api.on("before_reset", async (event, ctx) => {
|
|
13125
|
+
diag("HOOK_before_reset", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
12596
13126
|
const agentId = ctx.agentId ?? "main";
|
|
12597
13127
|
const agentName = resolveAgentName(agentId);
|
|
12598
13128
|
try {
|
|
12599
|
-
const entry =
|
|
12600
|
-
|
|
13129
|
+
const entry = buildActivityEntry(agentName, "before_reset", {
|
|
13130
|
+
reason: event.reason,
|
|
13131
|
+
sessionKey: ctx.sessionKey
|
|
12601
13132
|
});
|
|
12602
|
-
if (entry)
|
|
13133
|
+
if (entry) addActivityToHot(entry);
|
|
12603
13134
|
} catch (err) {
|
|
12604
|
-
logger.warn(`cohort-sync:
|
|
13135
|
+
logger.warn(`cohort-sync: before_reset activity failed: ${String(err)}`);
|
|
12605
13136
|
}
|
|
12606
13137
|
});
|
|
12607
13138
|
api.on("gateway_stop", async () => {
|
|
12608
|
-
|
|
12609
|
-
|
|
12610
|
-
|
|
12611
|
-
|
|
12612
|
-
if (activityFlushInterval) {
|
|
12613
|
-
clearInterval(activityFlushInterval);
|
|
12614
|
-
activityFlushInterval = null;
|
|
12615
|
-
}
|
|
13139
|
+
diag("HOOK_gateway_stop", { bridgeState: { ...getHotState().channelAgentBridge } });
|
|
13140
|
+
clearIntervalsFromHot();
|
|
13141
|
+
heartbeatInterval = null;
|
|
13142
|
+
activityFlushInterval = null;
|
|
12616
13143
|
await flushActivityBuffer().catch((err) => {
|
|
12617
13144
|
logger.warn(`cohort-sync: final activity flush failed: ${String(err)}`);
|
|
12618
13145
|
});
|
|
@@ -12629,7 +13156,7 @@ function registerHooks(api, cfg) {
|
|
|
12629
13156
|
logger.warn(`cohort-sync: final unreachable push failed for ${agentName}: ${String(err)}`);
|
|
12630
13157
|
}
|
|
12631
13158
|
}
|
|
12632
|
-
|
|
13159
|
+
saveSessionsToDisk(tracker);
|
|
12633
13160
|
try {
|
|
12634
13161
|
await markAllUnreachable(cfg, logger);
|
|
12635
13162
|
} catch (err) {
|
package/dist/package.json
CHANGED