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.
- pythinker_code/CHANGELOG.md +16 -0
- pythinker_code/__init__.py +0 -0
- pythinker_code/__main__.py +92 -0
- pythinker_code/acp/AGENTS.md +93 -0
- pythinker_code/acp/__init__.py +13 -0
- pythinker_code/acp/convert.py +128 -0
- pythinker_code/acp/host.py +298 -0
- pythinker_code/acp/mcp.py +46 -0
- pythinker_code/acp/server.py +497 -0
- pythinker_code/acp/session.py +496 -0
- pythinker_code/acp/tools.py +167 -0
- pythinker_code/acp/types.py +13 -0
- pythinker_code/acp/version.py +45 -0
- pythinker_code/agents/default/agent.yaml +36 -0
- pythinker_code/agents/default/coder.yaml +25 -0
- pythinker_code/agents/default/explore.yaml +46 -0
- pythinker_code/agents/default/plan.yaml +30 -0
- pythinker_code/agents/default/system.md +164 -0
- pythinker_code/agents/okabe/agent.yaml +22 -0
- pythinker_code/agentspec.py +163 -0
- pythinker_code/app.py +820 -0
- pythinker_code/approval_runtime/__init__.py +29 -0
- pythinker_code/approval_runtime/models.py +42 -0
- pythinker_code/approval_runtime/runtime.py +235 -0
- pythinker_code/auth/__init__.py +25 -0
- pythinker_code/auth/anthropic_direct.py +207 -0
- pythinker_code/auth/deepseek.py +192 -0
- pythinker_code/auth/lm_studio.py +418 -0
- pythinker_code/auth/minimax.py +203 -0
- pythinker_code/auth/oauth.py +1122 -0
- pythinker_code/auth/ollama.py +293 -0
- pythinker_code/auth/openai.py +771 -0
- pythinker_code/auth/opencode_go.py +203 -0
- pythinker_code/auth/openrouter.py +225 -0
- pythinker_code/auth/platforms.py +466 -0
- pythinker_code/background/__init__.py +36 -0
- pythinker_code/background/agent_runner.py +231 -0
- pythinker_code/background/ids.py +19 -0
- pythinker_code/background/manager.py +650 -0
- pythinker_code/background/models.py +105 -0
- pythinker_code/background/store.py +237 -0
- pythinker_code/background/summary.py +66 -0
- pythinker_code/background/worker.py +209 -0
- pythinker_code/cli/__init__.py +1326 -0
- pythinker_code/cli/__main__.py +19 -0
- pythinker_code/cli/_lazy_group.py +238 -0
- pythinker_code/cli/export.py +322 -0
- pythinker_code/cli/info.py +62 -0
- pythinker_code/cli/mcp.py +349 -0
- pythinker_code/cli/plugin.py +351 -0
- pythinker_code/cli/toad.py +74 -0
- pythinker_code/cli/vis.py +38 -0
- pythinker_code/cli/web.py +80 -0
- pythinker_code/config.py +453 -0
- pythinker_code/constant.py +33 -0
- pythinker_code/exception.py +43 -0
- pythinker_code/hooks/__init__.py +4 -0
- pythinker_code/hooks/config.py +34 -0
- pythinker_code/hooks/engine.py +371 -0
- pythinker_code/hooks/events.py +190 -0
- pythinker_code/hooks/runner.py +89 -0
- pythinker_code/llm.py +412 -0
- pythinker_code/metadata.py +79 -0
- pythinker_code/notifications/__init__.py +33 -0
- pythinker_code/notifications/llm.py +77 -0
- pythinker_code/notifications/manager.py +145 -0
- pythinker_code/notifications/models.py +50 -0
- pythinker_code/notifications/notifier.py +41 -0
- pythinker_code/notifications/store.py +118 -0
- pythinker_code/notifications/wire.py +21 -0
- pythinker_code/plugin/__init__.py +124 -0
- pythinker_code/plugin/manager.py +153 -0
- pythinker_code/plugin/tool.py +173 -0
- pythinker_code/prompts/__init__.py +6 -0
- pythinker_code/prompts/compact.md +73 -0
- pythinker_code/prompts/init.md +21 -0
- pythinker_code/py.typed +0 -0
- pythinker_code/session.py +319 -0
- pythinker_code/session_fork.py +325 -0
- pythinker_code/session_state.py +132 -0
- pythinker_code/share.py +14 -0
- pythinker_code/skill/__init__.py +727 -0
- pythinker_code/skill/flow/__init__.py +99 -0
- pythinker_code/skill/flow/d2.py +482 -0
- pythinker_code/skill/flow/mermaid.py +266 -0
- pythinker_code/skills/pythinker-code-help/SKILL.md +54 -0
- pythinker_code/skills/skill-creator/SKILL.md +367 -0
- pythinker_code/soul/__init__.py +304 -0
- pythinker_code/soul/agent.py +520 -0
- pythinker_code/soul/approval.py +267 -0
- pythinker_code/soul/btw.py +214 -0
- pythinker_code/soul/compaction.py +189 -0
- pythinker_code/soul/context.py +339 -0
- pythinker_code/soul/denwarenji.py +39 -0
- pythinker_code/soul/dynamic_injection.py +84 -0
- pythinker_code/soul/dynamic_injections/__init__.py +0 -0
- pythinker_code/soul/dynamic_injections/auto_mode.py +72 -0
- pythinker_code/soul/dynamic_injections/plan_mode.py +239 -0
- pythinker_code/soul/message.py +92 -0
- pythinker_code/soul/pythinkersoul.py +1613 -0
- pythinker_code/soul/slash.py +340 -0
- pythinker_code/soul/toolset.py +788 -0
- pythinker_code/subagents/__init__.py +21 -0
- pythinker_code/subagents/builder.py +42 -0
- pythinker_code/subagents/core.py +86 -0
- pythinker_code/subagents/git_context.py +172 -0
- pythinker_code/subagents/models.py +54 -0
- pythinker_code/subagents/output.py +71 -0
- pythinker_code/subagents/registry.py +28 -0
- pythinker_code/subagents/runner.py +428 -0
- pythinker_code/subagents/store.py +196 -0
- pythinker_code/telemetry/__init__.py +211 -0
- pythinker_code/telemetry/config.py +54 -0
- pythinker_code/telemetry/crash.py +157 -0
- pythinker_code/telemetry/metrics.py +208 -0
- pythinker_code/telemetry/otel.py +240 -0
- pythinker_code/telemetry/sentry.py +167 -0
- pythinker_code/telemetry/sink.py +189 -0
- pythinker_code/tools/AGENTS.md +6 -0
- pythinker_code/tools/__init__.py +105 -0
- pythinker_code/tools/agent/__init__.py +277 -0
- pythinker_code/tools/agent/description.md +41 -0
- pythinker_code/tools/ask_user/__init__.py +159 -0
- pythinker_code/tools/ask_user/description.md +19 -0
- pythinker_code/tools/background/__init__.py +318 -0
- pythinker_code/tools/background/list.md +10 -0
- pythinker_code/tools/background/output.md +11 -0
- pythinker_code/tools/background/stop.md +8 -0
- pythinker_code/tools/display.py +46 -0
- pythinker_code/tools/dmail/__init__.py +38 -0
- pythinker_code/tools/dmail/dmail.md +17 -0
- pythinker_code/tools/file/__init__.py +30 -0
- pythinker_code/tools/file/glob.md +17 -0
- pythinker_code/tools/file/glob.py +160 -0
- pythinker_code/tools/file/grep.md +6 -0
- pythinker_code/tools/file/grep_local.py +589 -0
- pythinker_code/tools/file/plan_mode.py +45 -0
- pythinker_code/tools/file/read.md +16 -0
- pythinker_code/tools/file/read.py +300 -0
- pythinker_code/tools/file/read_media.md +24 -0
- pythinker_code/tools/file/read_media.py +217 -0
- pythinker_code/tools/file/replace.md +7 -0
- pythinker_code/tools/file/replace.py +195 -0
- pythinker_code/tools/file/utils.py +257 -0
- pythinker_code/tools/file/write.md +5 -0
- pythinker_code/tools/file/write.py +177 -0
- pythinker_code/tools/plan/__init__.py +327 -0
- pythinker_code/tools/plan/description.md +29 -0
- pythinker_code/tools/plan/enter.py +190 -0
- pythinker_code/tools/plan/enter_description.md +35 -0
- pythinker_code/tools/plan/heroes.py +277 -0
- pythinker_code/tools/shell/__init__.py +253 -0
- pythinker_code/tools/shell/bash.md +35 -0
- pythinker_code/tools/shell/powershell.md +30 -0
- pythinker_code/tools/test.py +55 -0
- pythinker_code/tools/think/__init__.py +21 -0
- pythinker_code/tools/think/think.md +1 -0
- pythinker_code/tools/todo/__init__.py +168 -0
- pythinker_code/tools/todo/set_todo_list.md +23 -0
- pythinker_code/tools/utils.py +199 -0
- pythinker_code/tools/web/__init__.py +4 -0
- pythinker_code/tools/web/fetch.md +1 -0
- pythinker_code/tools/web/fetch.py +189 -0
- pythinker_code/tools/web/search.md +1 -0
- pythinker_code/tools/web/search.py +163 -0
- pythinker_code/ui/__init__.py +0 -0
- pythinker_code/ui/acp/__init__.py +99 -0
- pythinker_code/ui/print/__init__.py +474 -0
- pythinker_code/ui/print/visualize.py +185 -0
- pythinker_code/ui/shell/__init__.py +1696 -0
- pythinker_code/ui/shell/console.py +109 -0
- pythinker_code/ui/shell/debug.py +190 -0
- pythinker_code/ui/shell/echo.py +17 -0
- pythinker_code/ui/shell/export_import.py +117 -0
- pythinker_code/ui/shell/keyboard.py +300 -0
- pythinker_code/ui/shell/mcp_status.py +113 -0
- pythinker_code/ui/shell/model_picker.py +318 -0
- pythinker_code/ui/shell/oauth.py +272 -0
- pythinker_code/ui/shell/placeholders.py +531 -0
- pythinker_code/ui/shell/prompt.py +2278 -0
- pythinker_code/ui/shell/replay.py +215 -0
- pythinker_code/ui/shell/session_picker.py +227 -0
- pythinker_code/ui/shell/setup.py +212 -0
- pythinker_code/ui/shell/slash.py +898 -0
- pythinker_code/ui/shell/startup.py +32 -0
- pythinker_code/ui/shell/task_browser.py +486 -0
- pythinker_code/ui/shell/update.py +350 -0
- pythinker_code/ui/shell/usage.py +291 -0
- pythinker_code/ui/shell/usage_adapters/__init__.py +50 -0
- pythinker_code/ui/shell/usage_adapters/anthropic_admin.py +233 -0
- pythinker_code/ui/shell/usage_adapters/base.py +72 -0
- pythinker_code/ui/shell/usage_adapters/deepseek.py +137 -0
- pythinker_code/ui/shell/usage_adapters/minimax.py +236 -0
- pythinker_code/ui/shell/usage_adapters/openai_admin.py +225 -0
- pythinker_code/ui/shell/usage_adapters/openai_chatgpt.py +241 -0
- pythinker_code/ui/shell/usage_adapters/opencode_go.py +232 -0
- pythinker_code/ui/shell/usage_adapters/openrouter.py +105 -0
- pythinker_code/ui/shell/usage_adapters/pythinker.py +189 -0
- pythinker_code/ui/shell/usage_adapters/pythinker_ai.py +50 -0
- pythinker_code/ui/shell/usage_render.py +150 -0
- pythinker_code/ui/shell/visualize/__init__.py +165 -0
- pythinker_code/ui/shell/visualize/_approval_panel.py +505 -0
- pythinker_code/ui/shell/visualize/_blocks.py +629 -0
- pythinker_code/ui/shell/visualize/_btw_panel.py +224 -0
- pythinker_code/ui/shell/visualize/_input_router.py +48 -0
- pythinker_code/ui/shell/visualize/_interactive.py +523 -0
- pythinker_code/ui/shell/visualize/_live_view.py +826 -0
- pythinker_code/ui/shell/visualize/_question_panel.py +586 -0
- pythinker_code/ui/theme.py +241 -0
- pythinker_code/usage_ratelimit_cache.py +175 -0
- pythinker_code/utils/__init__.py +0 -0
- pythinker_code/utils/aiohttp.py +24 -0
- pythinker_code/utils/aioqueue.py +72 -0
- pythinker_code/utils/broadcast.py +37 -0
- pythinker_code/utils/changelog.py +108 -0
- pythinker_code/utils/clipboard.py +246 -0
- pythinker_code/utils/datetime.py +64 -0
- pythinker_code/utils/diff.py +135 -0
- pythinker_code/utils/editor.py +91 -0
- pythinker_code/utils/environment.py +73 -0
- pythinker_code/utils/envvar.py +22 -0
- pythinker_code/utils/export.py +696 -0
- pythinker_code/utils/file_filter.py +375 -0
- pythinker_code/utils/frontmatter.py +70 -0
- pythinker_code/utils/io.py +27 -0
- pythinker_code/utils/logging.py +146 -0
- pythinker_code/utils/media_tags.py +29 -0
- pythinker_code/utils/message.py +24 -0
- pythinker_code/utils/path.py +199 -0
- pythinker_code/utils/proctitle.py +33 -0
- pythinker_code/utils/proxy.py +31 -0
- pythinker_code/utils/pyinstaller.py +45 -0
- pythinker_code/utils/rich/__init__.py +33 -0
- pythinker_code/utils/rich/columns.py +99 -0
- pythinker_code/utils/rich/diff_render.py +481 -0
- pythinker_code/utils/rich/markdown.py +900 -0
- pythinker_code/utils/rich/markdown_sample.md +108 -0
- pythinker_code/utils/rich/markdown_sample_short.md +2 -0
- pythinker_code/utils/rich/syntax.py +114 -0
- pythinker_code/utils/sensitive.py +54 -0
- pythinker_code/utils/server.py +121 -0
- pythinker_code/utils/signals.py +43 -0
- pythinker_code/utils/slashcmd.py +124 -0
- pythinker_code/utils/string.py +41 -0
- pythinker_code/utils/subprocess_env.py +73 -0
- pythinker_code/utils/term.py +168 -0
- pythinker_code/utils/typing.py +20 -0
- pythinker_code/vis/__init__.py +0 -0
- pythinker_code/vis/api/__init__.py +5 -0
- pythinker_code/vis/api/sessions.py +687 -0
- pythinker_code/vis/api/statistics.py +209 -0
- pythinker_code/vis/api/system.py +19 -0
- pythinker_code/vis/app.py +175 -0
- pythinker_code/vis/static/assets/highlighted-body-B3W2YXNL-D2MTYyJz.js +1 -0
- pythinker_code/vis/static/assets/index-CezafTt_.js +185 -0
- pythinker_code/vis/static/assets/index-DSRInNnm.css +1 -0
- pythinker_code/vis/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/vis/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/vis/static/index.html +17 -0
- pythinker_code/web/__init__.py +5 -0
- pythinker_code/web/api/__init__.py +15 -0
- pythinker_code/web/api/config.py +208 -0
- pythinker_code/web/api/open_in.py +199 -0
- pythinker_code/web/api/sessions.py +1225 -0
- pythinker_code/web/app.py +451 -0
- pythinker_code/web/auth.py +191 -0
- pythinker_code/web/models.py +98 -0
- pythinker_code/web/runner/__init__.py +5 -0
- pythinker_code/web/runner/messages.py +57 -0
- pythinker_code/web/runner/process.py +754 -0
- pythinker_code/web/runner/worker.py +97 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- pythinker_code/web/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- pythinker_code/web/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- pythinker_code/web/static/assets/_baseUniq--dyU3g5v.js +1 -0
- pythinker_code/web/static/assets/abap-BdImnpbu.js +1 -0
- pythinker_code/web/static/assets/actionscript-3-CfeIJUat.js +1 -0
- pythinker_code/web/static/assets/ada-bCR0ucgS.js +1 -0
- pythinker_code/web/static/assets/andromeeda-C-Jbm3Hp.js +1 -0
- pythinker_code/web/static/assets/angular-html-CU67Zn6k.js +1 -0
- pythinker_code/web/static/assets/angular-ts-BwZT4LLn.js +1 -0
- pythinker_code/web/static/assets/apache-Pmp26Uib.js +1 -0
- pythinker_code/web/static/assets/apex-D8_7TLub.js +1 -0
- pythinker_code/web/static/assets/apl-dKokRX4l.js +1 -0
- pythinker_code/web/static/assets/applescript-Co6uUVPk.js +1 -0
- pythinker_code/web/static/assets/ara-BRHolxvo.js +1 -0
- pythinker_code/web/static/assets/arc-DkMjLpYa.js +1 -0
- pythinker_code/web/static/assets/architectureDiagram-VXUJARFQ-CHWVaGo9.js +36 -0
- pythinker_code/web/static/assets/asciidoc-Dv7Oe6Be.js +1 -0
- pythinker_code/web/static/assets/asm-D_Q5rh1f.js +1 -0
- pythinker_code/web/static/assets/astro-CbQHKStN.js +1 -0
- pythinker_code/web/static/assets/aurora-x-D-2ljcwZ.js +1 -0
- pythinker_code/web/static/assets/awk-DMzUqQB5.js +1 -0
- pythinker_code/web/static/assets/ayu-dark-CmMr59Fi.js +1 -0
- pythinker_code/web/static/assets/ballerina-BFfxhgS-.js +1 -0
- pythinker_code/web/static/assets/bat-BkioyH1T.js +1 -0
- pythinker_code/web/static/assets/beancount-k_qm7-4y.js +1 -0
- pythinker_code/web/static/assets/berry-uYugtg8r.js +1 -0
- pythinker_code/web/static/assets/bibtex-CHM0blh-.js +1 -0
- pythinker_code/web/static/assets/bicep-Bmn6On1c.js +1 -0
- pythinker_code/web/static/assets/blade-D4QpJJKB.js +1 -0
- pythinker_code/web/static/assets/blockDiagram-VD42YOAC-DzdKe497.js +122 -0
- pythinker_code/web/static/assets/bsl-BO_Y6i37.js +1 -0
- pythinker_code/web/static/assets/c-BIGW1oBm.js +1 -0
- pythinker_code/web/static/assets/c3-VCDPK7BO.js +1 -0
- pythinker_code/web/static/assets/c4Diagram-YG6GDRKO-D84Blotg.js +10 -0
- pythinker_code/web/static/assets/cadence-Bv_4Rxtq.js +1 -0
- pythinker_code/web/static/assets/cairo-KRGpt6FW.js +1 -0
- pythinker_code/web/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- pythinker_code/web/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- pythinker_code/web/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- pythinker_code/web/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- pythinker_code/web/static/assets/channel-CllSjjdl.js +1 -0
- pythinker_code/web/static/assets/chunk-4BX2VUAB-C9w8wleE.js +1 -0
- pythinker_code/web/static/assets/chunk-55IACEB6-YlYJ8HnF.js +1 -0
- pythinker_code/web/static/assets/chunk-B4BG7PRW-Bwtz_AHU.js +165 -0
- pythinker_code/web/static/assets/chunk-DI55MBZ5-BbEHkl8h.js +220 -0
- pythinker_code/web/static/assets/chunk-FMBD7UC4-BKPbvjLC.js +15 -0
- pythinker_code/web/static/assets/chunk-QN33PNHL-D73dQvKf.js +1 -0
- pythinker_code/web/static/assets/chunk-QZHKN3VN-zGiLKes_.js +1 -0
- pythinker_code/web/static/assets/chunk-TZMSLE5B-LHJCi2fy.js +1 -0
- pythinker_code/web/static/assets/clarity-D53aC0YG.js +1 -0
- pythinker_code/web/static/assets/classDiagram-2ON5EDUG-vX27iZwa.js +1 -0
- pythinker_code/web/static/assets/classDiagram-v2-WZHVMYZB-vX27iZwa.js +1 -0
- pythinker_code/web/static/assets/clojure-P80f7IUj.js +1 -0
- pythinker_code/web/static/assets/clone-DYBkaPm2.js +1 -0
- pythinker_code/web/static/assets/cmake-D1j8_8rp.js +1 -0
- pythinker_code/web/static/assets/cobol-nwyudZeR.js +1 -0
- pythinker_code/web/static/assets/code-block-IT6T5CEO-NtKViZGl.js +2 -0
- pythinker_code/web/static/assets/codeowners-Bp6g37R7.js +1 -0
- pythinker_code/web/static/assets/codeql-DsOJ9woJ.js +1 -0
- pythinker_code/web/static/assets/coffee-Ch7k5sss.js +1 -0
- pythinker_code/web/static/assets/common-lisp-Cg-RD9OK.js +1 -0
- pythinker_code/web/static/assets/coq-DkFqJrB1.js +1 -0
- pythinker_code/web/static/assets/cose-bilkent-S5V4N54A-DialjZpd.js +1 -0
- pythinker_code/web/static/assets/cpp-CofmeUqb.js +1 -0
- pythinker_code/web/static/assets/crystal-tKQVLTB8.js +1 -0
- pythinker_code/web/static/assets/csharp-K5feNrxe.js +1 -0
- pythinker_code/web/static/assets/css-DPfMkruS.js +1 -0
- pythinker_code/web/static/assets/csv-fuZLfV_i.js +1 -0
- pythinker_code/web/static/assets/cue-D82EKSYY.js +1 -0
- pythinker_code/web/static/assets/cypher-COkxafJQ.js +1 -0
- pythinker_code/web/static/assets/cytoscape.esm-C_Fzpdck.js +321 -0
- pythinker_code/web/static/assets/d-85-TOEBH.js +1 -0
- pythinker_code/web/static/assets/dagre-6UL2VRFP-DfuvkZZ7.js +4 -0
- pythinker_code/web/static/assets/dark-plus-C3mMm8J8.js +1 -0
- pythinker_code/web/static/assets/dart-CF10PKvl.js +1 -0
- pythinker_code/web/static/assets/dax-CEL-wOlO.js +1 -0
- pythinker_code/web/static/assets/defaultLocale-DX6XiGOO.js +1 -0
- pythinker_code/web/static/assets/desktop-BmXAJ9_W.js +1 -0
- pythinker_code/web/static/assets/diagram-PSM6KHXK-DLGctX3v.js +24 -0
- pythinker_code/web/static/assets/diagram-QEK2KX5R-DnxN6S0F.js +43 -0
- pythinker_code/web/static/assets/diagram-S2PKOQOG-Caq_Set9.js +24 -0
- pythinker_code/web/static/assets/diff-D97Zzqfu.js +1 -0
- pythinker_code/web/static/assets/docker-BcOcwvcX.js +1 -0
- pythinker_code/web/static/assets/dotenv-Da5cRb03.js +1 -0
- pythinker_code/web/static/assets/dracula-BzJJZx-M.js +1 -0
- pythinker_code/web/static/assets/dracula-soft-BXkSAIEj.js +1 -0
- pythinker_code/web/static/assets/dream-maker-BtqSS_iP.js +1 -0
- pythinker_code/web/static/assets/edge-BkV0erSs.js +1 -0
- pythinker_code/web/static/assets/elixir-CDX3lj18.js +1 -0
- pythinker_code/web/static/assets/elm-DbKCFpqz.js +1 -0
- pythinker_code/web/static/assets/emacs-lisp-C9XAeP06.js +1 -0
- pythinker_code/web/static/assets/erDiagram-Q2GNP2WA-BgTfALoK.js +60 -0
- pythinker_code/web/static/assets/erb-BOJIQeun.js +1 -0
- pythinker_code/web/static/assets/erlang-DsQrWhSR.js +1 -0
- pythinker_code/web/static/assets/everforest-dark-BgDCqdQA.js +1 -0
- pythinker_code/web/static/assets/everforest-light-C8M2exoo.js +1 -0
- pythinker_code/web/static/assets/fennel-BYunw83y.js +1 -0
- pythinker_code/web/static/assets/fish-BvzEVeQv.js +1 -0
- pythinker_code/web/static/assets/flowDiagram-NV44I4VS-QjW_fnGi.js +162 -0
- pythinker_code/web/static/assets/fluent-C4IJs8-o.js +1 -0
- pythinker_code/web/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- pythinker_code/web/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
- pythinker_code/web/static/assets/fsharp-CXgrBDvD.js +1 -0
- pythinker_code/web/static/assets/ganttDiagram-JELNMOA3-fqi8JFof.js +267 -0
- pythinker_code/web/static/assets/gdresource-B7Tvp0Sc.js +1 -0
- pythinker_code/web/static/assets/gdscript-DTMYz4Jt.js +1 -0
- pythinker_code/web/static/assets/gdshader-DkwncUOv.js +1 -0
- pythinker_code/web/static/assets/genie-D0YGMca9.js +1 -0
- pythinker_code/web/static/assets/gherkin-DyxjwDmM.js +1 -0
- pythinker_code/web/static/assets/git-commit-F4YmCXRG.js +1 -0
- pythinker_code/web/static/assets/git-rebase-r7XF79zn.js +1 -0
- pythinker_code/web/static/assets/gitGraphDiagram-NY62KEGX-i7o6VQ8x.js +65 -0
- pythinker_code/web/static/assets/github-dark-DHJKELXO.js +1 -0
- pythinker_code/web/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
- pythinker_code/web/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- pythinker_code/web/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- pythinker_code/web/static/assets/github-light-DAi9KRSo.js +1 -0
- pythinker_code/web/static/assets/github-light-default-D7oLnXFd.js +1 -0
- pythinker_code/web/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- pythinker_code/web/static/assets/gleam-BspZqrRM.js +1 -0
- pythinker_code/web/static/assets/glimmer-js-Rg0-pVw9.js +1 -0
- pythinker_code/web/static/assets/glimmer-ts-U6CK756n.js +1 -0
- pythinker_code/web/static/assets/glsl-DplSGwfg.js +1 -0
- pythinker_code/web/static/assets/gn-n2N0HUVH.js +1 -0
- pythinker_code/web/static/assets/gnuplot-DdkO51Og.js +1 -0
- pythinker_code/web/static/assets/go-Dn2_MT6a.js +1 -0
- pythinker_code/web/static/assets/graph-C0vZK2pT.js +1 -0
- pythinker_code/web/static/assets/graphql-ChdNCCLP.js +1 -0
- pythinker_code/web/static/assets/groovy-gcz8RCvz.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- pythinker_code/web/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- pythinker_code/web/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- pythinker_code/web/static/assets/hack-CaT9iCJl.js +1 -0
- pythinker_code/web/static/assets/haml-B8DHNrY2.js +1 -0
- pythinker_code/web/static/assets/handlebars-BL8al0AC.js +1 -0
- pythinker_code/web/static/assets/haskell-Df6bDoY_.js +1 -0
- pythinker_code/web/static/assets/haxe-CzTSHFRz.js +1 -0
- pythinker_code/web/static/assets/hcl-BWvSN4gD.js +1 -0
- pythinker_code/web/static/assets/hjson-D5-asLiD.js +1 -0
- pythinker_code/web/static/assets/hlsl-D3lLCCz7.js +1 -0
- pythinker_code/web/static/assets/houston-DnULxvSX.js +1 -0
- pythinker_code/web/static/assets/html-GMplVEZG.js +1 -0
- pythinker_code/web/static/assets/html-derivative-BFtXZ54Q.js +1 -0
- pythinker_code/web/static/assets/http-jrhK8wxY.js +1 -0
- pythinker_code/web/static/assets/hurl-irOxFIW8.js +1 -0
- pythinker_code/web/static/assets/hxml-Bvhsp5Yf.js +1 -0
- pythinker_code/web/static/assets/hy-DFXneXwc.js +1 -0
- pythinker_code/web/static/assets/imba-DGztddWO.js +1 -0
- pythinker_code/web/static/assets/index-BYCCk6-K.js +153 -0
- pythinker_code/web/static/assets/index-BpoLgcEt.js +1 -0
- pythinker_code/web/static/assets/index-Cpy4G3uJ.js +2 -0
- pythinker_code/web/static/assets/index-CzV_vCfu.css +1 -0
- pythinker_code/web/static/assets/index-DI2oedCt.js +19 -0
- pythinker_code/web/static/assets/index-DdIkp80K.js +5 -0
- pythinker_code/web/static/assets/infoDiagram-WHAUD3N6-BMPpeZSM.js +2 -0
- pythinker_code/web/static/assets/ini-BEwlwnbL.js +1 -0
- pythinker_code/web/static/assets/init-Gi6I4Gst.js +1 -0
- pythinker_code/web/static/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- pythinker_code/web/static/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- pythinker_code/web/static/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- pythinker_code/web/static/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- pythinker_code/web/static/assets/java-CylS5w8V.js +1 -0
- pythinker_code/web/static/assets/javascript-wDzz0qaB.js +1 -0
- pythinker_code/web/static/assets/jinja-4LBKfQ-Z.js +1 -0
- pythinker_code/web/static/assets/jison-wvAkD_A8.js +1 -0
- pythinker_code/web/static/assets/journeyDiagram-XKPGCS4Q-DAM7gngo.js +139 -0
- pythinker_code/web/static/assets/json-Cp-IABpG.js +1 -0
- pythinker_code/web/static/assets/json5-C9tS-k6U.js +1 -0
- pythinker_code/web/static/assets/jsonc-Des-eS-w.js +1 -0
- pythinker_code/web/static/assets/jsonl-DcaNXYhu.js +1 -0
- pythinker_code/web/static/assets/jsonnet-DFQXde-d.js +1 -0
- pythinker_code/web/static/assets/jssm-C2t-YnRu.js +1 -0
- pythinker_code/web/static/assets/jsx-g9-lgVsj.js +1 -0
- pythinker_code/web/static/assets/julia-CxzCAyBv.js +1 -0
- pythinker_code/web/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- pythinker_code/web/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- pythinker_code/web/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
- pythinker_code/web/static/assets/kanban-definition-3W4ZIXB7-ChpBpV0k.js +89 -0
- pythinker_code/web/static/assets/katex-D2lIc1rk.css +1 -0
- pythinker_code/web/static/assets/kdl-DV7GczEv.js +1 -0
- pythinker_code/web/static/assets/kotlin-BdnUsdx6.js +1 -0
- pythinker_code/web/static/assets/kusto-DZf3V79B.js +1 -0
- pythinker_code/web/static/assets/laserwave-DUszq2jm.js +1 -0
- pythinker_code/web/static/assets/latex-B4uzh10-.js +1 -0
- pythinker_code/web/static/assets/layout-C3Jp1gKO.js +1 -0
- pythinker_code/web/static/assets/lean-BZvkOJ9d.js +1 -0
- pythinker_code/web/static/assets/less-B1dDrJ26.js +1 -0
- pythinker_code/web/static/assets/light-plus-B7mTdjB0.js +1 -0
- pythinker_code/web/static/assets/linear-BGHtL1N4.js +1 -0
- pythinker_code/web/static/assets/liquid-DYVedYrR.js +1 -0
- pythinker_code/web/static/assets/llvm-BtvRca6l.js +1 -0
- pythinker_code/web/static/assets/log-2UxHyX5q.js +1 -0
- pythinker_code/web/static/assets/logo-BtOb2qkB.js +1 -0
- pythinker_code/web/static/assets/lua-BbnMAYS6.js +1 -0
- pythinker_code/web/static/assets/luau-C-HG3fhB.js +1 -0
- pythinker_code/web/static/assets/make-CHLpvVh8.js +1 -0
- pythinker_code/web/static/assets/markdown-Cvjx9yec.js +1 -0
- pythinker_code/web/static/assets/marko-DZsq8hO1.js +1 -0
- pythinker_code/web/static/assets/material-theme-D5KoaKCx.js +1 -0
- pythinker_code/web/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
- pythinker_code/web/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- pythinker_code/web/static/assets/material-theme-ocean-CyktbL80.js +1 -0
- pythinker_code/web/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- pythinker_code/web/static/assets/matlab-D7o27uSR.js +1 -0
- pythinker_code/web/static/assets/mdc-DUICxH0z.js +1 -0
- pythinker_code/web/static/assets/mdx-Cmh6b_Ma.js +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-B2P5VJ9v.css +1 -0
- pythinker_code/web/static/assets/mermaid-VLURNSYL-C_HW6koB.js +465 -0
- pythinker_code/web/static/assets/mermaid-mWjccvbQ.js +1 -0
- pythinker_code/web/static/assets/mermaid.core-CnT4VrPC.js +191 -0
- pythinker_code/web/static/assets/min-Dn5VRVmX.js +1 -0
- pythinker_code/web/static/assets/min-dark-CafNBF8u.js +1 -0
- pythinker_code/web/static/assets/min-light-CTRr51gU.js +1 -0
- pythinker_code/web/static/assets/mindmap-definition-VGOIOE7T-x8EwhfIt.js +68 -0
- pythinker_code/web/static/assets/mipsasm-CKIfxQSi.js +1 -0
- pythinker_code/web/static/assets/mojo-B93PlW-d.js +1 -0
- pythinker_code/web/static/assets/monokai-D4h5O-jR.js +1 -0
- pythinker_code/web/static/assets/moonbit-Ba13S78F.js +1 -0
- pythinker_code/web/static/assets/move-Bu9oaDYs.js +1 -0
- pythinker_code/web/static/assets/narrat-DRg8JJMk.js +1 -0
- pythinker_code/web/static/assets/nextflow-BrzmwbiE.js +1 -0
- pythinker_code/web/static/assets/nginx-DknmC5AR.js +1 -0
- pythinker_code/web/static/assets/night-owl-C39BiMTA.js +1 -0
- pythinker_code/web/static/assets/nim-CVrawwO9.js +1 -0
- pythinker_code/web/static/assets/nix-CwoSXNpI.js +1 -0
- pythinker_code/web/static/assets/nord-Ddv68eIx.js +1 -0
- pythinker_code/web/static/assets/nushell-C-sUppwS.js +1 -0
- pythinker_code/web/static/assets/objective-c-DXmwc3jG.js +1 -0
- pythinker_code/web/static/assets/objective-cpp-CLxacb5B.js +1 -0
- pythinker_code/web/static/assets/ocaml-C0hk2d4L.js +1 -0
- pythinker_code/web/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- pythinker_code/web/static/assets/one-light-PoHY5YXO.js +1 -0
- pythinker_code/web/static/assets/openscad-C4EeE6gA.js +1 -0
- pythinker_code/web/static/assets/ordinal-Cboi1Yqb.js +1 -0
- pythinker_code/web/static/assets/pascal-D93ZcfNL.js +1 -0
- pythinker_code/web/static/assets/perl-C0TMdlhV.js +1 -0
- pythinker_code/web/static/assets/php-CDn_0X-4.js +1 -0
- pythinker_code/web/static/assets/pieDiagram-ADFJNKIX-DgxBKGz2.js +30 -0
- pythinker_code/web/static/assets/pkl-u5AG7uiY.js +1 -0
- pythinker_code/web/static/assets/plastic-3e1v2bzS.js +1 -0
- pythinker_code/web/static/assets/plsql-ChMvpjG-.js +1 -0
- pythinker_code/web/static/assets/po-BTJTHyun.js +1 -0
- pythinker_code/web/static/assets/poimandres-CS3Unz2-.js +1 -0
- pythinker_code/web/static/assets/polar-C0HS_06l.js +1 -0
- pythinker_code/web/static/assets/postcss-CXtECtnM.js +1 -0
- pythinker_code/web/static/assets/powerquery-CEu0bR-o.js +1 -0
- pythinker_code/web/static/assets/powershell-Dpen1YoG.js +1 -0
- pythinker_code/web/static/assets/prisma-Dd19v3D-.js +1 -0
- pythinker_code/web/static/assets/prolog-CbFg5uaA.js +1 -0
- pythinker_code/web/static/assets/proto-C7zT0LnQ.js +1 -0
- pythinker_code/web/static/assets/pug-CGlum2m_.js +1 -0
- pythinker_code/web/static/assets/puppet-BMWR74SV.js +1 -0
- pythinker_code/web/static/assets/purescript-CklMAg4u.js +1 -0
- pythinker_code/web/static/assets/python-B6aJPvgy.js +1 -0
- pythinker_code/web/static/assets/qml-3beO22l8.js +1 -0
- pythinker_code/web/static/assets/qmldir-C8lEn-DE.js +1 -0
- pythinker_code/web/static/assets/qss-IeuSbFQv.js +1 -0
- pythinker_code/web/static/assets/quadrantDiagram-AYHSOK5B-DSpe_dqk.js +7 -0
- pythinker_code/web/static/assets/r-Dspwwk_N.js +1 -0
- pythinker_code/web/static/assets/racket-BqYA7rlc.js +1 -0
- pythinker_code/web/static/assets/raku-DXvB9xmW.js +1 -0
- pythinker_code/web/static/assets/razor-C1TweQQi.js +1 -0
- pythinker_code/web/static/assets/red-bN70gL4F.js +1 -0
- pythinker_code/web/static/assets/reg-C-SQnVFl.js +1 -0
- pythinker_code/web/static/assets/regexp-CDVJQ6XC.js +1 -0
- pythinker_code/web/static/assets/rel-C3B-1QV4.js +1 -0
- pythinker_code/web/static/assets/requirementDiagram-UZGBJVZJ-8o9hozL-.js +64 -0
- pythinker_code/web/static/assets/riscv-BM1_JUlF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- pythinker_code/web/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- pythinker_code/web/static/assets/rose-pine-qdsjHGoJ.js +1 -0
- pythinker_code/web/static/assets/rosmsg-BJDFO7_C.js +1 -0
- pythinker_code/web/static/assets/rst-B0xPkSld.js +1 -0
- pythinker_code/web/static/assets/ruby-BvKwtOVI.js +1 -0
- pythinker_code/web/static/assets/rust-B1yitclQ.js +1 -0
- pythinker_code/web/static/assets/sankeyDiagram-TZEHDZUN-BLOSUixH.js +10 -0
- pythinker_code/web/static/assets/sas-cz2c8ADy.js +1 -0
- pythinker_code/web/static/assets/sass-Cj5Yp3dK.js +1 -0
- pythinker_code/web/static/assets/scala-C151Ov-r.js +1 -0
- pythinker_code/web/static/assets/scheme-C98Dy4si.js +1 -0
- pythinker_code/web/static/assets/scss-OYdSNvt2.js +1 -0
- pythinker_code/web/static/assets/sdbl-DVxCFoDh.js +1 -0
- pythinker_code/web/static/assets/sequenceDiagram-WL72ISMW-6F2G8JTU.js +145 -0
- pythinker_code/web/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
- pythinker_code/web/static/assets/shellscript-Yzrsuije.js +1 -0
- pythinker_code/web/static/assets/shellsession-BADoaaVG.js +1 -0
- pythinker_code/web/static/assets/slack-dark-BthQWCQV.js +1 -0
- pythinker_code/web/static/assets/slack-ochin-DqwNpetd.js +1 -0
- pythinker_code/web/static/assets/smalltalk-BERRCDM3.js +1 -0
- pythinker_code/web/static/assets/snazzy-light-Bw305WKR.js +1 -0
- pythinker_code/web/static/assets/solarized-dark-DXbdFlpD.js +1 -0
- pythinker_code/web/static/assets/solarized-light-L9t79GZl.js +1 -0
- pythinker_code/web/static/assets/solidity-rGO070M0.js +1 -0
- pythinker_code/web/static/assets/soy-Brmx7dQM.js +1 -0
- pythinker_code/web/static/assets/sparql-rVzFXLq3.js +1 -0
- pythinker_code/web/static/assets/splunk-BtCnVYZw.js +1 -0
- pythinker_code/web/static/assets/sql-BLtJtn59.js +1 -0
- pythinker_code/web/static/assets/ssh-config-_ykCGR6B.js +1 -0
- pythinker_code/web/static/assets/stata-BH5u7GGu.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-FKZM4ZOC-DP8xP0iJ.js +1 -0
- pythinker_code/web/static/assets/stateDiagram-v2-4FDKWEC3-1l6-EZNX.js +1 -0
- pythinker_code/web/static/assets/stylus-BEDo0Tqx.js +1 -0
- pythinker_code/web/static/assets/svelte-zxCyuUbr.js +1 -0
- pythinker_code/web/static/assets/swift-Dg5xB15N.js +1 -0
- pythinker_code/web/static/assets/synthwave-84-CbfX1IO0.js +1 -0
- pythinker_code/web/static/assets/system-verilog-CnnmHF94.js +1 -0
- pythinker_code/web/static/assets/systemd-4A_iFExJ.js +1 -0
- pythinker_code/web/static/assets/talonscript-CkByrt1z.js +1 -0
- pythinker_code/web/static/assets/tasl-QIJgUcNo.js +1 -0
- pythinker_code/web/static/assets/tcl-dwOrl1Do.js +1 -0
- pythinker_code/web/static/assets/templ-W15q3VgB.js +1 -0
- pythinker_code/web/static/assets/terraform-BETggiCN.js +1 -0
- pythinker_code/web/static/assets/tex-CvyZ59Mk.js +1 -0
- pythinker_code/web/static/assets/timeline-definition-IT6M3QCI-DMgruDfK.js +61 -0
- pythinker_code/web/static/assets/tokyo-night-hegEt444.js +1 -0
- pythinker_code/web/static/assets/toml-vGWfd6FD.js +1 -0
- pythinker_code/web/static/assets/treemap-KMMF4GRG-Bo9ZkrAK.js +128 -0
- pythinker_code/web/static/assets/ts-tags-zn1MmPIZ.js +1 -0
- pythinker_code/web/static/assets/tsv-B_m7g4N7.js +1 -0
- pythinker_code/web/static/assets/tsx-COt5Ahok.js +1 -0
- pythinker_code/web/static/assets/turtle-BsS91CYL.js +1 -0
- pythinker_code/web/static/assets/twig-CO9l9SDP.js +1 -0
- pythinker_code/web/static/assets/typescript-BPQ3VLAy.js +1 -0
- pythinker_code/web/static/assets/typespec-BGHnOYBU.js +1 -0
- pythinker_code/web/static/assets/typst-DHCkPAjA.js +1 -0
- pythinker_code/web/static/assets/v-BcVCzyr7.js +1 -0
- pythinker_code/web/static/assets/vala-CsfeWuGM.js +1 -0
- pythinker_code/web/static/assets/vb-D17OF-Vu.js +1 -0
- pythinker_code/web/static/assets/verilog-BQ8w6xss.js +1 -0
- pythinker_code/web/static/assets/vesper-DU1UobuO.js +1 -0
- pythinker_code/web/static/assets/vhdl-CeAyd5Ju.js +1 -0
- pythinker_code/web/static/assets/viml-CJc9bBzg.js +1 -0
- pythinker_code/web/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
- pythinker_code/web/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
- pythinker_code/web/static/assets/vitesse-light-CVO1_9PV.js +1 -0
- pythinker_code/web/static/assets/vue-DN_0RTcg.js +1 -0
- pythinker_code/web/static/assets/vue-html-AaS7Mt5G.js +1 -0
- pythinker_code/web/static/assets/vue-vine-CQOfvN7w.js +1 -0
- pythinker_code/web/static/assets/vyper-CDx5xZoG.js +1 -0
- pythinker_code/web/static/assets/wasm-CG6Dc4jp.js +1 -0
- pythinker_code/web/static/assets/wasm-MzD3tlZU.js +1 -0
- pythinker_code/web/static/assets/wenyan-BV7otONQ.js +1 -0
- pythinker_code/web/static/assets/wgsl-Dx-B1_4e.js +1 -0
- pythinker_code/web/static/assets/wikitext-BhOHFoWU.js +1 -0
- pythinker_code/web/static/assets/wit-5i3qLPDT.js +1 -0
- pythinker_code/web/static/assets/wolfram-lXgVvXCa.js +1 -0
- pythinker_code/web/static/assets/xml-sdJ4AIDG.js +1 -0
- pythinker_code/web/static/assets/xsl-CtQFsRM5.js +1 -0
- pythinker_code/web/static/assets/xychartDiagram-PRI3JC2R-GeF2johi.js +7 -0
- pythinker_code/web/static/assets/yaml-Buea-lGh.js +1 -0
- pythinker_code/web/static/assets/zenscript-DVFEvuxE.js +1 -0
- pythinker_code/web/static/assets/zig-VOosw3JB.js +1 -0
- pythinker_code/web/static/brand/apple-touch-icon.png +0 -0
- pythinker_code/web/static/brand/arctecture.webp +0 -0
- pythinker_code/web/static/brand/bimi-logo.svg +46 -0
- pythinker_code/web/static/brand/favicon.ico +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/dm-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-sans-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/instrument-serif-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-italic-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/libre-baskerville-latin.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin-ext.woff2 +0 -0
- pythinker_code/web/static/brand/fonts/roboto-latin.woff2 +0 -0
- pythinker_code/web/static/brand/icon-192.png +0 -0
- pythinker_code/web/static/brand/icon-512.png +0 -0
- pythinker_code/web/static/brand/icon.svg +1 -0
- pythinker_code/web/static/brand/logo.png +0 -0
- pythinker_code/web/static/brand/pythinker_animated.svg +79 -0
- pythinker_code/web/static/brand/robots.txt +4 -0
- pythinker_code/web/static/index.html +15 -0
- pythinker_code/web/static/logo.png +0 -0
- pythinker_code/web/store/__init__.py +1 -0
- pythinker_code/web/store/sessions.py +432 -0
- pythinker_code/wire/__init__.py +148 -0
- pythinker_code/wire/file.py +151 -0
- pythinker_code/wire/jsonrpc.py +263 -0
- pythinker_code/wire/protocol.py +2 -0
- pythinker_code/wire/root_hub.py +27 -0
- pythinker_code/wire/serde.py +26 -0
- pythinker_code/wire/server.py +1069 -0
- pythinker_code/wire/types.py +698 -0
- pythinker_code-2.0.0.dist-info/METADATA +660 -0
- pythinker_code-2.0.0.dist-info/RECORD +731 -0
- pythinker_code-2.0.0.dist-info/WHEEL +4 -0
- pythinker_code-2.0.0.dist-info/entry_points.txt +4 -0
- pythinker_code-2.0.0.dist-info/licenses/LICENSE +202 -0
- 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
|