@bubblebrain-ai/bubble 0.0.63 → 0.0.65

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 (912) hide show
  1. package/README.md +437 -11
  2. package/dist/agent/abort-errors.d.ts +18 -0
  3. package/dist/agent/abort-errors.js +51 -0
  4. package/dist/agent/budget-ledger.d.ts +20 -0
  5. package/dist/agent/budget-ledger.js +45 -0
  6. package/dist/agent/categories.d.ts +100 -0
  7. package/dist/agent/categories.js +203 -0
  8. package/dist/agent/change-tracker.d.ts +40 -0
  9. package/dist/agent/change-tracker.js +89 -0
  10. package/dist/agent/child-runner.d.ts +64 -0
  11. package/dist/agent/child-runner.js +405 -0
  12. package/dist/agent/input-controller.d.ts +14 -0
  13. package/dist/agent/input-controller.js +43 -0
  14. package/dist/agent/internal-reminder-sanitizer.d.ts +25 -0
  15. package/dist/agent/internal-reminder-sanitizer.js +297 -0
  16. package/dist/agent/profiles.d.ts +76 -0
  17. package/dist/agent/profiles.js +526 -0
  18. package/dist/agent/provider-fingerprint.d.ts +6 -0
  19. package/dist/agent/provider-fingerprint.js +89 -0
  20. package/dist/agent/resident-history.d.ts +7 -0
  21. package/dist/agent/resident-history.js +61 -0
  22. package/dist/agent/result-integrator.d.ts +22 -0
  23. package/dist/agent/result-integrator.js +50 -0
  24. package/dist/agent/routing-catalog.d.ts +131 -0
  25. package/dist/agent/routing-catalog.js +260 -0
  26. package/dist/agent/structured-output.d.ts +37 -0
  27. package/dist/agent/structured-output.js +193 -0
  28. package/dist/agent/subagent/router.d.ts +65 -0
  29. package/dist/agent/subagent/router.js +203 -0
  30. package/dist/agent/subagent/runtime.d.ts +235 -0
  31. package/dist/agent/subagent/runtime.js +835 -0
  32. package/dist/agent/subagent-control.d.ts +100 -0
  33. package/dist/agent/subagent-control.js +70 -0
  34. package/dist/agent/subagent-lifecycle-reminder.d.ts +3 -0
  35. package/dist/agent/subagent-lifecycle-reminder.js +112 -0
  36. package/dist/agent/subagent-route-format.d.ts +8 -0
  37. package/dist/agent/subagent-route-format.js +18 -0
  38. package/dist/agent/subagent-scheduler.d.ts +105 -0
  39. package/dist/agent/subagent-scheduler.js +287 -0
  40. package/dist/agent/subagent-store.d.ts +57 -0
  41. package/dist/agent/subagent-store.js +180 -0
  42. package/dist/agent/subagent-summary.d.ts +30 -0
  43. package/dist/agent/subagent-summary.js +74 -0
  44. package/dist/agent/subagent-wait-guidance.d.ts +2 -0
  45. package/dist/agent/subagent-wait-guidance.js +8 -0
  46. package/dist/agent/subtask-policy.d.ts +13 -0
  47. package/dist/agent/subtask-policy.js +56 -0
  48. package/dist/agent/task-lifecycle-reminder.d.ts +26 -0
  49. package/dist/agent/task-lifecycle-reminder.js +59 -0
  50. package/dist/agent/tool-intent.d.ts +45 -0
  51. package/dist/agent/tool-intent.js +318 -0
  52. package/dist/agent/tool-visibility.d.ts +10 -0
  53. package/dist/agent/tool-visibility.js +6 -0
  54. package/dist/agent/transcript-helpers.d.ts +20 -0
  55. package/dist/agent/transcript-helpers.js +63 -0
  56. package/dist/agent/update-queue.d.ts +13 -0
  57. package/dist/agent/update-queue.js +69 -0
  58. package/dist/agent/workflow/control.d.ts +60 -0
  59. package/dist/agent/workflow/control.js +62 -0
  60. package/dist/agent/workflow/errors.d.ts +16 -0
  61. package/dist/agent/workflow/errors.js +24 -0
  62. package/dist/agent/workflow/runs.d.ts +73 -0
  63. package/dist/agent/workflow/runs.js +160 -0
  64. package/dist/agent/workflow/runtime.d.ts +88 -0
  65. package/dist/agent/workflow/runtime.js +319 -0
  66. package/dist/agent/worktree.d.ts +29 -0
  67. package/dist/agent/worktree.js +73 -0
  68. package/dist/agent.d.ts +460 -0
  69. package/dist/agent.js +2210 -0
  70. package/dist/anthropic-thinking.d.ts +4 -0
  71. package/dist/anthropic-thinking.js +11 -0
  72. package/dist/approval/controller.d.ts +71 -0
  73. package/dist/approval/controller.js +286 -0
  74. package/dist/approval/danger.d.ts +13 -0
  75. package/dist/approval/danger.js +55 -0
  76. package/dist/approval/diff-hunks.d.ts +12 -0
  77. package/dist/approval/diff-hunks.js +32 -0
  78. package/dist/approval/protected-paths.d.ts +1 -0
  79. package/dist/approval/protected-paths.js +35 -0
  80. package/dist/approval/session-cache.d.ts +21 -0
  81. package/dist/approval/session-cache.js +38 -0
  82. package/dist/approval/tool-helper.d.ts +14 -0
  83. package/dist/approval/tool-helper.js +47 -0
  84. package/dist/approval/types.d.ts +146 -0
  85. package/dist/approval/types.js +8 -0
  86. package/dist/bin.d.ts +2 -0
  87. package/dist/bin.js +3 -0
  88. package/dist/bubble-home.d.ts +8 -0
  89. package/dist/bubble-home.js +19 -0
  90. package/dist/checkpoints.d.ts +57 -0
  91. package/dist/checkpoints.js +0 -0
  92. package/dist/cli.d.ts +37 -0
  93. package/dist/cli.js +175 -0
  94. package/dist/clipboard.d.ts +14 -0
  95. package/dist/clipboard.js +132 -0
  96. package/dist/config.d.ts +96 -0
  97. package/dist/config.js +287 -0
  98. package/dist/context/budget.d.ts +28 -0
  99. package/dist/context/budget.js +130 -0
  100. package/dist/context/checkpoint.d.ts +18 -0
  101. package/dist/context/checkpoint.js +117 -0
  102. package/dist/context/compact-llm.d.ts +25 -0
  103. package/dist/context/compact-llm.js +175 -0
  104. package/dist/context/compact.d.ts +124 -0
  105. package/dist/context/compact.js +635 -0
  106. package/dist/context/compaction-files.d.ts +44 -0
  107. package/dist/context/compaction-files.js +148 -0
  108. package/dist/context/llm-compactor.d.ts +53 -0
  109. package/dist/context/llm-compactor.js +424 -0
  110. package/dist/context/overflow.d.ts +9 -0
  111. package/dist/context/overflow.js +50 -0
  112. package/dist/context/projector.d.ts +34 -0
  113. package/dist/context/projector.js +251 -0
  114. package/dist/context/prune.d.ts +26 -0
  115. package/dist/context/prune.js +171 -0
  116. package/dist/context/session-write-lock.d.ts +20 -0
  117. package/dist/context/session-write-lock.js +162 -0
  118. package/dist/context/token-estimator.d.ts +14 -0
  119. package/dist/context/token-estimator.js +106 -0
  120. package/dist/context/tool-images.d.ts +3 -0
  121. package/dist/context/tool-images.js +35 -0
  122. package/dist/context/tool-output-truncate.d.ts +24 -0
  123. package/dist/context/tool-output-truncate.js +181 -0
  124. package/dist/context/usage.d.ts +34 -0
  125. package/dist/context/usage.js +230 -0
  126. package/dist/debug-trace.d.ts +27 -0
  127. package/dist/debug-trace.js +401 -0
  128. package/dist/diff-stats.d.ts +5 -0
  129. package/dist/diff-stats.js +21 -0
  130. package/dist/external-runtime/grok-acp.d.ts +56 -0
  131. package/dist/external-runtime/grok-acp.js +386 -0
  132. package/dist/external-runtime/grok-binary.d.ts +25 -0
  133. package/dist/external-runtime/grok-binary.js +108 -0
  134. package/dist/external-runtime/grok-errors.d.ts +7 -0
  135. package/dist/external-runtime/grok-errors.js +15 -0
  136. package/dist/external-runtime/grok-input-policy.d.ts +58 -0
  137. package/dist/external-runtime/grok-input-policy.js +52 -0
  138. package/dist/external-runtime/grok-ndjson.d.ts +14 -0
  139. package/dist/external-runtime/grok-ndjson.js +193 -0
  140. package/dist/external-runtime/grok-network.d.ts +15 -0
  141. package/dist/external-runtime/grok-network.js +81 -0
  142. package/dist/external-runtime/grok-preflight.d.ts +28 -0
  143. package/dist/external-runtime/grok-preflight.js +194 -0
  144. package/dist/external-runtime/grok-process.d.ts +26 -0
  145. package/dist/external-runtime/grok-process.js +184 -0
  146. package/dist/external-runtime/grok-profile.d.ts +32 -0
  147. package/dist/external-runtime/grok-profile.js +419 -0
  148. package/dist/external-runtime/grok-provider.d.ts +31 -0
  149. package/dist/external-runtime/grok-provider.js +41 -0
  150. package/dist/external-runtime/grok-runtime.d.ts +91 -0
  151. package/dist/external-runtime/grok-runtime.js +882 -0
  152. package/dist/external-runtime/index.d.ts +13 -0
  153. package/dist/external-runtime/index.js +13 -0
  154. package/dist/external-runtime/session-policy.d.ts +11 -0
  155. package/dist/external-runtime/session-policy.js +30 -0
  156. package/dist/external-runtime/types.d.ts +65 -0
  157. package/dist/external-runtime/types.js +1 -0
  158. package/dist/feedback/collect.d.ts +7 -0
  159. package/dist/feedback/collect.js +119 -0
  160. package/dist/feedback/config.d.ts +14 -0
  161. package/dist/feedback/config.js +16 -0
  162. package/dist/feedback/redact.d.ts +1 -0
  163. package/dist/feedback/redact.js +25 -0
  164. package/dist/feedback/submit.d.ts +6 -0
  165. package/dist/feedback/submit.js +43 -0
  166. package/dist/feedback/types.d.ts +22 -0
  167. package/dist/feedback/types.js +1 -0
  168. package/dist/feishu/agent-host/approval-card.d.ts +11 -0
  169. package/dist/feishu/agent-host/approval-card.js +85 -0
  170. package/dist/feishu/agent-host/approval-ui.d.ts +59 -0
  171. package/dist/feishu/agent-host/approval-ui.js +214 -0
  172. package/dist/feishu/agent-host/run-driver.d.ts +51 -0
  173. package/dist/feishu/agent-host/run-driver.js +397 -0
  174. package/dist/feishu/agent-host/runtime-deps.d.ts +33 -0
  175. package/dist/feishu/agent-host/runtime-deps.js +8 -0
  176. package/dist/feishu/card/budget.d.ts +40 -0
  177. package/dist/feishu/card/budget.js +134 -0
  178. package/dist/feishu/card/renderer.d.ts +29 -0
  179. package/dist/feishu/card/renderer.js +245 -0
  180. package/dist/feishu/card/run-state-types.d.ts +49 -0
  181. package/dist/feishu/card/run-state-types.js +15 -0
  182. package/dist/feishu/card/run-state.d.ts +21 -0
  183. package/dist/feishu/card/run-state.js +223 -0
  184. package/dist/feishu/channel/channel.d.ts +52 -0
  185. package/dist/feishu/channel/channel.js +74 -0
  186. package/dist/feishu/config.d.ts +24 -0
  187. package/dist/feishu/config.js +97 -0
  188. package/dist/feishu/format.d.ts +6 -0
  189. package/dist/feishu/format.js +14 -0
  190. package/dist/feishu/index.d.ts +4 -0
  191. package/dist/feishu/index.js +4 -0
  192. package/dist/feishu/logger.d.ts +31 -0
  193. package/dist/feishu/logger.js +62 -0
  194. package/dist/feishu/paths.d.ts +12 -0
  195. package/dist/feishu/paths.js +38 -0
  196. package/dist/feishu/process-registry.d.ts +29 -0
  197. package/dist/feishu/process-registry.js +90 -0
  198. package/dist/feishu/router/commands.d.ts +38 -0
  199. package/dist/feishu/router/commands.js +286 -0
  200. package/dist/feishu/router/event-router.d.ts +40 -0
  201. package/dist/feishu/router/event-router.js +208 -0
  202. package/dist/feishu/router/whitelist.d.ts +23 -0
  203. package/dist/feishu/router/whitelist.js +20 -0
  204. package/dist/feishu/runtime/active-runs.d.ts +32 -0
  205. package/dist/feishu/runtime/active-runs.js +84 -0
  206. package/dist/feishu/runtime/pending-queue.d.ts +36 -0
  207. package/dist/feishu/runtime/pending-queue.js +98 -0
  208. package/dist/feishu/runtime/process-pool.d.ts +29 -0
  209. package/dist/feishu/runtime/process-pool.js +49 -0
  210. package/dist/feishu/schema.d.ts +17 -0
  211. package/dist/feishu/schema.js +252 -0
  212. package/dist/feishu/scope/scope-registry.d.ts +39 -0
  213. package/dist/feishu/scope/scope-registry.js +148 -0
  214. package/dist/feishu/scope/session-binder.d.ts +44 -0
  215. package/dist/feishu/scope/session-binder.js +100 -0
  216. package/dist/feishu/scope/session-store.d.ts +24 -0
  217. package/dist/feishu/scope/session-store.js +73 -0
  218. package/dist/feishu/secrets.d.ts +37 -0
  219. package/dist/feishu/secrets.js +129 -0
  220. package/dist/feishu/serve.d.ts +12 -0
  221. package/dist/feishu/serve.js +302 -0
  222. package/dist/feishu/types.d.ts +75 -0
  223. package/dist/feishu/types.js +23 -0
  224. package/dist/feishu/wizard.d.ts +24 -0
  225. package/dist/feishu/wizard.js +121 -0
  226. package/dist/goal/command.d.ts +20 -0
  227. package/dist/goal/command.js +76 -0
  228. package/dist/goal/engine.d.ts +33 -0
  229. package/dist/goal/engine.js +65 -0
  230. package/dist/goal/format.d.ts +18 -0
  231. package/dist/goal/format.js +112 -0
  232. package/dist/goal/prompts.d.ts +13 -0
  233. package/dist/goal/prompts.js +84 -0
  234. package/dist/goal/store.d.ts +64 -0
  235. package/dist/goal/store.js +174 -0
  236. package/dist/goal/tools.d.ts +13 -0
  237. package/dist/goal/tools.js +67 -0
  238. package/dist/goal/usage.d.ts +2 -0
  239. package/dist/goal/usage.js +3 -0
  240. package/dist/hooks/config.d.ts +9 -0
  241. package/dist/hooks/config.js +278 -0
  242. package/dist/hooks/controller.d.ts +24 -0
  243. package/dist/hooks/controller.js +254 -0
  244. package/dist/hooks/index.d.ts +6 -0
  245. package/dist/hooks/index.js +4 -0
  246. package/dist/hooks/log.d.ts +14 -0
  247. package/dist/hooks/log.js +54 -0
  248. package/dist/hooks/runner.d.ts +5 -0
  249. package/dist/hooks/runner.js +225 -0
  250. package/dist/hooks/trust.d.ts +37 -0
  251. package/dist/hooks/trust.js +143 -0
  252. package/dist/hooks/types.d.ts +173 -0
  253. package/dist/hooks/types.js +46 -0
  254. package/dist/loop/engine.d.ts +29 -0
  255. package/dist/loop/engine.js +63 -0
  256. package/dist/lsp/config.d.ts +18 -0
  257. package/dist/lsp/config.js +58 -0
  258. package/dist/lsp/diagnostics.d.ts +24 -0
  259. package/dist/lsp/diagnostics.js +103 -0
  260. package/dist/lsp/index.d.ts +3 -0
  261. package/dist/lsp/index.js +3 -0
  262. package/dist/lsp/service.d.ts +97 -0
  263. package/dist/lsp/service.js +815 -0
  264. package/dist/main.d.ts +5 -0
  265. package/dist/main.js +984 -0
  266. package/dist/mcp/client.d.ts +68 -0
  267. package/dist/mcp/client.js +177 -0
  268. package/dist/mcp/config.d.ts +30 -0
  269. package/dist/mcp/config.js +144 -0
  270. package/dist/mcp/manager.d.ts +67 -0
  271. package/dist/mcp/manager.js +328 -0
  272. package/dist/mcp/name.d.ts +26 -0
  273. package/dist/mcp/name.js +40 -0
  274. package/dist/mcp/transports.d.ts +54 -0
  275. package/dist/mcp/transports.js +267 -0
  276. package/dist/mcp/types.d.ts +111 -0
  277. package/dist/mcp/types.js +14 -0
  278. package/dist/memory/db.d.ts +65 -0
  279. package/dist/memory/db.js +332 -0
  280. package/dist/memory/index.d.ts +10 -0
  281. package/dist/memory/index.js +10 -0
  282. package/dist/memory/paths.d.ts +18 -0
  283. package/dist/memory/paths.js +38 -0
  284. package/dist/memory/phase1.d.ts +23 -0
  285. package/dist/memory/phase1.js +292 -0
  286. package/dist/memory/phase2.d.ts +19 -0
  287. package/dist/memory/phase2.js +119 -0
  288. package/dist/memory/prompts.d.ts +19 -0
  289. package/dist/memory/prompts.js +104 -0
  290. package/dist/memory/reset.d.ts +1 -0
  291. package/dist/memory/reset.js +13 -0
  292. package/dist/memory/session-policy.d.ts +25 -0
  293. package/dist/memory/session-policy.js +55 -0
  294. package/dist/memory/start.d.ts +24 -0
  295. package/dist/memory/start.js +50 -0
  296. package/dist/memory/storage.d.ts +11 -0
  297. package/dist/memory/storage.js +95 -0
  298. package/dist/memory/store.d.ts +43 -0
  299. package/dist/memory/store.js +193 -0
  300. package/dist/memory/usage.d.ts +1 -0
  301. package/dist/memory/usage.js +38 -0
  302. package/dist/model-catalog.d.ts +65 -0
  303. package/dist/model-catalog.js +399 -0
  304. package/dist/model-config.d.ts +40 -0
  305. package/dist/model-config.js +74 -0
  306. package/dist/model-data.generated.d.ts +16 -0
  307. package/dist/model-data.generated.js +275 -0
  308. package/dist/model-pricing.d.ts +54 -0
  309. package/dist/model-pricing.js +256 -0
  310. package/dist/model-selection.d.ts +7 -0
  311. package/dist/model-selection.js +18 -0
  312. package/dist/network/chatgpt-transport.d.ts +16 -0
  313. package/dist/network/chatgpt-transport.js +232 -0
  314. package/dist/network/errors.d.ts +47 -0
  315. package/dist/network/errors.js +48 -0
  316. package/dist/network/provider-transport-log.d.ts +12 -0
  317. package/dist/network/provider-transport-log.js +32 -0
  318. package/dist/network/provider-transport.d.ts +37 -0
  319. package/dist/network/provider-transport.js +256 -0
  320. package/dist/network/retry.d.ts +34 -0
  321. package/dist/network/retry.js +97 -0
  322. package/dist/network/system-proxy.d.ts +18 -0
  323. package/dist/network/system-proxy.js +175 -0
  324. package/dist/oauth/grok.d.ts +26 -0
  325. package/dist/oauth/grok.js +251 -0
  326. package/dist/oauth/index.d.ts +4 -0
  327. package/dist/oauth/index.js +3 -0
  328. package/dist/oauth/openai-codex.d.ts +15 -0
  329. package/dist/oauth/openai-codex.js +193 -0
  330. package/dist/oauth/refresh-control.d.ts +12 -0
  331. package/dist/oauth/refresh-control.js +42 -0
  332. package/dist/oauth/storage.d.ts +88 -0
  333. package/dist/oauth/storage.js +357 -0
  334. package/dist/oauth/types.d.ts +15 -0
  335. package/dist/oauth/types.js +1 -0
  336. package/dist/orchestrator/default-hooks.d.ts +22 -0
  337. package/dist/orchestrator/default-hooks.js +127 -0
  338. package/dist/orchestrator/hooks.d.ts +69 -0
  339. package/dist/orchestrator/hooks.js +52 -0
  340. package/dist/permission/mode.d.ts +19 -0
  341. package/dist/permission/mode.js +16 -0
  342. package/dist/permissions/rule.d.ts +42 -0
  343. package/dist/permissions/rule.js +293 -0
  344. package/dist/permissions/settings.d.ts +97 -0
  345. package/dist/permissions/settings.js +289 -0
  346. package/dist/permissions/shell-command.d.ts +36 -0
  347. package/dist/permissions/shell-command.js +226 -0
  348. package/dist/permissions/trust-prompt.d.ts +10 -0
  349. package/dist/permissions/trust-prompt.js +34 -0
  350. package/dist/permissions/trust.d.ts +26 -0
  351. package/dist/permissions/trust.js +113 -0
  352. package/dist/permissions/types.d.ts +61 -0
  353. package/dist/permissions/types.js +14 -0
  354. package/dist/print-output.d.ts +98 -0
  355. package/dist/print-output.js +117 -0
  356. package/dist/prompt/compose.d.ts +19 -0
  357. package/dist/prompt/compose.js +110 -0
  358. package/dist/prompt/delegation.d.ts +14 -0
  359. package/dist/prompt/delegation.js +100 -0
  360. package/dist/prompt/environment.d.ts +12 -0
  361. package/dist/prompt/environment.js +55 -0
  362. package/dist/prompt/provider-prompts/anthropic.d.ts +1 -0
  363. package/dist/prompt/provider-prompts/anthropic.js +5 -0
  364. package/dist/prompt/provider-prompts/codex.d.ts +1 -0
  365. package/dist/prompt/provider-prompts/codex.js +5 -0
  366. package/dist/prompt/provider-prompts/deepseek.d.ts +1 -0
  367. package/dist/prompt/provider-prompts/deepseek.js +7 -0
  368. package/dist/prompt/provider-prompts/default.d.ts +1 -0
  369. package/dist/prompt/provider-prompts/default.js +6 -0
  370. package/dist/prompt/provider-prompts/gemini.d.ts +1 -0
  371. package/dist/prompt/provider-prompts/gemini.js +5 -0
  372. package/dist/prompt/provider-prompts/glm.d.ts +1 -0
  373. package/dist/prompt/provider-prompts/glm.js +7 -0
  374. package/dist/prompt/provider-prompts/gpt.d.ts +1 -0
  375. package/dist/prompt/provider-prompts/gpt.js +5 -0
  376. package/dist/prompt/provider-prompts/kimi.d.ts +1 -0
  377. package/dist/prompt/provider-prompts/kimi.js +8 -0
  378. package/dist/prompt/reminders.d.ts +33 -0
  379. package/dist/prompt/reminders.js +135 -0
  380. package/dist/prompt/routing.d.ts +14 -0
  381. package/dist/prompt/routing.js +71 -0
  382. package/dist/prompt/runtime.d.ts +12 -0
  383. package/dist/prompt/runtime.js +40 -0
  384. package/dist/prompt/task-reminders.d.ts +12 -0
  385. package/dist/prompt/task-reminders.js +64 -0
  386. package/dist/provider-ai-sdk.d.ts +59 -0
  387. package/dist/provider-ai-sdk.js +584 -0
  388. package/dist/provider-anthropic.d.ts +126 -0
  389. package/dist/provider-anthropic.js +940 -0
  390. package/dist/provider-ark-responses.d.ts +29 -0
  391. package/dist/provider-ark-responses.js +518 -0
  392. package/dist/provider-artifacts.d.ts +7 -0
  393. package/dist/provider-artifacts.js +60 -0
  394. package/dist/provider-error-record.d.ts +43 -0
  395. package/dist/provider-error-record.js +213 -0
  396. package/dist/provider-grok.d.ts +50 -0
  397. package/dist/provider-grok.js +110 -0
  398. package/dist/provider-model-policy.d.ts +16 -0
  399. package/dist/provider-model-policy.js +33 -0
  400. package/dist/provider-openai-codex.d.ts +66 -0
  401. package/dist/provider-openai-codex.js +781 -0
  402. package/dist/provider-registry.d.ts +197 -0
  403. package/dist/provider-registry.js +1194 -0
  404. package/dist/provider-transform.d.ts +12 -0
  405. package/dist/provider-transform.js +293 -0
  406. package/dist/provider-zen-errors.d.ts +9 -0
  407. package/dist/provider-zen-errors.js +36 -0
  408. package/dist/provider.d.ts +85 -0
  409. package/dist/provider.js +925 -0
  410. package/dist/question/controller.d.ts +22 -0
  411. package/dist/question/controller.js +98 -0
  412. package/dist/question/index.d.ts +2 -0
  413. package/dist/question/index.js +2 -0
  414. package/dist/question/normalize.d.ts +6 -0
  415. package/dist/question/normalize.js +11 -0
  416. package/dist/question/types.d.ts +42 -0
  417. package/dist/question/types.js +6 -0
  418. package/dist/reasoning-debug.d.ts +7 -0
  419. package/dist/reasoning-debug.js +33 -0
  420. package/dist/rewind.d.ts +20 -0
  421. package/dist/rewind.js +29 -0
  422. package/dist/sdk/index.d.ts +319 -0
  423. package/dist/sdk/index.js +1068 -0
  424. package/dist/sdk/replay-event-log.d.ts +26 -0
  425. package/dist/sdk/replay-event-log.js +122 -0
  426. package/dist/sdk/session-turn-coordinator.d.ts +45 -0
  427. package/dist/sdk/session-turn-coordinator.js +227 -0
  428. package/dist/session-context-fence.d.ts +16 -0
  429. package/dist/session-context-fence.js +26 -0
  430. package/dist/session-log.d.ts +25 -0
  431. package/dist/session-log.js +444 -0
  432. package/dist/session-title.d.ts +16 -0
  433. package/dist/session-title.js +131 -0
  434. package/dist/session-types.d.ts +86 -0
  435. package/dist/session-types.js +1 -0
  436. package/dist/session.d.ts +135 -0
  437. package/dist/session.js +664 -0
  438. package/dist/skills/discovery.d.ts +12 -0
  439. package/dist/skills/discovery.js +166 -0
  440. package/dist/skills/format.d.ts +32 -0
  441. package/dist/skills/format.js +68 -0
  442. package/dist/skills/frontmatter.d.ts +5 -0
  443. package/dist/skills/frontmatter.js +60 -0
  444. package/dist/skills/invocation.d.ts +8 -0
  445. package/dist/skills/invocation.js +33 -0
  446. package/dist/skills/registry.d.ts +28 -0
  447. package/dist/skills/registry.js +79 -0
  448. package/dist/skills/types.d.ts +34 -0
  449. package/dist/skills/types.js +1 -0
  450. package/dist/slash-commands/commands.d.ts +10 -0
  451. package/dist/slash-commands/commands.js +1251 -0
  452. package/dist/slash-commands/feishu.d.ts +17 -0
  453. package/dist/slash-commands/feishu.js +400 -0
  454. package/dist/slash-commands/index.d.ts +4 -0
  455. package/dist/slash-commands/index.js +8 -0
  456. package/dist/slash-commands/registry.d.ts +32 -0
  457. package/dist/slash-commands/registry.js +73 -0
  458. package/dist/slash-commands/types.d.ts +109 -0
  459. package/dist/slash-commands/types.js +1 -0
  460. package/dist/slash-commands/unified.d.ts +38 -0
  461. package/dist/slash-commands/unified.js +38 -0
  462. package/dist/stats/usage.d.ts +59 -0
  463. package/dist/stats/usage.js +485 -0
  464. package/dist/stream-json.d.ts +13 -0
  465. package/dist/stream-json.js +74 -0
  466. package/dist/system-prompt.d.ts +40 -0
  467. package/dist/system-prompt.js +7 -0
  468. package/dist/tasks/manager.d.ts +145 -0
  469. package/dist/tasks/manager.js +634 -0
  470. package/dist/tasks/process-utils.d.ts +10 -0
  471. package/dist/tasks/process-utils.js +49 -0
  472. package/dist/tasks/promotion.d.ts +17 -0
  473. package/dist/tasks/promotion.js +30 -0
  474. package/dist/tasks/wake.d.ts +53 -0
  475. package/dist/tasks/wake.js +125 -0
  476. package/dist/text-display.d.ts +3 -0
  477. package/dist/text-display.js +25 -0
  478. package/dist/tools/agent-lifecycle.d.ts +30 -0
  479. package/dist/tools/agent-lifecycle.js +790 -0
  480. package/dist/tools/bash.d.ts +23 -0
  481. package/dist/tools/bash.js +443 -0
  482. package/dist/tools/child-tools.d.ts +41 -0
  483. package/dist/tools/child-tools.js +122 -0
  484. package/dist/tools/edit-apply.d.ts +25 -0
  485. package/dist/tools/edit-apply.js +274 -0
  486. package/dist/tools/edit.d.ts +18 -0
  487. package/dist/tools/edit.js +200 -0
  488. package/dist/tools/exa-mcp.d.ts +3 -0
  489. package/dist/tools/exa-mcp.js +74 -0
  490. package/dist/tools/exit-plan-mode.d.ts +22 -0
  491. package/dist/tools/exit-plan-mode.js +79 -0
  492. package/dist/tools/file-mutation-queue.d.ts +2 -0
  493. package/dist/tools/file-mutation-queue.js +43 -0
  494. package/dist/tools/file-state.d.ts +48 -0
  495. package/dist/tools/file-state.js +76 -0
  496. package/dist/tools/grep.d.ts +9 -0
  497. package/dist/tools/grep.js +387 -0
  498. package/dist/tools/index.d.ts +63 -0
  499. package/dist/tools/index.js +76 -0
  500. package/dist/tools/ls.d.ts +3 -0
  501. package/dist/tools/ls.js +85 -0
  502. package/dist/tools/lsp.d.ts +4 -0
  503. package/dist/tools/lsp.js +95 -0
  504. package/dist/tools/memory.d.ts +8 -0
  505. package/dist/tools/memory.js +77 -0
  506. package/dist/tools/path-utils.d.ts +8 -0
  507. package/dist/tools/path-utils.js +53 -0
  508. package/dist/tools/prompt-metadata.d.ts +3 -0
  509. package/dist/tools/prompt-metadata.js +17 -0
  510. package/dist/tools/question.d.ts +3 -0
  511. package/dist/tools/question.js +177 -0
  512. package/dist/tools/read.d.ts +8 -0
  513. package/dist/tools/read.js +309 -0
  514. package/dist/tools/sensitive-paths.d.ts +11 -0
  515. package/dist/tools/sensitive-paths.js +106 -0
  516. package/dist/tools/server-manager.d.ts +6 -0
  517. package/dist/tools/server-manager.js +6 -0
  518. package/dist/tools/server.d.ts +6 -0
  519. package/dist/tools/server.js +249 -0
  520. package/dist/tools/skill-search.d.ts +10 -0
  521. package/dist/tools/skill-search.js +134 -0
  522. package/dist/tools/skill.d.ts +7 -0
  523. package/dist/tools/skill.js +81 -0
  524. package/dist/tools/task-tools.d.ts +10 -0
  525. package/dist/tools/task-tools.js +133 -0
  526. package/dist/tools/tool-search.d.ts +23 -0
  527. package/dist/tools/tool-search.js +145 -0
  528. package/dist/tools/web-fetch.d.ts +6 -0
  529. package/dist/tools/web-fetch.js +76 -0
  530. package/dist/tools/web-search.d.ts +5 -0
  531. package/dist/tools/web-search.js +50 -0
  532. package/dist/tools/write.d.ts +10 -0
  533. package/dist/tools/write.js +155 -0
  534. package/dist/tui/animation-clock.d.ts +14 -0
  535. package/dist/tui/animation-clock.js +34 -0
  536. package/dist/tui/app.d.ts +176 -0
  537. package/dist/tui/app.js +1659 -0
  538. package/dist/tui/components/approval-dialog.d.ts +43 -0
  539. package/dist/tui/components/approval-dialog.js +212 -0
  540. package/dist/tui/components/bottom-sheet.d.ts +9 -0
  541. package/dist/tui/components/bottom-sheet.js +32 -0
  542. package/dist/tui/components/context-info.d.ts +54 -0
  543. package/dist/tui/components/context-info.js +388 -0
  544. package/dist/tui/components/feedback-dialog.d.ts +56 -0
  545. package/dist/tui/components/feedback-dialog.js +262 -0
  546. package/dist/tui/components/feishu-setup-dialog.d.ts +65 -0
  547. package/dist/tui/components/feishu-setup-dialog.js +332 -0
  548. package/dist/tui/components/image-preview.d.ts +32 -0
  549. package/dist/tui/components/image-preview.js +130 -0
  550. package/dist/tui/components/plan-dialog.d.ts +34 -0
  551. package/dist/tui/components/plan-dialog.js +196 -0
  552. package/dist/tui/components/provider-key-input.d.ts +18 -0
  553. package/dist/tui/components/provider-key-input.js +49 -0
  554. package/dist/tui/components/question-dialog.d.ts +25 -0
  555. package/dist/tui/components/question-dialog.js +263 -0
  556. package/dist/tui/components/responsive-transcript.d.ts +31 -0
  557. package/dist/tui/components/responsive-transcript.js +77 -0
  558. package/dist/tui/components/rewind-picker.d.ts +62 -0
  559. package/dist/tui/components/rewind-picker.js +317 -0
  560. package/dist/tui/components/session-picker.d.ts +49 -0
  561. package/dist/tui/components/session-picker.js +303 -0
  562. package/dist/tui/components/skills-panel.d.ts +63 -0
  563. package/dist/tui/components/skills-panel.js +582 -0
  564. package/dist/tui/components/stats-panel.d.ts +40 -0
  565. package/dist/tui/components/stats-panel.js +301 -0
  566. package/dist/tui/components/streaming-message.d.ts +69 -0
  567. package/dist/tui/components/streaming-message.js +398 -0
  568. package/dist/tui/components/subagent-inspector.d.ts +28 -0
  569. package/dist/tui/components/subagent-inspector.js +127 -0
  570. package/dist/tui/components/task-inspector.d.ts +26 -0
  571. package/dist/tui/components/task-inspector.js +70 -0
  572. package/dist/tui/components/tasks-pane.d.ts +126 -0
  573. package/dist/tui/components/tasks-pane.js +566 -0
  574. package/dist/tui/components/transcript.d.ts +103 -0
  575. package/dist/tui/components/transcript.js +858 -0
  576. package/dist/tui/components/welcome.d.ts +20 -0
  577. package/dist/tui/components/welcome.js +133 -0
  578. package/dist/tui/components/workflow-inspector.d.ts +30 -0
  579. package/dist/tui/components/workflow-inspector.js +136 -0
  580. package/dist/tui/composer-autocomplete.d.ts +76 -0
  581. package/dist/tui/composer-autocomplete.js +465 -0
  582. package/dist/tui/composer-style.d.ts +7 -0
  583. package/dist/tui/composer-style.js +29 -0
  584. package/dist/tui/controller/agent-event-reducer.d.ts +75 -0
  585. package/dist/tui/controller/agent-event-reducer.js +239 -0
  586. package/dist/tui/controller/composer-controller.d.ts +95 -0
  587. package/dist/tui/controller/composer-controller.js +502 -0
  588. package/dist/tui/controller/controller.d.ts +199 -0
  589. package/dist/tui/controller/controller.js +1034 -0
  590. package/dist/tui/controller/effects.d.ts +71 -0
  591. package/dist/tui/controller/effects.js +1 -0
  592. package/dist/tui/controller/goal-runtime-controller.d.ts +47 -0
  593. package/dist/tui/controller/goal-runtime-controller.js +202 -0
  594. package/dist/tui/controller/input-queue-machine.d.ts +71 -0
  595. package/dist/tui/controller/input-queue-machine.js +121 -0
  596. package/dist/tui/controller/intents.d.ts +56 -0
  597. package/dist/tui/controller/intents.js +6 -0
  598. package/dist/tui/controller/overlay-controller.d.ts +64 -0
  599. package/dist/tui/controller/overlay-controller.js +129 -0
  600. package/dist/tui/controller/ports.d.ts +57 -0
  601. package/dist/tui/controller/ports.js +9 -0
  602. package/dist/tui/controller/session-transition.d.ts +54 -0
  603. package/dist/tui/controller/session-transition.js +68 -0
  604. package/dist/tui/controller/state.d.ts +14 -0
  605. package/dist/tui/controller/state.js +36 -0
  606. package/dist/tui/controller/task-runtime-controller.d.ts +66 -0
  607. package/dist/tui/controller/task-runtime-controller.js +158 -0
  608. package/dist/tui/detect-theme.d.ts +4 -0
  609. package/dist/tui/detect-theme.js +116 -0
  610. package/dist/tui/display-history.d.ts +64 -0
  611. package/dist/tui/display-history.js +283 -0
  612. package/dist/tui/display-sanitizer.d.ts +3 -0
  613. package/dist/tui/display-sanitizer.js +38 -0
  614. package/dist/tui/edit-diff.d.ts +11 -0
  615. package/dist/tui/edit-diff.js +57 -0
  616. package/dist/tui/file-mentions.d.ts +29 -0
  617. package/dist/tui/file-mentions.js +174 -0
  618. package/dist/tui/footer.d.ts +37 -0
  619. package/dist/tui/footer.js +80 -0
  620. package/dist/tui/formatting/edit-diff.d.ts +11 -0
  621. package/dist/tui/formatting/edit-diff.js +52 -0
  622. package/dist/tui/formatting/summary.d.ts +43 -0
  623. package/dist/tui/formatting/summary.js +150 -0
  624. package/dist/tui/fullscreen.d.ts +48 -0
  625. package/dist/tui/fullscreen.js +256 -0
  626. package/dist/tui/image-display.d.ts +23 -0
  627. package/dist/tui/image-display.js +92 -0
  628. package/dist/tui/image-paste.d.ts +113 -0
  629. package/dist/tui/image-paste.js +565 -0
  630. package/dist/tui/input-history.d.ts +47 -0
  631. package/dist/tui/input-history.js +250 -0
  632. package/dist/tui/markdown-style.d.ts +5 -0
  633. package/dist/tui/markdown-style.js +23 -0
  634. package/dist/tui/model/composer-buffer.d.ts +55 -0
  635. package/dist/tui/model/composer-buffer.js +317 -0
  636. package/dist/tui/model/composer-types.d.ts +17 -0
  637. package/dist/tui/model/composer-types.js +1 -0
  638. package/dist/tui/model/display-history.d.ts +78 -0
  639. package/dist/tui/model/display-history.js +124 -0
  640. package/dist/tui/model/display-reconstruct.d.ts +3 -0
  641. package/dist/tui/model/display-reconstruct.js +130 -0
  642. package/dist/tui/model/file-mentions.d.ts +31 -0
  643. package/dist/tui/model/file-mentions.js +178 -0
  644. package/dist/tui/model/image-attachment.d.ts +15 -0
  645. package/dist/tui/model/image-attachment.js +1 -0
  646. package/dist/tui/model/image-paste.d.ts +119 -0
  647. package/dist/tui/model/image-paste.js +610 -0
  648. package/dist/tui/model/input-history.d.ts +1 -0
  649. package/dist/tui/model/input-history.js +1 -0
  650. package/dist/tui/model/input-queue.d.ts +12 -0
  651. package/dist/tui/model/input-queue.js +17 -0
  652. package/dist/tui/model/key-events.d.ts +9 -0
  653. package/dist/tui/model/key-events.js +8 -0
  654. package/dist/tui/model/paste-operation-tracker.d.ts +13 -0
  655. package/dist/tui/model/paste-operation-tracker.js +25 -0
  656. package/dist/tui/model/recent-activity.d.ts +8 -0
  657. package/dist/tui/model/recent-activity.js +71 -0
  658. package/dist/tui/model/subagent-view.d.ts +87 -0
  659. package/dist/tui/model/subagent-view.js +336 -0
  660. package/dist/tui/model/submit-dedupe.d.ts +5 -0
  661. package/dist/tui/model/submit-dedupe.js +25 -0
  662. package/dist/tui/model/sync-output.d.ts +17 -0
  663. package/dist/tui/model/sync-output.js +35 -0
  664. package/dist/tui/model/task-lifecycle.d.ts +57 -0
  665. package/dist/tui/model/task-lifecycle.js +124 -0
  666. package/dist/tui/model/terminal-env.d.ts +15 -0
  667. package/dist/tui/model/terminal-env.js +22 -0
  668. package/dist/tui/model/terminal-mouse.d.ts +28 -0
  669. package/dist/tui/model/terminal-mouse.js +82 -0
  670. package/dist/tui/model/theme-style.d.ts +10 -0
  671. package/dist/tui/model/theme-style.js +79 -0
  672. package/dist/tui/model/theme.d.ts +72 -0
  673. package/dist/tui/model/theme.js +121 -0
  674. package/dist/tui/model/trace-groups.d.ts +45 -0
  675. package/dist/tui/model/trace-groups.js +558 -0
  676. package/dist/tui/model/trace-interaction.d.ts +63 -0
  677. package/dist/tui/model/trace-interaction.js +87 -0
  678. package/dist/tui/model/width.d.ts +18 -0
  679. package/dist/tui/model/width.js +130 -0
  680. package/dist/tui/model-picker-data.d.ts +9 -0
  681. package/dist/tui/model-picker-data.js +39 -0
  682. package/dist/tui/model-switch.d.ts +49 -0
  683. package/dist/tui/model-switch.js +67 -0
  684. package/dist/tui/paste-placeholder.d.ts +11 -0
  685. package/dist/tui/paste-placeholder.js +52 -0
  686. package/dist/tui/recent-activity.d.ts +8 -0
  687. package/dist/tui/recent-activity.js +71 -0
  688. package/dist/tui/run-session-picker.d.ts +9 -0
  689. package/dist/tui/run-session-picker.js +64 -0
  690. package/dist/tui/run.d.ts +72 -0
  691. package/dist/tui/run.js +139 -0
  692. package/dist/tui/session-display.d.ts +6 -0
  693. package/dist/tui/session-display.js +12 -0
  694. package/dist/tui/session-picker-data.d.ts +18 -0
  695. package/dist/tui/session-picker-data.js +21 -0
  696. package/dist/tui/terminal/multiplexer.d.ts +5 -0
  697. package/dist/tui/terminal/multiplexer.js +14 -0
  698. package/dist/tui/testing/fake-agent.d.ts +24 -0
  699. package/dist/tui/testing/fake-agent.js +22 -0
  700. package/dist/tui/testing/spy-host.d.ts +21 -0
  701. package/dist/tui/testing/spy-host.js +55 -0
  702. package/dist/tui/trace-groups.d.ts +43 -0
  703. package/dist/tui/trace-groups.js +554 -0
  704. package/dist/tui/wordmark.d.ts +18 -0
  705. package/dist/tui/wordmark.js +208 -0
  706. package/dist/types.d.ts +503 -0
  707. package/dist/types.js +4 -0
  708. package/dist/update/index.d.ts +61 -0
  709. package/dist/update/index.js +267 -0
  710. package/dist/variant/thinking-level.d.ts +5 -0
  711. package/dist/variant/thinking-level.js +26 -0
  712. package/dist/variant/variant-resolver.d.ts +28 -0
  713. package/dist/variant/variant-resolver.js +62 -0
  714. package/node_modules/@bubblebrain-ai/pi-tui/LICENSE +21 -0
  715. package/node_modules/@bubblebrain-ai/pi-tui/README.md +854 -0
  716. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts +26 -0
  717. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.d.ts.map +1 -0
  718. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js +121 -0
  719. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/alt-screen-search.js.map +1 -0
  720. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts +86 -0
  721. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.d.ts.map +1 -0
  722. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js +649 -0
  723. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/autocomplete.js.map +1 -0
  724. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts +13 -0
  725. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.d.ts.map +1 -0
  726. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js +36 -0
  727. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/alt-screen-flash.js.map +1 -0
  728. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts +22 -0
  729. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.d.ts.map +1 -0
  730. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js +99 -0
  731. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/box.js.map +1 -0
  732. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts +22 -0
  733. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.d.ts.map +1 -0
  734. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js +36 -0
  735. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/cancellable-loader.js.map +1 -0
  736. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts +330 -0
  737. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.d.ts.map +1 -0
  738. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js +2344 -0
  739. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/editor.js.map +1 -0
  740. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts +7 -0
  741. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.d.ts.map +1 -0
  742. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js +43 -0
  743. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/h-stack.js.map +1 -0
  744. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts +28 -0
  745. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.d.ts.map +1 -0
  746. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js +82 -0
  747. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/image.js.map +1 -0
  748. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts +48 -0
  749. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.d.ts.map +1 -0
  750. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js +390 -0
  751. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/input.js.map +1 -0
  752. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts +31 -0
  753. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.d.ts.map +1 -0
  754. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js +67 -0
  755. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/loader.js.map +1 -0
  756. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts +103 -0
  757. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.d.ts.map +1 -0
  758. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js +797 -0
  759. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/markdown.js.map +1 -0
  760. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts +56 -0
  761. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.d.ts.map +1 -0
  762. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js +179 -0
  763. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/scroll-view.js.map +1 -0
  764. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts +53 -0
  765. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.d.ts.map +1 -0
  766. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js +175 -0
  767. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/select-list.js.map +1 -0
  768. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts +50 -0
  769. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.d.ts.map +1 -0
  770. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js +174 -0
  771. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/settings-list.js.map +1 -0
  772. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts +12 -0
  773. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.d.ts.map +1 -0
  774. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js +22 -0
  775. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/spacer.js.map +1 -0
  776. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts +32 -0
  777. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.d.ts.map +1 -0
  778. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js +109 -0
  779. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/stack.js.map +1 -0
  780. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts +19 -0
  781. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.d.ts.map +1 -0
  782. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js +81 -0
  783. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/text.js.map +1 -0
  784. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts +13 -0
  785. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.d.ts.map +1 -0
  786. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js +48 -0
  787. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/truncated-text.js.map +1 -0
  788. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts +8 -0
  789. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.d.ts.map +1 -0
  790. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js +26 -0
  791. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/components/v-stack.js.map +1 -0
  792. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts +39 -0
  793. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.d.ts.map +1 -0
  794. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js +2 -0
  795. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/editor-component.js.map +1 -0
  796. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts +16 -0
  797. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.d.ts.map +1 -0
  798. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js +110 -0
  799. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/fuzzy.js.map +1 -0
  800. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts +31 -0
  801. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.d.ts.map +1 -0
  802. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js +42 -0
  803. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/index.js.map +1 -0
  804. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts +273 -0
  805. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.d.ts.map +1 -0
  806. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js +231 -0
  807. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keybindings.js.map +1 -0
  808. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts +184 -0
  809. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.d.ts.map +1 -0
  810. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js +1174 -0
  811. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/keys.js.map +1 -0
  812. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts +28 -0
  813. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.d.ts.map +1 -0
  814. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js +46 -0
  815. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/kill-ring.js.map +1 -0
  816. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts +10 -0
  817. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.d.ts.map +1 -0
  818. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js +1268 -0
  819. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/latex.js.map +1 -0
  820. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts +40 -0
  821. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.d.ts.map +1 -0
  822. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js +6 -0
  823. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout-node.js.map +1 -0
  824. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts +42 -0
  825. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.d.ts.map +1 -0
  826. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js +335 -0
  827. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/layout.js.map +1 -0
  828. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts +3 -0
  829. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.d.ts.map +1 -0
  830. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js +60 -0
  831. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/native-modifiers.js.map +1 -0
  832. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts +56 -0
  833. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.d.ts.map +1 -0
  834. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js +363 -0
  835. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/stdin-buffer.js.map +1 -0
  836. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts +10 -0
  837. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.d.ts.map +1 -0
  838. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js +59 -0
  839. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-colors.js.map +1 -0
  840. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts +116 -0
  841. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.d.ts.map +1 -0
  842. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js +518 -0
  843. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal-image.js.map +1 -0
  844. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts +142 -0
  845. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.d.ts.map +1 -0
  846. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js +544 -0
  847. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/terminal.js.map +1 -0
  848. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts +11 -0
  849. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.d.ts.map +1 -0
  850. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js +11 -0
  851. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/testing.js.map +1 -0
  852. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts +141 -0
  853. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.d.ts.map +1 -0
  854. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js +1340 -0
  855. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-alt-screen.js.map +1 -0
  856. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts +39 -0
  857. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.d.ts.map +1 -0
  858. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js +536 -0
  859. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui-main-screen.js.map +1 -0
  860. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts +340 -0
  861. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.d.ts.map +1 -0
  862. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js +1020 -0
  863. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/tui.js.map +1 -0
  864. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts +17 -0
  865. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.d.ts.map +1 -0
  866. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js +27 -0
  867. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/undo-stack.js.map +1 -0
  868. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts +96 -0
  869. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.d.ts.map +1 -0
  870. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js +1193 -0
  871. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/utils.js.map +1 -0
  872. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts +25 -0
  873. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.d.ts.map +1 -0
  874. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js +96 -0
  875. package/node_modules/@bubblebrain-ai/pi-tui/dist/src/word-navigation.js.map +1 -0
  876. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts +73 -0
  877. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.d.ts.map +1 -0
  878. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js +188 -0
  879. package/node_modules/@bubblebrain-ai/pi-tui/dist/test/virtual-terminal.js.map +1 -0
  880. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/README.md +20 -0
  881. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/build.sh +62 -0
  882. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-arm64/darwin-modifiers.node +0 -0
  883. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/prebuilds/darwin-x64/darwin-modifiers.node +0 -0
  884. package/node_modules/@bubblebrain-ai/pi-tui/native/darwin/src/darwin-modifiers.c +70 -0
  885. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/README.md +22 -0
  886. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/build.mjs +229 -0
  887. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  888. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  889. package/node_modules/@bubblebrain-ai/pi-tui/native/win32/src/win32-console-mode.c +119 -0
  890. package/node_modules/@bubblebrain-ai/pi-tui/package.json +66 -0
  891. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  892. package/node_modules/get-east-asian-width/index.js +30 -0
  893. package/node_modules/get-east-asian-width/license +9 -0
  894. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  895. package/node_modules/get-east-asian-width/lookup.js +138 -0
  896. package/node_modules/get-east-asian-width/package.json +71 -0
  897. package/node_modules/get-east-asian-width/readme.md +65 -0
  898. package/node_modules/get-east-asian-width/utilities.js +24 -0
  899. package/node_modules/marked/LICENSE +44 -0
  900. package/node_modules/marked/README.md +115 -0
  901. package/node_modules/marked/bin/main.js +284 -0
  902. package/node_modules/marked/bin/marked.js +16 -0
  903. package/node_modules/marked/lib/marked.d.ts +760 -0
  904. package/node_modules/marked/lib/marked.esm.js +77 -0
  905. package/node_modules/marked/lib/marked.esm.js.map +7 -0
  906. package/node_modules/marked/lib/marked.umd.js +79 -0
  907. package/node_modules/marked/lib/marked.umd.js.map +7 -0
  908. package/node_modules/marked/man/marked.1 +113 -0
  909. package/node_modules/marked/man/marked.1.md +93 -0
  910. package/node_modules/marked/package.json +103 -0
  911. package/package.json +96 -17
  912. package/bin/bubble.js +0 -39
