@volter/teams 0.1.34 → 0.1.36

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/viewer/viewer.mjs CHANGED
@@ -5,6 +5,7 @@
5
5
  // showing where it is.
6
6
  import { attachBrowserPane, openBrowserMachine, openBrowserSession, sendBrowserTurn } from './channel.mjs';
7
7
  import { StreamRpc } from './stream-rpc.mjs';
8
+ import { followTeamSession } from './session-follow.mjs';
8
9
 
9
10
  const $ = (id) => document.getElementById(id);
10
11
  const decoder = new TextDecoder();
@@ -155,7 +156,6 @@ async function showSession(session, control) {
155
156
  } catch (error) { status(`Not delivered: ${error.message}`); return false; } finally { view.update({ sending: false }); }
156
157
  },
157
158
  });
158
- const headers = async () => ({ Authorization: `Bearer ${await freshToken()}` });
159
159
  applyLabels = () => view.update({ ...labels });
160
160
  applyLabels();
161
161
  const HARNESSES = { 'claude-code': 'Claude Code', codex: 'Codex', gemini: 'Gemini CLI', goose: 'Goose', opencode: 'OpenCode', pi: 'Pi', grok: 'Grok' };
@@ -170,56 +170,18 @@ async function showSession(session, control) {
170
170
  scope: control ? 'control' : 'view', working: body.working ?? null,
171
171
  } }, '*');
172
172
  };
173
- let reading = null;
174
- const read = () => (reading ??= (async () => {
175
- try {
176
- const response = await fetch(`${base}/messages?tail=300`, { headers: await headers() });
177
- if (!response.ok) throw Object.assign(new Error(`reading the session failed (${response.status})`), { stage: 'read', status: response.status });
178
- const body = await response.json();
179
- apply(body);
180
- return body;
181
- } finally { reading = null; }
182
- })());
183
- // The snapshot is read whole (a busy session's is over the follow stream's 1 MB buffer, which ended the stream
184
- // before it was sent); the follow stream then carries only the events after that snapshot's cursor, and each one
185
- // means "read again". It ends when the token does or the connection drops, and is reopened from where it left off.
186
- let cursor = null;
187
- for (;;) {
188
- try {
189
- if (!cursor) cursor = String((await read()).event_cursor ?? '') || null;
190
- const response = await fetch(`${base}/events?${cursor ? `after=${encodeURIComponent(cursor)}` : 'tail=1'}`, { headers: await headers() });
191
- if (!response.ok) throw Object.assign(new Error(`following the session failed (${response.status})`), { stage: 'follow', status: response.status });
192
- view.update({ live: true });
193
- status('');
194
- const reader = response.body.getReader();
195
- let buffer = '';
196
- for (;;) {
197
- const { value, done } = await reader.read();
198
- if (done) break;
199
- buffer += decoder.decode(value, { stream: true });
200
- let end;
201
- while ((end = buffer.indexOf('\n\n')) >= 0) {
202
- const block = buffer.slice(0, end);
203
- buffer = buffer.slice(end + 2);
204
- const data = block.split('\n').filter((line) => line.startsWith('data:')).map((line) => line.slice(5).trimStart()).join('\n');
205
- if (!data) continue;
206
- const event = JSON.parse(data);
207
- if (event.cursor) cursor = event.cursor;
208
- // The server ends a stream it can no longer resume (a slow reader, a retired cursor): start from a snapshot.
209
- if (event.terminal && event.type === 'resync_required') cursor = null;
210
- if (event.type === 'snapshot') apply(event.data);
211
- else if (event.type === 'session_changed') read().catch((error) => failed('read', error, error.status ?? null));
212
- else if (event.type === 'session_deleted') { view.update({ live: false, status: 'error', error: 'This session was deleted.' }); return; }
213
- }
214
- }
215
- } catch (error) {
216
- failed(error.stage ?? 'follow', error, error.status ?? null);
173
+ // The session, live: its snapshot, then its follow stream (session-follow.mjs, as a workplace drawing it itself uses).
174
+ followTeamSession({
175
+ origin: location.origin, teamId: session.team, sessionId: session.id, token: freshToken,
176
+ onSnapshot: apply,
177
+ onLive: (live) => { view.update({ live }); if (live) status(''); },
178
+ onError: ({ stage, status: code, message, snapshot }) => {
179
+ failed(stage, message, code);
217
180
  // Nothing drawn yet: the transcript says why instead of loading forever.
218
- if (!cursor) view.update({ status: 'error', error: error.message });
219
- }
220
- view.update({ live: false });
221
- await new Promise((resolve) => setTimeout(resolve, 2000));
222
- }
181
+ if (!snapshot) view.update({ status: 'error', error: message });
182
+ },
183
+ onDeleted: () => view.update({ live: false, status: 'error', error: 'This session was deleted.' }),
184
+ });
223
185
  }
224
186
 
225
187
  async function showPane(connect, key, control) {