@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/browser-channel.d.ts +59 -0
- package/package.json +11 -2
- package/server/core.mjs +8 -3
- package/server/migrations/009.mjs +8 -0
- package/server/migrations/index.mjs +6 -1
- package/server/viewer.mjs +2 -0
- package/session-follow.d.ts +50 -0
- package/session-follow.mjs +152 -0
- package/viewer/session-view-entry.mjs +1 -76
- package/viewer/session-view.css +6 -0
- package/viewer/session-view.mjs +29 -29
- package/viewer/viewer.mjs +12 -50
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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 (!
|
|
219
|
-
}
|
|
220
|
-
view.update({ live: false })
|
|
221
|
-
|
|
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) {
|