mixdog 0.9.96 → 0.9.99

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 (193) hide show
  1. package/README.md +0 -17
  2. package/package.json +16 -8
  3. package/scripts/build-tui.mjs +6 -6
  4. package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
  5. package/src/defaults/skills/setup/SKILL.md +1 -1
  6. package/src/lib/keychain-cjs.cjs +1 -1
  7. package/src/lib/mixdog-debug.cjs +2 -3
  8. package/src/rules/agent/30-explorer.md +43 -32
  9. package/src/rules/lead/01-general.md +1 -2
  10. package/src/rules/lead/lead-tool.md +0 -3
  11. package/src/rules/shared/01-tool.md +11 -10
  12. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
  13. package/src/runtime/agent/orchestrator/agent-trace.mjs +19 -5
  14. package/src/runtime/agent/orchestrator/mcp/client.mjs +81 -33
  15. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +7 -0
  16. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -3
  17. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +5 -3
  18. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
  19. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +23 -1
  20. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
  21. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -6
  22. package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
  23. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -4
  24. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +20 -2
  25. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +4 -3
  26. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +42 -26
  27. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
  28. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +44 -6
  29. package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
  30. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +2 -1
  31. package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
  32. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +3 -6
  33. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
  34. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +59 -42
  36. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -2
  37. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +12 -22
  38. package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
  39. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
  40. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +2 -0
  41. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +103 -12
  42. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -4
  43. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -30
  44. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +10 -30
  45. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +41 -22
  46. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +6 -2
  47. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +55 -11
  48. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
  49. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +55 -18
  50. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -0
  51. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
  52. package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +2 -2
  53. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
  54. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -105
  55. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +2 -1
  56. package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
  57. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -1
  58. package/src/runtime/attachments/pdf-extract.mjs +83 -0
  59. package/src/runtime/attachments/store.mjs +515 -0
  60. package/src/runtime/attachments/store.test.mjs +188 -0
  61. package/src/runtime/channels/lib/config.mjs +33 -33
  62. package/src/runtime/channels/lib/inbound-handler.mjs +7 -7
  63. package/src/runtime/channels/lib/interaction-handlers.mjs +4 -194
  64. package/src/runtime/channels/lib/memory-client.mjs +31 -157
  65. package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
  66. package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
  67. package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
  68. package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
  69. package/src/runtime/channels/lib/runtime-paths.mjs +5 -34
  70. package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
  71. package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
  72. package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
  73. package/src/runtime/channels/lib/worker-ipc.mjs +4 -70
  74. package/src/runtime/channels/lib/worker-main.mjs +39 -58
  75. package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
  76. package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
  77. package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
  78. package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
  79. package/src/runtime/channels/tool-defs.mjs +1 -1
  80. package/src/runtime/memory/index.mjs +72 -211
  81. package/src/runtime/memory/lib/agent-ipc.mjs +9 -8
  82. package/src/runtime/memory/lib/cycle-llm-adapters.mjs +2 -2
  83. package/src/runtime/memory/lib/http-router.mjs +1 -6
  84. package/src/runtime/memory/lib/http-wire.mjs +39 -4
  85. package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
  86. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
  87. package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
  88. package/src/runtime/memory/lib/memory-log.mjs +1 -1
  89. package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
  90. package/src/runtime/memory/lib/memory.mjs +1 -1
  91. package/src/runtime/memory/lib/session-ingest.mjs +1 -1
  92. package/src/runtime/search/index.mjs +2 -2
  93. package/src/runtime/shared/abort-race.mjs +70 -0
  94. package/src/runtime/shared/child-spawn-gate.mjs +101 -63
  95. package/src/runtime/shared/config.mjs +11 -24
  96. package/src/runtime/shared/json-metrics.mjs +94 -0
  97. package/src/runtime/shared/llm/index.mjs +1 -1
  98. package/src/runtime/shared/owner-fair-gate.mjs +134 -0
  99. package/src/runtime/shared/resource-admission.mjs +6 -5
  100. package/src/runtime/shared/tool-execution-owner.mjs +12 -0
  101. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  102. package/src/runtime/shared/tool-surface.mjs +91 -22
  103. package/src/runtime/shared/tool-workload-gates.mjs +29 -0
  104. package/src/runtime/shared/turn-snapshot.mjs +30 -2
  105. package/src/runtime/shared/turn-worktree-snapshot.mjs +19 -0
  106. package/src/session-runtime/channel-config-api.mjs +5 -5
  107. package/src/session-runtime/config-lifecycle.mjs +27 -27
  108. package/src/session-runtime/lifecycle-api.mjs +22 -3
  109. package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
  110. package/src/session-runtime/prewarm.mjs +1 -1
  111. package/src/session-runtime/provider-models.mjs +12 -2
  112. package/src/session-runtime/quick-model-rows.mjs +38 -7
  113. package/src/session-runtime/quick-search-models.mjs +15 -17
  114. package/src/session-runtime/remote-control.mjs +6 -7
  115. package/src/session-runtime/runtime-core.mjs +61 -32
  116. package/src/session-runtime/runtime-tunables.mjs +9 -6
  117. package/src/session-runtime/self-update.mjs +1 -1
  118. package/src/session-runtime/session-lifecycle.mjs +36 -15
  119. package/src/session-runtime/session-title.mjs +28 -4
  120. package/src/session-runtime/session-turn-api.mjs +55 -15
  121. package/src/session-runtime/settings-api.mjs +7 -7
  122. package/src/standalone/agent-dispatch-broker.mjs +1 -1
  123. package/src/standalone/channel-admin.mjs +42 -53
  124. package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +16 -18
  125. package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +73 -58
  126. package/src/standalone/channel-worker.mjs +124 -647
  127. package/src/standalone/{backend-daemon.mjs → daemon.mjs} +190 -132
  128. package/src/standalone/memory-runtime-proxy.mjs +58 -1
  129. package/src/standalone/{engine-daemon-client.mjs → session-client.mjs} +151 -86
  130. package/src/standalone/session-protocol.mjs +9 -4
  131. package/src/standalone/session-runtime-pool.mjs +393 -0
  132. package/src/standalone/session-runtime-record.mjs +20 -0
  133. package/src/standalone/session-runtime-worker.mjs +203 -0
  134. package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +192 -157
  135. package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +142 -63
  136. package/src/standalone/session-wire.mjs +55 -0
  137. package/src/tui/App.jsx +4 -4
  138. package/src/tui/app/app-format.mjs +1 -1
  139. package/src/tui/app/channel-pickers.mjs +41 -41
  140. package/src/tui/app/input-parsers.mjs +2 -2
  141. package/src/tui/app/onboarding-steps.mjs +2 -2
  142. package/src/tui/app/project-picker.mjs +8 -8
  143. package/src/tui/app/prompt-submit.mjs +15 -17
  144. package/src/tui/app/settings-picker.mjs +25 -25
  145. package/src/tui/app/use-prompt-handlers.mjs +7 -3
  146. package/src/tui/app/use-prompt-queue-history.mjs +1 -1
  147. package/src/tui/app/use-transcript-scroll.mjs +1 -1
  148. package/src/tui/components/ToolExecution.jsx +1 -1
  149. package/src/tui/components/prompt-input/restore-policy.mjs +1 -1
  150. package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
  151. package/src/tui/dist/index.mjs +403 -170
  152. package/src/tui/hooks/useSession.mjs +16 -0
  153. package/src/tui/index.jsx +11 -11
  154. package/src/tui/paste-attachments.mjs +13 -7
  155. package/src/tui/prompt-history-store.mjs +52 -4
  156. package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
  157. package/src/tui/{engine → session}/agent-job-feed.mjs +9 -5
  158. package/src/tui/{engine → session}/boot-profile.mjs +2 -2
  159. package/src/tui/{engine → session}/context-state.mjs +3 -3
  160. package/src/tui/{engine → session}/labels.mjs +2 -2
  161. package/src/tui/{engine → session}/live-share.mjs +28 -14
  162. package/src/tui/{engine → session}/notice-text.mjs +2 -2
  163. package/src/tui/{engine → session}/notification-plan.mjs +3 -3
  164. package/src/tui/session/oauth-flows.mjs +140 -0
  165. package/src/tui/{engine → session}/prompt-history.mjs +3 -3
  166. package/src/tui/{engine → session}/queue-helpers.mjs +8 -8
  167. package/src/tui/{engine → session}/render-timing.mjs +2 -2
  168. package/src/tui/{engine → session}/session-api-ext.mjs +42 -21
  169. package/src/tui/{engine → session}/session-api.mjs +17 -61
  170. package/src/tui/{engine → session}/session-flow.mjs +5 -3
  171. package/src/tui/{engine → session}/session-stats.mjs +3 -3
  172. package/src/tui/{engine → session}/tool-approval.mjs +3 -3
  173. package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
  174. package/src/tui/{engine → session}/tool-card-results.mjs +4 -4
  175. package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
  176. package/src/tui/{engine → session}/tool-result-text.mjs +3 -3
  177. package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
  178. package/src/tui/{engine → session}/turn.mjs +29 -107
  179. package/src/tui/{engine-local-session.mjs → session-local.mjs} +72 -78
  180. package/src/tui/session.mjs +22 -0
  181. package/src/ui/statusline-segments.mjs +1 -1
  182. package/src/ui/statusline.mjs +1 -1
  183. package/vendor/ink/build/components/App.js +10 -1
  184. package/src/session-runtime/memory-daemon-probe.mjs +0 -61
  185. package/src/standalone/engine-daemon-protocol.mjs +0 -32
  186. package/src/tui/engine/turn-watchdog.mjs +0 -92
  187. package/src/tui/engine.mjs +0 -22
  188. package/src/tui/hooks/useEngine.mjs +0 -16
  189. /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
  190. /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
  191. /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
  192. /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
  193. /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
