@hyperdrive.bot/fleet-server 0.3.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (991) hide show
  1. package/.env.example +31 -0
  2. package/README.md +111 -0
  3. package/dist/scripts/dev-runner.js +37 -0
  4. package/dist/scripts/mcp-stdio-socket-bridge-cli.mjs +62 -0
  5. package/dist/scripts/supervisor-entrypoint.js +156 -0
  6. package/dist/scripts/supervisor-log-config.js +31 -0
  7. package/dist/scripts/supervisor.js +345 -0
  8. package/dist/server/executable-resolution/executable-resolution.d.ts +10 -0
  9. package/dist/server/executable-resolution/executable-resolution.js +118 -0
  10. package/dist/server/executable-resolution/windows.d.ts +18 -0
  11. package/dist/server/executable-resolution/windows.js +65 -0
  12. package/dist/server/extensions/daemon-backend.d.ts +11 -0
  13. package/dist/server/extensions/daemon-backend.js +146 -0
  14. package/dist/server/extensions/extension-host-manager.d.ts +48 -0
  15. package/dist/server/extensions/extension-host-manager.js +141 -0
  16. package/dist/server/extensions/extension-loader.d.ts +11 -0
  17. package/dist/server/extensions/extension-loader.js +47 -0
  18. package/dist/server/extensions/extension-service.d.ts +17 -0
  19. package/dist/server/extensions/extension-service.js +76 -0
  20. package/dist/server/server/agent/activity-curator.d.ts +28 -0
  21. package/dist/server/server/agent/activity-curator.js +227 -0
  22. package/dist/server/server/agent/agent-archive.d.ts +11 -0
  23. package/dist/server/server/agent/agent-archive.js +16 -0
  24. package/dist/server/server/agent/agent-list-filter.d.ts +76 -0
  25. package/dist/server/server/agent/agent-list-filter.js +184 -0
  26. package/dist/server/server/agent/agent-loading.d.ts +12 -0
  27. package/dist/server/server/agent/agent-loading.js +76 -0
  28. package/dist/server/server/agent/agent-manager.d.ts +628 -0
  29. package/dist/server/server/agent/agent-manager.js +3370 -0
  30. package/dist/server/server/agent/agent-projections.d.ts +65 -0
  31. package/dist/server/server/agent/agent-projections.js +548 -0
  32. package/dist/server/server/agent/agent-prompt.d.ts +65 -0
  33. package/dist/server/server/agent/agent-prompt.js +271 -0
  34. package/dist/server/server/agent/agent-response-loop.d.ts +67 -0
  35. package/dist/server/server/agent/agent-response-loop.js +326 -0
  36. package/dist/server/server/agent/agent-routes.d.ts +18 -0
  37. package/dist/server/server/agent/agent-routes.js +107 -0
  38. package/dist/server/server/agent/agent-sdk-types.d.ts +809 -0
  39. package/dist/server/server/agent/agent-sdk-types.js +22 -0
  40. package/dist/server/server/agent/agent-storage.d.ts +209 -0
  41. package/dist/server/server/agent/agent-storage.js +365 -0
  42. package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
  43. package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
  44. package/dist/server/server/agent/agent-timeline-store-types.d.ts +50 -0
  45. package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
  46. package/dist/server/server/agent/agent-timeline-store.d.ts +27 -0
  47. package/dist/server/server/agent/agent-timeline-store.js +277 -0
  48. package/dist/server/server/agent/audio-utils.d.ts +3 -0
  49. package/dist/server/server/agent/audio-utils.js +19 -0
  50. package/dist/server/server/agent/authorship-ledger.d.ts +139 -0
  51. package/dist/server/server/agent/authorship-ledger.js +207 -0
  52. package/dist/server/server/agent/authorship-markup.d.ts +198 -0
  53. package/dist/server/server/agent/authorship-markup.js +288 -0
  54. package/dist/server/server/agent/blocker-log.d.ts +81 -0
  55. package/dist/server/server/agent/blocker-log.js +113 -0
  56. package/dist/server/server/agent/card-move-log.d.ts +66 -0
  57. package/dist/server/server/agent/card-move-log.js +113 -0
  58. package/dist/server/server/agent/create-agent/create.d.ts +126 -0
  59. package/dist/server/server/agent/create-agent/create.js +378 -0
  60. package/dist/server/server/agent/create-agent/routine.d.ts +45 -0
  61. package/dist/server/server/agent/create-agent/routine.js +53 -0
  62. package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +54 -0
  63. package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +152 -0
  64. package/dist/server/server/agent/create-agent-mode.d.ts +13 -0
  65. package/dist/server/server/agent/create-agent-mode.js +62 -0
  66. package/dist/server/server/agent/create-agent-title.d.ts +10 -0
  67. package/dist/server/server/agent/create-agent-title.js +34 -0
  68. package/dist/server/server/agent/dictation-debug.d.ts +13 -0
  69. package/dist/server/server/agent/dictation-debug.js +50 -0
  70. package/dist/server/server/agent/foreground-run-state.d.ts +50 -0
  71. package/dist/server/server/agent/foreground-run-state.js +160 -0
  72. package/dist/server/server/agent/import-sessions.d.ts +52 -0
  73. package/dist/server/server/agent/import-sessions.js +170 -0
  74. package/dist/server/server/agent/jarvis-judge.d.ts +157 -0
  75. package/dist/server/server/agent/jarvis-judge.js +289 -0
  76. package/dist/server/server/agent/judge-relay-gate.d.ts +70 -0
  77. package/dist/server/server/agent/judge-relay-gate.js +92 -0
  78. package/dist/server/server/agent/lifecycle-command.d.ts +75 -0
  79. package/dist/server/server/agent/lifecycle-command.js +97 -0
  80. package/dist/server/server/agent/mcp-server.d.ts +22 -0
  81. package/dist/server/server/agent/mcp-server.js +653 -0
  82. package/dist/server/server/agent/mcp-shared.d.ts +239 -0
  83. package/dist/server/server/agent/mcp-shared.js +200 -0
  84. package/dist/server/server/agent/output-language.d.ts +16 -0
  85. package/dist/server/server/agent/output-language.js +62 -0
  86. package/dist/server/server/agent/pcm16-resampler.d.ts +14 -0
  87. package/dist/server/server/agent/pcm16-resampler.js +63 -0
  88. package/dist/server/server/agent/permission-response.d.ts +15 -0
  89. package/dist/server/server/agent/permission-response.js +14 -0
  90. package/dist/server/server/agent/prompt-attachments.d.ts +7 -0
  91. package/dist/server/server/agent/prompt-attachments.js +90 -0
  92. package/dist/server/server/agent/provider-config-reload.d.ts +27 -0
  93. package/dist/server/server/agent/provider-config-reload.js +65 -0
  94. package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
  95. package/dist/server/server/agent/provider-history-timestamps.js +16 -0
  96. package/dist/server/server/agent/provider-launch-config.d.ts +51 -0
  97. package/dist/server/server/agent/provider-launch-config.js +183 -0
  98. package/dist/server/server/agent/provider-notices.d.ts +3 -0
  99. package/dist/server/server/agent/provider-notices.js +5 -0
  100. package/dist/server/server/agent/provider-registry-contract.d.ts +40 -0
  101. package/dist/server/server/agent/provider-registry-contract.js +28 -0
  102. package/dist/server/server/agent/provider-registry.d.ts +97 -0
  103. package/dist/server/server/agent/provider-registry.js +686 -0
  104. package/dist/server/server/agent/provider-session-import.d.ts +10 -0
  105. package/dist/server/server/agent/provider-session-import.js +49 -0
  106. package/dist/server/server/agent/provider-snapshot-manager.d.ts +166 -0
  107. package/dist/server/server/agent/provider-snapshot-manager.js +760 -0
  108. package/dist/server/server/agent/providers/acp-agent.d.ts +354 -0
  109. package/dist/server/server/agent/providers/acp-agent.js +2438 -0
  110. package/dist/server/server/agent/providers/claude/agent.d.ts +491 -0
  111. package/dist/server/server/agent/providers/claude/agent.js +5128 -0
  112. package/dist/server/server/agent/providers/claude/background-shell-kill-keys.d.ts +32 -0
  113. package/dist/server/server/agent/providers/claude/background-shell-kill-keys.js +34 -0
  114. package/dist/server/server/agent/providers/claude/background-task-tracker.d.ts +217 -0
  115. package/dist/server/server/agent/providers/claude/background-task-tracker.js +511 -0
  116. package/dist/server/server/agent/providers/claude/background-work-kinds.d.ts +124 -0
  117. package/dist/server/server/agent/providers/claude/background-work-kinds.js +96 -0
  118. package/dist/server/server/agent/providers/claude/feature-definitions.d.ts +8 -0
  119. package/dist/server/server/agent/providers/claude/feature-definitions.js +24 -0
  120. package/dist/server/server/agent/providers/claude/hook-handler.d.ts +37 -0
  121. package/dist/server/server/agent/providers/claude/hook-handler.js +193 -0
  122. package/dist/server/server/agent/providers/claude/hooks/event-translator.d.ts +49 -0
  123. package/dist/server/server/agent/providers/claude/hooks/event-translator.js +151 -0
  124. package/dist/server/server/agent/providers/claude/image-compression.d.ts +10 -0
  125. package/dist/server/server/agent/providers/claude/image-compression.js +32 -0
  126. package/dist/server/server/agent/providers/claude/model-manifest.d.ts +116 -0
  127. package/dist/server/server/agent/providers/claude/model-manifest.js +279 -0
  128. package/dist/server/server/agent/providers/claude/models.d.ts +11 -0
  129. package/dist/server/server/agent/providers/claude/models.js +97 -0
  130. package/dist/server/server/agent/providers/claude/partial-json.d.ts +5 -0
  131. package/dist/server/server/agent/providers/claude/partial-json.js +306 -0
  132. package/dist/server/server/agent/providers/claude/project-dir.d.ts +6 -0
  133. package/dist/server/server/agent/providers/claude/project-dir.js +57 -0
  134. package/dist/server/server/agent/providers/claude/pty-session-launcher.d.ts +62 -0
  135. package/dist/server/server/agent/providers/claude/pty-session-launcher.js +175 -0
  136. package/dist/server/server/agent/providers/claude/resume-source.d.ts +11 -0
  137. package/dist/server/server/agent/providers/claude/resume-source.js +37 -0
  138. package/dist/server/server/agent/providers/claude/rewind.d.ts +30 -0
  139. package/dist/server/server/agent/providers/claude/rewind.js +30 -0
  140. package/dist/server/server/agent/providers/claude/sidechain-tracker.d.ts +22 -0
  141. package/dist/server/server/agent/providers/claude/sidechain-tracker.js +232 -0
  142. package/dist/server/server/agent/providers/claude/spawn-config-writer.d.ts +70 -0
  143. package/dist/server/server/agent/providers/claude/spawn-config-writer.js +229 -0
  144. package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +47 -0
  145. package/dist/server/server/agent/providers/claude/task-notification-tool-call.js +292 -0
  146. package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.d.ts +24 -0
  147. package/dist/server/server/agent/providers/claude/test-rewind-claude-sdk.js +23 -0
  148. package/dist/server/server/agent/providers/claude/tool-allowlist-guard.d.ts +41 -0
  149. package/dist/server/server/agent/providers/claude/tool-allowlist-guard.js +95 -0
  150. package/dist/server/server/agent/providers/claude/tool-allowlist.d.ts +68 -0
  151. package/dist/server/server/agent/providers/claude/tool-allowlist.js +133 -0
  152. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.d.ts +3 -0
  153. package/dist/server/server/agent/providers/claude/tool-call-detail-parser.js +130 -0
  154. package/dist/server/server/agent/providers/claude/tool-call-mapper.d.ts +16 -0
  155. package/dist/server/server/agent/providers/claude/tool-call-mapper.js +129 -0
  156. package/dist/server/server/agent/providers/claude/transport/claude-paths.d.ts +31 -0
  157. package/dist/server/server/agent/providers/claude/transport/claude-paths.js +63 -0
  158. package/dist/server/server/agent/providers/claude/transport/continue-flag.d.ts +31 -0
  159. package/dist/server/server/agent/providers/claude/transport/continue-flag.js +42 -0
  160. package/dist/server/server/agent/providers/claude/transport/pty-query.d.ts +493 -0
  161. package/dist/server/server/agent/providers/claude/transport/pty-query.js +2075 -0
  162. package/dist/server/server/agent/providers/claude/transport/pty.d.ts +153 -0
  163. package/dist/server/server/agent/providers/claude/transport/pty.js +367 -0
  164. package/dist/server/server/agent/providers/claude/transport/redact.d.ts +8 -0
  165. package/dist/server/server/agent/providers/claude/transport/redact.js +33 -0
  166. package/dist/server/server/agent/providers/claude/transport/sdk.d.ts +65 -0
  167. package/dist/server/server/agent/providers/claude/transport/sdk.js +219 -0
  168. package/dist/server/server/agent/providers/claude/transport/tmux.d.ts +141 -0
  169. package/dist/server/server/agent/providers/claude/transport/tmux.js +218 -0
  170. package/dist/server/server/agent/providers/claude/transport/transcript-sdk-reader.d.ts +53 -0
  171. package/dist/server/server/agent/providers/claude/transport/transcript-sdk-reader.js +243 -0
  172. package/dist/server/server/agent/providers/claude/transport/transcript-tailer.d.ts +28 -0
  173. package/dist/server/server/agent/providers/claude/transport/transcript-tailer.js +166 -0
  174. package/dist/server/server/agent/providers/claude/transport/truncate-session.d.ts +50 -0
  175. package/dist/server/server/agent/providers/claude/transport/truncate-session.js +121 -0
  176. package/dist/server/server/agent/providers/claude/transport/turn-id-reconciler.d.ts +36 -0
  177. package/dist/server/server/agent/providers/claude/transport/turn-id-reconciler.js +75 -0
  178. package/dist/server/server/agent/providers/claude/transport/types.d.ts +61 -0
  179. package/dist/server/server/agent/providers/claude/transport/types.js +2 -0
  180. package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +90 -0
  181. package/dist/server/server/agent/providers/codex/app-server-transport.js +262 -0
  182. package/dist/server/server/agent/providers/codex/rewind.d.ts +21 -0
  183. package/dist/server/server/agent/providers/codex/rewind.js +46 -0
  184. package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.d.ts +36 -0
  185. package/dist/server/server/agent/providers/codex/test-utils/fake-app-server.js +217 -0
  186. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.d.ts +12 -0
  187. package/dist/server/server/agent/providers/codex/tool-call-detail-parser.js +104 -0
  188. package/dist/server/server/agent/providers/codex/tool-call-mapper.d.ts +24 -0
  189. package/dist/server/server/agent/providers/codex/tool-call-mapper.js +830 -0
  190. package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +308 -0
  191. package/dist/server/server/agent/providers/codex-app-server-agent.js +4515 -0
  192. package/dist/server/server/agent/providers/codex-feature-definitions.d.ts +11 -0
  193. package/dist/server/server/agent/providers/codex-feature-definitions.js +45 -0
  194. package/dist/server/server/agent/providers/copilot-acp-agent.d.ts +26 -0
  195. package/dist/server/server/agent/providers/copilot-acp-agent.js +187 -0
  196. package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +15 -0
  197. package/dist/server/server/agent/providers/cursor-acp-agent.js +18 -0
  198. package/dist/server/server/agent/providers/diagnostic-utils.d.ts +36 -0
  199. package/dist/server/server/agent/providers/diagnostic-utils.js +355 -0
  200. package/dist/server/server/agent/providers/generic-acp-agent.d.ts +42 -0
  201. package/dist/server/server/agent/providers/generic-acp-agent.js +161 -0
  202. package/dist/server/server/agent/providers/kiro-acp-agent.d.ts +17 -0
  203. package/dist/server/server/agent/providers/kiro-acp-agent.js +78 -0
  204. package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +85 -0
  205. package/dist/server/server/agent/providers/mock-load-test-agent.js +1010 -0
  206. package/dist/server/server/agent/providers/mock-slow-provider.d.ts +14 -0
  207. package/dist/server/server/agent/providers/mock-slow-provider.js +39 -0
  208. package/dist/server/server/agent/providers/opencode/paths.d.ts +2 -0
  209. package/dist/server/server/agent/providers/opencode/paths.js +7 -0
  210. package/dist/server/server/agent/providers/opencode/rewind.d.ts +18 -0
  211. package/dist/server/server/agent/providers/opencode/rewind.js +14 -0
  212. package/dist/server/server/agent/providers/opencode/server-manager.d.ts +91 -0
  213. package/dist/server/server/agent/providers/opencode/server-manager.js +425 -0
  214. package/dist/server/server/agent/providers/opencode/test-server-manager.d.ts +25 -0
  215. package/dist/server/server/agent/providers/opencode/test-server-manager.js +39 -0
  216. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.d.ts +92 -0
  217. package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-harness.js +268 -0
  218. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.d.ts +3 -0
  219. package/dist/server/server/agent/providers/opencode/tool-call-detail-parser.js +256 -0
  220. package/dist/server/server/agent/providers/opencode/tool-call-mapper.d.ts +13 -0
  221. package/dist/server/server/agent/providers/opencode/tool-call-mapper.js +53 -0
  222. package/dist/server/server/agent/providers/opencode-agent.d.ts +293 -0
  223. package/dist/server/server/agent/providers/opencode-agent.js +2908 -0
  224. package/dist/server/server/agent/providers/pi/agent.d.ts +137 -0
  225. package/dist/server/server/agent/providers/pi/agent.js +1651 -0
  226. package/dist/server/server/agent/providers/pi/cli-runtime.d.ts +21 -0
  227. package/dist/server/server/agent/providers/pi/cli-runtime.js +233 -0
  228. package/dist/server/server/agent/providers/pi/history-mapper.d.ts +9 -0
  229. package/dist/server/server/agent/providers/pi/history-mapper.js +140 -0
  230. package/dist/server/server/agent/providers/pi/rewind.d.ts +8 -0
  231. package/dist/server/server/agent/providers/pi/rewind.js +8 -0
  232. package/dist/server/server/agent/providers/pi/rpc-types.d.ts +208 -0
  233. package/dist/server/server/agent/providers/pi/rpc-types.js +2 -0
  234. package/dist/server/server/agent/providers/pi/runtime.d.ts +57 -0
  235. package/dist/server/server/agent/providers/pi/runtime.js +56 -0
  236. package/dist/server/server/agent/providers/pi/session-descriptor.d.ts +16 -0
  237. package/dist/server/server/agent/providers/pi/session-descriptor.js +346 -0
  238. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.d.ts +83 -0
  239. package/dist/server/server/agent/providers/pi/test-utils/fake-pi.js +184 -0
  240. package/dist/server/server/agent/providers/pi/tool-call-mapper.d.ts +112 -0
  241. package/dist/server/server/agent/providers/pi/tool-call-mapper.js +284 -0
  242. package/dist/server/server/agent/providers/provider-image-output.d.ts +25 -0
  243. package/dist/server/server/agent/providers/provider-image-output.js +111 -0
  244. package/dist/server/server/agent/providers/provider-runner.d.ts +27 -0
  245. package/dist/server/server/agent/providers/provider-runner.js +81 -0
  246. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.d.ts +3 -0
  247. package/dist/server/server/agent/providers/test-utils/session-stream-adapter.js +60 -0
  248. package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +884 -0
  249. package/dist/server/server/agent/providers/tool-call-detail-primitives.js +778 -0
  250. package/dist/server/server/agent/providers/tool-call-mapper-utils.d.ts +25 -0
  251. package/dist/server/server/agent/providers/tool-call-mapper-utils.js +208 -0
  252. package/dist/server/server/agent/recordings-debug.d.ts +3 -0
  253. package/dist/server/server/agent/recordings-debug.js +19 -0
  254. package/dist/server/server/agent/render-file-attachment.d.ts +29 -0
  255. package/dist/server/server/agent/render-file-attachment.js +41 -0
  256. package/dist/server/server/agent/resolve-resume-cwd.d.ts +14 -0
  257. package/dist/server/server/agent/resolve-resume-cwd.js +49 -0
  258. package/dist/server/server/agent/rewind/rewind.d.ts +10 -0
  259. package/dist/server/server/agent/rewind/rewind.js +29 -0
  260. package/dist/server/server/agent/rewind/test-rewind-session.d.ts +48 -0
  261. package/dist/server/server/agent/rewind/test-rewind-session.js +88 -0
  262. package/dist/server/server/agent/runtime-mcp-config.d.ts +14 -0
  263. package/dist/server/server/agent/runtime-mcp-config.js +53 -0
  264. package/dist/server/server/agent/session-digest-generator.d.ts +64 -0
  265. package/dist/server/server/agent/session-digest-generator.js +306 -0
  266. package/dist/server/server/agent/session-digest.d.ts +81 -0
  267. package/dist/server/server/agent/session-digest.js +226 -0
  268. package/dist/server/server/agent/structured-generation-providers.d.ts +29 -0
  269. package/dist/server/server/agent/structured-generation-providers.js +236 -0
  270. package/dist/server/server/agent/stt-debug.d.ts +10 -0
  271. package/dist/server/server/agent/stt-debug.js +33 -0
  272. package/dist/server/server/agent/stt-manager.d.ts +47 -0
  273. package/dist/server/server/agent/stt-manager.js +263 -0
  274. package/dist/server/server/agent/system-prompt.d.ts +2 -0
  275. package/dist/server/server/agent/system-prompt.js +8 -0
  276. package/dist/server/server/agent/timeline-append.d.ts +10 -0
  277. package/dist/server/server/agent/timeline-append.js +27 -0
  278. package/dist/server/server/agent/timeline-projection.d.ts +71 -0
  279. package/dist/server/server/agent/timeline-projection.js +373 -0
  280. package/dist/server/server/agent/tools/paseo-tools.d.ts +66 -0
  281. package/dist/server/server/agent/tools/paseo-tools.js +2616 -0
  282. package/dist/server/server/agent/tools/read-only-surface.d.ts +53 -0
  283. package/dist/server/server/agent/tools/read-only-surface.js +69 -0
  284. package/dist/server/server/agent/tools/types.d.ts +41 -0
  285. package/dist/server/server/agent/tools/types.js +2 -0
  286. package/dist/server/server/agent/tts-debug.d.ts +8 -0
  287. package/dist/server/server/agent/tts-debug.js +24 -0
  288. package/dist/server/server/agent/tts-manager.d.ts +41 -0
  289. package/dist/server/server/agent/tts-manager.js +375 -0
  290. package/dist/server/server/agent/wait-for-agent-tracker.d.ts +15 -0
  291. package/dist/server/server/agent/wait-for-agent-tracker.js +53 -0
  292. package/dist/server/server/agent-attention-policy.d.ts +29 -0
  293. package/dist/server/server/agent-attention-policy.js +36 -0
  294. package/dist/server/server/atomic-file.d.ts +3 -0
  295. package/dist/server/server/atomic-file.js +19 -0
  296. package/dist/server/server/auth.d.ts +68 -0
  297. package/dist/server/server/auth.js +207 -0
  298. package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
  299. package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +92 -0
  300. package/dist/server/server/auto-archive-on-merge/index.d.ts +8 -0
  301. package/dist/server/server/auto-archive-on-merge/index.js +15 -0
  302. package/dist/server/server/bootstrap.d.ts +163 -0
  303. package/dist/server/server/bootstrap.js +1597 -0
  304. package/dist/server/server/browser-tools/broker.d.ts +45 -0
  305. package/dist/server/server/browser-tools/broker.js +390 -0
  306. package/dist/server/server/browser-tools/errors.d.ts +22 -0
  307. package/dist/server/server/browser-tools/errors.js +27 -0
  308. package/dist/server/server/browser-tools/policy.d.ts +10 -0
  309. package/dist/server/server/browser-tools/policy.js +16 -0
  310. package/dist/server/server/browser-tools/tools.d.ts +16 -0
  311. package/dist/server/server/browser-tools/tools.js +845 -0
  312. package/dist/server/server/chat/chat-mentions.d.ts +47 -0
  313. package/dist/server/server/chat/chat-mentions.js +113 -0
  314. package/dist/server/server/chat/chat-service.d.ts +78 -0
  315. package/dist/server/server/chat/chat-service.js +337 -0
  316. package/dist/server/server/checkout/status-projection.d.ts +19 -0
  317. package/dist/server/server/checkout/status-projection.js +102 -0
  318. package/dist/server/server/checkout-diff-manager.d.ts +41 -0
  319. package/dist/server/server/checkout-diff-manager.js +189 -0
  320. package/dist/server/server/checkout-git-utils.d.ts +11 -0
  321. package/dist/server/server/checkout-git-utils.js +34 -0
  322. package/dist/server/server/client-message-id.d.ts +3 -0
  323. package/dist/server/server/client-message-id.js +12 -0
  324. package/dist/server/server/config.d.ts +20 -0
  325. package/dist/server/server/config.js +427 -0
  326. package/dist/server/server/connection-offer.d.ts +20 -0
  327. package/dist/server/server/connection-offer.js +59 -0
  328. package/dist/server/server/daemon-config-store.d.ts +78 -0
  329. package/dist/server/server/daemon-config-store.js +320 -0
  330. package/dist/server/server/daemon-keypair.d.ts +8 -0
  331. package/dist/server/server/daemon-keypair.js +42 -0
  332. package/dist/server/server/daemon-version.d.ts +5 -0
  333. package/dist/server/server/daemon-version.js +22 -0
  334. package/dist/server/server/daemon-worker.d.ts +2 -0
  335. package/dist/server/server/daemon-worker.js +251 -0
  336. package/dist/server/server/dictation/dictation-stream-manager.d.ts +95 -0
  337. package/dist/server/server/dictation/dictation-stream-manager.js +581 -0
  338. package/dist/server/server/editor-targets.d.ts +18 -0
  339. package/dist/server/server/editor-targets.js +109 -0
  340. package/dist/server/server/ensure-workspace-for-create.d.ts +30 -0
  341. package/dist/server/server/ensure-workspace-for-create.js +20 -0
  342. package/dist/server/server/exports.d.ts +28 -0
  343. package/dist/server/server/exports.js +31 -0
  344. package/dist/server/server/file-download/token-store.d.ts +24 -0
  345. package/dist/server/server/file-download/token-store.js +40 -0
  346. package/dist/server/server/file-explorer/file-download-frames.d.ts +27 -0
  347. package/dist/server/server/file-explorer/file-download-frames.js +47 -0
  348. package/dist/server/server/file-explorer/mime-types.d.ts +58 -0
  349. package/dist/server/server/file-explorer/mime-types.js +212 -0
  350. package/dist/server/server/file-explorer/service.d.ts +97 -0
  351. package/dist/server/server/file-explorer/service.js +362 -0
  352. package/dist/server/server/file-upload/index.d.ts +27 -0
  353. package/dist/server/server/file-upload/index.js +158 -0
  354. package/dist/server/server/fleet/decision-service.d.ts +47 -0
  355. package/dist/server/server/fleet/decision-service.js +156 -0
  356. package/dist/server/server/fleet/fleet-controls.d.ts +77 -0
  357. package/dist/server/server/fleet/fleet-controls.js +217 -0
  358. package/dist/server/server/fleet/fleet-reader.d.ts +106 -0
  359. package/dist/server/server/fleet/fleet-reader.js +470 -0
  360. package/dist/server/server/fleet/fleet-scaffold.d.ts +26 -0
  361. package/dist/server/server/fleet/fleet-scaffold.js +48 -0
  362. package/dist/server/server/fleet/fleet-scheduler.d.ts +75 -0
  363. package/dist/server/server/fleet/fleet-scheduler.js +186 -0
  364. package/dist/server/server/fleet/fleet-service.d.ts +75 -0
  365. package/dist/server/server/fleet/fleet-service.js +134 -0
  366. package/dist/server/server/hostnames.d.ts +13 -0
  367. package/dist/server/server/hostnames.js +94 -0
  368. package/dist/server/server/ingestion/adapters.d.ts +21 -0
  369. package/dist/server/server/ingestion/adapters.js +27 -0
  370. package/dist/server/server/ingestion/apply.d.ts +220 -0
  371. package/dist/server/server/ingestion/apply.js +345 -0
  372. package/dist/server/server/ingestion/backfill.d.ts +123 -0
  373. package/dist/server/server/ingestion/backfill.js +160 -0
  374. package/dist/server/server/ingestion/cap-limiter.d.ts +148 -0
  375. package/dist/server/server/ingestion/cap-limiter.js +264 -0
  376. package/dist/server/server/ingestion/cohort-recorder-memory.d.ts +25 -0
  377. package/dist/server/server/ingestion/cohort-recorder-memory.js +78 -0
  378. package/dist/server/server/ingestion/cohort-recorder-store.d.ts +12 -0
  379. package/dist/server/server/ingestion/cohort-recorder-store.js +99 -0
  380. package/dist/server/server/ingestion/cohort-resume.d.ts +22 -0
  381. package/dist/server/server/ingestion/cohort-resume.js +171 -0
  382. package/dist/server/server/ingestion/cohort-store.d.ts +227 -0
  383. package/dist/server/server/ingestion/cohort-store.js +462 -0
  384. package/dist/server/server/ingestion/composio/gateway.d.ts +8 -0
  385. package/dist/server/server/ingestion/composio/gateway.js +469 -0
  386. package/dist/server/server/ingestion/dry-run.d.ts +70 -0
  387. package/dist/server/server/ingestion/dry-run.js +142 -0
  388. package/dist/server/server/ingestion/errors.d.ts +48 -0
  389. package/dist/server/server/ingestion/errors.js +63 -0
  390. package/dist/server/server/ingestion/external-user-id.d.ts +24 -0
  391. package/dist/server/server/ingestion/external-user-id.js +35 -0
  392. package/dist/server/server/ingestion/filters/store.d.ts +28 -0
  393. package/dist/server/server/ingestion/filters/store.js +74 -0
  394. package/dist/server/server/ingestion/fingerprint.d.ts +13 -0
  395. package/dist/server/server/ingestion/fingerprint.js +31 -0
  396. package/dist/server/server/ingestion/grouping.d.ts +26 -0
  397. package/dist/server/server/ingestion/grouping.js +116 -0
  398. package/dist/server/server/ingestion/hard-failure.d.ts +73 -0
  399. package/dist/server/server/ingestion/hard-failure.js +125 -0
  400. package/dist/server/server/ingestion/health.d.ts +38 -0
  401. package/dist/server/server/ingestion/health.js +90 -0
  402. package/dist/server/server/ingestion/ledger-schema.d.ts +99 -0
  403. package/dist/server/server/ingestion/ledger-schema.js +359 -0
  404. package/dist/server/server/ingestion/ledger.d.ts +562 -0
  405. package/dist/server/server/ingestion/ledger.js +881 -0
  406. package/dist/server/server/ingestion/match.d.ts +57 -0
  407. package/dist/server/server/ingestion/match.js +102 -0
  408. package/dist/server/server/ingestion/pipedream/gateway.d.ts +8 -0
  409. package/dist/server/server/ingestion/pipedream/gateway.js +282 -0
  410. package/dist/server/server/ingestion/presets.d.ts +57 -0
  411. package/dist/server/server/ingestion/presets.js +57 -0
  412. package/dist/server/server/ingestion/reconcile/artifact-kind.d.ts +19 -0
  413. package/dist/server/server/ingestion/reconcile/artifact-kind.js +65 -0
  414. package/dist/server/server/ingestion/reconcile/calendar.d.ts +19 -0
  415. package/dist/server/server/ingestion/reconcile/calendar.js +110 -0
  416. package/dist/server/server/ingestion/reconcile/constants.d.ts +60 -0
  417. package/dist/server/server/ingestion/reconcile/constants.js +62 -0
  418. package/dist/server/server/ingestion/reconcile/github.d.ts +21 -0
  419. package/dist/server/server/ingestion/reconcile/github.js +83 -0
  420. package/dist/server/server/ingestion/reconcile/gitlab.d.ts +23 -0
  421. package/dist/server/server/ingestion/reconcile/gitlab.js +94 -0
  422. package/dist/server/server/ingestion/reconcile/gmail.d.ts +9 -0
  423. package/dist/server/server/ingestion/reconcile/gmail.js +141 -0
  424. package/dist/server/server/ingestion/reconcile/jira.d.ts +17 -0
  425. package/dist/server/server/ingestion/reconcile/jira.js +76 -0
  426. package/dist/server/server/ingestion/reconcile/kinds.d.ts +16 -0
  427. package/dist/server/server/ingestion/reconcile/kinds.js +59 -0
  428. package/dist/server/server/ingestion/reconcile/mcp-call.d.ts +18 -0
  429. package/dist/server/server/ingestion/reconcile/mcp-call.js +105 -0
  430. package/dist/server/server/ingestion/reconcile/payload.d.ts +38 -0
  431. package/dist/server/server/ingestion/reconcile/payload.js +82 -0
  432. package/dist/server/server/ingestion/reconcile/registry.d.ts +40 -0
  433. package/dist/server/server/ingestion/reconcile/registry.js +72 -0
  434. package/dist/server/server/ingestion/reconcile/runner.d.ts +43 -0
  435. package/dist/server/server/ingestion/reconcile/runner.js +91 -0
  436. package/dist/server/server/ingestion/reconcile/sentry.d.ts +20 -0
  437. package/dist/server/server/ingestion/reconcile/sentry.js +85 -0
  438. package/dist/server/server/ingestion/reconcile/service.d.ts +134 -0
  439. package/dist/server/server/ingestion/reconcile/service.js +409 -0
  440. package/dist/server/server/ingestion/reconcile/signature-guard.d.ts +30 -0
  441. package/dist/server/server/ingestion/reconcile/signature-guard.js +82 -0
  442. package/dist/server/server/ingestion/reconcile/testing/fake-mcp-server.d.ts +68 -0
  443. package/dist/server/server/ingestion/reconcile/testing/fake-mcp-server.js +158 -0
  444. package/dist/server/server/ingestion/reconcile/types.d.ts +65 -0
  445. package/dist/server/server/ingestion/reconcile/types.js +11 -0
  446. package/dist/server/server/ingestion/reconcile/verdict-mapping.d.ts +54 -0
  447. package/dist/server/server/ingestion/reconcile/verdict-mapping.js +82 -0
  448. package/dist/server/server/ingestion/resolve-items.d.ts +23 -0
  449. package/dist/server/server/ingestion/resolve-items.js +54 -0
  450. package/dist/server/server/ingestion/run-projection.d.ts +35 -0
  451. package/dist/server/server/ingestion/run-projection.js +76 -0
  452. package/dist/server/server/ingestion/service.d.ts +147 -0
  453. package/dist/server/server/ingestion/service.js +364 -0
  454. package/dist/server/server/ingestion/sources/store.d.ts +27 -0
  455. package/dist/server/server/ingestion/sources/store.js +71 -0
  456. package/dist/server/server/ingestion/testing/claim-and-hang.d.ts +2 -0
  457. package/dist/server/server/ingestion/testing/claim-and-hang.js +21 -0
  458. package/dist/server/server/ingestion/testing/cohort-crash.d.ts +2 -0
  459. package/dist/server/server/ingestion/testing/cohort-crash.js +89 -0
  460. package/dist/server/server/ingestion/types.d.ts +226 -0
  461. package/dist/server/server/ingestion/types.js +18 -0
  462. package/dist/server/server/ingestion/utc-day.d.ts +27 -0
  463. package/dist/server/server/ingestion/utc-day.js +44 -0
  464. package/dist/server/server/json-utils.d.ts +11 -0
  465. package/dist/server/server/json-utils.js +45 -0
  466. package/dist/server/server/lifecycle-reasons.d.ts +4 -0
  467. package/dist/server/server/lifecycle-reasons.js +6 -0
  468. package/dist/server/server/logger.d.ts +29 -0
  469. package/dist/server/server/logger.js +125 -0
  470. package/dist/server/server/loop-service.d.ts +234 -0
  471. package/dist/server/server/loop-service.js +769 -0
  472. package/dist/server/server/managed-processes/managed-processes.d.ts +76 -0
  473. package/dist/server/server/managed-processes/managed-processes.js +326 -0
  474. package/dist/server/server/meetings/capture-http.d.ts +3 -0
  475. package/dist/server/server/meetings/capture-http.js +71 -0
  476. package/dist/server/server/meetings/capture-orchestrator.d.ts +123 -0
  477. package/dist/server/server/meetings/capture-orchestrator.js +531 -0
  478. package/dist/server/server/meetings/capture-stream-ws.d.ts +42 -0
  479. package/dist/server/server/meetings/capture-stream-ws.js +68 -0
  480. package/dist/server/server/meetings/live-transcription.d.ts +57 -0
  481. package/dist/server/server/meetings/live-transcription.js +280 -0
  482. package/dist/server/server/meetings/meetings-http.d.ts +42 -0
  483. package/dist/server/server/meetings/meetings-http.js +266 -0
  484. package/dist/server/server/messages.d.ts +9 -0
  485. package/dist/server/server/messages.js +29 -0
  486. package/dist/server/server/migrations/backfill-workspace-id.migration.d.ts +9 -0
  487. package/dist/server/server/migrations/backfill-workspace-id.migration.js +64 -0
  488. package/dist/server/server/monitoring/telemetry.d.ts +13 -0
  489. package/dist/server/server/monitoring/telemetry.js +21 -0
  490. package/dist/server/server/package-version.d.ts +19 -0
  491. package/dist/server/server/package-version.js +60 -0
  492. package/dist/server/server/pagination/cursor.d.ts +16 -0
  493. package/dist/server/server/pagination/cursor.js +62 -0
  494. package/dist/server/server/pagination/sortable-pager.d.ts +24 -0
  495. package/dist/server/server/pagination/sortable-pager.js +68 -0
  496. package/dist/server/server/pairing-offer.d.ts +18 -0
  497. package/dist/server/server/pairing-offer.js +47 -0
  498. package/dist/server/server/pairing-qr.d.ts +7 -0
  499. package/dist/server/server/pairing-qr.js +45 -0
  500. package/dist/server/server/paseo-env.d.ts +13 -0
  501. package/dist/server/server/paseo-env.js +53 -0
  502. package/dist/server/server/paseo-home.d.ts +2 -0
  503. package/dist/server/server/paseo-home.js +19 -0
  504. package/dist/server/server/paseo-worktree-service.d.ts +52 -0
  505. package/dist/server/server/paseo-worktree-service.js +294 -0
  506. package/dist/server/server/path-utils.d.ts +5 -0
  507. package/dist/server/server/path-utils.js +34 -0
  508. package/dist/server/server/persisted-config.d.ts +390 -0
  509. package/dist/server/server/persisted-config.js +424 -0
  510. package/dist/server/server/persistence-hooks.d.ts +32 -0
  511. package/dist/server/server/persistence-hooks.js +99 -0
  512. package/dist/server/server/pid-lock.d.ts +32 -0
  513. package/dist/server/server/pid-lock.js +170 -0
  514. package/dist/server/server/private-files.d.ts +6 -0
  515. package/dist/server/server/private-files.js +37 -0
  516. package/dist/server/server/process-diagnostics.d.ts +17 -0
  517. package/dist/server/server/process-diagnostics.js +22 -0
  518. package/dist/server/server/push/categories.d.ts +35 -0
  519. package/dist/server/server/push/categories.js +40 -0
  520. package/dist/server/server/push/expo-worker.d.ts +51 -0
  521. package/dist/server/server/push/expo-worker.js +188 -0
  522. package/dist/server/server/push/index.d.ts +39 -0
  523. package/dist/server/server/push/index.js +45 -0
  524. package/dist/server/server/push/notifications.d.ts +9 -0
  525. package/dist/server/server/push/notifications.js +15 -0
  526. package/dist/server/server/push/push-service.d.ts +20 -0
  527. package/dist/server/server/push/push-service.js +66 -0
  528. package/dist/server/server/push/routes.d.ts +28 -0
  529. package/dist/server/server/push/routes.js +74 -0
  530. package/dist/server/server/push/subscriptions.d.ts +42 -0
  531. package/dist/server/server/push/subscriptions.js +112 -0
  532. package/dist/server/server/push/token-store.d.ts +29 -0
  533. package/dist/server/server/push/token-store.js +78 -0
  534. package/dist/server/server/push/triggers.d.ts +68 -0
  535. package/dist/server/server/push/triggers.js +103 -0
  536. package/dist/server/server/push/vapid-keys.d.ts +15 -0
  537. package/dist/server/server/push/vapid-keys.js +52 -0
  538. package/dist/server/server/push/web-worker.d.ts +44 -0
  539. package/dist/server/server/push/web-worker.js +99 -0
  540. package/dist/server/server/reference-enricher.d.ts +41 -0
  541. package/dist/server/server/reference-enricher.js +392 -0
  542. package/dist/server/server/relay-transport.d.ts +31 -0
  543. package/dist/server/server/relay-transport.js +545 -0
  544. package/dist/server/server/resolve-workspace-id-for-path.d.ts +4 -0
  545. package/dist/server/server/resolve-workspace-id-for-path.js +71 -0
  546. package/dist/server/server/resolve-worktree-creation-intent.d.ts +32 -0
  547. package/dist/server/server/resolve-worktree-creation-intent.js +121 -0
  548. package/dist/server/server/restart-manifest.d.ts +83 -0
  549. package/dist/server/server/restart-manifest.js +122 -0
  550. package/dist/server/server/resume-after-restart.d.ts +50 -0
  551. package/dist/server/server/resume-after-restart.js +108 -0
  552. package/dist/server/server/schedule/create-cadence.d.ts +22 -0
  553. package/dist/server/server/schedule/create-cadence.js +70 -0
  554. package/dist/server/server/schedule/cron.d.ts +8 -0
  555. package/dist/server/server/schedule/cron.js +114 -0
  556. package/dist/server/server/schedule/service.d.ts +207 -0
  557. package/dist/server/server/schedule/service.js +867 -0
  558. package/dist/server/server/schedule/source-prompt.d.ts +42 -0
  559. package/dist/server/server/schedule/source-prompt.js +87 -0
  560. package/dist/server/server/schedule/store.d.ts +13 -0
  561. package/dist/server/server/schedule/store.js +57 -0
  562. package/dist/server/server/script-health-monitor.d.ts +39 -0
  563. package/dist/server/server/script-health-monitor.js +159 -0
  564. package/dist/server/server/script-proxy.d.ts +3 -0
  565. package/dist/server/server/script-proxy.js +2 -0
  566. package/dist/server/server/script-route-branch-handler.d.ts +10 -0
  567. package/dist/server/server/script-route-branch-handler.js +11 -0
  568. package/dist/server/server/script-status-projection.d.ts +36 -0
  569. package/dist/server/server/script-status-projection.js +200 -0
  570. package/dist/server/server/search/extract.d.ts +53 -0
  571. package/dist/server/server/search/extract.js +222 -0
  572. package/dist/server/server/search/index-store.d.ts +83 -0
  573. package/dist/server/server/search/index-store.js +241 -0
  574. package/dist/server/server/search/indexer.d.ts +69 -0
  575. package/dist/server/server/search/indexer.js +362 -0
  576. package/dist/server/server/search/resolve-cwd.d.ts +40 -0
  577. package/dist/server/server/search/resolve-cwd.js +109 -0
  578. package/dist/server/server/search/session-search-service.d.ts +59 -0
  579. package/dist/server/server/search/session-search-service.js +171 -0
  580. package/dist/server/server/server-id.d.ts +17 -0
  581. package/dist/server/server/server-id.js +68 -0
  582. package/dist/server/server/service-proxy.d.ts +237 -0
  583. package/dist/server/server/service-proxy.js +714 -0
  584. package/dist/server/server/session/agent-config/agent-config-session.d.ts +50 -0
  585. package/dist/server/server/session/agent-config/agent-config-session.js +98 -0
  586. package/dist/server/server/session/agent-updates/agent-updates-service.d.ts +59 -0
  587. package/dist/server/server/session/agent-updates/agent-updates-service.js +220 -0
  588. package/dist/server/server/session/chat/chat-ingestion-session.d.ts +285 -0
  589. package/dist/server/server/session/chat/chat-ingestion-session.js +504 -0
  590. package/dist/server/server/session/chat/chat-schedule-loop-session.d.ts +175 -0
  591. package/dist/server/server/session/chat/chat-schedule-loop-session.js +720 -0
  592. package/dist/server/server/session/checkout/checkout-session.d.ts +140 -0
  593. package/dist/server/server/session/checkout/checkout-session.js +927 -0
  594. package/dist/server/server/session/checkout/git-metadata-generator.d.ts +53 -0
  595. package/dist/server/server/session/checkout/git-metadata-generator.js +159 -0
  596. package/dist/server/server/session/daemon/daemon-self-update-session-controller.d.ts +32 -0
  597. package/dist/server/server/session/daemon/daemon-self-update-session-controller.js +88 -0
  598. package/dist/server/server/session/daemon/daemon-self-updater.d.ts +32 -0
  599. package/dist/server/server/session/daemon/daemon-self-updater.js +56 -0
  600. package/dist/server/server/session/daemon/daemon-session.d.ts +89 -0
  601. package/dist/server/server/session/daemon/daemon-session.js +179 -0
  602. package/dist/server/server/session/daemon/diagnostics.d.ts +41 -0
  603. package/dist/server/server/session/daemon/diagnostics.js +431 -0
  604. package/dist/server/server/session/daemon/install-origin.d.ts +7 -0
  605. package/dist/server/server/session/daemon/install-origin.js +64 -0
  606. package/dist/server/server/session/daemon/npm-global-cli.d.ts +29 -0
  607. package/dist/server/server/session/daemon/npm-global-cli.js +98 -0
  608. package/dist/server/server/session/files/workspace-files-session.d.ts +53 -0
  609. package/dist/server/server/session/files/workspace-files-session.js +245 -0
  610. package/dist/server/server/session/git-mutation/git-mutation-service.d.ts +34 -0
  611. package/dist/server/server/session/git-mutation/git-mutation-service.js +71 -0
  612. package/dist/server/server/session/ingestion/source-session.d.ts +78 -0
  613. package/dist/server/server/session/ingestion/source-session.js +191 -0
  614. package/dist/server/server/session/project-config/project-config-session.d.ts +34 -0
  615. package/dist/server/server/session/project-config/project-config-session.js +125 -0
  616. package/dist/server/server/session/provider/provider-catalog-session.d.ts +105 -0
  617. package/dist/server/server/session/provider/provider-catalog-session.js +364 -0
  618. package/dist/server/server/session/session-rename-route.d.ts +4 -0
  619. package/dist/server/server/session/session-rename-route.js +71 -0
  620. package/dist/server/server/session/session-rename-service.d.ts +25 -0
  621. package/dist/server/server/session/session-rename-service.js +76 -0
  622. package/dist/server/server/session/voice/voice-session.d.ts +166 -0
  623. package/dist/server/server/session/voice/voice-session.js +899 -0
  624. package/dist/server/server/session/voice/voice-turn-controller.d.ts +38 -0
  625. package/dist/server/server/session/voice/voice-turn-controller.js +393 -0
  626. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.d.ts +36 -0
  627. package/dist/server/server/session/workspace-git-observer/workspace-git-observer-service.js +134 -0
  628. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.d.ts +46 -0
  629. package/dist/server/server/session/workspace-provisioning/workspace-provisioning-service.js +214 -0
  630. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.d.ts +41 -0
  631. package/dist/server/server/session/workspace-scripts/workspace-scripts-service.js +100 -0
  632. package/dist/server/server/session.d.ts +677 -0
  633. package/dist/server/server/session.js +6531 -0
  634. package/dist/server/server/speech/audio.d.ts +10 -0
  635. package/dist/server/server/speech/audio.js +101 -0
  636. package/dist/server/server/speech/provider-resolver.d.ts +3 -0
  637. package/dist/server/server/speech/provider-resolver.js +7 -0
  638. package/dist/server/server/speech/providers/local/config.d.ts +30 -0
  639. package/dist/server/server/speech/providers/local/config.js +134 -0
  640. package/dist/server/server/speech/providers/local/models.d.ts +11 -0
  641. package/dist/server/server/speech/providers/local/models.js +17 -0
  642. package/dist/server/server/speech/providers/local/runtime.d.ts +31 -0
  643. package/dist/server/server/speech/providers/local/runtime.js +135 -0
  644. package/dist/server/server/speech/providers/local/sherpa/assets/silero_vad.onnx +0 -0
  645. package/dist/server/server/speech/providers/local/sherpa/model-catalog.d.ts +56 -0
  646. package/dist/server/server/speech/providers/local/sherpa/model-catalog.js +74 -0
  647. package/dist/server/server/speech/providers/local/sherpa/model-downloader.d.ts +15 -0
  648. package/dist/server/server/speech/providers/local/sherpa/model-downloader.js +142 -0
  649. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.d.ts +49 -0
  650. package/dist/server/server/speech/providers/local/sherpa/sherpa-offline-recognizer.js +73 -0
  651. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.d.ts +9 -0
  652. package/dist/server/server/speech/providers/local/sherpa/sherpa-onnx-node-loader.js +107 -0
  653. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.d.ts +28 -0
  654. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-realtime-session.js +137 -0
  655. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.d.ts +21 -0
  656. package/dist/server/server/speech/providers/local/sherpa/sherpa-parakeet-stt.js +133 -0
  657. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.d.ts +18 -0
  658. package/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
  659. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.d.ts +21 -0
  660. package/dist/server/server/speech/providers/local/sherpa/sherpa-tts.js +101 -0
  661. package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.d.ts +19 -0
  662. package/dist/server/server/speech/providers/local/sherpa/silero-vad-provider.js +49 -0
  663. package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.d.ts +38 -0
  664. package/dist/server/server/speech/providers/local/sherpa/silero-vad-session.js +176 -0
  665. package/dist/server/server/speech/providers/local/worker-bytes.d.ts +4 -0
  666. package/dist/server/server/speech/providers/local/worker-bytes.js +9 -0
  667. package/dist/server/server/speech/providers/local/worker-client.d.ts +93 -0
  668. package/dist/server/server/speech/providers/local/worker-client.js +611 -0
  669. package/dist/server/server/speech/providers/local/worker-process.d.ts +2 -0
  670. package/dist/server/server/speech/providers/local/worker-process.js +270 -0
  671. package/dist/server/server/speech/providers/local/worker-protocol.d.ts +95 -0
  672. package/dist/server/server/speech/providers/local/worker-protocol.js +2 -0
  673. package/dist/server/server/speech/providers/openai/config.d.ts +19 -0
  674. package/dist/server/server/speech/providers/openai/config.js +148 -0
  675. package/dist/server/server/speech/providers/openai/runtime.d.ts +29 -0
  676. package/dist/server/server/speech/providers/openai/runtime.js +102 -0
  677. package/dist/server/server/speech/providers/openai/stt.d.ts +23 -0
  678. package/dist/server/server/speech/providers/openai/stt.js +209 -0
  679. package/dist/server/server/speech/providers/openai/tts.d.ts +19 -0
  680. package/dist/server/server/speech/providers/openai/tts.js +48 -0
  681. package/dist/server/server/speech/speech-config-resolver.d.ts +11 -0
  682. package/dist/server/server/speech/speech-config-resolver.js +132 -0
  683. package/dist/server/server/speech/speech-provider.d.ts +59 -0
  684. package/dist/server/server/speech/speech-provider.js +2 -0
  685. package/dist/server/server/speech/speech-runtime.d.ts +45 -0
  686. package/dist/server/server/speech/speech-runtime.js +562 -0
  687. package/dist/server/server/speech/speech-types.d.ts +22 -0
  688. package/dist/server/server/speech/speech-types.js +8 -0
  689. package/dist/server/server/speech/turn-detection-provider.d.ts +23 -0
  690. package/dist/server/server/speech/turn-detection-provider.js +2 -0
  691. package/dist/server/server/tenant-token-verifier.d.ts +43 -0
  692. package/dist/server/server/tenant-token-verifier.js +79 -0
  693. package/dist/server/server/update/installer.d.ts +70 -0
  694. package/dist/server/server/update/installer.js +181 -0
  695. package/dist/server/server/update/registry.d.ts +47 -0
  696. package/dist/server/server/update/registry.js +95 -0
  697. package/dist/server/server/update/slots.d.ts +45 -0
  698. package/dist/server/server/update/slots.js +97 -0
  699. package/dist/server/server/update/state.d.ts +41 -0
  700. package/dist/server/server/update/state.js +65 -0
  701. package/dist/server/server/uploads/file-attachment-validation.d.ts +61 -0
  702. package/dist/server/server/uploads/file-attachment-validation.js +56 -0
  703. package/dist/server/server/uploads/staging-service.d.ts +91 -0
  704. package/dist/server/server/uploads/staging-service.js +144 -0
  705. package/dist/server/server/utils/diff-highlighter.d.ts +57 -0
  706. package/dist/server/server/utils/diff-highlighter.js +279 -0
  707. package/dist/server/server/voice/voice-turn-controller.d.ts +38 -0
  708. package/dist/server/server/voice/voice-turn-controller.js +393 -0
  709. package/dist/server/server/voice-config.d.ts +15 -0
  710. package/dist/server/server/voice-config.js +54 -0
  711. package/dist/server/server/voice-permission-policy.d.ts +4 -0
  712. package/dist/server/server/voice-permission-policy.js +13 -0
  713. package/dist/server/server/voice-types.d.ts +12 -0
  714. package/dist/server/server/voice-types.js +2 -0
  715. package/dist/server/server/watch/composer-dictate.d.ts +42 -0
  716. package/dist/server/server/watch/composer-dictate.js +37 -0
  717. package/dist/server/server/watch/notification-action.d.ts +38 -0
  718. package/dist/server/server/watch/notification-action.js +54 -0
  719. package/dist/server/server/watch/notification-registry.d.ts +16 -0
  720. package/dist/server/server/watch/notification-registry.js +2 -0
  721. package/dist/server/server/web-ui.d.ts +10 -0
  722. package/dist/server/server/web-ui.js +247 -0
  723. package/dist/server/server/websocket/runtime-metrics.d.ts +94 -0
  724. package/dist/server/server/websocket/runtime-metrics.js +148 -0
  725. package/dist/server/server/websocket-server.d.ts +231 -0
  726. package/dist/server/server/websocket-server.js +1853 -0
  727. package/dist/server/server/whatsapp-bridge.d.ts +74 -0
  728. package/dist/server/server/whatsapp-bridge.js +142 -0
  729. package/dist/server/server/workflow/workflow-agent-resolution.d.ts +82 -0
  730. package/dist/server/server/workflow/workflow-agent-resolution.js +105 -0
  731. package/dist/server/server/workflow/workflow-feature.d.ts +2 -0
  732. package/dist/server/server/workflow/workflow-feature.js +8 -0
  733. package/dist/server/server/workflow/workflow-http-routes.d.ts +27 -0
  734. package/dist/server/server/workflow/workflow-http-routes.js +97 -0
  735. package/dist/server/server/workflow/workflow-manager.d.ts +363 -0
  736. package/dist/server/server/workflow/workflow-manager.js +852 -0
  737. package/dist/server/server/workflow/workflow-progress.d.ts +148 -0
  738. package/dist/server/server/workflow/workflow-progress.js +274 -0
  739. package/dist/server/server/workflow/workflow-storage.d.ts +36 -0
  740. package/dist/server/server/workflow/workflow-storage.js +143 -0
  741. package/dist/server/server/workspace/all-sessions-route.d.ts +32 -0
  742. package/dist/server/server/workspace/all-sessions-route.js +218 -0
  743. package/dist/server/server/workspace/recall-search-feature.d.ts +2 -0
  744. package/dist/server/server/workspace/recall-search-feature.js +31 -0
  745. package/dist/server/server/workspace/session-openable.d.ts +16 -0
  746. package/dist/server/server/workspace/session-openable.js +48 -0
  747. package/dist/server/server/workspace/sessions-route.d.ts +15 -0
  748. package/dist/server/server/workspace/sessions-route.js +108 -0
  749. package/dist/server/server/workspace/sessions-search-route.d.ts +40 -0
  750. package/dist/server/server/workspace/sessions-search-route.js +74 -0
  751. package/dist/server/server/workspace-archive-service.d.ts +65 -0
  752. package/dist/server/server/workspace-archive-service.js +229 -0
  753. package/dist/server/server/workspace-auto-name.d.ts +55 -0
  754. package/dist/server/server/workspace-auto-name.js +128 -0
  755. package/dist/server/server/workspace-bootstrap-dedupe.d.ts +34 -0
  756. package/dist/server/server/workspace-bootstrap-dedupe.js +23 -0
  757. package/dist/server/server/workspace-directory.d.ts +93 -0
  758. package/dist/server/server/workspace-directory.js +440 -0
  759. package/dist/server/server/workspace-git-metadata.d.ts +24 -0
  760. package/dist/server/server/workspace-git-metadata.js +55 -0
  761. package/dist/server/server/workspace-git-service.d.ts +257 -0
  762. package/dist/server/server/workspace-git-service.js +1386 -0
  763. package/dist/server/server/workspace-reconciliation-service.d.ts +57 -0
  764. package/dist/server/server/workspace-reconciliation-service.js +278 -0
  765. package/dist/server/server/workspace-registry-bootstrap.d.ts +13 -0
  766. package/dist/server/server/workspace-registry-bootstrap.js +121 -0
  767. package/dist/server/server/workspace-registry-model.d.ts +55 -0
  768. package/dist/server/server/workspace-registry-model.js +190 -0
  769. package/dist/server/server/workspace-registry.d.ts +117 -0
  770. package/dist/server/server/workspace-registry.js +194 -0
  771. package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
  772. package/dist/server/server/workspace-script-runtime-store.js +78 -0
  773. package/dist/server/server/workspace-service-env.d.ts +18 -0
  774. package/dist/server/server/workspace-service-env.js +85 -0
  775. package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
  776. package/dist/server/server/workspace-service-port-registry.js +59 -0
  777. package/dist/server/server/worktree/commands.d.ts +55 -0
  778. package/dist/server/server/worktree/commands.js +107 -0
  779. package/dist/server/server/worktree-bootstrap.d.ts +76 -0
  780. package/dist/server/server/worktree-bootstrap.js +720 -0
  781. package/dist/server/server/worktree-branch-name-generator.d.ts +35 -0
  782. package/dist/server/server/worktree-branch-name-generator.js +91 -0
  783. package/dist/server/server/worktree-core.d.ts +31 -0
  784. package/dist/server/server/worktree-core.js +105 -0
  785. package/dist/server/server/worktree-errors.d.ts +12 -0
  786. package/dist/server/server/worktree-errors.js +28 -0
  787. package/dist/server/server/worktree-session.d.ts +152 -0
  788. package/dist/server/server/worktree-session.js +466 -0
  789. package/dist/server/services/github-service.d.ts +337 -0
  790. package/dist/server/services/github-service.js +2101 -0
  791. package/dist/server/services/quota-fetcher/manifest.d.ts +4 -0
  792. package/dist/server/services/quota-fetcher/manifest.js +52 -0
  793. package/dist/server/services/quota-fetcher/provider.d.ts +17 -0
  794. package/dist/server/services/quota-fetcher/provider.js +2 -0
  795. package/dist/server/services/quota-fetcher/providers/claude.d.ts +26 -0
  796. package/dist/server/services/quota-fetcher/providers/claude.js +217 -0
  797. package/dist/server/services/quota-fetcher/providers/codex.d.ts +23 -0
  798. package/dist/server/services/quota-fetcher/providers/codex.js +211 -0
  799. package/dist/server/services/quota-fetcher/providers/copilot.d.ts +17 -0
  800. package/dist/server/services/quota-fetcher/providers/copilot.js +75 -0
  801. package/dist/server/services/quota-fetcher/providers/cursor.d.ts +17 -0
  802. package/dist/server/services/quota-fetcher/providers/cursor.js +123 -0
  803. package/dist/server/services/quota-fetcher/providers/grok.d.ts +18 -0
  804. package/dist/server/services/quota-fetcher/providers/grok.js +89 -0
  805. package/dist/server/services/quota-fetcher/providers/kimi.d.ts +20 -0
  806. package/dist/server/services/quota-fetcher/providers/kimi.js +89 -0
  807. package/dist/server/services/quota-fetcher/providers/minimax.d.ts +29 -0
  808. package/dist/server/services/quota-fetcher/providers/minimax.js +227 -0
  809. package/dist/server/services/quota-fetcher/providers/zai.d.ts +17 -0
  810. package/dist/server/services/quota-fetcher/providers/zai.js +58 -0
  811. package/dist/server/services/quota-fetcher/service.d.ts +28 -0
  812. package/dist/server/services/quota-fetcher/service.js +58 -0
  813. package/dist/server/services/quota-fetcher/usage.d.ts +22 -0
  814. package/dist/server/services/quota-fetcher/usage.js +49 -0
  815. package/dist/server/terminal/activity/terminal-activity-tracker.d.ts +20 -0
  816. package/dist/server/terminal/activity/terminal-activity-tracker.js +59 -0
  817. package/dist/server/terminal/agent-hooks/agent-hook-installer.d.ts +62 -0
  818. package/dist/server/terminal/agent-hooks/agent-hook-installer.js +117 -0
  819. package/dist/server/terminal/agent-hooks/claude/claude-settings.d.ts +7 -0
  820. package/dist/server/terminal/agent-hooks/claude/claude-settings.js +88 -0
  821. package/dist/server/terminal/agent-hooks/claude/claude.d.ts +4 -0
  822. package/dist/server/terminal/agent-hooks/claude/claude.js +47 -0
  823. package/dist/server/terminal/agent-hooks/codex/codex-settings.d.ts +7 -0
  824. package/dist/server/terminal/agent-hooks/codex/codex-settings.js +99 -0
  825. package/dist/server/terminal/agent-hooks/codex/codex.d.ts +4 -0
  826. package/dist/server/terminal/agent-hooks/codex/codex.js +30 -0
  827. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.d.ts +4 -0
  828. package/dist/server/terminal/agent-hooks/opencode/opencode-plugin.js +46 -0
  829. package/dist/server/terminal/agent-hooks/opencode/opencode.d.ts +3 -0
  830. package/dist/server/terminal/agent-hooks/opencode/opencode.js +23 -0
  831. package/dist/server/terminal/agent-hooks/provider-registry.d.ts +24 -0
  832. package/dist/server/terminal/agent-hooks/provider-registry.js +36 -0
  833. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.d.ts +10 -0
  834. package/dist/server/terminal/agent-hooks/terminal-agent-hook-setting.js +26 -0
  835. package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
  836. package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
  837. package/dist/server/terminal/terminal-capture.d.ts +12 -0
  838. package/dist/server/terminal/terminal-capture.js +43 -0
  839. package/dist/server/terminal/terminal-manager-factory.d.ts +6 -0
  840. package/dist/server/terminal/terminal-manager-factory.js +5 -0
  841. package/dist/server/terminal/terminal-manager.d.ts +78 -0
  842. package/dist/server/terminal/terminal-manager.js +319 -0
  843. package/dist/server/terminal/terminal-output-coalescer.d.ts +34 -0
  844. package/dist/server/terminal/terminal-output-coalescer.js +73 -0
  845. package/dist/server/terminal/terminal-restore.d.ts +18 -0
  846. package/dist/server/terminal/terminal-restore.js +52 -0
  847. package/dist/server/terminal/terminal-session-controller.d.ts +85 -0
  848. package/dist/server/terminal/terminal-session-controller.js +819 -0
  849. package/dist/server/terminal/terminal-ts-loader.mjs +20 -0
  850. package/dist/server/terminal/terminal-worker-process.d.ts +2 -0
  851. package/dist/server/terminal/terminal-worker-process.js +304 -0
  852. package/dist/server/terminal/terminal-worker-protocol.d.ts +119 -0
  853. package/dist/server/terminal/terminal-worker-protocol.js +2 -0
  854. package/dist/server/terminal/terminal.d.ts +151 -0
  855. package/dist/server/terminal/terminal.js +1161 -0
  856. package/dist/server/terminal/worker-terminal-manager.d.ts +20 -0
  857. package/dist/server/terminal/worker-terminal-manager.js +688 -0
  858. package/dist/server/utils/build-metadata-prompt.d.ts +19 -0
  859. package/dist/server/utils/build-metadata-prompt.js +29 -0
  860. package/dist/server/utils/checkout-git.d.ts +253 -0
  861. package/dist/server/utils/checkout-git.js +2238 -0
  862. package/dist/server/utils/directory-suggestions.d.ts +26 -0
  863. package/dist/server/utils/directory-suggestions.js +860 -0
  864. package/dist/server/utils/git-rev-parse-path.d.ts +3 -0
  865. package/dist/server/utils/git-rev-parse-path.js +21 -0
  866. package/dist/server/utils/github-remote.d.ts +9 -0
  867. package/dist/server/utils/github-remote.js +62 -0
  868. package/dist/server/utils/paseo-config-file.d.ts +30 -0
  869. package/dist/server/utils/paseo-config-file.js +90 -0
  870. package/dist/server/utils/path.d.ts +18 -0
  871. package/dist/server/utils/path.js +123 -0
  872. package/dist/server/utils/project-icon.d.ts +39 -0
  873. package/dist/server/utils/project-icon.js +364 -0
  874. package/dist/server/utils/promise-timeout.d.ts +9 -0
  875. package/dist/server/utils/promise-timeout.js +25 -0
  876. package/dist/server/utils/run-git-command.d.ts +37 -0
  877. package/dist/server/utils/run-git-command.js +232 -0
  878. package/dist/server/utils/spawn.d.ts +33 -0
  879. package/dist/server/utils/spawn.js +71 -0
  880. package/dist/server/utils/string-command-shell.d.ts +10 -0
  881. package/dist/server/utils/string-command-shell.js +21 -0
  882. package/dist/server/utils/tree-kill.d.ts +18 -0
  883. package/dist/server/utils/tree-kill.js +77 -0
  884. package/dist/server/utils/windows-command.d.ts +15 -0
  885. package/dist/server/utils/windows-command.js +41 -0
  886. package/dist/server/utils/worktree-metadata.d.ts +39 -0
  887. package/dist/server/utils/worktree-metadata.js +174 -0
  888. package/dist/server/utils/worktree.d.ts +205 -0
  889. package/dist/server/utils/worktree.js +1119 -0
  890. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css +1 -0
  891. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.br +0 -0
  892. package/dist/server/web-ui/_expo/static/css/xterm-3bb1704bf6cb0876640973dc0244b4cb.css.gz +0 -0
  893. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js +5 -0
  894. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.br +0 -0
  895. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.gz +0 -0
  896. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.map.br +0 -0
  897. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-67f19cbfce758da6aa23c38c2dbd6a33.js.map.gz +0 -0
  898. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js +3 -0
  899. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.br +0 -0
  900. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.gz +0 -0
  901. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.map.br +0 -0
  902. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-6888615a6b700f975c1f78501ad8577c.js.map.gz +0 -0
  903. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js +16683 -0
  904. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.br +0 -0
  905. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.gz +0 -0
  906. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.map.br +0 -0
  907. package/dist/server/web-ui/_expo/static/js/web/index-dc1e81e7cbad84d30a01c48dcc9a5a3e.js.map.gz +0 -0
  908. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js +3 -0
  909. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.br +0 -0
  910. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.gz +0 -0
  911. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.map.br +0 -0
  912. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-991ae7fd00f9fed45084fbb39160e8b8.js.map.gz +0 -0
  913. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js +5 -0
  914. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.br +0 -0
  915. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.gz +0 -0
  916. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.map.br +0 -0
  917. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-fdb4d787a259ce350f76280ca0e2bfa2.js.map.gz +0 -0
  918. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js +11 -0
  919. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.br +0 -0
  920. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.gz +0 -0
  921. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.map.br +0 -0
  922. package/dist/server/web-ui/_expo/static/js/web/profiler-9502a8e721925a6353ec45f7613f537e.js.map.gz +0 -0
  923. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js +60 -0
  924. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.br +0 -0
  925. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.gz +0 -0
  926. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.map.br +0 -0
  927. package/dist/server/web-ui/_expo/static/js/web/startRecording-4ed5094aa059a0fc724f5ae70dd2f693.js.map.gz +0 -0
  928. package/dist/server/web-ui/apple-touch-icon.png +0 -0
  929. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon-mask.0a328cd9c1afd0afe8e3b1ec5165b1b4.png +0 -0
  930. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/back-icon.35ba0eaec5a4f5ed12ca16fabeae451d.png +0 -0
  931. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55.png +0 -0
  932. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@2x.png +0 -0
  933. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@3x.png +0 -0
  934. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/clear-icon.c94f6478e7ae0cdd9f15de1fcb9e5e55@4x.png +0 -0
  935. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7.png +0 -0
  936. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@2x.png +0 -0
  937. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@3x.png +0 -0
  938. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/close-icon.808e1b1b9b53114ec2838071a7e6daa7@4x.png +0 -0
  939. package/dist/server/web-ui/assets/__node_modules/@react-navigation/elements/lib/module/assets/search-icon.286d67d3f74808a60a78d3ebf1a5fb57.png +0 -0
  940. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/arrow_down.017bc6ba3fc25503e5eb5e53826d48a8.png +0 -0
  941. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/error.d1ea1496f9057eb392d5bbf3732a61b7.png +0 -0
  942. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/file.19eeb73b9593a38f8e9f418337fc7d10.png +0 -0
  943. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/forward.d8b800c443b8972542883e0b9de2bdc6.png +0 -0
  944. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/pkg.ab19f4cbc543357183a20571f68380a3.png +0 -0
  945. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/sitemap.412dd9275b6b48ad28f5e3d81bb1f626.png +0 -0
  946. package/dist/server/web-ui/assets/__node_modules/expo-router/assets/unmatched.20e71bdf79e3a97bf55fd9e164041578.png +0 -0
  947. package/dist/server/web-ui/assets/assets/images/editor-apps/antigravity.6e91a685c33435e0b466a56db86cf141.png +0 -0
  948. package/dist/server/web-ui/assets/assets/images/editor-apps/cursor.c31d6bce4fe9aadc3fe59962f4c4fcf3.png +0 -0
  949. package/dist/server/web-ui/assets/assets/images/editor-apps/file-explorer.3e15e8f72c825c85ce336bcb0cdef776.png +0 -0
  950. package/dist/server/web-ui/assets/assets/images/editor-apps/finder.7f68fc2c475621a672e1be09309d5567.png +0 -0
  951. package/dist/server/web-ui/assets/assets/images/editor-apps/vscode.832bdb4c685d930f1c864c793703600b.png +0 -0
  952. package/dist/server/web-ui/assets/assets/images/editor-apps/webstorm.aa5dc2cd8c20cc0a155c4c5c5ab3c5f5.png +0 -0
  953. package/dist/server/web-ui/assets/assets/images/editor-apps/zed.f3a670b7f9aa226da4fe53fb86f1abbd.png +0 -0
  954. package/dist/server/web-ui/assets/assets/images/favicon-dark-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  955. package/dist/server/web-ui/assets/assets/images/favicon-dark-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  956. package/dist/server/web-ui/assets/assets/images/favicon-dark.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  957. package/dist/server/web-ui/assets/assets/images/favicon-light-attention.882b3a27dcb2073e9e31b334f9ed9728.png +0 -0
  958. package/dist/server/web-ui/assets/assets/images/favicon-light-running.8112342ff0d39e047a7f8d4fad9402f3.png +0 -0
  959. package/dist/server/web-ui/assets/assets/images/favicon-light.8005ed36ac07a5a7c60de25780897bd4.png +0 -0
  960. package/dist/server/web-ui/assets/assets/images/notification-icon.3bf81d33ddbf380606bdd248ba83e158.png +0 -0
  961. package/dist/server/web-ui/favicon.ico +0 -0
  962. package/dist/server/web-ui/index.html +196 -0
  963. package/dist/server/web-ui/index.html.br +0 -0
  964. package/dist/server/web-ui/index.html.gz +0 -0
  965. package/dist/server/web-ui/manifest.json +27 -0
  966. package/dist/server/web-ui/manifest.json.br +0 -0
  967. package/dist/server/web-ui/manifest.json.gz +0 -0
  968. package/dist/server/web-ui/metadata.json +1 -0
  969. package/dist/server/web-ui/metadata.json.br +1 -0
  970. package/dist/server/web-ui/metadata.json.gz +0 -0
  971. package/dist/server/web-ui/pwa-icon-192.png +0 -0
  972. package/dist/server/web-ui/pwa-icon-512.png +0 -0
  973. package/dist/server/web-ui/robots.txt +2 -0
  974. package/dist/server/web-ui/service-worker.js +87 -0
  975. package/dist/server/web-ui/service-worker.js.br +0 -0
  976. package/dist/server/web-ui/service-worker.js.gz +0 -0
  977. package/dist/src/executable-resolution/executable-resolution.js +118 -0
  978. package/dist/src/executable-resolution/windows.js +65 -0
  979. package/dist/src/server/agent/provider-launch-config.js +183 -0
  980. package/dist/src/server/paseo-env.js +53 -0
  981. package/dist/src/server/paseo-home.js +19 -0
  982. package/dist/src/server/persisted-config.js +424 -0
  983. package/dist/src/server/pid-lock.js +170 -0
  984. package/dist/src/server/private-files.js +37 -0
  985. package/dist/src/server/speech/providers/local/sherpa/sherpa-runtime-env.js +84 -0
  986. package/dist/src/server/update/slots.js +97 -0
  987. package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
  988. package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
  989. package/dist/src/utils/spawn.js +71 -0
  990. package/dist/src/utils/windows-command.js +41 -0
  991. package/package.json +146 -0