@@ -0,0 +1,1659 @@
1
+ /**
2
+ * Bubble production TUI on the vendored pi-tui renderer.
3
+ *
4
+ * Owns Bubble's terminal product surface: transcript (user cards, assistant
5
+ * text, reasoning, tool traces), composer
6
+ * with native history, queue/steer while running, status footer, blocking
7
+ * overlays (plan approval, tool approval, questions), model/theme pickers,
8
+ * task wake notices, and clean terminal lifecycle on exit.
9
+ *
10
+ * All runtime logic flows through BubbleTuiController + OverlayRequestController;
11
+ * this file owns rendering and input routing only.
12
+ */
13
+ import { classifyExternalRuntimeBinding } from "../external-runtime/session-policy.js";
14
+ import process from "node:process";
15
+ import { ProcessTerminal, TuiAltScreen, TuiMainScreen, VStack, Editor, Input, ScrollView, Markdown, matchesKey, isKeyRelease, isViewportTUI, } from "@bubblebrain-ai/pi-tui";
16
+ import { StreamingMessageComponent } from "./components/streaming-message.js";
17
+ import { ResponsiveTranscriptComponent } from "./components/responsive-transcript.js";
18
+ import { WelcomeBannerComponent } from "./components/welcome.js";
19
+ import { ApprovalDialogComponent } from "./components/approval-dialog.js";
20
+ import { PlanDialogComponent } from "./components/plan-dialog.js";
21
+ import { FeedbackDialogComponent } from "./components/feedback-dialog.js";
22
+ import { FeishuSetupDialogComponent } from "./components/feishu-setup-dialog.js";
23
+ import { QuestionDialogComponent } from "./components/question-dialog.js";
24
+ import { ProviderKeyInputComponent } from "./components/provider-key-input.js";
25
+ import { RewindPickerComponent, } from "./components/rewind-picker.js";
26
+ import { TaskStatusBarComponent, TasksPaneComponent, } from "./components/tasks-pane.js";
27
+ import { WorkflowInspectorComponent } from "./components/workflow-inspector.js";
28
+ import { SubagentInspectorComponent } from "./components/subagent-inspector.js";
29
+ import { TaskInspectorComponent } from "./components/task-inspector.js";
30
+ import { ContextInfoComponent } from "./components/context-info.js";
31
+ import { StatsPanelComponent } from "./components/stats-panel.js";
32
+ import { SkillsPanelComponent } from "./components/skills-panel.js";
33
+ import { SessionPickerComponent } from "./components/session-picker.js";
34
+ import { ComposerImagePreviewComponent, ImageViewerComponent } from "./components/image-preview.js";
35
+ import { registry as slashRegistry } from "../slash-commands/index.js";
36
+ import { syncSystemPrompt } from "../slash-commands/commands.js";
37
+ import { collectUsageStatsBundle } from "../stats/usage.js";
38
+ import { ComposerController, } from "./controller/composer-controller.js";
39
+ import { OverlayRequestController } from "./controller/overlay-controller.js";
40
+ import { createTranscriptTheme, projectTranscript } from "./components/transcript.js";
41
+ import { friendlyCwd, sessionBasename } from "./formatting/summary.js";
42
+ import { formatExternalRuntimeFooterLabel, ResponsiveFooterComponent } from "./footer.js";
43
+ import { ComposerAutocompleteProvider } from "./composer-autocomplete.js";
44
+ import { COMPOSER_EDITOR_OPTIONS, createComposerEditorTheme } from "./composer-style.js";
45
+ import { SessionManager } from "../session.js";
46
+ import { collectFeedback } from "../feedback/collect.js";
47
+ import { TraceInteractionState } from "./model/trace-interaction.js";
48
+ import { parseSkillInvocation } from "../skills/invocation.js";
49
+ import { getNextPermissionMode } from "../permission/mode.js";
50
+ import { maskKey } from "../config.js";
51
+ import { copyToClipboard } from "../clipboard.js";
52
+ import { displayImagesFromPayload, formatImageUserDisplayText, nextImageDisplayLabelStart } from "./image-display.js";
53
+ import { createAssistantMarkdownTheme } from "./markdown-style.js";
54
+ import { resolveThemePalette, } from "./model/theme.js";
55
+ import { themeBackgroundCodes, themeDim, themeForeground } from "./model/theme-style.js";
56
+ import { TuiAnimationClock } from "./animation-clock.js";
57
+ export class PiTuiApp {
58
+ options;
59
+ tui;
60
+ editor;
61
+ composer;
62
+ composerPreview;
63
+ composerSlot = new VStack([]);
64
+ viewportScroll = null;
65
+ transcriptBox = new VStack([]);
66
+ streamingMessage;
67
+ markdown;
68
+ markdownRenderer = (text, width) => {
69
+ this.markdown.setText(text);
70
+ return this.markdown.render(width);
71
+ };
72
+ settledTranscript;
73
+ welcome;
74
+ footer;
75
+ tasksPane;
76
+ taskStatusBar;
77
+ animationClock;
78
+ traceInteraction = new TraceInteractionState();
79
+ overlays;
80
+ themeMode;
81
+ detectedTheme;
82
+ themeOverrides;
83
+ theme;
84
+ transcriptTheme;
85
+ gitBranch;
86
+ metadataManager = null;
87
+ metadataUnsubscribe = null;
88
+ currentUpdateNotice;
89
+ updateNoticeRefreshStarted = false;
90
+ showReasoning = false;
91
+ verboseTrace = false;
92
+ controllerUnsubscribe = null;
93
+ questionUnsubscribe = null;
94
+ activeQuestion = null;
95
+ questionQueue = [];
96
+ providerKeyPhase = null;
97
+ rewindPhase = null;
98
+ rewindPreviewMessageIndex;
99
+ feishuSetupPhase = null;
100
+ disposed = false;
101
+ /** Compatibility/debug view backed by the canonical persistent history. */
102
+ get history() {
103
+ return this.composer.historyTexts();
104
+ }
105
+ constructor(options) {
106
+ this.options = options;
107
+ const terminal = options.terminal ?? new ProcessTerminal();
108
+ this.currentUpdateNotice = options.updateNotice;
109
+ this.themeMode = options.themeMode ?? "auto";
110
+ this.detectedTheme = options.detectedTheme
111
+ ?? (this.themeMode === "auto" ? "dark" : this.themeMode);
112
+ this.themeOverrides = options.themeOverrides;
113
+ this.theme = resolveThemePalette(this.themeMode, this.detectedTheme, this.themeOverrides).palette;
114
+ this.transcriptTheme = createTranscriptTheme(() => this.theme);
115
+ // Pick the renderer before start(). Entering fullscreen after the regular
116
+ // renderer has painted necessarily exposes a main-screen frame first.
117
+ // Constructing TuiAltScreen here makes its 1049h transition the first UI
118
+ // write and keeps the entire product surface on one application instance.
119
+ this.tui = (options.uiMode ?? "fullscreen") === "fullscreen"
120
+ ? new TuiAltScreen(terminal, undefined, undefined, {
121
+ mouseMotion: "all",
122
+ screenBackground: () => themeBackgroundCodes(this.theme.background),
123
+ })
124
+ : new TuiMainScreen(terminal);
125
+ this.markdown = new Markdown("", 0, 0, createAssistantMarkdownTheme(() => this.theme));
126
+ this.streamingMessage = new StreamingMessageComponent(8, () => this.tui.requestRender(), (action) => this.handleTraceAction(action), () => this.theme);
127
+ this.editor = new Editor(this.tui, createComposerEditorTheme(() => this.theme), COMPOSER_EDITOR_OPTIONS);
128
+ this.composer = new ComposerController({
129
+ editor: this.editor,
130
+ scope: {
131
+ sessionFile: this.activeSessionManager().getSessionFile(),
132
+ cwd: process.cwd(),
133
+ },
134
+ nextImageLabelStart: nextImageDisplayLabelStart(options.controller.getTranscript()),
135
+ onSubmit: (payload) => this.handleSubmit(payload),
136
+ onNotice: (message) => this.pushNotice(message),
137
+ onStateChange: () => this.renderSnapshot(),
138
+ onOpenImage: (item) => this.openImageViewer(item),
139
+ allowImageAttachments: () => !this.activeSessionManager().getMetadata?.().externalRuntime,
140
+ readClipboardImage: options.readClipboardImage,
141
+ historyFilePath: options.inputHistoryFilePath,
142
+ getTheme: () => this.theme,
143
+ // Injected terminals are isolated hosts and must not mutate the user's
144
+ // real ~/.bubble history unless they explicitly provide a history file.
145
+ persistHistory: options.terminal === undefined || options.inputHistoryFilePath !== undefined,
146
+ });
147
+ this.composerPreview = new ComposerImagePreviewComponent(() => this.composer.previewAttachment(), () => this.theme);
148
+ this.editor.setAutocompleteProvider(new ComposerAutocompleteProvider({
149
+ cwd: process.cwd(),
150
+ commands: () => slashRegistry.list(),
151
+ skills: () => this.options.skillRegistry?.summaries() ?? [],
152
+ uiMode: () => this.tui.mode,
153
+ registry: this.options.registry,
154
+ thinkingLevel: () => this.options.agent.thinking,
155
+ providerId: () => this.options.agent.providerId,
156
+ themeMode: () => this.themeMode,
157
+ detectedTheme: () => this.detectedTheme,
158
+ grokRuntimeActive: () => classifyExternalRuntimeBinding(this.activeSessionManager().getMetadata?.().externalRuntime) === "grok",
159
+ onModelSuggestionsChanged: () => {
160
+ if (!this.disposed)
161
+ this.editor.refreshAutocomplete();
162
+ },
163
+ }));
164
+ this.settledTranscript = new ResponsiveTranscriptComponent(() => ({
165
+ messages: this.options.controller.getTranscript(),
166
+ options: this.transcriptRenderOptions(),
167
+ }), { onTraceAction: (action) => this.handleTraceAction(action) });
168
+ this.welcome = new WelcomeBannerComponent(() => {
169
+ const { agent } = this.options;
170
+ return {
171
+ cwd: friendlyCwd(process.cwd()),
172
+ session: sessionBasename(this.activeSessionManager().getSessionFile()),
173
+ model: agent.model,
174
+ provider: agent.providerId,
175
+ thinking: agent.thinking,
176
+ updateNotice: this.currentUpdateNotice,
177
+ theme: this.theme,
178
+ };
179
+ });
180
+ this.footer = new ResponsiveFooterComponent(() => {
181
+ const extra = [];
182
+ const steerCount = this.options.controller.pendingSteerCount();
183
+ const queuedCount = this.options.controller.queuedInputCount();
184
+ const pendingImages = this.composer.pendingImageCount();
185
+ if (steerCount)
186
+ extra.push(themeForeground(this.theme.warning, `steer ×${steerCount}`));
187
+ if (queuedCount)
188
+ extra.push(themeForeground(this.theme.warning, `queue ×${queuedCount}`));
189
+ if (pendingImages)
190
+ extra.push(themeDim(this.theme.dim, `reading image ×${pendingImages}`));
191
+ const session = this.activeSessionManager();
192
+ const metadata = typeof session?.getMetadata === "function" ? session.getMetadata() : undefined;
193
+ const runtimeLabel = formatExternalRuntimeFooterLabel(metadata?.externalRuntime);
194
+ return {
195
+ agent: this.options.agent,
196
+ cwd: friendlyCwd(process.cwd()),
197
+ branch: this.gitBranch,
198
+ sessionTitle: runtimeLabel ? undefined : metadata?.title,
199
+ runtimeLabel,
200
+ extra,
201
+ mode: this.options.agent.mode,
202
+ goalLine: runtimeLabel ? undefined : this.options.controller.getGoalIndicator?.(),
203
+ theme: this.theme,
204
+ // At two rows or fewer, preserve the focused editor body + border.
205
+ hidden: this.tui.terminal.rows <= 2,
206
+ };
207
+ });
208
+ this.tasksPane = new TasksPaneComponent(() => ({
209
+ groups: this.options.controller.getSubagentGroups?.() ?? [],
210
+ workflows: this.options.controller.getWorkflows?.() ?? [],
211
+ tasks: this.options.controller.getBackgroundTasks?.() ?? [],
212
+ turnStartedAt: this.options.controller.getTurnStartedAt?.(),
213
+ }), () => this.tui.terminal.rows, {
214
+ // Pane navigation only changes local presentation state. Reprojecting
215
+ // the live trace/Markdown here is both unnecessary and expensive.
216
+ onRender: () => this.tui.requestRender(),
217
+ onOpenWorkflow: (item) => this.openWorkflowInspector(item),
218
+ onOpenSubagent: (item) => this.openSubagentInspector(item),
219
+ onOpenTask: (item) => this.openTaskInspector(item),
220
+ onStopWorkflow: (id) => this.options.controller.stopWorkflow(id),
221
+ onStopSubagent: (id) => this.options.controller.stopSubagent(id),
222
+ onStopTask: (id) => this.options.controller.stopBackgroundTask(id),
223
+ onCopyTaskOutput: (id) => {
224
+ const output = this.options.controller.getBackgroundTaskOutput(id);
225
+ if (!output)
226
+ return;
227
+ void copyToClipboard(output).then(() => this.pushNotice("Copied task output."));
228
+ },
229
+ onEscape: () => {
230
+ this.tasksPane.focused = false;
231
+ this.tui.setFocus(this.editor);
232
+ this.tui.requestRender();
233
+ },
234
+ }, () => this.theme);
235
+ this.animationClock = new TuiAnimationClock((elapsedMs) => {
236
+ const streamingChanged = this.streamingMessage.advanceAnimationFrame(elapsedMs);
237
+ const tasksChanged = this.tasksPane.advanceAnimationFrame();
238
+ if (streamingChanged || tasksChanged)
239
+ this.tui.requestRender();
240
+ });
241
+ this.taskStatusBar = new TaskStatusBarComponent(this.tasksPane);
242
+ this.overlays = new OverlayRequestController({ questionController: options.questionController });
243
+ this.syncMetadataSubscription();
244
+ this.installBlockingHandlers();
245
+ this.buildLayout();
246
+ this.wireInput();
247
+ this.renderSnapshot();
248
+ void options.resolveGitBranch?.(process.cwd()).then((branch) => {
249
+ if (this.disposed)
250
+ return;
251
+ this.gitBranch = branch?.trim() || undefined;
252
+ this.renderSnapshot();
253
+ }).catch(() => undefined);
254
+ }
255
+ activeSessionManager() {
256
+ return this.options.controller.getSessionManager?.() ?? this.options.sessionManager;
257
+ }
258
+ syncMetadataSubscription() {
259
+ const manager = this.activeSessionManager();
260
+ if (manager === this.metadataManager)
261
+ return;
262
+ this.metadataUnsubscribe?.();
263
+ this.metadataManager = manager;
264
+ this.metadataUnsubscribe = typeof manager?.subscribeMetadata === "function"
265
+ ? manager.subscribeMetadata(() => this.renderSnapshot())
266
+ : null;
267
+ }
268
+ installBlockingHandlers() {
269
+ const { planHandlerRef, approvalHandlerRef, questionController } = this.options;
270
+ if (planHandlerRef) {
271
+ planHandlerRef.current = async (plan) => {
272
+ return this.planDialog(plan);
273
+ };
274
+ }
275
+ if (approvalHandlerRef) {
276
+ approvalHandlerRef.current = async (request) => {
277
+ const choice = await this.approvalDialog(request);
278
+ // "This session": the approval controller records the request's
279
+ // sessionGrant (exact bash command / MCP tool) before the next dialog.
280
+ if (choice.kind === "approve_session") {
281
+ return { action: "approve", remember: "session" };
282
+ }
283
+ return choice.kind === "approve_once"
284
+ ? { action: "approve" }
285
+ : { action: "reject", feedback: "User denied the tool call." };
286
+ };
287
+ }
288
+ if (questionController) {
289
+ this.questionUnsubscribe = questionController.subscribe((event) => {
290
+ if (event.type === "asked") {
291
+ this.questionQueue.push(event.request);
292
+ this.openNextQuestion();
293
+ return;
294
+ }
295
+ this.removeQueuedQuestion(event.request.id);
296
+ if (this.activeQuestion?.id === event.request.id) {
297
+ this.activeQuestion.close();
298
+ }
299
+ });
300
+ }
301
+ }
302
+ buildLayout() {
303
+ // Keep the live component permanently after the settled transcript. It
304
+ // renders zero rows while idle. Appending it per turn eventually placed
305
+ // the same component reference in the tree multiple times and let settled
306
+ // rows land after it, breaking spacing/order on later turns.
307
+ this.transcriptBox.addChild(this.settledTranscript);
308
+ this.transcriptBox.addChild(this.streamingMessage);
309
+ this.composerSlot.addChild(this.composerPreview);
310
+ this.composerSlot.addChild(this.editor);
311
+ if (isViewportTUI(this.tui)) {
312
+ // Fullscreen owns a bounded viewport. History scrolls while the composer
313
+ // and footer stay docked. The activity lane is a permanent one-row
314
+ // boundary: spinner text changes, its geometry never does.
315
+ const document = new VStack([this.welcome, this.transcriptBox]);
316
+ const scroll = new ScrollView(document, { follow: "end", primary: true });
317
+ this.viewportScroll = scroll;
318
+ this.tui.setLayoutRoot(new VStack([
319
+ { component: scroll, basis: 0, grow: 1, minSize: 0 },
320
+ { component: this.taskStatusBar, basis: "auto", shrink: 0 },
321
+ { component: this.tasksPane, basis: "auto", shrink: 0 },
322
+ { component: this.streamingMessage.activityLane, basis: "auto", shrink: 0 },
323
+ { component: this.composerSlot, basis: "auto", shrink: 0 },
324
+ { component: this.footer, basis: "auto", shrink: 0 },
325
+ ]));
326
+ }
327
+ else {
328
+ this.tui.addChild(this.welcome);
329
+ this.tui.addChild(this.transcriptBox);
330
+ this.tui.addChild(this.taskStatusBar);
331
+ this.tui.addChild(this.tasksPane);
332
+ this.tui.addChild(this.streamingMessage.activityLane);
333
+ this.tui.addChild(this.composerSlot);
334
+ this.tui.addChild(this.footer);
335
+ }
336
+ this.tui.setFocus(this.editor);
337
+ this.controllerUnsubscribe = this.options.controller.subscribe(() => {
338
+ this.syncMetadataSubscription();
339
+ this.composer.setScope({
340
+ sessionFile: this.activeSessionManager().getSessionFile(),
341
+ cwd: process.cwd(),
342
+ }, nextImageDisplayLabelStart(this.options.controller.getTranscript()));
343
+ this.renderSnapshot();
344
+ });
345
+ }
346
+ wireInput() {
347
+ let ctrlCArmed = false;
348
+ let rewindEscapeArmed = false;
349
+ this.tui.addInputListener((data) => {
350
+ // Global shortcuts act on key presses only. Kitty keyboard protocol also
351
+ // reports releases; allowing those through can trigger a shortcut twice.
352
+ if (isKeyRelease(data))
353
+ return { consume: true };
354
+ // Modal dialogs own their complete keyboard contract. In particular,
355
+ // an approval arrives while a run is active, so letting the global
356
+ // Escape/Ctrl+C handler run first would cancel the agent and strand the
357
+ // unresolved permission request instead of rejecting the dialog.
358
+ if (this.tui.hasOverlay())
359
+ return undefined;
360
+ // The inline credential phase owns all of its input. In particular,
361
+ // Escape returns to /provider instead of cancelling an unrelated run,
362
+ // and Shift+Tab must not change permissions while a key is being typed.
363
+ if (this.providerKeyPhase)
364
+ return undefined;
365
+ if (this.rewindPhase)
366
+ return undefined;
367
+ if (this.editor.focused
368
+ && (matchesKey(data, "ctrl+v") || matchesKey(data, "super+v"))) {
369
+ // A terminal can only bracket text that it receives from the clipboard.
370
+ // Image-only Cmd+V is therefore silent in some terminals; Ctrl+V (and
371
+ // Kitty's super+v when available) gives the composer an explicit,
372
+ // terminal-independent signal to probe the native image clipboard.
373
+ this.composer.requestClipboardImagePaste();
374
+ return { consume: true };
375
+ }
376
+ if (matchesKey(data, "ctrl+g")) {
377
+ if (this.tasksPane.isOpen() && this.tasksPane.focused) {
378
+ this.tasksPane.close();
379
+ this.tasksPane.focused = false;
380
+ this.tui.setFocus(this.editor);
381
+ }
382
+ else {
383
+ if (!this.tasksPane.isOpen())
384
+ this.tasksPane.toggle(true);
385
+ this.tasksPane.focused = true;
386
+ this.tui.setFocus(this.tasksPane);
387
+ }
388
+ this.renderSnapshot();
389
+ return { consume: true };
390
+ }
391
+ if (matchesKey(data, "ctrl+b") && this.options.controller.isRunning()) {
392
+ // Current Grok contract: Ctrl+B transfers a live Execute into Tasks;
393
+ // Ctrl+G remains dedicated to opening and closing the Tasks Pane.
394
+ this.options.controller.promoteActiveBash?.();
395
+ return { consume: true };
396
+ }
397
+ // A focused Tasks Pane owns Escape. It returns focus to the composer;
398
+ // it must never fall through and cancel the running parent Agent.
399
+ if (this.tasksPane.focused && matchesKey(data, "escape"))
400
+ return undefined;
401
+ if (matchesKey(data, "shift+tab")) {
402
+ this.options.agent.setMode(getNextPermissionMode(this.options.agent.mode));
403
+ this.renderSnapshot();
404
+ return { consume: true };
405
+ }
406
+ if (matchesKey(data, "escape") && this.options.controller.cancelActiveRun()) {
407
+ // Ink parity: Escape interrupts an active run. When idle, leave the
408
+ // key unconsumed so the editor/autocomplete layer can handle it.
409
+ return { consume: true };
410
+ }
411
+ if (matchesKey(data, "escape") && this.options.controller.isBusy?.()) {
412
+ // A second Escape while the provider is acknowledging Compact
413
+ // cancellation must not arm rewind or leak into the editor.
414
+ return { consume: true };
415
+ }
416
+ if (matchesKey(data, "escape") && !this.options.controller.isRunning() && !this.editor.getText()) {
417
+ if (rewindEscapeArmed) {
418
+ rewindEscapeArmed = false;
419
+ this.openRewindPicker();
420
+ return { consume: true };
421
+ }
422
+ rewindEscapeArmed = true;
423
+ setTimeout(() => { rewindEscapeArmed = false; }, 650);
424
+ return undefined;
425
+ }
426
+ rewindEscapeArmed = false;
427
+ if (matchesKey(data, "ctrl+c")) {
428
+ // Match Ink: the first Ctrl+C interrupts the active run. Requiring a
429
+ // second keypress here made cancellation feel like a quit gesture and
430
+ // left the tool trace running behind the composer.
431
+ if (this.options.controller.cancelActiveRun()) {
432
+ ctrlCArmed = false;
433
+ return { consume: true };
434
+ }
435
+ if (this.options.controller.isBusy?.())
436
+ return { consume: true };
437
+ if (ctrlCArmed) {
438
+ this.dispose();
439
+ return { consume: true };
440
+ }
441
+ ctrlCArmed = true;
442
+ setTimeout(() => {
443
+ ctrlCArmed = false;
444
+ }, 1_500);
445
+ return { consume: true };
446
+ }
447
+ if (data === "\x14") {
448
+ this.showReasoning = !this.showReasoning;
449
+ this.renderSnapshot();
450
+ return { consume: true };
451
+ }
452
+ if (data === "\x0f") {
453
+ this.verboseTrace = !this.verboseTrace;
454
+ this.renderSnapshot();
455
+ return { consume: true };
456
+ }
457
+ return undefined;
458
+ });
459
+ }
460
+ handleSubmit(input) {
461
+ const payload = typeof input === "string"
462
+ ? { text: input, images: [] }
463
+ : input;
464
+ const trimmed = payload.text.trim();
465
+ if (!trimmed && payload.images.length === 0)
466
+ return;
467
+ // Passive output preserves a user's manual history position, but an
468
+ // explicit Composer submission changes their point of attention to the
469
+ // new turn. Re-enable follow-end before appending the user row so the same
470
+ // layout pass that renders it also brings it into view.
471
+ this.viewportScroll?.scrollToEnd();
472
+ if (trimmed.startsWith("/")) {
473
+ void this.handleCommand(trimmed);
474
+ return;
475
+ }
476
+ const controller = this.options.controller;
477
+ if (controller.isRunning()) {
478
+ // AgentRunInputQueue is intentionally text-only. Image payloads retain
479
+ // their bytes by waiting for the next provider turn instead of degrading
480
+ // into a text steer.
481
+ if (payload.images.length > 0)
482
+ controller.queueInput(payload);
483
+ else if (!controller.steer(trimmed))
484
+ controller.queueInput(payload);
485
+ this.renderSnapshot();
486
+ }
487
+ else if (controller.queueAfterCommand?.(payload)) {
488
+ this.renderSnapshot();
489
+ }
490
+ else {
491
+ this.pushUserRow(payload);
492
+ const agentInput = payload.images.length > 0 ? payload : trimmed;
493
+ void controller.runTurn(agentInput, process.cwd()).finally(() => this.renderSnapshot());
494
+ }
495
+ }
496
+ async handleCommand(command) {
497
+ const [name, ...rest] = command.slice(1).split(/\s+/);
498
+ const args = rest.join(" ");
499
+ // pi-tui-local commands first (renderer-specific modes).
500
+ if (name === "fullscreen") {
501
+ if (this.fullscreen) {
502
+ this.exitFullscreen();
503
+ return;
504
+ }
505
+ if (this.tui.mode === "fullscreen") {
506
+ this.pushNotice("Already in fullscreen mode");
507
+ this.renderSnapshot();
508
+ return;
509
+ }
510
+ void this.enterFullscreen();
511
+ return;
512
+ }
513
+ const skillInvocation = this.options.skillRegistry
514
+ ? parseSkillInvocation(command, this.options.skillRegistry)
515
+ : undefined;
516
+ if (skillInvocation) {
517
+ this.pushUserRow({ text: command, images: [] });
518
+ const controller = this.options.controller;
519
+ if (controller.isRunning()) {
520
+ if (!controller.steer(skillInvocation.actualPrompt)) {
521
+ controller.queueInput(skillInvocation.actualPrompt);
522
+ }
523
+ this.renderSnapshot();
524
+ }
525
+ else {
526
+ void controller.runTurn(skillInvocation.actualPrompt, process.cwd()).finally(() => this.renderSnapshot());
527
+ }
528
+ return;
529
+ }
530
+ // Everything else routes through the shared registry (21 builtin
531
+ // commands + MCP dynamic prompts + skill fallback) — the same surface
532
+ // the Ink TUI exposed.
533
+ const compactCommand = name === "compact";
534
+ let compactSignal;
535
+ try {
536
+ if (compactCommand)
537
+ compactSignal = this.options.controller.beginCommandActivity?.("compact");
538
+ const ctx = this.buildSlashContext(compactSignal);
539
+ const outcome = await slashRegistry.execute(`/${name}${args ? ` ${args}` : ""}`, ctx);
540
+ if (compactCommand) {
541
+ const text = outcome.result ?? (outcome.handled ? undefined : `Unknown command: /${name}`);
542
+ const compactionSummary = outcome.detail?.kind === "compaction-summary"
543
+ ? outcome.detail.content.trim()
544
+ : "";
545
+ this.options.controller.finishCommandActivity?.("compact", text ? {
546
+ key: `compact-${Date.now()}`,
547
+ role: text.startsWith("Error:") ? "error" : "assistant",
548
+ content: text,
549
+ syntheticKind: text.startsWith("Error:")
550
+ ? undefined
551
+ : compactionSummary
552
+ ? "ui_compact_summary"
553
+ : "ui_notice",
554
+ compactionSummary: compactionSummary || undefined,
555
+ } : undefined);
556
+ void this.options.controller.drainQueuedInputs?.(process.cwd());
557
+ return;
558
+ }
559
+ if (outcome.inject) {
560
+ // Command produced model-facing input (e.g. /rewind restore).
561
+ void this.handleSubmit(outcome.inject);
562
+ }
563
+ else if (outcome.result) {
564
+ this.pushNotice(outcome.result);
565
+ }
566
+ else if (!outcome.handled) {
567
+ this.pushNotice(`Unknown command: /${name}`);
568
+ }
569
+ }
570
+ catch (error) {
571
+ const text = error instanceof Error ? error.message : String(error);
572
+ if (compactCommand && compactSignal) {
573
+ this.options.controller.finishCommandActivity?.("compact", {
574
+ key: `compact-error-${Date.now()}`,
575
+ role: "error",
576
+ content: text,
577
+ });
578
+ }
579
+ else {
580
+ this.pushNotice(text);
581
+ }
582
+ }
583
+ finally {
584
+ if (compactSignal && this.options.controller.getCommandActivity?.()?.kind === "compact") {
585
+ this.options.controller.finishCommandActivity?.("compact");
586
+ }
587
+ this.renderSnapshot();
588
+ }
589
+ }
590
+ buildSlashContext(compactionAbortSignal) {
591
+ const options = this.options;
592
+ const agent = options.agent;
593
+ const addMessage = (role, content) => {
594
+ options.controller.appendDisplayMessage({
595
+ key: `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
596
+ role,
597
+ content,
598
+ syntheticKind: role === "assistant" ? "ui_notice" : undefined,
599
+ });
600
+ };
601
+ return {
602
+ agent,
603
+ addMessage,
604
+ clearMessages: () => options.controller.clearTranscript(),
605
+ cwd: process.cwd(),
606
+ exit: () => this.dispose(),
607
+ // Embedded/test hosts created before session switching may only expose
608
+ // the constructor manager; production controllers provide the live one.
609
+ sessionManager: (options.controller.getSessionManager?.() ?? options.sessionManager),
610
+ createProvider: options.createProvider,
611
+ openPicker: (mode, providerId) => {
612
+ if (mode === "model" || mode === "provider" || mode === "theme") {
613
+ // A bare picker command can still arrive from a fast Enter or a
614
+ // non-composer command host. Keep pi-tui on the shared inline
615
+ // command surface instead of falling back to a centered overlay.
616
+ this.composer.replaceDraft(mode === "model" ? "/model " : mode === "provider" ? "/provider " : "/theme ");
617
+ this.editor.refreshAutocomplete();
618
+ }
619
+ else if (mode === "login" || mode === "logout") {
620
+ // Same inline surface as /model: the account list appears as the
621
+ // command's argument menu, so nothing is chosen for the user.
622
+ this.composer.replaceDraft(mode === "login" ? "/login " : "/logout ");
623
+ this.editor.refreshAutocomplete();
624
+ }
625
+ else if (mode === "key" && providerId) {
626
+ this.openProviderKeyPhase(providerId);
627
+ }
628
+ else if (mode === "skill") {
629
+ this.openSkills();
630
+ }
631
+ else if (mode === "feishu-setup") {
632
+ this.openFeishuSetup();
633
+ }
634
+ },
635
+ registry: options.registry,
636
+ skillRegistry: options.skillRegistry,
637
+ bashAllowlist: options.bashAllowlist,
638
+ settingsManager: options.settingsManager,
639
+ hookController: options.hookController,
640
+ mcpManager: options.mcpManager,
641
+ lspService: options.lspService,
642
+ flushMemory: options.flushMemory,
643
+ runMemoryCompaction: options.runMemoryCompaction,
644
+ runMemorySummary: options.runMemorySummary,
645
+ runMemoryRefresh: options.runMemoryRefresh,
646
+ getThemeMode: () => this.themeMode,
647
+ getResolvedTheme: () => this.resolvedTheme(),
648
+ setThemeMode: (mode) => this.applyThemeMode(mode),
649
+ openFeedback: (initialDescription) => this.openFeedback(initialDescription),
650
+ openStats: () => this.openStats(),
651
+ ...(this.tui.mode === "fullscreen"
652
+ ? { openContextInfo: (snapshot) => this.openContextInfo(snapshot) }
653
+ : {}),
654
+ handleGoalCommand: (input) => options.controller.handleGoalCommand(input, process.cwd()),
655
+ openRewindPicker: () => this.openRewindPicker(),
656
+ fillComposer: (text) => {
657
+ this.composer.replaceDraft(text);
658
+ this.tui.setFocus(this.editor);
659
+ },
660
+ rebuildTranscript: () => options.controller.rebuildTranscriptFromAgent(),
661
+ openSessionPicker: () => this.openSessionPicker(),
662
+ compactionProgress: () => { },
663
+ compactionAbortSignal,
664
+ };
665
+ }
666
+ pushUserRow(payload) {
667
+ this.appendTranscriptRow({
668
+ key: `user-${Date.now()}`,
669
+ role: "user",
670
+ content: formatImageUserDisplayText(payload.displayText ?? payload.text, payload.images.length, payload.imageDisplayStart),
671
+ images: displayImagesFromPayload(payload),
672
+ });
673
+ }
674
+ resolvedTheme() {
675
+ return this.themeMode === "auto" ? this.detectedTheme : this.themeMode;
676
+ }
677
+ applyThemeMode(mode) {
678
+ if (this.themeMode === mode) {
679
+ // Persist an explicit selection even when it resolves to the palette
680
+ // already on screen (e.g. auto-dark -> forced dark).
681
+ this.options.callbacks.onThemeModeChange?.(mode);
682
+ return;
683
+ }
684
+ this.themeMode = mode;
685
+ // Preserve object identity so already-open panels that received this
686
+ // palette also repaint in place.
687
+ Object.assign(this.theme, resolveThemePalette(mode, this.detectedTheme, this.themeOverrides).palette);
688
+ this.options.callbacks.onThemeModeChange?.(mode);
689
+ this.tui.invalidate();
690
+ this.renderSnapshot();
691
+ }
692
+ pushNotice(text) {
693
+ this.appendTranscriptRow({
694
+ key: `notice-${Date.now()}`,
695
+ role: "assistant",
696
+ content: text,
697
+ syntheticKind: "ui_notice",
698
+ });
699
+ }
700
+ appendTranscriptRow(message) {
701
+ this.options.controller.appendDisplayMessage(message);
702
+ }
703
+ streamingMounted = false;
704
+ /** One display policy feeds both committed history and the live row pool. */
705
+ transcriptRenderOptions() {
706
+ return {
707
+ showReasoning: this.showReasoning,
708
+ verboseTrace: this.verboseTrace,
709
+ traceInteraction: this.traceInteraction,
710
+ dimFromMessageIndex: this.rewindPreviewMessageIndex,
711
+ theme: this.transcriptTheme,
712
+ // Stable identity lets the settled transcript cache survive composer-only
713
+ // frames. The stateful Markdown instance is still updated on cache misses.
714
+ markdownRenderer: this.markdownRenderer,
715
+ };
716
+ }
717
+ renderSnapshot() {
718
+ if (this.disposed)
719
+ return;
720
+ const columns = this.tui.terminal.columns || process.stdout.columns || 80;
721
+ this.updateStreamingRegion(columns);
722
+ this.tui.requestRender();
723
+ }
724
+ /**
725
+ * The streaming preview is a persistent row-pool component mounted as the
726
+ * transcript's LAST child (upstream AssistantMessageComponent pattern).
727
+ * Rows are only ever setText — re-creating components per frame is what
728
+ * leaked every intermediate prefix into scrollback (verified with a
729
+ * VirtualTerminal matrix). On turn end it collapses to zero rows in the
730
+ * same frame the settled message commits, so the full answer replaces the
731
+ * preview with no duplicate.
732
+ */
733
+ updateStreamingRegion(columns) {
734
+ const controller = this.options.controller;
735
+ const tail = controller.isRunning() ? controller.getStreamingTail() : null;
736
+ const commandActivity = controller.getCommandActivity?.() ?? null;
737
+ if (tail) {
738
+ if (!this.streamingMounted) {
739
+ this.streamingMounted = true;
740
+ this.streamingMessage.startSpinner();
741
+ }
742
+ this.streamingMessage.noteWidth(columns);
743
+ this.streamingMessage.update(tail, columns, this.transcriptRenderOptions());
744
+ }
745
+ else if (commandActivity) {
746
+ if (!this.streamingMounted) {
747
+ this.streamingMounted = true;
748
+ this.streamingMessage.startSpinner();
749
+ }
750
+ this.streamingMessage.noteWidth(columns);
751
+ this.streamingMessage.updateCommandActivity(commandActivity.kind === "compact" ? "Compacting" : commandActivity.kind, commandActivity.status === "cancelling", columns);
752
+ }
753
+ else if (this.streamingMounted) {
754
+ this.streamingMounted = false;
755
+ this.streamingMessage.clearToNothing();
756
+ }
757
+ this.syncAnimationClock();
758
+ }
759
+ syncAnimationClock() {
760
+ this.animationClock.setActive(this.fullscreen === null
761
+ && (this.streamingMessage.isAnimationActive() || this.tasksPane.isAnimationActive()));
762
+ }
763
+ // ---- Fullscreen mode ----------------------------------------------------
764
+ fullscreen = null;
765
+ /**
766
+ * Switch to the alternate-screen transcript view. The main screen stays
767
+ * mounted but paused (stopped renderer); both modes subscribe to the same
768
+ * controller so the transcript stays in sync. Escape / Ctrl+C / /fullscreen
769
+ * return here.
770
+ */
771
+ async enterFullscreen() {
772
+ if (this.fullscreen)
773
+ return;
774
+ this.animationClock.setActive(false);
775
+ this.tui.stop();
776
+ const { FullscreenApp } = await import("./fullscreen.js");
777
+ // The import yields; a dispose (or a second /fullscreen) racing it must not
778
+ // mount an alternate screen nobody will tear down.
779
+ if (this.disposed || this.fullscreen)
780
+ return;
781
+ this.fullscreen = new FullscreenApp({
782
+ controller: this.options.controller,
783
+ agent: this.options.agent,
784
+ onExit: () => this.exitFullscreen(),
785
+ onCommand: (command) => {
786
+ // Pickers and dialogs are owned by the main-screen TUI. Return to it
787
+ // before executing so every slash command uses the same registry and
788
+ // never leaks into the model as ordinary fullscreen input.
789
+ this.exitFullscreen();
790
+ void this.handleCommand(command);
791
+ },
792
+ terminal: this.options.terminal,
793
+ traceInteraction: this.traceInteraction,
794
+ composer: this.composer,
795
+ getTheme: () => this.theme,
796
+ });
797
+ this.fullscreen.start();
798
+ }
799
+ exitFullscreen() {
800
+ if (!this.fullscreen)
801
+ return;
802
+ this.fullscreen.dispose();
803
+ this.fullscreen = null;
804
+ this.composer.attachEditor(this.editor);
805
+ this.composer.setOpenImageHandler((item) => this.openImageViewer(item));
806
+ this.tui.start();
807
+ this.renderSnapshot();
808
+ }
809
+ // ---- Overlays -----------------------------------------------------------
810
+ openImageViewer(image) {
811
+ let handle;
812
+ const close = () => {
813
+ handle?.hide();
814
+ this.tui.setFocus(this.editor);
815
+ this.renderSnapshot();
816
+ };
817
+ const component = new ImageViewerComponent(image, close, () => this.tui.terminal.rows, () => this.theme);
818
+ handle = this.tui.showOverlay(component, {
819
+ anchor: "center",
820
+ width: "70%",
821
+ minWidth: 36,
822
+ maxWidth: 110,
823
+ maxHeight: "85%",
824
+ margin: { top: 2, right: 1, bottom: 2, left: 1 },
825
+ dismissOnOutsideClick: true,
826
+ });
827
+ this.tui.setFocus(component);
828
+ }
829
+ openStats() {
830
+ let handle;
831
+ const component = new StatsPanelComponent(collectUsageStatsBundle(), {
832
+ getTerminalRows: () => this.tui.terminal.rows,
833
+ onClose: () => handle?.hide(),
834
+ onRender: () => this.renderSnapshot(),
835
+ theme: this.theme,
836
+ });
837
+ handle = this.tui.showOverlay(component, {
838
+ anchor: "center",
839
+ width: "65%",
840
+ minWidth: 44,
841
+ maxWidth: 100,
842
+ maxHeight: 30,
843
+ margin: { top: 2, right: 0, bottom: 2, left: 0 },
844
+ });
845
+ this.tui.setFocus(component);
846
+ }
847
+ openSkills() {
848
+ const skillRegistry = this.options.skillRegistry;
849
+ if (!skillRegistry) {
850
+ this.pushNotice("No skill registry is attached to this session.");
851
+ return;
852
+ }
853
+ let handle;
854
+ const component = new SkillsPanelComponent(skillRegistry, {
855
+ getTerminalRows: () => this.tui.terminal.rows,
856
+ onClose: () => handle?.hide(),
857
+ onRender: () => this.renderSnapshot(),
858
+ getCatalogChars: () => this.options.agent.getSkillCatalogChars?.(),
859
+ onSkillsChanged: () => {
860
+ this.options.agent.setSkillSummaries(skillRegistry.summaries());
861
+ // The Skills catalog lives in the system prompt; rebuild it so the next turn sees the change.
862
+ const ctx = this.buildSlashContext();
863
+ syncSystemPrompt(ctx, ctx.agent.model ?? "");
864
+ this.editor.refreshAutocomplete();
865
+ this.renderSnapshot();
866
+ },
867
+ theme: this.theme,
868
+ });
869
+ handle = this.tui.showOverlay(component, {
870
+ anchor: "center",
871
+ width: "65%",
872
+ minWidth: 40,
873
+ maxWidth: 160,
874
+ maxHeight: 34,
875
+ margin: { top: 3, right: 0, bottom: 3, left: 0 },
876
+ dismissOnOutsideClick: true,
877
+ });
878
+ this.tui.setFocus(component);
879
+ }
880
+ openSessionPicker() {
881
+ if (this.options.controller.isBusy?.()) {
882
+ this.appendTranscriptRow({
883
+ key: `session-error-${Date.now()}`,
884
+ role: "error",
885
+ content: "Stop the current run before switching sessions.",
886
+ });
887
+ return;
888
+ }
889
+ const activeFile = this.activeSessionManager().getSessionFile();
890
+ const currentSessions = SessionManager.summarizeSessionsForCwd(process.cwd());
891
+ const allSessions = SessionManager.listAllSessions();
892
+ let handle;
893
+ const close = () => {
894
+ handle?.hide();
895
+ this.tui.setFocus(this.editor);
896
+ this.renderSnapshot();
897
+ };
898
+ const fail = (prefix, error) => {
899
+ close();
900
+ this.appendTranscriptRow({
901
+ key: `session-error-${Date.now()}`,
902
+ role: "error",
903
+ content: `${prefix}: ${error || "unknown error"}`,
904
+ });
905
+ };
906
+ const component = new SessionPickerComponent({
907
+ currentCwd: process.cwd(),
908
+ currentSessions,
909
+ allSessions,
910
+ activeFile,
911
+ getTerminalRows: () => this.tui.terminal.rows,
912
+ onClose: close,
913
+ onNewSession: () => {
914
+ const outcome = this.options.controller.createFreshSession(process.cwd());
915
+ if (!outcome.ok) {
916
+ fail("Failed to start a new session", outcome.error);
917
+ return;
918
+ }
919
+ this.composer.setScope({
920
+ sessionFile: this.activeSessionManager().getSessionFile(),
921
+ cwd: process.cwd(),
922
+ }, nextImageDisplayLabelStart(this.options.controller.getTranscript()));
923
+ this.viewportScroll?.scrollToEnd();
924
+ close();
925
+ },
926
+ onSelect: (file) => {
927
+ if (file === this.activeSessionManager().getSessionFile()) {
928
+ close();
929
+ return;
930
+ }
931
+ const summary = allSessions.find((session) => session.file === file);
932
+ const displayName = summary?.title || summary?.preview || sessionBasename(file) || "Session";
933
+ const outcome = this.options.controller.switchSession({
934
+ targetFile: file,
935
+ notice: `⤷ Resumed session: ${displayName}`,
936
+ });
937
+ if (!outcome.ok) {
938
+ fail("Failed to switch session", outcome.error);
939
+ return;
940
+ }
941
+ this.composer.setScope({
942
+ sessionFile: this.activeSessionManager().getSessionFile(),
943
+ cwd: process.cwd(),
944
+ }, nextImageDisplayLabelStart(this.options.controller.getTranscript()));
945
+ this.viewportScroll?.scrollToEnd();
946
+ close();
947
+ },
948
+ onRender: () => this.renderSnapshot(),
949
+ theme: this.theme,
950
+ });
951
+ handle = this.tui.showOverlay(component, {
952
+ anchor: "center",
953
+ width: "70%",
954
+ minWidth: 40,
955
+ maxWidth: 120,
956
+ maxHeight: 30,
957
+ margin: { top: 2, right: 0, bottom: 2, left: 0 },
958
+ dismissOnOutsideClick: true,
959
+ });
960
+ this.tui.setFocus(component);
961
+ }
962
+ openContextInfo(snapshot) {
963
+ let handle;
964
+ const messages = this.options.agent.messages;
965
+ const turnCount = messages.filter((message) => message.role === "user").length;
966
+ const toolCallCount = messages.reduce((count, message) => (count + (message.role === "assistant" ? message.toolCalls?.length ?? 0 : 0)), 0);
967
+ const compactionCount = this.options.agent.getCompactionStats().fired;
968
+ const mcpStates = this.options.mcpManager?.getStates?.() ?? [];
969
+ const sessionId = sessionBasename(this.activeSessionManager().getSessionFile());
970
+ const component = new ContextInfoComponent({
971
+ snapshot,
972
+ sessionId,
973
+ cwd: friendlyCwd(process.cwd()),
974
+ thinking: this.options.agent.thinking,
975
+ permissionMode: this.options.agent.mode,
976
+ turnCount,
977
+ toolCallCount,
978
+ compactionCount,
979
+ mcpServerCount: mcpStates.length,
980
+ }, {
981
+ getTerminalRows: () => this.tui.terminal.rows,
982
+ onClose: () => handle?.hide(),
983
+ onRender: () => this.renderSnapshot(),
984
+ ...(sessionId ? { copySessionId: () => copyToClipboard(sessionId) } : {}),
985
+ theme: this.theme,
986
+ });
987
+ handle = this.tui.showOverlay(component, {
988
+ anchor: "center",
989
+ width: "65%",
990
+ minWidth: 44,
991
+ maxWidth: 100,
992
+ maxHeight: 30,
993
+ margin: { top: 2, right: 0, bottom: 2, left: 0 },
994
+ });
995
+ this.tui.setFocus(component);
996
+ }
997
+ openProviderKeyPhase(providerId) {
998
+ const registry = this.options.registry;
999
+ const provider = registry?.getConfigured().find((candidate) => candidate.id === providerId);
1000
+ if (!registry || !provider) {
1001
+ this.pushNotice(`Provider ${providerId} could not be prepared for API key setup.`);
1002
+ this.renderSnapshot();
1003
+ return;
1004
+ }
1005
+ this.closeProviderKeyPhase(false);
1006
+ const input = new Input({ prompt: "◆ ", mask: "•" });
1007
+ const component = new ProviderKeyInputComponent(input, provider.name, () => this.theme);
1008
+ const phase = { input, component, submitting: false };
1009
+ this.providerKeyPhase = phase;
1010
+ this.composerSlot.removeChild(this.editor);
1011
+ this.composerSlot.addChild(component);
1012
+ input.onEscape = () => this.closeProviderKeyPhase(true);
1013
+ input.onBackspaceAtStart = () => this.closeProviderKeyPhase(true);
1014
+ input.onSubmit = (rawKey) => {
1015
+ const key = rawKey.replace(/[\r\n\t]/g, "").trim();
1016
+ if (!key || phase.submitting || this.providerKeyPhase !== phase)
1017
+ return;
1018
+ phase.submitting = true;
1019
+ this.closeProviderKeyPhase(false);
1020
+ void this.saveProviderKey(providerId, key);
1021
+ };
1022
+ this.tui.setFocus(input);
1023
+ this.renderSnapshot();
1024
+ }
1025
+ closeProviderKeyPhase(returnToProviderPicker) {
1026
+ const phase = this.providerKeyPhase;
1027
+ if (!phase)
1028
+ return;
1029
+ // Erase the component-owned copy before releasing the phase. The caller
1030
+ // may retain the submitted value just long enough to persist it, but UI
1031
+ // state never keeps the raw credential after this point.
1032
+ phase.input.setValue("");
1033
+ phase.input.onSubmit = undefined;
1034
+ phase.input.onEscape = undefined;
1035
+ phase.input.onBackspaceAtStart = undefined;
1036
+ this.composerSlot.removeChild(phase.component);
1037
+ this.composerSlot.addChild(this.editor);
1038
+ this.providerKeyPhase = null;
1039
+ this.composer.replaceDraft(returnToProviderPicker ? "/provider " : "");
1040
+ if (returnToProviderPicker)
1041
+ this.editor.refreshAutocomplete();
1042
+ this.tui.setFocus(this.editor);
1043
+ this.renderSnapshot();
1044
+ }
1045
+ async saveProviderKey(providerId, key) {
1046
+ const registry = this.options.registry;
1047
+ if (!registry)
1048
+ return;
1049
+ try {
1050
+ registry.updateProviderKey(providerId, key);
1051
+ const provider = registry.getConfigured().find((candidate) => candidate.id === providerId);
1052
+ if (!provider)
1053
+ throw new Error("provider disappeared after saving credentials");
1054
+ // Saving credentials configures the provider; it must not partially
1055
+ // mutate the active route. Provider + model + thinking + prompt + session
1056
+ // move together only through the shared /model switch transaction.
1057
+ registry.setDefault(providerId);
1058
+ this.pushNotice(`API key updated for ${provider.name} to ${maskKey(key)}. Use /model to select a compatible model.`);
1059
+ }
1060
+ catch (error) {
1061
+ const message = (error instanceof Error ? error.message : String(error)).replaceAll(key, "[redacted]");
1062
+ this.pushNotice(`Failed to update API key for ${providerId}: ${message}`);
1063
+ }
1064
+ this.renderSnapshot();
1065
+ }
1066
+ openRewindPicker() {
1067
+ if (this.rewindPhase || this.providerKeyPhase || this.disposed)
1068
+ return;
1069
+ const controller = this.options.controller;
1070
+ const points = controller.isRunning() ? [] : this.collectRewindPoints();
1071
+ if (!controller.isRunning() && points.length === 0) {
1072
+ this.pushNotice("Nothing to rewind: no user messages in this session.");
1073
+ this.renderSnapshot();
1074
+ return;
1075
+ }
1076
+ const draft = this.composer.snapshotDraft();
1077
+ const component = new RewindPickerComponent(controller.isRunning() ? "cancel-offer" : "picker", points, {
1078
+ getTerminalRows: () => this.tui.terminal.rows,
1079
+ onPreview: (point, scope) => this.previewRewindPoint(point, scope),
1080
+ onScopeChange: (point, scope) => this.previewRewindPoint(point, scope),
1081
+ onCancel: () => this.closeRewindPicker(true),
1082
+ onCancelRun: () => void this.cancelRunForRewind(component),
1083
+ onConfirm: (point, scope) => void this.executeRewindFromPicker(component, point, scope),
1084
+ onRender: () => this.renderSnapshot(),
1085
+ theme: this.theme,
1086
+ });
1087
+ this.rewindPhase = {
1088
+ component,
1089
+ draft,
1090
+ previousScrollTop: this.viewportScroll?.scrollTop ?? 0,
1091
+ };
1092
+ this.composerSlot.removeChild(this.editor);
1093
+ this.composerSlot.addChild(component);
1094
+ this.tui.setFocus(component);
1095
+ const selected = component.getSelectedPoint();
1096
+ if (selected)
1097
+ this.previewRewindPoint(selected, "all");
1098
+ this.renderSnapshot();
1099
+ }
1100
+ collectRewindPoints() {
1101
+ const session = this.options.controller.getSessionManager();
1102
+ const turns = session.listUserTurns();
1103
+ const checkpoints = session.getCheckpoints();
1104
+ return turns.map((turn, turnIndex) => ({
1105
+ turn,
1106
+ turnIndex,
1107
+ fileCount: checkpoints.filesTouchedAt(turn.id).length,
1108
+ }));
1109
+ }
1110
+ async cancelRunForRewind(component) {
1111
+ if (this.rewindPhase?.component !== component || component.getPhase() !== "cancel-offer")
1112
+ return;
1113
+ component.showLoading();
1114
+ this.options.controller.cancelActiveRun("Cancelled before rewind");
1115
+ await this.waitForControllerIdle();
1116
+ if (this.rewindPhase?.component !== component || this.disposed)
1117
+ return;
1118
+ component.showPicker(this.collectRewindPoints());
1119
+ }
1120
+ waitForControllerIdle() {
1121
+ const controller = this.options.controller;
1122
+ if (!controller.isRunning())
1123
+ return Promise.resolve();
1124
+ return new Promise((resolve) => {
1125
+ const unsubscribe = controller.subscribe(() => {
1126
+ if (controller.isRunning())
1127
+ return;
1128
+ unsubscribe();
1129
+ resolve();
1130
+ });
1131
+ // The run can settle between the first check and subscription.
1132
+ if (!controller.isRunning()) {
1133
+ unsubscribe();
1134
+ resolve();
1135
+ }
1136
+ });
1137
+ }
1138
+ async executeRewindFromPicker(component, point, scope) {
1139
+ if (this.rewindPhase?.component !== component || component.getPhase() === "executing")
1140
+ return;
1141
+ component.showExecuting();
1142
+ try {
1143
+ const result = await this.options.controller.rewindToTurn(point.turn.id, scope);
1144
+ const targetText = result.target.text;
1145
+ // Swap the panel, final composer value, transcript dim, and focus before
1146
+ // requesting the success paint. This prevents an empty-composer frame
1147
+ // between "Rewinding..." and the restored prompt.
1148
+ this.closeRewindPicker(scope === "code", scope === "code" ? undefined : targetText, false);
1149
+ this.viewportScroll?.scrollToEnd();
1150
+ const fileCount = result.files.restored.length + result.files.deleted.length;
1151
+ const failed = result.files.failed.length;
1152
+ if (scope === "code") {
1153
+ this.pushNotice(failed > 0
1154
+ ? `Files restored with ${failed} failure${failed === 1 ? "" : "s"}.`
1155
+ : fileCount > 0
1156
+ ? `Restored ${fileCount} file${fileCount === 1 ? "" : "s"}.`
1157
+ : "No tracked file edits to undo.");
1158
+ }
1159
+ else {
1160
+ const fileSuffix = scope === "all"
1161
+ ? failed > 0
1162
+ ? ` · ${failed} file restore failure${failed === 1 ? "" : "s"}`
1163
+ : fileCount > 0
1164
+ ? ` · ${fileCount} file${fileCount === 1 ? "" : "s"} restored`
1165
+ : " · no tracked file edits"
1166
+ : "";
1167
+ this.pushNotice(`Reverted conversation${fileSuffix}`);
1168
+ }
1169
+ this.renderSnapshot();
1170
+ }
1171
+ catch (error) {
1172
+ if (this.rewindPhase?.component !== component)
1173
+ return;
1174
+ component.showError(error instanceof Error ? error.message : String(error));
1175
+ }
1176
+ }
1177
+ closeRewindPicker(restoreDraft, replacementText, render = true) {
1178
+ const phase = this.rewindPhase;
1179
+ if (!phase)
1180
+ return;
1181
+ this.composerSlot.removeChild(phase.component);
1182
+ this.composerSlot.addChild(this.editor);
1183
+ this.rewindPhase = null;
1184
+ this.rewindPreviewMessageIndex = undefined;
1185
+ if (restoreDraft)
1186
+ this.composer.restoreDraft(phase.draft);
1187
+ else if (replacementText !== undefined)
1188
+ this.composer.replaceDraft(replacementText);
1189
+ if (restoreDraft) {
1190
+ this.viewportScroll?.scrollTo(phase.previousScrollTop, { disableFollow: true });
1191
+ }
1192
+ this.tui.setFocus(this.editor);
1193
+ if (render)
1194
+ this.renderSnapshot();
1195
+ }
1196
+ previewRewindPoint(point, scope) {
1197
+ if (!point) {
1198
+ this.rewindPreviewMessageIndex = undefined;
1199
+ this.renderSnapshot();
1200
+ return;
1201
+ }
1202
+ const transcript = this.options.controller.getTranscript();
1203
+ const userIndexes = transcript
1204
+ .map((message, index) => message.role === "user" ? index : -1)
1205
+ .filter((index) => index >= 0);
1206
+ const turnCount = this.options.controller.getSessionManager().listUserTurns().length;
1207
+ const relevantUsers = userIndexes.slice(Math.max(0, userIndexes.length - turnCount));
1208
+ const messageIndex = relevantUsers[point.turnIndex];
1209
+ this.rewindPreviewMessageIndex = scope === "code" ? undefined : messageIndex;
1210
+ if (messageIndex !== undefined && this.viewportScroll) {
1211
+ const columns = this.tui.terminal.columns || process.stdout.columns || 80;
1212
+ const rowsBefore = this.welcome.render(columns).length + projectTranscript(transcript.slice(0, messageIndex), { ...this.transcriptRenderOptions(), columns, trailingSpacer: false, dimFromMessageIndex: undefined }).rows.length;
1213
+ const centeredTop = rowsBefore - Math.floor(this.viewportScroll.viewportHeight / 2);
1214
+ this.viewportScroll.scrollTo(centeredTop, { disableFollow: true });
1215
+ }
1216
+ this.renderSnapshot();
1217
+ }
1218
+ planDialog(plan) {
1219
+ return new Promise((resolve) => {
1220
+ const dialog = new PlanDialogComponent(this.tui, plan, () => this.tui.terminal.rows, () => this.theme);
1221
+ const handle = this.tui.showOverlay(dialog, {
1222
+ anchor: "bottom-center",
1223
+ width: "100%",
1224
+ margin: { left: 1, right: 1 },
1225
+ });
1226
+ let settled = false;
1227
+ const finish = (decision) => {
1228
+ if (settled)
1229
+ return;
1230
+ settled = true;
1231
+ handle.hide();
1232
+ resolve(decision);
1233
+ };
1234
+ dialog.onApprove = (finalPlan) => finish({ action: "approve", plan: finalPlan });
1235
+ dialog.onReject = () => finish({ action: "reject", reason: "User rejected the plan." });
1236
+ this.tui.setFocus(dialog);
1237
+ });
1238
+ }
1239
+ openFeedback(initialDescription) {
1240
+ const collected = collectFeedback(this.options.agent, { description: "" });
1241
+ const { description: _description, ...base } = collected;
1242
+ let handle;
1243
+ const close = () => {
1244
+ handle?.hide();
1245
+ this.tui.setFocus(this.editor);
1246
+ this.renderSnapshot();
1247
+ };
1248
+ const recordResult = (result) => {
1249
+ if (result.kind === "success") {
1250
+ this.pushNotice(`Feedback submitted: ${result.url}`);
1251
+ }
1252
+ else if (result.kind === "error") {
1253
+ this.appendTranscriptRow({
1254
+ key: `feedback-error-${Date.now()}`,
1255
+ role: "error",
1256
+ content: `Feedback failed: ${result.message}`,
1257
+ });
1258
+ }
1259
+ };
1260
+ const dialog = new FeedbackDialogComponent(this.tui, base, initialDescription, {
1261
+ getTerminalRows: () => this.tui.terminal.rows,
1262
+ getTheme: () => this.theme,
1263
+ submit: this.options.submitFeedback,
1264
+ onDismiss: close,
1265
+ onResult: recordResult,
1266
+ onRender: () => this.renderSnapshot(),
1267
+ });
1268
+ handle = this.tui.showOverlay(dialog, {
1269
+ anchor: "bottom-center",
1270
+ width: "100%",
1271
+ margin: { left: 1, right: 1 },
1272
+ });
1273
+ this.tui.setFocus(dialog);
1274
+ }
1275
+ openFeishuSetup() {
1276
+ if (this.feishuSetupPhase)
1277
+ return;
1278
+ let handle;
1279
+ let closed = false;
1280
+ const close = (result) => {
1281
+ if (closed)
1282
+ return;
1283
+ closed = true;
1284
+ handle?.hide();
1285
+ this.feishuSetupPhase = null;
1286
+ this.tui.setFocus(this.editor);
1287
+ if (result.kind === "completed") {
1288
+ this.appendTranscriptRow({
1289
+ key: `feishu-setup-${Date.now()}`,
1290
+ role: "assistant",
1291
+ content: result.summary,
1292
+ syntheticKind: "ui_notice",
1293
+ });
1294
+ }
1295
+ else if (result.kind === "error") {
1296
+ this.appendTranscriptRow({
1297
+ key: `feishu-setup-error-${Date.now()}`,
1298
+ role: "error",
1299
+ content: `Feishu setup failed: ${result.message}`,
1300
+ });
1301
+ }
1302
+ else {
1303
+ this.pushNotice("已取消 Feishu setup。");
1304
+ }
1305
+ this.renderSnapshot();
1306
+ };
1307
+ const dialog = new FeishuSetupDialogComponent({
1308
+ getTerminalRows: () => this.tui.terminal.rows,
1309
+ getTheme: () => this.theme,
1310
+ onResult: close,
1311
+ onRender: () => this.renderSnapshot(),
1312
+ });
1313
+ handle = this.tui.showOverlay(dialog, {
1314
+ anchor: "bottom-center",
1315
+ width: "100%",
1316
+ margin: { left: 1, right: 1 },
1317
+ });
1318
+ this.feishuSetupPhase = { component: dialog, handle };
1319
+ this.tui.setFocus(dialog);
1320
+ dialog.start();
1321
+ }
1322
+ openWorkflowInspector(item) {
1323
+ let handle;
1324
+ const getSnapshot = () => {
1325
+ const workflow = this.options.controller.getWorkflows().find((candidate) => candidate.runId === item.id);
1326
+ const group = this.options.controller.getSubagentGroups().find((candidate) => (candidate.kind === "workflow" && (candidate.runId === item.id || candidate.id === item.id)));
1327
+ const members = workflow?.snapshots.length
1328
+ ? workflow.snapshots.map((snapshot) => ({
1329
+ subAgentId: snapshot.agentId,
1330
+ agentName: snapshot.agentName,
1331
+ nickname: snapshot.nickname,
1332
+ status: snapshot.status,
1333
+ category: snapshot.category,
1334
+ phase: snapshot.phase,
1335
+ route: snapshot.route,
1336
+ task: snapshot.task,
1337
+ summary: snapshot.summary,
1338
+ toolNotes: snapshot.toolNotes,
1339
+ error: snapshot.error,
1340
+ usage: snapshot.usage,
1341
+ createdAt: snapshot.createdAt,
1342
+ updatedAt: snapshot.updatedAt,
1343
+ }))
1344
+ : group?.members ?? item.members;
1345
+ return {
1346
+ id: item.id,
1347
+ title: workflow?.title ?? group?.label ?? item.title,
1348
+ status: workflow?.status ?? item.status,
1349
+ members,
1350
+ createdAt: workflow?.createdAt ?? item.createdAt,
1351
+ updatedAt: workflow?.updatedAt ?? item.updatedAt,
1352
+ };
1353
+ };
1354
+ const component = new WorkflowInspectorComponent({
1355
+ getSnapshot,
1356
+ getTerminalRows: () => this.tui.terminal.rows,
1357
+ theme: this.theme,
1358
+ onClose: () => handle?.hide(),
1359
+ onOpenAgent: (agentId) => {
1360
+ handle?.hide();
1361
+ const member = getSnapshot().members.find((candidate) => candidate.subAgentId === agentId);
1362
+ if (!member)
1363
+ return;
1364
+ this.openSubagentInspector({
1365
+ kind: "subagent",
1366
+ id: agentId,
1367
+ title: member.nickname ?? member.agentName ?? "subagent",
1368
+ status: member.status ?? "running",
1369
+ member,
1370
+ }, () => this.openWorkflowInspector(item));
1371
+ },
1372
+ onStop: (runId) => this.options.controller.stopWorkflow(runId),
1373
+ onRender: () => this.renderSnapshot(),
1374
+ });
1375
+ handle = this.tui.showOverlay(component, {
1376
+ width: "100%",
1377
+ maxHeight: "100%",
1378
+ margin: 1,
1379
+ });
1380
+ this.tui.setFocus(component);
1381
+ }
1382
+ openSubagentInspector(item, onClose) {
1383
+ let handle;
1384
+ const allAgentIds = () => this.options.controller.getSubagentGroups()
1385
+ .flatMap((group) => group.members)
1386
+ .map((member) => member.subAgentId)
1387
+ .filter((id) => !!id);
1388
+ const close = () => {
1389
+ handle?.hide();
1390
+ onClose?.();
1391
+ };
1392
+ const component = new SubagentInspectorComponent({
1393
+ agentId: item.id,
1394
+ controller: this.options.controller,
1395
+ theme: this.theme,
1396
+ getMember: () => this.options.controller.getSubagentGroups()
1397
+ .flatMap((group) => group.members)
1398
+ .find((member) => member.subAgentId === item.id) ?? item.member,
1399
+ getTerminalRows: () => this.tui.terminal.rows,
1400
+ renderOptions: () => this.transcriptRenderOptions(),
1401
+ onClose: close,
1402
+ onNavigate: (direction) => {
1403
+ const ids = allAgentIds();
1404
+ const current = ids.indexOf(item.id);
1405
+ if (current < 0 || ids.length < 2)
1406
+ return;
1407
+ const nextId = ids[(current + direction + ids.length) % ids.length];
1408
+ const member = this.options.controller.getSubagentGroups().flatMap((group) => group.members).find((candidate) => candidate.subAgentId === nextId);
1409
+ if (!member)
1410
+ return;
1411
+ handle?.hide();
1412
+ this.openSubagentInspector({
1413
+ kind: "subagent",
1414
+ id: nextId,
1415
+ title: member.nickname ?? member.agentName ?? "subagent",
1416
+ status: member.status ?? "running",
1417
+ member,
1418
+ }, onClose);
1419
+ },
1420
+ onRender: () => this.renderSnapshot(),
1421
+ });
1422
+ handle = this.tui.showOverlay(component, {
1423
+ width: "100%",
1424
+ maxHeight: "100%",
1425
+ margin: 1,
1426
+ });
1427
+ this.tui.setFocus(component);
1428
+ }
1429
+ handleTraceAction(action) {
1430
+ if (action.kind === "open-image") {
1431
+ const message = this.options.controller.getTranscript().find((candidate) => candidate.key === action.messageKey);
1432
+ const image = message?.images?.find((candidate) => candidate.label === action.imageLabel);
1433
+ if (image)
1434
+ this.openImageViewer(image);
1435
+ return;
1436
+ }
1437
+ if (action.kind === "open-task") {
1438
+ const task = this.options.controller.getBackgroundTasks()
1439
+ .find((candidate) => candidate.id === action.taskId);
1440
+ if (task) {
1441
+ this.openTaskInspector({
1442
+ kind: "task",
1443
+ id: task.id,
1444
+ title: task.description || task.command,
1445
+ status: task.status,
1446
+ task,
1447
+ });
1448
+ }
1449
+ else {
1450
+ const tool = this.options.controller.getTranscript()
1451
+ .flatMap((message) => message.toolCalls ?? [])
1452
+ .find((candidate) => candidate.metadata?.taskId === action.taskId
1453
+ && candidate.metadata?.taskLifecycle !== undefined);
1454
+ if (tool) {
1455
+ const lifecycle = String(tool.metadata?.taskLifecycle ?? "completed");
1456
+ const command = String(tool.args.command ?? action.taskId);
1457
+ const description = typeof tool.args.description === "string" ? tool.args.description : undefined;
1458
+ const startedAt = tool.startedAt ?? Date.now();
1459
+ const endedAt = typeof tool.metadata?.endedAt === "number" ? tool.metadata.endedAt : startedAt;
1460
+ this.openTaskInspector({
1461
+ kind: "task",
1462
+ id: action.taskId,
1463
+ title: description || command,
1464
+ status: lifecycle,
1465
+ task: {
1466
+ kind: "task",
1467
+ id: action.taskId,
1468
+ command,
1469
+ description,
1470
+ cwd: process.cwd(),
1471
+ status: lifecycle === "failed" ? "failed" : lifecycle === "killed" ? "killed" : "completed",
1472
+ exitCode: typeof tool.metadata?.exitCode === "number" ? tool.metadata.exitCode : null,
1473
+ startedAt,
1474
+ endedAt,
1475
+ outputTruncated: false,
1476
+ outputLines: typeof tool.metadata?.outputLines === "number" ? tool.metadata.outputLines : 0,
1477
+ },
1478
+ }, tool.result ?? "");
1479
+ }
1480
+ }
1481
+ return;
1482
+ }
1483
+ if (action.kind !== "open-subagent")
1484
+ return;
1485
+ const member = this.options.controller.getSubagentGroups()
1486
+ .flatMap((group) => group.members)
1487
+ .find((candidate) => candidate.subAgentId === action.subAgentId);
1488
+ if (!member)
1489
+ return;
1490
+ this.openSubagentInspector({
1491
+ kind: "subagent",
1492
+ id: action.subAgentId,
1493
+ title: member.nickname ?? member.agentName ?? "subagent",
1494
+ status: member.status ?? "running",
1495
+ member,
1496
+ });
1497
+ }
1498
+ openTaskInspector(item, fallbackOutput = "") {
1499
+ let handle;
1500
+ const taskOutput = () => this.options.controller.getBackgroundTaskOutput(item.id) || fallbackOutput;
1501
+ const component = new TaskInspectorComponent({
1502
+ id: item.id,
1503
+ title: item.title,
1504
+ getStatus: () => this.options.controller.getBackgroundTasks().find((task) => task.id === item.id)?.status ?? item.status,
1505
+ getOutput: taskOutput,
1506
+ getTerminalRows: () => this.tui.terminal.rows,
1507
+ onClose: () => handle?.hide(),
1508
+ onStop: () => this.options.controller.stopBackgroundTask(item.id),
1509
+ onCopy: () => copyToClipboard(taskOutput()),
1510
+ onRender: () => this.renderSnapshot(),
1511
+ theme: this.theme,
1512
+ });
1513
+ handle = this.tui.showOverlay(component, {
1514
+ width: "100%",
1515
+ maxHeight: "100%",
1516
+ margin: 1,
1517
+ });
1518
+ this.tui.setFocus(component);
1519
+ }
1520
+ approvalDialog(request) {
1521
+ return new Promise((resolve) => {
1522
+ const dialog = new ApprovalDialogComponent(request, () => this.tui.terminal.rows, {
1523
+ getTheme: () => this.theme,
1524
+ });
1525
+ const handle = this.tui.showOverlay(dialog, {
1526
+ anchor: "bottom-center",
1527
+ width: "100%",
1528
+ margin: { left: 1, right: 1 },
1529
+ });
1530
+ const finish = (choice) => {
1531
+ handle.hide();
1532
+ resolve(choice);
1533
+ };
1534
+ dialog.onSelect = finish;
1535
+ dialog.onCancel = () => finish({ kind: "reject" });
1536
+ this.tui.setFocus(dialog);
1537
+ });
1538
+ }
1539
+ openNextQuestion() {
1540
+ if (this.disposed || this.activeQuestion)
1541
+ return;
1542
+ const request = this.questionQueue.shift();
1543
+ if (!request)
1544
+ return;
1545
+ const questionController = this.options.questionController;
1546
+ if (!questionController)
1547
+ return;
1548
+ const dialog = new QuestionDialogComponent(request, () => this.tui.terminal.rows, () => this.theme);
1549
+ const handle = this.tui.showOverlay(dialog, {
1550
+ anchor: "bottom-center",
1551
+ width: "100%",
1552
+ margin: { left: 1, right: 1 },
1553
+ });
1554
+ let closed = false;
1555
+ const close = () => {
1556
+ if (closed)
1557
+ return;
1558
+ closed = true;
1559
+ handle.hide();
1560
+ if (this.activeQuestion?.id === request.id)
1561
+ this.activeQuestion = null;
1562
+ this.openNextQuestion();
1563
+ };
1564
+ this.activeQuestion = { id: request.id, close };
1565
+ dialog.onSubmit = (answers) => {
1566
+ close();
1567
+ questionController.reply(request.id, answers);
1568
+ };
1569
+ dialog.onCancel = () => {
1570
+ close();
1571
+ questionController.reject(request.id);
1572
+ };
1573
+ this.tui.setFocus(dialog);
1574
+ }
1575
+ removeQueuedQuestion(id) {
1576
+ const index = this.questionQueue.findIndex((request) => request.id === id);
1577
+ if (index >= 0)
1578
+ this.questionQueue.splice(index, 1);
1579
+ }
1580
+ // ---- Lifecycle ----------------------------------------------------------
1581
+ start() {
1582
+ this.tui.start();
1583
+ this.renderSnapshot();
1584
+ this.watchUpdateNoticeRefresh();
1585
+ }
1586
+ watchUpdateNoticeRefresh() {
1587
+ if (this.updateNoticeRefreshStarted)
1588
+ return;
1589
+ this.updateNoticeRefreshStarted = true;
1590
+ const refresh = this.options.updateNoticeRefresh;
1591
+ if (!refresh)
1592
+ return;
1593
+ void refresh.then((notice) => {
1594
+ if (this.disposed || !notice || notice === this.currentUpdateNotice)
1595
+ return;
1596
+ this.currentUpdateNotice = notice;
1597
+ // The root fullscreen renderer includes the welcome card. The legacy
1598
+ // main-to-fullscreen transition does not, so it needs a transcript row
1599
+ // even before the first user turn.
1600
+ if (this.fullscreen || this.options.controller.getTranscript().length > 0) {
1601
+ this.pushNotice(notice);
1602
+ }
1603
+ else {
1604
+ this.renderSnapshot();
1605
+ }
1606
+ }).catch(() => {
1607
+ // Update checks are best-effort and must never disturb an active session.
1608
+ });
1609
+ }
1610
+ dispose() {
1611
+ if (this.disposed)
1612
+ return;
1613
+ this.disposed = true;
1614
+ this.controllerUnsubscribe?.();
1615
+ this.controllerUnsubscribe = null;
1616
+ this.metadataUnsubscribe?.();
1617
+ this.metadataUnsubscribe = null;
1618
+ this.metadataManager = null;
1619
+ this.questionUnsubscribe?.();
1620
+ this.questionUnsubscribe = null;
1621
+ this.activeQuestion?.close();
1622
+ this.activeQuestion = null;
1623
+ this.questionQueue.length = 0;
1624
+ this.feishuSetupPhase?.component.dispose();
1625
+ this.feishuSetupPhase?.handle.hide();
1626
+ this.feishuSetupPhase = null;
1627
+ if (this.providerKeyPhase) {
1628
+ this.providerKeyPhase.input.setValue("");
1629
+ this.providerKeyPhase = null;
1630
+ }
1631
+ this.options.questionController?.rejectAll();
1632
+ // Root lifecycle events (notably SIGTERM) can dispose the app while the
1633
+ // alternate screen is still open. Tear it down directly so its controller
1634
+ // listener, spinner timers, and terminal mode cannot outlive the root app.
1635
+ this.fullscreen?.dispose();
1636
+ this.fullscreen = null;
1637
+ this.animationClock.dispose();
1638
+ this.composer.dispose();
1639
+ this.streamingMessage.dispose();
1640
+ this.tasksPane.dispose();
1641
+ this.options.controller.shutdown("user-quit");
1642
+ this.overlays.dispose();
1643
+ this.options.callbacks.onExitRequest();
1644
+ // The fullscreen frame is transient application chrome. Reprinting it
1645
+ // into the restored main buffer leaves the docked composer above the
1646
+ // post-session summary. Exit the alternate screen without exporting that
1647
+ // frame; regular mode keeps its native scrollback behavior unchanged.
1648
+ this.tui.stop({ preserveScreen: this.tui.mode === "fullscreen" });
1649
+ }
1650
+ async waitUntilExit() {
1651
+ await new Promise((resolve) => {
1652
+ const original = this.dispose.bind(this);
1653
+ this.dispose = () => {
1654
+ original();
1655
+ resolve();
1656
+ };
1657
+ });
1658
+ }
1659
+ }