@volter/supercode-teams 0.3.16 → 0.3.18

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": "@volter/supercode-teams",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "type": "module",
5
5
  "description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
6
6
  "license": "MIT",
@@ -62,11 +62,11 @@
62
62
  "@volter/identity": "^0.4.1",
63
63
  "@volter/supercode-calendar": "^0.1.0",
64
64
  "@volter/supercode-frontend": "^0.2.2",
65
- "@volter/supercode-harness-sdk": "^0.3.43",
65
+ "@volter/supercode-harness-sdk": "^0.3.58",
66
66
  "@volter/supercode-health": "^0.1.0",
67
67
  "@volter/supercode-terminal": "^0.2.54",
68
68
  "@volter/supercode-trace": "^0.1.0",
69
- "@volter/teams": "0.2.8",
69
+ "@volter/teams": "0.2.9",
70
70
  "@xterm/addon-serialize": "^0.14.0",
71
71
  "@xterm/headless": "^6.0.0",
72
72
  "protobufjs": "8.8.0",
@@ -3,7 +3,7 @@ import { appAuthentication } from '@volter/teams/server/apps';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { validateListQuery } from '../contracts/index.mjs';
5
5
  import { cursorHash, cursorToken, liveBindings } from './catalog.mjs';
6
- import { writeSse } from '@volter/teams/server/events';
6
+ import { sseRoom, writeSse } from '@volter/teams/server/events';
7
7
 
8
8
  function openStream(api, req, res, auth, requestId, fail) {
9
9
  const count = api.openStreams.get(auth.credential_id) ?? 0;
@@ -32,9 +32,12 @@ function openStream(api, req, res, auth, requestId, fail) {
32
32
  if (!closed) writeSse(res, { type, reason, terminal: true });
33
33
  close();
34
34
  },
35
- write(event) {
35
+ // Waits for the response to flush past the backlog cap (sseRoom); only a reader that takes nothing is slow.
36
+ async write(event) {
36
37
  if (closed) return false;
37
- if (res.writableLength + Buffer.byteLength(JSON.stringify(event)) > 1048576) {
38
+ const room = await sseRoom(res, Buffer.byteLength(JSON.stringify(event)));
39
+ if (closed || room === 'closed') return false;
40
+ if (room === 'slow') {
38
41
  this.terminal('resync_required', 'slow_reader');
39
42
  return false;
40
43
  }
@@ -122,14 +125,14 @@ export async function handleSessionEvents({ api, req, res, auth, id, url, reques
122
125
  if (delivered > team.journal_revision || delivered < team.journal_revision && (retained.first_revision === null || delivered < retained.first_revision - 1)) fail(409, 'resync_required', 'event cursor is outside retained journal');
123
126
  }
124
127
  const stream = openStream(api, req, res, auth, requestId, fail);
125
- if (after === null) stream.write({ type: 'snapshot', cursor: initial.event_cursor, revision: initial.revision, data: initial });
128
+ if (after === null && !await stream.write({ type: 'snapshot', cursor: initial.event_cursor, revision: initial.revision, data: initial })) return;
126
129
  const enqueue = streamQueue(stream, async () => {
127
130
  if(!auth.bound&&!await appAuthentication(api,auth))fail(401,'authentication_required','app authorization changed');
128
131
  // This worker operation reads current credential, membership, groups and every constituent ACL together.
129
132
  await api.store.authorizedSession(sessionArgs(api, auth, sessionId));
130
133
  const replay = await api.store.query('SELECT revision,kind FROM journal WHERE team_id=? AND session_id=? AND revision>? ORDER BY revision', [auth.team_id, sessionId, delivered]);
131
134
  for (const event of replay) {
132
- if (!stream.write(event.kind === 'delete'
135
+ if (!await stream.write(event.kind === 'delete'
133
136
  ? { type: 'session_deleted', cursor: String(event.revision), revision: event.revision, session_id: sessionId }
134
137
  : { type: 'session_changed', cursor: String(event.revision), revision: event.revision, session_id: sessionId, reason: event.kind === 'metadata' ? 'metadata' : 'capture', read_required: true })) break;
135
138
  delivered = event.revision;
@@ -226,7 +229,7 @@ export async function handleCatalogEvents({ api, req, res, auth, url, requestId,
226
229
  // A binding's observed_at moves with every heartbeat; only a change of what the window says is a reset.
227
230
  const digest = createHash('sha256').update(JSON.stringify(page.data, (key, value) => key === 'bindings' && Array.isArray(value) ? value.map(({ observed_at: _seen, ...binding }) => binding) : value)).digest('hex');
228
231
  const cursor = `${page.revision}:${page.authEpoch}:${digest}`;
229
- if (cursor !== lastCursor && stream.write({ type: 'catalog_reset', cursor, revision: page.revision, data: page.data })) lastCursor = cursor;
232
+ if (cursor !== lastCursor && await stream.write({ type: 'catalog_reset', cursor, revision: page.revision, data: page.data })) lastCursor = cursor;
230
233
  });
231
234
  // Subscribe before taking the first snapshot, and serialize resets so older windows cannot arrive last.
232
235
  stream.cleanup(api.events.subscribe(`team:${auth.team_id}`, enqueue));