@cfio/cohort-sync 0.2.1 → 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 +564 -128
- 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
|
+
};
|
|
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
|
+
};
|
|
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
|
+
};
|
|
12106
12244
|
}
|
|
12107
|
-
case "
|
|
12108
|
-
const
|
|
12109
|
-
|
|
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;
|
|
12376
12676
|
}
|
|
12377
|
-
|
|
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`);
|
|
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);
|
|
@@ -12437,7 +12770,7 @@ function registerHooks(api, cfg) {
|
|
|
12437
12770
|
const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
|
|
12438
12771
|
for (const agentId of allAgentIds) {
|
|
12439
12772
|
const agentName = resolveAgentName(agentId);
|
|
12440
|
-
const pruned = tracker.pruneStaleSessions(agentName,
|
|
12773
|
+
const pruned = tracker.pruneStaleSessions(agentName, 864e5);
|
|
12441
12774
|
if (pruned.length > 0) {
|
|
12442
12775
|
logger.info(`cohort-sync: pruned ${pruned.length} stale sessions for ${agentName}`);
|
|
12443
12776
|
}
|
|
@@ -12467,10 +12800,11 @@ function registerHooks(api, cfg) {
|
|
|
12467
12800
|
logger.warn(`cohort-sync: heartbeat session push failed for ${agentName}: ${String(err)}`);
|
|
12468
12801
|
}
|
|
12469
12802
|
}
|
|
12803
|
+
saveSessionsToDisk(tracker);
|
|
12470
12804
|
logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
|
|
12471
12805
|
}
|
|
12472
12806
|
async function flushActivityBuffer() {
|
|
12473
|
-
const entries =
|
|
12807
|
+
const entries = drainActivityFromHot();
|
|
12474
12808
|
if (entries.length === 0) return;
|
|
12475
12809
|
try {
|
|
12476
12810
|
await pushActivity(cfg.apiKey, entries);
|
|
@@ -12479,6 +12813,7 @@ function registerHooks(api, cfg) {
|
|
|
12479
12813
|
}
|
|
12480
12814
|
}
|
|
12481
12815
|
api.on("gateway_start", async (event) => {
|
|
12816
|
+
diag("HOOK_gateway_start", { port: event.port, eventKeys: Object.keys(event) });
|
|
12482
12817
|
try {
|
|
12483
12818
|
checkForUpdate(PLUGIN_VERSION, logger).catch(() => {
|
|
12484
12819
|
});
|
|
@@ -12494,6 +12829,15 @@ function registerHooks(api, cfg) {
|
|
|
12494
12829
|
} catch (err) {
|
|
12495
12830
|
logger.error(`cohort-sync: gateway_start sync failed: ${String(err)}`);
|
|
12496
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 } });
|
|
12497
12841
|
await initSubscription(
|
|
12498
12842
|
event.port,
|
|
12499
12843
|
cfg,
|
|
@@ -12519,7 +12863,8 @@ function registerHooks(api, cfg) {
|
|
|
12519
12863
|
}
|
|
12520
12864
|
logger.info(`cohort-sync: seeded telemetry for ${allAgentIds.length} agents`);
|
|
12521
12865
|
});
|
|
12522
|
-
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 });
|
|
12523
12868
|
const agentId = ctx.agentId ?? "main";
|
|
12524
12869
|
const agentName = resolveAgentName(agentId);
|
|
12525
12870
|
try {
|
|
@@ -12532,11 +12877,21 @@ function registerHooks(api, cfg) {
|
|
|
12532
12877
|
tracker.markTelemetryPushed(agentName);
|
|
12533
12878
|
}
|
|
12534
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
|
+
}
|
|
12535
12889
|
} catch (err) {
|
|
12536
12890
|
logger.warn(`cohort-sync: agent_end sync failed: ${String(err)}`);
|
|
12537
12891
|
}
|
|
12538
12892
|
});
|
|
12539
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 });
|
|
12540
12895
|
const agentId = ctx.agentId ?? "main";
|
|
12541
12896
|
const agentName = resolveAgentName(agentId);
|
|
12542
12897
|
try {
|
|
@@ -12574,6 +12929,7 @@ function registerHooks(api, cfg) {
|
|
|
12574
12929
|
}
|
|
12575
12930
|
});
|
|
12576
12931
|
api.on("after_compaction", async (event, ctx) => {
|
|
12932
|
+
diag("HOOK_after_compaction", { ctx: dumpCtx(ctx), messageCount: event.messageCount, tokenCount: event.tokenCount });
|
|
12577
12933
|
const agentId = ctx.agentId ?? "main";
|
|
12578
12934
|
const agentName = resolveAgentName(agentId);
|
|
12579
12935
|
try {
|
|
@@ -12589,18 +12945,21 @@ function registerHooks(api, cfg) {
|
|
|
12589
12945
|
tracker.markTelemetryPushed(agentName);
|
|
12590
12946
|
}
|
|
12591
12947
|
}
|
|
12592
|
-
const entry =
|
|
12593
|
-
|
|
12594
|
-
|
|
12948
|
+
const entry = buildActivityEntry(agentName, "after_compaction", {
|
|
12949
|
+
messageCount: event.messageCount,
|
|
12950
|
+
compactedCount: ext.compactedCount ?? event.compactedCount,
|
|
12951
|
+
sessionKey: ctx.sessionKey
|
|
12595
12952
|
});
|
|
12596
|
-
if (entry)
|
|
12953
|
+
if (entry) addActivityToHot(entry);
|
|
12597
12954
|
} catch (err) {
|
|
12598
12955
|
logger.warn(`cohort-sync: after_compaction telemetry failed: ${String(err)}`);
|
|
12599
12956
|
}
|
|
12600
12957
|
});
|
|
12601
12958
|
api.on("before_agent_start", async (_event, ctx) => {
|
|
12959
|
+
diag("HOOK_before_agent_start", { ctx: dumpCtx(ctx), event: dumpEvent(_event) });
|
|
12602
12960
|
const agentId = ctx.agentId ?? "main";
|
|
12603
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 });
|
|
12604
12963
|
try {
|
|
12605
12964
|
tracker.updateStatus(agentName, "working");
|
|
12606
12965
|
if (tracker.shouldPushTelemetry(agentName)) {
|
|
@@ -12620,20 +12979,27 @@ function registerHooks(api, cfg) {
|
|
|
12620
12979
|
await pushSessions(cfg.apiKey, agentName, sessSnapshot);
|
|
12621
12980
|
tracker.markSessionsPushed(agentName);
|
|
12622
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);
|
|
12623
12992
|
}
|
|
12624
|
-
const entry = buildActivitySentence(agentName, "before_agent_start", {
|
|
12625
|
-
channel: ctx.messageProvider ?? "unknown"
|
|
12626
|
-
});
|
|
12627
|
-
if (entry) tracker.addActivity(entry);
|
|
12628
12993
|
} catch (err) {
|
|
12629
12994
|
logger.warn(`cohort-sync: before_agent_start telemetry failed: ${String(err)}`);
|
|
12630
12995
|
}
|
|
12631
12996
|
});
|
|
12632
12997
|
api.on("session_start", async (event, ctx) => {
|
|
12998
|
+
diag("HOOK_session_start", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
12633
12999
|
const agentId = ctx.agentId ?? "main";
|
|
12634
13000
|
const agentName = resolveAgentName(agentId);
|
|
12635
13001
|
try {
|
|
12636
|
-
const sessionKey = ctx.
|
|
13002
|
+
const sessionKey = ctx.sessionId ?? String(Date.now());
|
|
12637
13003
|
tracker.addSession(agentName, sessionKey);
|
|
12638
13004
|
if (tracker.shouldPushSessions(agentName)) {
|
|
12639
13005
|
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
@@ -12641,66 +13007,136 @@ function registerHooks(api, cfg) {
|
|
|
12641
13007
|
tracker.markSessionsPushed(agentName);
|
|
12642
13008
|
}
|
|
12643
13009
|
const parsed = parseSessionKey(sessionKey);
|
|
12644
|
-
const entry =
|
|
12645
|
-
channel: parsed.channel
|
|
13010
|
+
const entry = buildActivityEntry(agentName, "session_start", {
|
|
13011
|
+
channel: parsed.channel,
|
|
13012
|
+
sessionKey,
|
|
13013
|
+
resumedFrom: event.resumedFrom
|
|
12646
13014
|
});
|
|
12647
|
-
if (entry)
|
|
13015
|
+
if (entry) addActivityToHot(entry);
|
|
12648
13016
|
} catch (err) {
|
|
12649
13017
|
logger.warn(`cohort-sync: session_start tracking failed: ${String(err)}`);
|
|
12650
13018
|
}
|
|
12651
13019
|
});
|
|
12652
13020
|
api.on("session_end", async (event, ctx) => {
|
|
13021
|
+
diag("HOOK_session_end", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
12653
13022
|
const agentId = ctx.agentId ?? "main";
|
|
12654
13023
|
const agentName = resolveAgentName(agentId);
|
|
12655
13024
|
try {
|
|
12656
|
-
const sessionKey = ctx.
|
|
13025
|
+
const sessionKey = ctx.sessionId ?? "";
|
|
12657
13026
|
tracker.removeSession(agentName, sessionKey);
|
|
12658
13027
|
if (tracker.shouldPushSessions(agentName)) {
|
|
12659
13028
|
const sessionsSnapshot = tracker.getSessionsSnapshot(agentName);
|
|
12660
13029
|
await pushSessions(cfg.apiKey, agentName, sessionsSnapshot);
|
|
12661
13030
|
tracker.markSessionsPushed(agentName);
|
|
12662
13031
|
}
|
|
12663
|
-
const entry =
|
|
12664
|
-
|
|
13032
|
+
const entry = buildActivityEntry(agentName, "session_end", {
|
|
13033
|
+
sessionKey,
|
|
13034
|
+
messageCount: event.messageCount,
|
|
13035
|
+
durationMs: event.durationMs
|
|
13036
|
+
});
|
|
13037
|
+
if (entry) addActivityToHot(entry);
|
|
12665
13038
|
} catch (err) {
|
|
12666
13039
|
logger.warn(`cohort-sync: session_end tracking failed: ${String(err)}`);
|
|
12667
13040
|
}
|
|
12668
13041
|
});
|
|
12669
|
-
api.on("
|
|
13042
|
+
api.on("after_tool_call", async (event, ctx) => {
|
|
13043
|
+
diag("HOOK_after_tool_call", { ctx: dumpCtx(ctx), toolName: event.toolName, error: event.error });
|
|
12670
13044
|
const agentName = resolveAgentFromContext(ctx);
|
|
12671
13045
|
try {
|
|
12672
|
-
const entry =
|
|
12673
|
-
toolName: event.toolName
|
|
12674
|
-
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")
|
|
12675
13053
|
});
|
|
12676
|
-
if (entry)
|
|
13054
|
+
if (entry) addActivityToHot(entry);
|
|
12677
13055
|
} catch (err) {
|
|
12678
|
-
logger.warn(`cohort-sync:
|
|
13056
|
+
logger.warn(`cohort-sync: after_tool_call activity failed: ${String(err)}`);
|
|
12679
13057
|
}
|
|
12680
13058
|
});
|
|
12681
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
|
+
});
|
|
12682
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
|
+
});
|
|
12683
13074
|
try {
|
|
12684
|
-
const entry =
|
|
12685
|
-
channel:
|
|
13075
|
+
const entry = buildActivityEntry(agentName, "message_received", {
|
|
13076
|
+
channel: channel ?? "unknown"
|
|
12686
13077
|
});
|
|
12687
|
-
if (entry)
|
|
13078
|
+
if (entry) addActivityToHot(entry);
|
|
12688
13079
|
} catch (err) {
|
|
12689
13080
|
logger.warn(`cohort-sync: message_received activity failed: ${String(err)}`);
|
|
12690
13081
|
}
|
|
12691
13082
|
});
|
|
12692
|
-
api.on("message_sent", async (
|
|
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
|
+
});
|
|
12693
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
|
+
});
|
|
12694
13100
|
try {
|
|
12695
|
-
const entry =
|
|
12696
|
-
channel:
|
|
13101
|
+
const entry = buildActivityEntry(agentName, "message_sent", {
|
|
13102
|
+
channel: channel ?? "unknown",
|
|
13103
|
+
success: event.success,
|
|
13104
|
+
error: event.error
|
|
12697
13105
|
});
|
|
12698
|
-
if (entry)
|
|
13106
|
+
if (entry) addActivityToHot(entry);
|
|
12699
13107
|
} catch (err) {
|
|
12700
13108
|
logger.warn(`cohort-sync: message_sent activity failed: ${String(err)}`);
|
|
12701
13109
|
}
|
|
12702
13110
|
});
|
|
13111
|
+
api.on("before_compaction", async (_event, ctx) => {
|
|
13112
|
+
diag("HOOK_before_compaction", { ctx: dumpCtx(ctx) });
|
|
13113
|
+
const agentId = ctx.agentId ?? "main";
|
|
13114
|
+
const agentName = resolveAgentName(agentId);
|
|
13115
|
+
try {
|
|
13116
|
+
const entry = buildActivityEntry(agentName, "before_compaction", {
|
|
13117
|
+
sessionKey: ctx.sessionKey
|
|
13118
|
+
});
|
|
13119
|
+
if (entry) addActivityToHot(entry);
|
|
13120
|
+
} catch (err) {
|
|
13121
|
+
logger.warn(`cohort-sync: before_compaction activity failed: ${String(err)}`);
|
|
13122
|
+
}
|
|
13123
|
+
});
|
|
13124
|
+
api.on("before_reset", async (event, ctx) => {
|
|
13125
|
+
diag("HOOK_before_reset", { ctx: dumpCtx(ctx), event: dumpEvent(event) });
|
|
13126
|
+
const agentId = ctx.agentId ?? "main";
|
|
13127
|
+
const agentName = resolveAgentName(agentId);
|
|
13128
|
+
try {
|
|
13129
|
+
const entry = buildActivityEntry(agentName, "before_reset", {
|
|
13130
|
+
reason: event.reason,
|
|
13131
|
+
sessionKey: ctx.sessionKey
|
|
13132
|
+
});
|
|
13133
|
+
if (entry) addActivityToHot(entry);
|
|
13134
|
+
} catch (err) {
|
|
13135
|
+
logger.warn(`cohort-sync: before_reset activity failed: ${String(err)}`);
|
|
13136
|
+
}
|
|
13137
|
+
});
|
|
12703
13138
|
api.on("gateway_stop", async () => {
|
|
13139
|
+
diag("HOOK_gateway_stop", { bridgeState: { ...getHotState().channelAgentBridge } });
|
|
12704
13140
|
clearIntervalsFromHot();
|
|
12705
13141
|
heartbeatInterval = null;
|
|
12706
13142
|
activityFlushInterval = null;
|
|
@@ -12720,7 +13156,7 @@ function registerHooks(api, cfg) {
|
|
|
12720
13156
|
logger.warn(`cohort-sync: final unreachable push failed for ${agentName}: ${String(err)}`);
|
|
12721
13157
|
}
|
|
12722
13158
|
}
|
|
12723
|
-
|
|
13159
|
+
saveSessionsToDisk(tracker);
|
|
12724
13160
|
try {
|
|
12725
13161
|
await markAllUnreachable(cfg, logger);
|
|
12726
13162
|
} catch (err) {
|
package/dist/package.json
CHANGED