openzoo 0.50.25 → 0.50.26

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.
@@ -663,6 +663,56 @@ function ssePush(channel, payload) {
663
663
  }
664
664
  }
665
665
 
666
+ let focusedAgentId = null;
667
+ const agentActivity = new Map(); // id -> { updatedAt, unreadCount, hasUnread, preview }
668
+ function noteFocus(id) {
669
+ if (!id) return;
670
+ focusedAgentId = id;
671
+ const a = agentActivity.get(id) || {};
672
+ a.hasUnread = false;
673
+ a.unreadCount = 0;
674
+ agentActivity.set(id, a);
675
+ }
676
+ function stampActivity(agent) {
677
+ const a = agentActivity.get(agent.id) || {};
678
+ const updatedAt = a.updatedAt || agent.updatedAt || agent.createdAt || 0;
679
+ return {
680
+ ...agent,
681
+ updatedAt,
682
+ hasUnread: !!a.hasUnread,
683
+ unreadCount: a.unreadCount || 0,
684
+ };
685
+ }
686
+ function sortAgentsByActivity(list) {
687
+ return [...list].sort((x, y) => {
688
+ const ax = agentActivity.get(x.id)?.updatedAt || x.updatedAt || x.createdAt || 0;
689
+ const ay = agentActivity.get(y.id)?.updatedAt || y.updatedAt || y.createdAt || 0;
690
+ return ay - ax;
691
+ });
692
+ }
693
+ function bumpAgent(id, { preview = '', notify = true } = {}) {
694
+ const now = Date.now();
695
+ const a = agentActivity.get(id) || { unreadCount: 0 };
696
+ a.updatedAt = now;
697
+ if (preview) a.preview = String(preview).slice(0, 120);
698
+ const other = notify && focusedAgentId && focusedAgentId !== id;
699
+ if (other) {
700
+ a.hasUnread = true;
701
+ a.unreadCount = (a.unreadCount || 0) + 1;
702
+ }
703
+ agentActivity.set(id, a);
704
+ const list = cachedAgentList() || [];
705
+ const idx = list.findIndex((x) => x.id === id);
706
+ const base = idx >= 0 ? list[idx] : { id, name: id };
707
+ const agent = stampActivity({ ...base, updatedAt: now });
708
+ if (idx >= 0) list[idx] = agent;
709
+ else list.unshift(agent);
710
+ saveAgents(sortAgentsByActivity(list));
711
+ // asar handleGatewaySseEvent: channel agent-upserted → {agent, activeAgentId}
712
+ ssePush('agent-upserted', { agent, activeAgentId: focusedAgentId || id });
713
+ ssePush('agents', { agents: sortAgentsByActivity(cachedAgentList() || []).slice(0, 80).map(stampActivity), activeAgentId: focusedAgentId || id });
714
+ }
715
+
666
716
  /** Grok Bot Helper daemon: GET /local-exec/requests is SSE, POST /local-exec/responses
667
717
  * is `{providerId, frames}`. We used to JSON-[] the GET which is "disconnected"
668
718
  * (measured: Grok Bot "can't see files on your local computer"). */
@@ -874,9 +924,9 @@ function mergeAgentLists(remote) {
874
924
  for (const a of [...local, ...(Array.isArray(remote) ? remote : [])]) {
875
925
  if (!a?.id || seen.has(a.id)) continue;
876
926
  seen.add(a.id);
877
- out.push(a);
927
+ out.push(stampActivity(a));
878
928
  }
879
- return out;
929
+ return sortAgentsByActivity(out);
880
930
  }
881
931
  function gatewayEntry(e) {
882
932
  const { seq, pulledRemote, ...rest } = e;
@@ -1452,6 +1502,21 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
1452
1502
  log(`cursor-backend: updateAgent local id=${agent.id}`);
1453
1503
  return true;
1454
1504
  }
1505
+ if (name === 'setAgentUnread') {
1506
+ let parsed = {};
1507
+ try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
1508
+ const id = String(parsed.id || '');
1509
+ const unread = parsed.isUnread === true;
1510
+ if (id) {
1511
+ const a = agentActivity.get(id) || {};
1512
+ a.hasUnread = unread;
1513
+ a.unreadCount = unread ? Math.max(1, a.unreadCount || 1) : 0;
1514
+ agentActivity.set(id, a);
1515
+ if (!unread) noteFocus(id);
1516
+ }
1517
+ jsonSend(res, { ok: true });
1518
+ return true;
1519
+ }
1455
1520
  if (name === 'deleteAgents') {
1456
1521
  let parsed = {};
1457
1522
  try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
@@ -1495,6 +1560,8 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
1495
1560
  requestId: nonce,
1496
1561
  richText: parsed.richText,
1497
1562
  });
1563
+ noteFocus(agentId);
1564
+ bumpAgent(agentId, { preview: prompt, notify: false });
1498
1565
  ssePush('transcript', { ...gatewayEntry(userLine), agentId });
1499
1566
  jsonSend(res, { accepted: true });
1500
1567
  const modelCmd = /^\s*\/model(?:\s+(\S+))?\s*$/i.exec(prompt || '');
@@ -1527,6 +1594,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
1527
1594
  }
1528
1595
  const line = fanoutLine(agentId, 'assistant', text, { clientNonce: nonce, requestId: nonce });
1529
1596
  ssePush('transcript', { ...gatewayEntry(line), agentId });
1597
+ bumpAgent(agentId, { preview: text, notify: true });
1530
1598
  log(`cursor-backend: sendPrompt done agent=${agentId} seq=${line.seq} text=${JSON.stringify(text.slice(0, 80))}`);
1531
1599
  });
1532
1600
  return true;
@@ -1535,6 +1603,7 @@ async function handleHijackedPodHttp(req, res, full, body, log) {
1535
1603
  let parsed = {};
1536
1604
  try { parsed = JSON.parse(String(body || '{}')); } catch { parsed = {}; }
1537
1605
  const id = String(parsed.id || parsed.agentId || 'openzoo');
1606
+ noteFocus(id);
1538
1607
  tailedAgents.add(id);
1539
1608
  const t = agentTranscript(id);
1540
1609
  if (!t.pulledRemote && realPod?.agent) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.25",
3
+ "version": "0.50.26",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",