mixdog 0.9.95 → 0.9.96

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 (178) hide show
  1. package/LICENSES/Apache-2.0.txt +201 -0
  2. package/LICENSES/MIT.txt +56 -0
  3. package/LICENSES/codex-NOTICE.txt +6 -0
  4. package/NOTICE.md +46 -9
  5. package/package.json +12 -9
  6. package/scripts/.tmp-coverage-report.mjs +96 -0
  7. package/scripts/build-tui.mjs +17 -1
  8. package/scripts/fixtures/patch-replay-corpus.json +98 -0
  9. package/src/defaults/mixdog-config.template.json +5 -8
  10. package/src/rules/agent/30-explorer.md +7 -4
  11. package/src/rules/shared/01-tool.md +20 -29
  12. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
  13. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
  14. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
  15. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
  16. package/src/runtime/agent/orchestrator/config.mjs +246 -67
  17. package/src/runtime/agent/orchestrator/mcp/client.mjs +26 -11
  18. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
  19. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
  20. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
  21. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
  22. package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
  23. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
  24. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
  25. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
  26. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
  27. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
  28. package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
  29. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
  30. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
  31. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
  32. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
  33. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +28 -4
  34. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -33
  35. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
  36. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
  37. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
  38. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +1 -27
  39. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -10
  40. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +5 -0
  41. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
  42. package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
  43. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
  44. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
  45. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
  46. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  47. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
  48. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +12 -0
  49. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -33
  50. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
  51. package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
  53. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
  54. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +118 -18
  55. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
  56. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
  57. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
  58. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
  59. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +10 -26
  60. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +1 -1
  61. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +3 -3
  62. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -23
  63. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +23 -9
  64. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
  65. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
  66. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
  67. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
  69. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
  70. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
  71. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
  72. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +87 -15
  73. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +6 -15
  74. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +2 -2
  75. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
  76. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
  77. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +29 -1
  78. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
  79. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
  80. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +10 -10
  81. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +56 -8
  82. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
  83. package/src/runtime/channels/lib/config.mjs +2 -2
  84. package/src/runtime/channels/lib/inbound-handler.mjs +1 -1
  85. package/src/runtime/channels/lib/scheduler.mjs +2 -2
  86. package/src/runtime/memory/index.mjs +5 -38
  87. package/src/runtime/memory/lib/agent-ipc.mjs +145 -81
  88. package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
  89. package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
  90. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +12 -33
  91. package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
  92. package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
  93. package/src/runtime/memory/lib/http-router.mjs +8 -0
  94. package/src/runtime/memory/lib/ko-morph.mjs +3 -20
  95. package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
  96. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
  97. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
  98. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
  99. package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
  100. package/src/runtime/memory/lib/pg/process.mjs +32 -9
  101. package/src/runtime/memory/tool-defs.mjs +1 -5
  102. package/src/runtime/search/tool-defs.mjs +2 -2
  103. package/src/runtime/shared/agent-route-config.mjs +119 -0
  104. package/src/runtime/shared/atomic-file.mjs +80 -4
  105. package/src/runtime/shared/channel-notification-routing.mjs +2 -2
  106. package/src/runtime/shared/child-guardian.mjs +204 -108
  107. package/src/runtime/shared/child-spawn-gate.mjs +60 -31
  108. package/src/runtime/shared/config.mjs +130 -44
  109. package/src/runtime/shared/resource-admission.mjs +146 -97
  110. package/src/runtime/shared/stream-progress.mjs +6 -0
  111. package/src/runtime/shared/tool-surface.mjs +0 -5
  112. package/src/runtime/shared/turn-snapshot.mjs +31 -5
  113. package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
  114. package/src/session-runtime/config-helpers.mjs +39 -37
  115. package/src/session-runtime/model-route-api.mjs +7 -5
  116. package/src/session-runtime/provider-auth-api.mjs +0 -7
  117. package/src/session-runtime/runtime-core.mjs +11 -11
  118. package/src/session-runtime/runtime-tunables.mjs +4 -4
  119. package/src/session-runtime/session-lifecycle.mjs +9 -2
  120. package/src/session-runtime/session-turn-api.mjs +41 -9
  121. package/src/session-runtime/settings-api.mjs +5 -17
  122. package/src/session-runtime/tool-defs.mjs +3 -3
  123. package/src/session-runtime/workflow-agents-api.mjs +55 -81
  124. package/src/session-runtime/workflow.mjs +14 -24
  125. package/src/standalone/agent-dispatch-broker.mjs +198 -0
  126. package/src/standalone/agent-tool/helpers.mjs +2 -2
  127. package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
  128. package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
  129. package/src/standalone/agent-tool/tool-def.mjs +0 -14
  130. package/src/standalone/backend-daemon.mjs +97 -36
  131. package/src/standalone/channel-admin.mjs +12 -12
  132. package/src/standalone/channel-daemon-client.mjs +17 -1
  133. package/src/standalone/channel-daemon-transport.mjs +90 -8
  134. package/src/standalone/engine-daemon-client.mjs +380 -205
  135. package/src/standalone/engine-daemon-protocol.mjs +3 -4
  136. package/src/standalone/engine-daemon-service.mjs +378 -134
  137. package/src/standalone/engine-daemon-transport.mjs +268 -77
  138. package/src/standalone/explore-tool.mjs +187 -70
  139. package/src/standalone/fair-call-scheduler.mjs +264 -0
  140. package/src/standalone/session-protocol.mjs +188 -0
  141. package/src/tui/App.jsx +30 -17
  142. package/src/tui/app/app-view.jsx +5 -2
  143. package/src/tui/app/core-memory-picker.mjs +19 -19
  144. package/src/tui/app/doctor.mjs +5 -11
  145. package/src/tui/app/message-selector.mjs +103 -0
  146. package/src/tui/app/onboarding-steps.mjs +1 -2
  147. package/src/tui/app/project-picker.mjs +80 -56
  148. package/src/tui/app/prompt-submit.mjs +46 -41
  149. package/src/tui/app/resume-picker.mjs +2 -2
  150. package/src/tui/app/route-pickers.mjs +3 -1
  151. package/src/tui/app/settings-picker.mjs +6 -8
  152. package/src/tui/app/transcript-window.mjs +38 -0
  153. package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
  154. package/src/tui/app/use-prompt-handlers.mjs +86 -21
  155. package/src/tui/app/use-prompt-hint.mjs +3 -2
  156. package/src/tui/app/use-prompt-queue-history.mjs +10 -3
  157. package/src/tui/app/use-transcript-scroll.mjs +9 -2
  158. package/src/tui/app/use-transcript-window.mjs +50 -17
  159. package/src/tui/components/PromptInput.jsx +71 -38
  160. package/src/tui/components/Spinner.jsx +1 -1
  161. package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
  162. package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
  163. package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
  164. package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
  165. package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
  166. package/src/tui/dist/index.mjs +920 -1592
  167. package/src/tui/engine/live-share.mjs +34 -0
  168. package/src/tui/engine/session-api-ext.mjs +1 -4
  169. package/src/tui/engine/session-api.mjs +103 -33
  170. package/src/tui/engine/session-flow.mjs +7 -1
  171. package/src/tui/engine/tool-card-results.mjs +4 -2
  172. package/src/tui/engine/tool-result-text.mjs +10 -0
  173. package/src/tui/engine/turn.mjs +9 -69
  174. package/src/tui/engine-local-session.mjs +47 -31
  175. package/src/tui/index.jsx +1 -2
  176. package/src/tui/spinner-meta.mjs +4 -4
  177. package/src/tui/spinner-verbs.mjs +51 -3
  178. package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
