mixdog 1.4.0 → 1.4.1

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.
Files changed (236) hide show
  1. package/package.json +1 -1
  2. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  3. package/scripts/gate-local.mjs +12 -3
  4. package/scripts/latest-guard.mjs +21 -9
  5. package/scripts/lib/isolated-test-env.mjs +10 -5
  6. package/scripts/lib/run-node-tests.mjs +37 -37
  7. package/scripts/lib/scoped-temp.d.mts +16 -0
  8. package/scripts/lib/scoped-temp.mjs +123 -0
  9. package/scripts/lib/stale-temp.mjs +44 -0
  10. package/scripts/post-release-check.mjs +1 -6
  11. package/scripts/pptx-kit-index.mjs +17 -4
  12. package/scripts/suite-health.mjs +10 -1
  13. package/scripts/test-data-dir.mjs +10 -7
  14. package/src/app.mjs +4 -0
  15. package/src/defaults/shipped-history.json +5 -0
  16. package/src/defaults/skills/browser-use/SKILL.md +6 -4
  17. package/src/defaults/skills/computer-use/SKILL.md +4 -2
  18. package/src/lib/rules-builder.cjs +29 -12
  19. package/src/output-styles/common.md +2 -2
  20. package/src/output-styles/simple.md +1 -1
  21. package/src/rules/agent/AGENT.md +5 -4
  22. package/src/rules/lead/LEAD.md +10 -5
  23. package/src/rules/shared/10-tool-workflow.md +3 -1
  24. package/src/rules/shared/45-destructive.md +0 -2
  25. package/src/rules/shared/50-execution.md +2 -0
  26. package/src/rules/shared/70-delivery.md +7 -9
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
  28. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  29. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +1 -1
  30. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  31. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
  32. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +11 -0
  33. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  34. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
  35. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  36. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  37. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  40. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  41. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  43. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  44. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  46. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
  47. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  48. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  49. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  50. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  51. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  60. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  62. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  63. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  64. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  65. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  66. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
  67. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
  68. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
  70. package/src/runtime/agent/orchestrator/providers/registry.mjs +12 -0
  71. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  72. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -6
  73. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
  75. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  76. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  77. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +8 -2
  78. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  79. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  80. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  81. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  82. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  83. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  84. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
  85. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
  86. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  87. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
  89. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  90. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +7 -2
  91. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  92. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  93. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  94. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +2 -2
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  97. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  98. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  99. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  100. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  101. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
  102. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +61 -6
  103. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -8
  104. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
  105. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -3
  106. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  107. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  108. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
  109. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  110. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  111. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  112. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  113. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  114. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  115. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  116. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  118. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  120. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  121. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  123. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  124. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
  125. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
  126. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  127. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  129. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  130. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  131. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  132. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  134. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  135. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  136. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  137. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
  138. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  139. package/src/runtime/channels/lib/whisper-port.mjs +10 -1
  140. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  141. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  142. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  143. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  144. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  145. package/src/runtime/local-provider/server-process.mjs +2 -1
  146. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  147. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  148. package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
  149. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  150. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  151. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  152. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  153. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  154. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  155. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  156. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  157. package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
  158. package/src/runtime/office/quality/design-review.mjs +1 -5
  159. package/src/runtime/office/quality/document-brief.mjs +1 -5
  160. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  161. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  162. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  163. package/src/runtime/shared/defaults-separation.mjs +1 -4
  164. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  165. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  166. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  167. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  168. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  169. package/src/runtime/shared/llm/cost.mjs +5 -1
  170. package/src/runtime/shared/loopback-listener.mjs +25 -2
  171. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  172. package/src/runtime/shared/model-route-chain.mjs +22 -4
  173. package/src/runtime/shared/open-url.mjs +10 -0
  174. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  175. package/src/runtime/shared/session-scratch.mjs +37 -0
  176. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  177. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  178. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  179. package/src/runtime/shared/tool-surface.mjs +3 -0
  180. package/src/runtime/shared/tool-work-units.mjs +2 -0
  181. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  182. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  183. package/src/runtime/shared/worktrees/git.mjs +15 -0
  184. package/src/runtime/shared/worktrees/manager.mjs +87 -38
  185. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  186. package/src/runtime/visualize-bridge/client.mjs +2 -0
  187. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  188. package/src/session-runtime/agent-task-sessions.mjs +152 -63
  189. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  190. package/src/session-runtime/boot/tools.mjs +5 -1
  191. package/src/session-runtime/bot-status.mjs +12 -6
  192. package/src/session-runtime/bot-target.mjs +38 -0
  193. package/src/session-runtime/channel-config-api.mjs +6 -1
  194. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  195. package/src/session-runtime/conversation-import/discovery.mjs +271 -25
  196. package/src/session-runtime/conversation-import/service.mjs +26 -5
  197. package/src/session-runtime/handoff-attachments.mjs +82 -31
  198. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  199. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  200. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  201. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
  202. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  203. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  204. package/src/session-runtime/model-route/set-route.mjs +11 -2
  205. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  206. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  207. package/src/session-runtime/provider-models.mjs +4 -2
  208. package/src/session-runtime/resource-recall-api.mjs +6 -1
  209. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  210. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  211. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  212. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  213. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  214. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  215. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  216. package/src/session-runtime/services/provider-admin.mjs +2 -1
  217. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  218. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  219. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
  220. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  221. package/src/session-runtime/workspace/transitions.mjs +25 -0
  222. package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
  223. package/src/standalone/daemon-session-control.mjs +18 -2
  224. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  225. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  226. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  227. package/src/tui/app/app-view/floating-panel.jsx +7 -4
  228. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  229. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  230. package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
  231. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  232. package/src/tui/app/provider-setup-picker.mjs +9 -5
  233. package/src/tui/components/QuestionPanel.jsx +13 -4
  234. package/src/tui/dist/index.mjs +75 -28
  235. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  236. package/src/ui/statusline-agents.mjs +1 -3
