mixdog 1.2.2 → 1.2.3

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 (415) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/provider-pingpong-live.mjs +509 -0
  4. package/src/agents/advisor/AGENT.md +1 -1
  5. package/src/agents/advisor/agent.json +1 -1
  6. package/src/defaults/shipped-history.json +5 -0
  7. package/src/defaults/skills/setup/SKILL.md +6 -1
  8. package/src/defaults/skills/setup/references/actions.md +44 -22
  9. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  10. package/src/headless-exec.mjs +15 -8
  11. package/src/lib/keychain-cjs.cjs +7 -4
  12. package/src/output-styles/common.md +11 -10
  13. package/src/rules/agent/AGENT.md +5 -5
  14. package/src/rules/lead/LEAD.md +5 -5
  15. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  16. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  17. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  19. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  21. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  22. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  23. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +2 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +7 -6
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -10
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  31. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +58 -7
  39. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
  40. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +16 -3
  41. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  42. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +12 -6
  43. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +114 -72
  45. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +37 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  50. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +3 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor.mjs +9 -2
  52. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  53. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  54. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  55. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  56. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  57. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  59. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +25 -8
  60. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +87 -5
  61. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  62. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +13 -13
  63. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +355 -37
  64. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
  65. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +93 -47
  66. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +55 -10
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +26 -8
  68. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  71. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  72. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  73. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  74. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  75. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  76. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  77. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  78. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  80. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  81. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  82. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  83. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +121 -61
  89. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  90. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
  91. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  92. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  93. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +42 -24
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +53 -5
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
  108. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  109. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
  110. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  111. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +25 -4
  112. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  114. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
  119. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
  121. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +71 -24
  122. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  123. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  124. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  125. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  126. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  127. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  130. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +11 -3
  131. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  132. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  133. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  134. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +147 -8
  135. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  136. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +5 -0
  137. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  138. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  139. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  140. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  141. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  142. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  143. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  147. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  148. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  150. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  151. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  154. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
  156. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  157. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  158. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  159. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  160. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  161. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  162. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  163. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  164. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  165. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  167. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  168. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  169. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  170. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  171. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  172. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +63 -20
  173. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +2 -1
  174. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  175. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  179. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +4 -1
  180. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  181. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +16 -0
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  184. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  185. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  187. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  188. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +15 -3
  189. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  190. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  192. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  193. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  195. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  196. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -0
  197. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  198. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  199. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  200. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  201. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  202. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  203. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  204. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  205. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  206. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  207. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  208. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
  209. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
  210. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  211. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  212. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  213. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  214. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  216. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  217. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  218. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  223. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  224. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  225. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  226. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  227. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  228. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  229. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  230. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  231. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  232. package/src/runtime/github/tool.mjs +92 -2
  233. package/src/runtime/memory/index.mjs +1 -0
  234. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  235. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  236. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  237. package/src/runtime/memory/lib/pg/process.mjs +31 -26
  238. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +108 -3
  240. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  241. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  242. package/src/runtime/office/README.md +1 -1
  243. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  244. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  245. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  246. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  247. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  248. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  249. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  250. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  251. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  252. package/src/runtime/office/core/office-sessions.mjs +5 -0
  253. package/src/runtime/office/core/office-transactions.mjs +3 -0
  254. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  255. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  256. package/src/runtime/office/index.mjs +30 -0
  257. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  258. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  259. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  260. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  261. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  262. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  263. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  264. package/src/runtime/shared/buffered-appender.mjs +18 -0
  265. package/src/runtime/shared/config.mjs +78 -18
  266. package/src/runtime/shared/err-text.mjs +6 -0
  267. package/src/runtime/shared/file-permissions.d.mts +6 -1
  268. package/src/runtime/shared/file-permissions.mjs +54 -6
  269. package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
  270. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  271. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  272. package/src/runtime/shared/loopback-listener.mjs +9 -0
  273. package/src/runtime/shared/provider-api-key.mjs +2 -0
  274. package/src/runtime/shared/review-diff.mjs +52 -0
  275. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  276. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  277. package/src/runtime/shared/slow-span.mjs +114 -0
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  279. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  280. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  281. package/src/runtime/shared/tool-card-model.mjs +5 -2
  282. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  283. package/src/runtime/shared/tool-primitives.mjs +13 -0
  284. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  285. package/src/runtime/shared/tool-surface.mjs +18 -3
  286. package/src/runtime/shared/tool-work-units.mjs +40 -4
  287. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  288. package/src/runtime/shared/transcript-writer.mjs +10 -2
  289. package/src/runtime/shared/turn-changes.mjs +268 -100
  290. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  291. package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
  292. package/src/runtime/shared/turn-snapshot.mjs +857 -683
  293. package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
  294. package/src/runtime/shared/user-data-guard.mjs +262 -49
  295. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  296. package/src/runtime/shared/worktrees/contract.mjs +34 -0
  297. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  298. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  299. package/src/runtime/shared/worktrees/git.mjs +8 -3
  300. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  301. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  302. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  303. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  304. package/src/runtime/terminal-bridge/client.mjs +44 -11
  305. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  306. package/src/runtime/tidy/apply.mjs +47 -2
  307. package/src/runtime/tidy/run-engines.mjs +11 -5
  308. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  309. package/src/session-runtime/boot/apis.mjs +13 -5
  310. package/src/session-runtime/boot/facade.mjs +1 -0
  311. package/src/session-runtime/boot/infrastructure.mjs +6 -1
  312. package/src/session-runtime/boot/session.mjs +0 -1
  313. package/src/session-runtime/boot/tools.mjs +4 -1
  314. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  315. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  316. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  317. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  318. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  319. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  320. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  321. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  322. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -3
  323. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  324. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  325. package/src/session-runtime/lifecycle/teardown.mjs +72 -7
  326. package/src/session-runtime/lifecycle-api.mjs +1 -0
  327. package/src/session-runtime/mcp-glue/connect.mjs +17 -5
  328. package/src/session-runtime/model-route/set-route.mjs +2 -2
  329. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  330. package/src/session-runtime/native-web-search.mjs +10 -1
  331. package/src/session-runtime/new-session-config.mjs +4 -2
  332. package/src/session-runtime/provider-auth/login.mjs +4 -0
  333. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  334. package/src/session-runtime/runtime-review-api.mjs +8 -2
  335. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  336. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  337. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  338. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  339. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  340. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  341. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  342. package/src/session-runtime/services/agent-tool.mjs +3 -0
  343. package/src/session-runtime/services/channel-admin.mjs +19 -3
  344. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  345. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  346. package/src/session-runtime/services/provider-admin.mjs +81 -15
  347. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  348. package/src/session-runtime/session-turn-api.mjs +14 -5
  349. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  350. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  351. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  352. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  353. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  354. package/src/session-runtime/skills-api.mjs +3 -5
  355. package/src/session-runtime/tool-surface.mjs +5 -1
  356. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  357. package/src/session-runtime/turn/session-ops.mjs +7 -5
  358. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  359. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  360. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  361. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  362. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  363. package/src/session-runtime/turn/turn-run.mjs +25 -9
  364. package/src/session-runtime/usage-stats-api.mjs +43 -11
  365. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  366. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  367. package/src/session-runtime/workspace/transitions.mjs +190 -15
  368. package/src/standalone/daemon-agent-control.mjs +9 -1
  369. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  370. package/src/standalone/daemon-owner.mjs +17 -4
  371. package/src/standalone/daemon-pg-stop.mjs +51 -0
  372. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  373. package/src/standalone/daemon-stop-budget.mjs +18 -0
  374. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  375. package/src/standalone/daemon-telemetry.mjs +19 -0
  376. package/src/standalone/daemon.mjs +61 -1
  377. package/src/standalone/desktop-service-registry.mjs +5 -0
  378. package/src/standalone/local-session-runtime.mjs +70 -19
  379. package/src/standalone/session-client.mjs +42 -0
  380. package/src/standalone/session-protocol.mjs +7 -0
  381. package/src/standalone/session-proxy/projection.mjs +41 -4
  382. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  383. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  384. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  385. package/src/standalone/session-service-api.mjs +6 -1
  386. package/src/standalone/session-state-patch.d.mts +2 -1
  387. package/src/standalone/session-state-patch.mjs +41 -1
  388. package/src/tui/App.jsx +25 -0
  389. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  390. package/src/tui/app/btw-controller.mjs +118 -0
  391. package/src/tui/app/create-app-pickers.mjs +2 -0
  392. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  393. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  394. package/src/tui/app/slash-commands.mjs +13 -3
  395. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  396. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  397. package/src/tui/app/use-global-key-input.mjs +5 -12
  398. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  399. package/src/tui/components/BtwPanel.jsx +53 -0
  400. package/src/tui/components/QuestionPanel.jsx +275 -0
  401. package/src/tui/components/question-panel-model.mjs +156 -0
  402. package/src/tui/dist/index.mjs +2413 -1465
  403. package/src/tui/session/context-state.mjs +4 -2
  404. package/src/tui/session/session-api/commands.mjs +10 -0
  405. package/src/tui/session/session-api/controls.mjs +2 -2
  406. package/src/tui/session/session-api/integrations.mjs +9 -2
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  408. package/src/tui/session/session-api/lifecycle.mjs +2 -1
  409. package/src/tui/session/session-api/settings.mjs +6 -0
  410. package/src/tui/session/session-api.mjs +1 -0
  411. package/src/tui/session/tool-approval.mjs +10 -7
  412. package/src/tui/session/transcript-spill.mjs +14 -7
  413. package/src/tui/session/turn-stream.mjs +174 -31
  414. package/src/tui/session/turn.mjs +12 -5
  415. package/src/workflows/default/WORKFLOW.md +3 -1
