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