@@ -1,15 +1,15 @@
1
- // Transcript spill buffer + engine item state, extracted from engine.mjs.
1
+ // Transcript spill buffer + session runtime item state, extracted from session-local.mjs.
2
2
  /**
3
- * src/tui/engine.mjs - the engine<->React bridge (React-free).
3
+ * src/tui/session-local.mjs - the session runtime<->React bridge (React-free).
4
4
  *
5
5
  * Runs mixdog's session manager outside React and exposes a tiny subscribable
6
6
  * store. The React/ink layer consumes it via useSyncExternalStore
7
- * (see hooks/useEngine.mjs).
7
+ * (see hooks/useSession.mjs).
8
8
  *
9
- * Pure/stateless helpers live in ./engine/* (boot-profile, session-stats,
9
+ * Pure/stateless helpers live in ./session/* (boot-profile, session-stats,
10
10
  * labels, notice-text, tool-result-text, tool-call-fields, agent-envelope,
11
11
  * queue-helpers) and are re-exported here so the public surface is unchanged.
12
- * This file keeps the stateful createEngineSession store + notification plan.
12
+ * This file keeps the stateful session store + notification plan.
13
13
  */
14
14
  import { performance } from 'node:perf_hooks';
15
15
  import { mkdtempSync, readFileSync, readdirSync, rmSync, statSync, watch, writeFileSync } from 'node:fs';
