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,802 @@
1
+ # pyright: reportUnusedClass=false
2
+ """Renderable block components for the streaming agent view.
3
+
4
+ Each block receives data via method calls and produces Rich renderables.
5
+ They have no knowledge of the event loop or prompt_toolkit.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import random
12
+ import time
13
+ from collections import deque
14
+ from typing import TYPE_CHECKING, Any, NamedTuple, cast
15
+
16
+ if TYPE_CHECKING:
17
+ from markdown_it import MarkdownIt
18
+
19
+ import streamingjson # type: ignore[reportMissingTypeStubs]
20
+ from rich.console import Console, ConsoleOptions, Group, RenderableType, RenderResult
21
+ from rich.style import Style
22
+ from rich.text import Text
23
+
24
+ from pythinker_code.soul import format_context_status, format_token_count
25
+ from pythinker_code.tools import extract_key_argument
26
+ from pythinker_code.ui.shell.components import ToolExecutionComponent
27
+ from pythinker_code.ui.shell.console import console
28
+ from pythinker_code.ui.shell.tool_renderers import (
29
+ ToolResultPayload,
30
+ get_tool_renderer,
31
+ )
32
+ from pythinker_code.ui.shell.tool_renderers.generic import generic_renderer
33
+ from pythinker_code.ui.shell.visualize._worklog import (
34
+ WorkLogState,
35
+ denied_error,
36
+ render_display_blocks,
37
+ render_worklog_entry,
38
+ tool_style,
39
+ )
40
+ from pythinker_code.ui.theme import tui_rich_style
41
+ from pythinker_code.ui.tui_config import is_card_style
42
+ from pythinker_code.utils.datetime import format_elapsed
43
+ from pythinker_code.utils.rich.columns import BulletColumns
44
+ from pythinker_code.utils.rich.markdown import Markdown
45
+ from pythinker_code.wire.types import (
46
+ MCPStatusSnapshot,
47
+ Notification,
48
+ StatusUpdate,
49
+ ToolCall,
50
+ ToolCallPart,
51
+ ToolResult,
52
+ ToolReturnValue,
53
+ )
54
+
55
+ _ELLIPSIS = "..."
56
+ _THINKING_PREVIEW_LINES = 6
57
+ _SELF_CLOSING_BLOCKS = frozenset(("fence", "code_block", "hr", "html_block"))
58
+ MAX_SUBAGENT_TOOL_CALLS_TO_SHOW = 4
59
+
60
+ # Background-agent statuses that mean "still running" — the tool call result
61
+ # has arrived but the spawned agent has not yet finished. Blocks with this
62
+ # status must stay in the Live area so their spinner keeps animating.
63
+ _AGENT_ACTIVE_STATUSES = frozenset({"created", "starting", "running", "awaiting_approval"})
64
+
65
+
66
+ def _is_active_background_agent(tool_name: str, result_text: str) -> bool:
67
+ """Return True when result_text represents a still-running background Agent."""
68
+ if tool_name != "Agent":
69
+ return False
70
+ values: dict[str, str] = {}
71
+ for line in result_text.splitlines():
72
+ if ":" in line:
73
+ k, _, v = line.partition(":")
74
+ values[k.strip()] = v.strip()
75
+ return values.get("kind") == "agent" and values.get("status") in _AGENT_ACTIVE_STATUSES
76
+
77
+
78
+ # Animated bullet frames shown after the "Thinking" label. Dots grow in
79
+ # from the left, reach three, then drain out from the left — a continuous
80
+ # rightward flow that loops every ``_BULLET_FRAME_INTERVAL * len(frames)``.
81
+ _BULLET_FRAMES = (". ", ".. ", "...", " ..", " .", " ")
82
+ _BULLET_FRAME_INTERVAL = 0.13 # seconds per frame
83
+
84
+
85
+ def _bullet_frame_for(elapsed: float) -> str:
86
+ """Select the current bullet frame from wall-clock elapsed time."""
87
+ idx = int(elapsed / _BULLET_FRAME_INTERVAL) % len(_BULLET_FRAMES)
88
+ return _BULLET_FRAMES[idx]
89
+
90
+
91
+ def _truncate_to_display_width(line: str, max_width: int) -> str:
92
+ """Truncate *line* so its terminal display width fits within *max_width*.
93
+
94
+ Uses ``rich.cells.cell_len`` for CJK-aware column width measurement.
95
+ """
96
+ from rich.cells import cell_len
97
+
98
+ if cell_len(line) <= max_width:
99
+ return line
100
+ ellipsis_width = cell_len(_ELLIPSIS)
101
+ budget = max_width - ellipsis_width
102
+ width = 0
103
+ for i, ch in enumerate(line):
104
+ width += cell_len(ch)
105
+ if width > budget:
106
+ return line[:i] + _ELLIPSIS
107
+ return line
108
+
109
+
110
+ # Lazy-initialized markdown-it parser for incremental token commitment.
111
+ _md_parser: MarkdownIt | None = None
112
+
113
+
114
+ def _get_md_parser() -> MarkdownIt:
115
+ global _md_parser
116
+ if _md_parser is None:
117
+ from markdown_it import MarkdownIt
118
+
119
+ # Match the extensions used by the rendering path (utils/rich/markdown.py)
120
+ # so that block boundaries are detected consistently.
121
+ _md_parser = MarkdownIt().enable("strikethrough").enable("table")
122
+ return _md_parser
123
+
124
+
125
+ def _estimate_tokens(text: str) -> float:
126
+ """Estimate token count for mixed CJK/Latin text.
127
+
128
+ Returns a **float** so that callers can accumulate across small chunks
129
+ without per-chunk floor truncation (e.g. a 3-char ASCII chunk would
130
+ yield 0 if truncated to int immediately, but 0.75 as float).
131
+
132
+ Heuristics based on common BPE tokenizers (cl100k, o200k):
133
+ - CJK ideographs: ~1.5 tokens per character (often split into 2-byte pieces)
134
+ - Latin / ASCII: ~1 token per 4 characters (words average ~4 chars)
135
+ """
136
+ cjk = 0
137
+ other = 0
138
+ for ch in text:
139
+ cp = ord(ch)
140
+ if (
141
+ 0x4E00 <= cp <= 0x9FFF # CJK Unified Ideographs
142
+ or 0x3400 <= cp <= 0x4DBF # CJK Extension A
143
+ or 0xF900 <= cp <= 0xFAFF # CJK Compatibility Ideographs
144
+ or 0x3000 <= cp <= 0x303F # CJK Symbols and Punctuation
145
+ or 0xFF00 <= cp <= 0xFFEF # Fullwidth Forms
146
+ ):
147
+ cjk += 1
148
+ else:
149
+ other += 1
150
+ return cjk * 1.5 + other / 4
151
+
152
+
153
+ def _find_committed_boundary(text: str) -> int | None:
154
+ """Return the character offset up to which *text* can be safely committed.
155
+
156
+ Uses the incremental token commitment algorithm: parse text into block-level
157
+ tokens via ``markdown-it-py``, confirm all blocks except the last one (which
158
+ may be incomplete due to streaming truncation).
159
+
160
+ Returns ``None`` when there are fewer than 2 blocks (nothing to confirm yet).
161
+ """
162
+ md = _get_md_parser()
163
+ tokens = md.parse(text)
164
+
165
+ # Collect only TOP-LEVEL block boundaries by tracking nesting depth.
166
+ # Nested tokens (e.g. list_item_open inside bullet_list_open) must not be
167
+ # treated as independent blocks — otherwise lists and blockquotes get split.
168
+ block_maps: list[list[int]] = []
169
+ depth = 0
170
+ for t in tokens:
171
+ if t.nesting == 1:
172
+ if depth == 0 and t.map is not None:
173
+ block_maps.append(t.map)
174
+ depth += 1
175
+ elif t.nesting == -1:
176
+ depth -= 1
177
+ elif depth == 0 and t.type in _SELF_CLOSING_BLOCKS and t.map is not None:
178
+ block_maps.append(t.map)
179
+
180
+ if len(block_maps) < 2:
181
+ return None
182
+
183
+ # Convert end-line number to character offset by scanning newlines.
184
+ target_line = block_maps[-2][1]
185
+ offset = 0
186
+ for _ in range(target_line):
187
+ offset = text.index("\n", offset) + 1
188
+ return offset
189
+
190
+
191
+ def _tail_lines(text: str, n: int) -> str:
192
+ """Extract the last *n* lines from *text* via reverse scanning (O(n))."""
193
+ pos = len(text)
194
+ for _ in range(n):
195
+ pos = text.rfind("\n", 0, pos)
196
+ if pos == -1:
197
+ return text
198
+ return text[pos + 1 :]
199
+
200
+
201
+ class _ContentBlock:
202
+ """Streaming content block with incremental markdown commitment.
203
+
204
+ For **composing** (``is_think=False``), confirmed markdown blocks are flushed
205
+ to the terminal permanently via ``console.print()`` as they become complete,
206
+ giving users real-time streaming output. Only the unconfirmed tail remains
207
+ in the transient Rich Live area.
208
+
209
+ For **thinking** (``is_think=True``), the default behavior is to keep the
210
+ raw reasoning text only for token accounting and never render it. The
211
+ Live area shows a compact ``Thinking`` label with an animated bullet
212
+ sequence, elapsed time, token count, and a live tokens/second pulse;
213
+ when the block ends, a one-liner ``Thought for Xs · N tokens`` is
214
+ committed to history in grey italics.
215
+
216
+ When ``show_thinking_stream=True``, the legacy behavior is restored: the
217
+ Live area shows a ``Thinking...`` spinner above a 6-line scrolling preview
218
+ of the raw reasoning text, and the full reasoning markdown is committed
219
+ to history when the block ends.
220
+ """
221
+
222
+ def __init__(self, is_think: bool, *, show_thinking_stream: bool = False):
223
+ self.is_think = is_think
224
+ self._show_thinking_stream = show_thinking_stream
225
+ self.raw_text = ""
226
+ # Accumulated float estimate — avoids per-chunk int truncation.
227
+ self._token_count: float = 0.0
228
+ self._start_time = time.monotonic()
229
+ # Incremental commitment state (composing only).
230
+ self._committed_len = 0
231
+ self._has_printed_bullet = False
232
+
233
+ # -- Public API ----------------------------------------------------------
234
+
235
+ def append(self, content: str) -> None:
236
+ self.raw_text += content
237
+ self._token_count += _estimate_tokens(content)
238
+ # Block boundaries require newlines; skip parse for mid-line chunks.
239
+ if not self.is_think and "\n" in content:
240
+ self._flush_committed()
241
+
242
+ def compose(self) -> RenderableType:
243
+ """Render the transient Live area content.
244
+
245
+ Thinking mode shows the italic ``Thinking`` label with animated
246
+ bullets; composing mode shows the dots spinner over the
247
+ uncommitted markdown tail. When ``show_thinking_stream`` is enabled,
248
+ thinking mode falls back to the legacy ``Thinking...`` spinner stacked
249
+ above a 6-line scrolling preview of the raw reasoning text.
250
+ """
251
+ if self.is_think:
252
+ if self._show_thinking_stream:
253
+ return self._compose_thinking_stream()
254
+ return self._compose_thinking()
255
+ return self._compose_spinner()
256
+
257
+ def compose_final(self) -> RenderableType:
258
+ """Render the remaining uncommitted content when the block ends."""
259
+ if self.is_think:
260
+ if self._show_thinking_stream:
261
+ remaining = self._pending_text()
262
+ if not remaining:
263
+ return Text("")
264
+ return BulletColumns(
265
+ Markdown(remaining, style="grey50 italic"),
266
+ bullet_style="grey50",
267
+ )
268
+ elapsed_str = format_elapsed(time.monotonic() - self._start_time)
269
+ count_str = format_token_count(int(self._token_count))
270
+ return Text(
271
+ f"Thought for {elapsed_str} · {count_str} tokens",
272
+ style="grey50 italic",
273
+ )
274
+ remaining = self._pending_text()
275
+ if not remaining:
276
+ return Text("")
277
+ return self._wrap_bullet(Markdown(remaining))
278
+
279
+ def has_pending(self) -> bool:
280
+ """Whether there is uncommitted content to flush."""
281
+ # Thinking blocks always commit a final trace line if any content
282
+ # was received, so gate on raw_text rather than uncommitted length.
283
+ if self.is_think:
284
+ return bool(self.raw_text)
285
+ return bool(self._pending_text())
286
+
287
+ # -- Private -------------------------------------------------------------
288
+
289
+ def _pending_text(self) -> str:
290
+ return self.raw_text[self._committed_len :]
291
+
292
+ def _wrap_bullet(self, renderable: RenderableType) -> BulletColumns:
293
+ """First call gets the ``•`` bullet; subsequent calls get a space."""
294
+ if self._has_printed_bullet:
295
+ return BulletColumns(renderable, bullet=Text(" "))
296
+ self._has_printed_bullet = True
297
+ return BulletColumns(renderable)
298
+
299
+ def _flush_committed(self) -> None:
300
+ """Commit confirmed markdown blocks to permanent terminal output."""
301
+ pending = self._pending_text()
302
+ if not pending:
303
+ return
304
+ boundary = _find_committed_boundary(pending)
305
+ if boundary is None:
306
+ return
307
+ committed_text = pending[:boundary]
308
+ console.print(self._wrap_bullet(Markdown(committed_text)))
309
+ self._committed_len += boundary
310
+
311
+ def _compose_spinner(self) -> Text:
312
+ elapsed = time.monotonic() - self._start_time
313
+ elapsed_str = format_elapsed(elapsed)
314
+ count_str = f"{format_token_count(int(self._token_count))} tokens"
315
+
316
+ glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
317
+ line = Text(f"{glyph} ", style=Style(color="grey50"))
318
+ line.append("Composing...")
319
+ line.append(f" {elapsed_str}", style="grey50")
320
+ line.append(f" · {count_str}", style="grey50")
321
+ return line
322
+
323
+ def _compose_thinking_stream(self) -> RenderableType:
324
+ """Legacy 'Thinking...' spinner stacked over a 6-line scrolling preview."""
325
+ spinner = self._compose_thinking_spinner()
326
+ pending = self._pending_text()
327
+ if not pending:
328
+ return spinner
329
+ preview = self._build_preview(pending)
330
+ return Group(spinner, Text(preview, style="grey50 italic"))
331
+
332
+ def _compose_thinking_spinner(self) -> Text:
333
+ """Legacy 'Thinking...' header used by the stream-mode preview."""
334
+ elapsed = time.monotonic() - self._start_time
335
+ elapsed_str = format_elapsed(elapsed)
336
+ count_str = f"{format_token_count(int(self._token_count))} tokens"
337
+ glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
338
+ line = Text(f"{glyph} ", style=Style(color="grey50"))
339
+ line.append("Thinking...")
340
+ line.append(f" {elapsed_str}", style="grey50")
341
+ line.append(f" · {count_str}", style="grey50")
342
+ return line
343
+
344
+ def _build_preview(self, text: str) -> str:
345
+ """Tail-trim *text* to the last ``_THINKING_PREVIEW_LINES`` and clamp width."""
346
+ max_width = console.width - 2 if console.width else 78
347
+ tail_text = _tail_lines(text, _THINKING_PREVIEW_LINES)
348
+ lines = tail_text.split("\n")
349
+ return "\n".join(_truncate_to_display_width(line, max_width) for line in lines)
350
+
351
+ def _compose_thinking(self) -> Text:
352
+ """Render the thinking line: italic Thinking + bullets + metadata."""
353
+ elapsed = time.monotonic() - self._start_time
354
+ elapsed_str = format_elapsed(elapsed)
355
+ tokens_int = int(self._token_count)
356
+ count_str = f"{format_token_count(tokens_int)} tokens"
357
+ frame = _bullet_frame_for(elapsed)
358
+
359
+ parts: list[tuple[str, str | Style]] = [
360
+ ("Thinking", "italic"),
361
+ (f" {frame}", "cyan"),
362
+ (f" {elapsed_str}", "grey50"),
363
+ (f" · {count_str}", "grey50"),
364
+ ]
365
+
366
+ # Live tok/s pulse — a real heartbeat signal that confirms the model
367
+ # is still streaming even when the raw content is hidden.
368
+ if elapsed > 0.5 and tokens_int > 0:
369
+ rate = int(tokens_int / elapsed)
370
+ if rate > 0:
371
+ parts.append((f" · {rate} tok/s", "grey50"))
372
+
373
+ return Text.assemble(*parts)
374
+
375
+
376
+ class _ToolCallBlock:
377
+ class FinishedSubCall(NamedTuple):
378
+ call: ToolCall
379
+ result: ToolReturnValue
380
+
381
+ def __init__(self, tool_call: ToolCall):
382
+ self._tool_name = tool_call.function.name
383
+ self._tool_call_id = tool_call.id
384
+ self._lexer = streamingjson.Lexer()
385
+ if tool_call.function.arguments is not None:
386
+ self._lexer.append_string(tool_call.function.arguments)
387
+
388
+ self._argument = self._extract_worklog_argument(
389
+ tool_call.function.arguments, self._tool_name
390
+ )
391
+ self._result: ToolReturnValue | None = None
392
+ self._subagent_id: str | None = None
393
+ self._subagent_type: str | None = None
394
+
395
+ self._ongoing_subagent_tool_calls: dict[str, ToolCall] = {}
396
+ self._last_subagent_tool_call: ToolCall | None = None
397
+ self._n_finished_subagent_tool_calls = 0
398
+ self._finished_subagent_tool_calls = deque[_ToolCallBlock.FinishedSubCall](
399
+ maxlen=MAX_SUBAGENT_TOOL_CALLS_TO_SHOW
400
+ )
401
+ # Pythinker card: lazily built when the tui style is "card" AND a
402
+ # renderer is registered for this tool. Stays None on the legacy
403
+ # ``pythinker`` worklog path so that rendering is bit-for-bit
404
+ # unchanged.
405
+ self._tui_card: ToolExecutionComponent | None = None
406
+ # True while the Agent tool result indicates a still-running background
407
+ # agent. The block stays in _tool_call_blocks (and in the Live area)
408
+ # rather than being flushed to static scrollback, so the spinner keeps
409
+ # animating at the Live refresh rate.
410
+ self._is_background_pending: bool = False
411
+
412
+ self._renderable: RenderableType = self._compose()
413
+
414
+ def compose(self) -> RenderableType:
415
+ # Running tool cards and background-pending Agent cards include live
416
+ # status markers. Recompose them on each Live/prompt refresh.
417
+ if self._result is None or self._is_background_pending:
418
+ return self._compose()
419
+ return self._renderable
420
+
421
+ @property
422
+ def tool_call_id(self) -> str:
423
+ return self._tool_call_id
424
+
425
+ @property
426
+ def finished(self) -> bool:
427
+ return self._result is not None
428
+
429
+ @property
430
+ def is_background_pending(self) -> bool:
431
+ return self._is_background_pending
432
+
433
+ def append_args_part(self, args_part: str):
434
+ if self.finished:
435
+ return
436
+ self._lexer.append_string(args_part)
437
+ # TODO: maybe don't extract detail if it's already stable
438
+ argument = self._extract_worklog_argument(self._lexer.complete_json(), self._tool_name)
439
+ if argument and argument != self._argument:
440
+ self._argument = argument
441
+ self._renderable = self._compose()
442
+
443
+ def finish(self, result: ToolReturnValue):
444
+ self._result = result
445
+ result_text = self._card_result_text(result)
446
+ self._is_background_pending = _is_active_background_agent(self._tool_name, result_text)
447
+ self._renderable = self._compose()
448
+
449
+ def append_sub_tool_call(self, tool_call: ToolCall):
450
+ self._ongoing_subagent_tool_calls[tool_call.id] = tool_call
451
+ self._last_subagent_tool_call = tool_call
452
+
453
+ def append_sub_tool_call_part(self, tool_call_part: ToolCallPart):
454
+ if self._last_subagent_tool_call is None:
455
+ return
456
+ if not tool_call_part.arguments_part:
457
+ return
458
+ if self._last_subagent_tool_call.function.arguments is None:
459
+ self._last_subagent_tool_call.function.arguments = tool_call_part.arguments_part
460
+ else:
461
+ self._last_subagent_tool_call.function.arguments += tool_call_part.arguments_part
462
+
463
+ def finish_sub_tool_call(self, tool_result: ToolResult):
464
+ self._last_subagent_tool_call = None
465
+ sub_tool_call = self._ongoing_subagent_tool_calls.pop(tool_result.tool_call_id, None)
466
+ if sub_tool_call is None:
467
+ return
468
+
469
+ self._finished_subagent_tool_calls.append(
470
+ _ToolCallBlock.FinishedSubCall(
471
+ call=sub_tool_call,
472
+ result=tool_result.return_value,
473
+ )
474
+ )
475
+ self._n_finished_subagent_tool_calls += 1
476
+ self._renderable = self._compose()
477
+
478
+ def set_subagent_metadata(self, agent_id: str, subagent_type: str) -> None:
479
+ changed = (self._subagent_id, self._subagent_type) != (agent_id, subagent_type)
480
+ self._subagent_id = agent_id
481
+ self._subagent_type = subagent_type
482
+ if changed:
483
+ self._renderable = self._compose()
484
+
485
+ def _compose(self) -> RenderableType:
486
+ if is_card_style():
487
+ card_rendered = self._compose_card()
488
+ if card_rendered is not None:
489
+ return card_rendered
490
+ children: list[RenderableType] = []
491
+ if self._subagent_id is not None and self._subagent_type is not None:
492
+ children.append(
493
+ BulletColumns(
494
+ Text(
495
+ f"subagent {self._subagent_type} ({self._subagent_id})",
496
+ style="grey50",
497
+ ),
498
+ bullet_style="grey50",
499
+ )
500
+ )
501
+
502
+ style = tool_style(self._tool_name)
503
+ if style.label == "Subagent" and self._result is not None:
504
+ if self._n_finished_subagent_tool_calls:
505
+ summary = Text(
506
+ f"{self._n_finished_subagent_tool_calls} tool calls completed",
507
+ style="grey50",
508
+ )
509
+ if self._finished_subagent_tool_calls:
510
+ summary.append(
511
+ f" · {len(self._finished_subagent_tool_calls)} recent tracked",
512
+ style="grey50",
513
+ )
514
+ children.append(BulletColumns(summary, bullet_style="grey50"))
515
+ elif self._n_finished_subagent_tool_calls > MAX_SUBAGENT_TOOL_CALLS_TO_SHOW:
516
+ n_hidden = self._n_finished_subagent_tool_calls - MAX_SUBAGENT_TOOL_CALLS_TO_SHOW
517
+ children.append(
518
+ BulletColumns(
519
+ Text(
520
+ f"{n_hidden} more tool call{'s' if n_hidden > 1 else ''} ...",
521
+ style="grey50 italic",
522
+ ),
523
+ bullet_style="grey50",
524
+ )
525
+ )
526
+ if not (style.label == "Subagent" and self._result is not None):
527
+ for sub_call, sub_result in self._finished_subagent_tool_calls:
528
+ argument = extract_key_argument(
529
+ sub_call.function.arguments or "", sub_call.function.name
530
+ )
531
+ sub_url = self._extract_full_url(
532
+ sub_call.function.arguments, sub_call.function.name
533
+ )
534
+ sub_text = Text()
535
+ sub_text.append("Used ")
536
+ sub_text.append(sub_call.function.name, style="blue")
537
+ if argument:
538
+ sub_text.append(" (", style="grey50")
539
+ arg_style = Style(color="grey50", link=sub_url) if sub_url else "grey50"
540
+ sub_text.append(argument, style=arg_style)
541
+ sub_text.append(")", style="grey50")
542
+ children.append(
543
+ BulletColumns(
544
+ sub_text,
545
+ bullet_style="green" if not sub_result.is_error else "dark_red",
546
+ )
547
+ )
548
+
549
+ if self._result is None:
550
+ return render_worklog_entry(
551
+ label=style.label,
552
+ target=self._argument,
553
+ state=WorkLogState.RUNNING,
554
+ icon=style.icon,
555
+ icon_style=style.style,
556
+ children=children,
557
+ )
558
+
559
+ error_message = self._result.message if self._result.is_error else ""
560
+ if self._result.is_error and not error_message:
561
+ error_message = getattr(self._result, "brief", "") or "Tool failed"
562
+ state = (
563
+ WorkLogState.DENIED
564
+ if self._result.is_error and denied_error(error_message)
565
+ else WorkLogState.FAILED
566
+ if self._result.is_error
567
+ else WorkLogState.COMPLETED
568
+ )
569
+ children.extend(
570
+ render_display_blocks(
571
+ getattr(self._result, "display", []) or [], is_error=self._result.is_error
572
+ )
573
+ )
574
+ return render_worklog_entry(
575
+ label=style.label,
576
+ target=self._argument,
577
+ state=state,
578
+ detail=error_message if self._result.is_error else None,
579
+ icon=style.icon,
580
+ icon_style=style.style,
581
+ children=children,
582
+ )
583
+
584
+ def _compose_card(self) -> RenderableType | None:
585
+ """Build/update the Pythinker card. Returns None to fall through.
586
+
587
+ Renderer resolution: prefer a tool-specific renderer registered
588
+ under ``tool_name``; fall back to the generic renderer so any
589
+ tool gets a Pythinker card under the flag. Returns None only if
590
+ the generic renderer itself is missing (i.e. the built-ins were
591
+ never registered).
592
+ """
593
+ definition = get_tool_renderer(self._tool_name)
594
+ if definition is None:
595
+ definition = generic_renderer()
596
+ if self._tui_card is None:
597
+ self._tui_card = ToolExecutionComponent(
598
+ self._tool_name,
599
+ self._tool_call_id,
600
+ definition=definition,
601
+ )
602
+ # We see the tool call event, so the model has begun work.
603
+ self._tui_card.mark_execution_started()
604
+ raw_args = self._lexer.complete_json() or "{}"
605
+ try:
606
+ parsed = json.loads(raw_args, strict=False)
607
+ except json.JSONDecodeError:
608
+ parsed = {}
609
+ if isinstance(parsed, dict):
610
+ self._tui_card.update_args(cast(dict[str, Any], parsed))
611
+ # Args are complete once a result lands; before that we treat
612
+ # complete_json output as best-effort.
613
+ if self._result is not None:
614
+ self._tui_card.set_args_complete()
615
+ self._tui_card.set_result(
616
+ ToolResultPayload(
617
+ text=self._card_result_text(self._result),
618
+ is_error=self._result.is_error,
619
+ ),
620
+ is_partial=self._is_background_pending,
621
+ )
622
+ return self._tui_card.render()
623
+
624
+ @staticmethod
625
+ def _card_result_text(result: ToolReturnValue) -> str:
626
+ """Flatten a ToolReturnValue to a single text payload for cards.
627
+
628
+ Tool renderers expect the *primary content* (file body, command
629
+ output, grep matches) — that lives in ``output`` for Pythinker.
630
+ Fall back to ``message`` (e.g. "Successfully wrote N bytes" from
631
+ WriteFile, where ``output`` is empty) and finally ``brief`` for
632
+ tools that only emit a summary block. Non-string outputs are
633
+ skipped here; specialized renderers should pull richer detail
634
+ from ``ctx.args``.
635
+ """
636
+ if result.is_error:
637
+ parts: list[str] = []
638
+ if result.message:
639
+ parts.append(result.message)
640
+ if isinstance(result.output, str) and result.output:
641
+ parts.append(result.output)
642
+ if not parts:
643
+ brief = getattr(result, "brief", "") or "Tool failed"
644
+ parts.append(brief)
645
+ return "\n\n".join(parts)
646
+ if isinstance(result.output, str) and result.output:
647
+ return result.output
648
+ if result.message:
649
+ return result.message
650
+ return getattr(result, "brief", "") or ""
651
+
652
+ @staticmethod
653
+ def _extract_worklog_argument(arguments: str | None, tool_name: str) -> str | None:
654
+ argument = extract_key_argument(arguments or "", tool_name)
655
+ try:
656
+ args = json.loads(arguments or "{}", strict=False)
657
+ except json.JSONDecodeError:
658
+ return argument
659
+ if not isinstance(args, dict):
660
+ return argument
661
+ args = cast(dict[str, Any], args)
662
+ match tool_name:
663
+ case "ReadFile":
664
+ path = args.get("path") or args.get("file_path")
665
+ return str(path) if path else argument
666
+ case _:
667
+ return argument
668
+
669
+ @staticmethod
670
+ def _extract_full_url(arguments: str | None, tool_name: str) -> str | None:
671
+ """Extract the full URL from FetchURL tool arguments."""
672
+ if tool_name != "FetchURL" or not arguments:
673
+ return None
674
+ try:
675
+ args = json.loads(arguments, strict=False)
676
+ except (json.JSONDecodeError, TypeError):
677
+ return None
678
+ if isinstance(args, dict):
679
+ url = cast(dict[str, Any], args).get("url")
680
+ if url:
681
+ return str(url)
682
+ return None
683
+
684
+
685
+ class _NotificationBlock:
686
+ _SEVERITY_STYLE = {
687
+ "info": "cyan",
688
+ "success": "green",
689
+ "warning": "yellow",
690
+ "error": "red",
691
+ }
692
+
693
+ def __init__(self, notification: Notification):
694
+ self.notification = notification
695
+
696
+ def compose(self) -> RenderableType:
697
+ style = self._SEVERITY_STYLE.get(self.notification.severity, "cyan")
698
+ lines: list[RenderableType] = [Text(self.notification.title, style=f"bold {style}")]
699
+ body = self.notification.body.strip()
700
+ if body:
701
+ body_lines = body.splitlines()
702
+ preview = "\n".join(body_lines[:2])
703
+ if len(body_lines) > 2:
704
+ preview += "\n..."
705
+ lines.append(Text(preview, style="grey50"))
706
+ return BulletColumns(Group(*lines), bullet_style=style)
707
+
708
+
709
+ class _StatusBlock:
710
+ def __init__(self, initial: StatusUpdate) -> None:
711
+ self.text = Text("", justify="right")
712
+ self._context_usage: float = 0.0
713
+ self._context_tokens: int = 0
714
+ self._max_context_tokens: int = 0
715
+ self._mcp_status: MCPStatusSnapshot | None = None
716
+ self.update(initial)
717
+
718
+ def render(self) -> RenderableType:
719
+ return self.text
720
+
721
+ def update(self, status: StatusUpdate) -> None:
722
+ if status.context_usage is not None:
723
+ self._context_usage = status.context_usage
724
+ if status.context_tokens is not None:
725
+ self._context_tokens = status.context_tokens
726
+ if status.max_context_tokens is not None:
727
+ self._max_context_tokens = status.max_context_tokens
728
+ if status.mcp_status is not None:
729
+ self._mcp_status = status.mcp_status
730
+ if status.context_usage is not None or status.mcp_status is not None:
731
+ parts: list[str] = []
732
+ if self._context_usage or self._max_context_tokens:
733
+ parts.append(
734
+ format_context_status(
735
+ self._context_usage,
736
+ self._context_tokens,
737
+ self._max_context_tokens,
738
+ )
739
+ )
740
+ if self._mcp_status is not None and self._mcp_status.loading:
741
+ parts.append(
742
+ f"MCP {self._mcp_status.connected}/{self._mcp_status.total} · "
743
+ f"{self._mcp_status.tools} tools"
744
+ )
745
+ self.text.plain = " ".join(parts)
746
+
747
+
748
+ class _CompactionBlock:
749
+ """Animated compaction progress with a time-based estimate.
750
+
751
+ The bar fills toward 95% over ``EXPECTED_DURATION_S`` (compaction has no
752
+ real progress signal), then disappears once ``CompactionEnd`` arrives.
753
+ """
754
+
755
+ BAR_WIDTH = 40
756
+ EXPECTED_DURATION_S = 60.0
757
+ MAX_ESTIMATED_PROGRESS = 0.95
758
+
759
+ TIPS: tuple[str, ...] = (
760
+ "Shift+Tab toggles plan mode for multi-step work",
761
+ "Subagents keep your main context clean",
762
+ "/verify before declaring work done",
763
+ "/learn captures a lesson after a correction",
764
+ "@-mention files to attach them to the next message",
765
+ "/feedback sends a note to the Pythinker team",
766
+ "/theme switches between dark and light",
767
+ "Ctrl+O opens your $EDITOR for long messages",
768
+ "Use /resume to pick up a previous session",
769
+ )
770
+
771
+ def __init__(self) -> None:
772
+ self._start = time.monotonic()
773
+ self._tip = random.choice(self.TIPS)
774
+
775
+ def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
776
+ yield from console.render(self._render(), options)
777
+
778
+ def _render(self) -> RenderableType:
779
+ elapsed = max(0.0, time.monotonic() - self._start)
780
+ progress = min(elapsed / self.EXPECTED_DURATION_S, self.MAX_ESTIMATED_PROGRESS)
781
+ filled = int(round(progress * self.BAR_WIDTH))
782
+ empty = self.BAR_WIDTH - filled
783
+ pct = int(progress * 100)
784
+ accent = tui_rich_style("accent")
785
+ muted = tui_rich_style("muted")
786
+ subtle = tui_rich_style("dim")
787
+
788
+ title = Text()
789
+ glyph = "●" if int(time.monotonic() / 0.8) % 2 == 0 else " "
790
+ title.append(f"{glyph} ", style=tui_rich_style("muted"))
791
+ title.append("Compacting conversation…")
792
+ title.append(f" ({format_elapsed(elapsed)})", style=subtle)
793
+
794
+ bar = Text(" ")
795
+ bar.append("▰" * filled, style=accent)
796
+ bar.append("▱" * empty, style=muted)
797
+ bar.append(f" {pct}%", style=accent + Style(bold=True))
798
+
799
+ tip = Text(" ⎿ ", style=muted)
800
+ tip.append(f"Tip: {self._tip}", style=subtle)
801
+
802
+ return Group(title, bar, tip)