@cfio/cohort-sync 0.2.1 → 0.2.3
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 +594 -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,27 +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
|
-
hot.agentStates = states;
|
|
11746
|
-
}
|
|
11755
|
+
function saveIntervalsToHot(heartbeat, activityFlush) {
|
|
11756
|
+
const state = getHotState();
|
|
11757
|
+
state.intervals = { heartbeat, activityFlush };
|
|
11747
11758
|
}
|
|
11748
|
-
function
|
|
11749
|
-
|
|
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 };
|
|
11750
11764
|
}
|
|
11751
|
-
function
|
|
11752
|
-
const
|
|
11753
|
-
|
|
11754
|
-
|
|
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}"`);
|
|
11755
11771
|
}
|
|
11756
11772
|
}
|
|
11757
|
-
function
|
|
11758
|
-
const
|
|
11759
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
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;
|
|
11763
11784
|
}
|
|
11764
11785
|
|
|
11765
11786
|
// src/sync.ts
|
|
@@ -11990,6 +12011,9 @@ async function fullSync(agentName, model, cfg, logger, openClawAgents) {
|
|
|
11990
12011
|
logger.info("cohort-sync: full sync complete");
|
|
11991
12012
|
}
|
|
11992
12013
|
|
|
12014
|
+
// src/agent-state.ts
|
|
12015
|
+
import { basename } from "node:path";
|
|
12016
|
+
|
|
11993
12017
|
// src/session-key.ts
|
|
11994
12018
|
function parseSessionKey(key) {
|
|
11995
12019
|
if (!key || !key.includes(":")) {
|
|
@@ -12056,57 +12080,180 @@ function shouldPushSessions(current, lastPushed) {
|
|
|
12056
12080
|
}
|
|
12057
12081
|
return false;
|
|
12058
12082
|
}
|
|
12059
|
-
function
|
|
12060
|
-
const
|
|
12061
|
-
|
|
12083
|
+
function summarizeError(error) {
|
|
12084
|
+
const str = String(error ?? "");
|
|
12085
|
+
const firstLine = str.split("\n")[0] ?? str;
|
|
12086
|
+
return firstLine.slice(0, 120);
|
|
12062
12087
|
}
|
|
12063
|
-
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) {
|
|
12064
12095
|
const ts = Date.now();
|
|
12065
12096
|
const name = agentName.charAt(0).toUpperCase() + agentName.slice(1);
|
|
12066
|
-
|
|
12067
|
-
|
|
12097
|
+
const sessionKey = context.sessionKey;
|
|
12098
|
+
const model = context.model;
|
|
12099
|
+
const channel = context.channel;
|
|
12100
|
+
switch (hook) {
|
|
12101
|
+
case "after_tool_call": {
|
|
12068
12102
|
const toolName = String(context.toolName ?? "a tool");
|
|
12069
12103
|
const params = context.params;
|
|
12070
|
-
|
|
12071
|
-
|
|
12072
|
-
|
|
12073
|
-
|
|
12104
|
+
const error = context.error;
|
|
12105
|
+
const durationMs = context.durationMs;
|
|
12106
|
+
const hasError = error != null && error !== "";
|
|
12107
|
+
let text;
|
|
12074
12108
|
if (toolName === "Read") {
|
|
12075
12109
|
const file = params?.file_path ? basename(String(params.file_path)) : "a file";
|
|
12076
|
-
|
|
12077
|
-
}
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
}
|
|
12081
|
-
|
|
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") {
|
|
12082
12117
|
const taskNum = params?.task_number ?? "?";
|
|
12083
|
-
|
|
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}`;
|
|
12084
12121
|
}
|
|
12085
|
-
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
|
+
};
|
|
12086
12135
|
}
|
|
12087
12136
|
case "message_received": {
|
|
12088
|
-
const ch = String(
|
|
12089
|
-
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
|
+
};
|
|
12090
12148
|
}
|
|
12091
12149
|
case "message_sent": {
|
|
12092
|
-
const ch = String(
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
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
|
+
};
|
|
12099
12166
|
}
|
|
12100
12167
|
case "session_start": {
|
|
12101
|
-
const ch = String(
|
|
12102
|
-
|
|
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
|
+
};
|
|
12103
12181
|
}
|
|
12104
12182
|
case "session_end": {
|
|
12105
|
-
|
|
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
|
+
};
|
|
12106
12199
|
}
|
|
12107
|
-
case "
|
|
12108
|
-
|
|
12109
|
-
|
|
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
|
+
};
|
|
12210
|
+
}
|
|
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
|
+
};
|
|
12110
12257
|
}
|
|
12111
12258
|
default:
|
|
12112
12259
|
return null;
|
|
@@ -12183,6 +12330,9 @@ var AgentStateTracker = class {
|
|
|
12183
12330
|
return pruned;
|
|
12184
12331
|
}
|
|
12185
12332
|
// --- Telemetry updates ---
|
|
12333
|
+
getStatus(agentName) {
|
|
12334
|
+
return this.agents.get(agentName)?.status ?? null;
|
|
12335
|
+
}
|
|
12186
12336
|
updateStatus(agentName, status) {
|
|
12187
12337
|
this.getOrCreate(agentName).status = status;
|
|
12188
12338
|
}
|
|
@@ -12288,7 +12438,8 @@ var AgentStateTracker = class {
|
|
|
12288
12438
|
exportState() {
|
|
12289
12439
|
return {
|
|
12290
12440
|
agents: new Map(this.agents),
|
|
12291
|
-
sessionKeyToAgent: new Map(this.sessionKeyToAgent)
|
|
12441
|
+
sessionKeyToAgent: new Map(this.sessionKeyToAgent),
|
|
12442
|
+
activityBuffer: [...this.activityBuffer]
|
|
12292
12443
|
};
|
|
12293
12444
|
}
|
|
12294
12445
|
importState(states) {
|
|
@@ -12299,6 +12450,9 @@ var AgentStateTracker = class {
|
|
|
12299
12450
|
}
|
|
12300
12451
|
this.agents.set(name, state);
|
|
12301
12452
|
}
|
|
12453
|
+
if (!(states instanceof Map) && states.activityBuffer) {
|
|
12454
|
+
this.activityBuffer.push(...states.activityBuffer);
|
|
12455
|
+
}
|
|
12302
12456
|
if (!(states instanceof Map) && states.sessionKeyToAgent) {
|
|
12303
12457
|
for (const [key, agent] of states.sessionKeyToAgent) {
|
|
12304
12458
|
this.sessionKeyToAgent.set(key, agent);
|
|
@@ -12308,6 +12462,43 @@ var AgentStateTracker = class {
|
|
|
12308
12462
|
};
|
|
12309
12463
|
|
|
12310
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
|
+
}
|
|
12311
12502
|
var PLUGIN_VERSION = "unknown";
|
|
12312
12503
|
try {
|
|
12313
12504
|
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), "package.json");
|
|
@@ -12315,6 +12506,7 @@ try {
|
|
|
12315
12506
|
PLUGIN_VERSION = pkgJson.version ?? "unknown";
|
|
12316
12507
|
} catch {
|
|
12317
12508
|
}
|
|
12509
|
+
diag("MODULE_LOADED", { BUILD_ID, PLUGIN_VERSION });
|
|
12318
12510
|
function parseIdentityFile(workspaceDir) {
|
|
12319
12511
|
try {
|
|
12320
12512
|
const filePath = path.join(workspaceDir, "IDENTITY.md");
|
|
@@ -12347,34 +12539,175 @@ function resolveIdentity(configIdentity, workspaceDir) {
|
|
|
12347
12539
|
avatar: configIdentity?.avatar ?? fileIdentity?.avatar
|
|
12348
12540
|
};
|
|
12349
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
|
+
}
|
|
12350
12600
|
function registerHooks(api, cfg) {
|
|
12351
12601
|
const { logger, config } = api;
|
|
12352
12602
|
const nameMap = cfg.agentNameMap;
|
|
12353
|
-
const tracker =
|
|
12603
|
+
const tracker = getOrCreateTracker();
|
|
12354
12604
|
let heartbeatInterval = null;
|
|
12355
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
|
+
});
|
|
12356
12617
|
setConvexUrl(cfg);
|
|
12357
12618
|
setLogger(logger);
|
|
12358
12619
|
restoreFromHotReload(logger);
|
|
12359
12620
|
restoreRosterFromHotReload(getRosterHotState(), logger);
|
|
12360
|
-
|
|
12361
|
-
|
|
12362
|
-
tracker.
|
|
12363
|
-
|
|
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
|
+
}
|
|
12364
12635
|
}
|
|
12365
12636
|
function resolveAgentName(agentId) {
|
|
12366
12637
|
return (nameMap?.[agentId] ?? agentId).toLowerCase();
|
|
12367
12638
|
}
|
|
12368
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
|
+
});
|
|
12369
12652
|
if (ctx.agentId && typeof ctx.agentId === "string") {
|
|
12370
|
-
|
|
12653
|
+
const resolved2 = resolveAgentName(ctx.agentId);
|
|
12654
|
+
diag("RESOLVE_AGENT_FROM_CTX_RESULT", { method: "agentId", raw: ctx.agentId, resolved: resolved2 });
|
|
12655
|
+
return resolved2;
|
|
12371
12656
|
}
|
|
12372
12657
|
const sessionKey = ctx.sessionKey ?? ctx.sessionId;
|
|
12373
12658
|
if (sessionKey && typeof sessionKey === "string") {
|
|
12374
12659
|
const mapped = tracker.getSessionAgent(sessionKey);
|
|
12375
|
-
if (mapped)
|
|
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;
|
|
12376
12697
|
}
|
|
12377
|
-
|
|
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`);
|
|
12378
12711
|
}
|
|
12379
12712
|
clearIntervalsFromHot();
|
|
12380
12713
|
heartbeatInterval = setInterval(() => {
|
|
@@ -12386,9 +12719,9 @@ function registerHooks(api, cfg) {
|
|
|
12386
12719
|
flushActivityBuffer().catch((err) => {
|
|
12387
12720
|
logger.warn(`cohort-sync: activity flush tick failed: ${String(err)}`);
|
|
12388
12721
|
});
|
|
12389
|
-
},
|
|
12722
|
+
}, 3e3);
|
|
12390
12723
|
saveIntervalsToHot(heartbeatInterval, activityFlushInterval);
|
|
12391
|
-
logger.info("cohort-sync: intervals created (heartbeat=2m, activityFlush=
|
|
12724
|
+
logger.info("cohort-sync: intervals created (heartbeat=2m, activityFlush=3s)");
|
|
12392
12725
|
api.registerTool((toolCtx) => {
|
|
12393
12726
|
const agentId = toolCtx.agentId ?? "main";
|
|
12394
12727
|
const agentName = resolveAgentName(agentId);
|
|
@@ -12433,11 +12766,23 @@ function registerHooks(api, cfg) {
|
|
|
12433
12766
|
if (m && typeof m === "object" && "primary" in m) return String(m.primary);
|
|
12434
12767
|
return "unknown";
|
|
12435
12768
|
}
|
|
12769
|
+
function getModelContextLimit(model) {
|
|
12770
|
+
const m = model.toLowerCase();
|
|
12771
|
+
if (m.includes("opus") || m.includes("sonnet") || m.includes("haiku")) return 2e5;
|
|
12772
|
+
if (m.includes("gpt-4o")) return 128e3;
|
|
12773
|
+
if (m.includes("gpt-4-turbo") || m.includes("gpt-4-1")) return 128e3;
|
|
12774
|
+
if (m.includes("gpt-4")) return 8192;
|
|
12775
|
+
if (m.includes("o3") || m.includes("o4-mini")) return 2e5;
|
|
12776
|
+
if (m.includes("gemini-2")) return 1e6;
|
|
12777
|
+
if (m.includes("gemini")) return 128e3;
|
|
12778
|
+
if (m.includes("deepseek")) return 128e3;
|
|
12779
|
+
return 2e5;
|
|
12780
|
+
}
|
|
12436
12781
|
async function pushHeartbeat() {
|
|
12437
12782
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
12438
12783
|
for (const agentId of allAgentIds) {
|
|
12439
12784
|
const agentName = resolveAgentName(agentId);
|
|
12440
|
-
const pruned = tracker.pruneStaleSessions(agentName,
|
|
12785
|
+
const pruned = tracker.pruneStaleSessions(agentName, 864e5);
|
|
12441
12786
|
if (pruned.length > 0) {
|
|
12442
12787
|
logger.info(`cohort-sync: pruned ${pruned.length} stale sessions for ${agentName}`);
|
|
12443
12788
|
}
|
|
@@ -12467,10 +12812,11 @@ function registerHooks(api, cfg) {
|
|
|
12467
12812
|
logger.warn(`cohort-sync: heartbeat session push failed for ${agentName}: ${String(err)}`);
|
|
12468
12813
|
}
|
|
12469
12814
|
}
|
|
12815
|
+
saveSessionsToDisk(tracker);
|
|
12470
12816
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
12471
12817
|
}
|
|
12472
12818
|
async function flushActivityBuffer() {
|
|
12473
|
-
const entries =
|
|
12819
|
+
const entries = drainActivityFromHot();
|
|
12474
12820
|
if (entries.length === 0) return;
|
|
12475
12821
|
try {
|
|
12476
12822
|
await pushActivity(cfg.apiKey, entries);
|
|
@@ -12479,6 +12825,7 @@ function registerHooks(api, cfg) {
|
|
|
12479
12825
|
}
|
|
12480
12826
|
}
|
|
12481
12827
|
api.on("gateway_start", async (event) => {
|
|
12828
|
+
diag("HOOK_gateway_start", { port: event.port, eventKeys: Object.keys(event) });
|
|
12482
12829
|
try {
|
|
12483
12830
|
checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
|
|
12484
12831
|
});
|
|
@@ -12494,6 +12841,15 @@ function registerHooks(api, cfg) {
|
|
|
12494
12841
|
} catch (err) {
|
|
12495
12842
|
logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
|
|
12496
12843
|
}
|
|
12844
|
+
for (const a of config?.agents?.list ?? []) {
|
|
12845
|
+
const agentName = resolveAgentName(a.id);
|
|
12846
|
+
const mp = a.messageProvider;
|
|
12847
|
+
diag("GATEWAY_START_SEED_BRIDGE", { agentId: a.id, agentName, messageProvider: mp, accountId: a.accountId });
|
|
12848
|
+
if (mp && typeof mp === "string") {
|
|
12849
|
+
setChannelAgent(mp, agentName);
|
|
12850
|
+
}
|
|
12851
|
+
}
|
|
12852
|
+
diag("GATEWAY_START_BRIDGE_AFTER_SEED", { bridge: { ...getHotState().channelAgentBridge } });
|
|
12497
12853
|
await initSubscription(
|
|
12498
12854
|
event.port,
|
|
12499
12855
|
cfg,
|
|
@@ -12519,7 +12875,8 @@ function registerHooks(api, cfg) {
|
|
|
12519
12875
|
}
|
|
12520
12876
|
logger.info(`cohort-sync: seeded telemetry for ${allAgentIds.length} agents`);
|
|
12521
12877
|
});
|
|
12522
|
-
api.on("agent_end", async (
|
|
12878
|
+
api.on("agent_end", async (event, ctx) => {
|
|
12879
|
+
diag("HOOK_agent_end", { ctx: dumpCtx(ctx), success: event.success, error: event.error, durationMs: event.durationMs });
|
|
12523
12880
|
const agentId = ctx.agentId ?? "main";
|
|
12524
12881
|
const agentName = resolveAgentName(agentId);
|
|
12525
12882
|
try {
|
|
@@ -12532,23 +12889,44 @@ function registerHooks(api, cfg) {
|
|
|
12532
12889
|
tracker.markTelemetryPushed(agentName);
|
|
12533
12890
|
}
|
|
12534
12891
|
}
|
|
12892
|
+
if (event.success === false) {
|
|
12893
|
+
const entry = buildActivityEntry(agentName, "agent_end", {
|
|
12894
|
+
success: false,
|
|
12895
|
+
error: event.error,
|
|
12896
|
+
durationMs: event.durationMs,
|
|
12897
|
+
sessionKey: ctx.sessionKey
|
|
12898
|
+
});
|
|
12899
|
+
if (entry) addActivityToHot(entry);
|
|
12900
|
+
}
|
|
12535
12901
|
} catch (err) {
|
|
12536
12902
|
logger.warn(`cohort-sync: agent_end sync failed: ${String(err)}`);
|
|
12537
12903
|
}
|
|
12538
12904
|
});
|
|
12539
12905
|
api.on("llm_output", async (event, ctx) => {
|
|
12906
|
+
const usage = event.usage ?? {};
|
|
12907
|
+
const contextTokens = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
12908
|
+
const model = event.model ?? resolveModel(ctx.agentId ?? "main");
|
|
12909
|
+
const contextLimit = getModelContextLimit(model);
|
|
12910
|
+
diag("HOOK_llm_output", {
|
|
12911
|
+
ctx: dumpCtx(ctx),
|
|
12912
|
+
model,
|
|
12913
|
+
tokensIn: usage.input,
|
|
12914
|
+
tokensOut: usage.output,
|
|
12915
|
+
cacheRead: usage.cacheRead,
|
|
12916
|
+
cacheWrite: usage.cacheWrite,
|
|
12917
|
+
contextTokens,
|
|
12918
|
+
contextLimit
|
|
12919
|
+
});
|
|
12540
12920
|
const agentId = ctx.agentId ?? "main";
|
|
12541
12921
|
const agentName = resolveAgentName(agentId);
|
|
12542
12922
|
try {
|
|
12543
|
-
const usage = event.usage ?? {};
|
|
12544
|
-
const ext = event;
|
|
12545
12923
|
const sessionKey = ctx.sessionKey;
|
|
12546
12924
|
tracker.updateFromLlmOutput(agentName, sessionKey, {
|
|
12547
|
-
model
|
|
12925
|
+
model,
|
|
12548
12926
|
tokensIn: usage.input ?? 0,
|
|
12549
12927
|
tokensOut: usage.output ?? 0,
|
|
12550
|
-
contextTokens
|
|
12551
|
-
contextLimit
|
|
12928
|
+
contextTokens,
|
|
12929
|
+
contextLimit
|
|
12552
12930
|
});
|
|
12553
12931
|
if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
|
|
12554
12932
|
tracker.addSession(agentName, sessionKey);
|
|
@@ -12574,13 +12952,13 @@ function registerHooks(api, cfg) {
|
|
|
12574
12952
|
}
|
|
12575
12953
|
});
|
|
12576
12954
|
api.on("after_compaction", async (event, ctx) => {
|
|
12955
|
+
diag("HOOK_after_compaction", { ctx: dumpCtx(ctx), messageCount: event.messageCount, tokenCount: event.tokenCount });
|
|
12577
12956
|
const agentId = ctx.agentId ?? "main";
|
|
12578
12957
|
const agentName = resolveAgentName(agentId);
|
|
12579
12958
|
try {
|
|
12580
|
-
const ext = event;
|
|
12581
12959
|
tracker.updateFromCompaction(agentName, {
|
|
12582
|
-
contextTokens:
|
|
12583
|
-
contextLimit:
|
|
12960
|
+
contextTokens: event.tokenCount ?? 0,
|
|
12961
|
+
contextLimit: getModelContextLimit(resolveModel(agentId))
|
|
12584
12962
|
});
|
|
12585
12963
|
if (tracker.shouldPushTelemetry(agentName)) {
|
|
12586
12964
|
const snapshot = tracker.getTelemetrySnapshot(agentName);
|
|
@@ -12589,18 +12967,21 @@ function registerHooks(api, cfg) {
|
|
|
12589
12967
|
tracker.markTelemetryPushed(agentName);
|
|
12590
12968
|
}
|
|
12591
12969
|
}
|
|
12592
|
-
const entry =
|
|
12593
|
-
|
|
12594
|
-
|
|
12970
|
+
const entry = buildActivityEntry(agentName, "after_compaction", {
|
|
12971
|
+
messageCount: event.messageCount,
|
|
12972
|
+
compactedCount: event.compactedCount,
|
|
12973
|
+
sessionKey: ctx.sessionKey
|
|
12595
12974
|
});
|
|
12596
|
-
if (entry)
|
|
12975
|
+
if (entry) addActivityToHot(entry);
|
|
12597
12976
|
} catch (err) {
|
|
12598
12977
|
logger.warn(`cohort-sync: after_compaction telemetry failed: ${String(err)}`);
|
|
12599
12978
|
}
|
|
12600
12979
|
});
|
|
12601
12980
|
api.on("before_agent_start", async (_event, ctx) => {
|
|
12981
|
+
diag("HOOK_before_agent_start", { ctx: dumpCtx(ctx), event: dumpEvent(_event) });
|
|
12602
12982
|
const agentId = ctx.agentId ?? "main";
|
|
12603
12983
|
const agentName = resolveAgentName(agentId);
|
|
12984
|
+
diag("HOOK_before_agent_start_RESOLVED", { agentId, agentName, ctxChannelId: ctx.channelId, ctxMessageProvider: ctx.messageProvider, ctxSessionKey: ctx.sessionKey, ctxAccountId: ctx.accountId });
|
|
12604
12985
|
try {
|
|
12605
12986
|
tracker.updateStatus(agentName, "working");
|
|
12606
12987
|
if (tracker.shouldPushTelemetry(agentName)) {
|
|
@@ -12620,20 +13001,27 @@ function registerHooks(api, cfg) {
|
|
|
12620
13001
|
await pushSessions(cfg.apiKey, agentName, sessSnapshot);
|
|
12621
13002
|
tracker.markSessionsPushed(agentName);
|
|
12622
13003
|
}
|
|
13004
|
+
} else if (sessionKey) {
|
|
13005
|
+
tracker.setSessionAgent(sessionKey, agentName);
|
|
13006
|
+
}
|
|
13007
|
+
const ctxChannelId = ctx.channelId;
|
|
13008
|
+
if (ctxChannelId) {
|
|
13009
|
+
setChannelAgent(ctxChannelId, agentName);
|
|
13010
|
+
}
|
|
13011
|
+
const mp = ctx.messageProvider;
|
|
13012
|
+
if (mp) {
|
|
13013
|
+
setChannelAgent(mp, agentName);
|
|
12623
13014
|
}
|
|
12624
|
-
const entry = buildActivitySentence(agentName, "before_agent_start", {
|
|
12625
|
-
channel: ctx.messageProvider ?? "unknown"
|
|
12626
|
-
});
|
|
12627
|
-
if (entry) tracker.addActivity(entry);
|
|
12628
13015
|
} catch (err) {
|
|
12629
13016
|
logger.warn(`cohort-sync: before_agent_start telemetry failed: ${String(err)}`);
|
|
12630
13017
|
}
|
|
12631
13018
|
});
|
|
12632
13019
|
api.on("session_start", async (event, ctx) => {
|
|
13020
|
+
diag("HOOK_session_start", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
12633
13021
|
const agentId = ctx.agentId ?? "main";
|
|
12634
13022
|
const agentName = resolveAgentName(agentId);
|
|
12635
13023
|
try {
|
|
12636
|
-
const sessionKey = ctx.
|
|
13024
|
+
const sessionKey = ctx.sessionId ?? String(Date.now());
|
|
12637
13025
|
tracker.addSession(agentName, sessionKey);
|
|
12638
13026
|
if (tracker.shouldPushSessions(agentName)) {
|
|
12639
13027
|
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
@@ -12641,66 +13029,136 @@ function registerHooks(api, cfg) {
|
|
|
12641
13029
|
tracker.markSessionsPushed(agentName);
|
|
12642
13030
|
}
|
|
12643
13031
|
const parsed = parseSessionKey(sessionKey);
|
|
12644
|
-
const entry =
|
|
12645
|
-
channel: parsed.channel
|
|
13032
|
+
const entry = buildActivityEntry(agentName, "session_start", {
|
|
13033
|
+
channel: parsed.channel,
|
|
13034
|
+
sessionKey,
|
|
13035
|
+
resumedFrom: event.resumedFrom
|
|
12646
13036
|
});
|
|
12647
|
-
if (entry)
|
|
13037
|
+
if (entry) addActivityToHot(entry);
|
|
12648
13038
|
} catch (err) {
|
|
12649
13039
|
logger.warn(`cohort-sync: session_start tracking failed: ${String(err)}`);
|
|
12650
13040
|
}
|
|
12651
13041
|
});
|
|
12652
13042
|
api.on("session_end", async (event, ctx) => {
|
|
13043
|
+
diag("HOOK_session_end", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
12653
13044
|
const agentId = ctx.agentId ?? "main";
|
|
12654
13045
|
const agentName = resolveAgentName(agentId);
|
|
12655
13046
|
try {
|
|
12656
|
-
const sessionKey = ctx.
|
|
13047
|
+
const sessionKey = ctx.sessionId ?? "";
|
|
12657
13048
|
tracker.removeSession(agentName, sessionKey);
|
|
12658
13049
|
if (tracker.shouldPushSessions(agentName)) {
|
|
12659
13050
|
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12660
13051
|
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
12661
13052
|
tracker.markSessionsPushed(agentName);
|
|
12662
13053
|
}
|
|
12663
|
-
const entry =
|
|
12664
|
-
|
|
13054
|
+
const entry = buildActivityEntry(agentName, "session_end", {
|
|
13055
|
+
sessionKey,
|
|
13056
|
+
messageCount: event.messageCount,
|
|
13057
|
+
durationMs: event.durationMs
|
|
13058
|
+
});
|
|
13059
|
+
if (entry) addActivityToHot(entry);
|
|
12665
13060
|
} catch (err) {
|
|
12666
13061
|
logger.warn(`cohort-sync: session_end tracking failed: ${String(err)}`);
|
|
12667
13062
|
}
|
|
12668
13063
|
});
|
|
12669
|
-
api.on("
|
|
13064
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
13065
|
+
diag("HOOK_after_tool_call", { ctx: dumpCtx(ctx), toolName: event.toolName, error: event.error });
|
|
12670
13066
|
const agentName = resolveAgentFromContext(ctx);
|
|
12671
13067
|
try {
|
|
12672
|
-
const entry =
|
|
12673
|
-
toolName: event.toolName
|
|
12674
|
-
params: event.params
|
|
13068
|
+
const entry = buildActivityEntry(agentName, "after_tool_call", {
|
|
13069
|
+
toolName: event.toolName,
|
|
13070
|
+
params: event.params,
|
|
13071
|
+
error: event.error,
|
|
13072
|
+
durationMs: event.durationMs,
|
|
13073
|
+
sessionKey: ctx.sessionKey,
|
|
13074
|
+
model: resolveModel(ctx.agentId ?? "main")
|
|
12675
13075
|
});
|
|
12676
|
-
if (entry)
|
|
13076
|
+
if (entry) addActivityToHot(entry);
|
|
12677
13077
|
} catch (err) {
|
|
12678
|
-
logger.warn(`cohort-sync:
|
|
13078
|
+
logger.warn(`cohort-sync: after_tool_call activity failed: ${String(err)}`);
|
|
12679
13079
|
}
|
|
12680
13080
|
});
|
|
12681
13081
|
api.on("message_received", async (_event, ctx) => {
|
|
13082
|
+
diag("HOOK_message_received_RAW", {
|
|
13083
|
+
ctx: dumpCtx(ctx),
|
|
13084
|
+
event: dumpEvent(_event),
|
|
13085
|
+
bridgeStateBefore: { ...getHotState().channelAgentBridge }
|
|
13086
|
+
});
|
|
12682
13087
|
const agentName = resolveAgentFromContext(ctx);
|
|
13088
|
+
const channel = ctx.channelId;
|
|
13089
|
+
diag("HOOK_message_received_RESOLVED", {
|
|
13090
|
+
agentName,
|
|
13091
|
+
channel,
|
|
13092
|
+
accountId: ctx.accountId,
|
|
13093
|
+
conversationId: ctx.conversationId,
|
|
13094
|
+
from: _event.from
|
|
13095
|
+
});
|
|
12683
13096
|
try {
|
|
12684
|
-
const entry =
|
|
12685
|
-
channel:
|
|
13097
|
+
const entry = buildActivityEntry(agentName, "message_received", {
|
|
13098
|
+
channel: channel ?? "unknown"
|
|
12686
13099
|
});
|
|
12687
|
-
if (entry)
|
|
13100
|
+
if (entry) addActivityToHot(entry);
|
|
12688
13101
|
} catch (err) {
|
|
12689
13102
|
logger.warn(`cohort-sync: message_received activity failed: ${String(err)}`);
|
|
12690
13103
|
}
|
|
12691
13104
|
});
|
|
12692
|
-
api.on("message_sent", async (
|
|
13105
|
+
api.on("message_sent", async (event, ctx) => {
|
|
13106
|
+
diag("HOOK_message_sent_RAW", {
|
|
13107
|
+
ctx: dumpCtx(ctx),
|
|
13108
|
+
event: dumpEvent(event),
|
|
13109
|
+
bridgeStateBefore: { ...getHotState().channelAgentBridge }
|
|
13110
|
+
});
|
|
12693
13111
|
const agentName = resolveAgentFromContext(ctx);
|
|
13112
|
+
const channel = ctx.channelId;
|
|
13113
|
+
diag("HOOK_message_sent_RESOLVED", {
|
|
13114
|
+
agentName,
|
|
13115
|
+
channel,
|
|
13116
|
+
accountId: ctx.accountId,
|
|
13117
|
+
conversationId: ctx.conversationId,
|
|
13118
|
+
to: event.to,
|
|
13119
|
+
success: event.success,
|
|
13120
|
+
error: event.error
|
|
13121
|
+
});
|
|
12694
13122
|
try {
|
|
12695
|
-
const entry =
|
|
12696
|
-
channel:
|
|
13123
|
+
const entry = buildActivityEntry(agentName, "message_sent", {
|
|
13124
|
+
channel: channel ?? "unknown",
|
|
13125
|
+
success: event.success,
|
|
13126
|
+
error: event.error
|
|
12697
13127
|
});
|
|
12698
|
-
if (entry)
|
|
13128
|
+
if (entry) addActivityToHot(entry);
|
|
12699
13129
|
} catch (err) {
|
|
12700
13130
|
logger.warn(`cohort-sync: message_sent activity failed: ${String(err)}`);
|
|
12701
13131
|
}
|
|
12702
13132
|
});
|
|
13133
|
+
api.on("before_compaction", async (_event, ctx) => {
|
|
13134
|
+
diag("HOOK_before_compaction", { ctx: dumpCtx(ctx) });
|
|
13135
|
+
const agentId = ctx.agentId ?? "main";
|
|
13136
|
+
const agentName = resolveAgentName(agentId);
|
|
13137
|
+
try {
|
|
13138
|
+
const entry = buildActivityEntry(agentName, "before_compaction", {
|
|
13139
|
+
sessionKey: ctx.sessionKey
|
|
13140
|
+
});
|
|
13141
|
+
if (entry) addActivityToHot(entry);
|
|
13142
|
+
} catch (err) {
|
|
13143
|
+
logger.warn(`cohort-sync: before_compaction activity failed: ${String(err)}`);
|
|
13144
|
+
}
|
|
13145
|
+
});
|
|
13146
|
+
api.on("before_reset", async (event, ctx) => {
|
|
13147
|
+
diag("HOOK_before_reset", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
13148
|
+
const agentId = ctx.agentId ?? "main";
|
|
13149
|
+
const agentName = resolveAgentName(agentId);
|
|
13150
|
+
try {
|
|
13151
|
+
const entry = buildActivityEntry(agentName, "before_reset", {
|
|
13152
|
+
reason: event.reason,
|
|
13153
|
+
sessionKey: ctx.sessionKey
|
|
13154
|
+
});
|
|
13155
|
+
if (entry) addActivityToHot(entry);
|
|
13156
|
+
} catch (err) {
|
|
13157
|
+
logger.warn(`cohort-sync: before_reset activity failed: ${String(err)}`);
|
|
13158
|
+
}
|
|
13159
|
+
});
|
|
12703
13160
|
api.on("gateway_stop", async () => {
|
|
13161
|
+
diag("HOOK_gateway_stop", { bridgeState: { ...getHotState().channelAgentBridge } });
|
|
12704
13162
|
clearIntervalsFromHot();
|
|
12705
13163
|
heartbeatInterval = null;
|
|
12706
13164
|
activityFlushInterval = null;
|
|
@@ -12720,7 +13178,7 @@ function registerHooks(api, cfg) {
|
|
|
12720
13178
|
logger.warn(`cohort-sync: final unreachable push failed for ${agentName}: ${String(err)}`);
|
|
12721
13179
|
}
|
|
12722
13180
|
}
|
|
12723
|
-
|
|
13181
|
+
saveSessionsToDisk(tracker);
|
|
12724
13182
|
try {
|
|
12725
13183
|
await markAllUnreachable(cfg, logger);
|
|
12726
13184
|
} catch (err) {
|
package/dist/package.json
CHANGED