@@ -32,8 +32,8 @@ import { presentErrorText } from '../../runtime/shared/err-text.mjs';
32
32
  import { sessionPath } from '../../runtime/agent/orchestrator/session/store/paths-heartbeat.mjs';
33
33
  import { listThemes, getThemeSetting, setThemeSetting } from '../theme.mjs';
34
34
  import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-markdown.mjs';
35
- import { bootProfile } from '../engine/boot-profile.mjs';
36
- import { createSessionStats, applyUsageDelta } from '../engine/session-stats.mjs';
35
+ import { bootProfile } from '../session/boot-profile.mjs';
36
+ import { createSessionStats, applyUsageDelta } from '../session/session-stats.mjs';
37
37
  import {
38
38
  pickVerb,
39
39
  pickDoneVerb,
@@ -41,26 +41,26 @@ import {
41
41
  compactEventLabel,
42
42
  compactEventDetail,
43
43
  projectNameFromPath,
44
- } from '../engine/labels.mjs';
45
- import { polishNoticeText } from '../engine/notice-text.mjs';
44
+ } from '../session/labels.mjs';
45
+ import { polishNoticeText } from '../session/notice-text.mjs';
46
46
  import {
47
47
  toolResultText,
48
48
  toolAggregateDetailFallback,
49
49
  toolGroupedDisplayFallback,
50
50
  toolErrorDisplay,
51
- } from '../engine/tool-result-text.mjs';
51
+ } from '../session/tool-result-text.mjs';
52
52
  import {
53
53
  toolCallId,
54
54
  toolResultCallId,
55
55
  toolCallName,
56
56
  toolCallArgs,
57
- } from '../engine/tool-call-fields.mjs';
57
+ } from '../session/tool-call-fields.mjs';
58
58
  import {
59
59
  parseBackgroundTaskEnvelope,
60
60
  parseSyntheticAgentMessage,
61
61
  toolResultStatus,
62
62
  isErrorToolStatus,
63
- } from '../engine/agent-envelope.mjs';
63
+ } from '../session/agent-envelope.mjs';
64
64
  import {
65
65
  queuePriorityValue,
66
66
  defaultQueuePriority,
@@ -74,39 +74,39 @@ import {
74
74
  mergePastedImages,
75
75
  mergePastedTexts,
76
76
  callCommitCallbacks,
77
- } from '../engine/queue-helpers.mjs';
77
+ } from '../session/queue-helpers.mjs';
78
78
  import {
79
79
  resolveTuiRuntimeNotificationDelivery,
80
- } from '../engine/notification-plan.mjs';
81
- import { yieldToRenderer } from '../engine/render-timing.mjs';
80
+ } from '../session/notification-plan.mjs';
81
+ import { yieldToRenderer } from '../session/render-timing.mjs';
82
82
  import {
83
83
  aggregateRawResult,
84
84
  aggregateBucketForCategory,
85
85
  aggregateSummaries,
86
86
  assignAggregateSummaryOrder,
87
- } from '../engine/tool-result-status.mjs';
88
- import { createToolApproval } from '../engine/tool-approval.mjs';
89
- import { createToolCardResults } from '../engine/tool-card-results.mjs';
90
- import { createAgentJobFeed } from '../engine/agent-job-feed.mjs';
91
- import { appendAgentResponseTail } from '../engine/agent-response-tail.mjs';
87
+ } from '../session/tool-result-status.mjs';
88
+ import { createToolApproval } from '../session/tool-approval.mjs';
89
+ import { createToolCardResults } from '../session/tool-card-results.mjs';
90
+ import { createAgentJobFeed } from '../session/agent-job-feed.mjs';
91
+ import { appendAgentResponseTail } from '../session/agent-response-tail.mjs';
92
92
  import {
93
93
  appendTuiSteeringPersist,
94
94
  dropTuiSteeringPersist,
95
95
  drainTuiSteeringPersist,
96
96
  flushTuiSteeringPersist,
97
- } from '../engine/tui-steering-persist.mjs';
98
- import { createContextState } from '../engine/context-state.mjs';
99
- import { recomputePromptHistory } from '../engine/prompt-history.mjs';
97
+ } from '../session/tui-steering-persist.mjs';
98
+ import { createContextState } from '../session/context-state.mjs';
99
+ import { recomputePromptHistory } from '../session/prompt-history.mjs';
100
100
  import {
101
101
  appendPromptHistory,
102
102
  buildMergedPromptHistory,
103
103
  loadPromptHistory,
104
104
  } from '../prompt-history-store.mjs';