@@ -5,6 +5,7 @@ import { formatLocalAndUtcTimestamp } from '../../../../shared/time-format.mjs';
5
5
  import { SUMMARY_PREFIX } from '../compact.mjs';
6
6
  import { isProtectedContextUserMessage } from '../compact/messages.mjs';
7
7
  import { attachmentTextForPart, isAttachmentReference } from '../../../../attachments/store.mjs';
8
+ import { samePath } from '../../../../shared/worktrees/paths.mjs';
8
9
 
9
10
  export function promptContentText(content) {
10
11
  if (typeof content === 'string') return content;
@@ -144,7 +145,11 @@ function replaceSystemMessageContent(session, target, content) {
144
145
  return true;
145
146
  }
146
147
 
147
- export function refreshSessionBp3Environment(session, cwd) {
148
+ export function refreshSessionBp3Environment(session, requestedCwd) {
149
+ // A staged workspace change (see stageWorkspaceReroot) keeps the system
150
+ // prompt rooted where it was until the next re-root: the `# Session` Cwd
151
+ // stays the rooted one, the change reaches the model as a tail notice.
152
+ const cwd = session?.workspaceReroot?.rootCwd || requestedCwd;
148
153
  // Split layout (bp3EnvSplit): the volatile environment lives in its own
149
154
  // UNMARKED cacheTier:'env' system block; the tier3 core block is never
150
155
  // rewritten, so the refresh cannot invalidate the BP3 cache write.
@@ -242,20 +247,12 @@ export function replaceWorkspaceEnvironment(environmentTail, environmentContext)
242
247
  }
243
248
 
244
249
  /**
245
- * Re-root an existing session's prompt at a new workspace without touching
246
- * the conversation: environment facts, project memory and instruction files
247
- * are replaced in place (never appended), then the `# Session` block is
248
- * rebuilt for `cwd`. Messages are replaced, not mutated, so delta saves write
249
- * a full snapshot.
250
- * @param {object} session
251
- * @param {{ cwd: string, environmentContext?: string, coreMemoryContext?: string, instructionFilesContext?: string }} context
252
- * @returns {boolean}
250
+ * Re-root a session's prompt at a workspace in place: environment facts,
251
+ * project memory and instruction files are replaced (never appended), then
252
+ * the `# Session` block is rebuilt for `cwd`. Messages are replaced, not
253
+ * mutated, so delta saves write a full snapshot.
253
254
  */
254
- export function refreshSessionWorkspaceContext(
255
- session,
256
- { cwd, environmentContext, coreMemoryContext, instructionFilesContext } = {}
257
- ) {
258
- if (!session) return false;
255
+ function rerootSessionWorkspace(session, { cwd, environmentContext, coreMemoryContext, instructionFilesContext }) {
259
256
  if (typeof session.bp3EnvironmentContext === 'string') {
260
257
  session.bp3EnvironmentContext = replaceWorkspaceEnvironment(session.bp3EnvironmentContext, environmentContext);
261
258
  }
@@ -275,7 +272,204 @@ export function refreshSessionWorkspaceContext(
275
272
  return refreshSessionBp3Environment(session, cwd);
276
273
  }
277
274
 
275
+ const SESSION_BLOCK_CWD_RE = /(?:^|\n)# Session\nCwd: ([^\n]+)/;
276
+ const WORKER_ENV_CWD_RE = /(?:^|\n)- Cwd: ([^\n]+?) — the active Project root/;
277
+
278
+ function textOf(message) {
279
+ return typeof message?.content === 'string' ? message.content : '';
280
+ }
281
+
282
+ /**
283
+ * The workspace the session's system prompt currently presents: its Cwd and
284
+ * the environment facts, project memory and instruction-files slices, read
285
+ * back from the blocks refreshSessionBp3Environment/composeSystemPrompt wrote.
286
+ */
287
+ function presentedWorkspace(session) {
288
+ const core = session.bp3EnvSplit === true ? textOf(bp3SystemMessage(session)) : String(session.bp3CoreContext || '');
289
+ const instructionAt = bp3SliceAt(core, INSTRUCTION_FILES_HEADING);
290
+ const head = instructionAt ? core.slice(0, instructionAt.cut) : core;
291
+ const memory = (head ? head.split(BP3_PART_SEPARATOR) : []).find((part) => part.startsWith(CORE_MEMORY_HEADING));
292
+ const tail = typeof session.bp3EnvironmentContext === 'string' ? session.bp3EnvironmentContext : '';
293
+ const facts = tail.split(BP3_PART_SEPARATOR)[0] || '';
294
+ const promptText = [textOf(bpEnvSystemMessage(session)), textOf(bp3SystemMessage(session))].join('\n');
295
+ const cwd = promptText.match(SESSION_BLOCK_CWD_RE)?.[1] || facts.match(WORKER_ENV_CWD_RE)?.[1] || null;
296
+ return {
297
+ cwd: cwd ? cwd.trim() : null,
298
+ environmentContext: facts.startsWith('- ') ? facts.trim() : '',
299
+ coreMemoryContext: memory ? memory.slice(CORE_MEMORY_HEADING.length).trim() : '',
300
+ instructionFilesContext: instructionAt ? core.slice(instructionAt.start).trim() : '',
301
+ };
302
+ }
303
+
304
+ const WORKSPACE_SLICES = ['environmentContext', 'coreMemoryContext', 'instructionFilesContext'];
305
+
306
+ /**
307
+ * A workspace change of a session that already has a conversation: the
308
+ * system prompt stays byte-identical (provider prompt cache intact) and the
309
+ * change is staged on `session.workspaceReroot`:
310
+ * rootCwd — the Cwd the system prompt stays rooted at;
311
+ * seen — what the model has been told (the prompt, then acknowledged notices);
312
+ * target — the current workspace, applied to the prompt at the next re-root
313
+ * (compaction, a route change, a cleared conversation).
314
+ * `undefined` slices keep what is there. The state object is replaced, never
315
+ * mutated, so a transaction snapshot of the field restores it exactly.
316
+ */
317
+ function stageWorkspaceReroot(session, context) {
318
+ const prior = session.workspaceReroot;
319
+ const seen = prior ? prior.seen : presentedWorkspace(session);
320
+ const target = { ...(prior ? prior.target : {}), cwd: String(context.cwd || '').trim() };
321
+ for (const slice of WORKSPACE_SLICES) {
322
+ if (context[slice] !== undefined) target[slice] = trimmedSlice(context[slice]);
323
+ }
324
+ // The move into a worktree belongs to this change only; a later one drops it.
325
+ delete target.move;
326
+ const from = String(context.move?.from || '').trim();
327
+ if (from) target.move = { from, changes: context.move.changes ?? null };
328
+ session.workspaceReroot = {
329
+ version: 1,
330
+ revision: (Number(prior?.revision) || 0) + 1,
331
+ rootCwd: prior ? prior.rootCwd : seen.cwd,
332
+ seen,
333
+ target,
334
+ };
335
+ return true;
336
+ }
337
+
338
+ /**
339
+ * Re-root an existing session's prompt at a new workspace. A session without
340
+ * a conversation is re-rooted in place (no cache to keep); one with a
341
+ * conversation keeps its system messages untouched and stages the change
342
+ * (stageWorkspaceReroot): the model learns it from the tail notice of its
343
+ * next user turn (snapshotPendingWorkspaceNotice), and the next re-root
344
+ * (applyPendingWorkspaceReroot) rewrites the prompt.
345
+ * `move` ({ from, changes: 'carried'|'left'|null }: the session left the
346
+ * project folder `from` for a worktree) adds to the notice what happened to
347
+ * the uncommitted changes and that paths under `from` now resolve under `cwd`.
348
+ * @param {object} session
349
+ * @param {{ cwd: string, environmentContext?: string, coreMemoryContext?: string, instructionFilesContext?: string, move?: { from: string, changes?: 'carried'|'left'|null } }} context
350
+ * @returns {boolean}
351
+ */
352
+ export function refreshSessionWorkspaceContext(session, context = {}) {
353
+ if (!session) return false;
354
+ if (hasUserConversationMessage(session.messages)) return stageWorkspaceReroot(session, context);
355
+ applyPendingWorkspaceReroot(session);
356
+ return rerootSessionWorkspace(session, context);
357
+ }
358
+
359
+ /**
360
+ * Give `stored` (the canonical transcript) the system prefix of `messages`
361
+ * (a running turn's working array built on it), message objects included.
362
+ */
363
+ export function syncSystemPrefix(stored, messages) {
364
+ const systemCount = (list) => {
365
+ let count = 0;
366
+ while (count < list.length && list[count]?.role === 'system') count += 1;
367
+ return count;
368
+ };
369
+ stored.splice(0, systemCount(stored), ...messages.slice(0, systemCount(messages)));
370
+ }
371
+
372
+ /**
373
+ * Apply a staged workspace change to the system prompt (the session's own
374
+ * messages, plus `messages` when a running turn works on another array whose
375
+ * system prefix is the session's) and drop the staged state: the prompt now
376
+ * presents the current workspace, so no notice is due any more.
377
+ * @returns {boolean} whether a staged change was applied
378
+ */
379
+ export function applyPendingWorkspaceReroot(session, messages = session?.messages) {
380
+ const pending = session?.workspaceReroot;
381
+ if (!pending) return false;
382
+ delete session.workspaceReroot;
383
+ const stored = session.messages;
384
+ const other = Array.isArray(messages) && messages !== stored;
385
+ if (other) session.messages = messages;
386
+ try {
387
+ rerootSessionWorkspace(session, pending.target);
388
+ } finally {
389
+ if (other) session.messages = stored;
390
+ }
391
+ if (other && Array.isArray(stored)) syncSystemPrefix(stored, messages);
392
+ return true;
393
+ }
394
+
395
+ function sameWorkspacePath(left, right) {
396
+ return !!left && !!right && samePath(left, right);
397
+ }
398
+
399
+ /** The lines a move from the project folder into a worktree adds to the notice. */
400
+ function workspaceMoveLines(move, cwd) {
401
+ if (!move?.from || sameWorkspacePath(move.from, cwd)) return [];
402
+ const changes =
403
+ move.changes === 'carried'
404
+ ? 'Uncommitted changes were moved into the worktree; ignored files (dependencies, build output, env files) were not.'
405
+ : move.changes === 'left'
406
+ ? `Uncommitted changes were left in ${move.from}; they are not present here.`
407
+ : '';
408
+ return [
409
+ changes,
410
+ `For continued work, substitute ${cwd} for the project root ${move.from} in paths mentioned earlier; absolute paths are not remapped automatically. Re-read affected files before editing them.`,
411
+ ].filter(Boolean);
412
+ }
413
+
414
+ function workspaceNoticeContent({ seen, target }) {
415
+ if (!target?.cwd || sameWorkspacePath(seen?.cwd, target.cwd)) return '';
416
+ const changed = (slice) => target[slice] !== undefined && target[slice] !== (seen?.[slice] || '');
417
+ const sections = [
418
+ [
419
+ '# Working Folder Changed',
420
+ `The working folder is now ${target.cwd}. This replaces the Cwd and environment facts given earlier in this conversation; relative paths and shell commands resolve here.`,
421
+ ...workspaceMoveLines(target.move, target.cwd),
422
+ changed('environmentContext') ? target.environmentContext : '',
423
+ ]
424
+ .filter(Boolean)
425
+ .join('\n'),
426
+ ];
427
+ if (changed('instructionFilesContext')) {
428
+ sections.push(
429
+ target.instructionFilesContext
430
+ ? `The instruction files below replace all previously provided instruction files.\n\n${target.instructionFilesContext}`
431
+ : 'The new working folder has no instruction files: the previously provided instruction files no longer apply.'
432
+ );
433
+ }
434
+ if (changed('coreMemoryContext')) {
435
+ sections.push(
436
+ target.coreMemoryContext
437
+ ? `The core memory below replaces all previously provided core memory.\n\n${CORE_MEMORY_HEADING}${target.coreMemoryContext}`
438
+ : 'The new working folder has no core memory: the previously provided core memory no longer applies.'
439
+ );
440
+ }
441
+ return `<system-reminder>\n${sections.join('\n\n')}\n</system-reminder>`;
442
+ }
443
+
444
+ /**
445
+ * The workspace notice the next user turn carries, or null: nothing when the
446
+ * folder did not change from what the model was told, and only the slices
447
+ * that changed. Lifecycle mirrors deferred-tool-delta.mjs (snapshot → acknowledge
448
+ * when the provider accepted the turn), so a failed turn re-sends it.
449
+ * @returns {{ revision: number, content: string } | null}
450
+ */
451
+ export function snapshotPendingWorkspaceNotice(session) {
452
+ const pending = session?.workspaceReroot;
453
+ if (!pending) return null;
454
+ const content = workspaceNoticeContent(pending);
455
+ return content ? { revision: pending.revision, content } : null;
456
+ }
457
+
458
+ /** The notice of `revision` reached the model: what it was told is now the target. */
459
+ export function acknowledgePendingWorkspaceNotice(session, revision) {
460
+ const pending = session?.workspaceReroot;
461
+ if (!pending || pending.revision !== Number(revision)) return false;
462
+ const seen = { ...pending.seen, cwd: pending.target.cwd };
463
+ for (const slice of WORKSPACE_SLICES) {
464
+ if (pending.target[slice] !== undefined) seen[slice] = pending.target[slice];
465
+ }
466
+ session.workspaceReroot = { ...pending, seen };
467
+ return true;
468
+ }
469
+
278
470
  export function resetSessionBp3Environment(session) {
471
+ // A cleared conversation has no cache to keep: the prompt re-roots now.
472
+ applyPendingWorkspaceReroot(session);
279
473
  if (session?.bp3EnvSplit === true) {
280
474
  const target = bpEnvSystemMessage(session);
281
475
  if (!target || typeof session?.bp3EnvironmentContext !== 'string') return false;
@@ -18,6 +18,7 @@ import { forgetSessionSaveBaseline } from '../store/save-worker.mjs';
18
18
  import { forgetSessionLoadCache } from '../store/load-cache.mjs';
19
19
  import { releaseReadSnapshotScope } from '../../tools/builtin/snapshot-store.mjs';
20
20
  import { cancelNativeTasks } from '../../tools/lib/native-spawn-client.mjs';
21
+ import { forgetSideQuestion } from '../side-question-store.mjs';
21
22
 
22
23
  /**
23
24
  * Close a session. Plants a `closed=true` tombstone on disk with a bumped
@@ -66,7 +67,10 @@ export function closeSession(id, reason = 'manual', opts = {}) {
66
67
  // it once disk generation moves past that. Without this bump the late
67
68
  // write could silently overwrite the session after the user resumes
68
69
  // it back (BL: burned-session late-save clobber).
69
- const newGen = tombstone ? markSessionClosed(id, reason) : bumpSessionGeneration(id, reason);
70
+ const outcome = { absent: false };
71
+ const newGen = tombstone
72
+ ? markSessionClosed(id, reason, { outcome })
73
+ : bumpSessionGeneration(id, reason, { outcome });
70
74
  // Durable lifecycle barrier check. ANY nonnumeric result means the barrier
71
75
  // is not on disk — a write failure, a liveness/ownership veto, a contended
72
76
  // commit lock, or a missing record. Only a failed write publishes a
@@ -76,7 +80,13 @@ export function closeSession(id, reason = 'manual', opts = {}) {
76
80
  // crash checkpoint while the session stays open and resumable on disk — the
77
81
  // way a failed close silently destroys a turn. Leave every runtime
78
82
  // structure intact, surface the cause, and report failure to the caller.
79
- if (typeof newGen !== 'number') {
83
+ //
84
+ // The one exception is a record that was legitimately already gone (never
85
+ // saved, or deleted): the barrier cancelled its pending writes under the
86
+ // commit lock and found no copy on disk or in memory, so there is nothing
87
+ // a late save could clobber and nothing left open to resume. That close
88
+ // succeeds and tears the runtime down normally.
89
+ if (typeof newGen !== 'number' && outcome.absent !== true) {
80
90
  reportCloseBarrierFailure({ id, reason, tombstone, entry });
81
91
  return false;
82
92
  }
@@ -95,7 +105,7 @@ export function closeSession(id, reason = 'manual', opts = {}) {
95
105
  */
96
106
  function reportCloseBarrierFailure({ id, reason, tombstone, entry }) {
97
107
  const commitError = getSessionLifecycleCommitError(id);
98
- let detail = 'no durable barrier written (veto, contended commit, or missing record)';
108
+ let detail = 'no durable barrier written (veto or contended commit)';
99
109
  if (commitError) {
100
110
  const code = commitError.code ? ` (${commitError.code})` : '';
101
111
  detail = `${commitError.message}${code}`;
@@ -163,6 +173,7 @@ function teardownSessionRuntime({ id, entry, reason, tombstone, newGen }) {
163
173
  /* ignore */
164
174
  }
165
175
  }
176
+ forgetSideQuestion(id, { cancel: true });
166
177
  closeProviderConnections(id, `session-close:${reason}`);
167
178
  // Diagnostic: one-line stderr so operators can distinguish the four close
168
179
  // pathways (request-abort / manual / idle-sweep / runner-crash). iterCount
@@ -288,6 +299,7 @@ export function unloadSessionRuntime(id, reason = 'runtime-unload') {
288
299
  if (IN_FLIGHT_STAGES.has(entry.stage)) return false;
289
300
  }
290
301
  _stopToolActivityHeartbeat(id);
302
+ forgetSideQuestion(id, { cancel: true });
291
303
  closeProviderConnections(id, `runtime-unload:${reason}`);
292
304
  releaseSessionReadCaches(id);
293
305
  _clearSessionRuntime(id);
@@ -27,6 +27,7 @@ import { isSessionCompactionBlocked, getSessionAbortSignal, _runtimeEntries } fr
27
27
  import { mintSessionId } from './session-id.mjs';
28
28
  import { hideCompactionArchive } from '../compaction-archive-capture.mjs';
29
29
  import { writeStderrBestEffort } from '../../../../shared/best-effort-stderr.mjs';
30
+ import { forgetSideQuestion } from '../side-question-store.mjs';
30
31
 
31
32
  /** Force-flush session metrics to disk. Used by watchdog terminal-reap (fix B). */
32
33
  export async function flushSessionMetrics(sessionId) {
@@ -91,13 +92,30 @@ export async function updateSessionManualTitle(id, title) {
91
92
  if (!session || session.closed === true) return false;
92
93
  const normalized = normalizeSessionTitle(title);
93
94
  if (!normalized) return false;
94
- if (session.title === normalized && session.titleLocked === true) return false;
95
+ if (session.title === normalized && session.titleLocked === true && session.titleSource === 'user') return false;
95
96
  session.title = normalized;
96
97
  session.titleLocked = true;
98
+ session.titleSource = 'user';
97
99
  session.titleUpdatedAt = Date.now();
98
100
  await saveSessionAsync(session, { expectedGeneration: session.generation });
99
101
  return true;
100
102
  }
103
+ // A title the model chose (session tool). Never overwrites a user title; a
104
+ // legacy locked session without titleSource counts as user-titled. It locks
105
+ // the title against auto-generated ones but stays replaceable by the model.
106
+ export async function updateSessionAiTitle(id, title) {
107
+ const session = loadSession(id);
108
+ if (!session || session.closed === true) return { ok: false, reason: 'not-found' };
109
+ const normalized = normalizeSessionTitle(title);
110
+ if (!normalized) return { ok: false, reason: 'empty-title' };
111
+ if (session.titleLocked === true && session.titleSource !== 'ai') return { ok: false, reason: 'user-title' };
112
+ session.title = normalized;
113
+ session.titleLocked = true;
114
+ session.titleSource = 'ai';
115
+ session.titleUpdatedAt = Date.now();
116
+ await saveSessionAsync(session, { expectedGeneration: session.generation });
117
+ return { ok: true, title: normalized };
118
+ }
101
119
  // --- Clear messages (keep system prompt + provider/model/cwd) ---
102
120
  // Token/provider accounting a cleared transcript starts from; shared by the
103
121
  // live session and the cold fork of its outgoing transcript.
@@ -262,8 +280,10 @@ export async function clearSessionMessages(sessionId, options = {}) {
262
280
 
263
281
  session.messages = keep;
264
282
  // Clear truncates the transcript wholesale; drop the provider prefix
265
- // snapshot so the next send re-baselines instead of history_shrink.
283
+ // snapshot so the next send re-baselines instead of history_shrink, and
284
+ // the request a /btw side question would fork.
266
285
  delete session._providerPrefixGuardState;
286
+ forgetSideQuestion(sessionId);
267
287
  resetSessionBp3Environment(session);
268
288
  Object.assign(session, clearedTokenAccounting(now));
269
289
  session.compaction = {
@@ -372,9 +392,11 @@ export async function rewindSessionMessagesTo(sessionId, options = {}) {
372
392
  const removed = messages.length - start;
373
393
  session.messages = messages.slice(0, start);
374
394
  // The provider cache is keyed on the exact prefix we just truncated;
375
- // the prefix-guard snapshot describes it too and must go with it.
395
+ // the prefix-guard snapshot and the /btw fork request describe it too and
396
+ // must go with it.
376
397
  session.providerState = undefined;
377
398
  delete session._providerPrefixGuardState;
399
+ forgetSideQuestion(sessionId);
378
400
  session.updatedAt = Date.now();
379
401
  session.lastUsedAt = Date.now();
380
402
  await saveSessionAsync(session, { expectedGeneration: session.generation });
@@ -0,0 +1,82 @@
1
+ // manager/session-fallback-title.mjs
2
+ // Durable, transcript-independent fallback title: the first usable
3
+ // user-authored prompt, stored on `session.title` so the conversation keeps a
4
+ // name after compaction replaces the transcript. Leaf module (no session-crud
5
+ // import) so ask-turn-execute, compaction-runner and pre-send-compact can share it.
6
+ import { compactSessionTitle } from '../../runtime-core/session-title.mjs';
7
+ import { cleanSessionPreview, isSessionPreviewNoise, sessionMessageText } from '../../runtime-core/session-text.mjs';
8
+ import { IMPORTED_MESSAGE_SOURCE } from '../imported-message.mjs';
9
+ import { isActualUserInstructionMessage } from '../compact/messages.mjs';
10
+
11
+ const UNTITLED_PLACEHOLDER = /^untitled(?:\s+session)?$/i;
12
+ const MEDIA_PLACEHOLDER = /^\[(?:image|video|file)\]$/i;
13
+ // Attachment envelopes carry payloads, not prose: closed blocks, then a block
14
+ // that lost its closing tag (the rest of the text is payload), then bare
15
+ // self-closing tags. Stripped before truncation so a large payload cannot
16
+ // crowd the prompt out of the bounded source.
17
+ const ATTACHMENT_TAGS = 'file|attachment|document|image|video|pdf';
18
+ const ATTACHMENT_BLOCK = new RegExp(`<(${ATTACHMENT_TAGS})\\b[^>]*>[\\s\\S]*?<\\/\\1\\s*>`, 'gi');
19
+ const ATTACHMENT_UNTERMINATED = new RegExp(`<(?:${ATTACHMENT_TAGS})\\b[^>]*>[\\s\\S]*$`, 'i');
20
+ const ATTACHMENT_SELF_CLOSING = new RegExp(`<(?:${ATTACHMENT_TAGS})\\b[^>]*\\/>`, 'gi');
21
+ const MEDIA_MARKER = /\[(?:Image|Video|File)(?:\s*#?\d+)?(?::[^\]\r\n]*)?\]/gi;
22
+
23
+ function stripAttachmentEnvelopes(text) {
24
+ return text
25
+ .replace(ATTACHMENT_BLOCK, ' ')
26
+ .replace(ATTACHMENT_SELF_CLOSING, ' ')
27
+ .replace(ATTACHMENT_UNTERMINATED, ' ')
28
+ .replace(MEDIA_MARKER, ' ');
29
+ }
30
+
31
+ function isUsableTitle(title) {
32
+ const text = String(title || '').trim();
33
+ return !!text && !UNTITLED_PLACEHOLDER.test(text);
34
+ }
35
+
36
+ /** Bounded single-line title from user-authored text; '' when nothing usable. */
37
+ export function fallbackTitleFromText(content) {
38
+ const raw = sessionMessageText(content);
39
+ if (!raw || /^\s*reference files:/i.test(raw)) return '';
40
+ const prose = stripAttachmentEnvelopes(raw);
41
+ if (isSessionPreviewNoise(prose)) return '';
42
+ const title = compactSessionTitle(cleanSessionPreview(prose, 4_000));
43
+ return isUsableTitle(title) && !MEDIA_PLACEHOLDER.test(title) ? title : '';
44
+ }
45
+
46
+ /** A turn the human typed: provenance lives in `meta` (`source` of an injected
47
+ * goal/notification/compaction turn, `synthetic`), never on the message root. */
48
+ function isHumanAuthoredTurn(message) {
49
+ if (message?.role !== 'user' || message.internal === true || message.synthetic === true) return false;
50
+ const meta = message.meta;
51
+ if (meta && typeof meta === 'object') {
52
+ if (meta.synthetic === true) return false;
53
+ if (meta.source && meta.source !== IMPORTED_MESSAGE_SOURCE) return false;
54
+ }
55
+ return isActualUserInstructionMessage(message);
56
+ }
57
+
58
+ /** Title from the first usable human user message of a transcript. */
59
+ export function fallbackTitleFromMessages(messages) {
60
+ for (const message of Array.isArray(messages) ? messages : []) {
61
+ if (!isHumanAuthoredTurn(message)) continue;
62
+ const title = fallbackTitleFromText(message.content);
63
+ if (title) return title;
64
+ }
65
+ return '';
66
+ }
67
+
68
+ /** Fills `session.title` only when missing. Never touches a locked (manual/AI)
69
+ * or generated title, and leaves generatedTitleStage unset so
70
+ * updateSessionGeneratedTitle can still upgrade it. The caller persists. */
71
+ export function applyFallbackSessionTitle(session, title) {
72
+ if (!session || !isUsableTitle(title) || isUsableTitle(session.title)) return false;
73
+ if (session.titleLocked === true || session.generatedTitleStage) return false;
74
+ session.title = title;
75
+ session.titleUpdatedAt = Date.now();
76
+ return true;
77
+ }
78
+
79
+ export function backfillFallbackSessionTitle(session, messages) {
80
+ if (!session || isUsableTitle(session.title)) return false;
81
+ return applyFallbackSessionTitle(session, fallbackTitleFromMessages(messages));
82
+ }
@@ -37,8 +37,9 @@ import { getAgentRuntimeSync, warnAgentRuntimeResolveFailureOnce } from './agent
37
37
  import { ensureCodexWireSessionId, mintSessionId } from './session-id.mjs';
38
38
  import { providerCacheKey } from './provider-cache-key.mjs';
39
39
  import { buildSessionRecord, normalizeDesktopSessionMetadata, sessionOriginFields } from './session-record.mjs';
40
- import { refreshSessionBp3Environment } from './prompt-utils.mjs';
40
+ import { applyPendingWorkspaceReroot, refreshSessionBp3Environment } from './prompt-utils.mjs';
41
41
  import { isWorktreeBound } from '../../../../shared/worktrees/contract.mjs';
42
+ import { applyAgentToolExposure } from '../loop/tool-exposure-migration.mjs';
42
43
 
43
44
  // Owner liveness (attach-on-resume guard) and the durable turn-checkpoint
44
45
  // recovery it gates; re-exported so prior importers of this module stay
@@ -243,6 +244,8 @@ export function createSession(opts) {
243
244
  origin: sessionOriginFields(opts, { profile, presetObj, providerCacheOpts, surface }),
244
245
  });
245
246
  session.providerCacheOptsOverride = Boolean(resolved.providerCacheOpts);
247
+ // A non-native agent sends core + bridge, built from its filtered tools.
248
+ applyAgentToolExposure(session);
246
249
  refreshSessionBp3Environment(session, opts.cwd);
247
250
  // In-process registry + async debounced save: same-process create → load
248
251
  // reads live memory; disk flush is for cross-process / restart durability.
@@ -367,6 +370,11 @@ function resetSessionForRouteChange(id, session, previousModel, previousProvider
367
370
  session.lastContextTokensUpdatedAt = Date.now();
368
371
  session.lastContextTokensStaleAfterCompact = false;
369
372
  session.providerState = undefined;
373
+ // The previous route's prefix snapshot measured another prompt (model line,
374
+ // stripped replay); the new chain baselines on its first send.
375
+ delete session._providerPrefixGuardState;
376
+ // The new route resolves its own wire before its first request.
377
+ session.wireRoute = null;
370
378
  const prepared = _prepareResumeTools(session, session.preset || 'full');
371
379
  session.tools = prepared.tools;
372
380
  session.toolSpec = prepared.toolSpec;
@@ -384,6 +392,7 @@ function resetSessionForRouteChange(id, session, previousModel, previousProvider
384
392
  ]) {
385
393
  if (Array.isArray(session[key])) session[key] = filterModelEditToolNames(session[key], session.model);
386
394
  }
395
+ applyAgentToolExposure(session, { existing: true });
387
396
  _refreshSessionRuleVariantsForModel(session, previousModel, previousProvider);
388
397
  _forgetPreparedResume(id);
389
398
  }
@@ -436,6 +445,9 @@ export function updateSessionRoute(id, route = {}, options = {}) {
436
445
  // route and set sessionStartMetaInjected, so the ask-time refresh guard
437
446
  // skips it and an empty-session route change would otherwise keep the old
438
447
  // model line in the system prompt (model self-identity confusion).
448
+ // A new route starts a cold cache: a staged working-folder change re-roots
449
+ // the prompt now instead of riding a notice.
450
+ if (routeChanged) applyPendingWorkspaceReroot(session);
439
451
  refreshSessionBp3Environment(session, session.cwd);
440
452
  session.updatedAt = Date.now();
441
453
  setLiveSession(session);
@@ -513,7 +525,12 @@ function _refreshResumedTools(session, sessionId, preset) {
513
525
  // Keep the persisted tool mode in sync on resume (see createSession note).
514
526
  session.toolSpec = prepared.toolSpec;
515
527
  session.tools = prepared.tools;
516
- const missing = oldTools.filter((t) => !session.tools.find((n) => n.name === t.name));
528
+ applyAgentToolExposure(session, { existing: true });
529
+ // A bridge surface keeps the rest of its tools in the catalog.
530
+ const available = new Set(
531
+ [...session.tools, ...(session.deferredToolCatalog || [])].map((tool) => tool?.name).filter(Boolean)
532
+ );
533
+ const missing = oldTools.filter((t) => !available.has(t.name));
517
534
  if (missing.length) {
518
535
  process.stderr.write(
519
536
  `[session] Warning: ${missing.length} tools no longer available: ${missing.map((t) => t.name).join(', ')}\n`
@@ -109,6 +109,9 @@ export function buildSessionRecord({ opts, route, presetObj, surface, prompt, me
109
109
  codexWireSessionId: providerName === 'openai-oauth' ? mintUuidV7() : null,
110
110
  provider: providerName,
111
111
  model: modelName,
112
+ // Resolved and pinned before the first request (wire-route.mjs). null —
113
+ // not absent — marks a session created under the per-model wire table.
114
+ wireRoute: null,
112
115
  messages,
113
116
  contextWindow: contextMeta.contextWindow,
114
117
  rawContextWindow: contextMeta.rawContextWindow,
@@ -48,6 +48,9 @@ const SESSION_ROUTE_TOOL_RANK = new Map(
48
48
  const FILESYSTEM_TOOL_NAMES = new Set(['code_graph', 'find', 'glob', 'list', 'grep', 'read', 'edit', 'apply_patch']);
49
49
  const READONLY_FS_TOOL_NAMES = new Set(['code_graph', 'find', 'glob', 'list', 'grep', 'read']);
50
50
 
51
+ // Lead-only control tools: never part of a worker/sub-agent session surface.
52
+ export const AGENT_WORKER_DENIED_TOOLS = Object.freeze(new Set(['agent', 'ask_user', 'session']));
53
+
51
54
  export function finalizeSessionToolList(
52
55
  tools,
53
56
  { schemaAllowedTools = null, disallowedTools = null, ownerIsAgent = false } = {}
@@ -64,7 +67,7 @@ export function finalizeSessionToolList(
64
67
  out = out.filter((t) => !denySet.has(String(t?.name || '').toLowerCase()));
65
68
  }
66
69
  if (ownerIsAgent) {
67
- out = out.filter((t) => String(t?.name || '').toLowerCase() !== 'agent');
70
+ out = out.filter((t) => !AGENT_WORKER_DENIED_TOOLS.has(String(t?.name || '').toLowerCase()));
68
71
  out = orderSessionTools(out);
69
72
  }
70
73
  return out;
@@ -123,9 +123,10 @@ export {
123
123
  updateSessionStatus,
124
124
  updateSessionGeneratedTitle,
125
125
  updateSessionManualTitle,
126
+ updateSessionAiTitle,
126
127
  flushSessionMetrics,
127
128
  } from './manager/session-crud.mjs';
128
- export { deleteSession, listOwnedAgentSessionIds } from './store.mjs';
129
+ export { deleteSession, listOwnedAgentSessionIds, fenceSessionWrites } from './store.mjs';
129
130
  // Read-only parsed-session access (desktop pane peek): no resume, no
130
131
  // ownership, no liveness side effects.
131
132
  export { loadSession } from './store.mjs';
@@ -31,6 +31,8 @@ import { bumpUsageMetricsEpoch } from './manager/usage-metrics.mjs';
31
31
  import { resetReadStateAfterCompaction } from './read-dedup.mjs';
32
32
  import { scheduleCompactionArchive, stampCompactionMessageIds } from './compaction-archive-capture.mjs';
33
33
  import { writeStderrBestEffort } from '../../../shared/best-effort-stderr.mjs';
34
+ import { applyPendingWorkspaceReroot } from './manager/prompt-utils.mjs';
35
+ import { backfillFallbackSessionTitle } from './manager/session-fallback-title.mjs';
34
36
  import {
35
37
  acknowledgePendingGoalReminder,
36
38
  markPendingGoalReminder,
@@ -378,6 +380,7 @@ function adoptCompactedTranscript(ctx, run) {
378
380
  const { state } = ctx;
379
381
  const { messages, sessionRef, sessionId, opts } = state;
380
382
  const replaced = messages.slice();
383
+ if (sessionRef) backfillFallbackSessionTitle(sessionRef, replaced);
381
384
  messages.length = 0;
382
385
  messages.push(...run.compacted);
383
386
  // Display-only archive of the dropped conversation; the projection and the
@@ -388,6 +391,9 @@ function adoptCompactedTranscript(ctx, run) {
388
391
  ctx.droppedMessages = archived?.dropped ?? 0;
389
392
  ctx.droppedIdentities = archived?.identities ?? null;
390
393
  acknowledgeGoalReminder(sessionRef, run);
394
+ // The compacted transcript breaks the cache anyway: a staged working-folder
395
+ // change re-roots the system prompt now and needs no notice any more.
396
+ if (sessionRef) applyPendingWorkspaceReroot(sessionRef, messages);
391
397
  resetReadStateAfterCompaction(sessionId);
392
398
  // Cross-turn receipts reference tool results that compaction just removed
393
399
  // from context; a stub for them would point at nothing. Clear in place so
@@ -44,6 +44,7 @@ import { agentContextOverflowError } from './loop/context-overflow.mjs';
44
44
  import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
45
45
  import { isOutputLimitStopReason } from './loop/termination.mjs';
46
46
  import { instrumentSendCallbacks } from './send-instrumentation.mjs';
47
+ import { rejectedToolSchema } from './tool-schema-rejection.mjs';
47
48
 
48
49
  function normalizedIncompleteUsage(raw) {
49
50
  if (!raw || typeof raw !== 'object') return undefined;
@@ -405,6 +406,7 @@ async function recoverFromSendError(sendErr, state) {
405
406
  transportRetriesUsed = 0,
406
407
  imageStripUsed = false,
407
408
  thinkingReplayRepairUsed = false,
409
+ toolSchemaDropUsed = false,
408
410
  relayWitness,
409
411
  } = state;
410
412
  const outcome = readStreamOutcome(sendErr, relayWitness);
@@ -446,6 +448,17 @@ async function recoverFromSendError(sendErr, state) {
446
448
  ) {
447
449
  return retryTransport(sendErr, state);
448
450
  }
451
+ // A 400 naming one tool's input schema: the session drops that tool (with
452
+ // the notice) and the turn is replayed once without it. Nothing was
453
+ // generated, and the same body would fail every later turn.
454
+ if (toolSchemaDropUsed !== true) {
455
+ const exclusion = rejectedToolSchema(sendErr, state);
456
+ if (exclusion) {
457
+ logLoop(`[loop] provider rejected tool schema "${exclusion.name}" (${loopTag(state)}); dropping it and replaying once`);
458
+ traceLoop(state, 'tool_schema_drop', { tool: exclusion.name });
459
+ return { action: 'retry_tool_schema_drop', exclusion };
460
+ }
461
+ }
449
462
  // Anthropic replay-shape refusal: the latest assistant turn carries
450
463
  // thinking blocks the API will not accept back (a thinking run it did not
451
464
  // produce, or an empty text block). This is deterministic — the same body