@cfio/cohort-sync 0.2.0 → 0.2.1

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 CHANGED
@@ -11748,6 +11748,19 @@ function saveAgentStatesToHot(states) {
11748
11748
  function getAgentStatesFromHot() {
11749
11749
  return getHotState()?.agentStates ?? null;
11750
11750
  }
11751
+ function saveIntervalsToHot(heartbeat, activityFlush) {
11752
+ const hot = getHotState();
11753
+ if (hot) {
11754
+ hot.intervals = { heartbeat, activityFlush };
11755
+ }
11756
+ }
11757
+ function clearIntervalsFromHot() {
11758
+ const hot = getHotState();
11759
+ if (!hot?.intervals) return;
11760
+ if (hot.intervals.heartbeat) clearInterval(hot.intervals.heartbeat);
11761
+ if (hot.intervals.activityFlush) clearInterval(hot.intervals.activityFlush);
11762
+ hot.intervals = { heartbeat: null, activityFlush: null };
11763
+ }
11751
11764
 
11752
11765
  // src/sync.ts
11753
11766
  function extractJson(raw) {
@@ -12014,6 +12027,7 @@ function shouldPushTelemetry(current, lastPushed) {
12014
12027
  if (!lastPushed) return true;
12015
12028
  if (current.status !== lastPushed.status) return true;
12016
12029
  if (current.compactions !== lastPushed.compactions) return true;
12030
+ if (current.activeSessions !== lastPushed.activeSessions) return true;
12017
12031
  if (current.contextLimit > 0 && lastPushed.contextLimit > 0) {
12018
12032
  const currentPct = current.contextTokens / current.contextLimit;
12019
12033
  const lastPct = lastPushed.contextTokens / lastPushed.contextLimit;
@@ -12101,6 +12115,13 @@ function buildActivitySentence(agentName, event, context) {
12101
12115
  var AgentStateTracker = class {
12102
12116
  agents = /* @__PURE__ */ new Map();
12103
12117
  activityBuffer = [];
12118
+ sessionKeyToAgent = /* @__PURE__ */ new Map();
12119
+ setSessionAgent(sessionKey, agentName) {
12120
+ this.sessionKeyToAgent.set(sessionKey, agentName);
12121
+ }
12122
+ getSessionAgent(sessionKey) {
12123
+ return this.sessionKeyToAgent.get(sessionKey) ?? null;
12124
+ }
12104
12125
  getOrCreate(agentName) {
12105
12126
  let state = this.agents.get(agentName);
12106
12127
  if (!state) {
@@ -12142,6 +12163,25 @@ var AgentStateTracker = class {
12142
12163
  state.sessions.delete(sessionKey);
12143
12164
  }
12144
12165
  }
12166
+ hasSession(agentName, sessionKey) {
12167
+ const state = this.agents.get(agentName);
12168
+ if (!state) return false;
12169
+ return state.sessions.has(sessionKey);
12170
+ }
12171
+ pruneStaleSessions(agentName, maxAgeMs) {
12172
+ const state = this.agents.get(agentName);
12173
+ if (!state) return [];
12174
+ const now = Date.now();
12175
+ const pruned = [];
12176
+ for (const [key, session] of state.sessions) {
12177
+ const lastMs = new Date(session.lastActivity).getTime();
12178
+ if (now - lastMs > maxAgeMs) {
12179
+ state.sessions.delete(key);
12180
+ pruned.push(key);
12181
+ }
12182
+ }
12183
+ return pruned;
12184
+ }
12145
12185
  // --- Telemetry updates ---
12146
12186
  updateStatus(agentName, status) {
12147
12187
  this.getOrCreate(agentName).status = status;
@@ -12243,17 +12283,27 @@ var AgentStateTracker = class {
12243
12283
  clear() {
12244
12284
  this.agents.clear();
12245
12285
  this.activityBuffer = [];
12286
+ this.sessionKeyToAgent.clear();
12246
12287
  }
12247
12288
  exportState() {
12248
- return new Map(this.agents);
12289
+ return {
12290
+ agents: new Map(this.agents),
12291
+ sessionKeyToAgent: new Map(this.sessionKeyToAgent)
12292
+ };
12249
12293
  }
12250
12294
  importState(states) {
12251
- for (const [name, state] of states) {
12295
+ const agentMap = states instanceof Map ? states : states.agents;
12296
+ for (const [name, state] of agentMap) {
12252
12297
  if (!(state.sessions instanceof Map)) {
12253
12298
  state.sessions = new Map(Object.entries(state.sessions));
12254
12299
  }
12255
12300
  this.agents.set(name, state);
12256
12301
  }
12302
+ if (!(states instanceof Map) && states.sessionKeyToAgent) {
12303
+ for (const [key, agent] of states.sessionKeyToAgent) {
12304
+ this.sessionKeyToAgent.set(key, agent);
12305
+ }
12306
+ }
12257
12307
  }
12258
12308
  };
12259
12309
 
@@ -12315,6 +12365,30 @@ function registerHooks(api, cfg) {
12315
12365
  function resolveAgentName(agentId) {
12316
12366
  return (nameMap?.[agentId] ?? agentId).toLowerCase();
12317
12367
  }
12368
+ function resolveAgentFromContext(ctx) {
12369
+ if (ctx.agentId && typeof ctx.agentId === "string") {
12370
+ return resolveAgentName(ctx.agentId);
12371
+ }
12372
+ const sessionKey = ctx.sessionKey ?? ctx.sessionId;
12373
+ if (sessionKey && typeof sessionKey === "string") {
12374
+ const mapped = tracker.getSessionAgent(sessionKey);
12375
+ if (mapped) return mapped;
12376
+ }
12377
+ return resolveAgentName("main");
12378
+ }
12379
+ clearIntervalsFromHot();
12380
+ heartbeatInterval = setInterval(() => {
12381
+ pushHeartbeat().catch((err) => {
12382
+ logger.warn(`cohort-sync: heartbeat tick failed: ${String(err)}`);
12383
+ });
12384
+ }, 12e4);
12385
+ activityFlushInterval = setInterval(() => {
12386
+ flushActivityBuffer().catch((err) => {
12387
+ logger.warn(`cohort-sync: activity flush tick failed: ${String(err)}`);
12388
+ });
12389
+ }, 1500);
12390
+ saveIntervalsToHot(heartbeatInterval, activityFlushInterval);
12391
+ logger.info("cohort-sync: intervals created (heartbeat=2m, activityFlush=1.5s)");
12318
12392
  api.registerTool((toolCtx) => {
12319
12393
  const agentId = toolCtx.agentId ?? "main";
12320
12394
  const agentName = resolveAgentName(agentId);
@@ -12361,6 +12435,13 @@ function registerHooks(api, cfg) {
12361
12435
  }
12362
12436
  async function pushHeartbeat() {
12363
12437
  const allAgentIds = ["main", ...(config?.agents?.list ?? []).map((a) => a.id)];
12438
+ for (const agentId of allAgentIds) {
12439
+ const agentName = resolveAgentName(agentId);
12440
+ const pruned = tracker.pruneStaleSessions(agentName, 6e5);
12441
+ if (pruned.length > 0) {
12442
+ logger.info(`cohort-sync: pruned ${pruned.length} stale sessions for ${agentName}`);
12443
+ }
12444
+ }
12364
12445
  for (const agentId of allAgentIds) {
12365
12446
  const agentName = resolveAgentName(agentId);
12366
12447
  try {
@@ -12374,6 +12455,18 @@ function registerHooks(api, cfg) {
12374
12455
  logger.warn(`cohort-sync: heartbeat push failed for ${agentName}: ${String(err)}`);
12375
12456
  }
12376
12457
  }
12458
+ for (const agentId of allAgentIds) {
12459
+ const agentName = resolveAgentName(agentId);
12460
+ try {
12461
+ if (tracker.shouldPushSessions(agentName)) {
12462
+ const sessSnapshot = tracker.getSessionsSnapshot(agentName);
12463
+ await pushSessions(cfg.apiKey, agentName, sessSnapshot);
12464
+ tracker.markSessionsPushed(agentName);
12465
+ }
12466
+ } catch (err) {
12467
+ logger.warn(`cohort-sync: heartbeat session push failed for ${agentName}: ${String(err)}`);
12468
+ }
12469
+ }
12377
12470
  logger.info(`cohort-sync: heartbeat pushed for ${allAgentIds.length} agents`);
12378
12471
  }
12379
12472
  async function flushActivityBuffer() {
@@ -12425,18 +12518,6 @@ function registerHooks(api, cfg) {
12425
12518
  }
12426
12519
  }
12427
12520
  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
12521
  });
12441
12522
  api.on("agent_end", async (_event, ctx) => {
12442
12523
  const agentId = ctx.agentId ?? "main";
@@ -12469,6 +12550,13 @@ function registerHooks(api, cfg) {
12469
12550
  contextTokens: ext.contextTokens ?? usage.total ?? 0,
12470
12551
  contextLimit: ext.contextLimit ?? 0
12471
12552
  });
12553
+ if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
12554
+ tracker.addSession(agentName, sessionKey);
12555
+ logger.info(`cohort-sync: inferred session for ${agentName} from llm_output (${sessionKey})`);
12556
+ }
12557
+ if (sessionKey) {
12558
+ tracker.setSessionAgent(sessionKey, agentName);
12559
+ }
12472
12560
  if (tracker.shouldPushTelemetry(agentName)) {
12473
12561
  const snapshot = tracker.getTelemetrySnapshot(agentName);
12474
12562
  if (snapshot) {
@@ -12522,6 +12610,17 @@ function registerHooks(api, cfg) {
12522
12610
  tracker.markTelemetryPushed(agentName);
12523
12611
  }
12524
12612
  }
12613
+ const sessionKey = ctx.sessionKey;
12614
+ if (sessionKey && !tracker.hasSession(agentName, sessionKey)) {
12615
+ tracker.addSession(agentName, sessionKey);
12616
+ tracker.setSessionAgent(sessionKey, agentName);
12617
+ logger.info(`cohort-sync: inferred session for ${agentName} (${sessionKey})`);
12618
+ if (tracker.shouldPushSessions(agentName)) {
12619
+ const sessSnapshot = tracker.getSessionsSnapshot(agentName);
12620
+ await pushSessions(cfg.apiKey, agentName, sessSnapshot);
12621
+ tracker.markSessionsPushed(agentName);
12622
+ }
12623
+ }
12525
12624
  const entry = buildActivitySentence(agentName, "before_agent_start", {
12526
12625
  channel: ctx.messageProvider ?? "unknown"
12527
12626
  });
@@ -12568,8 +12667,7 @@ function registerHooks(api, cfg) {
12568
12667
  }
12569
12668
  });
12570
12669
  api.on("before_tool_call", async (event, ctx) => {
12571
- const agentId = ctx.agentId ?? "main";
12572
- const agentName = resolveAgentName(agentId);
12670
+ const agentName = resolveAgentFromContext(ctx);
12573
12671
  try {
12574
12672
  const entry = buildActivitySentence(agentName, "before_tool_call", {
12575
12673
  toolName: event.toolName ?? event.name,
@@ -12581,8 +12679,7 @@ function registerHooks(api, cfg) {
12581
12679
  }
12582
12680
  });
12583
12681
  api.on("message_received", async (_event, ctx) => {
12584
- const agentId = ctx.agentId ?? "main";
12585
- const agentName = resolveAgentName(agentId);
12682
+ const agentName = resolveAgentFromContext(ctx);
12586
12683
  try {
12587
12684
  const entry = buildActivitySentence(agentName, "message_received", {
12588
12685
  channel: ctx.messageProvider ?? "unknown"
@@ -12593,8 +12690,7 @@ function registerHooks(api, cfg) {
12593
12690
  }
12594
12691
  });
12595
12692
  api.on("message_sent", async (_event, ctx) => {
12596
- const agentId = ctx.agentId ?? "main";
12597
- const agentName = resolveAgentName(agentId);
12693
+ const agentName = resolveAgentFromContext(ctx);
12598
12694
  try {
12599
12695
  const entry = buildActivitySentence(agentName, "message_sent", {
12600
12696
  channel: ctx.messageProvider ?? "unknown"
@@ -12605,14 +12701,9 @@ function registerHooks(api, cfg) {
12605
12701
  }
12606
12702
  });
12607
12703
  api.on("gateway_stop", async () => {
12608
- if (heartbeatInterval) {
12609
- clearInterval(heartbeatInterval);
12610
- heartbeatInterval = null;
12611
- }
12612
- if (activityFlushInterval) {
12613
- clearInterval(activityFlushInterval);
12614
- activityFlushInterval = null;
12615
- }
12704
+ clearIntervalsFromHot();
12705
+ heartbeatInterval = null;
12706
+ activityFlushInterval = null;
12616
12707
  await flushActivityBuffer().catch((err) => {
12617
12708
  logger.warn(`cohort-sync: final activity flush failed: ${String(err)}`);
12618
12709
  });
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Syncs agent status and skills to Cohort dashboard",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfio/cohort-sync",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Syncs agent status and skills to Cohort dashboard",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.cohort.bot/gateway",