newmark-agent 0.5.4 → 0.5.7

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.
@@ -46,8 +46,14 @@ function providerStreamTimeoutError(timeoutMs) {
46
46
  * Read one SSE chunk with both user cancellation and an inactivity deadline.
47
47
  * Cancelling the reader is important: rejecting the race alone leaves the
48
48
  * provider socket alive and lets later requests accumulate behind it.
49
+ *
50
+ * timeoutMs defaults to 0 (no stream idle deadline), matching the request-
51
+ * level DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS = 0 and the Android client's
52
+ * readTimeout(0) / SSE_IDLE_TIMEOUT_MS = 0L. A caller that still wants an
53
+ * inactivity cap passes an explicit positive value (the recovery verify
54
+ * passes 50ms to prove reader cancellation).
49
55
  */
50
- async function readProviderStreamChunk(reader, signal, timeoutMs = 30_000) {
56
+ async function readProviderStreamChunk(reader, signal, timeoutMs = 0) {
51
57
  if (signal.aborted)
52
58
  throw providerAbortError(signal);
53
59
  let timer;
@@ -56,9 +62,14 @@ async function readProviderStreamChunk(reader, signal, timeoutMs = 30_000) {
56
62
  onAbort = () => reject(providerAbortError(signal));
57
63
  signal.addEventListener('abort', onAbort, { once: true });
58
64
  });
59
- const timeoutPromise = new Promise((_, reject) => {
60
- timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
61
- });
65
+ // timeoutMs <= 0 means no inactivity deadline (unlimited). setTimeout with
66
+ // 0 would fire on the next tick, so we only arm the timer for positive
67
+ // values and race a never-settling promise otherwise.
68
+ const timeoutPromise = timeoutMs > 0
69
+ ? new Promise((_, reject) => {
70
+ timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
71
+ })
72
+ : new Promise(() => undefined);
62
73
  try {
63
74
  return await Promise.race([reader.read(), abortPromise, timeoutPromise]);
64
75
  }
package/dist/server.js CHANGED
@@ -1298,6 +1298,27 @@ async function handleApi(req, res, body) {
1298
1298
  mobileJson(res, await hostedConversationUiAction({ workspaceId, conversationId }, action, String(params.value || ''), params));
1299
1299
  return;
1300
1300
  }
1301
+ case '/api/queue-action': {
1302
+ // Desktop-renderer queue mutations. The mobile endpoint requires a
1303
+ // pairing token; the renderer shares the same process and is trusted,
1304
+ // so it gets a dedicated unauthenticated route that reuses the same
1305
+ // GUI runtime-pool queueAction path (update/delete/reorder/guide).
1306
+ const params = JSON.parse(body || '{}');
1307
+ const workspaceId = String(params.workspaceId || '');
1308
+ const conversationId = String(params.conversationId || '');
1309
+ const action = String(params.action || '');
1310
+ const allowed = new Set(['queue_enqueue', 'queue_update', 'queue_delete', 'queue_reorder', 'queue_toggle_pause', 'queue_guide']);
1311
+ if (!workspaceId || !conversationId || !allowed.has(action)) {
1312
+ jsonResponse(res, { error: 'workspaceId, conversationId, and a valid queue action are required' }, 400);
1313
+ return;
1314
+ }
1315
+ if (!hostedConversationUiAction) {
1316
+ jsonResponse(res, { error: 'This action requires the GUI-hosted runtime pool' }, 409);
1317
+ return;
1318
+ }
1319
+ jsonResponse(res, await hostedConversationUiAction({ workspaceId, conversationId }, action, String(params.value || ''), params));
1320
+ return;
1321
+ }
1301
1322
  case '/api/mobile/conversation-rename': {
1302
1323
  const params = JSON.parse(body || '{}');
1303
1324
  const workspaceId = String(params.workspaceId || '');