@@ -184,6 +184,7 @@ export async function sendCompatChat(provider, messages, useModel, tools, opts)
184
184
  signal: attemptSignal,
185
185
  label: provider.name,
186
186
  providerName: provider.name,
187
+ sessionId: opts.sessionId,
187
188
  onStreamDelta: opts.onStreamDelta,
188
189
  onToolCall: opts.onToolCall,
189
190
  onTextDelta: opts.onTextDelta,
@@ -5,7 +5,14 @@
5
5
  */
6
6
  import { mergeToolCallDelta } from './tool-call-acc.mjs';
7
7
  import { reportStreamItemStart } from '../lib/send-callbacks.mjs';
8
- import { appendText, drainLeakedCalls, relayText, reportProgress, reportTransport } from './stream-state.mjs';
8
+ import {
9
+ appendText,
10
+ drainLeakedCalls,
11
+ relayText,
12
+ reportLifecycle,
13
+ reportProgress,
14
+ reportTransport,
15
+ } from './stream-state.mjs';
9
16
  import { chatContentParts } from '../openai-compat-reasoning-field.mjs';
10
17
 
11
18
  function appendReasoning(state, text, source) {
@@ -50,7 +57,7 @@ export function applyCompatChunk(state, chunk) {
50
57
  const choice = chunk?.choices?.[0];
51
58
  const delta = choice?.delta;
52
59
  if (typeof delta?.role === 'string' && delta.role) {
53
- reportProgress(state, 'semantic');
60
+ reportLifecycle(state, 'semantic');
54
61
  }
55
62
  const contentParts = chatContentParts(delta?.content);
56
63
  if (contentParts) applyContentParts(state, contentParts);
@@ -70,6 +70,13 @@ export function reportProgress(state, kind) {
70
70
  } catch {}
71
71
  }
72
72
 
73
+ /** Chunk metadata without output (a role-only delta): progress once the
74
+ * model has produced output; before that it neither ends the first-output
75
+ * phase nor starts the semantic idle (the chunk already reported transport). */
76
+ export function reportLifecycle(state, kind) {
77
+ if (state.semanticIdleDeadlineAt > 0) reportProgress(state, kind);
78
+ }
79
+
73
80
  /** Text the model exposed: accumulate, refresh the idle deadline, relay live. */
74
81
  export function appendText(state, text) {
75
82
  state.content += text;
@@ -10,6 +10,7 @@ import {
10
10
  PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
11
11
  PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
12
12
  createTimeoutSignal,
13
+ providerFirstOutputTimeoutMs,
13
14
  } from '../stall-policy.mjs';
14
15
  import { TIMEOUT_PHASE } from '../../../shared/provider-timeout-phase.mjs';
15
16
  import {
@@ -28,6 +29,7 @@ import {
28
29
  toolWorkStarted,
29
30
  } from './openai-compat-chat-stream/stream-state.mjs';
30
31
  import { applyCompatChunk } from './openai-compat-chat-stream/chunk-events.mjs';
32
+ import { streamFirstOutputWindow } from './lib/first-output-window.mjs';
31
33
  import { incompleteStreamError, settleCompatStream } from './openai-compat-chat-stream/stream-result.mjs';
32
34
 
33
35
  export async function consumeCompatChatCompletionStream(
@@ -45,11 +47,20 @@ export async function consumeCompatChatCompletionStream(
45
47
  semanticIdleTimeoutMs,
46
48
  firstByteTimeoutMs,
47
49
  leakedToolCallRecovery = true,
50
+ sessionId = null,
48
51
  } = {}
49
52
  ) {
50
53
  const idleOverrideEnabled = Number.isFinite(Number(semanticIdleTimeoutMs)) && Number(semanticIdleTimeoutMs) > 0;
51
54
  const idleEnabled = idleOverrideEnabled || PROVIDER_SSE_IDLE_WATCHDOG_ENABLED;
52
55
  const idleMs = idleOverrideEnabled ? Number(semanticIdleTimeoutMs) : PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
56
+ // Until the first semantic output the logical request's first-output window
57
+ // bounds the stream; a request that already had output runs under the
58
+ // semantic idle from the stream's opening.
59
+ const firstOutput = streamFirstOutputWindow({
60
+ sessionId,
61
+ onStreamDelta,
62
+ timeoutMs: providerFirstOutputTimeoutMs(idleMs),
63
+ });
53
64
  const state = createCompatStreamState({
54
65
  providerName,
55
66
  knownToolNames,
@@ -60,6 +71,7 @@ export async function consumeCompatChatCompletionStream(
60
71
  onStreamItemStart,
61
72
  leakedToolCallRecovery,
62
73
  });
74
+ if (firstOutput.outputSeen) state.semanticIdleDeadlineAt = Date.now() + idleMs;
63
75
  reportTransport(state);
64
76
  const iterator = stream[Symbol.asyncIterator]();
65
77
  let iteratorDone = false;
@@ -72,9 +84,8 @@ export async function consumeCompatChatCompletionStream(
72
84
  while (true) {
73
85
  const { value: chunk, done } = await nextAsyncWithWatchdog(iterator, {
74
86
  signal: state.sawFirstEvent ? signal : firstByteTimeout.signal,
75
- idleMs,
76
- idleDeadlineAt: state.semanticIdleDeadlineAt,
77
- idleEnabled: state.sawFirstEvent && idleEnabled && state.semanticIdleDeadlineAt > 0,
87
+ ...firstOutput.idleWatch(state.semanticIdleDeadlineAt, idleMs),
88
+ idleEnabled: state.sawFirstEvent && idleEnabled,
78
89
  idleLabel: `${label} SSE idle`,
79
90
  streamLabel: label,
80
91
  emittedToolCall: () => toolWorkStarted(state),
@@ -147,7 +147,8 @@ function settleMaxOutputIncomplete(event, state, ctx, reason) {
147
147
  function onCreated(event, state, ctx) {
148
148
  if (event.response?.model) state.model = event.response.model;
149
149
  if (event.response?.id) state.responseId = event.response.id;
150
- signal(ctx, 'semantic');
150
+ // Metadata, not output: progress only once the stream has seen output.
151
+ ctx.onLifecycle?.('semantic');
151
152
  }
152
153
 
153
154
  function onOutputTextDelta(event, state, ctx) {
@@ -196,7 +197,8 @@ function onOutputItemAdded(event, state, ctx) {
196
197
  state.toolInFlight = true;
197
198
  }
198
199
  reportStreamItemStart(ctx.onStreamItemStart, responsesItemStart(event.item));
199
- signal(ctx, state.toolInFlight ? 'tool' : 'semantic');
200
+ if (state.toolInFlight) signal(ctx, 'tool');
201
+ else ctx.onLifecycle?.('semantic');
200
202
  }
201
203
 
202
204
  // A tool call's args (or custom-tool input) are streaming — mark tool work
@@ -241,6 +241,7 @@ async function streamCompatResponses({ provider, params, label, signal, opts, he
241
241
  return await consumeCompatResponsesStream(stream, {
242
242
  signal: attemptSignal,
243
243
  label,
244
+ sessionId: opts.sessionId,
244
245
  onStreamDelta: opts.onStreamDelta,
245
246
  onToolCall: opts.onToolCall,
246
247
  onTextDelta: opts.onTextDelta,
@@ -168,9 +168,11 @@ export async function nextAsyncWithWatchdog(
168
168
  }
169
169
  // The SDK JSON-decodes each frame inside next(): a SyntaxError here is a
170
170
  // malformed wire frame, typed at this boundary and nowhere else.
171
- iterator.next().then(resolve, (error) =>
172
- reject(error instanceof SyntaxError ? malformedCompatStreamError(streamLabel || idleLabel, error) : error)
173
- );
171
+ iterator
172
+ .next()
173
+ .then(resolve, (error) =>
174
+ reject(error instanceof SyntaxError ? malformedCompatStreamError(streamLabel || idleLabel, error) : error)
175
+ );
174
176
  });
175
177
  } catch (err) {
176
178
  if (idleTimedOut)
@@ -7,6 +7,7 @@ import {
7
7
  PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
8
8
  PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
9
9
  createTimeoutSignal,
10
+ providerFirstOutputTimeoutMs,
10
11
  } from '../stall-policy.mjs';
11
12
  import {
12
13
  closeCompatStream,
@@ -23,6 +24,7 @@ import {
23
24
  parseCompletedToolCallArgumentsJson,
24
25
  } from './lib/openai-tool-args.mjs';
25
26
  import { handleCompatResponsesStreamEvent } from './openai-compat-responses-events.mjs';
27
+ import { streamFirstOutputWindow } from './lib/first-output-window.mjs';
26
28
  import {
27
29
  attachPartialState,
28
30
  createResponsesLeakRelay,
@@ -56,6 +58,7 @@ export async function consumeCompatResponsesStream(
56
58
  responseOutputText,
57
59
  knownToolNames,
58
60
  semanticIdleTimeoutMs,
61
+ sessionId = null,
59
62
  } = {}
60
63
  ) {
61
64
  try {
@@ -68,13 +71,23 @@ export async function consumeCompatResponsesStream(
68
71
  const idleEnabled = idleOverrideEnabled || PROVIDER_SSE_IDLE_WATCHDOG_ENABLED;
69
72
  // Per-event (last-event-relative) SEMANTIC idle — see the Chat path note.
70
73
  const idleMs = idleOverrideEnabled ? Number(semanticIdleTimeoutMs) : PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
74
+ // Until the first semantic output the logical request's first-output window
75
+ // bounds the stream; a request that already had output runs under the
76
+ // semantic idle from the stream's opening.
77
+ const firstOutput = streamFirstOutputWindow({
78
+ sessionId,
79
+ onStreamDelta,
80
+ timeoutMs: providerFirstOutputTimeoutMs(idleMs),
81
+ });
71
82
  const state = createResponsesStreamState();
83
+ if (firstOutput.outputSeen) state.semanticIdleDeadlineAt = Date.now() + idleMs;
72
84
  const reportProgress = (kind) => {
73
85
  if (kind !== 'transport') state.semanticIdleDeadlineAt = Date.now() + idleMs;
74
86
  try {
75
87
  onStreamDelta?.(kind);
76
88
  } catch {}
77
89
  };
90
+ const reportLifecycle = (kind) => reportProgress(state.semanticIdleDeadlineAt > 0 ? kind : 'transport');
78
91
  let sawFirstEvent = false;
79
92
  const { relayLeakText, flushLeak, leakedCalls } = createResponsesLeakRelay({
80
93
  state,
@@ -88,6 +101,7 @@ export async function consumeCompatResponsesStream(
88
101
  parseResponsesToolCalls,
89
102
  responseOutputText,
90
103
  onStreamDelta: reportProgress,
104
+ onLifecycle: reportLifecycle,
91
105
  onToolCall,
92
106
  onTextDelta,
93
107
  onStreamItemStart,
@@ -98,9 +112,8 @@ export async function consumeCompatResponsesStream(
98
112
  while (true) {
99
113
  const { value: event, done } = await nextAsyncWithWatchdog(iterator, {
100
114
  signal: sawFirstEvent ? signal : firstByteTimeout.signal,
101
- idleMs,
102
- idleDeadlineAt: state.semanticIdleDeadlineAt,
103
- idleEnabled: sawFirstEvent && idleEnabled && state.semanticIdleDeadlineAt > 0,
115
+ ...firstOutput.idleWatch(state.semanticIdleDeadlineAt, idleMs),
116
+ idleEnabled: sawFirstEvent && idleEnabled,
104
117
  idleLabel: `${label} SSE idle`,
105
118
  streamLabel: label,
106
119
  // Unsafe-to-retry once any tool call (native or recovered-leaked)
@@ -131,6 +131,7 @@ export async function sendXaiResponses(provider, messages, useModel, tools, opts
131
131
  return await consumeCompatResponsesStream(stream, {
132
132
  signal: attemptSignal,
133
133
  label: 'xai:responses',
134
+ sessionId: opts.sessionId,
134
135
  onStreamDelta: opts.onStreamDelta,
135
136
  onToolCall: opts.onToolCall,
136
137
  onTextDelta: opts.onTextDelta,
@@ -15,7 +15,7 @@ function outputItemKind(item) {
15
15
  return 'semantic';
16
16
  }
17
17
 
18
- export function createOutputItemEvents({ state, items, meaningful }) {
18
+ export function createOutputItemEvents({ state, items, meaningful, lifecycle }) {
19
19
  const { toolTracker } = state;
20
20
 
21
21
  const onOutputItemAdded = (item) => {
@@ -52,7 +52,10 @@ export function createOutputItemEvents({ state, items, meaningful }) {
52
52
  toolTracker.mark(item);
53
53
  state.toolInFlight = true;
54
54
  }
55
- meaningful(state.toolInFlight ? 'tool' : 'semantic');
55
+ // A tool call starting is model output; an empty reasoning or message
56
+ // item is lifecycle (the silent thinking follows it).
57
+ if (state.toolInFlight) meaningful('tool');
58
+ else lifecycle('semantic');
56
59
  };
57
60
 
58
61
  const onToolInputDelta = (itemId) => {
@@ -7,18 +7,28 @@ import { createOutputItemEvents } from './response-events/output-items.mjs';
7
7
  import { createTerminalFrameEvents } from './response-events/terminal-frames.mjs';
8
8
  import { reportStreamItemStart, responsesItemStart } from '../lib/send-callbacks.mjs';
9
9
 
10
- export function createResponseEvents({ state, items, text, outcome, meaningful, onServerEvent, onStreamItemStart }) {
11
- const output = createOutputItemEvents({ state, items, meaningful });
10
+ export function createResponseEvents({
11
+ state,
12
+ items,
13
+ text,
14
+ outcome,
15
+ meaningful,
16
+ lifecycle,
17
+ onServerEvent,
18
+ onStreamItemStart,
19
+ }) {
20
+ const output = createOutputItemEvents({ state, items, meaningful, lifecycle });
12
21
  const terminal = createTerminalFrameEvents({ state, items, text, outcome, meaningful });
13
22
 
14
23
  const handleEvent = (event) => {
15
24
  if (!event || typeof event.type !== 'string') return;
16
25
  onServerEvent();
17
26
  switch (event.type) {
27
+ // The server's ack is metadata, not model output: it satisfies only the
28
+ // first-server-event deadline (onServerEvent above).
18
29
  case 'response.created':
19
30
  if (event.response?.model) state.model = event.response.model;
20
31
  if (event.response?.id) state.responseId = event.response.id;
21
- meaningful('semantic');
22
32
  break;
23
33
  case 'response.output_text.delta':
24
34
  text.relayLeakText(event.delta || '');
@@ -24,7 +24,8 @@ import { createResponseEvents } from './openai-http-sse-response-state/response-
24
24
  * @param {(call: object) => void} [deps.onToolCall]
25
25
  * @param {(text: string) => void} [deps.onTextDelta]
26
26
  * @param {(kind: string) => void} deps.meaningful semantic progress (re-arms the idle watchdog)
27
- * @param {() => void} deps.onServerEvent first-event → semantic-idle handover
27
+ * @param {(kind: string) => void} [deps.lifecycle] item lifecycle without output: progress only after semantic output
28
+ * @param {() => void} deps.onServerEvent any parsed server event (satisfies the first-server-event deadline)
28
29
  * @param {(detail: object) => void} [deps.onStreamItemStart] a tool call or phased message started
29
30
  */
30
31
  export function createHttpSseResponseState({
@@ -33,6 +34,7 @@ export function createHttpSseResponseState({
33
34
  onTextDelta,
34
35
  onStreamItemStart = null,
35
36
  meaningful,
37
+ lifecycle = () => {},
36
38
  onServerEvent,
37
39
  label,
38
40
  }) {
@@ -40,7 +42,16 @@ export function createHttpSseResponseState({
40
42
  const outcome = createResponseOutcome({ state });
41
43
  const items = createResponseItems({ state, onToolCall });
42
44
  const text = createTextRelay({ state, body, onTextDelta, meaningful, emitToolCall: items.emitToolCall });
43
- const events = createResponseEvents({ state, items, text, outcome, meaningful, onServerEvent, onStreamItemStart });
45
+ const events = createResponseEvents({
46
+ state,
47
+ items,
48
+ text,
49
+ outcome,
50
+ meaningful,
51
+ lifecycle,
52
+ onServerEvent,
53
+ onStreamItemStart,
54
+ });
44
55
 
45
56
  return {
46
57
  handleEvent: events.handleEvent,
@@ -1,7 +1,15 @@
1
1
  /**
2
2
  * openai-http-sse-watchdogs.mjs — liveness gates of one HTTP/SSE fallback
3
3
  * stream: the total/external abort relay, the fixed first-server-event
4
- * deadline, the SEMANTIC idle watchdog, and the cancellable reader.read().
4
+ * deadline, the first-OUTPUT window, the SEMANTIC idle watchdog, and the
5
+ * cancellable reader.read().
6
+ *
7
+ * Until the model's first semantic output (reasoning, text, a tool call) only
8
+ * the first-server-event deadline and the first-output window run: server
9
+ * metadata (response.created / in_progress, an empty reasoning or message
10
+ * item) and keepalives neither end that phase nor start the semantic-idle
11
+ * clock, so a model may think silently longer than the idle window. From the
12
+ * first semantic output on, the semantic idle owns the stream.
5
13
  *
6
14
  * After headerTimeout.cleanup() the in-flight fetch no longer carries a live
7
15
  * signal, so a totalTimeout / external abort that fires during a pending
@@ -17,8 +25,10 @@ import {
17
25
  PROVIDER_FIRST_BYTE_TIMEOUT_MS,
18
26
  PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
19
27
  PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
28
+ providerFirstOutputTimeoutMs,
20
29
  streamStalledError,
21
30
  } from '../stall-policy.mjs';
31
+ import { streamFirstOutputWindow } from './lib/first-output-window.mjs';
22
32
 
23
33
  function positiveOverride(value) {
24
34
  const ms = Number(value);
@@ -31,8 +41,18 @@ function positiveOverride(value) {
31
41
  * @param {ReadableStreamDefaultReader} deps.reader
32
42
  * @param {{ signal?: AbortSignal, cleanup(): void }} deps.totalTimeout
33
43
  * @param {() => object} deps.stallPartial streamed partial state attached to a semantic-idle stall
44
+ * @param {string|null} [deps.sessionId] the session whose logical request this stream serves
45
+ * @param {Function} [deps.onStreamDelta] the stream's progress callback; without it the stream is a fork of the request
34
46
  */
35
- export function createHttpSseWatchdogs({ opts, reader, totalTimeout, stallPartial, label: LABEL }) {
47
+ export function createHttpSseWatchdogs({
48
+ opts,
49
+ reader,
50
+ totalTimeout,
51
+ stallPartial,
52
+ label: LABEL,
53
+ sessionId = null,
54
+ onStreamDelta,
55
+ }) {
36
56
  let abortReason = null;
37
57
  let pendingReadReject = null;
38
58
  const rejectPendingRead = (err) => {
@@ -58,9 +78,46 @@ export function createHttpSseWatchdogs({ opts, reader, totalTimeout, stallPartia
58
78
  else totalTimeout.signal.addEventListener('abort', onTotalAbort, { once: true });
59
79
  }
60
80
 
61
- // Initial wait is governed only by the fixed first-server-event policy.
62
- // Semantic idle begins after a parsed server event, so a lower
63
- // semantic-idle override cannot shorten this first-event wait.
81
+ // SEMANTIC idle watchdog: reset ONLY on meaningful() (text/reasoning/tool
82
+ // deltas), never on raw bytes/keepalive frames, so a stream that emits some
83
+ // deltas then goes silent trips a short, named terminal failure instead of
84
+ // hanging until the 30-min agent watchdog. Disablable via the shared env.
85
+ let semanticIdleTimer = null;
86
+ const semanticIdleOverrideMs = positiveOverride(opts?._semanticIdleTimeoutMs);
87
+ const semanticIdleMs = semanticIdleOverrideMs || PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
88
+ const semanticIdleEnabled = semanticIdleOverrideMs > 0 || PROVIDER_SSE_IDLE_WATCHDOG_ENABLED;
89
+ // FIRST-OUTPUT window of the logical request this stream serves, retired by
90
+ // the first semantic output. Metadata-only streams (repeated
91
+ // response.in_progress, keepalive/ping frames, unknown event types) cannot
92
+ // refresh it, so a wedge that never produces a token still ends here.
93
+ const firstOutputOverrideMs = positiveOverride(opts?._firstOutputTimeoutMs);
94
+ const firstOutput = streamFirstOutputWindow({
95
+ sessionId,
96
+ onStreamDelta,
97
+ timeoutMs: firstOutputOverrideMs || providerFirstOutputTimeoutMs(semanticIdleMs),
98
+ });
99
+ const firstOutputEnabled = firstOutputOverrideMs > 0 || semanticIdleEnabled;
100
+
101
+ // A stalled stream fails with the named stall error, carrying the streamed
102
+ // partial state so the agent loop can accept a wedged FINAL no-tool summary
103
+ // as a successful partial-final instead of dropping the result.
104
+ // pendingToolUse gates out any mid-flight tool call.
105
+ const abortStalled = (timeoutMs) => {
106
+ const partial = stallPartial();
107
+ const err = streamStalledError(LABEL, timeoutMs, { emittedToolCall: partial.emittedToolCall });
108
+ try {
109
+ err.partialContent = partial.content;
110
+ err.partialToolCalls = partial.toolCalls;
111
+ err.pendingToolUse = partial.pendingToolUse;
112
+ err.partialModel = partial.model;
113
+ } catch {
114
+ /* best-effort enrichment */
115
+ }
116
+ abortStream(err);
117
+ };
118
+
119
+ // Initial wait for any server event is governed by the fixed
120
+ // first-server-event policy; a lower semantic-idle override cannot shorten it.
64
121
  let firstServerEventTimer = null;
65
122
  const firstServerEventMs = positiveOverride(opts?._firstServerEventTimeoutMs) || PROVIDER_FIRST_BYTE_TIMEOUT_MS;
66
123
  const clearFirstServerEvent = () => {
@@ -82,15 +139,15 @@ export function createHttpSseWatchdogs({ opts, reader, totalTimeout, stallPartia
82
139
  firstServerEventTimer.unref?.();
83
140
  } catch {}
84
141
  };
142
+ // Until the stream's first output the request's first-output window bounds
143
+ // it. A request that already had output has none: its stream runs under the
144
+ // semantic idle from its first server event (noteServerEvent).
145
+ const armFirstOutput = () => {
146
+ if (!firstOutput.outputSeen && firstOutputEnabled && firstOutput.timeoutMs > 0) {
147
+ firstOutput.arm(() => abortStalled(firstOutput.timeoutMs));
148
+ }
149
+ };
85
150
 
86
- // SEMANTIC idle watchdog: reset ONLY on meaningful() (text/reasoning/tool
87
- // deltas), never on raw bytes/keepalive frames, so a stream that emits some
88
- // deltas then goes silent trips a short, named terminal failure instead of
89
- // hanging until the 30-min agent watchdog. Disablable via the shared env.
90
- let semanticIdleTimer = null;
91
- const semanticIdleOverrideMs = positiveOverride(opts?._semanticIdleTimeoutMs);
92
- const semanticIdleMs = semanticIdleOverrideMs || PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
93
- const semanticIdleEnabled = semanticIdleOverrideMs > 0 || PROVIDER_SSE_IDLE_WATCHDOG_ENABLED;
94
151
  const clearSemanticIdle = () => {
95
152
  if (semanticIdleTimer) {
96
153
  clearTimeout(semanticIdleTimer);
@@ -100,43 +157,22 @@ export function createHttpSseWatchdogs({ opts, reader, totalTimeout, stallPartia
100
157
  const armSemanticIdle = () => {
101
158
  if (!semanticIdleEnabled || !(semanticIdleMs > 0)) return;
102
159
  clearSemanticIdle();
103
- semanticIdleTimer = setTimeout(() => {
104
- const partial = stallPartial();
105
- const err = streamStalledError(LABEL, semanticIdleMs, { emittedToolCall: partial.emittedToolCall });
106
- // Partial-final recovery: attach the streamed partial state so the
107
- // agent loop can accept a wedged FINAL no-tool summary as a successful
108
- // partial-final instead of dropping the result. pendingToolUse gates
109
- // out any mid-flight tool call.
110
- try {
111
- err.partialContent = partial.content;
112
- err.partialToolCalls = partial.toolCalls;
113
- err.pendingToolUse = partial.pendingToolUse;
114
- err.partialModel = partial.model;
115
- } catch {
116
- /* best-effort enrichment */
117
- }
118
- abortStream(err);
119
- }, semanticIdleMs);
160
+ semanticIdleTimer = setTimeout(() => abortStalled(semanticIdleMs), semanticIdleMs);
120
161
  try {
121
162
  semanticIdleTimer.unref?.();
122
163
  } catch {}
123
164
  };
124
- // One-shot latch for the first-server-event → semantic-idle handover. Once
125
- // the FIRST real SSE server event arrives, the fixed initial deadline is
126
- // satisfied and semantic-idle ownership begins. Arming on every parsed event
127
- // let a metadata-only stream (repeated response.in_progress, keepalive/ping
128
- // frames, unknown event types) refresh the watchdog forever without
129
- // producing a single token. Only meaningful progress re-arms it from then on.
130
- let semanticIdleArmed = false;
165
+ // A parsed server event satisfies the first-server-event deadline and, for a
166
+ // request that already had output, starts the semantic idle (once: only
167
+ // output re-arms it).
131
168
  const noteServerEvent = () => {
132
169
  clearFirstServerEvent();
133
- if (!semanticIdleArmed) {
134
- semanticIdleArmed = true;
135
- armSemanticIdle();
136
- }
170
+ if (firstOutput.outputSeen && !semanticIdleTimer) armSemanticIdle();
137
171
  };
172
+ // Semantic output: the first one ends the first-output phase; every one
173
+ // re-arms the semantic idle.
138
174
  const noteMeaningful = () => {
139
- semanticIdleArmed = true;
175
+ firstOutput.noteOutput();
140
176
  armSemanticIdle();
141
177
  };
142
178
 
@@ -151,6 +187,7 @@ export function createHttpSseWatchdogs({ opts, reader, totalTimeout, stallPartia
151
187
  const dispose = () => {
152
188
  pendingReadReject = null;
153
189
  clearFirstServerEvent();
190
+ firstOutput.clear();
154
191
  clearSemanticIdle();
155
192
  try {
156
193
  reader.releaseLock?.();
@@ -165,9 +202,11 @@ export function createHttpSseWatchdogs({ opts, reader, totalTimeout, stallPartia
165
202
  return {
166
203
  abortReason: () => abortReason,
167
204
  armFirstServerEvent,
205
+ armFirstOutput,
168
206
  clearSemanticIdle,
169
207
  noteServerEvent,
170
208
  noteMeaningful,
209
+ semanticSeen: () => firstOutput.outputSeen,
171
210
  read,
172
211
  dispose,
173
212
  };
@@ -183,6 +183,8 @@ export async function sendViaHttpSse({
183
183
  totalTimeout,
184
184
  stallPartial: () => state.stallPartial(),
185
185
  label,
186
+ sessionId: opts?.sessionId,
187
+ onStreamDelta,
186
188
  });
187
189
  const meaningful = (kind = 'semantic') => {
188
190
  if (ttftMs == null) ttftMs = Date.now() - sseStartedAt;
@@ -191,12 +193,17 @@ export async function sendViaHttpSse({
191
193
  onStreamDelta?.(kind);
192
194
  } catch {}
193
195
  };
196
+ // An empty reasoning or message item added: progress only after output.
197
+ const lifecycle = (kind = 'semantic') => {
198
+ if (watchdogs.semanticSeen()) meaningful(kind);
199
+ };
194
200
  const state = createHttpSseResponseState({
195
201
  body,
196
202
  onToolCall,
197
203
  onTextDelta,
198
204
  onStreamItemStart: typeof opts?.onStreamItemStart === 'function' ? opts.onStreamItemStart : null,
199
205
  meaningful,
206
+ lifecycle,
200
207
  onServerEvent: watchdogs.noteServerEvent,
201
208
  label,
202
209
  });
@@ -210,6 +217,7 @@ export async function sendViaHttpSse({
210
217
  let buffer = '';
211
218
  try {
212
219
  watchdogs.armFirstServerEvent();
220
+ watchdogs.armFirstOutput();
213
221
  while (true) {
214
222
  if (totalTimeout.signal?.aborted) {
215
223
  watchdogs.clearSemanticIdle();