@@ -0,0 +1,2075 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { PtyExitedError } from "./pty.js";
6
+ import { TranscriptSdkReader } from "./transcript-sdk-reader.js";
7
+ /**
8
+ * PtyQuery — a `Query`-shaped adapter over an interactive `claude` PTY.
9
+ *
10
+ * The query pump consumes `for await (message of query)` plus a handful of control
11
+ * methods. `SdkTransport` gets its `Query` straight from the SDK; the PTY transport has
12
+ * no SDK, so PtyQuery reconstructs the same `SDKMessage` stream:
13
+ * - user turns → typed into the PTY (write + Enter), gated on claude being ready
14
+ * - assistant / tool_result messages → tailed from the session transcript JSONL
15
+ * - turn end → detected from the transcript (an `assistant` with a terminal
16
+ * `stop_reason` after a short idle) → a synthesized `result` message,
17
+ * which is what makes the pump emit `turn_completed`.
18
+ *
19
+ * Turn-end is transcript-derived (not hook-derived) on purpose: it removes the
20
+ * `paseo agent-hook` → socket round-trip from the critical path. A `Stop`-hook fast-path
21
+ * can be layered on later.
22
+ *
23
+ * Control methods: `interrupt()` sends ESC to the TUI; `setModel`/`setPermissionMode`/
24
+ * thinking are already driven by the `instanceof PtyTransport` branches in agent.ts
25
+ * (which write to the transport directly), so here they are safe no-ops. The long tail
26
+ * of `Query` methods (rewindFiles, readFile, …) is unsupported on the PTY path; the
27
+ * instance is cast `as unknown as Query` at the seam.
28
+ */
29
+ /**
30
+ * Readiness is "the terminal stopped talking", not "the terminal said something".
31
+ *
32
+ * This used to schedule a settle 1500ms after EVERY chunk, and because settling is
33
+ * idempotent the first chunk always won: readiness fired 1.5s after the first byte no
34
+ * matter how much boot output was still streaming. The splash banner is a first byte, so
35
+ * on a slow start (seven MCP servers to connect) paseo began typing into a terminal that
36
+ * was not accepting input yet, and the prompt was lost outright. Observed 2026-08-01: a
37
+ * session whose entire recording was the splash line, then 70s of failed Enters.
38
+ *
39
+ * Resetting the timer on each chunk makes it a real debounce, so it settles once output
40
+ * has actually gone quiet. The ceiling is correspondingly generous, because waiting a few
41
+ * extra seconds costs nothing next to losing the message.
42
+ */
43
+ const READY_QUIET_SETTLE_MS = 1500;
44
+ const READY_MAX_WAIT_MS = 60000;
45
+ /**
46
+ * How long the idle prompt must hold before we believe it. The TUI repaints its footer
47
+ * mid-turn, so a single frame without the busy marker is a flicker, not idleness.
48
+ */
49
+ const READY_IDLE_STABLE_MS = 400;
50
+ /**
51
+ * How long a control slash command (`/model`, `/effort`) waits for the terminal to accept
52
+ * input before giving up.
53
+ *
54
+ * Bounded by the same reasoning as a stall: if the TUI has not shown an input box in that
55
+ * long, waiting further buys nothing and the caller deserves to hear that the setting did
56
+ * not apply.
57
+ */
58
+ const CONTROL_COMMAND_MAX_WAIT_MS = 60000;
59
+ /** Poll interval while waiting for the input box. Cheap: it only reads cached state. */
60
+ const CONTROL_COMMAND_POLL_MS = 100;
61
+ /**
62
+ * Absolute ceiling on waiting for a busy terminal to go idle. Only reached while the TUI is
63
+ * actively telling us it is working, so it is a backstop against a wedged agent, not a boot
64
+ * budget. Matches the turn-stall backstop deliberately: both answer "this terminal claims to
65
+ * be working but never finished".
66
+ */
67
+ const READY_BUSY_MAX_WAIT_MS = 10 * 60000;
68
+ /** The TUI prints this in its footer for exactly as long as a turn is running. */
69
+ const BUSY_MARKER = "esc to interrupt";
70
+ /**
71
+ * Footer fragments that only render when the input box is accepting typing. Matched against
72
+ * the rolling tail, and only believed when {@link BUSY_MARKER} is absent, because the busy
73
+ * footer contains some of these too (`… · esc to interrupt · ← for agents`).
74
+ */
75
+ const IDLE_PROMPT_MARKERS = [
76
+ "for agents",
77
+ "for shortcuts",
78
+ "bypass permissions",
79
+ "shift+tab to cycle",
80
+ "for commands",
81
+ ];
82
+ const TURN_END_IDLE_MS = 700;
83
+ /**
84
+ * How long to wait for the TUI to react to a slash command before giving up on it. Short:
85
+ * a command either renders immediately or was swallowed, and pressing Enter again would
86
+ * run it twice.
87
+ */
88
+ const SLASH_COMMAND_ECHO_WAIT_MS = 3000;
89
+ /**
90
+ * Echo-verified delivery.
91
+ *
92
+ * A claude TUI that is still parsing a large resumed transcript PAINTS its idle footer
93
+ * before its input loop is live, and in that state it DISCARDS keystrokes: typing produces
94
+ * no echo at all. Observed 2026-08-03 on a 215MB session: attach at 13:03:00, idle footer
95
+ * by 13:03:04, prompt typed plus five Enters, zero echo in the recording, no user line,
96
+ * message lost -- while every successful delivery in the same recordings shows the typed
97
+ * text echoed back. Echo is therefore the one signal that distinguishes "idle" from
98
+ * "idle-looking but deaf", and its absence also proves the keystrokes were discarded,
99
+ * which is exactly what makes retyping safe rather than a duplication risk.
100
+ */
101
+ /**
102
+ * How much of the typed text must be found in the terminal output for it to count as an
103
+ * echo. Byte-counting was not enough: a freshly spawned claude under load keeps painting
104
+ * boot output (splash, MCP connects) after the readiness gate settles, and those bytes
105
+ * passed the old any-bytes check while the input loop was still dead -- three scheduled
106
+ * spawns failed that way in five minutes on a loaded box, 2026-08-03. Boot output never
107
+ * contains the user's message; a real input-box echo always does.
108
+ */
109
+ /** How much of the message identifies it in a transcript record. */
110
+ const RECEIPT_FINGERPRINT_CHARS = 64;
111
+ /** Below this length a fingerprint must match a record WHOLE, never as a substring. */
112
+ const RECEIPT_EXACT_MATCH_BELOW_CHARS = 12;
113
+ /** How long one delivery attempt waits for its transcript receipt before retrying. */
114
+ const RECEIPT_WINDOW_MS = 4000;
115
+ /** A queued message can only land when the running turn ends, so wait far longer for it. */
116
+ const QUEUED_RECEIPT_WINDOW_MS = 10 * 60000;
117
+ /**
118
+ * The TUI's own words for "your message is in the queue".
119
+ *
120
+ * A message typed while a turn is running is QUEUED by claude, and a queued message
121
+ * appends NO `user` record until the running turn ends -- so the one delivery oracle
122
+ * (receiptSince) cannot fire, by construction, for minutes at a time. The old test for
123
+ * "is a turn running" was `lastTranscriptAt > attemptStartedAt`, i.e. "did the transcript
124
+ * grow during my 4s window". That is a proxy, and it reads FALSE whenever the running
125
+ * turn is simply quiet for four seconds (thinking, or one slow tool call). When it reads
126
+ * false the loop concludes the terminal is deaf and retypes.
127
+ *
128
+ * Retyping a QUEUED message is not idempotent: the composer is empty (the text already
129
+ * left it), so Ctrl+U clears nothing and the retype either stacks a second queued copy or
130
+ * concatenates onto text the clear failed to kill. Observed live 2026-08-20 on session
131
+ * 0b1ec622, four retries logged at 18:28:13 / 18:29:59 / 18:30:06 / 18:30:14 / 18:30:27,
132
+ * producing these transcript records:
133
+ *
134
+ * mint another xpmint another xp
135
+ * a new one with another cpf: 76797678153, caixaa new one with another cpf: ...
136
+ *
137
+ * The terminal was stating the answer the whole time. Paseo's own turn heartbeat captured
138
+ * it at 18:30:15: `... Press up to edit queued messages - esc to interrupt`. This reads
139
+ * that footer instead of inferring it from file growth.
140
+ */
141
+ const QUEUED_FOOTER_RE = /Press up to edit queued messages/i;
142
+ /** How much of the tail to scan for the footer. It is repainted on every frame. */
143
+ const QUEUED_FOOTER_SCAN_CHARS = 2000;
144
+ /** Backoff between retype attempts against a deaf terminal. */
145
+ const DEAF_RETRY_INITIAL_MS = 2000;
146
+ const DEAF_RETRY_MAX_MS = 60000;
147
+ const TERMINAL_STOP_REASONS = new Set(["end_turn", "stop_sequence", "max_tokens"]);
148
+ /**
149
+ * Liveness backstop: how long an in-flight turn may append NOTHING to the transcript
150
+ * before PtyQuery gives up and synthesizes a failed `result`.
151
+ *
152
+ * Without this the PTY path has no liveness detection at all, and a claude-side stall is
153
+ * indistinguishable from a long turn: the agent sits at `status: running` forever with no
154
+ * output, no error and no timeout. Observed 2026-07-31: an agent accepted a prompt, hit a
155
+ * provider auth failure, and stayed "running" for 2h33m emitting nothing, because
156
+ * - the PTY's own output is unsubscribed once the readiness gate settles, so whatever the
157
+ * TUI prints (`API Error 401 · Retrying in 32s`, …) is never seen, and
158
+ * - `scheduleTurnEndCheck()` is only ever armed from `onTranscriptMessage()`, so silence
159
+ * never arms anything, and `maybeCompleteTurn()` returns early unless it already saw a
160
+ * terminal `stop_reason`.
161
+ * `TURN_END_IDLE_MS` is a 700ms turn-BOUNDARY debounce, not a stall detector.
162
+ *
163
+ * Deliberately generous: a legitimate turn can be quiet for a long time (one slow tool call
164
+ * writes nothing until its `tool_result` lands), and a false positive marks a healthy turn
165
+ * failed. Ten minutes is far past any normal tool call while still bounded. Tune per-call
166
+ * with `stallTimeoutMs`; `0` disables the backstop entirely.
167
+ */
168
+ const TURN_STALL_TIMEOUT_MS = 10 * 60000;
169
+ /**
170
+ * How much terminal text to keep for diagnosis. The TUI is the only place that says what
171
+ * claude is actually doing while it is quiet ("Compacting conversation…", a retry notice,
172
+ * a prompt), and PtyQuery otherwise unsubscribes from it once the readiness gate settles,
173
+ * so a stall investigation has nothing to read.
174
+ *
175
+ * A rolling tail rather than a live log on purpose: the TUI redraws constantly (spinner
176
+ * frames, cursor moves), so streaming it would flood the daemon log for no benefit. This
177
+ * keeps only the last few KB and emits it exactly once, when a turn is declared stalled.
178
+ */
179
+ const TERMINAL_TAIL_MAX_CHARS = 4000;
180
+ /** Chars of the tail to include in the user-facing stall message; the rest goes to the log. */
181
+ const TERMINAL_TAIL_EXCERPT_CHARS = 300;
182
+ /**
183
+ * Telemetry. The PTY path had none: a session that went quiet left no record of what it had
184
+ * been doing, and four distinct failure modes (prompt never submitted, stall after a
185
+ * compaction, never finished booting, genuinely waiting on the model) were indistinguishable
186
+ * from each other and from a slow turn. Each of the three below exists to separate them.
187
+ */
188
+ /** Heartbeat cadence while a turn is outstanding. Cheap: one line per turn per interval. */
189
+ const LIVENESS_HEARTBEAT_MS = 30000;
190
+ /**
191
+ * The claude TUI's post-interrupt modal. A cancelled turn leaves the terminal sitting on
192
+ * `Interrupted · What should Claude do instead?`, and in that state Enter answers the dialog
193
+ * instead of submitting a turn, so every subsequent prompt is swallowed whole and the retry
194
+ * loop in submitTurn cannot win. Observed live 2026-08-01: a turn was cancelled at 16:56:08
195
+ * and the next message failed all five submit attempts 35s later.
196
+ *
197
+ * Matched against the terminal tail rather than guessed at, and acted on only while the
198
+ * sighting is recent, because sending ESC blindly would interrupt a healthy running turn.
199
+ */
200
+ const INTERRUPT_PROMPT_MARKER = "what should claude do instead";
201
+ /**
202
+ * The CLI's large-session resume dialog. Resuming a session past a usage threshold shows
203
+ *
204
+ * This session is Nd old and NNNk tokens. ...We recommend resuming from a summary.
205
+ * > 1. Resume from summary (recommended) <- PRE-SELECTED
206
+ * 2. Resume full session as-is
207
+ * 3. Don't ask me again
208
+ *
209
+ * "Resume from summary" runs /compact, and confirming it is recorded in the transcript as
210
+ * a USER-TYPED /compact (entrypoint cli, userType external) -- indistinguishable from real
211
+ * input. Left unhandled, paseo's flow walks straight into it: the dialog is quiet, so the
212
+ * readiness gate settles; the user's message is typed into the menu and eaten; and the
213
+ * submit Enter confirms the pre-selected compaction. Proven by isolated replication
214
+ * 2026-08-03: two sessions compacted from a single Enter with paseo not involved, a third
215
+ * resumed intact when option 2 was selected instead.
216
+ *
217
+ * The answer is option 2: the user asked to talk to this session, so resume it whole.
218
+ * Selecting it takes one Down (to move off option 1) and one Enter.
219
+ */
220
+ const RESUME_DIALOG_MARKER = "resume from summary";
221
+ /** How long a sighting stays actionable; mirrors INTERRUPT_PROMPT_TTL_MS reasoning. */
222
+ const RESUME_DIALOG_TTL_MS = 5 * 60000;
223
+ /** How long a sighting stays actionable. Beyond this, assume the modal is long gone. */
224
+ const INTERRUPT_PROMPT_TTL_MS = 5 * 60000;
225
+ /**
226
+ * Full-session terminal recording, written next to the daemon log under
227
+ * `$PASEO_HOME/pty-recordings/<sessionId>.log`.
228
+ *
229
+ * The 4KB in-memory tail only survives to the next stall and holds seconds of history once
230
+ * the spinner is running; a session that failed at boot produced 579 chars total. A file
231
+ * keeps the whole run, so a failure can be read backwards afterwards instead of inferred.
232
+ * Capped and truncated rather than rotated: this is diagnostic data, the recent past is what
233
+ * matters, and an unbounded file on a long-running agent is its own incident.
234
+ */
235
+ /** Tabs to try before giving up on finding the picker's Submit tab. */
236
+ const DIALOG_SUBMIT_MAX_TABS = 12;
237
+ const RECORDING_MAX_BYTES = 4 * 1024 * 1024;
238
+ /** Keep this much when the cap is hit, so a truncation never leaves an empty file. */
239
+ const RECORDING_KEEP_BYTES = 1024 * 1024;
240
+ /**
241
+ * Monotonic milliseconds for every liveness comparison here.
242
+ *
243
+ * Never Date.now(): a stepping wall clock silently breaks all of them. Measured on this
244
+ * machine, the clock oscillated +/-48.6s roughly six times a minute, which made the
245
+ * readiness gate believe its 60s budget had elapsed after ~11s and type the user's prompt
246
+ * into a terminal that was still working, losing the message. The tell was an overshoot of
247
+ * 3641ms on a loop that ticks every 500ms: real elapse cannot overshoot by more than one
248
+ * tick, a clock step can.
249
+ */
250
+ function monotonicNowMs() {
251
+ return performance.now();
252
+ }
253
+ export class PtyQuery {
254
+ constructor(opts) {
255
+ /** One queued notice per message, not one per retry. Reset by deliverPrompt(). */
256
+ this.queuedNoticeSent = false;
257
+ // output queue → async iterator
258
+ this.outQueue = [];
259
+ this.outResolvers = [];
260
+ this.done = false;
261
+ this.offReadyData = null;
262
+ /** Unsubscribes the transport exit tap. Teardown MUST call this. */
263
+ this.offExitTap = null;
264
+ /** Set once the agent process has exited; the two remaining "still running" claims read it. */
265
+ this.transportExited = null;
266
+ /**
267
+ * Cancels the readiness gate and its timers.
268
+ *
269
+ * Teardown MUST call this. A query closed before the gate settles used to leave its
270
+ * deadline interval running, so a session that attached and detached in the same
271
+ * millisecond still logged "readiness gate hit its timeout" a full minute later, warning
272
+ * about a terminal nobody was waiting on and leaking a timer per closed session.
273
+ */
274
+ this.cancelReadiness = null;
275
+ // turn-end tracking
276
+ this.turnEndTimer = null;
277
+ this.lastStopReason = null;
278
+ this.lastUsage = null;
279
+ this.turnStartedAt = 0;
280
+ this.numTurns = 0;
281
+ this.stallTimer = null;
282
+ this.turnInFlight = false;
283
+ // terminal tail (see TERMINAL_TAIL_MAX_CHARS)
284
+ this.terminalTail = "";
285
+ this.offTerminalTap = null;
286
+ // telemetry
287
+ this.startedAt = monotonicNowMs();
288
+ this.recordingPath = null;
289
+ this.recordingBytes = 0;
290
+ this.ptyBytesTotal = 0;
291
+ this.ptyBytesAtLastBeat = 0;
292
+ this.lastMeaningfulLine = "";
293
+ this.lastTranscriptAt = 0;
294
+ this.heartbeatTimer = null;
295
+ this.readySettledBy = null;
296
+ /** Last time the TUI footer said a turn was running. 0 = never seen. */
297
+ this.lastBusyAt = 0;
298
+ /**
299
+ * Serializes everything that TYPES into the terminal.
300
+ *
301
+ * Prompt delivery and control slash commands both write to the same single input line, and
302
+ * before this they ran on independent async paths with nothing ordering them.
303
+ */
304
+ this.writeGate = Promise.resolve();
305
+ /** Last sighting of the large-session resume dialog. 0 = never seen. */
306
+ this.resumeDialogAt = 0;
307
+ /** Set when the CLI rejected the just-submitted command; carries the CLI's own reply. */
308
+ this.rejectedCommandNotice = null;
309
+ /** Interactive TUI dialog awaiting the user (AskUserQuestion / plan approval), if any. */
310
+ this.pendingDialog = null;
311
+ /**
312
+ * Raw capture window for echo verification. The rolling tail deliberately drops lines
313
+ * with fewer than three letters as spinner noise, which also swallows the echo of a
314
+ * short message: "> go" never reaches the tail, so verifying against the tail held a
315
+ * two-letter message forever. While this is non-null, appendTerminalTail mirrors every
316
+ * stripped chunk here unfiltered.
317
+ */
318
+ this.echoProbe = null;
319
+ this.interruptPromptAt = 0;
320
+ this.transport = opts.transport;
321
+ this.input = opts.input;
322
+ this.sessionId = opts.sessionId;
323
+ this.transcriptPath = opts.transcriptPath;
324
+ this.logger = opts.logger;
325
+ this.stallTimeoutMs = opts.stallTimeoutMs ?? TURN_STALL_TIMEOUT_MS;
326
+ this.onDialogChange = opts.onDialogChange ?? null;
327
+ this.onNotice = opts.onNotice ?? null;
328
+ this.reader = new TranscriptSdkReader({
329
+ transcriptPath: opts.transcriptPath,
330
+ onMessage: (m) => this.onTranscriptMessage(m),
331
+ ...(opts.onUserPrompt ? { onUserPrompt: opts.onUserPrompt } : {}),
332
+ });
333
+ // Keep a rolling tail of terminal output. Registered BEFORE the readiness tap below so
334
+ // that looksIdle() sees the CURRENT chunk: handlers fire in registration order, and
335
+ // with the order reversed an idle prompt in the final frame was never noticed, because
336
+ // readiness inspected a tail that did not yet contain it. This one lives for the whole
337
+ // session so a stall has something to explain itself with. Chunks arrive already redacted (PtyTransport.onData
338
+ // runs redactChunk), so nothing further is needed here.
339
+ this.offTerminalTap = this.transport.onData((chunk) => this.appendTerminalTail(chunk));
340
+ // Learn about the agent process dying the moment it happens. Without this tap the only
341
+ // thing that ever noticed was the stall backstop, ten minutes later, and it then told the
342
+ // user the session was "still running and may recover" - of a process that had already
343
+ // printed its farewell banner and exited. The stall timer is for a live agent that is
344
+ // slow; it is the wrong instrument for a dead one.
345
+ this.offExitTap = this.transport.onExit((code, signal) => this.onTransportExit(code, signal));
346
+ // The executor stays trivial and settlement lives outside it, so there is exactly one
347
+ // resolve() call in the file. Timers and handlers below all funnel through settle(),
348
+ // which is guarded by `settled`; expressing that inside the executor tripped
349
+ // promise/no-multiple-resolved, and the rule is right that multiple resolve paths in an
350
+ // executor are hard to audit.
351
+ let resolveReady = () => { };
352
+ this.readyPromise = new Promise((resolve) => {
353
+ resolveReady = resolve;
354
+ });
355
+ let settled = false;
356
+ // Single resolve site. Both the normal outcomes and teardown funnel through here, so
357
+ // the promise provably resolves once no matter which timer or path gets there first.
358
+ const settle = (by) => {
359
+ if (settled)
360
+ return;
361
+ settled = true;
362
+ this.readySettledBy = by === "cancelled" ? null : by;
363
+ this.offReadyData?.();
364
+ clearTimeout(quietTimer ?? undefined);
365
+ clearTimeout(idleTimer ?? undefined);
366
+ clearInterval(deadlineTimer);
367
+ // A torn-down query never had a readiness problem, so it reports nothing.
368
+ if (by !== "cancelled") {
369
+ // Settling on the timeout means claude never produced output that looked like a
370
+ // booted TUI, so the prompt about to be typed may go into a terminal that is not
371
+ // listening yet and be lost outright. Loud on purpose.
372
+ const level = by === "timeout" ? "warn" : "debug";
373
+ this.logger[level]({
374
+ sessionId: this.sessionId,
375
+ settledBy: by,
376
+ elapsedMs: monotonicNowMs() - this.startedAt,
377
+ ptyBytes: this.ptyBytesTotal,
378
+ sawBusyMarker: this.lastBusyAt > 0,
379
+ lastLine: this.lastMeaningfulLine.slice(-120),
380
+ }, by === "timeout"
381
+ ? "PtyQuery: readiness gate hit its timeout, claude may not be accepting input yet"
382
+ : "PtyQuery: readiness settled on terminal output");
383
+ }
384
+ resolveReady();
385
+ };
386
+ const finish = settle;
387
+ this.cancelReadiness = () => settle("cancelled");
388
+ let quietTimer = null;
389
+ let idleTimer = null;
390
+ this.offReadyData = this.transport.onData(() => {
391
+ // Two independent paths to ready, because a PtyQuery attaches to a LONG-LIVED
392
+ // terminal, not only to a fresh boot (observed 2026-08-01: nine attach/detach
393
+ // cycles on one session's PTY).
394
+ //
395
+ // - quiet: nothing has been painted for a while. Correct for a cold boot.
396
+ // - idle-prompt: the footer positively shows an input box and no busy marker.
397
+ //
398
+ // Quiet ALONE is wrong on re-attach: if the agent is mid-turn the spinner repaints
399
+ // at a couple of KB/s forever, so "went quiet" can never fire and the gate used to
400
+ // fall through to its timeout and type into a working terminal. That was the real
401
+ // cause of the lost prompts, and it is why absence-of-output cannot be the only
402
+ // signal here.
403
+ if (quietTimer)
404
+ clearTimeout(quietTimer);
405
+ quietTimer = setTimeout(() => finish("quiet"), READY_QUIET_SETTLE_MS);
406
+ quietTimer.unref?.();
407
+ if (this.looksIdle()) {
408
+ // Hold the idle read briefly: the footer repaints mid-turn and a single frame
409
+ // without the busy marker is a flicker, not idleness.
410
+ if (!idleTimer) {
411
+ idleTimer = setTimeout(() => {
412
+ if (this.looksIdle())
413
+ finish("idle-prompt");
414
+ idleTimer = null;
415
+ }, READY_IDLE_STABLE_MS);
416
+ idleTimer.unref?.();
417
+ }
418
+ }
419
+ else if (idleTimer) {
420
+ clearTimeout(idleTimer);
421
+ idleTimer = null;
422
+ }
423
+ });
424
+ // The deadline is conditional on evidence, not a flat budget. Only a terminal showing
425
+ // NO sign of life gets cut off at the short budget.
426
+ //
427
+ // Liveness is "has this PTY ever produced output", NOT "did the busy footer repaint
428
+ // recently". An earlier version keyed on the footer and timed out anyway on a session
429
+ // that was plainly working (60017ms, 78991 bytes, sawBusyMarker true but outside the
430
+ // window). Measured across captured sessions, the gap between footer repaints has a
431
+ // median of 37.7s and a p90 of 483s, so no recency window is defensible.
432
+ //
433
+ // Reaching this branch at all already proves the terminal is streaming: the quiet
434
+ // timer above resolves readiness after 1.5s of silence, so if we are still un-ready
435
+ // the quiet timer must be getting reset continuously. A genuinely dead terminal never
436
+ // fires onData, never arms the quiet timer, and never records a byte, which is exactly
437
+ // the case the short budget exists for.
438
+ const deadlineTimer = setInterval(() => {
439
+ const waited = monotonicNowMs() - this.startedAt;
440
+ if (this.ptyBytesTotal > 0) {
441
+ if (waited >= READY_BUSY_MAX_WAIT_MS)
442
+ finish("timeout");
443
+ return;
444
+ }
445
+ if (waited >= READY_MAX_WAIT_MS)
446
+ finish("timeout");
447
+ }, 500);
448
+ deadlineTimer.unref?.();
449
+ this.openRecording();
450
+ if (opts.initMessage)
451
+ this.emit(opts.initMessage);
452
+ this.reader.start();
453
+ void this.runInputLoop();
454
+ }
455
+ // ---- output queue / AsyncGenerator ----------------------------------------
456
+ emit(message) {
457
+ if (this.done)
458
+ return;
459
+ const resolve = this.outResolvers.shift();
460
+ if (resolve) {
461
+ resolve({ value: message, done: false });
462
+ return;
463
+ }
464
+ this.outQueue.push(message);
465
+ }
466
+ next() {
467
+ const queued = this.outQueue.shift();
468
+ if (queued !== undefined)
469
+ return Promise.resolve({ value: queued, done: false });
470
+ if (this.done)
471
+ return Promise.resolve({ value: undefined, done: true });
472
+ return new Promise((resolve) => this.outResolvers.push(resolve));
473
+ }
474
+ return() {
475
+ this.finish();
476
+ return Promise.resolve({ value: undefined, done: true });
477
+ }
478
+ throw(error) {
479
+ this.finish();
480
+ return Promise.reject(error instanceof Error ? error : new Error(String(error)));
481
+ }
482
+ [Symbol.asyncIterator]() {
483
+ return this;
484
+ }
485
+ finish() {
486
+ if (this.done)
487
+ return;
488
+ this.done = true;
489
+ const hadDialog = this.pendingDialog !== null;
490
+ this.pendingDialog = null;
491
+ if (hadDialog)
492
+ this.notifyDialogChange();
493
+ this.cancelReadiness?.();
494
+ if (this.turnEndTimer)
495
+ clearTimeout(this.turnEndTimer);
496
+ this.turnEndTimer = null;
497
+ this.turnInFlight = false;
498
+ this.clearStallTimer();
499
+ this.offReadyData?.();
500
+ this.offTerminalTap?.();
501
+ this.offTerminalTap = null;
502
+ this.offExitTap?.();
503
+ this.offExitTap = null;
504
+ this.stopHeartbeat();
505
+ this.recordLine("--- PtyQuery detached ---");
506
+ try {
507
+ this.reader.stop();
508
+ }
509
+ catch {
510
+ /* ignore */
511
+ }
512
+ while (this.outResolvers.length > 0) {
513
+ this.outResolvers.shift()?.({ value: undefined, done: true });
514
+ }
515
+ }
516
+ // ---- input → PTY ----------------------------------------------------------
517
+ async runInputLoop() {
518
+ // Gate the first prompt on claude being ready to accept input (boot + settle),
519
+ // otherwise early keystrokes are dropped by the TUI (observed in the spike).
520
+ await this.readyPromise;
521
+ try {
522
+ for await (const userMessage of this.input) {
523
+ if (this.done)
524
+ break;
525
+ // Inline images cannot be typed into a terminal, but the CLI agent can Read a
526
+ // file: stage each image to disk and reference it by path, the same serialization
527
+ // job toSdkUserMessage does for the SDK path.
528
+ const imageRefs = this.stageInlineImages(userMessage)
529
+ .map((p) => `[Attached image: ${p}]`)
530
+ .join(" ");
531
+ const text = [extractUserText(userMessage), imageRefs].filter(Boolean).join(" ").trim();
532
+ if (!text) {
533
+ // An empty prompt used to be dropped silently right here, AFTER the manager had
534
+ // already flipped the agent to "running": the sender stared at a spinner forever
535
+ // (observed live 2026-08-04, an image-only message from a 0.3.10 app whose
536
+ // composer also stripped the attachment). Nothing-deliverable is an answerable
537
+ // outcome, so answer it.
538
+ this.recordLine("--- message had no deliverable content; notifying sender ---");
539
+ this.emit(this.buildNothingDeliverableResult());
540
+ continue;
541
+ }
542
+ if (this.pendingDialog) {
543
+ // The TUI is showing a question/plan dialog: typed text would be eaten by it and
544
+ // pre-typing ESC would decline it. Either answer it (a reply that names an
545
+ // option) or tell the sender what is being asked - never type across it.
546
+ await this.answerPendingDialog(text);
547
+ continue;
548
+ }
549
+ this.turnStartedAt = monotonicNowMs();
550
+ // Clear modals BEFORE typing, not after: text typed into either dialog is
551
+ // consumed by it, so recovering at submit time is already too late.
552
+ await this.answerResumeDialog();
553
+ await this.clearInterruptPrompt();
554
+ // Deliberately NOT normalized (e.g. lowercasing an auto-capitalized command
555
+ // token): paseo must never silently rewrite what the user typed. A wrong command is
556
+ // handled by relaying the CLI's own rejection -- including its "did you mean"
557
+ // suggestion -- back to the sender, which corrects every rejection cause honestly
558
+ // instead of hiding one narrow cause invisibly.
559
+ // Same gate as sendControlCommand: a control command must never interleave with a
560
+ // prompt on the shared input line.
561
+ const delivery = await this.runExclusive(() => this.deliverPrompt(text));
562
+ if (this.done)
563
+ break;
564
+ if (delivery === "rejected") {
565
+ // Not lost, refused: hand the CLI's own answer (which includes its "did you
566
+ // mean" suggestion) to the sender instead of a phantom running turn.
567
+ const notice = this.rejectedCommandNotice ?? "the command was not recognized.";
568
+ this.rejectedCommandNotice = null;
569
+ this.emit(this.buildCommandRejectedResult(notice));
570
+ continue;
571
+ }
572
+ if (delivery === "exited") {
573
+ // Dead, not deaf. `continue`, never `break`: the exit tap is what ends this query,
574
+ // and breaking here would instead leave the input channel undrained, so a resend
575
+ // would sit unread and the sender would watch a spinner - the symptom being fixed.
576
+ this.emit(this.buildExitedResult(this.transportExited?.code ?? null, this.transportExited?.signal ?? null));
577
+ continue;
578
+ }
579
+ if (delivery === "undelivered") {
580
+ // No transcript receipt for the whole budget: the message never became a turn,
581
+ // nothing was queued, and only the sender can decide what to do next.
582
+ this.recordLine("--- no delivery receipt within the budget; message not delivered ---");
583
+ this.emit(this.buildSubmitFailedResult());
584
+ continue;
585
+ }
586
+ // "unconfirmed" is the slash-command case: exactly one Enter was sent and the CLI
587
+ // reacted, but a slash command may append no user record to confirm against.
588
+ // Treat it as started and let the stall backstop notice if it never runs -- never
589
+ // press Enter again, which would run the command twice.
590
+ // The turn is now claude's to answer; start the liveness clock. submitTurn() has
591
+ // confirmed claude accepted it (a real user line landed), so from here on silence
592
+ // means a stall, not a slow start.
593
+ this.turnInFlight = true;
594
+ this.armStallTimer();
595
+ this.startHeartbeat();
596
+ }
597
+ }
598
+ catch (err) {
599
+ this.logger.warn({ err }, "PtyQuery input loop ended");
600
+ }
601
+ }
602
+ /**
603
+ * The message the user sees when the terminal would not take their prompt.
604
+ *
605
+ * Only reachable while the query is still live (teardown breaks out before this), so it
606
+ * always describes a message that genuinely did not land and that only the user can
607
+ * resend. Shaped as a normal result so it renders in the timeline rather than being
608
+ * buried in the daemon log.
609
+ */
610
+ /** The CLI refused the command; relay its own reply so the sender can correct and resend. */
611
+ buildCommandRejectedResult(notice) {
612
+ const base = this.buildResult("submit_failed");
613
+ return {
614
+ ...base,
615
+ subtype: "error_during_execution",
616
+ is_error: true,
617
+ errors: [`The agent's CLI rejected the command: ${notice} Nothing was run.`],
618
+ };
619
+ }
620
+ /**
621
+ * Write each inline base64 image block to a temp file so the terminal agent can Read it
622
+ * by path. A staging failure is logged and skipped rather than thrown: the text part of
623
+ * the message (if any) must still be delivered.
624
+ */
625
+ stageInlineImages(message) {
626
+ const content = message.message?.content;
627
+ if (!Array.isArray(content))
628
+ return [];
629
+ const staged = [];
630
+ for (const block of content) {
631
+ if (!block || typeof block !== "object")
632
+ continue;
633
+ const image = block;
634
+ if (image.type !== "image")
635
+ continue;
636
+ const source = image.source;
637
+ if (!source || source.type !== "base64" || !source.data)
638
+ continue;
639
+ try {
640
+ const dir = path.join(os.tmpdir(), "paseo-pty-images", this.sessionId);
641
+ fs.mkdirSync(dir, { recursive: true });
642
+ const ext = IMAGE_MEDIA_TYPE_EXT[source.media_type ?? ""] ?? "png";
643
+ const file = path.join(dir, `image-${randomUUID().slice(0, 8)}.${ext}`);
644
+ fs.writeFileSync(file, Buffer.from(source.data, "base64"));
645
+ staged.push(file);
646
+ }
647
+ catch (err) {
648
+ this.logger.warn({ err }, "PtyQuery: failed to stage an inline image for delivery");
649
+ }
650
+ }
651
+ return staged;
652
+ }
653
+ /**
654
+ * The message had no text and no stageable image, so there is literally nothing to type.
655
+ * Emitted instead of dropping the message, because the manager has already marked the
656
+ * agent running by the time the input loop sees it: silence here is a forever-spinner.
657
+ */
658
+ buildNothingDeliverableResult() {
659
+ const base = this.buildResult("submit_failed");
660
+ return {
661
+ ...base,
662
+ subtype: "error_during_execution",
663
+ is_error: true,
664
+ errors: [
665
+ `Your message had nothing the terminal transport could deliver: no text, and no ` +
666
+ `image it could stage to disk. Nothing was sent, so please resend it with text.` +
667
+ `${this.stillRunningClause()}`,
668
+ ],
669
+ };
670
+ }
671
+ /**
672
+ * " The session itself is still running." -- but only when it is.
673
+ *
674
+ * The exit tap fixes the in-flight turn and the stall message. These two other results
675
+ * asserted the same thing unconditionally and are reachable after the process is gone, so
676
+ * they need the same correction. Wording matches buildExitedResult: no "aborted", and
677
+ * "status" rather than "code", so isAbortError() cannot swallow it and
678
+ * buildTurnFailedEvent() cannot scrape a phantom exit code out of it.
679
+ */
680
+ stillRunningClause() {
681
+ if (!this.transportExited)
682
+ return " The session itself is still running.";
683
+ return (` The agent process has exited, so the session is NOT running any more and will not ` +
684
+ `recover on its own; the conversation is still on disk, recover it with ` +
685
+ `"paseo import ${this.sessionId}".`);
686
+ }
687
+ /** Tell the provider the dialog state changed; never let a listener throw into the loop. */
688
+ notifyDialogChange() {
689
+ if (!this.onDialogChange)
690
+ return;
691
+ try {
692
+ this.onDialogChange(this.pendingDialog ? { ...this.pendingDialog } : null);
693
+ }
694
+ catch (err) {
695
+ this.logger.warn({ err }, "PtyQuery: dialog listener threw");
696
+ }
697
+ }
698
+ /** The dialog the TUI is waiting on, if any. */
699
+ interactiveDialog() {
700
+ return this.pendingDialog ? { ...this.pendingDialog } : null;
701
+ }
702
+ /**
703
+ * Answer the pending dialog by option index (0-based), the way the app's question card
704
+ * does. Returns false when there is no dialog or the keystrokes never registered.
705
+ */
706
+ async answerDialogByIndex(index) {
707
+ if (!this.pendingDialog || index < 0)
708
+ return false;
709
+ return await this.sendDialogAnswer(this.pendingDialog, index);
710
+ }
711
+ /**
712
+ * Answer a multi-question picker, one entry per question in on-screen order; a null
713
+ * leaves that question unanswered. The app's question card collects an answer for EVERY
714
+ * question, so collapsing them to a single index would throw away answers the user
715
+ * actually gave and submit those questions blank.
716
+ */
717
+ async answerDialogByIndexes(indexes) {
718
+ const dialog = this.pendingDialog;
719
+ if (!dialog)
720
+ return false;
721
+ if (indexes.length <= 1) {
722
+ const only = indexes[0];
723
+ return typeof only === "number" && only >= 0 ? await this.answerDialogByIndex(only) : false;
724
+ }
725
+ this.recordLine(`--- answering ${indexes.length} questions ---`);
726
+ for (const index of indexes) {
727
+ if (this.done || !this.pendingDialog)
728
+ break;
729
+ if (typeof index === "number" && index >= 0) {
730
+ for (let i = 0; i < index && !this.done; i++) {
731
+ await this.transport.writeRaw?.("\u001b[B");
732
+ await delay(DIALOG_KEY_DELAY_MS);
733
+ }
734
+ // Enter checks this question and moves focus to the next one.
735
+ await this.transport.writeRaw?.("\r");
736
+ }
737
+ else {
738
+ // No answer for this one: Tab past it rather than picking whatever is highlighted.
739
+ await this.transport.writeRaw?.("\t");
740
+ }
741
+ await delay(DIALOG_KEY_DELAY_MS);
742
+ }
743
+ await this.reachSubmitTabAndConfirm();
744
+ const deadline = monotonicNowMs() + DIALOG_ANSWER_CONFIRM_MS;
745
+ while (this.pendingDialog && !this.done && monotonicNowMs() < deadline) {
746
+ await delay(250);
747
+ }
748
+ if (this.pendingDialog) {
749
+ this.recordLine("--- multi-question answer did not register ---");
750
+ this.notifyDialogDriveFailed();
751
+ return false;
752
+ }
753
+ this.turnStartedAt = monotonicNowMs();
754
+ this.turnInFlight = true;
755
+ this.armStallTimer();
756
+ this.startHeartbeat();
757
+ return true;
758
+ }
759
+ /**
760
+ * Try to answer the pending TUI dialog with the user's reply. A reply that names an
761
+ * option (its 1-based number or its label) becomes Down-arrow presses + Enter, the same
762
+ * proven mechanism as the resume-dialog answerer. Anything else gets the question echoed
763
+ * back with the options, and nothing is typed - an unanswerable message must never turn
764
+ * into a silent decline.
765
+ */
766
+ async answerPendingDialog(text) {
767
+ const dialog = this.pendingDialog;
768
+ if (!dialog)
769
+ return;
770
+ const index = matchDialogOption(text, dialog.options);
771
+ if (index !== null) {
772
+ const answered = await this.sendDialogAnswer(dialog, index);
773
+ if (!answered) {
774
+ this.emit(this.buildDialogPendingResult(dialog, true));
775
+ }
776
+ return;
777
+ }
778
+ // The reply names no option. That is NOT a dead end: an AskUserQuestion picker always
779
+ // renders a free-text entry after the model's options, so free text has a real home
780
+ // in the dialog. Dropping it here is what made a typed answer look like it vanished.
781
+ if (dialog.name === "AskUserQuestion" && (await this.sendDialogFreeText(text)))
782
+ return;
783
+ this.recordLine(`--- reply does not name a ${dialog.name} option; echoing question ---`);
784
+ this.notify(`Your message was not delivered: the agent is waiting on a ${dialog.name === "ExitPlanMode" ? "plan-approval" : "question"} dialog and the reply matched no option. Reply with an option number to answer it.`);
785
+ this.emit(this.buildDialogPendingResult(dialog));
786
+ }
787
+ /**
788
+ * Answer the question with free text by driving the picker's own "Type something" entry.
789
+ *
790
+ * The entry number is READ OFF THE SCREEN rather than computed as options.length + 1:
791
+ * the CLI owns that list and appends more than one trailing entry (a free-text one and a
792
+ * chat one, verified against claude 2.1.239), so an offset derived from the tool input
793
+ * would silently drift the day the CLI adds another. Returns false when the entry is not
794
+ * on screen or the answer never registers, so the caller can still tell the sender.
795
+ */
796
+ async sendDialogFreeText(text) {
797
+ const entry = this.findFreeTextEntryNumber();
798
+ if (entry === null) {
799
+ this.recordLine("--- no free-text entry visible in the dialog ---");
800
+ return false;
801
+ }
802
+ this.recordLine(`--- answering with free text via entry ${entry} ---`);
803
+ for (let i = 1; i < entry && !this.done; i++) {
804
+ await this.transport.writeRaw?.("\u001b[B");
805
+ await delay(DIALOG_KEY_DELAY_MS);
806
+ }
807
+ await this.transport.writeRaw?.("\r");
808
+ await delay(DIALOG_KEY_DELAY_MS);
809
+ await this.transport.write(text);
810
+ await this.transport.writeRaw?.("\r");
811
+ // Same reason as sendDialogAnswer: with more than one question the picker advances
812
+ // rather than finishing, so without this the text lands, the wait below times out, and
813
+ // the sender is told the message was NOT delivered -- which invites them to send it
814
+ // twice.
815
+ await this.finishMultiQuestionDialog();
816
+ const deadline = monotonicNowMs() + DIALOG_ANSWER_CONFIRM_MS;
817
+ while (this.pendingDialog && !this.done && monotonicNowMs() < deadline) {
818
+ await delay(250);
819
+ }
820
+ if (this.pendingDialog) {
821
+ this.recordLine("--- free-text answer did not register ---");
822
+ return false;
823
+ }
824
+ this.turnStartedAt = monotonicNowMs();
825
+ this.turnInFlight = true;
826
+ this.armStallTimer();
827
+ this.startHeartbeat();
828
+ return true;
829
+ }
830
+ /**
831
+ * 1-based number of the picker's free-text entry, or null when it is not on screen.
832
+ * Whitespace is squeezed out before matching because a redrawing TUI captured through a
833
+ * PTY loses spaces inside a line ("4.Typesomething." is a real observed frame).
834
+ */
835
+ findFreeTextEntryNumber() {
836
+ const squished = this.readTerminalTail().toLowerCase().replace(/\s+/g, "");
837
+ let found = null;
838
+ for (const match of squished.matchAll(/(\d+)\.typesomething/g)) {
839
+ const value = Number(match[1]);
840
+ if (Number.isFinite(value) && value > 0)
841
+ found = value;
842
+ }
843
+ return found;
844
+ }
845
+ /**
846
+ * The dialog was driven and did not settle. Say so.
847
+ *
848
+ * This exists because of the failure it was written for: tapping an option on the app's
849
+ * question card resolved the permission, drove real keystrokes, answered the first
850
+ * question, and then stalled -- and every caller DISCARDED the false this returns, so the
851
+ * card vanished, the chip kept spinning, and nothing anywhere told the sender. The typed
852
+ * path had a result message to fall back on; the card path had nothing at all. A notice
853
+ * at the point of failure covers both callers instead of one.
854
+ */
855
+ notifyDialogDriveFailed() {
856
+ this.notify("Your answer was sent to the agent's question but the dialog did not close. " +
857
+ "It is still waiting, so try answering it again.");
858
+ }
859
+ /** Sender-facing line; never let a listener throw into the delivery loop. */
860
+ notify(text) {
861
+ try {
862
+ this.onNotice?.(text);
863
+ }
864
+ catch (err) {
865
+ this.logger.warn({ err }, "PtyQuery: notice listener threw");
866
+ }
867
+ }
868
+ /**
869
+ * Drive the dialog's own keys: Down to the option, Enter to confirm. Confirmation is the
870
+ * tool_result landing in the transcript (which clears pendingDialog) - an optimistic flag
871
+ * here would record intent as fact.
872
+ */
873
+ async sendDialogAnswer(dialog, index) {
874
+ this.recordLine(`--- answering ${dialog.name} with option ${index + 1} ---`);
875
+ for (let i = 0; i < index && !this.done; i++) {
876
+ await this.transport.writeRaw?.("\u001b[B");
877
+ await delay(DIALOG_KEY_DELAY_MS);
878
+ }
879
+ await this.transport.writeRaw?.("\r");
880
+ // Enter both answers the focused question AND advances to the next tab. With more
881
+ // than one question the dialog is therefore still up, and the tool_result cannot land
882
+ // until the whole set is submitted -- which is why a multi-question call used to time
883
+ // out here and report "answer did not register" (verified against claude 2.1.239).
884
+ await this.finishMultiQuestionDialog();
885
+ const deadline = monotonicNowMs() + DIALOG_ANSWER_CONFIRM_MS;
886
+ while (this.pendingDialog && !this.done && monotonicNowMs() < deadline) {
887
+ await delay(250);
888
+ }
889
+ if (this.pendingDialog) {
890
+ this.recordLine(`--- ${dialog.name} answer did not register ---`);
891
+ this.notifyDialogDriveFailed();
892
+ return false;
893
+ }
894
+ // Answer accepted: the turn is live again, restart the liveness clock.
895
+ this.turnStartedAt = monotonicNowMs();
896
+ this.turnInFlight = true;
897
+ this.armStallTimer();
898
+ this.startHeartbeat();
899
+ return true;
900
+ }
901
+ /**
902
+ * Walk a multi-question picker to its Submit tab and confirm.
903
+ *
904
+ * Only the first question has an answer here (a reply, or the app card's choice), so the
905
+ * rest are submitted unanswered on purpose: the CLI supports exactly that and reports it
906
+ * to the model ("Beta went unanswered"), which is strictly better than the alternative
907
+ * this replaces, where the dialog stayed up until somebody cancelled it.
908
+ *
909
+ * Every step is screen-verified rather than counted, because the number of Tabs between
910
+ * the focused question and Submit depends on how many questions are left.
911
+ */
912
+ async finishMultiQuestionDialog() {
913
+ const dialog = this.pendingDialog;
914
+ if (!dialog || dialog.optionsByQuestion.length <= 1)
915
+ return;
916
+ const unanswered = dialog.optionsByQuestion.length - 1;
917
+ this.recordLine(`--- multi-question dialog: submitting with ${unanswered} unanswered ---`);
918
+ if (await this.reachSubmitTabAndConfirm()) {
919
+ this.notify(`Submitted your answer to the first question. ${unanswered} other ` +
920
+ `${unanswered === 1 ? "question" : "questions"} in the same prompt went ` +
921
+ `unanswered - reply again to answer ${unanswered === 1 ? "it" : "them"}.`);
922
+ }
923
+ }
924
+ /**
925
+ * Tab until the picker's review tab is on screen, then confirm the pre-selected
926
+ * "1. Submit answers". Screen-verified rather than counted: how many Tabs away Submit
927
+ * is depends on how many questions are left. Returns false if it never got there, so
928
+ * the caller does not claim a submission that did not happen.
929
+ */
930
+ async reachSubmitTabAndConfirm() {
931
+ for (let attempt = 0; attempt < DIALOG_SUBMIT_MAX_TABS && !this.done; attempt++) {
932
+ await delay(DIALOG_KEY_DELAY_MS);
933
+ if (!this.pendingDialog)
934
+ return false;
935
+ if (this.onSubmitTab()) {
936
+ await this.transport.writeRaw?.("\r");
937
+ return true;
938
+ }
939
+ await this.transport.writeRaw?.("\t");
940
+ }
941
+ this.recordLine("--- never reached the Submit tab ---");
942
+ return false;
943
+ }
944
+ /** True when the picker is showing its review/Submit tab. */
945
+ onSubmitTab() {
946
+ const squished = this.readTerminalTail().toLowerCase().replace(/\s+/g, "");
947
+ return squished.includes("submitanswers") || squished.includes("readytosubmit");
948
+ }
949
+ /** The agent is waiting on a dialog; tell the sender what it asks and how to answer. */
950
+ buildDialogPendingResult(dialog, answerFailed = false) {
951
+ const base = this.buildResult("submit_failed");
952
+ const optionLines = dialog.options.length > 0
953
+ ? ` Options: ${dialog.options.map((o, i) => `${i + 1}) ${o}`).join(" ")}.`
954
+ : "";
955
+ const preamble = answerFailed
956
+ ? `The answer keystrokes did not register in the terminal, so the dialog is still up.`
957
+ : `Your message was NOT delivered because the agent is showing a ${dialog.name === "ExitPlanMode" ? "plan-approval" : "question"} dialog and is waiting for an answer.`;
958
+ return {
959
+ ...base,
960
+ subtype: "error_during_execution",
961
+ is_error: true,
962
+ errors: [
963
+ `${preamble}${optionLines} Reply with just the option number (or its exact text) ` +
964
+ `to answer it.`,
965
+ ],
966
+ };
967
+ }
968
+ buildSubmitFailedResult() {
969
+ const base = this.buildResult("submit_failed");
970
+ const excerpt = this.terminalTail.slice(-TERMINAL_TAIL_EXCERPT_CHARS).trim();
971
+ const tailLine = excerpt ? ` Last terminal output: ${JSON.stringify(excerpt)}.` : "";
972
+ return {
973
+ ...base,
974
+ subtype: "error_during_execution",
975
+ is_error: true,
976
+ errors: [
977
+ `Your message did not reach the agent: the terminal would not accept it. ` +
978
+ `Nothing was sent, so please send it again.${this.stillRunningClause()}${tailLine}`,
979
+ ],
980
+ };
981
+ }
982
+ /**
983
+ * Press Enter to submit the composed prompt, and CONFIRM the turn actually started by
984
+ * watching for the USER LINE claude appends to <sid>.jsonl the instant it accepts a turn.
985
+ * During boot the TUI intermittently swallows the first Enter (a "what's new" notice /
986
+ * MCP-auth warning steals focus), so retry until it takes.
987
+ *
988
+ * The confirmation used to be `transcriptSize() > before`, which is any growth at all.
989
+ * That is not specific to the turn starting: claude writes `attachment`, `last-prompt`
990
+ * and other bookkeeping lines on its own schedule, so a swallowed Enter that happened to
991
+ * coincide with unrelated writes read as success. Observed in production 2026-08-01: a
992
+ * session logged 157 consecutive `attachment` lines and NOT ONE `user` line, yet
993
+ * submitTurn returned on the first attempt. The prompt stayed unsent in the TUI composer,
994
+ * paseo set turnInFlight and waited on a turn that had never begun, and the agent showed
995
+ * a spinner until the liveness backstop fired ten minutes later. The retry loop below was
996
+ * always right; the thing it was checking was not.
997
+ */
998
+ /**
999
+ * Dismiss the post-interrupt modal if the terminal is showing one. ESC is only sent on a
1000
+ * recent sighting: on an idle prompt it is harmless, but on a live turn it would interrupt
1001
+ * real work, so a stale marker must never trigger it.
1002
+ */
1003
+ async clearInterruptPrompt() {
1004
+ if (!this.interruptPromptAt)
1005
+ return;
1006
+ if (this.pendingDialog) {
1007
+ // ESC here would DECLINE the pending question/plan dialog - that is where every
1008
+ // phantom "User declined to answer" came from. The dialog branch of the input loop
1009
+ // owns this state; never clear modals across it.
1010
+ return;
1011
+ }
1012
+ if (monotonicNowMs() - this.interruptPromptAt > INTERRUPT_PROMPT_TTL_MS) {
1013
+ this.interruptPromptAt = 0;
1014
+ return;
1015
+ }
1016
+ this.logger.warn({ sessionId: this.sessionId, lastLine: this.lastMeaningfulLine.slice(-120) }, "PtyQuery: terminal is showing the post-interrupt prompt, dismissing it before submitting");
1017
+ this.recordLine("--- dismissing post-interrupt prompt before submit ---");
1018
+ for (let attempt = 0; attempt < 3 && this.interruptPromptAt && !this.done; attempt++) {
1019
+ const seenAt = this.interruptPromptAt;
1020
+ await this.transport.writeRaw?.("\u001b");
1021
+ await delay(250);
1022
+ // The marker only refreshes while the modal is still drawing; if it stopped, we are out.
1023
+ if (this.interruptPromptAt === seenAt) {
1024
+ this.interruptPromptAt = 0;
1025
+ return;
1026
+ }
1027
+ }
1028
+ this.interruptPromptAt = 0;
1029
+ }
1030
+ /**
1031
+ * Answer the large-session resume dialog with "Resume full session as-is".
1032
+ *
1033
+ * Must run BEFORE anything is typed: text typed while the menu is up is eaten by it, and
1034
+ * an Enter confirms the pre-selected "Resume from summary", compacting the session the
1035
+ * user was trying to talk to. Down moves the selection off option 1; Enter confirms
1036
+ * option 2. Only acts on a recent sighting, because on any other screen a stray
1037
+ * Down+Enter is at best a wasted keystroke and at worst an answer to a different menu.
1038
+ */
1039
+ async answerResumeDialog() {
1040
+ if (!this.resumeDialogAt)
1041
+ return;
1042
+ if (monotonicNowMs() - this.resumeDialogAt > RESUME_DIALOG_TTL_MS) {
1043
+ this.resumeDialogAt = 0;
1044
+ return;
1045
+ }
1046
+ this.logger.warn({ sessionId: this.sessionId, lastLine: this.lastMeaningfulLine.slice(-120) }, "PtyQuery: large-session resume dialog is showing; selecting 'Resume full session as-is'");
1047
+ this.recordLine("--- resume dialog: selecting 'Resume full session as-is' ---");
1048
+ await this.transport.writeRaw?.("\u001b[B"); // Down: off the pre-selected compact option
1049
+ await delay(300);
1050
+ await this.transport.writeRaw?.("\r");
1051
+ await delay(500);
1052
+ this.resumeDialogAt = 0;
1053
+ }
1054
+ /**
1055
+ * Type the prompt and confirm the terminal actually received it, retrying against a
1056
+ * deaf terminal with backoff.
1057
+ *
1058
+ * Returns true once an echo (or a transcript user line) confirms the keystrokes landed;
1059
+ * false only after the terminal stayed deaf for the whole {@link READY_BUSY_MAX_WAIT_MS}
1060
+ * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
1061
+ * (zero echo), and every iteration first checks whether a user line landed anyway.
1062
+ */
1063
+ /**
1064
+ * Empty the input box before a retype. Ctrl+U kills the line the TUI is composing; a
1065
+ * pasted chip is dropped the same way. Best-effort: a transport that cannot take raw
1066
+ * keys just leaves the composer as it was.
1067
+ */
1068
+ /**
1069
+ * Is the TUI currently showing its "queued messages" footer?
1070
+ *
1071
+ * This reads a CLI STATUS LINE, not a rendering of our own text. The distinction matters
1072
+ * because this file has (rightly) burned four incidents on trusting renderings as proof
1073
+ * of DELIVERY -- bytes, then echoed text, then a raw probe, then a paste chip. Delivery
1074
+ * is still proved by one thing only, a matching `user` record. The footer answers a
1075
+ * different and much cheaper question: is a turn running right now. The CLI is the
1076
+ * authority on that, and it prints the answer.
1077
+ */
1078
+ queuedFooterVisible() {
1079
+ return QUEUED_FOOTER_RE.test(this.terminalTail.slice(-QUEUED_FOOTER_SCAN_CHARS));
1080
+ }
1081
+ /** Tell the sender their message is in line, once per message. */
1082
+ notifyQueued() {
1083
+ if (this.queuedNoticeSent)
1084
+ return;
1085
+ this.queuedNoticeSent = true;
1086
+ try {
1087
+ this.onNotice?.("Your message was accepted and is queued behind the turn this session is already " +
1088
+ "running. It will start as soon as that turn finishes.");
1089
+ }
1090
+ catch (err) {
1091
+ this.logger.debug({ err }, "PtyQuery: queued notice callback threw");
1092
+ }
1093
+ }
1094
+ async clearComposer() {
1095
+ try {
1096
+ // Ctrl+E (end of line), Ctrl+U (kill to start), Ctrl+K (kill to end).
1097
+ //
1098
+ // Ctrl+U alone was not enough. In a readline-style buffer it kills from the CURSOR
1099
+ // to the start of the line: it empties the composer only when the cursor already
1100
+ // sits at the end, and it never touches what is after the cursor. Live 2026-08-20 a
1101
+ // retype landed ON TOP of text a Ctrl+U was supposed to have killed, which is how
1102
+ // session 0b1ec622 got these into its transcript:
1103
+ //
1104
+ // mint another xpmint another xp
1105
+ // a new one with another cpf: 76797678153, caixaa new one with another cpf: ...
1106
+ //
1107
+ // This sequence is cursor-position independent and idempotent: all three keys are
1108
+ // no-ops on an already empty composer, so it cannot damage the common case. Each
1109
+ // gets its own beat, because the TUI only processes keys as fast as its input loop
1110
+ // drains them.
1111
+ //
1112
+ // It makes the clear far more likely to succeed. It does NOT prove the composer is
1113
+ // empty, and this file deliberately has no way to prove that: every signal for it is
1114
+ // a rendering, and renderings are what four earlier incidents taught it not to
1115
+ // trust. The real protection against duplication is not retyping at all once the
1116
+ // text has demonstrably left the composer -- the queued-footer guard in
1117
+ // deliverPrompt(), which removes the case behind every duplicate observed so far.
1118
+ for (const key of ["\u0005", "\u0015", "\u000b"]) {
1119
+ await this.transport.writeRaw?.(key);
1120
+ await delay(60);
1121
+ }
1122
+ await delay(120);
1123
+ }
1124
+ catch (err) {
1125
+ this.logger.debug({ err }, "PtyQuery: could not clear the composer before retyping");
1126
+ }
1127
+ }
1128
+ /**
1129
+ * Deliver a prompt and prove it, or say it failed. See docs/pty-delivery.md.
1130
+ *
1131
+ * One authority: a transcript user record matching the text. Nothing the terminal paints
1132
+ * gates this - echoed characters, paste chips, boot noise and footer hints are renderings,
1133
+ * and renderings changed with every CLI version while this code kept "discovering" new
1134
+ * ones (bytes -> text -> raw probe -> paste chip, four incidents, same defect each time).
1135
+ *
1136
+ * Retries are safe because the composer is cleared first, and a message QUEUED behind a
1137
+ * running turn is never retyped - that is the one way to send it twice.
1138
+ */
1139
+ async deliverPrompt(text) {
1140
+ this.queuedNoticeSent = false;
1141
+ const fingerprint = PtyQuery.fingerprint(text);
1142
+ const watermark = this.transcriptSize();
1143
+ const deadline = monotonicNowMs() + READY_BUSY_MAX_WAIT_MS;
1144
+ let backoff = DEAF_RETRY_INITIAL_MS;
1145
+ let attempt = 0;
1146
+ while (!this.done && monotonicNowMs() < deadline) {
1147
+ // Re-checked every attempt, not once up front: the child can die between attempts and
1148
+ // this loop's budget is ten minutes. Retrying into a corpse is what produced the
1149
+ // "no delivery receipt; retrying" storms that ended at the 20-retry cap.
1150
+ if (this.transportExited)
1151
+ return "exited";
1152
+ attempt += 1;
1153
+ const attemptStartedAt = monotonicNowMs();
1154
+ // Empty the input box first: whatever an earlier attempt may have left there is
1155
+ // undelivered by definition (no receipt), and typing on top of it is how one message
1156
+ // became nine stacked paste blocks in production.
1157
+ if (attempt > 1)
1158
+ await this.clearComposer();
1159
+ try {
1160
+ await this.transport.write(text);
1161
+ }
1162
+ catch (err) {
1163
+ // The transport now refuses a write into a dead pty. Without this catch the rejection
1164
+ // escapes deliverPrompt into the input loop's handler, which only logs "input loop
1165
+ // ended" -- so the sender would watch a spinner and never be told anything.
1166
+ if (err instanceof PtyExitedError)
1167
+ return "exited";
1168
+ throw err;
1169
+ }
1170
+ const submitted = await this.submitTurn(text, watermark, fingerprint);
1171
+ if (submitted === "rejected")
1172
+ return "rejected";
1173
+ if (submitted === "delivered") {
1174
+ if (attempt > 1) {
1175
+ this.recordLine(`--- delivered on attempt ${attempt} ---`);
1176
+ }
1177
+ return "delivered";
1178
+ }
1179
+ // No receipt. Is a turn actually RUNNING (so our text is queued behind it), or was
1180
+ // the Enter simply swallowed? Two independent signals, ORed on purpose:
1181
+ //
1182
+ // - the TUI's own queued footer, which is the CLI stating the fact outright, and
1183
+ // - real SDK messages arriving during this attempt, the original heuristic.
1184
+ //
1185
+ // File growth alone cannot tell queued from swallowed: claude writes `attachment`
1186
+ // and other bookkeeping records on its own schedule, and treating that as "queued"
1187
+ // would sit here for ten minutes while the prompt was never submitted (the
1188
+ // 2026-08-01 bug, in reverse). But it also misses the common case, a running turn
1189
+ // that is quiet for the four-second receipt window, and the cost of missing it is a
1190
+ // retype that corrupts the queued prompt. The footer settles that case directly.
1191
+ if (this.queuedFooterVisible() || this.lastTranscriptAt > attemptStartedAt) {
1192
+ this.recordLine("--- no receipt yet; queued behind a running turn ---");
1193
+ // Say so where the sender can see it. Once per message, not once per poll.
1194
+ this.notifyQueued();
1195
+ const late = await pollUntil(() => this.receiptSince(watermark, fingerprint), QUEUED_RECEIPT_WINDOW_MS, 500);
1196
+ if (late)
1197
+ return "delivered";
1198
+ // Queued, and still no receipt after the full window. Do NOT fall through to the
1199
+ // retype: the text is demonstrably out of the composer, so retyping cannot undo
1200
+ // anything and can only stack a duplicate.
1201
+ //
1202
+ // "unconfirmed", never "undelivered". They are not interchangeable here:
1203
+ // `undelivered` makes the caller emit buildSubmitFailedResult(), whose text is
1204
+ // "Nothing was sent, so please send it again" -- a lie about a message sitting in
1205
+ // claude's queue, and an instruction that produces the exact duplicate this branch
1206
+ // exists to prevent. `unconfirmed` means "accepted, start not observed", which is
1207
+ // true, and the stall backstop is already the right net for it.
1208
+ return "unconfirmed";
1209
+ }
1210
+ this.logger.warn({ sessionId: this.sessionId, attempt, nextRetryMs: backoff }, "PtyQuery: no delivery receipt for the prompt; retrying");
1211
+ this.recordLine(`--- no delivery receipt (attempt ${attempt}); retrying in ${Math.round(backoff / 1000)}s ---`);
1212
+ await delay(backoff);
1213
+ backoff = Math.min(backoff * 2, DEAF_RETRY_MAX_MS);
1214
+ }
1215
+ return "undelivered";
1216
+ }
1217
+ /**
1218
+ * Press Enter to submit the composed prompt, and CONFIRM the turn actually started by
1219
+ * watching for the USER LINE claude appends to <sid>.jsonl the instant it accepts a turn.
1220
+ * During boot the TUI intermittently swallows the first Enter (a "what's new" notice /
1221
+ * MCP-auth warning steals focus), so retry until it takes.
1222
+ *
1223
+ * The confirmation used to be `transcriptSize() > before`, which is any growth at all.
1224
+ * That is not specific to the turn starting: claude writes `attachment`, `last-prompt`
1225
+ * and other bookkeeping lines on its own schedule, so a swallowed Enter that happened to
1226
+ * coincide with unrelated writes read as success. Observed in production 2026-08-01: a
1227
+ * session logged 157 consecutive `attachment` lines and NOT ONE `user` line, yet
1228
+ * submitTurn returned on the first attempt. The prompt stayed unsent in the TUI composer,
1229
+ * paseo set turnInFlight and waited on a turn that had never begun, and the agent showed
1230
+ * a spinner until the liveness backstop fired ten minutes later. The retry loop below was
1231
+ * always right; the thing it was checking was not.
1232
+ */
1233
+ /**
1234
+ * Type the prompt and confirm the terminal actually received it, retrying against a
1235
+ * deaf terminal with backoff.
1236
+ *
1237
+ * Returns true once an echo (or a transcript user line) confirms the keystrokes landed;
1238
+ * false only after the terminal stayed deaf for the whole {@link READY_BUSY_MAX_WAIT_MS}
1239
+ * ceiling. Retyping cannot double-deliver: a deaf terminal demonstrably discards input
1240
+ * (zero echo), and every iteration first checks whether a user line landed anyway.
1241
+ */
1242
+ async submitTurn(text, watermark, fingerprint) {
1243
+ const before = watermark;
1244
+ // A slash command gets EXACTLY ONE Enter. Never retry it.
1245
+ //
1246
+ // Typing "/x" opens the TUI's own command menu, and in that state each Enter runs the
1247
+ // highlighted entry. The retry loop below was added to beat a swallowed first Enter on
1248
+ // a booting terminal, and for ordinary prompts it works; for a slash command it
1249
+ // re-executes the command once per attempt. Captured on a recording:
1250
+ //
1251
+ // /compact <- written once by paseo
1252
+ // /compact Free up context by summarizing… <- the TUI's menu opens
1253
+ // > /compact > /compact Not enough messages to compact.
1254
+ //
1255
+ // Two executions from one message. That is why sessions compacted twice, and why a
1256
+ // second /compact could land on an already-compacted session at a few thousand tokens.
1257
+ // Confirmation cannot come from the transcript here either: a slash command does not
1258
+ // append the user line that sawUserLineSince() waits for, so the loop never sees
1259
+ // success and always burns all five attempts.
1260
+ //
1261
+ // Instead, confirm from the terminal: the command was accepted if the TUI produced
1262
+ // output in response. If it produced nothing, report it rather than pressing again --
1263
+ // a lost slash command is recoverable by resending, a duplicated one is not.
1264
+ if (text.trim().startsWith("/")) {
1265
+ const bytesBefore = this.ptyBytesTotal;
1266
+ await this.transport.writeRaw?.("\r");
1267
+ const reacted = await pollUntil(() => this.receiptSince(before, fingerprint) || this.ptyBytesTotal > bytesBefore, SLASH_COMMAND_ECHO_WAIT_MS, 150);
1268
+ // The terminal responding is not the same as the command running. An unknown command
1269
+ // is answered with `Unknown command: /X. Did you mean /y?` and NOTHING starts -- but
1270
+ // that reply previously counted as acceptance, so the app showed a running turn that
1271
+ // did not exist and the sender saw dead silence. Observed live: a phone-keyboard
1272
+ // auto-capitalized "/WhatsApp-read" rejected twice while the UI said "working".
1273
+ // Checked regardless of the byte-poll outcome: the rejection can repaint before the
1274
+ // poll's baseline is captured, in which case `reacted` is false but the refusal is
1275
+ // already sitting in the tail.
1276
+ await delay(400);
1277
+ const m = this.terminalTail.slice(-500).match(/Unknown command:[^\n]{0,160}/i);
1278
+ if (m) {
1279
+ this.rejectedCommandNotice = m[0].trim();
1280
+ this.recordLine(`--- CLI rejected the command: ${this.rejectedCommandNotice} ---`);
1281
+ return "rejected";
1282
+ }
1283
+ if (!reacted) {
1284
+ this.logger.warn({ sessionId: this.sessionId, command: text.trim().split(/\s/, 1)[0] }, "PtyQuery: slash command produced no terminal response; not retrying to avoid double execution");
1285
+ }
1286
+ // A slash command may not append a user record at all (version-dependent), so the
1287
+ // CLI's own reaction stands in for the receipt here. This reads a CLI RESPONSE, not
1288
+ // a rendering, and it is never retried: a lost slash command is recoverable by
1289
+ // resending, a duplicated one is not.
1290
+ return reacted ? "delivered" : "unconfirmed";
1291
+ }
1292
+ await this.transport.writeRaw?.("\r");
1293
+ if (await pollUntil(() => this.receiptSince(before, fingerprint), RECEIPT_WINDOW_MS, 150)) {
1294
+ return "delivered";
1295
+ }
1296
+ // No receipt from this attempt. The delivery loop decides what that means: it can see
1297
+ // whether the transcript is moving (queued behind a running turn) and owns the retry.
1298
+ this.logger.debug({ sessionId: this.sessionId, lastLine: this.lastMeaningfulLine.slice(-120) }, "PtyQuery: no receipt for this submit attempt");
1299
+ return "unconfirmed";
1300
+ }
1301
+ /**
1302
+ * True once a real `user` line has been appended past `offset`. Reads only the bytes
1303
+ * added since then, so it stays cheap on a transcript that can reach tens of MB.
1304
+ * `isMeta` user lines are claude's own bookkeeping and do not mean a turn started.
1305
+ */
1306
+ /**
1307
+ * Fingerprint used to match a transcript record against what we typed. Whitespace is
1308
+ * stripped on both sides because the terminal is free to re-wrap and re-space anything
1309
+ * it renders, while the transcript keeps the text verbatim - so the character sequence
1310
+ * is the part that survives every rendering.
1311
+ */
1312
+ static fingerprint(text) {
1313
+ return text.replace(/\s+/g, "").slice(0, RECEIPT_FINGERPRINT_CHARS);
1314
+ }
1315
+ /**
1316
+ * THE delivery authority: did claude append a user record matching what we sent?
1317
+ *
1318
+ * Content-matched on purpose. The old check accepted ANY user record past the watermark,
1319
+ * which cannot tell "my message became a turn" apart from an unrelated record (a resume
1320
+ * dialog answering itself, a queued earlier message). Matching the text answers the only
1321
+ * question that matters, and answers it identically whether the terminal echoed the
1322
+ * characters, collapsed them into a paste chip, or drew nothing at all.
1323
+ */
1324
+ receiptSince(offset, fingerprint) {
1325
+ for (const record of this.readUserRecordsSince(offset)) {
1326
+ const body = PtyQuery.fingerprint(record);
1327
+ if (!fingerprint)
1328
+ return true;
1329
+ // Short messages must match whole, or a two-letter fingerprint like "ok" would
1330
+ // happily match an unrelated record that merely contains those letters.
1331
+ if (fingerprint.length < RECEIPT_EXACT_MATCH_BELOW_CHARS) {
1332
+ if (body === fingerprint)
1333
+ return true;
1334
+ continue;
1335
+ }
1336
+ if (body.includes(fingerprint))
1337
+ return true;
1338
+ }
1339
+ return false;
1340
+ }
1341
+ /** Text of every non-meta user record written past `offset`. */
1342
+ readUserRecordsSince(offset) {
1343
+ let fd = null;
1344
+ const out = [];
1345
+ try {
1346
+ const size = this.transcriptSize();
1347
+ if (size <= offset)
1348
+ return out;
1349
+ fd = fs.openSync(this.transcriptPath, "r");
1350
+ const length = size - offset;
1351
+ const buffer = Buffer.alloc(length);
1352
+ fs.readSync(fd, buffer, 0, length, offset);
1353
+ for (const line of buffer.toString("utf8").split("\n")) {
1354
+ if (!line.trim())
1355
+ continue;
1356
+ try {
1357
+ // A trailing partial line simply fails to parse; the next poll sees it whole.
1358
+ const parsed = JSON.parse(line);
1359
+ if (parsed?.type !== "user" || parsed.isMeta)
1360
+ continue;
1361
+ out.push(userRecordText(parsed.message?.content));
1362
+ }
1363
+ catch {
1364
+ /* partial or non-JSON line */
1365
+ }
1366
+ }
1367
+ return out;
1368
+ }
1369
+ catch {
1370
+ return out;
1371
+ }
1372
+ finally {
1373
+ if (fd !== null) {
1374
+ try {
1375
+ fs.closeSync(fd);
1376
+ }
1377
+ catch {
1378
+ /* ignore */
1379
+ }
1380
+ }
1381
+ }
1382
+ }
1383
+ transcriptSize() {
1384
+ try {
1385
+ return fs.statSync(this.transcriptPath).size;
1386
+ }
1387
+ catch {
1388
+ return 0;
1389
+ }
1390
+ }
1391
+ // ---- transcript → SDKMessage stream + turn-end ----------------------------
1392
+ onTranscriptMessage(message) {
1393
+ this.emit(message);
1394
+ this.trackInteractiveDialog(message);
1395
+ if (message.type === "assistant") {
1396
+ const beta = message.message;
1397
+ this.lastStopReason = beta.stop_reason ?? null;
1398
+ if (beta.usage)
1399
+ this.lastUsage = beta.usage;
1400
+ }
1401
+ // Any transcript activity proves the turn is alive; push the stall deadline out.
1402
+ this.lastTranscriptAt = monotonicNowMs();
1403
+ // Real turn activity means the modal is gone, whatever the tail still remembers.
1404
+ this.interruptPromptAt = 0;
1405
+ this.armStallTimer();
1406
+ this.scheduleTurnEndCheck();
1407
+ }
1408
+ /**
1409
+ * Track interactive TUI dialogs (AskUserQuestion / plan-mode approval) from the
1410
+ * transcript. While one is pending the terminal is WAITING, not stalled: the stall
1411
+ * backstop must stand down, no ESC may be sent (it declines the dialog - the source of
1412
+ * every phantom "User declined" this week), and an incoming reply is translated to
1413
+ * dialog keystrokes instead of typed text.
1414
+ */
1415
+ trackInteractiveDialog(message) {
1416
+ const content = message.message?.content;
1417
+ if (!Array.isArray(content))
1418
+ return;
1419
+ for (const block of content) {
1420
+ if (!block || typeof block !== "object")
1421
+ continue;
1422
+ const b = block;
1423
+ if (message.type === "assistant" &&
1424
+ b.type === "tool_use" &&
1425
+ b.id &&
1426
+ (b.name === "AskUserQuestion" || b.name === "ExitPlanMode")) {
1427
+ this.pendingDialog = {
1428
+ toolUseId: b.id,
1429
+ name: b.name,
1430
+ input: isRecord(b.input) ? b.input : {},
1431
+ options: extractDialogOptions(b.name, b.input),
1432
+ optionsByQuestion: extractDialogOptionsByQuestion(b.name, b.input),
1433
+ };
1434
+ this.recordLine(`--- interactive dialog pending: ${b.name} ---`);
1435
+ this.notifyDialogChange();
1436
+ }
1437
+ const pending = this.pendingDialog;
1438
+ if (pending && b.type === "tool_result" && b.tool_use_id === pending.toolUseId) {
1439
+ this.recordLine(`--- interactive dialog answered: ${pending.name} ---`);
1440
+ this.pendingDialog = null;
1441
+ this.notifyDialogChange();
1442
+ }
1443
+ }
1444
+ }
1445
+ scheduleTurnEndCheck() {
1446
+ if (this.turnEndTimer)
1447
+ clearTimeout(this.turnEndTimer);
1448
+ this.turnEndTimer = setTimeout(() => this.maybeCompleteTurn(), TURN_END_IDLE_MS);
1449
+ }
1450
+ maybeCompleteTurn() {
1451
+ if (this.done)
1452
+ return;
1453
+ const stop = this.lastStopReason;
1454
+ if (!stop || !TERMINAL_STOP_REASONS.has(stop))
1455
+ return; // e.g. "tool_use" → more coming
1456
+ this.numTurns += 1;
1457
+ // The turn ended on its own; stand the liveness backstop down before emitting.
1458
+ this.turnInFlight = false;
1459
+ this.clearStallTimer();
1460
+ this.stopHeartbeat();
1461
+ this.emit(this.buildResult(stop));
1462
+ // reset so a stale terminal reason can't re-fire before the next assistant turn
1463
+ this.lastStopReason = null;
1464
+ }
1465
+ /**
1466
+ * (Re)arm the liveness backstop. Called when a turn starts and on every transcript
1467
+ * message, so the deadline only expires after an unbroken window of silence.
1468
+ */
1469
+ armStallTimer() {
1470
+ if (this.stallTimeoutMs <= 0 || !this.turnInFlight)
1471
+ return;
1472
+ this.clearStallTimer();
1473
+ this.stallTimer = setTimeout(() => this.onTurnStalled(), this.stallTimeoutMs);
1474
+ // Never hold the event loop open just to wait for a stall.
1475
+ this.stallTimer.unref?.();
1476
+ }
1477
+ clearStallTimer() {
1478
+ if (this.stallTimer)
1479
+ clearTimeout(this.stallTimer);
1480
+ this.stallTimer = null;
1481
+ }
1482
+ /**
1483
+ * No transcript activity for the whole budget while a turn was outstanding. Surface it as
1484
+ * a failed turn instead of leaving the caller on an endless spinner. We do NOT kill the
1485
+ * PTY: claude may still be retrying, and if it recovers its messages keep flowing through
1486
+ * the reader as a fresh turn.
1487
+ */
1488
+ onTurnStalled() {
1489
+ this.stallTimer = null;
1490
+ if (this.done || !this.turnInFlight)
1491
+ return;
1492
+ if (this.pendingDialog) {
1493
+ // Waiting on a human is not a stall. Observed live 2026-08-04: an ExitPlanMode
1494
+ // approval sat pending for 2.5 hours and the backstop declared the turn failed,
1495
+ // which read as a wedge and got a healthy process killed.
1496
+ this.recordLine(`--- stall budget elapsed but ${this.pendingDialog.name} is pending ---`);
1497
+ this.armStallTimer();
1498
+ return;
1499
+ }
1500
+ this.turnInFlight = false;
1501
+ this.stopHeartbeat();
1502
+ const silentForMs = this.stallTimeoutMs;
1503
+ const terminalTail = this.readTerminalTail();
1504
+ this.recordLine(`--- STALLED after ${silentForMs}ms of transcript silence ---`);
1505
+ this.logger.error({
1506
+ sessionId: this.sessionId,
1507
+ transcriptPath: this.transcriptPath,
1508
+ silentForMs,
1509
+ // The whole point of the tap: whatever the TUI was showing while it went quiet.
1510
+ terminalTail,
1511
+ }, "PtyQuery: turn stalled, no transcript activity within the liveness budget");
1512
+ this.numTurns += 1;
1513
+ this.emit(this.buildStalledResult(silentForMs, terminalTail));
1514
+ }
1515
+ /**
1516
+ * Append terminal output to the rolling tail.
1517
+ *
1518
+ * Scrubbed and filtered on the way IN, not on the way out, because the TUI's spinner
1519
+ * redraws at a few hundred bytes/second: a raw buffer holds only a second or two of
1520
+ * history and would evict the one line worth reading. Keeping only lines that carry
1521
+ * letters or digits means a message like "Compacting conversation…" survives minutes of
1522
+ * animation. Verified against a real stall: the raw tail was 163 chars of pure spinner.
1523
+ */
1524
+ /**
1525
+ * True when the rolling tail's last frame looks like an input box waiting for typing.
1526
+ *
1527
+ * Deliberately requires the busy marker to be ABSENT rather than trusting the prompt
1528
+ * markers alone: the working footer renders as `… · esc to interrupt · ← for agents`, so
1529
+ * "for agents" on its own is present in both states and would read a mid-turn terminal
1530
+ * as idle.
1531
+ */
1532
+ /** Runs `fn` after every previously queued terminal write has finished. */
1533
+ runExclusive(fn) {
1534
+ const next = this.writeGate.then(fn, fn);
1535
+ // Keep the chain alive even when a link rejects; each caller still sees its own error.
1536
+ this.writeGate = next.catch(() => undefined);
1537
+ return next;
1538
+ }
1539
+ /**
1540
+ * Type a control slash command (`/model`, `/effort`) into the terminal, but only once the
1541
+ * TUI is actually accepting input, and never while another write is in flight.
1542
+ *
1543
+ * Why this exists instead of `transport.write()` straight from the agent: a control command
1544
+ * written to the transport bypasses the input loop, so it races the prompt being delivered
1545
+ * and the two COLLIDE on the same input line. Captured live 2026-08-29 (session c132c833)
1546
+ * from claude's own transcript:
1547
+ *
1548
+ * command-args>claude-opus-5[1m]Responda APENAS: R1<
1549
+ *
1550
+ * The model id and the user's prompt, concatenated with no separator. Measured
1551
+ * consequences: the model never changed (the old one kept answering two minutes later),
1552
+ * the user's prompt was swallowed by the slash command, and the agent never returned to
1553
+ * idle. Waiting for the input box also keeps the command out of claude's PERSISTED input
1554
+ * queue, so it cannot be replayed into a future session.
1555
+ *
1556
+ * Returns false when the terminal never became typeable, so the caller can refuse to
1557
+ * record a setting change that did not happen.
1558
+ */
1559
+ async sendControlCommand(command) {
1560
+ return await this.runExclusive(async () => {
1561
+ const deadline = monotonicNowMs() + CONTROL_COMMAND_MAX_WAIT_MS;
1562
+ while (!this.done && monotonicNowMs() < deadline) {
1563
+ if (this.looksIdle()) {
1564
+ // Text first, Enter SEPARATELY -- never `write(`${command}\r`)` as one chunk.
1565
+ //
1566
+ // Typing "/" opens the TUI's own command menu, and a carriage return arriving in the
1567
+ // same chunk as the text is taken as a newline inside the composer rather than a
1568
+ // submit. The command then just sits there, and the next thing typed is appended to
1569
+ // it. Captured live 2026-08-29 14:00:47 on a daemon that already had the write gate:
1570
+ // paseo wrote a clean `/effort low\r` (provenance `length: 12`) and the CLI still
1571
+ // received
1572
+ //
1573
+ // command-args>low\nThis is me changing effort to low<
1574
+ //
1575
+ // the level and the user's next message as one argument. submitTurn() has always
1576
+ // sent Enter as its own writeRaw for exactly this reason; this path did not.
1577
+ const bytesBefore = this.ptyBytesTotal;
1578
+ await this.transport.write(command);
1579
+ await this.transport.writeRaw?.("\r");
1580
+ // Exactly one Enter, never retried: a lost control command is recoverable by
1581
+ // resending, a duplicated one runs the command twice (see submitTurn).
1582
+ const reacted = await pollUntil(() => this.ptyBytesTotal > bytesBefore, SLASH_COMMAND_ECHO_WAIT_MS, 150);
1583
+ if (!reacted) {
1584
+ this.recordLine(`--- "${command}" produced no terminal response ---`);
1585
+ this.logger.warn({ sessionId: this.sessionId, command }, "PtyQuery: control command produced no terminal response; not retrying");
1586
+ return false;
1587
+ }
1588
+ return true;
1589
+ }
1590
+ await delay(CONTROL_COMMAND_POLL_MS);
1591
+ }
1592
+ if (this.done)
1593
+ return false;
1594
+ this.recordLine(`--- terminal never accepted input; "${command}" not sent ---`);
1595
+ this.logger.warn({ sessionId: this.sessionId, command, waitedMs: CONTROL_COMMAND_MAX_WAIT_MS }, "PtyQuery: terminal never returned to an input box, refusing to type a control command");
1596
+ return false;
1597
+ });
1598
+ }
1599
+ looksIdle() {
1600
+ const tail = this.terminalTail.slice(-400).toLowerCase();
1601
+ if (!tail)
1602
+ return false;
1603
+ if (tail.includes(BUSY_MARKER))
1604
+ return false;
1605
+ if (this.lastBusyAt > 0 && monotonicNowMs() - this.lastBusyAt < READY_IDLE_STABLE_MS)
1606
+ return false;
1607
+ return IDLE_PROMPT_MARKERS.some((m) => tail.includes(m));
1608
+ }
1609
+ appendTerminalTail(chunk) {
1610
+ if (!chunk)
1611
+ return;
1612
+ this.ptyBytesTotal += chunk.length;
1613
+ const text = stripTerminalControl(chunk);
1614
+ if (!text)
1615
+ return;
1616
+ if (this.echoProbe) {
1617
+ this.echoProbe.buf = (this.echoProbe.buf + text.replace(/\s+/g, "")).slice(-2000);
1618
+ }
1619
+ // Read the busy marker off the raw stripped text, NOT the filtered tail below:
1620
+ // isProgressNoise() drops a line that is only the footer, which is exactly the frame
1621
+ // that proves a turn is running.
1622
+ if (text.toLowerCase().includes(BUSY_MARKER))
1623
+ this.lastBusyAt = monotonicNowMs();
1624
+ for (const rawLine of text.split("\n")) {
1625
+ const line = rawLine.trim();
1626
+ if (!line || !/[a-z0-9]/i.test(line))
1627
+ continue;
1628
+ if (isProgressNoise(line))
1629
+ continue;
1630
+ // de-dupe consecutive identical redraws of the same line
1631
+ if (this.terminalTail.endsWith(line + "\n"))
1632
+ continue;
1633
+ this.terminalTail += line + "\n";
1634
+ this.lastMeaningfulLine = line;
1635
+ if (line.toLowerCase().includes(INTERRUPT_PROMPT_MARKER)) {
1636
+ this.interruptPromptAt = monotonicNowMs();
1637
+ }
1638
+ if (line.toLowerCase().includes(RESUME_DIALOG_MARKER)) {
1639
+ this.resumeDialogAt = monotonicNowMs();
1640
+ }
1641
+ this.recordLine(line);
1642
+ }
1643
+ if (this.terminalTail.length > TERMINAL_TAIL_MAX_CHARS) {
1644
+ this.terminalTail = this.terminalTail.slice(-TERMINAL_TAIL_MAX_CHARS);
1645
+ }
1646
+ }
1647
+ // ---- telemetry ------------------------------------------------------------
1648
+ /** Open `$PASEO_HOME/pty-recordings/<sessionId>.log`. Best-effort: never fail a session. */
1649
+ openRecording() {
1650
+ try {
1651
+ const home = process.env.PASEO_HOME;
1652
+ if (!home)
1653
+ return;
1654
+ const dir = path.join(home, "pty-recordings");
1655
+ fs.mkdirSync(dir, { recursive: true });
1656
+ this.recordingPath = path.join(dir, `${this.sessionId}.log`);
1657
+ this.recordingBytes = this.safeSize(this.recordingPath);
1658
+ this.recordLine(`--- PtyQuery attached, transcript=${this.transcriptPath} ---`);
1659
+ this.logger.debug({ sessionId: this.sessionId, recordingPath: this.recordingPath }, "PtyQuery: recording terminal output");
1660
+ }
1661
+ catch {
1662
+ this.recordingPath = null;
1663
+ }
1664
+ }
1665
+ recordLine(line) {
1666
+ if (!this.recordingPath)
1667
+ return;
1668
+ try {
1669
+ const entry = `${new Date().toISOString()} ${line}\n`;
1670
+ fs.appendFileSync(this.recordingPath, entry);
1671
+ this.recordingBytes += entry.length;
1672
+ if (this.recordingBytes > RECORDING_MAX_BYTES) {
1673
+ // Keep the recent past, drop the rest. Diagnostics, not an audit trail.
1674
+ const kept = fs.readFileSync(this.recordingPath).subarray(-RECORDING_KEEP_BYTES);
1675
+ fs.writeFileSync(this.recordingPath, kept);
1676
+ this.recordingBytes = kept.length;
1677
+ }
1678
+ }
1679
+ catch {
1680
+ this.recordingPath = null;
1681
+ }
1682
+ }
1683
+ safeSize(p) {
1684
+ try {
1685
+ return fs.statSync(p).size;
1686
+ }
1687
+ catch {
1688
+ return 0;
1689
+ }
1690
+ }
1691
+ /**
1692
+ * While a turn is outstanding, emit one line every {@link LIVENESS_HEARTBEAT_MS} with
1693
+ * enough state to tell the failure modes apart without waiting for the stall budget:
1694
+ * terminal bytes since the last beat (0 means the TUI is frozen, not thinking), seconds
1695
+ * since the transcript last grew, and the last meaningful line on screen.
1696
+ */
1697
+ startHeartbeat() {
1698
+ this.stopHeartbeat();
1699
+ this.ptyBytesAtLastBeat = this.ptyBytesTotal;
1700
+ this.heartbeatTimer = setInterval(() => this.beat(), LIVENESS_HEARTBEAT_MS);
1701
+ this.heartbeatTimer.unref?.();
1702
+ }
1703
+ stopHeartbeat() {
1704
+ if (this.heartbeatTimer)
1705
+ clearInterval(this.heartbeatTimer);
1706
+ this.heartbeatTimer = null;
1707
+ }
1708
+ beat() {
1709
+ if (this.done || !this.turnInFlight) {
1710
+ this.stopHeartbeat();
1711
+ return;
1712
+ }
1713
+ const ptyBytesSinceLastBeat = this.ptyBytesTotal - this.ptyBytesAtLastBeat;
1714
+ this.ptyBytesAtLastBeat = this.ptyBytesTotal;
1715
+ this.logger.info({
1716
+ sessionId: this.sessionId,
1717
+ event: "pty.turn.heartbeat",
1718
+ turnAgeMs: this.turnStartedAt ? monotonicNowMs() - this.turnStartedAt : 0,
1719
+ ptyBytesSinceLastBeat,
1720
+ secondsSinceTranscript: this.lastTranscriptAt
1721
+ ? Math.round((monotonicNowMs() - this.lastTranscriptAt) / 1000)
1722
+ : null,
1723
+ readySettledBy: this.readySettledBy,
1724
+ lastLine: this.lastMeaningfulLine.slice(-160),
1725
+ }, "PtyQuery: turn in flight");
1726
+ }
1727
+ /**
1728
+ * The tail as readable text: ANSI/OSC sequences dropped and runs of whitespace collapsed,
1729
+ * because a raw TUI capture is mostly cursor movement and redraw padding.
1730
+ */
1731
+ readTerminalTail() {
1732
+ return this.terminalTail.trim().slice(-TERMINAL_TAIL_MAX_CHARS);
1733
+ }
1734
+ buildResult(stopReason) {
1735
+ const usage = (this.lastUsage ?? {
1736
+ input_tokens: 0,
1737
+ output_tokens: 0,
1738
+ cache_creation_input_tokens: 0,
1739
+ cache_read_input_tokens: 0,
1740
+ });
1741
+ const result = {
1742
+ type: "result",
1743
+ subtype: "success",
1744
+ duration_ms: this.turnStartedAt ? Math.round(monotonicNowMs() - this.turnStartedAt) : 0,
1745
+ duration_api_ms: 0,
1746
+ is_error: false,
1747
+ num_turns: this.numTurns,
1748
+ result: "",
1749
+ stop_reason: stopReason,
1750
+ total_cost_usd: 0,
1751
+ usage,
1752
+ modelUsage: {},
1753
+ permission_denials: [],
1754
+ uuid: randomUUID(),
1755
+ session_id: this.sessionId,
1756
+ };
1757
+ return result;
1758
+ }
1759
+ /**
1760
+ * The agent process exited. Fail any in-flight turn immediately and end the query.
1761
+ *
1762
+ * Unlike a stall there is nothing to wait for: no retry, no recovery, no later transcript
1763
+ * activity. Emitting here rather than letting the stall timer fire turns a ten-minute wait
1764
+ * plus a false "still running" into an immediate, accurate failure, and finishing the query
1765
+ * stops the layer above treating this transport as a live destination for prompts.
1766
+ */
1767
+ onTransportExit(code, signal) {
1768
+ // Recorded before the `done` bail: the wording helpers and the delivery guards must know
1769
+ // the process is gone even when the query had already been torn down.
1770
+ this.transportExited ?? (this.transportExited = { code, signal });
1771
+ if (this.done)
1772
+ return;
1773
+ const wasInFlight = this.turnInFlight;
1774
+ this.turnInFlight = false;
1775
+ this.clearStallTimer();
1776
+ this.logger.error({ sessionId: this.sessionId, exitCode: code, exitSignal: signal, wasInFlight }, "PtyQuery: the agent process exited");
1777
+ this.recordLine(`--- agent process exited (status ${code ?? "none"}, signal ${signal ?? "none"}) ---`);
1778
+ if (wasInFlight) {
1779
+ this.numTurns += 1;
1780
+ this.emit(this.buildExitedResult(code, signal));
1781
+ }
1782
+ this.finish();
1783
+ }
1784
+ /**
1785
+ * A non-success `result` for an agent that exited.
1786
+ *
1787
+ * Wording constraints match {@link buildStalledResult}: `isAbortError()` drops results
1788
+ * matching /\baborted\b/i and `buildTurnFailedEvent()` scrapes /\bcode\s+(\d+)\b/i for an
1789
+ * exit code, so this says "status", never "code", and never says "aborted". It also must
1790
+ * NOT claim the session is still running, which is the exact lie this change deletes.
1791
+ */
1792
+ buildExitedResult(code, signal) {
1793
+ const base = this.buildResult("process_exited");
1794
+ const how = signal ? `on signal ${signal}` : `with status ${code ?? "unknown"}`;
1795
+ const excerpt = this.terminalTail.slice(-TERMINAL_TAIL_EXCERPT_CHARS).trim();
1796
+ const tailLine = excerpt ? ` Last terminal output: ${JSON.stringify(excerpt)}.` : "";
1797
+ return {
1798
+ ...base,
1799
+ subtype: "error_during_execution",
1800
+ is_error: true,
1801
+ errors: [
1802
+ `The agent process exited ${how} before finishing this turn, so the session is NOT ` +
1803
+ `running any more and will not recover on its own. The conversation is still on ` +
1804
+ `disk: recover it with "paseo import ${this.sessionId}", then restore the agent's ` +
1805
+ `model, thinking budget and permission mode, which import does not carry over.` +
1806
+ tailLine,
1807
+ ],
1808
+ };
1809
+ }
1810
+ buildStalledResult(silentForMs, terminalTail) {
1811
+ // A dead process is not a stall, and must never be described as one that "may recover".
1812
+ if (this.transportExited) {
1813
+ return this.buildExitedResult(this.transportExited.code, this.transportExited.signal);
1814
+ }
1815
+ const minutes = Math.round(silentForMs / 60000);
1816
+ const base = this.buildResult("stall_timeout");
1817
+ // The last thing the terminal showed is usually the whole diagnosis ("Compacting
1818
+ // conversation…", a retry notice), so lead the message with it when we have one.
1819
+ const excerpt = (terminalTail ?? "").slice(-TERMINAL_TAIL_EXCERPT_CHARS).trim();
1820
+ const tailLine = excerpt ? ` Last terminal output: ${JSON.stringify(excerpt)}.` : "";
1821
+ return {
1822
+ ...base,
1823
+ subtype: "error_during_execution",
1824
+ is_error: true,
1825
+ errors: [
1826
+ `No response from the claude PTY session for ${minutes} min. ` +
1827
+ `The turn was accepted but produced no output, so it was marked failed. ` +
1828
+ `The underlying session is still running and may recover.${tailLine}`,
1829
+ ],
1830
+ };
1831
+ }
1832
+ // ---- control surface ------------------------------------------------------
1833
+ async interrupt() {
1834
+ // ESC interrupts the active turn in the claude TUI.
1835
+ await this.transport.writeRaw?.("\x1b");
1836
+ }
1837
+ // These are driven by the `instanceof PtyTransport` branches in agent.ts (which write
1838
+ // to the transport directly), so on the query they are inert.
1839
+ async setPermissionMode() { }
1840
+ async setModel() { }
1841
+ async setMaxThinkingTokens() { }
1842
+ async applyFlagSettings() { }
1843
+ async supportedModels() {
1844
+ return [];
1845
+ }
1846
+ async supportedCommands() {
1847
+ return [];
1848
+ }
1849
+ async supportedAgents() {
1850
+ return [];
1851
+ }
1852
+ async mcpServerStatus() {
1853
+ return [];
1854
+ }
1855
+ close() {
1856
+ this.finish();
1857
+ void this.transport.kill().catch(() => { });
1858
+ }
1859
+ }
1860
+ /** Extract the plain-text prompt from a user SDK message for typing into the PTY. */
1861
+ /** Delay between Down-arrow presses when answering a dialog; the TUI animates selection. */
1862
+ const DIALOG_KEY_DELAY_MS = 150;
1863
+ /** How long to wait for the dialog's tool_result to land after answering keystrokes. */
1864
+ const DIALOG_ANSWER_CONFIRM_MS = 8000;
1865
+ /**
1866
+ * Options the TUI dialog offers, in on-screen order. AskUserQuestion carries them in its
1867
+ * input; the plan-approval dialog's options are the CLI's own and version-dependent, so
1868
+ * they stay empty and replies are matched by number only.
1869
+ */
1870
+ function extractDialogOptionsByQuestion(name, input) {
1871
+ if (name !== "AskUserQuestion")
1872
+ return [];
1873
+ const questions = input?.questions;
1874
+ if (!Array.isArray(questions))
1875
+ return [];
1876
+ return questions.map((question) => {
1877
+ const options = question?.options;
1878
+ if (!Array.isArray(options))
1879
+ return [];
1880
+ const labels = [];
1881
+ for (const option of options) {
1882
+ if (typeof option === "string")
1883
+ labels.push(option);
1884
+ else if (option && typeof option === "object") {
1885
+ const label = option.label;
1886
+ if (typeof label === "string")
1887
+ labels.push(label);
1888
+ }
1889
+ }
1890
+ return labels;
1891
+ });
1892
+ }
1893
+ function extractDialogOptions(name, input) {
1894
+ if (name !== "AskUserQuestion")
1895
+ return [];
1896
+ const questions = input?.questions;
1897
+ if (!Array.isArray(questions) || questions.length === 0)
1898
+ return [];
1899
+ const options = questions[0]?.options;
1900
+ if (!Array.isArray(options))
1901
+ return [];
1902
+ const labels = [];
1903
+ for (const option of options) {
1904
+ if (typeof option === "string")
1905
+ labels.push(option);
1906
+ else if (option && typeof option === "object") {
1907
+ const label = option.label;
1908
+ if (typeof label === "string")
1909
+ labels.push(label);
1910
+ }
1911
+ }
1912
+ return labels;
1913
+ }
1914
+ /**
1915
+ * Resolve the user's reply to a 0-based dialog option index, or null when it names none.
1916
+ * "2" picks the second option; an exact case-insensitive label match works too. Free text
1917
+ * stays null on purpose: guessing would answer a question the user did not answer.
1918
+ */
1919
+ function normalizeOptionText(value) {
1920
+ return value
1921
+ .trim()
1922
+ .toLowerCase()
1923
+ .replace(/[\u2018\u2019\u201c\u201d]/g, "'")
1924
+ .replace(/[^a-z0-9]+/g, " ")
1925
+ .trim();
1926
+ }
1927
+ /** The single index every candidate list agrees on, or null when it is 0 or 2+. */
1928
+ function soleIndex(indices) {
1929
+ return indices.length === 1 ? (indices[0] ?? null) : null;
1930
+ }
1931
+ function matchDialogOption(text, options) {
1932
+ const reply = text.trim();
1933
+ if (/^[1-9]$/.test(reply)) {
1934
+ const index = Number(reply) - 1;
1935
+ if (options.length === 0 || index < options.length)
1936
+ return index;
1937
+ return null;
1938
+ }
1939
+ const lowered = reply.toLowerCase();
1940
+ const byLabel = options.findIndex((o) => o.trim().toLowerCase() === lowered);
1941
+ if (byLabel >= 0)
1942
+ return byLabel;
1943
+ // Exact-label-only matching sounds safe and is not: the app's own suggestion chip sent
1944
+ // "Yes" against an option labelled "Yes, merge to master", scored no match, and the
1945
+ // message was discarded (2026-08-21, question unanswered for 1h38m). Widen to
1946
+ // punctuation-insensitive equality, then to a UNIQUE prefix, then to a UNIQUE substring.
1947
+ // Uniqueness is the whole safety property: anything matching two options stays null,
1948
+ // because guessing answers a question the user did not answer.
1949
+ const normalizedReply = normalizeOptionText(reply);
1950
+ if (normalizedReply.length === 0)
1951
+ return null;
1952
+ const normalized = options.map(normalizeOptionText);
1953
+ const exact = normalized.flatMap((o, i) => (o === normalizedReply ? [i] : []));
1954
+ const byExact = soleIndex(exact);
1955
+ if (byExact !== null)
1956
+ return byExact;
1957
+ const prefix = normalized.flatMap((o, i) => (o.startsWith(normalizedReply) ? [i] : []));
1958
+ const byPrefix = soleIndex(prefix);
1959
+ if (byPrefix !== null)
1960
+ return byPrefix;
1961
+ // Only worth a substring pass when the reply is long enough to be a real quotation of
1962
+ // the label; a two-character reply substring-matching one option is a coincidence.
1963
+ if (normalizedReply.length < 4)
1964
+ return null;
1965
+ const contains = normalized.flatMap((o, i) => (o.includes(normalizedReply) ? [i] : []));
1966
+ return soleIndex(contains);
1967
+ }
1968
+ /** Flatten a transcript user record's content to plain text for fingerprint matching. */
1969
+ function userRecordText(content) {
1970
+ if (typeof content === "string")
1971
+ return content;
1972
+ if (!Array.isArray(content))
1973
+ return "";
1974
+ const parts = [];
1975
+ for (const block of content) {
1976
+ if (typeof block === "string")
1977
+ parts.push(block);
1978
+ else if (isRecord(block) && typeof block.text === "string")
1979
+ parts.push(block.text);
1980
+ }
1981
+ return parts.join(" ");
1982
+ }
1983
+ function isRecord(value) {
1984
+ return !!value && typeof value === "object" && !Array.isArray(value);
1985
+ }
1986
+ /** Extensions for the media types the app can send; anything unknown stages as .png. */
1987
+ const IMAGE_MEDIA_TYPE_EXT = {
1988
+ "image/jpeg": "jpg",
1989
+ "image/png": "png",
1990
+ "image/gif": "gif",
1991
+ "image/webp": "webp",
1992
+ };
1993
+ function extractUserText(message) {
1994
+ const content = message.message?.content;
1995
+ if (typeof content === "string")
1996
+ return content;
1997
+ if (Array.isArray(content)) {
1998
+ return content
1999
+ .filter((b) => !!b && typeof b === "object" && b.type === "text")
2000
+ .map((b) => b.text)
2001
+ .join("");
2002
+ }
2003
+ return "";
2004
+ }
2005
+ /**
2006
+ * Drop ANSI/OSC sequences and remaining control chars. Scanned by code point rather than a
2007
+ * regex so the source carries no control characters and avoids no-control-regex (same
2008
+ * approach as sanitizeUploadFilename).
2009
+ */
2010
+ function stripTerminalControl(src) {
2011
+ const ESC = 0x1b;
2012
+ const BEL = 0x07;
2013
+ const out = [];
2014
+ for (let i = 0; i < src.length; i++) {
2015
+ const code = src.charCodeAt(i);
2016
+ if (code === ESC) {
2017
+ const next = src[i + 1];
2018
+ if (next === "[") {
2019
+ i += 2;
2020
+ while (i < src.length && !isCsiFinal(src.charCodeAt(i)))
2021
+ i++;
2022
+ }
2023
+ else if (next === "]") {
2024
+ i += 2;
2025
+ while (i < src.length && src.charCodeAt(i) !== BEL && src.charCodeAt(i) !== ESC)
2026
+ i++;
2027
+ if (i < src.length && src.charCodeAt(i) === ESC)
2028
+ i++;
2029
+ }
2030
+ else {
2031
+ i += 1;
2032
+ }
2033
+ continue;
2034
+ }
2035
+ if (code === 0x0a) {
2036
+ out.push("\n");
2037
+ continue;
2038
+ }
2039
+ if (code === 0x09) {
2040
+ out.push(" ");
2041
+ continue;
2042
+ }
2043
+ if (code <= 0x1f || code === 0x7f)
2044
+ continue;
2045
+ out.push(src[i]);
2046
+ }
2047
+ return out.join("").replace(/[ ]{2,}/g, " ");
2048
+ }
2049
+ /**
2050
+ * True for the TUI's own progress chrome: spinner frames, the elapsed-time counter and
2051
+ * token tickers. These redraw constantly and carry no diagnostic value, and keeping them
2052
+ * would evict the lines that do.
2053
+ */
2054
+ function isProgressNoise(line) {
2055
+ if (/^[^a-z]*\d+m?\s*\d*s\)?[^a-z]*$/i.test(line))
2056
+ return true;
2057
+ if (/^\W*(esc to interrupt|ctrl\+[a-z])\W*$/i.test(line))
2058
+ return true;
2059
+ const letters = (line.match(/[a-z]/gi) ?? []).length;
2060
+ return letters < 3;
2061
+ }
2062
+ function isCsiFinal(code) {
2063
+ return code >= 0x40 && code <= 0x7e;
2064
+ }
2065
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2066
+ async function pollUntil(cond, timeoutMs, intervalMs) {
2067
+ const deadline = monotonicNowMs() + timeoutMs;
2068
+ while (monotonicNowMs() < deadline) {
2069
+ if (cond())
2070
+ return true;
2071
+ await delay(intervalMs);
2072
+ }
2073
+ return cond();
2074
+ }
2075
+ //# sourceMappingURL=pty-query.js.map