105
- import { createSessionFlow } from '../engine/session-flow.mjs';
106
- import { createRunTurn } from '../engine/turn.mjs';
107
- import { createEngineApi } from '../engine/session-api.mjs';
108
- import { createFrameBatchedStorePublisher } from '../engine/frame-batched-store.mjs';
109
- import { createLiveShare, liveSharePipePath } from '../engine/live-share.mjs';
105
+ import { createSessionFlow } from '../session/session-flow.mjs';
106
+ import { createRunTurn } from '../session/turn.mjs';
107
+ import { createSessionApi } from '../session/session-api.mjs';
108
+ import { createFrameBatchedStorePublisher } from '../session/frame-batched-store.mjs';
109
+ import { createLiveShare, liveSharePipePath } from '../session/live-share.mjs';
110
110
  import { displayModelName } from '../../ui/model-display.mjs';
111
111
 
112
112
  export const TUI_DEBUG = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_TUI_DEBUG || ''));
@@ -484,14 +484,14 @@ export function refillTranscriptViewOverlap(viewItems, previousLiveItems, nextLi
484
484
  }
485
485
 
486
486
  // Re-export the shared tool-result/notification helpers so importers (and tests)
487
- // keep resolving them from engine.mjs unchanged.
487
+ // keep resolving them from session-local.mjs unchanged.
488
488
  export { toolResultText, toolAggregateDetailFallback, toolGroupedDisplayFallback };
489
489
  export { parseBackgroundTaskEnvelope };
490
- // Re-export the pure notification delivery plan (moved to ./engine/notification-plan.mjs)
491
- // so importers/tests keep resolving resolveTuiRuntimeNotificationDelivery from engine.mjs.
490
+ // Re-export the pure notification delivery plan (moved to ./session/notification-plan.mjs)
491
+ // so importers/tests keep resolving resolveTuiRuntimeNotificationDelivery from session-local.mjs.
492
492
  export { resolveTuiRuntimeNotificationDelivery };
493
493
 
