pythinker-code 0.8.0__py3-none-any.whl

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 (790) hide show
  1. pythinker_code/CHANGELOG.md +60 -0
  2. pythinker_code/__init__.py +0 -0
  3. pythinker_code/__main__.py +97 -0
  4. pythinker_code/acp/AGENTS.md +93 -0
  5. pythinker_code/acp/__init__.py +13 -0
  6. pythinker_code/acp/convert.py +128 -0
  7. pythinker_code/acp/host.py +301 -0
  8. pythinker_code/acp/mcp.py +46 -0
  9. pythinker_code/acp/server.py +497 -0
  10. pythinker_code/acp/session.py +502 -0
  11. pythinker_code/acp/tools.py +174 -0
  12. pythinker_code/acp/types.py +13 -0
  13. pythinker_code/acp/version.py +45 -0
  14. pythinker_code/agents/default/agent.yaml +55 -0
  15. pythinker_code/agents/default/code_reviewer.yaml +47 -0
  16. pythinker_code/agents/default/coder.yaml +42 -0
  17. pythinker_code/agents/default/debugger.yaml +35 -0
  18. pythinker_code/agents/default/explore.yaml +59 -0
  19. pythinker_code/agents/default/implementer.yaml +46 -0
  20. pythinker_code/agents/default/plan.yaml +42 -0
  21. pythinker_code/agents/default/review.yaml +47 -0
  22. pythinker_code/agents/default/security_reviewer.yaml +37 -0
  23. pythinker_code/agents/default/system.md +192 -0
  24. pythinker_code/agents/default/verifier.yaml +46 -0
  25. pythinker_code/agents/okabe/agent.yaml +22 -0
  26. pythinker_code/agentspec.py +163 -0
  27. pythinker_code/app.py +847 -0
  28. pythinker_code/approval_runtime/__init__.py +29 -0
  29. pythinker_code/approval_runtime/models.py +42 -0
  30. pythinker_code/approval_runtime/runtime.py +235 -0
  31. pythinker_code/auth/__init__.py +25 -0
  32. pythinker_code/auth/anthropic_direct.py +207 -0
  33. pythinker_code/auth/deepseek.py +192 -0
  34. pythinker_code/auth/github_feedback.py +228 -0
  35. pythinker_code/auth/lm_studio.py +418 -0
  36. pythinker_code/auth/minimax.py +203 -0
  37. pythinker_code/auth/oauth.py +1145 -0
  38. pythinker_code/auth/ollama.py +293 -0
  39. pythinker_code/auth/openai.py +783 -0
  40. pythinker_code/auth/opencode_go.py +203 -0
  41. pythinker_code/auth/openrouter.py +225 -0
  42. pythinker_code/auth/platforms.py +475 -0
  43. pythinker_code/background/__init__.py +36 -0
  44. pythinker_code/background/agent_runner.py +231 -0
  45. pythinker_code/background/ids.py +19 -0
  46. pythinker_code/background/manager.py +668 -0
  47. pythinker_code/background/models.py +118 -0
  48. pythinker_code/background/store.py +243 -0
  49. pythinker_code/background/summary.py +66 -0
  50. pythinker_code/background/worker.py +209 -0
  51. pythinker_code/cli/__init__.py +1326 -0
  52. pythinker_code/cli/__main__.py +19 -0
  53. pythinker_code/cli/_lazy_group.py +268 -0
  54. pythinker_code/cli/debug.py +11 -0
  55. pythinker_code/cli/export.py +322 -0
  56. pythinker_code/cli/info.py +62 -0
  57. pythinker_code/cli/mcp.py +362 -0
  58. pythinker_code/cli/plugin.py +351 -0
  59. pythinker_code/cli/review.py +74 -0
  60. pythinker_code/cli/secscan.py +11 -0
  61. pythinker_code/cli/security_scan.py +35 -0
  62. pythinker_code/cli/toad.py +74 -0
  63. pythinker_code/cli/update.py +26 -0
  64. pythinker_code/cli/vis.py +38 -0
  65. pythinker_code/cli/web.py +80 -0
  66. pythinker_code/config.py +511 -0
  67. pythinker_code/constant.py +33 -0
  68. pythinker_code/events.py +106 -0
  69. pythinker_code/exception.py +43 -0
  70. pythinker_code/extensions.py +151 -0
  71. pythinker_code/hooks/__init__.py +4 -0
  72. pythinker_code/hooks/config.py +34 -0
  73. pythinker_code/hooks/engine.py +383 -0
  74. pythinker_code/hooks/events.py +190 -0
  75. pythinker_code/hooks/runner.py +92 -0
  76. pythinker_code/llm.py +441 -0
  77. pythinker_code/metadata.py +79 -0
  78. pythinker_code/notifications/__init__.py +33 -0
  79. pythinker_code/notifications/llm.py +77 -0
  80. pythinker_code/notifications/manager.py +145 -0
  81. pythinker_code/notifications/models.py +50 -0
  82. pythinker_code/notifications/notifier.py +41 -0
  83. pythinker_code/notifications/store.py +118 -0
  84. pythinker_code/notifications/wire.py +21 -0
  85. pythinker_code/plugin/__init__.py +124 -0
  86. pythinker_code/plugin/manager.py +166 -0
  87. pythinker_code/plugin/tool.py +173 -0
  88. pythinker_code/prompt_templates.py +181 -0
  89. pythinker_code/prompts/__init__.py +6 -0
  90. pythinker_code/prompts/compact.md +73 -0
  91. pythinker_code/prompts/init.md +21 -0
  92. pythinker_code/py.typed +0 -0
  93. pythinker_code/session.py +319 -0
  94. pythinker_code/session_fork.py +325 -0
  95. pythinker_code/session_state.py +132 -0
  96. pythinker_code/share.py +14 -0
  97. pythinker_code/skill/__init__.py +727 -0
  98. pythinker_code/skill/flow/__init__.py +99 -0
  99. pythinker_code/skill/flow/d2.py +482 -0
  100. pythinker_code/skill/flow/mermaid.py +266 -0
  101. pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
  102. pythinker_code/skills/skill-creator/SKILL.md +367 -0
  103. pythinker_code/soul/__init__.py +304 -0
  104. pythinker_code/soul/agent.py +552 -0
  105. pythinker_code/soul/approval.py +267 -0
  106. pythinker_code/soul/btw.py +220 -0
  107. pythinker_code/soul/compaction.py +189 -0
  108. pythinker_code/soul/context.py +339 -0
  109. pythinker_code/soul/denwarenji.py +39 -0
  110. pythinker_code/soul/dynamic_injection.py +84 -0
  111. pythinker_code/soul/dynamic_injections/__init__.py +0 -0
  112. pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
  113. pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
  114. pythinker_code/soul/message.py +92 -0
  115. pythinker_code/soul/permission.py +368 -0
  116. pythinker_code/soul/pythinkersoul.py +1763 -0
  117. pythinker_code/soul/slash.py +340 -0
  118. pythinker_code/soul/toolset.py +826 -0
  119. pythinker_code/subagents/__init__.py +21 -0
  120. pythinker_code/subagents/builder.py +43 -0
  121. pythinker_code/subagents/core.py +86 -0
  122. pythinker_code/subagents/discovery.py +234 -0
  123. pythinker_code/subagents/git_context.py +172 -0
  124. pythinker_code/subagents/models.py +56 -0
  125. pythinker_code/subagents/output.py +71 -0
  126. pythinker_code/subagents/registry.py +28 -0
  127. pythinker_code/subagents/runner.py +442 -0
  128. pythinker_code/subagents/store.py +200 -0
  129. pythinker_code/telemetry/__init__.py +217 -0
  130. pythinker_code/telemetry/config.py +113 -0
  131. pythinker_code/telemetry/crash.py +191 -0
  132. pythinker_code/telemetry/errors.py +113 -0
  133. pythinker_code/telemetry/metrics.py +208 -0
  134. pythinker_code/telemetry/otel.py +303 -0
  135. pythinker_code/telemetry/sentry.py +212 -0
  136. pythinker_code/telemetry/sink.py +189 -0
  137. pythinker_code/tools/AGENTS.md +6 -0
  138. pythinker_code/tools/__init__.py +105 -0
  139. pythinker_code/tools/agent/__init__.py +326 -0
  140. pythinker_code/tools/agent/description.md +65 -0
  141. pythinker_code/tools/ask_user/__init__.py +162 -0
  142. pythinker_code/tools/ask_user/description.md +19 -0
  143. pythinker_code/tools/background/__init__.py +318 -0
  144. pythinker_code/tools/background/list.md +10 -0
  145. pythinker_code/tools/background/output.md +11 -0
  146. pythinker_code/tools/background/stop.md +8 -0
  147. pythinker_code/tools/display.py +46 -0
  148. pythinker_code/tools/dmail/__init__.py +38 -0
  149. pythinker_code/tools/dmail/dmail.md +17 -0
  150. pythinker_code/tools/file/__init__.py +31 -0
  151. pythinker_code/tools/file/glob.md +17 -0
  152. pythinker_code/tools/file/glob.py +163 -0
  153. pythinker_code/tools/file/grep.md +6 -0
  154. pythinker_code/tools/file/grep_local.py +904 -0
  155. pythinker_code/tools/file/plan_mode.py +45 -0
  156. pythinker_code/tools/file/read.md +16 -0
  157. pythinker_code/tools/file/read.py +303 -0
  158. pythinker_code/tools/file/read_media.md +24 -0
  159. pythinker_code/tools/file/read_media.py +220 -0
  160. pythinker_code/tools/file/replace.md +7 -0
  161. pythinker_code/tools/file/replace.py +204 -0
  162. pythinker_code/tools/file/utils.py +257 -0
  163. pythinker_code/tools/file/write.md +5 -0
  164. pythinker_code/tools/file/write.py +186 -0
  165. pythinker_code/tools/plan/__init__.py +362 -0
  166. pythinker_code/tools/plan/description.md +29 -0
  167. pythinker_code/tools/plan/enter.py +193 -0
  168. pythinker_code/tools/plan/enter_description.md +35 -0
  169. pythinker_code/tools/plan/handoff.py +69 -0
  170. pythinker_code/tools/plan/heroes.py +277 -0
  171. pythinker_code/tools/shell/__init__.py +263 -0
  172. pythinker_code/tools/shell/bash.md +35 -0
  173. pythinker_code/tools/shell/powershell.md +30 -0
  174. pythinker_code/tools/test.py +55 -0
  175. pythinker_code/tools/think/__init__.py +21 -0
  176. pythinker_code/tools/think/think.md +1 -0
  177. pythinker_code/tools/todo/__init__.py +168 -0
  178. pythinker_code/tools/todo/set_todo_list.md +23 -0
  179. pythinker_code/tools/utils.py +200 -0
  180. pythinker_code/tools/web/__init__.py +4 -0
  181. pythinker_code/tools/web/fetch.md +1 -0
  182. pythinker_code/tools/web/fetch.py +261 -0
  183. pythinker_code/tools/web/search.md +1 -0
  184. pythinker_code/tools/web/search.py +163 -0
  185. pythinker_code/ui/__init__.py +0 -0
  186. pythinker_code/ui/acp/__init__.py +99 -0
  187. pythinker_code/ui/print/__init__.py +474 -0
  188. pythinker_code/ui/print/visualize.py +185 -0
  189. pythinker_code/ui/shell/__init__.py +1806 -0
  190. pythinker_code/ui/shell/components/__init__.py +110 -0
  191. pythinker_code/ui/shell/components/base.py +25 -0
  192. pythinker_code/ui/shell/components/bash_execution.py +249 -0
  193. pythinker_code/ui/shell/components/bordered_loader.py +62 -0
  194. pythinker_code/ui/shell/components/diff.py +308 -0
  195. pythinker_code/ui/shell/components/footer.py +231 -0
  196. pythinker_code/ui/shell/components/key_hints.py +27 -0
  197. pythinker_code/ui/shell/components/messages.py +152 -0
  198. pythinker_code/ui/shell/components/render_utils.py +198 -0
  199. pythinker_code/ui/shell/components/settings_list.py +369 -0
  200. pythinker_code/ui/shell/components/special_messages.py +125 -0
  201. pythinker_code/ui/shell/components/tool_execution.py +261 -0
  202. pythinker_code/ui/shell/console.py +109 -0
  203. pythinker_code/ui/shell/debug.py +190 -0
  204. pythinker_code/ui/shell/echo.py +30 -0
  205. pythinker_code/ui/shell/export_import.py +117 -0
  206. pythinker_code/ui/shell/keyboard.py +300 -0
  207. pythinker_code/ui/shell/keymap.py +84 -0
  208. pythinker_code/ui/shell/mcp_status.py +112 -0
  209. pythinker_code/ui/shell/model_picker.py +318 -0
  210. pythinker_code/ui/shell/oauth.py +273 -0
  211. pythinker_code/ui/shell/placeholders.py +578 -0
  212. pythinker_code/ui/shell/prompt.py +2888 -0
  213. pythinker_code/ui/shell/replay.py +215 -0
  214. pythinker_code/ui/shell/selector.py +364 -0
  215. pythinker_code/ui/shell/selectors/__init__.py +38 -0
  216. pythinker_code/ui/shell/selectors/extension.py +37 -0
  217. pythinker_code/ui/shell/selectors/oauth.py +63 -0
  218. pythinker_code/ui/shell/selectors/settings.py +349 -0
  219. pythinker_code/ui/shell/selectors/show_images.py +29 -0
  220. pythinker_code/ui/shell/selectors/theme.py +28 -0
  221. pythinker_code/ui/shell/selectors/thinking.py +42 -0
  222. pythinker_code/ui/shell/session_picker.py +227 -0
  223. pythinker_code/ui/shell/setup.py +212 -0
  224. pythinker_code/ui/shell/slash.py +1433 -0
  225. pythinker_code/ui/shell/spinner_words.py +222 -0
  226. pythinker_code/ui/shell/startup.py +32 -0
  227. pythinker_code/ui/shell/task_browser.py +486 -0
  228. pythinker_code/ui/shell/tool_renderers/__init__.py +197 -0
  229. pythinker_code/ui/shell/tool_renderers/_render_utils.py +168 -0
  230. pythinker_code/ui/shell/tool_renderers/agent.py +140 -0
  231. pythinker_code/ui/shell/tool_renderers/ask_user.py +93 -0
  232. pythinker_code/ui/shell/tool_renderers/background.py +144 -0
  233. pythinker_code/ui/shell/tool_renderers/bash.py +103 -0
  234. pythinker_code/ui/shell/tool_renderers/edit.py +163 -0
  235. pythinker_code/ui/shell/tool_renderers/find.py +81 -0
  236. pythinker_code/ui/shell/tool_renderers/generic.py +60 -0
  237. pythinker_code/ui/shell/tool_renderers/grep.py +98 -0
  238. pythinker_code/ui/shell/tool_renderers/plan.py +98 -0
  239. pythinker_code/ui/shell/tool_renderers/read.py +103 -0
  240. pythinker_code/ui/shell/tool_renderers/think.py +66 -0
  241. pythinker_code/ui/shell/tool_renderers/todo.py +164 -0
  242. pythinker_code/ui/shell/tool_renderers/web.py +128 -0
  243. pythinker_code/ui/shell/tool_renderers/write.py +102 -0
  244. pythinker_code/ui/shell/update.py +352 -0
  245. pythinker_code/ui/shell/usage.py +291 -0
  246. pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
  247. pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
  248. pythinker_code/ui/shell/usage_adapters/base.py +72 -0
  249. pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
  250. pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
  251. pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
  252. pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
  253. pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
  254. pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
  255. pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
  256. pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
  257. pythinker_code/ui/shell/usage_render.py +150 -0
  258. pythinker_code/ui/shell/visualize/__init__.py +165 -0
  259. pythinker_code/ui/shell/visualize/_approval_panel.py +539 -0
  260. pythinker_code/ui/shell/visualize/_blocks.py +802 -0
  261. pythinker_code/ui/shell/visualize/_btw_panel.py +227 -0
  262. pythinker_code/ui/shell/visualize/_input_router.py +48 -0
  263. pythinker_code/ui/shell/visualize/_interactive.py +531 -0
  264. pythinker_code/ui/shell/visualize/_live_view.py +891 -0
  265. pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
  266. pythinker_code/ui/shell/visualize/_worklog.py +245 -0
  267. pythinker_code/ui/theme.py +395 -0
  268. pythinker_code/ui/tui_config.py +82 -0
  269. pythinker_code/usage_ratelimit_cache.py +175 -0
  270. pythinker_code/utils/__init__.py +0 -0
  271. pythinker_code/utils/aiohttp.py +24 -0
  272. pythinker_code/utils/aioqueue.py +72 -0
  273. pythinker_code/utils/broadcast.py +38 -0
  274. pythinker_code/utils/changelog.py +108 -0
  275. pythinker_code/utils/clipboard.py +246 -0
  276. pythinker_code/utils/datetime.py +64 -0
  277. pythinker_code/utils/diff.py +135 -0
  278. pythinker_code/utils/editor.py +91 -0
  279. pythinker_code/utils/environment.py +73 -0
  280. pythinker_code/utils/envvar.py +22 -0
  281. pythinker_code/utils/export.py +696 -0
  282. pythinker_code/utils/file_filter.py +375 -0
  283. pythinker_code/utils/frontmatter.py +70 -0
  284. pythinker_code/utils/io.py +27 -0
  285. pythinker_code/utils/logging.py +146 -0
  286. pythinker_code/utils/media_tags.py +29 -0
  287. pythinker_code/utils/message.py +24 -0
  288. pythinker_code/utils/path.py +199 -0
  289. pythinker_code/utils/proctitle.py +33 -0
  290. pythinker_code/utils/proxy.py +31 -0
  291. pythinker_code/utils/pyinstaller.py +45 -0
  292. pythinker_code/utils/rich/__init__.py +33 -0
  293. pythinker_code/utils/rich/columns.py +99 -0
  294. pythinker_code/utils/rich/diff_render.py +481 -0
  295. pythinker_code/utils/rich/markdown.py +935 -0
  296. pythinker_code/utils/rich/markdown_sample.md +108 -0
  297. pythinker_code/utils/rich/markdown_sample_short.md +2 -0
  298. pythinker_code/utils/rich/syntax.py +114 -0
  299. pythinker_code/utils/sensitive.py +54 -0
  300. pythinker_code/utils/server.py +121 -0
  301. pythinker_code/utils/signals.py +43 -0
  302. pythinker_code/utils/slashcmd.py +124 -0
  303. pythinker_code/utils/string.py +41 -0
  304. pythinker_code/utils/subprocess_env.py +83 -0
  305. pythinker_code/utils/term.py +168 -0
  306. pythinker_code/utils/typing.py +20 -0
  307. pythinker_code/vis/__init__.py +0 -0
  308. pythinker_code/vis/api/__init__.py +5 -0
  309. pythinker_code/vis/api/sessions.py +714 -0
  310. pythinker_code/vis/api/statistics.py +209 -0
  311. pythinker_code/vis/api/system.py +19 -0
  312. pythinker_code/vis/app.py +199 -0
  313. pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-CY1rtwrX.js +1 -0
  314. pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
  315. pythinker_code/vis/static/assets/index-DgmTI2M_.js +185 -0
  316. pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  317. pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  318. pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  319. pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  320. pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  321. pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  322. pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  323. pythinker_code/vis/static/index.html +17 -0
  324. pythinker_code/web/__init__.py +5 -0
  325. pythinker_code/web/api/__init__.py +15 -0
  326. pythinker_code/web/api/config.py +217 -0
  327. pythinker_code/web/api/open_in.py +233 -0
  328. pythinker_code/web/api/sessions.py +1256 -0
  329. pythinker_code/web/app.py +449 -0
  330. pythinker_code/web/auth.py +191 -0
  331. pythinker_code/web/models.py +98 -0
  332. pythinker_code/web/runner/__init__.py +5 -0
  333. pythinker_code/web/runner/messages.py +57 -0
  334. pythinker_code/web/runner/process.py +754 -0
  335. pythinker_code/web/runner/worker.py +97 -0
  336. pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  337. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  338. pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  339. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  340. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  341. pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  342. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  343. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  344. pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  345. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  346. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  347. pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  348. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  349. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  350. pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  351. pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  352. pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  353. pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  354. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  355. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  356. pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  357. pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  358. pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  359. pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  360. pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  361. pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  362. pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  363. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  364. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  365. pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  366. pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  367. pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  368. pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  369. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  370. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  371. pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  372. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  373. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  374. pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  375. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  376. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  377. pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  378. pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  379. pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  380. pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  381. pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  382. pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  383. pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  384. pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  385. pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  386. pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  387. pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  388. pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  389. pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  390. pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  391. pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  392. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  393. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  394. pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  395. pythinker_code/web/static/assets/_baseUniq-DpSMr1jx.js +1 -0
  396. pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
  397. pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
  398. pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
  399. pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
  400. pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
  401. pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
  402. pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
  403. pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
  404. pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
  405. pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
  406. pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
  407. pythinker_code/web/static/assets/arc-DpsahJyV.js +1 -0
  408. pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-DqiRv9Eg.js +36 -0
  409. pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
  410. pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
  411. pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
  412. pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  413. pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
  414. pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
  415. pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
  416. pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
  417. pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
  418. pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
  419. pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
  420. pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
  421. pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
  422. pythinker_code/web/static/assets/blockDiagram-VD42YOAC-WgtUvqbp.js +122 -0
  423. pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
  424. pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
  425. pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
  426. pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-rK0RPuZd.js +10 -0
  427. pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
  428. pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
  429. pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  430. pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  431. pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  432. pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  433. pythinker_code/web/static/assets/channel-B0rlvkH-.js +1 -0
  434. pythinker_code/web/static/assets/chunk-4BX2VUAB-DIkMuLV-.js +1 -0
  435. pythinker_code/web/static/assets/chunk-55IACEB6-CORdm4k4.js +1 -0
  436. pythinker_code/web/static/assets/chunk-B4BG7PRW-D9xDhwHO.js +165 -0
  437. pythinker_code/web/static/assets/chunk-DI55MBZ5-BDmF9Bh-.js +220 -0
  438. pythinker_code/web/static/assets/chunk-FMBD7UC4-BCse_HmM.js +15 -0
  439. pythinker_code/web/static/assets/chunk-QN33PNHL-DCpBmTzA.js +1 -0
  440. pythinker_code/web/static/assets/chunk-QZHKN3VN-BqLuqobw.js +1 -0
  441. pythinker_code/web/static/assets/chunk-TZMSLE5B-8K2ogOKS.js +1 -0
  442. pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
  443. pythinker_code/web/static/assets/classDiagram-2ON5EDUG-D_ZHSii2.js +1 -0
  444. pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-D_ZHSii2.js +1 -0
  445. pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
  446. pythinker_code/web/static/assets/clone-GSXejyY1.js +1 -0
  447. pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
  448. pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
  449. pythinker_code/web/static/assets/code-block-IT6T5CEO-DWTFYA28.js +2 -0
  450. pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
  451. pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
  452. pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
  453. pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  454. pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
  455. pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-BRI7ES-N.js +1 -0
  456. pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
  457. pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
  458. pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
  459. pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
  460. pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
  461. pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
  462. pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
  463. pythinker_code/web/static/assets/cytoscape.esm-B6BxUuKW.js +321 -0
  464. pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
  465. pythinker_code/web/static/assets/dagre-6UL2VRFP-Ci5GdWfi.js +4 -0
  466. pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
  467. pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
  468. pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
  469. pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  470. pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
  471. pythinker_code/web/static/assets/diagram-PSM6KHXK-0hhAylV4.js +24 -0
  472. pythinker_code/web/static/assets/diagram-QEK2KX5R-8fxgaW6d.js +43 -0
  473. pythinker_code/web/static/assets/diagram-S2PKOQOG-FRr0_atE.js +24 -0
  474. pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
  475. pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
  476. pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
  477. pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
  478. pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  479. pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
  480. pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
  481. pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
  482. pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
  483. pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
  484. pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-B3T-hJUM.js +60 -0
  485. pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
  486. pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
  487. pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  488. pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
  489. pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
  490. pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
  491. pythinker_code/web/static/assets/flowDiagram-NV44I4VS-D0S3u7ot.js +162 -0
  492. pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
  493. pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  494. pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  495. pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
  496. pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-CHrN2a23.js +267 -0
  497. pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
  498. pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
  499. pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
  500. pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
  501. pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
  502. pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
  503. pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
  504. pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-CfcXZWg0.js +65 -0
  505. pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
  506. pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  507. pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  508. pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  509. pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
  510. pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
  511. pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  512. pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
  513. pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
  514. pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
  515. pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
  516. pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
  517. pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
  518. pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
  519. pythinker_code/web/static/assets/graph-8jMJwCqE.js +1 -0
  520. pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
  521. pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
  522. pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  523. pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  524. pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  525. pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  526. pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  527. pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  528. pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
  529. pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
  530. pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
  531. pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
  532. pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
  533. pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
  534. pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
  535. pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
  536. pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
  537. pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
  538. pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
  539. pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
  540. pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
  541. pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
  542. pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
  543. pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
  544. pythinker_code/web/static/assets/index-BXrFnzMy.js +153 -0
  545. pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
  546. pythinker_code/web/static/assets/index-BrfQJnRD.js +5 -0
  547. pythinker_code/web/static/assets/index-C4gFzubz.js +2 -0
  548. pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
  549. pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
  550. pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-DdxonBf3.js +2 -0
  551. pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
  552. pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
  553. pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  554. pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  555. pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  556. pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  557. pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  558. pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  559. pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  560. pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
  561. pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
  562. pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
  563. pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
  564. pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-BXf4aQei.js +139 -0
  565. pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
  566. pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
  567. pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
  568. pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
  569. pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
  570. pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
  571. pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
  572. pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
  573. pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  574. pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  575. pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  576. pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-DLpPPOu8.js +89 -0
  577. pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
  578. pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
  579. pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
  580. pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
  581. pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
  582. pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
  583. pythinker_code/web/static/assets/layout-DH73UoAH.js +1 -0
  584. pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
  585. pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
  586. pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
  587. pythinker_code/web/static/assets/linear-bAer2-sK.js +1 -0
  588. pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
  589. pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
  590. pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
  591. pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
  592. pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
  593. pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
  594. pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
  595. pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
  596. pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
  597. pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
  598. pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  599. pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  600. pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  601. pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  602. pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
  603. pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
  604. pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
  605. pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
  606. pythinker_code/web/static/assets/mermaid-VLURNSYL-CuqbwKXv.js +465 -0
  607. pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
  608. pythinker_code/web/static/assets/mermaid.core-Nx-rTKiV.js +191 -0
  609. pythinker_code/web/static/assets/min-DbfD8Ywu.js +1 -0
  610. pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
  611. pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
  612. pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-C6l761Ue.js +68 -0
  613. pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
  614. pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
  615. pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
  616. pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
  617. pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
  618. pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
  619. pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
  620. pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
  621. pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
  622. pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
  623. pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
  624. pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
  625. pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
  626. pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
  627. pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
  628. pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
  629. pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  630. pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
  631. pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
  632. pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
  633. pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
  634. pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
  635. pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
  636. pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-fNg41mT9.js +30 -0
  637. pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
  638. pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
  639. pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
  640. pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
  641. pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
  642. pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
  643. pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
  644. pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
  645. pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
  646. pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
  647. pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
  648. pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
  649. pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
  650. pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
  651. pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
  652. pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
  653. pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
  654. pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
  655. pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
  656. pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DJz3Kx87.js +7 -0
  657. pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
  658. pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
  659. pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
  660. pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
  661. pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
  662. pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
  663. pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
  664. pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
  665. pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-B4SbrfE9.js +64 -0
  666. pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
  667. pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  668. pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  669. pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  670. pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
  671. pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
  672. pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
  673. pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
  674. pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-CoSUjLAG.js +10 -0
  675. pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
  676. pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
  677. pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
  678. pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
  679. pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
  680. pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
  681. pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-PjhBNHi3.js +145 -0
  682. pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  683. pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
  684. pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
  685. pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
  686. pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
  687. pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
  688. pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
  689. pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  690. pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
  691. pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
  692. pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
  693. pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
  694. pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
  695. pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
  696. pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
  697. pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
  698. pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DOwESt8-.js +1 -0
  699. pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-yl3OHWiP.js +1 -0
  700. pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
  701. pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
  702. pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
  703. pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  704. pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
  705. pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
  706. pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
  707. pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
  708. pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
  709. pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
  710. pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
  711. pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
  712. pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-CkCLnAgi.js +61 -0
  713. pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
  714. pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
  715. pythinker_code/web/static/assets/treemap-KMMF4GRG-CZS5XwTf.js +128 -0
  716. pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
  717. pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
  718. pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
  719. pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
  720. pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
  721. pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
  722. pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
  723. pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
  724. pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
  725. pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
  726. pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
  727. pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
  728. pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
  729. pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
  730. pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
  731. pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  732. pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  733. pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  734. pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
  735. pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
  736. pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
  737. pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
  738. pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
  739. pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
  740. pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
  741. pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
  742. pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
  743. pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
  744. pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
  745. pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
  746. pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
  747. pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-DkqqHNLh.js +7 -0
  748. pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
  749. pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
  750. pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
  751. pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
  752. pythinker_code/web/static/brand/arctecture.webp +0 -0
  753. pythinker_code/web/static/brand/bimi-logo.svg +46 -0
  754. pythinker_code/web/static/brand/favicon.ico +0 -0
  755. pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
  756. pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
  757. pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
  758. pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
  759. pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
  760. pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
  761. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
  762. pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
  763. pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
  764. pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
  765. pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
  766. pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
  767. pythinker_code/web/static/brand/icon-192.png +0 -0
  768. pythinker_code/web/static/brand/icon-512.png +0 -0
  769. pythinker_code/web/static/brand/icon.svg +1 -0
  770. pythinker_code/web/static/brand/logo.png +0 -0
  771. pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
  772. pythinker_code/web/static/brand/robots.txt +4 -0
  773. pythinker_code/web/static/index.html +15 -0
  774. pythinker_code/web/static/logo.png +0 -0
  775. pythinker_code/web/store/__init__.py +1 -0
  776. pythinker_code/web/store/sessions.py +433 -0
  777. pythinker_code/wire/__init__.py +148 -0
  778. pythinker_code/wire/file.py +151 -0
  779. pythinker_code/wire/jsonrpc.py +263 -0
  780. pythinker_code/wire/protocol.py +2 -0
  781. pythinker_code/wire/root_hub.py +27 -0
  782. pythinker_code/wire/serde.py +26 -0
  783. pythinker_code/wire/server.py +1072 -0
  784. pythinker_code/wire/types.py +698 -0
  785. pythinker_code-0.8.0.dist-info/METADATA +706 -0
  786. pythinker_code-0.8.0.dist-info/RECORD +790 -0
  787. pythinker_code-0.8.0.dist-info/WHEEL +4 -0
  788. pythinker_code-0.8.0.dist-info/entry_points.txt +4 -0
  789. pythinker_code-0.8.0.dist-info/licenses/LICENSE +202 -0
  790. pythinker_code-0.8.0.dist-info/licenses/NOTICE +14 -0
