mixdog 0.9.0 → 0.9.2
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/package.json +10 -3
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/session-ingest-smoke.mjs +2 -2
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/mixdog-debug.cjs +0 -22
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +34 -56
- package/src/mixdog-session-runtime.mjs +710 -319
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -4
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1254
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +54 -3
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/session-discovery.mjs +0 -4
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +187 -43
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
- package/src/runtime/memory/lib/session-ingest.mjs +116 -7
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/search/index.mjs +2 -7
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +0 -1
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/child-spawn-gate.mjs +0 -6
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/seeds.mjs +1 -11
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2137 -750
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +146 -9
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +177 -100
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +312 -40
- package/src/tui/components/tool-output-format.test.mjs +180 -1
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +7324 -2393
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +354 -142
- package/src/tui/markdown/format-token.test.mjs +155 -17
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -647
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +154 -20
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -126,7 +126,7 @@ export function classifyError(err) {
|
|
|
126
126
|
|| code === 'EAI_AGAIN' || code === 'ENOTFOUND' || code === 'EAI_NODATA'
|
|
127
127
|
|| code === 'ECONNREFUSED' || code === 'ENETUNREACH' || code === 'EHOSTUNREACH'
|
|
128
128
|
|| code === 'EPIPE'
|
|
129
|
-
|| code === 'EPROVIDERTIMEOUT' || code === 'EGEMINITIMEOUT'
|
|
129
|
+
|| code === 'EPROVIDERTIMEOUT' || code === 'EGEMINITIMEOUT' || code === 'ESTREAMSTALL'
|
|
130
130
|
|| code === 'EWSACQUIRETIMEOUT') {
|
|
131
131
|
return 'transient'
|
|
132
132
|
}
|
|
@@ -231,6 +231,270 @@ export function jitterDelayMs(ms, ratio = PROVIDER_RETRY_JITTER_RATIO, mode = 's
|
|
|
231
231
|
return Math.max(0, Math.round(base + offset))
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
// ── Shared network-resilience interface ──────────────────────────────────────
|
|
235
|
+
// One home for the logic that used to be copied per provider: mid-stream
|
|
236
|
+
// classifier (WS + SSE), transport fallback predicate, stream-safety stamp
|
|
237
|
+
// latches, abort-aware sleep, handshake classifier, and the retry-budget table.
|
|
238
|
+
// Provider differences are passed as ARGUMENTS (policy objects), never branched
|
|
239
|
+
// on a hardcoded provider name. Behavior is preserved EXACTLY — each path below
|
|
240
|
+
// is the relocated original, selected by policy.mode.
|
|
241
|
+
|
|
242
|
+
// F) Retry-budget profiles as DATA. The numbers live ONLY here now.
|
|
243
|
+
// ws.transientCloseRetries (4) — ws_1006 / ws_1011 connection-loss buckets.
|
|
244
|
+
// ws.defaultRetries (2) — every other WS mid-stream bucket.
|
|
245
|
+
// sse.defaultRetries (3) — anthropic single-shot SSE mid-stream budget.
|
|
246
|
+
export const MIDSTREAM_RETRY_POLICY = {
|
|
247
|
+
ws: { transientCloseRetries: 4, defaultRetries: 2, backoff: [250, 1000, 2000, 4000] },
|
|
248
|
+
sse: { defaultRetries: 3, backoff: [250, 1000, 2000, 4000] },
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// WS buckets that earn the larger transient-close retry budget.
|
|
252
|
+
const WS_TRANSIENT_CLOSE_CLASSIFIERS = new Set(['ws_1006', 'ws_1011'])
|
|
253
|
+
|
|
254
|
+
function _midstreamLimitFor(classifier, policy) {
|
|
255
|
+
if (policy.mode === 'ws') {
|
|
256
|
+
return WS_TRANSIENT_CLOSE_CLASSIFIERS.has(classifier)
|
|
257
|
+
? policy.transientCloseRetries
|
|
258
|
+
: policy.defaultRetries
|
|
259
|
+
}
|
|
260
|
+
return policy.defaultRetries
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// WS gates each classifier against its own budget (mirrors the old
|
|
264
|
+
// _allowMidstreamRetry). SSE applies a single top-of-function budget gate and
|
|
265
|
+
// then returns raw classifier strings, so perClassifierGate:false returns the
|
|
266
|
+
// classifier unconditionally here.
|
|
267
|
+
function _allowMidstream(classifier, attemptIndex, policy) {
|
|
268
|
+
if (policy.perClassifierGate === false) return classifier
|
|
269
|
+
return attemptIndex < _midstreamLimitFor(classifier, policy) ? classifier : null
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// A) Unified mid-stream classifier. Returns a classifier string or null.
|
|
273
|
+
// `signals` is the provider's mid-stream state object (field names unchanged
|
|
274
|
+
// from each provider's midState). `policy.mode` selects the WS or SSE path so
|
|
275
|
+
// both providers reproduce their exact current branch order and gating.
|
|
276
|
+
export function classifyMidstreamError(err, signals, policy = {}) {
|
|
277
|
+
if (!signals) return null
|
|
278
|
+
const attemptIndex = signals.attemptIndex | 0
|
|
279
|
+
if (policy.mode === 'sse') return _classifyMidstreamSse(err, signals, attemptIndex, policy)
|
|
280
|
+
return _classifyMidstreamWs(err, signals, attemptIndex, policy)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Verbatim relocation of openai-oauth-ws's _classifyMidstreamError. `signals`
|
|
284
|
+
// carries sawResponseCreated / sawCompleted / emittedText / emittedToolCall /
|
|
285
|
+
// wsCloseCode / firstByteTimeout / wsSendFailed /
|
|
286
|
+
// userAbort / watchdogAbort / responseFailedPayload exactly as before.
|
|
287
|
+
function _classifyMidstreamWs(err, state, attemptIndex, policy) {
|
|
288
|
+
if (state.sawCompleted) return null
|
|
289
|
+
if (state.emittedToolCall) {
|
|
290
|
+
const _cc = Number(err?.wsCloseCode || state.wsCloseCode || 0)
|
|
291
|
+
if (!(_cc === 1000 && state.sawResponseCreated && !state.sawCompleted)) return null
|
|
292
|
+
}
|
|
293
|
+
if (state.emittedText || err?.liveTextEmitted) return null
|
|
294
|
+
if (state.firstByteTimeout || err?.firstByteTimeout) {
|
|
295
|
+
return _allowMidstream('first_byte_timeout', attemptIndex, policy)
|
|
296
|
+
}
|
|
297
|
+
if (err?.wsSendFailed || state.wsSendFailed) {
|
|
298
|
+
return _allowMidstream('ws_send_failed', attemptIndex, policy)
|
|
299
|
+
}
|
|
300
|
+
if (!state.sawResponseCreated) {
|
|
301
|
+
const closeCode = Number(err?.wsCloseCode || state.wsCloseCode || 0)
|
|
302
|
+
if (closeCode !== 1011 && closeCode !== 1012) return null
|
|
303
|
+
}
|
|
304
|
+
if (state.userAbort) return null
|
|
305
|
+
|
|
306
|
+
if (!err) return null
|
|
307
|
+
const status = Number(err?.httpStatus || 0)
|
|
308
|
+
if (status === 401 || status === 403 || status === 429) return null
|
|
309
|
+
if (status >= 500 && status < 600) {
|
|
310
|
+
return _allowMidstream(`http_${status}`, attemptIndex, policy)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const name = err?.name || ''
|
|
314
|
+
if (name === 'AgentStallAbortError') return _allowMidstream('agent_stall', attemptIndex, policy)
|
|
315
|
+
if (name === 'StreamStalledAbortError' || name === 'StreamStalledError' || err?.code === 'ESTREAMSTALL' || err?.streamStalled === true) {
|
|
316
|
+
// A stall that fired AFTER a tool call was emitted is unsafe to replay
|
|
317
|
+
// (double side-effect); surface it as terminal so the turn is not retried.
|
|
318
|
+
if (err?.unsafeToRetry === true) return null
|
|
319
|
+
return _allowMidstream('stream_stalled', attemptIndex, policy)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (state.watchdogAbort === 'AgentStallAbortError') return _allowMidstream('agent_stall', attemptIndex, policy)
|
|
323
|
+
if (state.watchdogAbort === 'StreamStalledAbortError') return _allowMidstream('stream_stalled', attemptIndex, policy)
|
|
324
|
+
|
|
325
|
+
const closeCode = Number(err?.wsCloseCode || state.wsCloseCode || 0)
|
|
326
|
+
if (closeCode === 1006) return _allowMidstream('ws_1006', attemptIndex, policy)
|
|
327
|
+
if (closeCode === 1011) return _allowMidstream('ws_1011', attemptIndex, policy)
|
|
328
|
+
if (closeCode === 1012) return _allowMidstream('ws_1012', attemptIndex, policy)
|
|
329
|
+
if (closeCode >= 4000 && closeCode < 5000 && closeCode !== 4000) return null
|
|
330
|
+
if (closeCode === 4000) return _allowMidstream('ws_4000', attemptIndex, policy)
|
|
331
|
+
if (closeCode === 1000 && state.sawResponseCreated && !state.sawCompleted) return _allowMidstream('ws_1000', attemptIndex, policy)
|
|
332
|
+
|
|
333
|
+
const failed = err?.responseFailed || state.responseFailedPayload
|
|
334
|
+
if (failed) {
|
|
335
|
+
try {
|
|
336
|
+
const blob = JSON.stringify(failed).toLowerCase()
|
|
337
|
+
if (blob.includes('stream_disconnected')) return _allowMidstream('response_failed_disconnected', attemptIndex, policy)
|
|
338
|
+
if (blob.includes('network_error')) return _allowMidstream('response_failed_network', attemptIndex, policy)
|
|
339
|
+
if (blob.includes('auth context expired')) return _allowMidstream('response_failed_auth_expired', attemptIndex, policy)
|
|
340
|
+
} catch {}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return null
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Verbatim relocation of anthropic-oauth's _classifyMidstreamError. `signals`
|
|
347
|
+
// carries sawMessageStart / sawCompleted / emittedToolCall / userAbort /
|
|
348
|
+
// watchdogAbort exactly as before.
|
|
349
|
+
function _classifyMidstreamSse(err, state, attemptIndex, policy) {
|
|
350
|
+
if (attemptIndex >= policy.defaultRetries) return null
|
|
351
|
+
if (state.sawCompleted) return null
|
|
352
|
+
if (!state.sawMessageStart) return null
|
|
353
|
+
if (state.userAbort) return null
|
|
354
|
+
if (state.emittedToolCall) return null
|
|
355
|
+
|
|
356
|
+
if (!err) return null
|
|
357
|
+
const status = Number(err?.httpStatus || 0)
|
|
358
|
+
if (status === 401 || status === 403 || status === 429) return null
|
|
359
|
+
|
|
360
|
+
const name = err?.name || ''
|
|
361
|
+
if (name === 'AgentStallAbortError') return 'agent_stall'
|
|
362
|
+
if (name === 'StreamStalledAbortError' || name === 'StreamStalledError' || err?.code === 'ESTREAMSTALL' || err?.streamStalled === true) {
|
|
363
|
+
// A stall AFTER a tool emit is unsafe to replay (double side-effect) →
|
|
364
|
+
// terminal (null), no mid-stream retry. Otherwise route to stream_stalled.
|
|
365
|
+
if (err?.unsafeToRetry === true) return null
|
|
366
|
+
return 'stream_stalled'
|
|
367
|
+
}
|
|
368
|
+
if (state.watchdogAbort === 'AgentStallAbortError') return 'agent_stall'
|
|
369
|
+
if (state.watchdogAbort === 'StreamStalledAbortError') return 'stream_stalled'
|
|
370
|
+
|
|
371
|
+
const code = err?.code || err?.cause?.code || ''
|
|
372
|
+
if (code === 'ECONNRESET') return 'reset'
|
|
373
|
+
if (code === 'ETIMEDOUT' || code === 'ESOCKETTIMEDOUT') return 'timeout'
|
|
374
|
+
if (code === 'ENOTFOUND' || code === 'EAI_AGAIN' || code === 'EAI_NODATA') return 'dns'
|
|
375
|
+
|
|
376
|
+
const msg = String(err?.message || '').toLowerCase()
|
|
377
|
+
if (msg.includes('stream timed out after') && msg.includes('of inactivity')) return 'sse_idle_timeout'
|
|
378
|
+
if (msg.includes('body stream') && msg.includes('terminated')) return 'stream_terminated'
|
|
379
|
+
if (msg.includes('fetch failed')) return 'fetch_failed'
|
|
380
|
+
|
|
381
|
+
return null
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// B) Unified transport (WS→HTTP) fallback predicate. Identical deny-order +
|
|
385
|
+
// allow-list to the two former copies; `enabled` replaces the per-provider
|
|
386
|
+
// env-flag check (caller computes the flag and passes it).
|
|
387
|
+
const TRANSPORT_FALLBACK_CLASSIFIERS = new Set([
|
|
388
|
+
'timeout', 'reset', 'dns', 'refused', 'network', 'acquire_timeout', 'http_5xx',
|
|
389
|
+
'first_byte_timeout',
|
|
390
|
+
'ws_1006', 'ws_1011', 'ws_1012', 'ws_1000', 'ws_4000', 'agent_stall', 'stream_stalled',
|
|
391
|
+
'response_failed_disconnected', 'response_failed_network', 'response_failed_auth_expired',
|
|
392
|
+
'ws_send_failed',
|
|
393
|
+
])
|
|
394
|
+
const TRANSPORT_FALLBACK_ERRNO = new Set([
|
|
395
|
+
'EWSACQUIRETIMEOUT', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'ECONNRESET', 'EAI_AGAIN',
|
|
396
|
+
'ENOTFOUND', 'EAI_NODATA', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH', 'EPIPE',
|
|
397
|
+
])
|
|
398
|
+
|
|
399
|
+
export function shouldFallbackTransport(err, { signal, enabled = true } = {}) {
|
|
400
|
+
if (!enabled) return false
|
|
401
|
+
if (signal?.aborted) return false
|
|
402
|
+
if (err?.liveTextEmitted === true) return false
|
|
403
|
+
if (err?.emittedToolCall === true || err?.unsafeToRetry === true) return false
|
|
404
|
+
const status = Number(err?.httpStatus || err?.status || 0)
|
|
405
|
+
if (status === 401 || status === 403 || status === 404 || status === 429) return false
|
|
406
|
+
if (status >= 500 && status < 600) return true
|
|
407
|
+
const code = String(err?.code || '')
|
|
408
|
+
if (TRANSPORT_FALLBACK_ERRNO.has(code)) return true
|
|
409
|
+
const classifier = String(err?.retryClassifier || err?.midstreamClassifier || '')
|
|
410
|
+
if (TRANSPORT_FALLBACK_CLASSIFIERS.has(classifier)) return true
|
|
411
|
+
if (/^http_5\d\d$/.test(classifier)) return true
|
|
412
|
+
if (err?.firstByteTimeout) return true
|
|
413
|
+
const msg = String(err?.message || '')
|
|
414
|
+
return /opening handshake has timed out|socket hang up|acquire timed out|no first server event|no meaningful output/i.test(msg)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// C) Stream-safety stamp latches. Mirrors openai-oauth-ws's _stampLiveText /
|
|
418
|
+
// _stampTool: once text/tool has been marked, every subsequent throw path
|
|
419
|
+
// re-applies the liveTextEmitted/emittedToolCall + unsafeToRetry markers so
|
|
420
|
+
// no upstream gate can reissue the turn and concatenate attempts.
|
|
421
|
+
export function createStreamSafetyStamps() {
|
|
422
|
+
let textLatched = false
|
|
423
|
+
let toolLatched = false
|
|
424
|
+
const stampText = (e) => {
|
|
425
|
+
if (textLatched && e) { try { e.liveTextEmitted = true; e.unsafeToRetry = true } catch {} }
|
|
426
|
+
return e
|
|
427
|
+
}
|
|
428
|
+
const stampTool = (e) => {
|
|
429
|
+
if (toolLatched && e) { try { e.emittedToolCall = true; e.unsafeToRetry = true } catch {} }
|
|
430
|
+
return e
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
markText() { textLatched = true },
|
|
434
|
+
markTool() { toolLatched = true },
|
|
435
|
+
stampText,
|
|
436
|
+
stampTool,
|
|
437
|
+
stampAll: (e) => stampTool(stampText(e)),
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const _defaultAbortSleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
442
|
+
|
|
443
|
+
// D) Abort-aware sleep (single copy). Resolves after `ms`, or rejects with the
|
|
444
|
+
// signal's reason (or `abortMessage`) the moment the signal aborts. `sleepFn`
|
|
445
|
+
// is the injectable no-signal sleep (test seam); `abortMessage` preserves
|
|
446
|
+
// each caller's prior fallback text when the abort reason is not an Error.
|
|
447
|
+
export async function sleepWithAbort(ms, signal, sleepFn = _defaultAbortSleep, abortMessage = 'sleep aborted') {
|
|
448
|
+
if (!ms) return
|
|
449
|
+
if (!signal) {
|
|
450
|
+
await (sleepFn || _defaultAbortSleep)(ms)
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
await new Promise((resolve, reject) => {
|
|
454
|
+
const t = setTimeout(() => {
|
|
455
|
+
try { signal.removeEventListener('abort', onAbort) } catch {}
|
|
456
|
+
resolve()
|
|
457
|
+
}, ms)
|
|
458
|
+
const onAbort = () => {
|
|
459
|
+
clearTimeout(t)
|
|
460
|
+
const reason = signal.reason
|
|
461
|
+
reject(reason instanceof Error ? reason : new Error(abortMessage))
|
|
462
|
+
}
|
|
463
|
+
if (signal.aborted) { onAbort(); return }
|
|
464
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
465
|
+
})
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// E) Handshake classifier (moved here from openai-oauth-ws). Default-deny:
|
|
469
|
+
// anything not recognized as transient returns null. 401/403/404/429 are
|
|
470
|
+
// permanent. Returns 'timeout' | 'reset' | 'dns' | 'refused' | 'network' |
|
|
471
|
+
// 'acquire_timeout' | `http_5xx` | null.
|
|
472
|
+
export function classifyHandshakeError(err) {
|
|
473
|
+
if (!err) return null
|
|
474
|
+
const code = err.code || ''
|
|
475
|
+
const msg = String(err.message || '')
|
|
476
|
+
const status = Number(err.httpStatus || 0)
|
|
477
|
+
|
|
478
|
+
if (status === 401 || status === 403 || status === 404 || status === 429) {
|
|
479
|
+
return null
|
|
480
|
+
}
|
|
481
|
+
if (status >= 500 && status < 600) {
|
|
482
|
+
return `http_${status}`
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (code === 'ECONNRESET') return 'reset'
|
|
486
|
+
if (code === 'EAI_AGAIN' || code === 'ENOTFOUND' || code === 'EAI_NODATA') return 'dns'
|
|
487
|
+
if (code === 'ECONNREFUSED') return 'refused'
|
|
488
|
+
if (code === 'ETIMEDOUT' || code === 'ESOCKETTIMEDOUT') return 'timeout'
|
|
489
|
+
if (code === 'EWSACQUIRETIMEOUT') return 'acquire_timeout'
|
|
490
|
+
if (code === 'ENETUNREACH' || code === 'EHOSTUNREACH' || code === 'EPIPE') return 'network'
|
|
491
|
+
|
|
492
|
+
if (/opening handshake has timed out/i.test(msg)) return 'timeout'
|
|
493
|
+
if (/socket hang up/i.test(msg)) return 'reset'
|
|
494
|
+
|
|
495
|
+
return null
|
|
496
|
+
}
|
|
497
|
+
|
|
234
498
|
/**
|
|
235
499
|
* Run an async function with exponential-backoff retry on transient errors.
|
|
236
500
|
*
|