494
- export function replaceEngineItemsState({
494
+ export function replaceSessionItemsState({
495
495
  state,
496
496
  items,
497
497
  itemIndexById,
@@ -513,9 +513,9 @@ export function replaceEngineItemsState({
513
513
  };
514
514
  }
515
515
 
516
- // Shared by the live engine and focused transcript tests so revision/tail
516
+ // Shared by the live session runtime and focused transcript tests so revision/tail
517
517
  // regressions exercise the exact mutation implementation used in production.
518
- export function createEngineItemMutators({
518
+ export function createSessionItemMutators({
519
519
  getState,
520
520
  set,
521
521
  itemIndexById,
@@ -1,24 +1,23 @@
1
1
  /**
2
- * src/tui/engine/turn.mjs - lead TUI turn engine (createRunTurn). Extracted from engine.mjs.
2
+ * src/tui/session/turn.mjs - lead TUI turn session runtime (createRunTurn). Extracted from session-local.mjs.
3
3
  */
4
- import { aggregateToolCategoryEntry, aggregateDoneCategories, classifyToolCategory, formatAggregateDetail, summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
4
+ import { aggregateToolCategoryEntries, aggregateDoneCategories, classifyToolCategory, formatAggregateDetail, summarizeToolResult } from '../../runtime/shared/tool-surface.mjs';
5
5
  import { applyUsageDelta } from './session-stats.mjs';
6
6
  import { pickVerb, pickDoneVerb, compactEventLabel, compactEventDetail } from './labels.mjs';
7
7
  import { toolResultText, toolErrorDisplay, stripShellExitHeader } from './tool-result-text.mjs';
8
8
  import { toolCallId, toolResultCallId, toolCallName, toolCallArgs } from './tool-call-fields.mjs';
9
9
  import { promptDisplayText, STEERING_SUPPRESSED_DISPLAY } from './queue-helpers.mjs';
10
10
  import { TUI_FRAME_MS, yieldToRenderer } from './render-timing.mjs';
11
- import { createTurnWatchdog } from './turn-watchdog.mjs';
12
11
  import { aggregateRawResult, aggregateBucketForCategory, aggregateSummaries, assignAggregateSummaryOrder, failureDetailText, toolCallOutcome } from './tool-result-status.mjs';
13
12
 
14
13
  export const STREAM_BATCH_INTERVAL_MS = TUI_FRAME_MS;
15
14
 
16
15
  export function createRunTurn(bag) {
17
16
  const {
18
- runtime, nextId, tuiDebug, LEAD_TURN_TIMEOUT_MS, flags, pending, itemIndexById, getState, set, flushEmit, flushEmitImmediate, pushItem, appendItems, patchItem, replaceItems, updateStreamingTail: updateStreamingTailFromStore, settleStreamingTail: settleStreamingTailFromStore, clearStreamingTail: clearStreamingTailFromStore, pushNotice, pushUserOrSyntheticItem, markToolCallActive, markToolCallDone, clearActiveToolSummary, agentStatusState, routeState, transcriptRouteMetadata, syncContextStats, denyAllToolApprovals, requestToolApproval, patchToolCardResult, flushToolResults, flushDeferredExecutionPendingResumeKick, drain, drainPendingSteering,
17
+ runtime, nextId, tuiDebug, flags, pending, itemIndexById, getState, set, flushEmit, flushEmitImmediate, pushItem, appendItems, patchItem, replaceItems, updateStreamingTail: updateStreamingTailFromStore, settleStreamingTail: settleStreamingTailFromStore, clearStreamingTail: clearStreamingTailFromStore, pushNotice, pushUserOrSyntheticItem, markToolCallActive, markToolCallDone, clearActiveToolSummary, agentStatusState, routeState, transcriptRouteMetadata, syncContextStats, denyAllToolApprovals, requestToolApproval, patchToolCardResult, flushToolResults, flushDeferredExecutionPendingResumeKick, drainPendingSteering,
19
18
  } = bag;
20
19
  // Small fallbacks keep isolated createRunTurn harnesses source-compatible;
21
- // the real engine supplies atomic implementations that also maintain revision.
20
+ // the real session runtime supplies atomic implementations that also maintain revision.
22
21
  const updateStreamingTail = updateStreamingTailFromStore || ((id, patch = {}, extra = {}) => {
23
22
  set({
24
23
  streamingTail: { ...(getState().streamingTail || {}), ...patch, kind: 'assistant', id, streaming: true },
@@ -48,11 +47,8 @@ export function createRunTurn(bag) {
48
47
  const turnTranscriptMeta = { ...baseTranscriptMeta, completionVerb };
49
48
  flags.pendingTranscriptMeta = null;
50
49
  const { at: _userItemAt, ...turnRouteMeta } = turnTranscriptMeta;
51
- // Per-turn epoch. Force-release (watchdog grace) bumps the shared counter so
52
- // this turn's own eventual `finally` — which may run LONG after force-release
53
- // already started a new turn that reuses the per-session mutex — can detect
54
- // it is stale and skip all shared-getState() writes (busy, flags.activePromptRestore,
55
- // turndone, drain kick). Neutralizes the stale unwind without touching the mutex.
50
+ // Per-turn epoch prevents a disposed/replaced surface from publishing a
51
+ // terminal snapshot after another turn has become the visible owner.
56
52
  const turnEpoch = ++flags.leadTurnEpoch;
57
53
  const isCurrentTurn = () => !flags.disposed && flags.leadTurnEpoch === turnEpoch;
58
54
  const inputBaseline = getState().stats.inputTokens;
@@ -79,80 +75,8 @@ export function createRunTurn(bag) {
79
75
  let assistantText = '';
80
76
  let currentAssistantId = null;
81
77
 
82
- tuiDebug(`runTurn start turn=${turnIndex} pending=${pending.length} timeoutMs=${LEAD_TURN_TIMEOUT_MS}`);
83
- // ── Idle watchdog ─────────────────────────────────────────────────────
84
- // If this turn stops making observable progress (provider call stuck),
85
- // trip after the cap: abort the in-flight run via the existing interrupt
86
- // path (runtime.abort, the same one Esc uses), which rejects the pending
87
- // runtime.ask() with a SessionClosedError so the normal cancelled-turn
88
- // teardown runs. Keep this progress-based rather than total wall-clock:
89
- // long multi-tool turns can legitimately exceed 5m while still producing
90
- // model/tool events.
91
- let watchdogGraceTimer = null;
92
- const configuredLeadToolMaxMs = Number(process.env.MIXDOG_LEAD_TOOL_MAX_MS);
93
- const leadToolMaxMs = Number.isFinite(configuredLeadToolMaxMs) && configuredLeadToolMaxMs > 0
94
- ? configuredLeadToolMaxMs
95
- : 30 * 60 * 1000;
96
- // Scheduling/liveness lives in turn-watchdog.mjs; the trip ACTION stays here
97
- // because it unwinds this turn's UI state.
98
- const watchdog = createTurnWatchdog({
99
- runtime,
100
- timeoutMs: LEAD_TURN_TIMEOUT_MS,
101
- toolMaxMs: leadToolMaxMs,
102
- isCurrentTurn,
103
- onTrip: ({ idleMs, lastProgressLabel }) => {
104
- if (_batchTimer !== null) {
105
- clearTimeout(_batchTimer);
106
- _batchTimer = null;
107
- }
108
- _pendingTextFlush = false;
109
- _pendingThinkFlush = false;
110
- _pendingThinkingLastEndedAt = 0;
111
- const elapsed = Date.now() - startedAt;
112
- tuiDebug(`runTurn WATCHDOG TRIP turn=${turnIndex} elapsedMs=${elapsed} idleMs=${idleMs} lastProgress=${lastProgressLabel} — aborting stuck turn`);
113
- pushNotice(`Turn timed out after ${Math.round(idleMs / 1000)}s idle (last progress: ${lastProgressLabel}) — aborting stuck request. Input will be released shortly if abort does not unwind.`, 'warn', { transcript: true });
114
- try { runtime.abort('watchdog'); } catch {}
115
- // Belt-and-suspenders: if runtime.abort() did not reject runtime.ask()
116
- // (unwind starved), hard-release the turn after a short grace so the
117
- // React store is never left with busy=true and no drain in flight.
118
- watchdogGraceTimer = setTimeout(() => {
119
- if (flags.disposed) return;
120
- if (!getState().busy) return;
121
- if (flags.leadTurnEpoch !== turnEpoch) return; // a newer turn already owns the store
122
- tuiDebug(`runTurn WATCHDOG FORCE-RELEASE turn=${turnIndex} — abort unwind starved`);
123
- denyAllToolApprovals('turn timed out');
124
- clearActiveToolSummary();
125
- flushToolResults([], toolCards, cardByCallId, toolGroups, resultsDone, { finalize: true, cancelled: true });
126
- finalizeToolHeaders();
127
- clearDeferredTimers();
128
- flags.flushDeferredBeforeImmediatePush = null;
129
- if (currentAssistantId && currentAssistantText.trim()) {
130
- settleStreamingTail(currentAssistantId, { text: currentAssistantText });
131
- } else {
132
- clearStreamingTail(currentAssistantId);
133
- }
134
- // Bump the epoch FIRST so this (still-stuck) turn's later finally becomes
135
- // a no-op for shared getState() and cannot corrupt the turn we hand off to.
136
- flags.leadTurnEpoch++;
137
- set({ busy: false, spinner: null, thinking: null, lastTurn: null });
138
- flags.activePromptRestore = null;
139
- if (flags.draining) flags.draining = false;
140
- if (pending.length > 0) void drain();
141
- // busy→false here bypasses both the normal turn-end flush and the
142
- // drain-finally flush, so a deferred completion kick would never re-arm.
143
- // Fire it explicitly (idempotent: guarded by deferred flag + !busy).
144
- flushDeferredExecutionPendingResumeKick();
145
- flushEmitImmediate?.();
146
- }, 5_000);
147
- watchdogGraceTimer.unref?.();
148
- },
149
- });
150
- const clearWatchdog = () => {
151
- watchdog.clear();
152
- if (watchdogGraceTimer) { clearTimeout(watchdogGraceTimer); watchdogGraceTimer = null; }
153
- };
154
- const markTurnProgress = (label) => watchdog.markProgress(label);
155
- watchdog.arm();
78
+ tuiDebug(`runTurn start turn=${turnIndex} pending=${pending.length}`);
79
+ const markTurnProgress = () => isCurrentTurn();
156
80
  let currentAssistantText = '';
157
81
  // Segments sealed as their own assistant item(s) this turn via
158
82
  // commitAssistantSegment (e.g. a tool preamble, then a no-newline tail
@@ -585,11 +509,11 @@ export function createRunTurn(bag) {
585
509
  let _lastNewlineIdx = -1; // offset of the last completed-line '\n' found so far
586
510
  let _emittedNewlineIdx = -2; // newline offset backing _emittedVisibleText (-2 forces first compute)
587
511
  let _emittedVisibleText = ''; // cached visible slice for the current newline offset
588
- // Engine-local streaming scalars. Neither responseLength nor thinkingText is
512
+ // Session runtime-local streaming scalars. Neither responseLength nor thinkingText is
589
513
  // rendered per-token by any consumer: App reads getState().thinking only as a
590
514
  // boolean (App.jsx `!!(getState().thinking || liveSpinner?.thinking)`) and the
591
515
  // Spinner takes outputTokens, not responseLength. So we keep these growing
592
- // values in engine-local vars and publish to the store only on a visible
516
+ // values in session runtime-local vars and publish to the store only on a visible
593
517
  // transition (thinking on↔off), a completed visible text line, tool/usage
594
518
  // updates, or finalization — not on every 8ms streaming flush.
595
519
  let _publishedThinkingActive = false; // last thinking boolean pushed to store
@@ -690,7 +614,7 @@ export function createRunTurn(bag) {
690
614
  // reads the thinking flag + timing anchors — none of them render the
691
615
  // growing thinkingText. So publish the thinking boolean only on the
692
616
  // OFF→ON transition (or when compacting toggles the flag), not on every
693
- // 8ms reasoning chunk. The full thinkingText stays engine-local and is
617
+ // 8ms reasoning chunk. The full thinkingText stays session runtime-local and is
694
618
  // emitted at finalize via the normal spinner/thinking teardown.
695
619
  const nextThinkingActive = !compactingActive;
696
620
  // Skip the push when the published thinking boolean is unchanged: neither
@@ -909,10 +833,11 @@ export function createRunTurn(bag) {
909
833
  const bucket = aggregateBucketForCategory(category, { agentBatch });
910
834
  const callId = toolCallId(c);
911
835
  const callKey = callId || `__tool_${toolCards.length}_${i}`;
912
- // The old App scan counted multi-pattern calls via
913
- // aggregateToolCategoryEntry(...).count, not a flat 1. Derive the same
836
+ // The old App scan counted multi-pattern calls via category work
837
+ // units, not a flat 1. Derive the same
914
838
  // count here so the incremental Explore/Search summary matches.
915
- const activeCount = Number(aggregateToolCategoryEntry(name, args, category)?.count || 1);
839
+ const categoryEntries = aggregateToolCategoryEntries(name, args, category);
840
+ const activeCount = categoryEntries.reduce((total, entry) => total + Number(entry.count || 1), 0);
916
841
  // Track Explore/Search calls as active for the incremental prompt-
917
842
  // line summary; cleared when their result lands or the turn ends.
918
843
  markToolCallActive(callKey, category, activeCount, Date.now());
@@ -961,15 +886,16 @@ export function createRunTurn(bag) {
961
886
  continue;
962
887
  }
963
888
 
964
- const categoryEntry = aggregateToolCategoryEntry(name, args, category);
965
889
  const aggregateCard = ensureAggregateCard(bucket);
966
890
  if (shellAfterEdit) aggregateCard.verifyShell = true;
967
- if (!aggregateCard.categories.has(categoryEntry.key)) aggregateCard.categoryOrder.push(categoryEntry.key);
968
- const prevCategory = aggregateCard.categories.get(categoryEntry.key);
969
- aggregateCard.categories.set(categoryEntry.key, {
970
- ...categoryEntry,
971
- count: Number(prevCategory?.count || 0) + Number(categoryEntry.count || 1),
972
- });
891
+ for (const categoryEntry of categoryEntries) {
892
+ if (!aggregateCard.categories.has(categoryEntry.key)) aggregateCard.categoryOrder.push(categoryEntry.key);
893
+ const prevCategory = aggregateCard.categories.get(categoryEntry.key);
894
+ aggregateCard.categories.set(categoryEntry.key, {
895
+ ...categoryEntry,
896
+ count: Number(prevCategory?.count || 0) + Number(categoryEntry.count || 1),
897
+ });
898
+ }
973
899
  aggregateCard.calls.set(callKey, { name, args, category, summary: null, summarySeq: null, isError: false, isCallError: false, isExitError: false, exitCode: null, resultText: null, resolved: false, completedEarly: false });
974
900
  touchedAggregates.add(aggregateCard);
975
901
  const card = { itemId: aggregateCard.itemId, callId: callKey, done: false, aggregate: aggregateCard };
@@ -1164,7 +1090,7 @@ export function createRunTurn(bag) {
1164
1090
  flushStreamBatch();
1165
1091
  },
1166
1092
  onReasoningDelta: (chunk) => {
1167
- if (!isCurrentTurn() || watchdog.tripped) return;
1093
+ if (!isCurrentTurn()) return;
1168
1094
  if (String(chunk ?? '')) {
1169
1095
  if (!markTurnProgress('reasoning-delta')) return;
1170
1096
  }
@@ -1281,13 +1207,9 @@ export function createRunTurn(bag) {
1281
1207
  } finally {
1282
1208
  const isStaleUnwind = !isCurrentTurn();
1283
1209
  if (!isStaleUnwind) denyAllToolApprovals(cancelled ? 'turn cancelled' : 'turn finished');
1284
- // Turn is unwinding normally (or via abort) — cancel the idle watchdog and
1285
- // its force-release grace so they never fire on a live turn.
1286
- clearWatchdog();
1287
1210
  clearLiveTailTimer();
1288
- // If the watchdog force-release already fired, a NEWER turn now owns the
1289
- // shared store (busy, flags.activePromptRestore, turndone, drain). This stale
1290
- // unwind must NOT write shared getState() or it corrupts that turn.
1211
+ // A replaced/disposed surface must not write shared state owned by the
1212
+ // current visible turn.
1291
1213
  let closingItems = [];
1292
1214
  if (deferredEntries.length) {
1293
1215
  if (!isStaleUnwind) {
@@ -1307,7 +1229,7 @@ export function createRunTurn(bag) {
1307
1229
  if (!isStaleUnwind) flags.flushDeferredBeforeImmediatePush = null;
1308
1230
  closeThinkingSegment();
1309
1231
  if (isStaleUnwind) {
1310
- tuiDebug(`runTurn STALE UNWIND turn=${turnIndex} — force-released; skipping shared UI/state writes`);
1232
+ tuiDebug(`runTurn STALE UNWIND turn=${turnIndex} — skipping shared UI/state writes`);
1311
1233
  } else {
1312
1234
  if (currentAssistantId && getState().streamingTail?.id === currentAssistantId) {
1313
1235
  if (currentAssistantText.trim()) {
@@ -1323,7 +1245,7 @@ export function createRunTurn(bag) {
1323
1245
  flags.activePromptRestore = null;
1324
1246
  const elapsedMs = Date.now() - startedAt;
1325
1247
  const thinkingElapsedMs = thinkingStartedAt ? accumulatedThinkingMs : 0;
1326
- // responseLength is engine-local now (not pushed per-token), so compute the
1248
+ // responseLength is session runtime-local now (not pushed per-token), so compute the
1327
1249
  // fallback from the live accumulator instead of the possibly-stale
1328
1250
  // getState().spinner.responseLength. Final-only / non-streaming turns never
1329
1251
  // accumulate `assistantText` (only currentAssistantText is set at the
@@ -1374,7 +1296,7 @@ export function createRunTurn(bag) {
1374
1296
  flushEmit?.();
1375
1297
  _publishedThinkingActive = false; // turn teardown cleared getState().thinking
1376
1298
  const finalStatus = cancelled ? 'cancelled' : (failed ? 'failed' : 'done');
1377
- tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt}${watchdog.tripped ? ' watchdogTripped=1' : ''} pending=${pending.length}`);
1299
+ tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt} pending=${pending.length}`);
1378
1300
  return finalStatus;
1379
1301
  }
1380
1302