herdr-remote-relay 0.3.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "herdr-remote-relay",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Standalone relay server and web terminal for Herdr Remote",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,10 +27,18 @@ const MAX_DIMENSION = 500;
27
27
  const MIN_SESSION_COLS = 20;
28
28
  const MIN_SESSION_ROWS = 6;
29
29
 
30
+ /** Matches the host's own cap; the relay re-applies it rather than trusting it. */
31
+ const MAX_AGENT_STATUS_ENTRIES = 16;
32
+
30
33
  function randomId(prefix) {
31
34
  return `${prefix}-${crypto.randomBytes(9).toString('base64url')}`;
32
35
  }
33
36
 
37
+ /** A bounded string, or null. For fields whose contents a pane decided. */
38
+ function text(value, limit) {
39
+ return typeof value === 'string' && value.length > 0 ? value.slice(0, limit) : null;
40
+ }
41
+
34
42
  function clampDimension(value, fallback) {
35
43
  const numeric = Number(value);
36
44
  if (!Number.isInteger(numeric)) return fallback;
@@ -813,6 +821,12 @@ class RelayServer {
813
821
  if (this.hosts.get(host.id) === host) this.detachHost(host, { notify: true, reason: 'host_shutdown' });
814
822
  return;
815
823
  }
824
+ // What the workstation's agents are doing belongs to the workstation, not
825
+ // to one stream: every window watching it gets the same answer.
826
+ if (message.type === 'agent_status') {
827
+ this.broadcastAgentStatus(host, message);
828
+ return;
829
+ }
816
830
  // Session events target the single client that owns this stream.
817
831
  const streamId = typeof message.clientId === 'string' ? message.clientId : message.streamId;
818
832
  const clientId = streamId ? this.streams.get(streamId) : null;
@@ -1262,6 +1276,46 @@ class RelayServer {
1262
1276
  }
1263
1277
  }
1264
1278
 
1279
+ /**
1280
+ * Pass on what the workstation's agents are doing.
1281
+ *
1282
+ * Sanitised rather than forwarded: the host is trusted, but this is the one
1283
+ * message whose contents come from terminal titles, and it lands in a status
1284
+ * bar on every attached window. The counts are numbers and the list is
1285
+ * bounded, so a workstation cannot grow a browser's memory by naming things
1286
+ * carefully.
1287
+ */
1288
+ broadcastAgentStatus(host, message) {
1289
+ const counts = {};
1290
+ if (message.counts && typeof message.counts === 'object') {
1291
+ for (const [status, value] of Object.entries(message.counts)) {
1292
+ if (typeof status === 'string' && Number.isFinite(value)) {
1293
+ counts[status.slice(0, 32)] = Math.max(0, Math.min(9999, Math.trunc(value)));
1294
+ }
1295
+ }
1296
+ }
1297
+ const agents = (Array.isArray(message.agents) ? message.agents : [])
1298
+ .slice(0, MAX_AGENT_STATUS_ENTRIES)
1299
+ .map((agent) => ({
1300
+ paneId: text(agent?.paneId, 64),
1301
+ workspaceId: text(agent?.workspaceId, 64),
1302
+ agent: text(agent?.agent, 32),
1303
+ title: text(agent?.title, 64),
1304
+ status: text(agent?.status, 16),
1305
+ focused: agent?.focused === true,
1306
+ }));
1307
+ const payload = {
1308
+ type: 'agent_status',
1309
+ counts,
1310
+ total: Number.isFinite(message.total) ? Math.max(0, Math.trunc(message.total)) : agents.length,
1311
+ agents,
1312
+ };
1313
+ for (const clientId of host.clients) {
1314
+ const client = this.clients.get(clientId);
1315
+ if (client) jsonSend(client.ws, payload);
1316
+ }
1317
+ }
1318
+
1265
1319
  /**
1266
1320
  * Close every existing session belonging to `deviceId` on `host`.
1267
1321
  *