openvisio-agent 0.19.12 → 0.20.0

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/src/ws.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // The backend agent WebSocket — a BYO agent's real-time event channel. Connects
2
2
  // to the org's API Gateway WS with the agent's api_key + identifier (NOT a JWT),
3
3
  // keeps the connection warm with keepalives, auto-reconnects with backoff, and
4
- // hands targeted dispatches (task:assigned / agent:mention) to a callback.
4
+ // hands targeted dispatches and verified organization fanout to a callback.
5
5
  //
6
6
  // Uses the built-in global WebSocket (Node >= 21). Zero dependencies — see
7
7
  // `assertWebSocket` for the version guard.
@@ -9,6 +9,7 @@
9
9
  // Docs: agent-websocket.md
10
10
  // wss://<api-gw>.execute-api.<region>.amazonaws.com/<stage>/?api_key=…&identifier=…
11
11
  // client → { "type": "keepalive" } (refreshes last_seen_at)
12
+ // client → { "type": "subscribe", "organization_id": 3 } (verified profile only)
12
13
  // server → { "k": "<kind>", "d": <payload> }
13
14
 
14
15
  import { stripSlash } from './lib.mjs'
@@ -45,6 +46,14 @@ export function connectAgentWs({ wsUrl, apiKey, identifier, onEvent, onConnect,
45
46
  let reconnectTimer = null
46
47
  let backoff = BACKOFF_MIN_MS
47
48
  let closed = false // set once .close() is called — stops reconnects
49
+ let organizationId = null
50
+ let subscribedOrganizationId = null
51
+
52
+ const subscribe = () => {
53
+ if (closed || organizationId == null || !ws || ws.readyState !== 1 || subscribedOrganizationId === organizationId) return
54
+ try { ws.send(JSON.stringify({ type: 'subscribe', organization_id: organizationId })); subscribedOrganizationId = organizationId }
55
+ catch { /* the retained organization is retried on reconnect */ }
56
+ }
48
57
 
49
58
  const clearKeepalive = () => { if (keepalive) { clearInterval(keepalive); keepalive = null } }
50
59
 
@@ -65,6 +74,8 @@ export function connectAgentWs({ wsUrl, apiKey, identifier, onEvent, onConnect,
65
74
  sock.addEventListener('open', () => {
66
75
  if (sock !== ws) return
67
76
  backoff = BACKOFF_MIN_MS // healthy connection → reset the backoff ramp
77
+ subscribedOrganizationId = null
78
+ subscribe()
68
79
  log(`connected as @${identifier}`)
69
80
  try { onConnect && onConnect() } catch { /* reconciliation is best-effort */ }
70
81
  clearKeepalive()
@@ -99,6 +110,14 @@ export function connectAgentWs({ wsUrl, apiKey, identifier, onEvent, onConnect,
99
110
  open()
100
111
 
101
112
  const api = {
113
+ // Call only with organization identity read from the authenticated agent's
114
+ // own profile, never an incoming message or an arbitrary project record.
115
+ subscribeOrganization(value) {
116
+ if (closed || !((typeof value === 'number' || (typeof value === 'string' && /^\d+$/.test(value))) && Number.isSafeInteger(Number(value)) && Number(value) > 0)) return false
117
+ organizationId = Number(value)
118
+ subscribe()
119
+ return true
120
+ },
102
121
  /** Fire-and-forget send (no-op if the socket isn't open). */
103
122
  send(obj) { try { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)) } catch { /* closing */ } },
104
123
  close() {