@@ -0,0 +1,891 @@
1
+ # pyright: reportPrivateUsage=false, reportUnusedClass=false
2
+ """Base event-consuming view for the streaming agent (Rich Live mode).
3
+
4
+ ``_LiveView`` consumes wire events, updates internal state (content blocks,
5
+ tool calls, spinners, approval/question queues), and composes them into a
6
+ Rich renderable via ``compose()``. The Rich ``Live`` context drives refresh.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import time
13
+ from collections import deque
14
+ from collections.abc import Awaitable, Callable
15
+ from contextlib import asynccontextmanager, suppress
16
+ from typing import Literal
17
+
18
+ from pythinker_core.message import Message
19
+ from pythinker_core.tooling import ToolError, ToolOk
20
+ from rich.console import Group, RenderableType
21
+ from rich.live import Live
22
+ from rich.markup import escape as rich_escape
23
+ from rich.panel import Panel
24
+ from rich.style import Style
25
+ from rich.text import Text
26
+
27
+ from pythinker_code.ui.shell.console import console
28
+ from pythinker_code.ui.shell.echo import render_user_echo
29
+ from pythinker_code.ui.shell.keyboard import KeyboardListener, KeyEvent
30
+ from pythinker_code.ui.shell.spinner_words import spinner_frame, spinner_message
31
+ from pythinker_code.ui.shell.visualize._approval_panel import (
32
+ ApprovalRequestPanel,
33
+ show_approval_in_pager,
34
+ )
35
+ from pythinker_code.ui.shell.visualize._blocks import (
36
+ Markdown,
37
+ _CompactionBlock,
38
+ _ContentBlock,
39
+ _NotificationBlock,
40
+ _StatusBlock,
41
+ _ToolCallBlock,
42
+ )
43
+ from pythinker_code.ui.shell.visualize._question_panel import (
44
+ QuestionRequestPanel,
45
+ prompt_other_input,
46
+ show_question_body_in_pager,
47
+ )
48
+ from pythinker_code.ui.shell.visualize._worklog import render_worklog_card
49
+ from pythinker_code.ui.tui_config import is_card_style
50
+ from pythinker_code.utils.aioqueue import Queue, QueueShutDown
51
+ from pythinker_code.utils.logging import logger
52
+ from pythinker_code.wire import WireUISide
53
+ from pythinker_code.wire.types import (
54
+ ApprovalRequest,
55
+ ApprovalResponse,
56
+ BtwBegin,
57
+ BtwEnd,
58
+ CompactionBegin,
59
+ CompactionEnd,
60
+ ContentPart,
61
+ MCPLoadingBegin,
62
+ MCPLoadingEnd,
63
+ Notification,
64
+ PlanDisplay,
65
+ QuestionRequest,
66
+ StatusUpdate,
67
+ SteerInput,
68
+ StepBegin,
69
+ StepInterrupted,
70
+ SubagentEvent,
71
+ TextPart,
72
+ ThinkPart,
73
+ ToolCall,
74
+ ToolCallPart,
75
+ ToolCallRequest,
76
+ ToolResult,
77
+ TurnBegin,
78
+ TurnEnd,
79
+ WireMessage,
80
+ )
81
+
82
+ MAX_LIVE_NOTIFICATIONS = 4
83
+ EXTERNAL_MESSAGE_GRACE_S = 0.1
84
+ _LIVE_VERTICAL_OVERFLOW: Literal["crop", "ellipsis", "visible"] = "ellipsis"
85
+
86
+
87
+ @asynccontextmanager
88
+ async def _keyboard_listener(
89
+ handler: Callable[[KeyboardListener, KeyEvent], Awaitable[None]],
90
+ ):
91
+ listener = KeyboardListener()
92
+ await listener.start()
93
+
94
+ async def _keyboard():
95
+ while True:
96
+ event = await listener.get()
97
+ await handler(listener, event)
98
+
99
+ task = asyncio.create_task(_keyboard())
100
+ try:
101
+ yield
102
+ finally:
103
+ task.cancel()
104
+ with suppress(asyncio.CancelledError):
105
+ await task
106
+ await listener.stop()
107
+
108
+
109
+ class _LiveView:
110
+ def __init__(
111
+ self,
112
+ initial_status: StatusUpdate,
113
+ cancel_event: asyncio.Event | None = None,
114
+ *,
115
+ show_thinking_stream: bool = False,
116
+ ):
117
+ self._cancel_event = cancel_event
118
+ self._show_thinking_stream = show_thinking_stream
119
+
120
+ self._active_turn_depth = 0
121
+ self._compaction_block: _CompactionBlock | None = None
122
+ self._mcp_loading_spinner: RenderableType | None = None
123
+ self._btw_spinner: RenderableType | None = None
124
+ self._btw_question: str | None = None
125
+
126
+ self._current_content_block: _ContentBlock | None = None
127
+ self._tool_call_blocks: dict[str, _ToolCallBlock] = {}
128
+ self._last_tool_call_block: _ToolCallBlock | None = None
129
+ self._approval_request_queue = deque[ApprovalRequest]()
130
+ """
131
+ It is possible that multiple subagents request approvals at the same time,
132
+ in which case we will have to queue them up and show them one by one.
133
+ """
134
+ self._current_approval_request_panel: ApprovalRequestPanel | None = None
135
+ self._question_request_queue = deque[QuestionRequest]()
136
+ self._current_question_panel: QuestionRequestPanel | None = None
137
+ self._notification_blocks = deque[_NotificationBlock]()
138
+ self._live_notification_blocks = deque[_NotificationBlock](maxlen=MAX_LIVE_NOTIFICATIONS)
139
+ self._status_block = _StatusBlock(initial_status)
140
+
141
+ self._need_recompose = False
142
+ self._external_messages: Queue[WireMessage] = Queue()
143
+
144
+ def _reset_live_shape(self, live: Live) -> None:
145
+ # Rich doesn't expose a public API to clear Live's cached render height.
146
+ # After leaving the pager, stale height causes cursor restores to jump,
147
+ # so we reset the private _shape to re-anchor the next refresh.
148
+ live._live_render._shape = None # type: ignore[reportPrivateUsage]
149
+
150
+ async def _drain_external_message_after_wire_shutdown(
151
+ self,
152
+ external_task: asyncio.Task[WireMessage],
153
+ ) -> tuple[WireMessage | None, asyncio.Task[WireMessage]]:
154
+ try:
155
+ msg = await asyncio.wait_for(
156
+ asyncio.shield(external_task),
157
+ timeout=EXTERNAL_MESSAGE_GRACE_S,
158
+ )
159
+ except (TimeoutError, QueueShutDown):
160
+ return None, external_task
161
+ return msg, asyncio.create_task(self._external_messages.get())
162
+
163
+ async def visualize_loop(self, wire: WireUISide):
164
+ with Live(
165
+ self.compose(),
166
+ console=console,
167
+ refresh_per_second=10,
168
+ transient=True,
169
+ # Never let the transient Live region paint beyond the terminal
170
+ # viewport. Interactive prompt mode has its own row budget; this
171
+ # protects non-interactive Rich Live mode from tall tool cards,
172
+ # approval panels, or streaming output overlapping the screen.
173
+ vertical_overflow=_LIVE_VERTICAL_OVERFLOW,
174
+ ) as live:
175
+
176
+ async def keyboard_handler(listener: KeyboardListener, event: KeyEvent) -> None:
177
+ # Handle Ctrl+E specially - pause Live while the pager is active
178
+ if event == KeyEvent.CTRL_E:
179
+ if self.has_expandable_panel():
180
+ from pythinker_code.telemetry import track
181
+
182
+ track("shortcut_expand")
183
+ await listener.pause()
184
+ live.stop()
185
+ try:
186
+ self._show_expandable_panel_content()
187
+ finally:
188
+ # Reset live render shape so the next refresh re-anchors cleanly.
189
+ self._reset_live_shape(live)
190
+ live.start()
191
+ live.update(self.compose(), refresh=True)
192
+ await listener.resume()
193
+ return
194
+
195
+ # Handle ENTER/SPACE on question panel when "Other" is selected
196
+ if self._should_prompt_question_other_for_key(event):
197
+ panel = self._current_question_panel
198
+ assert panel is not None
199
+ question_text = panel.current_question_text
200
+ await listener.pause()
201
+ live.stop()
202
+ try:
203
+ text = await prompt_other_input(question_text)
204
+ finally:
205
+ self._reset_live_shape(live)
206
+ live.start()
207
+ await listener.resume()
208
+
209
+ self._submit_question_other_text(text)
210
+ live.update(self.compose(), refresh=True)
211
+ return
212
+
213
+ self.dispatch_keyboard_event(event)
214
+ if self._need_recompose:
215
+ live.update(self.compose(), refresh=True)
216
+ self._need_recompose = False
217
+
218
+ async with _keyboard_listener(keyboard_handler):
219
+ wire_task = asyncio.create_task(wire.receive())
220
+ external_task = asyncio.create_task(self._external_messages.get())
221
+ try:
222
+ while True:
223
+ try:
224
+ done, _ = await asyncio.wait(
225
+ [wire_task, external_task],
226
+ return_when=asyncio.FIRST_COMPLETED,
227
+ )
228
+ if wire_task in done:
229
+ msg = wire_task.result()
230
+ wire_task = asyncio.create_task(wire.receive())
231
+ else:
232
+ msg = external_task.result()
233
+ external_task = asyncio.create_task(self._external_messages.get())
234
+ except QueueShutDown:
235
+ (
236
+ msg,
237
+ external_task,
238
+ ) = await self._drain_external_message_after_wire_shutdown(
239
+ external_task
240
+ )
241
+ if msg is not None:
242
+ self.dispatch_wire_message(msg)
243
+ if self._need_recompose:
244
+ live.update(self.compose(), refresh=True)
245
+ self._need_recompose = False
246
+ continue
247
+ self.cleanup(is_interrupt=False)
248
+ live.update(self.compose(), refresh=True)
249
+ break
250
+
251
+ if isinstance(msg, StepInterrupted):
252
+ self.cleanup(is_interrupt=True)
253
+ live.update(self.compose(), refresh=True)
254
+ break
255
+
256
+ self.dispatch_wire_message(msg)
257
+ if self._need_recompose:
258
+ live.update(self.compose(), refresh=True)
259
+ self._need_recompose = False
260
+ finally:
261
+ wire_task.cancel()
262
+ external_task.cancel()
263
+ self._external_messages.shutdown(immediate=True)
264
+ with suppress(asyncio.CancelledError, QueueShutDown):
265
+ await wire_task
266
+ with suppress(asyncio.CancelledError, QueueShutDown):
267
+ await external_task
268
+
269
+ def refresh_soon(self) -> None:
270
+ self._need_recompose = True
271
+
272
+ def _on_question_panel_state_changed(self) -> None:
273
+ """Hook for subclasses to react when question panel visibility changes."""
274
+ return None
275
+
276
+ def enqueue_external_message(self, msg: WireMessage) -> None:
277
+ try:
278
+ self._external_messages.put_nowait(msg)
279
+ except QueueShutDown:
280
+ logger.debug("Ignoring external wire message after live view shutdown: {msg}", msg=msg)
281
+
282
+ def has_expandable_panel(self) -> bool:
283
+ return (
284
+ self._expandable_approval_panel() is not None
285
+ or self._expandable_question_panel() is not None
286
+ )
287
+
288
+ def _expandable_approval_panel(self) -> ApprovalRequestPanel | None:
289
+ panel = self._current_approval_request_panel
290
+ if panel is not None and panel.has_expandable_content:
291
+ return panel
292
+ return None
293
+
294
+ def _expandable_question_panel(self) -> QuestionRequestPanel | None:
295
+ panel = self._current_question_panel
296
+ if panel is not None and panel.has_expandable_content:
297
+ return panel
298
+ return None
299
+
300
+ def _show_expandable_panel_content(self) -> bool:
301
+ if approval_panel := self._expandable_approval_panel():
302
+ show_approval_in_pager(approval_panel)
303
+ return True
304
+ if question_panel := self._expandable_question_panel():
305
+ show_question_body_in_pager(question_panel)
306
+ return True
307
+ return False
308
+
309
+ def _should_prompt_question_other_for_key(self, key: KeyEvent) -> bool:
310
+ panel = self._current_question_panel
311
+ if panel is None or not panel.should_prompt_other_input():
312
+ return False
313
+ return key == KeyEvent.ENTER or (key == KeyEvent.SPACE and not panel.is_multi_select)
314
+
315
+ def _submit_question_other_text(self, text: str) -> None:
316
+ panel = self._current_question_panel
317
+ if panel is None:
318
+ return
319
+
320
+ all_done = panel.submit_other(text)
321
+ if all_done:
322
+ panel.request.resolve(panel.get_answers())
323
+ self.show_next_question_request()
324
+ self.refresh_soon()
325
+
326
+ # -- Composable rendering --------------------------------------------------
327
+
328
+ def compose_interactive_panels(self) -> list[RenderableType]:
329
+ """Approval and question panels — interactive overlays.
330
+
331
+ In Non-interactive mode (Rich Live), these are rendered by ``compose()``.
332
+ In Interactive mode (prompt_toolkit), these are rendered by modal
333
+ delegates in Layer 2, so ``render_agent_status()`` skips them to
334
+ avoid double-rendering.
335
+ """
336
+ blocks: list[RenderableType] = []
337
+ if self._current_approval_request_panel:
338
+ blocks.append(self._current_approval_request_panel.render())
339
+ if self._current_question_panel:
340
+ blocks.append(self._current_question_panel.render())
341
+ return blocks
342
+
343
+ def compose_agent_output(self) -> list[RenderableType]:
344
+ """Spinners, content blocks, tool calls, notifications.
345
+
346
+ Pure agent streaming status — no interactive overlays.
347
+ Always safe to render regardless of modal state.
348
+
349
+ Display priority (highest → lowest):
350
+ 1. MCP loading spinner (connecting to servers)
351
+ 2. Compaction spinner (context compaction in progress)
352
+ 3. Content blocks + tool call blocks (streaming output)
353
+ 4. Moon spinner fallback (turn active but nothing else visible)
354
+ The btw spinner is always shown (side-channel, not mutually exclusive).
355
+ """
356
+ blocks: list[RenderableType] = []
357
+ if self._btw_spinner is not None:
358
+ blocks.append(self._btw_spinner)
359
+ if self._mcp_loading_spinner is not None:
360
+ blocks.append(self._mcp_loading_spinner)
361
+ elif self._compaction_block is not None:
362
+ blocks.append(self._compaction_block)
363
+ else:
364
+ has_main_content = False
365
+ if self._current_content_block is not None:
366
+ blocks.append(self._current_content_block.compose())
367
+ has_main_content = True
368
+ # When an approval panel is on-screen for a specific tool call, the
369
+ # panel already previews the same command/diff that the pending tool
370
+ # card would show. Suppress the matching card to avoid the duplicate.
371
+ suppressed_tool_call_id: str | None = None
372
+ if self._current_approval_request_panel is not None:
373
+ suppressed_tool_call_id = self._current_approval_request_panel.request.tool_call_id
374
+ first_tool_block = True
375
+ for tool_call in list(self._tool_call_blocks.values()):
376
+ if (
377
+ suppressed_tool_call_id is not None
378
+ and tool_call.tool_call_id == suppressed_tool_call_id
379
+ ):
380
+ continue
381
+ # Blank-line spacer between consecutive tool cards so adjacent
382
+ # tinted backgrounds don't visually merge into one block. Use a
383
+ # single space rather than an empty Text so Rich keeps the row.
384
+ if not first_tool_block and is_card_style():
385
+ blocks.append(Text(" "))
386
+ blocks.append(tool_call.compose())
387
+ has_main_content = True
388
+ first_tool_block = False
389
+ if self._active_turn_depth > 0:
390
+ # Keep a stable activity indicator visible even while content or
391
+ # tool cards are already on-screen. This makes long-running
392
+ # background waits feel alive instead of frozen.
393
+ if has_main_content and is_card_style():
394
+ blocks.append(Text(" "))
395
+ blocks.append(self._working_indicator())
396
+ for notification in list(self._live_notification_blocks):
397
+ blocks.append(notification.compose())
398
+ return blocks
399
+
400
+ def _working_indicator(self) -> Text:
401
+ # Use manual frame selection instead of Rich Spinner here. This path is
402
+ # re-rendered by prompt_toolkit/Rich Live, and constructing a fresh Rich
403
+ # Spinner each render resets its internal clock, making the glyph look
404
+ # stuck next to the rotating verb.
405
+ line = Text(f"{spinner_frame()} ", style="cyan")
406
+ line.append(spinner_message(), style="grey50")
407
+ return line
408
+
409
+ def compose(self, *, include_status: bool = True) -> RenderableType:
410
+ """Compose the full live view display content.
411
+
412
+ Combines interactive panels (approval/question) and agent output.
413
+ Panels are rendered first so they remain visible at the top of the
414
+ terminal even when tool-call output is long enough to push content
415
+ beyond the visible area.
416
+
417
+ In Interactive mode, prefer ``compose_agent_output()`` for Layer 1
418
+ rendering to avoid double-rendering panels that modal delegates
419
+ already handle in Layer 2.
420
+ """
421
+ blocks: list[RenderableType] = []
422
+ blocks.extend(self.compose_interactive_panels())
423
+ blocks.extend(self.compose_agent_output())
424
+ if include_status:
425
+ blocks.append(self._status_block.render())
426
+ return Group(*blocks)
427
+
428
+ def dispatch_wire_message(self, msg: WireMessage) -> None:
429
+ """Dispatch the Wire message to UI components."""
430
+ assert not isinstance(msg, StepInterrupted) # handled in visualize_loop
431
+
432
+ if isinstance(msg, StepBegin):
433
+ self.cleanup(is_interrupt=False)
434
+ self._mcp_loading_spinner = None
435
+ # Defensive: if StepBegin arrives without a preceding TurnBegin
436
+ # (e.g. during replay), ensure the turn is considered active.
437
+ if self._active_turn_depth == 0:
438
+ self._active_turn_depth = 1
439
+ self.refresh_soon()
440
+ return
441
+
442
+ match msg:
443
+ case TurnBegin():
444
+ self._active_turn_depth += 1
445
+ self.flush_content()
446
+ self.refresh_soon()
447
+ case SteerInput(user_input=user_input):
448
+ self.cleanup(is_interrupt=False)
449
+ content: list[ContentPart]
450
+ if isinstance(user_input, list):
451
+ content = list(user_input)
452
+ else:
453
+ content = [TextPart(text=user_input)]
454
+ console.print(render_user_echo(Message(role="user", content=content)))
455
+ case TurnEnd():
456
+ self._active_turn_depth = max(0, self._active_turn_depth - 1)
457
+ case CompactionBegin():
458
+ self._compaction_block = _CompactionBlock()
459
+ self.refresh_soon()
460
+ case CompactionEnd():
461
+ self._compaction_block = None
462
+ self.refresh_soon()
463
+ case MCPLoadingBegin():
464
+ glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
465
+ line = Text(f"{glyph} ", style=Style(color="grey50"))
466
+ line.append("Connecting MCP servers...", style="grey50")
467
+ self._mcp_loading_spinner = line
468
+ self.refresh_soon()
469
+ case MCPLoadingEnd():
470
+ self._mcp_loading_spinner = None
471
+ self.refresh_soon()
472
+ case BtwBegin(question=question):
473
+ truncated = (question[:40] + "...") if len(question) > 40 else question
474
+ self._btw_question = question
475
+ glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
476
+ line = Text(f"{glyph} ", style=Style(color="grey50"))
477
+ line.append(f"Side question... {truncated}", style="grey50")
478
+ self._btw_spinner = line
479
+ self.refresh_soon()
480
+ case BtwEnd(response=response, error=error):
481
+ self._btw_spinner = None
482
+ q = self._btw_question or ""
483
+ truncated_q = (q[:50] + "...") if len(q) > 50 else q
484
+ self._btw_question = None
485
+ if response:
486
+ console.print(
487
+ Panel(
488
+ Markdown(response),
489
+ title=f"[dim]btw: {rich_escape(truncated_q)}[/dim]",
490
+ border_style="grey50",
491
+ padding=(0, 1),
492
+ )
493
+ )
494
+ elif error:
495
+ console.print(
496
+ Panel(
497
+ Text(error, style="red"),
498
+ title="[dim]btw (error)[/dim]",
499
+ border_style="red",
500
+ padding=(0, 1),
501
+ )
502
+ )
503
+ self.refresh_soon()
504
+ case StatusUpdate():
505
+ self._status_block.update(msg)
506
+ case Notification():
507
+ self.append_notification(msg)
508
+ case ContentPart():
509
+ self.append_content(msg)
510
+ case ToolCall():
511
+ self.append_tool_call(msg)
512
+ case ToolCallPart():
513
+ self.append_tool_call_part(msg)
514
+ case ToolResult():
515
+ self.append_tool_result(msg)
516
+ case ApprovalResponse():
517
+ self._reconcile_approval_requests()
518
+ case SubagentEvent():
519
+ self.handle_subagent_event(msg)
520
+ case PlanDisplay():
521
+ self.display_plan(msg)
522
+ case ApprovalRequest():
523
+ self.request_approval(msg)
524
+ case QuestionRequest():
525
+ self.request_question(msg)
526
+ case ToolCallRequest():
527
+ logger.warning("Unexpected ToolCallRequest in shell UI: {msg}", msg=msg)
528
+ case _:
529
+ pass
530
+
531
+ def _try_submit_question(self, method: str = "enter") -> None:
532
+ """Submit the current question answer; if all done, resolve and advance."""
533
+ panel = self._current_question_panel
534
+ if panel is None:
535
+ return
536
+ all_done = panel.submit()
537
+ if all_done:
538
+ from pythinker_code.telemetry import track
539
+
540
+ track("question_answered", method=method)
541
+ panel.request.resolve(panel.get_answers())
542
+ self.show_next_question_request()
543
+
544
+ def dispatch_keyboard_event(self, event: KeyEvent) -> None:
545
+ # Handle question panel keyboard events
546
+ if self._current_question_panel is not None:
547
+ match event:
548
+ case KeyEvent.UP:
549
+ self._current_question_panel.move_up()
550
+ case KeyEvent.DOWN:
551
+ self._current_question_panel.move_down()
552
+ case KeyEvent.LEFT:
553
+ self._current_question_panel.prev_tab()
554
+ case KeyEvent.RIGHT | KeyEvent.TAB:
555
+ self._current_question_panel.next_tab()
556
+ case KeyEvent.SPACE:
557
+ if self._current_question_panel.is_multi_select:
558
+ self._current_question_panel.toggle_select()
559
+ else:
560
+ self._try_submit_question(method="space")
561
+ case KeyEvent.ENTER:
562
+ # "Other" is handled in keyboard_handler (async context)
563
+ self._try_submit_question(method="enter")
564
+ case KeyEvent.ESCAPE:
565
+ from pythinker_code.telemetry import track
566
+
567
+ track("question_dismissed")
568
+ self._current_question_panel.request.resolve({})
569
+ self.show_next_question_request()
570
+ case (
571
+ KeyEvent.NUM_1
572
+ | KeyEvent.NUM_2
573
+ | KeyEvent.NUM_3
574
+ | KeyEvent.NUM_4
575
+ | KeyEvent.NUM_5
576
+ | KeyEvent.NUM_6
577
+ ):
578
+ # Number keys select option in question panel
579
+ num_map = {
580
+ KeyEvent.NUM_1: 0,
581
+ KeyEvent.NUM_2: 1,
582
+ KeyEvent.NUM_3: 2,
583
+ KeyEvent.NUM_4: 3,
584
+ KeyEvent.NUM_5: 4,
585
+ KeyEvent.NUM_6: 5,
586
+ }
587
+ idx = num_map[event]
588
+ panel = self._current_question_panel
589
+ if panel.select_index(idx):
590
+ if panel.is_multi_select:
591
+ panel.toggle_select()
592
+ elif not panel.is_other_selected:
593
+ # Auto-submit for single-select (unless "Other")
594
+ self._try_submit_question(method="number_key")
595
+ case _:
596
+ pass
597
+ self.refresh_soon()
598
+ return
599
+
600
+ # handle ESC key to cancel the run
601
+ if event == KeyEvent.ESCAPE and self._cancel_event is not None:
602
+ from pythinker_code.telemetry import track
603
+
604
+ track("cancel")
605
+ self._cancel_event.set()
606
+ return
607
+
608
+ # Handle approval panel keyboard events
609
+ if self._current_approval_request_panel is not None:
610
+ match event:
611
+ case KeyEvent.UP:
612
+ self._current_approval_request_panel.move_up()
613
+ self.refresh_soon()
614
+ case KeyEvent.DOWN:
615
+ self._current_approval_request_panel.move_down()
616
+ self.refresh_soon()
617
+ case KeyEvent.ENTER:
618
+ self._submit_approval()
619
+ case KeyEvent.NUM_1 | KeyEvent.NUM_2 | KeyEvent.NUM_3 | KeyEvent.NUM_4:
620
+ # Number keys directly select and submit approval option
621
+ num_map = {
622
+ KeyEvent.NUM_1: 0,
623
+ KeyEvent.NUM_2: 1,
624
+ KeyEvent.NUM_3: 2,
625
+ KeyEvent.NUM_4: 3,
626
+ }
627
+ idx = num_map[event]
628
+ if idx < len(self._current_approval_request_panel.options):
629
+ self._current_approval_request_panel.selected_index = idx
630
+ self._submit_approval()
631
+ case _:
632
+ pass
633
+ return
634
+
635
+ def _submit_approval(self) -> None:
636
+ """Submit the currently selected approval response."""
637
+ assert self._current_approval_request_panel is not None
638
+ request = self._current_approval_request_panel.request
639
+ resp = self._current_approval_request_panel.get_selected_response()
640
+ request.resolve(resp)
641
+ if resp == "approve_for_session":
642
+ to_remove_from_queue: list[ApprovalRequest] = []
643
+ for request in self._approval_request_queue:
644
+ # approve all queued requests with the same action
645
+ if request.action == self._current_approval_request_panel.request.action:
646
+ request.resolve("approve_for_session")
647
+ to_remove_from_queue.append(request)
648
+ for request in to_remove_from_queue:
649
+ self._approval_request_queue.remove(request)
650
+ self.show_next_approval_request()
651
+
652
+ def cleanup(self, is_interrupt: bool) -> None:
653
+ """Cleanup the live view on step end or interruption."""
654
+ self.flush_content()
655
+
656
+ for block in self._tool_call_blocks.values():
657
+ if not block.finished:
658
+ # this should not happen, but just in case
659
+ block.finish(
660
+ ToolError(message="", brief="Interrupted")
661
+ if is_interrupt
662
+ else ToolOk(output="")
663
+ )
664
+ self._last_tool_call_block = None
665
+ self.flush_finished_tool_calls()
666
+ # Drain background-pending blocks skipped above. They must be printed
667
+ # to scrollback here; the transient Live area is about to be erased.
668
+ for tool_call_id in list(self._tool_call_blocks.keys()):
669
+ block = self._tool_call_blocks.pop(tool_call_id)
670
+ console.print()
671
+ console.print(block.compose())
672
+ self.refresh_soon()
673
+ self.flush_notifications()
674
+
675
+ # Clear transient spinners to prevent visual residuals after interrupts
676
+ self._compaction_block = None
677
+ self._mcp_loading_spinner = None
678
+ self._btw_spinner = None
679
+
680
+ if is_interrupt:
681
+ self._active_turn_depth = 0
682
+
683
+ while self._approval_request_queue:
684
+ # should not happen, but just in case
685
+ self._approval_request_queue.popleft().resolve("reject")
686
+ self._current_approval_request_panel = None
687
+
688
+ while self._question_request_queue:
689
+ self._question_request_queue.popleft().resolve({})
690
+ self._current_question_panel = None
691
+
692
+ def flush_content(self) -> None:
693
+ """Flush the current content block."""
694
+ if self._current_content_block is not None:
695
+ if self._current_content_block.has_pending():
696
+ console.print(self._current_content_block.compose_final())
697
+ self._current_content_block = None
698
+ self.refresh_soon()
699
+
700
+ def flush_finished_tool_calls(self) -> None:
701
+ """Flush all leading finished tool call blocks.
702
+
703
+ Background-pending blocks (Agent results with still-running status) are
704
+ skipped with ``continue`` instead of stopping the flush — they stay in
705
+ the Live area so their spinner keeps animating. Subsequent finished
706
+ blocks can still flush past them because background agents are async.
707
+ """
708
+ tool_call_ids = list(self._tool_call_blocks.keys())
709
+ for tool_call_id in tool_call_ids:
710
+ block = self._tool_call_blocks[tool_call_id]
711
+ if block.is_background_pending:
712
+ continue # stays in Live area; animated each refresh tick
713
+ if not block.finished:
714
+ break
715
+
716
+ self._tool_call_blocks.pop(tool_call_id)
717
+ console.print()
718
+ console.print(block.compose())
719
+ if self._last_tool_call_block == block:
720
+ self._last_tool_call_block = None
721
+ self.refresh_soon()
722
+
723
+ def flush_notifications(self) -> None:
724
+ """Flush rendered notifications to terminal history."""
725
+ self._live_notification_blocks.clear()
726
+ while self._notification_blocks:
727
+ console.print(self._notification_blocks.popleft().compose())
728
+ self.refresh_soon()
729
+
730
+ def append_content(self, part: ContentPart) -> None:
731
+ match part:
732
+ case ThinkPart(think=text) | TextPart(text=text):
733
+ is_think = isinstance(part, ThinkPart)
734
+ # Skip empty TextPart, but still create the block for empty
735
+ # ThinkPart so the "Thinking" indicator shows immediately
736
+ # (e.g. Anthropic/OpenAI block-start events yield think="").
737
+ if not text and not is_think:
738
+ return
739
+ if self._current_content_block is None:
740
+ self._current_content_block = _ContentBlock(
741
+ is_think, show_thinking_stream=self._show_thinking_stream
742
+ )
743
+ self.refresh_soon()
744
+ elif self._current_content_block.is_think != is_think:
745
+ self.flush_content()
746
+ self._current_content_block = _ContentBlock(
747
+ is_think, show_thinking_stream=self._show_thinking_stream
748
+ )
749
+ self.refresh_soon()
750
+ if text:
751
+ self._current_content_block.append(text)
752
+ self.refresh_soon()
753
+ case _:
754
+ # TODO: support more content part types
755
+ pass
756
+
757
+ def append_tool_call(self, tool_call: ToolCall) -> None:
758
+ self.flush_content()
759
+ self._tool_call_blocks[tool_call.id] = _ToolCallBlock(tool_call)
760
+ self._last_tool_call_block = self._tool_call_blocks[tool_call.id]
761
+ self.refresh_soon()
762
+
763
+ def append_tool_call_part(self, part: ToolCallPart) -> None:
764
+ if not part.arguments_part:
765
+ return
766
+ if self._last_tool_call_block is None:
767
+ return
768
+ self._last_tool_call_block.append_args_part(part.arguments_part)
769
+ self.refresh_soon()
770
+
771
+ def append_tool_result(self, result: ToolResult) -> None:
772
+ if block := self._tool_call_blocks.get(result.tool_call_id):
773
+ block.finish(result.return_value)
774
+ self.flush_finished_tool_calls()
775
+ self.refresh_soon()
776
+
777
+ def append_notification(self, notification: Notification) -> None:
778
+ block = _NotificationBlock(notification)
779
+ self._notification_blocks.append(block)
780
+ self._live_notification_blocks.append(block)
781
+ self.refresh_soon()
782
+
783
+ def request_approval(self, request: ApprovalRequest) -> None:
784
+ self._approval_request_queue.append(request)
785
+
786
+ if self._current_approval_request_panel is None:
787
+ console.bell()
788
+ self.show_next_approval_request()
789
+
790
+ def _reconcile_approval_requests(self) -> None:
791
+ self._approval_request_queue = deque(
792
+ request for request in self._approval_request_queue if not request.resolved
793
+ )
794
+ if (
795
+ self._current_approval_request_panel is not None
796
+ and self._current_approval_request_panel.request.resolved
797
+ ):
798
+ self._current_approval_request_panel = None
799
+ self.show_next_approval_request()
800
+ else:
801
+ self.refresh_soon()
802
+
803
+ def show_next_approval_request(self) -> None:
804
+ """
805
+ Show the next approval request from the queue.
806
+ If there are no pending requests, clear the current approval panel.
807
+ """
808
+ if not self._approval_request_queue:
809
+ if self._current_approval_request_panel is not None:
810
+ self._current_approval_request_panel = None
811
+ self.refresh_soon()
812
+ return
813
+
814
+ while self._approval_request_queue:
815
+ request = self._approval_request_queue.popleft()
816
+ if request.resolved:
817
+ # skip resolved requests
818
+ continue
819
+ self._current_approval_request_panel = ApprovalRequestPanel(request)
820
+ self.refresh_soon()
821
+ break
822
+ else:
823
+ # All queued requests were already resolved
824
+ if self._current_approval_request_panel is not None:
825
+ self._current_approval_request_panel = None
826
+ self.refresh_soon()
827
+
828
+ def display_plan(self, msg: PlanDisplay) -> None:
829
+ """Render plan content inline in the chat with a bordered panel."""
830
+ self.flush_content()
831
+ self.flush_finished_tool_calls()
832
+ plan_body = Markdown(msg.content)
833
+ panel = render_worklog_card(
834
+ "Plan",
835
+ plan_body,
836
+ subtitle=msg.file_path,
837
+ border_style="cyan",
838
+ )
839
+ console.print(panel)
840
+
841
+ def request_question(self, request: QuestionRequest) -> None:
842
+ self._question_request_queue.append(request)
843
+ if self._current_question_panel is None:
844
+ console.bell()
845
+ self.show_next_question_request()
846
+
847
+ def show_next_question_request(self) -> None:
848
+ """Show the next question request from the queue."""
849
+ if not self._question_request_queue:
850
+ if self._current_question_panel is not None:
851
+ self._current_question_panel = None
852
+ self.refresh_soon()
853
+ self._on_question_panel_state_changed()
854
+ return
855
+
856
+ while self._question_request_queue:
857
+ request = self._question_request_queue.popleft()
858
+ if request.resolved:
859
+ continue
860
+ self._current_question_panel = QuestionRequestPanel(request)
861
+ self.refresh_soon()
862
+ self._on_question_panel_state_changed()
863
+ break
864
+ else:
865
+ # All queued requests were already resolved
866
+ if self._current_question_panel is not None:
867
+ self._current_question_panel = None
868
+ self.refresh_soon()
869
+ self._on_question_panel_state_changed()
870
+
871
+ def handle_subagent_event(self, event: SubagentEvent) -> None:
872
+ if event.parent_tool_call_id is None:
873
+ return
874
+ block = self._tool_call_blocks.get(event.parent_tool_call_id)
875
+ if block is None:
876
+ return
877
+ if event.agent_id is not None and event.subagent_type is not None:
878
+ block.set_subagent_metadata(event.agent_id, event.subagent_type)
879
+
880
+ match event.event:
881
+ case ToolCall() as tool_call:
882
+ block.append_sub_tool_call(tool_call)
883
+ case ToolCallPart() as tool_call_part:
884
+ block.append_sub_tool_call_part(tool_call_part)
885
+ case ToolResult() as tool_result:
886
+ block.finish_sub_tool_call(tool_result)
887
+ self.refresh_soon()
888
+ case _:
889
+ # ignore other events for now
890
+ # TODO: may need to handle multi-level nested subagents
891
+ pass