@@ -1,15 +1,12 @@
1
1
  {
2
2
  "outputStyle": "default",
3
- "mcpServers": {},
4
- "channels": {
5
- "promptInjection": {
6
- "mode": "hook",
7
- "targetPath": ""
8
- }
3
+ "agent": {
4
+ "mcpServers": {},
5
+ "profile": { "title": "", "language": "system" },
6
+ "recap": { "enabled": true }
9
7
  },
8
+ "channels": {},
10
9
  "memory": {
11
- "enabled": true,
12
- "user": { "title": "" },
13
10
  "cycle1": { "interval": "10m" },
14
11
  "cycle2": { "interval": "1h" }
15
12
  }
@@ -32,11 +32,14 @@ or concept synonym; never a prose phrase. Spaces and non-ASCII are allowed
32
32
  only in verbatim quoted error/log literals. Translate other non-English
33
33
  queries to English identifiers.
34
34
 
35
- Scope is session cwd; `path` may be omitted. For unverified `src` paths, use
35
+ Scope is every `<roots><root>…</root></roots>` entry when supplied, otherwise
36
+ session cwd. Search every supplied root in the turn-1 batch: grep/glob batch
37
+ `path[]`, while find uses one sibling call per root. Never silently fall back to
38
+ cwd or omit a supplied root. A find result is relative to its exact root; prefix
39
+ that root when returning a path outside cwd. For unverified `src` paths, use
36
40
  `find` first; never guess or invent directories or pair `path:"."` with guessed
37
- `src/**`. Scoped grep/glob may use only an exact find-returned path, no earlier
38
- than turn 2. After zero hits, change tokens or scope, never wording or guessed
39
- paths.
41
+ `src/**`. Scoped grep/glob may use only a supplied root or an exact find-returned
42
+ path. After zero hits, change tokens or scope, never wording or guessed paths.
40
43
 
41
44
  An anchor is a `path:line` containing a query token or synonym, including a
42
45
  code_graph hit. Generic terms without query specificity are zero. Never
@@ -1,31 +1,22 @@
1
1
  # Tool Use
2
2
 
3
- - Gather every known facet — environment, capability, artifact, failure — in
4
- one bounded first message, one shortest route each: unknown coordinates→
5
- `explore` first and alone, its anchors routing the next batch (roles without
6
- it: `find`); a known path or anchor skips it; partial path/name→`find`;
7
- verified root+wildcard→`glob`; text/code→`grep`; symbol/relation→`code_graph`;
8
- known file/span→`read`, not `grep`; verified directory→`list`; known
9
- edit→`apply_patch`; web/current→`search`; only what none of them reach
10
- (process/env, git, build/run/test)→`shell`, never to cat/ls/find/grep.
11
- - Fewest turns, not fewest calls: always send independent calls together in
12
- one message, merged per tool — one `shell` chain (`&&`/`;`), one `read`
13
- region array, one `apply_patch` with every determined edit proved by its
14
- `post_shell` in the same turn; a later turn only for unseen output.
15
- - Verified paths: project root, session cwd, user-provided, tool-returned.
16
- `find` first for guessed fragments; on ENOENT find the basename; retry
17
- `EXPLORATION_FAILED` once with changed tokens.
18
- - Never re-fetch what a tool already returned: every span this session emitted —
19
- `path:line` hits, read regions, an `apply_patch` post-patch body — is final for
20
- its range, so read only uncovered lines and stop once evidence covers the
21
- deliverable; only zero/error justifies new scope, each call narrow
22
- (`head_limit`, regions).
23
- - Verify in proportion to risk: one decisive probe rides the edit call. A pass
24
- is final; on failure fix and rerun what failed. Diagnostics non-fatal; report
25
- verified vs assumed.
26
- - `apply_patch` is the primary edit tool: once path and content are known it
27
- joins the current batch — all files and hunks in one call, never one turn per
28
- file (the format contract lives in its tool description).
29
- - After a background task starts or reports, end the turn — its notification
30
- resumes the work. Never poll or block; wait only for what the turn cannot
31
- proceed without. Long commands whose output the next step ignores go async.
3
+ - Unknown coordinates → one `explore` call with every unknown facet, sent
4
+ alone. Then batch every anchored retrieval needed to determine the complete
5
+ edit: partial path/name→`find`; exact directory entries→`list`; wildcard→
6
+ `glob`; text/regex-anchored source blocks→`grep`; anchorless known file/range→
7
+ `read`; symbol/relation→`code_graph`; web/current→`search`; returned URL body→
8
+ `web_fetch`; prior work→`recall`; durable compact English memory→`memory`;
9
+ explicit project change→`cwd`; explicit user-requested conversation reset→
10
+ `session_manage`; process/env, git, build/run/test→`shell`. Never use shell
11
+ equivalents for file discovery or content retrieval.
12
+ - Use verified paths (cwd, project root, user-provided, or tool-returned);
13
+ guessed fragments use `find`. Merge independent calls per tool in one
14
+ message and fetch all information needed in that batch. Follow up only after
15
+ zero/error or a newly revealed dependency; never re-fetch an unchanged span.
16
+ - Once every final edit is fully determined, send one assistant tool batch
17
+ containing one `apply_patch` for all files/hunks and one `shell` chain for
18
+ verification. The runtime supports this mixed batch. On failure fix and
19
+ rerun only what failed; report verified versus assumed.
20
+ - After a call returns a background `task_id`, end the turn; its completion
21
+ notification resumes work. Never poll; use task control only for recovery or
22
+ a required blocking result.
@@ -36,6 +36,7 @@ import {
36
36
  abortAgentProgressWatchdog,
37
37
  agentWatchdogPolicyActive,
38
38
  evaluateAgentWatchdogAbort,
39
+ partialHandoffTextFromSession,
39
40
  resolveAgentWatchdogPolicy,
40
41
  resolveHandoffMessageStartIndex,
41
42
  watchdogPartialHandoffFromError,
@@ -65,6 +66,17 @@ function formatCompactElapsedSeconds(ms) {
65
66
  return `${Math.max(1, Math.ceil(value / 1000))}s`;
66
67
  }
67
68
 
69
+ // True when an abort explicitly opted into partial salvage — the error object
70
+ // or the abort reason carries `salvagePartial: true`. A DEADLINE-driven caller
71
+ // (explore hard timeout) sets it so the anchors the sub-agent already produced
72
+ // are returned instead of discarded; user cancellation (ESC) never sets it and
73
+ // keeps the throw-everything behaviour.
74
+ function salvagePartialRequested(error, signal) {
75
+ if (error && typeof error === 'object' && error.salvagePartial === true) return true;
76
+ const reason = signal?.reason;
77
+ return !!(reason && typeof reason === 'object' && reason.salvagePartial === true);
78
+ }
79
+
68
80
  function agentCompactEventLabel(event = {}) {
69
81
  const status = String(event.status || '').toLowerCase();
70
82
  const reactive = String(event.trigger || '').toLowerCase() === 'reactive';
@@ -229,7 +241,7 @@ export function makeAgentDispatch(opts = {}) {
229
241
  }
230
242
  const agent = opts.agent;
231
243
 
232
- return async function agentDispatch({ prompt, preset: presetArg, sourceName: sourceNameArg, parentSignal: callParentSignal, idleTimeoutMs: callIdleTimeoutMs, cwd: callCwd }) {
244
+ return async function agentDispatch({ prompt, preset: presetArg, sourceName: sourceNameArg, parentSignal: callParentSignal, idleTimeoutMs: callIdleTimeoutMs, cwd: callCwd, sessionId: callSessionId }) {
233
245
  if (typeof prompt !== 'string' || !prompt) {
234
246
  throw new Error(`[agent-dispatch] prompt required for agent "${agent}"`);
235
247
  }
@@ -243,6 +255,7 @@ export function makeAgentDispatch(opts = {}) {
243
255
  lease = await admission.acquire('agent', {
244
256
  signal: admissionAbortLink.signal,
245
257
  label: agent,
258
+ ownerKey: callSessionId || opts.ownerSessionId || opts.parentSessionId || opts.sessionId || null,
246
259
  });
247
260
  } catch (error) {
248
261
  admissionAbortLink.dispose();
@@ -470,7 +483,10 @@ export function makeAgentDispatch(opts = {}) {
470
483
  try { closeSession(session.id, 'ephemeral-done'); } catch { /* ignore */ }
471
484
  return out;
472
485
  } catch (err) {
473
- const partial = watchdogPartialHandoffFromError(err, getSession(session.id), _handoffMsgStart);
486
+ const partial = watchdogPartialHandoffFromError(err, getSession(session.id), _handoffMsgStart)
487
+ ?? (salvagePartialRequested(err, _abortLink?.signal)
488
+ ? partialHandoffTextFromSession(getSession(session.id), _handoffMsgStart)
489
+ : null);
474
490
  if (partial) {
475
491
  terminalStatus = 'idle';
476
492
  try { closeSession(session.id, 'ephemeral-done'); } catch { /* ignore */ }
@@ -7,10 +7,26 @@
7
7
  import { appendAgentTrace } from '../agent-trace-io.mjs';
8
8
  import { getHiddenAgent } from '../internal-agents.mjs';
9
9
  import {
10
+ PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
11
+ PROVIDER_WS_SEMANTIC_IDLE_TIMEOUT_MS,
12
+ STALL_TICK_MS,
10
13
  resolveAgentStallThresholds,
11
14
  resolveAgentToolThresholdSeconds,
12
15
  } from '../stall-policy.mjs';
13
16
 
17
+ // Ordering guarantee, stated in stall-policy.mjs: the provider layer — which
18
+ // can retry in place or fall back to non-streaming — must fire STRICTLY before
19
+ // the agent watchdog's terminal abort. Role abort budgets (worker/reviewer
20
+ // 300s, explore 240s) sat at or BELOW the provider semantic-idle window
21
+ // (300s), inverting that order: the watchdog aborted the shared signal first,
22
+ // so the provider's recovery never ran and the `agent_stall` failure — which
23
+ // the classifier calls retryable — died on throwIfAborted instead. Hold the
24
+ // role-derived idle budget at one watchdog tick above the provider window.
25
+ const PROVIDER_RECOVERY_FLOOR_MS = Math.max(
26
+ PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
27
+ PROVIDER_WS_SEMANTIC_IDLE_TIMEOUT_MS,
28
+ ) + STALL_TICK_MS;
29
+
14
30
  const WATCHDOG_ABORT_RE = /^agent (?:first (?:transport|semantic response|response) stale|task stale|tool running stale)\s*\(/;
15
31
 
16
32
  /**
@@ -123,6 +139,16 @@ export function watchdogPartialHandoffFromError(error, session, messageStartInde
123
139
  return text.trim() ? text : null;
124
140
  }
125
141
 
142
+ // Salvage path for NON-watchdog aborts that explicitly opt in (the abort error
143
+ // / abort reason carries `salvagePartial: true` — e.g. the explore wall-clock
144
+ // hard timeout). Same collection rule as the watchdog handoff: only assistant
145
+ // text appended during this run. Plain user cancellation never opts in, so ESC
146
+ // still discards the run.
147
+ export function partialHandoffTextFromSession(session, messageStartIndex = 0) {
148
+ const text = collectSessionAssistantHandoffText(session, messageStartIndex);
149
+ return text.trim() ? text : null;
150
+ }
151
+
126
152
  function resolveWatchdogAbortElapsedMs({ error, snapshot, policy, now, anchorTs, lastProgressAt }) {
127
153
  if (snapshot && policy) {
128
154
  if (snapshot.waitingForFirstActivity) {
@@ -223,7 +249,8 @@ export function resolveAgentWatchdogPolicy(agent, overrides = {}) {
223
249
  idleStaleMs = Math.floor(overrides.idleTimeoutMs);
224
250
  } else if (getHiddenAgent(agent)) {
225
251
  const { abort } = resolveAgentStallThresholds(agent);
226
- idleStaleMs = abort * 1000;
252
+ // Role budget, floored so the provider recovery window always wins.
253
+ idleStaleMs = Math.max(abort * 1000, PROVIDER_RECOVERY_FLOOR_MS);
227
254
  } else {
228
255
  // Part B: the primary mid-stream stall catch is now the provider-level
229
256
  // SEMANTIC idle abort (~120s, ping-immune). This public-agent idle is a
@@ -238,6 +265,9 @@ export function resolveAgentWatchdogPolicy(agent, overrides = {}) {
238
265
  idleStaleMs = backstopMs > 0
239
266
  ? Math.min(DEFAULT_STALE_TIMEOUT_MS, backstopMs)
240
267
  : DEFAULT_STALE_TIMEOUT_MS;
268
+ // Same floor for the public backstop: a workflow role (worker 300s,
269
+ // explore 240s) must not undercut the provider window either.
270
+ idleStaleMs = Math.max(idleStaleMs, PROVIDER_RECOVERY_FLOOR_MS);
241
271
  }
242
272
 
243
273
  const idleSec = idleStaleMs / 1000;
@@ -1,16 +1,10 @@
1
- import { isKnownProvider } from '../../../../standalone/provider-admin.mjs';
1
+ import { configuredAgentRouteCandidates } from '../../../shared/agent-route-config.mjs';
2
2
  import { loadConfig } from '../config.mjs';
3
3
  import { getHiddenAgent } from '../internal-agents.mjs';
4
4
 
5
- const DEFAULT_AGENT_ROUTE_PROVIDER = 'anthropic-oauth';
6
-
7
- function normalizeMaintenanceCandidate(candidate, config) {
5
+ function normalizeMaintenanceCandidate(candidate) {
8
6
  if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) return candidate || null;
9
- const configuredProvider = String(config?.defaultProvider || '').trim();
10
- const fallbackProvider = isKnownProvider(configuredProvider)
11
- ? configuredProvider
12
- : DEFAULT_AGENT_ROUTE_PROVIDER;
13
- const provider = String(candidate.provider || fallbackProvider).trim();
7
+ const provider = String(candidate.provider || '').trim();
14
8
  const model = String(candidate.model || '').trim();
15
9
  if (!provider || !model) return null;
16
10
  return {
@@ -34,20 +28,15 @@ export function resolveMaintenanceRoute({ preset, optsPreset, agent, config: cfg
34
28
  if (hidden) {
35
29
  try {
36
30
  const config = cfgIn || loadConfig({ secrets: false });
37
- const maint = config?.maintenance || {};
38
31
  const key = hidden.maintKey || hidden.slot;
39
32
  const role = key === 'explore' ? 'explore' : (key === 'memory' ? 'maintainer' : '');
40
- const workflowSlot = key === 'explore' ? 'explorer' : (key === 'memory' ? 'memory' : '');
41
- if (!role) return maint[key] ?? null;
33
+ if (!role) return config?.maintenance?.[key] ?? null;
42
34
  const candidates = [
43
- role ? config?.agents?.[role] : null,
44
- key === 'memory' ? config?.agents?.maintenance : null,
45
- workflowSlot ? config?.workflowRoutes?.[workflowSlot] : null,
46
- maint[key],
35
+ ...configuredAgentRouteCandidates(config, role),
47
36
  role ? config?.default : null,
48
37
  ];
49
38
  for (const candidate of candidates) {
50
- const route = normalizeMaintenanceCandidate(candidate, config);
39
+ const route = normalizeMaintenanceCandidate(candidate);
51
40
  if (route) return route;
52
41
  }
53
42
  return null;
@@ -31,7 +31,16 @@ function compactRecoveredErrorMessage(value) {
31
31
  : message;
32
32
  }
33
33
 
34
- function traceAgentLoop({ sessionId, iteration, sendMs, messageCount, bodyBytesEst, agent = null }) {
34
+ function traceAgentLoop({
35
+ sessionId,
36
+ iteration,
37
+ sendMs,
38
+ preSendMs,
39
+ toolResumeMs,
40
+ messageCount,
41
+ bodyBytesEst,
42
+ agent = null,
43
+ }) {
35
44
  // Two emit modes, no behavior change either way:
36
45
  // VERBOSE=1 → full loop row incl. body_bytes_est (payload serialized).
37
46
  // TIMING=1 → lightweight send-latency attribution for high-fanout
@@ -45,6 +54,8 @@ function traceAgentLoop({ sessionId, iteration, sendMs, messageCount, bodyBytesE
45
54
  kind: 'loop',
46
55
  agent: agent || null,
47
56
  send_ms: sendMs,
57
+ pre_send_ms: preSendMs ?? null,
58
+ tool_resume_ms: toolResumeMs ?? null,
48
59
  message_count: messageCount,
49
60
  body_bytes_est: bodyBytesEst ?? null,
50
61
  });