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
@@ -1,8 +1,9 @@
1
1
  // slash-dispatch/session-commands.mjs
2
2
  // Slash commands that act on the session itself: clear/new, compact, goal,
3
- // resume, inherit, doctor and quit. Busy guards refuse the destructive ones
3
+ // resume, fork (alias inherit), doctor and quit. Busy guards refuse the destructive ones
4
4
  // while a turn or session command is running.
5
5
  import { presentErrorText } from '../../../runtime/shared/err-text.mjs';
6
+ import { BTW_USAGE_HINT } from '../btw-controller.mjs';
6
7
 
7
8
  function compactFailureNotice(error) {
8
9
  if (error == null || String(error).trim() === '') return 'Compact failed.';
@@ -63,6 +64,18 @@ export const sessionCommands = {
63
64
  return true;
64
65
  },
65
66
 
67
+ // Side question: allowed mid-turn; never queued, steered, or interrupting.
68
+ btw(ctx, arg) {
69
+ const { store, deps } = ctx;
70
+ const question = String(arg || '').trim();
71
+ if (!question) {
72
+ store.pushNotice(BTW_USAGE_HINT, 'warn');
73
+ return true;
74
+ }
75
+ deps.btw.ask(question);
76
+ return true;
77
+ },
78
+
66
79
  goal(ctx, arg) {
67
80
  const { store } = ctx;
68
81
  void Promise.resolve(store.goalControl?.({ command: arg }))
@@ -91,18 +104,18 @@ export const sessionCommands = {
91
104
  return true;
92
105
  },
93
106
 
94
- inherit(ctx) {
107
+ fork(ctx, _arg, rawName) {
95
108
  const { state, store } = ctx;
96
- if (state.busy) return refuseWhileBusy(store, 'inherit');
109
+ if (state.busy) return refuseWhileBusy(store, rawName === 'inherit' ? 'inherit' : 'fork');
97
110
  void Promise.resolve(store.inheritSession?.())
98
111
  .then((result) => {
99
112
  if (!result) {
100
- store.pushNotice('nothing to inherit', 'warn');
113
+ store.pushNotice('nothing to fork', 'warn');
101
114
  return;
102
115
  }
103
- store.pushNotice(`inherited ${result.messages} messages into ${result.sessionId}`, 'info');
116
+ store.pushNotice(`forked ${result.messages} messages into ${result.sessionId}`, 'info');
104
117
  })
105
- .catch((e) => store.pushNotice(`inherit failed: ${e?.message || e}`, 'error'));
118
+ .catch((e) => store.pushNotice(`fork failed: ${e?.message || e}`, 'error'));
106
119
  return true;
107
120
  },
108
121
 
@@ -0,0 +1,12 @@
1
+ // Global tool-approval shortcuts: Esc/d/n deny, a/y approve. Returns the
2
+ // decision to resolve with, or null. Question approvals (ask_user) are
3
+ // answered only inside QuestionPanel, so typed letters and Esc never reach here.
4
+ export function approvalShortcutDecision(toolApproval, input, key) {
5
+ if (!toolApproval || toolApproval.kind === 'question') return null;
6
+ const value = String(input || '')
7
+ .trim()
8
+ .toLowerCase();
9
+ if (key.escape || value === 'd' || value === 'n') return { approved: false, reason: 'denied by user' };
10
+ if (value === 'a' || value === 'y') return { approved: true, reason: 'approved by user' };
11
+ return null;
12
+ }
@@ -8,6 +8,7 @@
8
8
  import { useInput } from 'ink';
9
9
  import { overlayBlocksGlobalTranscriptScroll } from './slash-commands.mjs';
10
10
  import { copyActiveSelection } from './use-global-key-input/copy-first.mjs';
11
+ import { approvalShortcutDecision } from './use-global-key-input/approval-shortcut.mjs';
11
12
  import { decodeSelectionMoveChord } from './use-global-key-input/shift-arrow.mjs';
12
13
  export function useGlobalKeyInput({
13
14
  store,
@@ -43,18 +44,10 @@ export function useGlobalKeyInput({
43
44
  // settle it through the same path a real release takes, before any branch
44
45
  // below reads the selection.
45
46
  settleStuckDrag?.();
46
- if (toolApproval) {
47
- const value = String(input || '')
48
- .trim()
49
- .toLowerCase();
50
- if (key.escape || value === 'd' || value === 'n') {
51
- store.resolveToolApproval?.(toolApproval.id, { approved: false, reason: 'denied by user' });
52
- return;
53
- }
54
- if (value === 'a' || value === 'y') {
55
- store.resolveToolApproval?.(toolApproval.id, { approved: true, reason: 'approved by user' });
56
- return;
57
- }
47
+ const decision = approvalShortcutDecision(toolApproval, input, key);
48
+ if (decision) {
49
+ store.resolveToolApproval?.(toolApproval.id, decision);
50
+ return;
58
51
  }
59
52
  if (key.ctrl && (input === 'c' || input === 'C')) {
60
53
  copyActiveSelection({ promptSelectionRef, dragRef, copySelection, showSelectionCopyHint });
@@ -36,6 +36,8 @@ export function usePromptHandlers({
36
36
  usagePanel,
37
37
  closeUsagePanel,
38
38
  contextPanel,
39
+ btwPanel,
40
+ closeBtwPanel,
39
41
  // paste-attachment helpers (owned by App, threaded in explicitly)
40
42
  installPastedImages,
41
43
  clearPastedImagesSnapshot,
@@ -72,6 +74,13 @@ export function usePromptHandlers({
72
74
  return true;
73
75
  }
74
76
 
77
+ // /btw side panel: Esc closes it (cancelling a pending ask) before the
78
+ // turn interrupt / draft handling sees the key.
79
+ if (btwPanel) {
80
+ closeBtwPanel();
81
+ return true;
82
+ }
83
+
75
84
  if (meta.phase === 'clear-arm') {
76
85
  showPromptHint('Esc again to clear', 'plain', PROMPT_ESCAPE_HINT_TIMEOUT_MS);
77
86
  return true;
@@ -107,6 +116,8 @@ export function usePromptHandlers({
107
116
  },
108
117
  [
109
118
  contextPanel,
119
+ btwPanel,
120
+ closeBtwPanel,
110
121
  surface,
111
122
  usagePanel,
112
123
  closeUsagePanel,
@@ -0,0 +1,53 @@
1
+ /**
2
+ * components/BtwPanel.jsx — transient `/btw` side-question panel above the prompt.
3
+ */
4
+ import { useEffect, useState } from 'react';
5
+ import { Box, Text, useInput } from 'ink';
6
+ import { theme } from '../theme.mjs';
7
+ import { Markdown } from './Markdown.jsx';
8
+ import { BTW_CHROME_ROWS, btwContentRows, btwScrollMax, btwScrollOffset } from '../app/btw-controller.mjs';
9
+
10
+ export function BtwPanel({ panel, columns = 80, panelRows, themeEpoch = 0 }) {
11
+ const bodyRows = Math.max(1, (panelRows ?? BTW_CHROME_ROWS + 1) - BTW_CHROME_ROWS);
12
+ const total = btwContentRows(panel, columns);
13
+ const maxOffset = btwScrollMax(total, bodyRows);
14
+ const [offset, setOffset] = useState(0);
15
+ const top = Math.min(offset, maxOffset);
16
+ const scrollable = maxOffset > 0;
17
+
18
+ // A new question or a settled answer starts from the top.
19
+ useEffect(() => {
20
+ setOffset(0);
21
+ }, [panel.question, panel.status]);
22
+
23
+ useInput((_input, key) => {
24
+ if (!scrollable || (!key.pageUp && !key.pageDown)) return;
25
+ setOffset((current) => btwScrollOffset(key, current, bodyRows, total));
26
+ });
27
+
28
+ let body;
29
+ if (panel.status === 'pending') body = <Text color={theme.inactive}>answering… (Esc to cancel)</Text>;
30
+ else if (panel.status === 'error') body = <Text color={theme.error}>{panel.text}</Text>;
31
+ else
32
+ body = (
33
+ <Markdown themeEpoch={themeEpoch} columns={columns}>
34
+ {panel.text}
35
+ </Markdown>
36
+ );
37
+ return (
38
+ <Box flexDirection="column" flexShrink={0} width="100%">
39
+ <Box borderStyle="round" borderColor={theme.promptBorder} paddingX={1} width="100%" flexDirection="column">
40
+ <Text color={theme.panelTitle} wrap="truncate-end">{`btw · ${panel.question}`}</Text>
41
+ <Box flexDirection="column" height={bodyRows} overflow="hidden">
42
+ <Box flexDirection="column" flexShrink={0} marginTop={-top}>
43
+ {body}
44
+ </Box>
45
+ </Box>
46
+ <Text color={theme.inactive}>
47
+ {scrollable ? `PgUp/PgDn scroll (${top + 1}-${Math.min(total, top + bodyRows)}/${total}) · ` : ''}
48
+ Esc to close · not saved to the conversation
49
+ </Text>
50
+ </Box>
51
+ </Box>
52
+ );
53
+ }
@@ -0,0 +1,275 @@
1
+ /**
2
+ * components/QuestionPanel.jsx — ask_user multiple-choice panel.
3
+ *
4
+ * Same round-border chrome as Picker. Digits/↑↓ choose, Space toggles
5
+ * (multi-select), ←/→ switch questions, Enter confirms the current question
6
+ * (and sends on the last), Esc dismisses. The last row is an inline text
7
+ * input; while typing, every key belongs to the input (no slash commands).
8
+ */
9
+ import { useRef, useState } from 'react';
10
+ import { Box, Text, useInput, usePaste, useStdin } from 'ink';
11
+ import stringWidth from 'string-width';
12
+ import { theme } from '../theme.mjs';
13
+ import { truncatePanelText, padPanelCells } from './panel-cell-text.mjs';
14
+ import { isCsiPrivateReply } from './prompt-input/key-signals.mjs';
15
+ import {
16
+ CUSTOM_LABEL,
17
+ appendCapped,
18
+ answerFor,
19
+ buildAnswers,
20
+ initialCursor,
21
+ optionLayout,
22
+ planQuestionView,
23
+ questionHelp,
24
+ } from './question-panel-model.mjs';
25
+
26
+ const MAX_TAB_CELLS = 10;
27
+
28
+ function cleanTyped(value) {
29
+ return String(value ?? '')
30
+ .replace(/\x1b\[[\d;?<]*[A-Za-z~]/g, '')
31
+ .replace(/[\r\n\t]+/g, ' ')
32
+ .replace(/[\x00-\x1f\x7f]/g, '');
33
+ }
34
+
35
+ export function QuestionPanel({ questions, columns = 80, rows, onSubmit, onDismiss }) {
36
+ const { isRawModeSupported } = useStdin();
37
+ const [state, setState] = useState(() => ({
38
+ qi: 0,
39
+ cursors: questions.map(initialCursor),
40
+ drafts: questions.map(() => ({ selected: [], text: '' })),
41
+ typing: false,
42
+ buf: '',
43
+ }));
44
+ const ref = useRef(state);
45
+ ref.current = state;
46
+ const commit = (next) => {
47
+ ref.current = next;
48
+ setState(next);
49
+ };
50
+ const last = questions.length - 1;
51
+
52
+ const withDraft = (s, qi, patch) => ({
53
+ ...s,
54
+ drafts: s.drafts.map((d, i) => (i === qi ? { ...d, ...patch } : d)),
55
+ });
56
+
57
+ // Confirm the current question and advance (or send on the last one).
58
+ const advance = (s) => {
59
+ if (s.qi >= last) {
60
+ onSubmit(buildAnswers(questions, s.drafts));
61
+ return;
62
+ }
63
+ commit({ ...s, qi: s.qi + 1, typing: false, buf: '' });
64
+ };
65
+
66
+ const typeInto = (input) => {
67
+ const s = ref.current;
68
+ const text = cleanTyped(input);
69
+ if (text) commit({ ...s, buf: appendCapped(s.buf, text) });
70
+ };
71
+
72
+ usePaste((text) => {
73
+ if (ref.current.typing) typeInto(text);
74
+ }, { isActive: isRawModeSupported });
75
+
76
+ useInput(
77
+ (input, key) => {
78
+ const s = ref.current;
79
+ const raw = String(input ?? '');
80
+ if (/(?:\x1b)?\[<\d+;\d+;\d+[Mm]/.test(raw) || isCsiPrivateReply(raw)) return;
81
+ const question = questions[s.qi];
82
+ const n = question.options.length;
83
+ const multi = question.multi_select === true;
84
+
85
+ if (s.typing) {
86
+ if (key.escape) return commit({ ...s, typing: false, buf: '' });
87
+ if (key.return) {
88
+ const text = s.buf.trim();
89
+ const draft = s.drafts[s.qi];
90
+ const next = withDraft(s, s.qi, { text, selected: multi ? draft.selected : [] });
91
+ if (text) return advance({ ...next, typing: false, buf: '' });
92
+ return commit({ ...next, typing: false, buf: '' });
93
+ }
94
+ if (key.backspace || key.delete) return commit({ ...s, buf: Array.from(s.buf).slice(0, -1).join('') });
95
+ if (key.ctrl || key.meta || key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.tab) return;
96
+ return typeInto(raw);
97
+ }
98
+
99
+ if (key.escape) return onDismiss();
100
+ const setCursor = (cursor) => commit({ ...s, cursors: s.cursors.map((c, i) => (i === s.qi ? cursor : c)) });
101
+ const choose = (idx, toggle) => {
102
+ const draft = s.drafts[s.qi];
103
+ let selected;
104
+ if (multi && toggle) {
105
+ selected = draft.selected.includes(idx) ? draft.selected.filter((i) => i !== idx) : [...draft.selected, idx];
106
+ } else {
107
+ selected = multi ? draft.selected : [idx];
108
+ }
109
+ const cursors = s.cursors.map((c, i) => (i === s.qi ? idx : c));
110
+ commit({ ...withDraft(s, s.qi, { selected, text: multi ? draft.text : '' }), cursors });
111
+ };
112
+ const cursor = s.cursors[s.qi];
113
+ if (key.upArrow) return setCursor((cursor - 1 + n + 1) % (n + 1));
114
+ if (key.downArrow) return setCursor((cursor + 1) % (n + 1));
115
+ if (key.leftArrow || key.rightArrow) {
116
+ const qi = Math.max(0, Math.min(last, s.qi + (key.leftArrow ? -1 : 1)));
117
+ return commit({ ...s, qi });
118
+ }
119
+ if (key.return) {
120
+ if (cursor === n) return commit({ ...s, typing: true, buf: s.drafts[s.qi].text });
121
+ const draft = s.drafts[s.qi];
122
+ let next = s;
123
+ if (!multi) next = withDraft(s, s.qi, { selected: [cursor], text: '' });
124
+ else if (draft.selected.length === 0 && !draft.text.trim()) next = withDraft(s, s.qi, { selected: [cursor] });
125
+ return advance(next);
126
+ }
127
+ if (raw === ' ' && !key.ctrl && !key.meta) {
128
+ if (cursor < n) choose(cursor, true);
129
+ return;
130
+ }
131
+ if (/^[1-9]$/.test(raw)) {
132
+ const idx = Number(raw) - 1;
133
+ if (idx < n) choose(idx, true);
134
+ else if (idx === n) setCursor(n);
135
+ }
136
+ },
137
+ { isActive: isRawModeSupported }
138
+ );
139
+
140
+ const question = questions[state.qi];
141
+ const multi = question.multi_select === true;
142
+ const layout = optionLayout(question, columns);
143
+ const n = question.options.length;
144
+ const cursor = state.cursors[state.qi];
145
+ const draft = state.drafts[state.qi];
146
+ const avail = layout.avail;
147
+
148
+ const title = questions.length > 1 ? `Question ${state.qi + 1}/${questions.length}` : 'Question';
149
+ const tabs =
150
+ questions.length > 1
151
+ ? questions.map((q, i) => ({
152
+ text: truncatePanelText(String(q.header || `Q${i + 1}`), MAX_TAB_CELLS),
153
+ answered: answerFor(q, state.drafts[i]) !== null,
154
+ current: i === state.qi,
155
+ }))
156
+ : [];
157
+
158
+ const indexText = (i) => padPanelCells(`${i + 1}.`, layout.indexCells - 1) + ' ';
159
+ const renderLine = (selected, children, key) => {
160
+ const text = selected ? { backgroundColor: theme.selectionBackground, color: theme.selectionText } : {};
161
+ return (
162
+ <Text key={key} {...text} wrap="truncate">
163
+ {children}
164
+ </Text>
165
+ );
166
+ };
167
+
168
+ const plan = planQuestionView(question, columns, rows ?? Infinity, cursor);
169
+ const out = [];
170
+ const optionRow = (i) => {
171
+ const row = layout.rows[i];
172
+ const selected = i === cursor;
173
+ const checked = draft.selected.includes(i);
174
+ const lead = `${selected ? '› ' : ' '}${indexText(i)}`;
175
+ const fill = ' '.repeat(Math.max(0, avail - layout.prefixCells - stringWidth(row.label + row.suffix + row.detail)));
176
+ out.push(
177
+ renderLine(
178
+ selected,
179
+ <>
180
+ {lead}
181
+ {multi ? (
182
+ <Text color={selected ? theme.selectionText : checked ? theme.success : undefined}>
183
+ {checked ? '[x] ' : '[ ] '}
184
+ </Text>
185
+ ) : null}
186
+ {row.label}
187
+ {row.suffix ? <Text color={selected ? theme.selectionText : theme.suggestion}>{row.suffix}</Text> : null}
188
+ {row.detail ? <Text color={selected ? theme.selectionText : theme.subtle}>{row.detail}</Text> : null}
189
+ {selected ? fill : ''}
190
+ </>,
191
+ `o${i}`
192
+ )
193
+ );
194
+ };
195
+
196
+ const customSelected = cursor === n;
197
+ const customLead = `${customSelected ? '› ' : ' '}${indexText(n)}`;
198
+ const markerPad = multi ? ' ' : '';
199
+ let customBody;
200
+ if (state.typing) {
201
+ const room = Math.max(1, avail - stringWidth(customLead) - stringWidth(markerPad) - 1);
202
+ let shown = Array.from(state.buf).slice(-room).join('');
203
+ while (stringWidth(shown) > room) shown = Array.from(shown).slice(1).join('');
204
+ customBody = `${shown}▌`;
205
+ } else if (draft.text) {
206
+ customBody = truncatePanelText(draft.text, Math.max(1, avail - stringWidth(customLead) - stringWidth(markerPad)));
207
+ } else {
208
+ customBody = CUSTOM_LABEL;
209
+ }
210
+ const customColor = customSelected ? theme.selectionText : draft.text && !state.typing ? theme.text : theme.inactive;
211
+ const customRow = () =>
212
+ renderLine(
213
+ customSelected,
214
+ <>
215
+ {customLead}
216
+ {markerPad}
217
+ <Text color={state.typing ? undefined : customColor}>{customBody}</Text>
218
+ {customSelected ? ' '.repeat(Math.max(0, avail - stringWidth(customLead + markerPad + customBody))) : ''}
219
+ </>,
220
+ 'custom'
221
+ );
222
+
223
+ const moreRow = (key, text) => (
224
+ <Text key={key} color={theme.subtle} wrap="truncate">
225
+ {` ${text}`}
226
+ </Text>
227
+ );
228
+ if (plan.above > 0) out.push(moreRow('above', `↑ ${plan.above} more`));
229
+ for (const item of plan.items) {
230
+ if (item.index === n) out.push(customRow());
231
+ else optionRow(item.index);
232
+ }
233
+ if (plan.below > 0) out.push(moreRow('below', `↓ ${plan.below} more`));
234
+
235
+ const help = questionHelp({ multi, typing: state.typing, last: state.qi >= last, count: n, questionCount: questions.length });
236
+
237
+ return (
238
+ <Box flexDirection="column" flexShrink={0} width="100%">
239
+ <Box flexDirection="column" borderStyle="round" borderColor={theme.promptBorder} paddingX={1} width="100%">
240
+ <Box flexDirection="row" justifyContent="space-between">
241
+ <Box flexShrink={1}>
242
+ <Text wrap="truncate">
243
+ <Text color={theme.panelTitle}>{title}</Text>
244
+ {tabs.map((tab, i) => (
245
+ <Text key={i}>
246
+ {' '}
247
+ {tab.current ? (
248
+ <Text backgroundColor={theme.selectionBackground} color={theme.selectionText}>
249
+ {` ${tab.answered ? '✓ ' : ''}${tab.text} `}
250
+ </Text>
251
+ ) : tab.answered ? (
252
+ <Text color={theme.success}>{`✓ ${tab.text}`}</Text>
253
+ ) : (
254
+ <Text color={theme.subtle}>{tab.text}</Text>
255
+ )}
256
+ </Text>
257
+ ))}
258
+ </Text>
259
+ </Box>
260
+ <Box flexShrink={0} marginLeft={2}>
261
+ <Text color={theme.subtle}>{help}</Text>
262
+ </Box>
263
+ </Box>
264
+ <Text> </Text>
265
+ {plan.qLines.map((line, i) => (
266
+ <Text key={i} color={theme.text} wrap="truncate">
267
+ {line}
268
+ </Text>
269
+ ))}
270
+ <Text> </Text>
271
+ {out}
272
+ </Box>
273
+ </Box>
274
+ );
275
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * components/question-panel-model.mjs — pure model for the ask_user
3
+ * QuestionPanel: answer building, row layout and reserved panel height.
4
+ */
5
+ import stringWidth from 'string-width';
6
+ import wrapAnsi from 'wrap-ansi';
7
+ import { truncatePanelText } from './panel-cell-text.mjs';
8
+
9
+ export const CUSTOM_LABEL = '직접 입력…';
10
+ export const RECOMMENDED_SUFFIX = ' (추천)';
11
+ export const MULTI_HINT = ' (여러 개 선택 가능)';
12
+ // border(2) + title + blank + blank-after-question; question rows added separately.
13
+ const FIXED_ROWS = 5;
14
+ // Panel interior = frame − 2 border − 2 paddingX.
15
+ const CHROME_COLUMNS = 4;
16
+
17
+ export function questionsOf(toolApproval) {
18
+ const list = toolApproval?.args?.questions;
19
+ return Array.isArray(list) ? list : [];
20
+ }
21
+
22
+ // One question's answer from its draft ({ selected: optionIndex[], text? }).
23
+ export function answerFor(question, draft) {
24
+ const options = Array.isArray(question?.options) ? question.options : [];
25
+ const selected = [...new Set(draft?.selected || [])]
26
+ .filter((i) => Number.isInteger(i) && i >= 0 && i < options.length)
27
+ .sort((a, b) => a - b)
28
+ .map((i) => options[i].label);
29
+ const text = String(draft?.text ?? '').trim();
30
+ if (selected.length === 0 && !text) return null;
31
+ return text ? { selected, text } : { selected };
32
+ }
33
+
34
+ export function buildAnswers(questions, drafts) {
35
+ return questions.map((question, i) => answerFor(question, drafts?.[i]));
36
+ }
37
+
38
+ const DETAIL_GAP = ' ';
39
+ const MIN_DETAIL_CELLS = 6;
40
+
41
+ // Geometry of one question's option rows at `columns` frame width: one line
42
+ // per option (its label, the recommended mark, then its one-sentence
43
+ // tradeoff in the cells left, cut with an ellipsis).
44
+ export function optionLayout(question, columns) {
45
+ const options = Array.isArray(question?.options) ? question.options : [];
46
+ const total = options.length + 1;
47
+ const indexCells = stringWidth(`${total}. `);
48
+ const markerCells = question?.multi_select ? 4 : 0; // "[x] "
49
+ const prefixCells = 2 + indexCells + markerCells; // "› " + "1. " + marker
50
+ const avail = Math.max(10, columns - CHROME_COLUMNS);
51
+ const labelCap = Math.max(4, avail - prefixCells);
52
+ const rows = options.map((option, i) => {
53
+ const suffix = question?.recommended === i ? RECOMMENDED_SUFFIX : '';
54
+ const label = truncatePanelText(String(option.label ?? ''), Math.max(1, labelCap - stringWidth(suffix)));
55
+ const left = labelCap - stringWidth(label + suffix) - DETAIL_GAP.length;
56
+ const text = String(option.description ?? '').trim();
57
+ const detail = text && left >= MIN_DETAIL_CELLS ? `${DETAIL_GAP}${truncatePanelText(text, left)}` : '';
58
+ return { label, suffix, detail };
59
+ });
60
+ return { rows, prefixCells, avail, indexCells };
61
+ }
62
+
63
+ export const MAX_ANSWER_CHARS = 4000;
64
+ const MIN_OPTION_ROWS = 3;
65
+
66
+ // Append `text` to `base`, never exceeding MAX_ANSWER_CHARS UTF-16 units (the
67
+ // unit the runtime checks), and never splitting a character.
68
+ export function appendCapped(base, text) {
69
+ let out = base;
70
+ for (const char of text) {
71
+ if (out.length + char.length > MAX_ANSWER_CHARS) break;
72
+ out += char;
73
+ }
74
+ return out;
75
+ }
76
+
77
+ export function questionLines(question, columns) {
78
+ const shown = `${question?.question ?? ''}${question?.multi_select ? MULTI_HINT : ''}`;
79
+ return wrapAnsi(shown, Math.max(1, columns - CHROME_COLUMNS), { trim: false, hard: true }).split('\n');
80
+ }
81
+
82
+ function ellipsize(line, width) {
83
+ return `${truncatePanelText(line, Math.max(1, width - 1)).replace(/…$/, '')}…`;
84
+ }
85
+
86
+ // Fit the panel into `rows` (Infinity = unbudgeted). Always keeps the question
87
+ // text (capped, leaving MIN_OPTION_ROWS for options) and the highlighted
88
+ // option; the rest scroll with ↑/↓ more indicators.
89
+ export function planQuestionView(question, columns, rows, cursor) {
90
+ const layout = optionLayout(question, columns);
91
+ const n = layout.rows.length;
92
+ let qLines = questionLines(question, columns);
93
+ const optionBudget = rows - FIXED_ROWS;
94
+ const qCap = Number.isFinite(rows) ? Math.max(1, optionBudget - MIN_OPTION_ROWS) : Infinity;
95
+ if (qLines.length > qCap) {
96
+ qLines = qLines.slice(0, qCap);
97
+ qLines[qCap - 1] = ellipsize(qLines[qCap - 1], Math.max(1, columns - CHROME_COLUMNS));
98
+ }
99
+ const avail = Number.isFinite(rows) ? Math.max(1, rows - FIXED_ROWS - qLines.length) : Infinity;
100
+ const full = Array(n + 1).fill(1);
101
+ const total = n + 1;
102
+ let heights = full;
103
+ if (full.reduce((a, b) => a + b, 0) > avail) {
104
+ heights = full.map((h, i) => (i === cursor ? Math.min(h, Math.max(1, avail - 2)) : 1));
105
+ }
106
+ let start = Math.min(cursor, total - 1);
107
+ let end = start + 1;
108
+ let used = heights[start];
109
+ const fits = (s, e, sum) => sum + (s > 0 ? 1 : 0) + (e < total ? 1 : 0) <= avail;
110
+ if (!fits(start, end, used) && avail < Infinity) {
111
+ // highlighted option alone exceeds budget with indicators: shrink it.
112
+ heights = heights.map((h, i) => (i === cursor ? 1 : h));
113
+ used = 1;
114
+ }
115
+ for (let grew = true; grew; ) {
116
+ grew = false;
117
+ if (end < total && fits(start, end + 1, used + heights[end])) {
118
+ used += heights[end++];
119
+ grew = true;
120
+ }
121
+ if (start > 0 && fits(start - 1, end, used + heights[start - 1])) {
122
+ used += heights[--start];
123
+ grew = true;
124
+ }
125
+ }
126
+ const items = [];
127
+ for (let i = start; i < end; i++) items.push({ index: i, lines: heights[i] });
128
+ return { qLines, items, above: start, below: total - end };
129
+ }
130
+
131
+ function questionRows(question, columns) {
132
+ const optionRows = Array.isArray(question?.options) ? question.options.length : 0;
133
+ return FIXED_ROWS + questionLines(question, columns).length + optionRows + 1; // +1 custom-input row
134
+ }
135
+
136
+ // Reserved floating-panel rows: the tallest question, so switching questions
137
+ // never resizes the panel.
138
+ export function questionPanelRows(questions, columns) {
139
+ return Math.max(FIXED_ROWS + 3, ...questions.map((q) => questionRows(q, columns)));
140
+ }
141
+
142
+ export function initialCursor(question) {
143
+ const n = Array.isArray(question?.options) ? question.options.length : 0;
144
+ const r = question?.recommended;
145
+ return Number.isInteger(r) && r >= 0 && r < n ? r : 0;
146
+ }
147
+
148
+ export function questionHelp({ multi, typing, last, count, questionCount = 1 }) {
149
+ if (typing) return 'Enter Send · Esc Back to list';
150
+ // Several questions: the title row also holds the tabs, so keep help short.
151
+ const parts = questionCount > 1 ? [] : [`1–${count} Choose`, '↑/↓ Move'];
152
+ if (multi) parts.push('Space Toggle');
153
+ if (questionCount > 1) parts.push('←/→ Question');
154
+ parts.push(last ? 'Enter Send' : 'Enter Next', 'Esc Dismiss');
155
+ return parts.join(' · ');
156
+ }