agentgui 1.0.940 → 1.0.942

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.
@@ -27,14 +27,17 @@ function withToken(url) {
27
27
  return url + (url.includes('?') ? '&' : '?') + 'token=' + encodeURIComponent(tok);
28
28
  }
29
29
 
30
+ function lsGet(k) { try { return localStorage.getItem(k); } catch { return null; } }
31
+ function lsSet(k, v) { try { localStorage.setItem(k, v); } catch {} }
32
+
30
33
  export function getBackend() {
31
34
  const u = new URL(location.href);
32
35
  const fromQs = u.searchParams.get('backend');
33
- if (fromQs) { localStorage.setItem(KEY, fromQs); return fromQs; }
34
- return localStorage.getItem(KEY) || DEFAULT_BACKEND;
36
+ if (fromQs) { lsSet(KEY, fromQs); return fromQs; }
37
+ return lsGet(KEY) || DEFAULT_BACKEND;
35
38
  }
36
39
 
37
- export function setBackend(url) { localStorage.setItem(KEY, url); }
40
+ export function setBackend(url) { lsSet(KEY, url); }
38
41
 
39
42
  export async function probeBackend(base) {
40
43
  try {
@@ -262,25 +265,31 @@ export async function* streamChat(base, { model, messages, signal, agentId, resu
262
265
  const sessionId = started?.sessionId;
263
266
  if (!sessionId) { yield { type: 'error', error: 'no sessionId from server' }; return; }
264
267
 
268
+ const finish = () => { done = true; if (resolveWait) { resolveWait(); resolveWait = null; } };
269
+
265
270
  const unsub = addSessionListener(sessionId, (ev) => {
266
271
  if (ev.type === 'streaming_progress') {
267
272
  const block = ev.block;
268
273
  if (block?.type === 'text' && block.text) push({ type: 'text', text: block.text });
269
274
  else if (block?.type === 'tool_use') push({ type: 'tool', block });
270
- else if (block?.type === 'tool_result') push({ type: 'tool', block });
275
+ else if (block?.type === 'tool_result') push({ type: 'tool_result', block });
271
276
  else if (block?.type === 'result') push({ type: 'result', block });
272
277
  } else if (ev.type === 'streaming_complete') {
273
- done = true;
274
- if (resolveWait) { resolveWait(); resolveWait = null; }
278
+ finish();
275
279
  } else if (ev.type === 'streaming_error') {
276
280
  errored = ev.error || 'streaming error';
277
- done = true;
278
- if (resolveWait) { resolveWait(); resolveWait = null; }
281
+ finish();
279
282
  }
280
283
  });
281
284
 
282
- // Wire AbortSignal to chat.cancel.
283
- const onAbort = () => { wsCall(base, 'chat.cancel', { sessionId }).catch(() => {}); };
285
+ // If the websocket drops mid-stream, streaming_complete will never arrive —
286
+ // surface an error and end the iterator instead of hanging forever.
287
+ const onWs = (s) => { if ((s === 'closed' || s === 'error') && !done) { errored = errored || 'connection lost during stream'; finish(); } };
288
+ const unsubWs = onWsStatus ? onWsStatus(onWs) : null;
289
+
290
+ // Wire AbortSignal to chat.cancel — and end the iterator immediately so the
291
+ // caller's busy state clears even if the server never emits a final event.
292
+ const onAbort = () => { wsCall(base, 'chat.cancel', { sessionId }).catch(() => {}); finish(); };
284
293
  if (signal) {
285
294
  if (signal.aborted) onAbort();
286
295
  else signal.addEventListener('abort', onAbort, { once: true });
@@ -297,6 +306,7 @@ export async function* streamChat(base, { model, messages, signal, agentId, resu
297
306
  if (errored) yield { type: 'error', error: errored };
298
307
  } finally {
299
308
  unsub();
309
+ if (typeof unsubWs === 'function') unsubWs();
300
310
  if (signal) signal.removeEventListener?.('abort', onAbort);
301
311
  }
302
312
  }