zettcode 0.1.12__tar.gz → 0.3.0__tar.gz
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.
- {zettcode-0.1.12 → zettcode-0.3.0}/.gitignore +4 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/PKG-INFO +2 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/pyproject.toml +11 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/__init__.py +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/agent/commands.py +3 -3
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/entries.py +7 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/presenters.py +12 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/rendering.py +158 -77
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/transcript/stream.py +5 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/commands.py +12 -7
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/model.py +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/__init__.py +24 -22
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/builtin_commands.py +333 -34
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/builtins.py +38 -21
- zettcode-0.1.12/src/zettcode/app/plugins/container.py → zettcode-0.3.0/src/zettcode/app/plugins/groups.py +48 -31
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/loader.py +102 -32
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/mixins.py +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/plugin.py +11 -6
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/app.py +39 -9
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/followups.py +2 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/keys.py +6 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/messaging.py +28 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/notices.py +1 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/sessions.py +18 -7
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/shell.py +9 -5
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/updating.py +57 -3
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/__init__.py +10 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/approval_page.py +11 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/composer.py +20 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/model_settings_page.py +2 -3
- zettcode-0.3.0/src/zettcode/app/ui/widgets/plugins_page.py +428 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/self_compact_page.py +9 -54
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/statusline_page.py +3 -33
- zettcode-0.3.0/src/zettcode/app/ui/widgets/switch.py +87 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/update_page.py +4 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/client.py +104 -6
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/hub.py +76 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/models.py +5 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/protocol.py +14 -3
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/roster.py +7 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/config.py +30 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/config_edit.py +17 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/daemon/cli.py +76 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/daemon/run.py +27 -5
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/turns.py +7 -5
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/core.py +13 -3
- zettcode-0.3.0/src/zettcode/engine/extensions/harness.py +199 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/mcp_notice.py +8 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/subagents.py +4 -3
- zettcode-0.3.0/src/zettcode/engine/extensions/truncation.py +156 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/messenger.py +78 -4
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/permission/__init__.py +18 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/permission/sandbox.py +19 -3
- zettcode-0.3.0/src/zettcode/engine/plugins/__init__.py +73 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/plugins/builtin_agent.py +144 -91
- zettcode-0.3.0/src/zettcode/engine/plugins/commands.py +131 -0
- zettcode-0.1.12/src/zettcode/engine/plugins/container.py → zettcode-0.3.0/src/zettcode/engine/plugins/context.py +31 -8
- zettcode-0.3.0/src/zettcode/engine/plugins/groups.py +80 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/plugins/loader.py +64 -30
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/plugins/plugin.py +55 -11
- zettcode-0.3.0/src/zettcode/engine/plugins/switches.py +412 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/server_tools.py +24 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/storage/__init__.py +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/tools/boundary.py +7 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/tools/files.py +42 -47
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/tools/output.py +12 -5
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/tools/shell.py +20 -10
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/tools/workspace.py +80 -77
- zettcode-0.3.0/src/zettcode/gateway/__init__.py +22 -0
- zettcode-0.3.0/src/zettcode/gateway/app.py +389 -0
- zettcode-0.3.0/src/zettcode/gateway/backend.py +291 -0
- zettcode-0.3.0/src/zettcode/gateway/chat.py +128 -0
- zettcode-0.3.0/src/zettcode/gateway/cli.py +138 -0
- zettcode-0.3.0/src/zettcode/gateway/service.py +811 -0
- zettcode-0.3.0/src/zettcode/gateway/settings.py +62 -0
- zettcode-0.3.0/src/zettcode/gateway/static/app.css +753 -0
- zettcode-0.3.0/src/zettcode/gateway/static/app.js +3 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_AMS-Regular-U6PRYMIZ.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Caligraphic-Bold-5QL5CMTE.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Caligraphic-Regular-KX5MEWCF.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Fraktur-Bold-2QVFK6NQ.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Fraktur-Regular-2PEIFJSJ.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Main-Bold-YP5VVQRP.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Main-BoldItalic-N4V3DX7S.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Main-Italic-RELBIK7M.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Main-Regular-ARRPAO67.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Math-BoldItalic-K4WTGH3J.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Math-Italic-6KGCHLFN.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_SansSerif-Bold-RRNVJFFW.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_SansSerif-Italic-HMPFTM52.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_SansSerif-Regular-XIQ62X4E.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Script-Regular-APUWIHLP.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Size1-Regular-5LRUTBFT.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Size2-Regular-LELKET5D.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Size3-Regular-WQRQ47UD.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Size4-Regular-CDMV7U5C.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/assets/KaTeX_Typewriter-Regular-VBYJ4NRC.woff2 +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/chunks/chunk-Y6SLVHK3.js +1 -0
- zettcode-0.3.0/src/zettcode/gateway/static/chunks/render-G6G3TSB3.js +341 -0
- zettcode-0.3.0/src/zettcode/gateway/static/icon.png +0 -0
- zettcode-0.3.0/src/zettcode/gateway/static/index.html +23 -0
- zettcode-0.3.0/src/zettcode/gateway/static/logo.svg +205 -0
- zettcode-0.3.0/src/zettcode/gateway/static/manifest.webmanifest +11 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/paths.py +4 -0
- zettcode-0.3.0/src/zettcode/plugin_tui.py +104 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/relay.py +3 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/__init__.py +11 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/client.py +299 -10
- zettcode-0.3.0/src/zettcode/sdk/commands.py +9 -0
- zettcode-0.3.0/src/zettcode/sdk/daemons.py +153 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/errors.py +13 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/events.py +10 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/session.py +71 -25
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/translation.py +25 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/types.py +184 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/server/app.py +67 -2
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/server/runner.py +41 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/server/schemas.py +90 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/server/serialize.py +7 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/theme.py +15 -7
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/ayu-mirage.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/catppuccin-latte.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/catppuccin-mocha.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/dracula.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/github-dimmed.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/github-light.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/gruvbox-dark.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/gruvbox-light.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/kanagawa.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/nord.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/one-dark.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/rose-pine-dawn.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/rose-pine.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/solarized-dark.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/solarized-light.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/themes/tokyo-night.toml +1 -1
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/update.py +31 -15
- zettcode-0.1.12/src/zettcode/engine/extensions/harness.py +0 -89
- zettcode-0.1.12/src/zettcode/engine/plugins/__init__.py +0 -64
- {zettcode-0.1.12 → zettcode-0.3.0}/README.md +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/_compat.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/agent/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/agent/settings.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/changes.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/effort_gauge.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/projection.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/pulse.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/replay.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/shimmer.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/stops.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/text.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/tips.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/transcript/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/transcript/fold.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/transcript/log.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/transcript/oneoff.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/agent/conversation/transcript/turn.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/brand.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/commandline/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/commandline/arguments.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/commandline/completion.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/commandline/history.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/commandline/runner.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/cat.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/chibi.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/dog.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/kangaroo.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/penguin.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/polarbear.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/scarfy.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/seagull.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/slime.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/snowbear.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/sprout.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/tiger.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/pets/whale.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/host.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/plugins/state.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/bars.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/boot.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/clipboard.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/commands.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/config_form.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/demo.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/gc_policy.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/labels.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/providers.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/settings.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/turns.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/ask_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/bash_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/completer.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/context_charts.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/context_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/effort_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/follow_up.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/form_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/input_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/model_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/panel.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/pet.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/pet_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/root.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/send_kind_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/sessions_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/steering.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/transcript.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/usage_charts.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/usage_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/app/ui/widgets/welcome.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/__main__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/files.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/finder.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/log.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/process.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/runner.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/server.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/text.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/broker/web.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/budget.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/cli.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/collector.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/daemon/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/daemon/__main__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/daemon/process.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/daemon/state.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/doctor.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/base.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/context.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/exports.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/interaction.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/sessions.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/agent/settings.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/dialogue/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/dialogue/ask.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/dialogue/side.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/dialogue/title.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/export/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/export/trace/script.js +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/export/trace/style.css +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/export/trace/template.html +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/capabilities.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/compaction.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/context.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/files.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/harness_switch.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/mcp.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/messaging.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/relay.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/tool_output.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/extensions/usage.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/permission/approval.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/permission/readonly.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/pipeline.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/plugins/extension.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/plugins/mixins.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/plugins/services.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/plugins/state.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/storage/layout.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/storage/ledger.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/storage/locks.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/storage/metadata.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/storage/records.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/storage/store.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/tools/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/engine/tools/images.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/error_text.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/filelock.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/mentions.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/oneshot.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/prompt.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/providers.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/py.typed +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/registry.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/handlers.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/sdk/models.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/server/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/server/cli.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/server/settings.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/statusline.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/capabilities.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/clipboard.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/console.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/app.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/events.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/focus.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/geometry.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/host.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/keymap.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/scheduler.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/screen.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/core/widget.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/gallery.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/glyphs.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/input/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/input/bridge.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/input/decoder.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/input/events.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/input/keys.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/input/reader.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/layout/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/layout/box.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/layout/overlay.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/layout/scroll.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/layout/solver.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/layout/spacing.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/ansi.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/canvas.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/code.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/color.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/renderer.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/rich_text.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/style.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/sweep.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/render/text.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/runner.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/terminal.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/testing/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/testing/harness.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/testing/snapshot.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/theme_file.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/__init__.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/collapsible.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/completion.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/console.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/dialog.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/diff.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/list.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/list_page.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/markdown.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/progress.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/rich_text.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/status.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/table.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/tasks.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/text.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/textarea.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/tui/widgets/toast.py +0 -0
- {zettcode-0.1.12 → zettcode-0.3.0}/src/zettcode/wording.py +0 -0
|
@@ -24,6 +24,10 @@ docs/.vitepress/.temp/
|
|
|
24
24
|
# The TypeScript client is built from its sources; only dist/ is published.
|
|
25
25
|
clients/typescript/dist/
|
|
26
26
|
|
|
27
|
+
# The gateway's page is built from clients/gateway into the package when a distribution is made (`make gateway-ui`);
|
|
28
|
+
# the build output is not kept in the repository. pyproject's `artifacts` puts it in the wheel all the same.
|
|
29
|
+
zettcode/src/zettcode/gateway/static/
|
|
30
|
+
|
|
27
31
|
# The palette gallery is generated by `make palettes`: every widget painted in
|
|
28
32
|
# every palette, written by docs/scripts/palette_gallery.py. Regenerate it
|
|
29
33
|
# rather than committing a 2 MB copy that goes stale the moment a colour moves.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: zettcode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A focused terminal coding agent powered by zett-agent
|
|
5
5
|
Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
|
|
6
6
|
Project-URL: Issues, https://github.com/Chang-LeHung/zettcode/issues
|
|
@@ -35,7 +35,7 @@ Requires-Dist: truststore<1,>=0.10
|
|
|
35
35
|
Requires-Dist: uvicorn<1,>=0.35
|
|
36
36
|
Requires-Dist: wcwidth<1,>=0.2
|
|
37
37
|
Requires-Dist: websockets<17,>=15
|
|
38
|
-
Requires-Dist: zett-agent<0.3,>=0.2.
|
|
38
|
+
Requires-Dist: zett-agent<0.3,>=0.2.2
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
|
|
41
41
|
# ZettCode
|
|
@@ -57,13 +57,17 @@ dependencies = [
|
|
|
57
57
|
# tiktoken, and httpx behind their first use, so warming a runtime costs less
|
|
58
58
|
# than it did. An exact pin would make zettcode fight every other tool that
|
|
59
59
|
# shares the dependency — including an upgrade of zett-agent itself, which pip
|
|
60
|
-
# would downgrade to satisfy it.
|
|
61
|
-
|
|
60
|
+
# would downgrade to satisfy it. 0.2.2 is the floor for one reason this program
|
|
61
|
+
# now leans on: a hosted tool the provider left open is closed with
|
|
62
|
+
# ``output={"status": "not_reported"}`` instead of leaving the step waiting, and
|
|
63
|
+
# `engine/server_tools.describe_output` reads that to say so.
|
|
64
|
+
"zett-agent>=0.2.2,<0.3",
|
|
62
65
|
]
|
|
63
66
|
|
|
64
67
|
[project.scripts]
|
|
65
68
|
zettcode = "zettcode.cli:main"
|
|
66
69
|
zettcode-server = "zettcode.server.cli:main"
|
|
70
|
+
zettcode-gateway = "zettcode.gateway.cli:main"
|
|
67
71
|
|
|
68
72
|
[project.urls]
|
|
69
73
|
Repository = "https://github.com/Chang-LeHung/zettcode"
|
|
@@ -77,6 +81,11 @@ build-backend = "hatchling.build"
|
|
|
77
81
|
# Single source of truth: the version is written once, in the package.
|
|
78
82
|
path = "src/zettcode/__init__.py"
|
|
79
83
|
|
|
84
|
+
# The gateway's page is built (make gateway-ui) into the package and is not in git; hatchling skips what git ignores
|
|
85
|
+
# unless it is named here.
|
|
86
|
+
[tool.hatch.build]
|
|
87
|
+
artifacts = ["src/zettcode/gateway/static/**"]
|
|
88
|
+
|
|
80
89
|
[tool.hatch.build.targets.wheel]
|
|
81
90
|
packages = ["src/zettcode"]
|
|
82
91
|
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
#: The one place the version is written. ``pyproject.toml`` reads it from here
|
|
9
9
|
#: and the release workflow checks the tag against it, so a bump is one edit.
|
|
10
|
-
__version__ = "0.
|
|
10
|
+
__version__ = "0.3.0"
|
|
11
11
|
|
|
12
12
|
#: Where each public name lives; read by :func:`__getattr__` on first use.
|
|
13
13
|
_EXPORTS = {
|
|
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from zettcode.app.commands import Command, CommandContext, CommandHost, CommandResult
|
|
11
11
|
from zettcode.app.pets import PetCatalog, bundled_pets
|
|
12
12
|
from zettcode.app.plugins.host import HOST_LINK, HostLink
|
|
13
|
-
from zettcode.app.plugins.plugin import
|
|
13
|
+
from zettcode.app.plugins.plugin import TuiPlugins
|
|
14
14
|
from zettcode.app.plugins.state import TurnSegment, UiRow
|
|
15
15
|
from zettcode.engine.agent.exports import ExportsMixin
|
|
16
16
|
from zettcode.providers import BUILTIN_PROVIDERS, Provider, merge_providers
|
|
@@ -28,10 +28,10 @@ class CommandsMixin(AppearanceMixin, ExportsMixin):
|
|
|
28
28
|
"""The ``/new``, ``/compact``, and ``/export`` commands, and the plugins' commands, mentions, and rows."""
|
|
29
29
|
|
|
30
30
|
@property
|
|
31
|
-
def terminal_plugins(self) ->
|
|
31
|
+
def terminal_plugins(self) -> TuiPlugins:
|
|
32
32
|
"""Return what the plugins contributed to the terminal; the engine was built with the terminal's loader."""
|
|
33
33
|
plugins = self.runtime.plugins
|
|
34
|
-
if not isinstance(plugins,
|
|
34
|
+
if not isinstance(plugins, TuiPlugins):
|
|
35
35
|
raise TypeError("This agent's engine was not built with the terminal's plugin loader")
|
|
36
36
|
return plugins
|
|
37
37
|
|
|
@@ -243,10 +243,16 @@ class ToolEntry(TimedEntry):
|
|
|
243
243
|
class ReadEntry(ToolEntry):
|
|
244
244
|
"""A file that was read: its body is source code, drawn as the language it is.
|
|
245
245
|
|
|
246
|
-
``language`` names the scanner for the output, or is ``None`` for a file that is not code.
|
|
246
|
+
``language`` names the scanner for the output, or is ``None`` for a file that is not code. ``lines`` is how many
|
|
247
|
+
lines came back, once a completed read has one; it is a field of the row rather than part of its title, so the
|
|
248
|
+
title being restated or cut at the width cannot lose it. ``picture`` marks a path that looks like a picture — the suffix the read tool
|
|
249
|
+
sends as an image — since a row is opened from the arguments, before the result exists; a file whose content is an
|
|
250
|
+
image under another name is counted as text until the result itself says otherwise.
|
|
247
251
|
"""
|
|
248
252
|
|
|
249
253
|
language: str | None = None
|
|
254
|
+
lines: int | None = None
|
|
255
|
+
picture: bool = False
|
|
250
256
|
|
|
251
257
|
|
|
252
258
|
@dataclass(slots=True, kw_only=True)
|
|
@@ -680,10 +680,21 @@ class SessionMessagePresenter(ToolPresenter[MessageEntry]):
|
|
|
680
680
|
|
|
681
681
|
|
|
682
682
|
class ReadPresenter(ToolPresenter[ReadEntry]):
|
|
683
|
-
"""A file that was read: the body is highlighted as the language the file is in."""
|
|
683
|
+
"""A file that was read: the body is highlighted as the language the file is in, and the row says how much."""
|
|
684
684
|
|
|
685
685
|
entry_type = ReadEntry
|
|
686
686
|
|
|
687
|
+
def after_title(self, entry: ReadEntry, theme: Theme) -> list[Span]:
|
|
688
|
+
"""Say how many lines came back, beside the title rather than inside it.
|
|
689
|
+
|
|
690
|
+
A field of the row survives what a title does not: `restate_tool` rewrites the title from the arguments, and
|
|
691
|
+
the count is drawn from the entry, the way an edit's ``+N -N`` is. A row too narrow for the title and the
|
|
692
|
+
count together cuts the count, as it cuts any tail — an edit's counts go the same way.
|
|
693
|
+
"""
|
|
694
|
+
if entry.lines is None:
|
|
695
|
+
return []
|
|
696
|
+
return [Span(f" \u00b7 {entry.lines} line" + ("" if entry.lines == 1 else "s"), Style(foreground=theme.subtle))]
|
|
697
|
+
|
|
687
698
|
def body_lines(self, entry: ReadEntry, width: int, theme: Theme, muted: Style) -> list[TextLine]:
|
|
688
699
|
"""Show the output as source code when the file is."""
|
|
689
700
|
return self.output_lines(entry, width, theme, muted, language=entry.language)
|
|
@@ -33,7 +33,7 @@ from pathlib import Path
|
|
|
33
33
|
from zettcode.tui import ELLIPSIS, SEPARATOR
|
|
34
34
|
from zettcode.tui.render import GENERIC, language_for
|
|
35
35
|
|
|
36
|
-
from .entries import DeleteEntry, EditEntry, MessageEntry, ReadEntry, ToolEntry, WriteEntry
|
|
36
|
+
from .entries import DeleteEntry, EditEntry, EntryStatus, MessageEntry, ReadEntry, ToolEntry, WriteEntry
|
|
37
37
|
from .text import DECLINED_NOTE, limit_output
|
|
38
38
|
|
|
39
39
|
#: Output kinds the chain shapes; a link claims one or more of them.
|
|
@@ -41,9 +41,25 @@ TOOL = "tool"
|
|
|
41
41
|
THINKING = "thinking"
|
|
42
42
|
ANSWER = "answer"
|
|
43
43
|
|
|
44
|
+
#: Suffixes that look like a picture, copied from the read tool's accepted set: a row is opened from the arguments,
|
|
45
|
+
#: before the result exists, so the suffix is all it has. The durable answer is a boolean on the finished event, taken
|
|
46
|
+
#: from the result's own content — see :func:`is_picture_path`.
|
|
47
|
+
PICTURE_SUFFIXES = frozenset({".png", ".jpg", ".jpeg", ".gif", ".webp"})
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def is_picture_path(path: str) -> bool:
|
|
51
|
+
"""Return whether ``path`` looks like a picture by its suffix.
|
|
52
|
+
|
|
53
|
+
A guess, and named like one: the read tool decides by the file's signature, so a PNG copied to ``.bin`` is counted
|
|
54
|
+
as text and a text file named ``notes.png`` is not counted. Both are a line about a row nobody is reading closely;
|
|
55
|
+
the exact answer needs the boolean the result will carry (see :data:`PICTURE_SUFFIXES`).
|
|
56
|
+
"""
|
|
57
|
+
return Path(path).suffix.lower() in PICTURE_SUFFIXES
|
|
58
|
+
|
|
59
|
+
|
|
44
60
|
#: The hint a bounded read leaves behind; it is advice for the model, not the
|
|
45
61
|
#: reader, and the row already shows the text it applies to.
|
|
46
|
-
CONTINUATION = re.compile(r"\n?\.\.\. \[(?:line truncated|more lines)[^\]]*\]\s*\Z")
|
|
62
|
+
CONTINUATION = re.compile(r"\n?\.\.\. \[(?:line truncated|output truncated|more lines)[^\]]*\]\s*\Z")
|
|
47
63
|
|
|
48
64
|
#: Wrapper tags a model may leave around its reasoning channel.
|
|
49
65
|
REASONING_TAGS = re.compile(r"</?(?:think(?:ing)?|reasoning)>|<\|(?:thinking|reasoning)\|>", re.IGNORECASE)
|
|
@@ -128,6 +144,16 @@ class RenderHandler(ABC):
|
|
|
128
144
|
"""Return the text under a tool row; only a tool link changes this."""
|
|
129
145
|
return output
|
|
130
146
|
|
|
147
|
+
def finished(self, entry: ToolEntry, text: str, status: EntryStatus) -> None:
|
|
148
|
+
"""Adjust a row now that its result is in — a count, a size — and change nothing by default.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
entry: The row, whose body is already set and bounded.
|
|
152
|
+
text: The body as it came back, after :meth:`body` shaped it and before it was bounded.
|
|
153
|
+
status: Whether the call completed, failed or was skipped: a row may only count what worked.
|
|
154
|
+
"""
|
|
155
|
+
return None
|
|
156
|
+
|
|
131
157
|
def text(self, text: str, *, opening: bool) -> str:
|
|
132
158
|
"""Return one streamed fragment, unchanged unless a link shapes it.
|
|
133
159
|
|
|
@@ -183,6 +209,12 @@ class Renderers:
|
|
|
183
209
|
handler = self._claim(TOOL, name)
|
|
184
210
|
return output if handler is None else handler.body(name, output)
|
|
185
211
|
|
|
212
|
+
def finished(self, entry: ToolEntry, text: str, status: EntryStatus) -> None:
|
|
213
|
+
"""Let the claiming link adjust a row once its result is in."""
|
|
214
|
+
handler = self._claim(TOOL, entry.tool)
|
|
215
|
+
if handler is not None:
|
|
216
|
+
handler.finished(entry, text, status)
|
|
217
|
+
|
|
186
218
|
def text(self, kind: str, text: str, *, opening: bool = False) -> str:
|
|
187
219
|
"""Return one streamed fragment of ``kind``, shaped by the claiming link."""
|
|
188
220
|
handler = self._claim(kind)
|
|
@@ -197,38 +229,48 @@ def argument(arguments: Mapping[str, object], key: str, default: str = "") -> st
|
|
|
197
229
|
return default
|
|
198
230
|
|
|
199
231
|
|
|
200
|
-
def
|
|
232
|
+
def edit_changes(arguments: Mapping[str, object]) -> tuple[tuple[str, str], ...]:
|
|
233
|
+
"""Return the text pairs an ``edit`` call replaces: one pair now, a list of them from an older transcript.
|
|
234
|
+
|
|
235
|
+
A call carries ``old_string`` and ``new_string``. A session stored before that shape gave the arguments an
|
|
236
|
+
``edits`` list, and it is read the same way so the row of an old transcript still shows its diff. Anything
|
|
237
|
+
that is not two strings is dropped rather than guessed at.
|
|
238
|
+
"""
|
|
239
|
+
legacy = arguments.get("edits")
|
|
240
|
+
if isinstance(legacy, Sequence) and not isinstance(legacy, str):
|
|
241
|
+
return tuple(
|
|
242
|
+
(edit["old_text"], edit["new_text"])
|
|
243
|
+
for edit in legacy
|
|
244
|
+
if isinstance(edit, Mapping)
|
|
245
|
+
and isinstance(edit.get("old_text"), str)
|
|
246
|
+
and isinstance(edit.get("new_text"), str)
|
|
247
|
+
)
|
|
248
|
+
old_string, new_string = arguments.get("old_string"), arguments.get("new_string")
|
|
249
|
+
if isinstance(old_string, str) and isinstance(new_string, str):
|
|
250
|
+
return ((old_string, new_string),)
|
|
251
|
+
return ()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def edited_lines(changes: Sequence[tuple[str, str]]) -> tuple[int, int]:
|
|
201
255
|
"""Return the lines an ``edit`` call adds and removes.
|
|
202
256
|
|
|
203
|
-
The tool answers ``Replaced
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
rewrite, and that is what a reader comparing two rows is looking at.
|
|
257
|
+
The tool answers ``Replaced 1 occurrence in app.py``, which says that a change landed but not how large it was:
|
|
258
|
+
a two-line fix and a two-hundred-line rewrite read the same. The call carries both texts, so the difference is
|
|
259
|
+
counted here — with :class:`difflib.SequenceMatcher` rather than by subtracting lengths, because a replacement
|
|
260
|
+
that keeps the lines around it should not read as a rewrite, and that is what a reader comparing two rows is
|
|
261
|
+
looking at.
|
|
209
262
|
|
|
210
263
|
Args:
|
|
211
|
-
|
|
212
|
-
mappings with ``old_text`` and ``new_text``. Anything else is ignored
|
|
213
|
-
rather than guessed at.
|
|
264
|
+
changes: The old and new text of each change, as :func:`edit_changes` reads them.
|
|
214
265
|
|
|
215
266
|
Returns:
|
|
216
|
-
``(added, removed)`` across every
|
|
217
|
-
countable, which is also what a call with no edits at all answers.
|
|
267
|
+
``(added, removed)`` across every change; ``(0, 0)`` when the call carried nothing countable.
|
|
218
268
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
answer is where that number appears.
|
|
269
|
+
A change that replaces every occurrence counts once, not once per occurrence: how many the file holds is
|
|
270
|
+
something only the file knows, and the tool's own answer is where that number appears.
|
|
222
271
|
"""
|
|
223
|
-
if not isinstance(edits, Sequence) or isinstance(edits, str):
|
|
224
|
-
return (0, 0)
|
|
225
272
|
added = removed = 0
|
|
226
|
-
for
|
|
227
|
-
if not isinstance(edit, Mapping):
|
|
228
|
-
continue
|
|
229
|
-
old_text, new_text = edit.get("old_text"), edit.get("new_text")
|
|
230
|
-
if not isinstance(old_text, str) or not isinstance(new_text, str):
|
|
231
|
-
continue
|
|
273
|
+
for old_text, new_text in changes:
|
|
232
274
|
matcher = difflib.SequenceMatcher(None, old_text.splitlines(), new_text.splitlines())
|
|
233
275
|
for tag, old_start, old_end, new_start, new_end in matcher.get_opcodes():
|
|
234
276
|
if tag in ("replace", "delete"):
|
|
@@ -238,31 +280,24 @@ def edited_lines(edits: object) -> tuple[int, int]:
|
|
|
238
280
|
return (added, removed)
|
|
239
281
|
|
|
240
282
|
|
|
241
|
-
def edit_diff(
|
|
283
|
+
def edit_diff(changes: Sequence[tuple[str, str]], *, limit: int = MAX_DIFF_ROWS) -> tuple[DiffRow, ...]:
|
|
242
284
|
"""Return the rows of the change an ``edit`` call makes.
|
|
243
285
|
|
|
244
|
-
Each
|
|
286
|
+
Each change carries the text it replaces and the text it puts there, so the change can be shown
|
|
245
287
|
without reading the file: the lines that differ, and one unchanged line around each so it reads in
|
|
246
|
-
place.
|
|
247
|
-
cut at ``limit`` rows with a last row saying so, because the
|
|
288
|
+
place. Changes are told apart by a labelled break when there is more than one — which only an older
|
|
289
|
+
transcript has — and what is kept is cut at ``limit`` rows with a last row saying so, because the
|
|
290
|
+
arguments can be a whole file.
|
|
248
291
|
|
|
249
292
|
Args:
|
|
250
|
-
|
|
251
|
-
two texts is skipped rather than guessed at, exactly as :func:`edited_lines` does.
|
|
293
|
+
changes: The old and new text of each change, as :func:`edit_changes` reads them.
|
|
252
294
|
limit: Most rows to keep.
|
|
253
295
|
"""
|
|
254
|
-
if not isinstance(edits, Sequence) or isinstance(edits, str):
|
|
255
|
-
return ()
|
|
256
|
-
usable = [
|
|
257
|
-
edit
|
|
258
|
-
for edit in edits
|
|
259
|
-
if isinstance(edit, Mapping) and isinstance(edit.get("old_text"), str) and isinstance(edit.get("new_text"), str)
|
|
260
|
-
]
|
|
261
296
|
rows: list[DiffRow] = []
|
|
262
|
-
for number,
|
|
263
|
-
if len(
|
|
264
|
-
rows.append(("@", f"edit {number} of {len(
|
|
265
|
-
old_lines, new_lines =
|
|
297
|
+
for number, (old_text, new_text) in enumerate(changes, start=1):
|
|
298
|
+
if len(changes) > 1:
|
|
299
|
+
rows.append(("@", f"edit {number} of {len(changes)}"))
|
|
300
|
+
old_lines, new_lines = old_text.splitlines(), new_text.splitlines()
|
|
266
301
|
opcodes = difflib.SequenceMatcher(None, old_lines, new_lines).get_opcodes()
|
|
267
302
|
for index, (tag, old_start, old_end, new_start, new_end) in enumerate(opcodes):
|
|
268
303
|
if tag == "equal":
|
|
@@ -350,6 +385,15 @@ def _value_text(value: object) -> str:
|
|
|
350
385
|
return str(value)
|
|
351
386
|
|
|
352
387
|
|
|
388
|
+
def file_path(arguments: Mapping[str, object], default: str = "") -> str:
|
|
389
|
+
"""Return the path a file tool was called for.
|
|
390
|
+
|
|
391
|
+
``file_path`` is the argument now; a session stored before the rename carries ``path``, and is still shown the
|
|
392
|
+
file it changed rather than as an unknown one.
|
|
393
|
+
"""
|
|
394
|
+
return argument(arguments, "file_path") or argument(arguments, "path") or default
|
|
395
|
+
|
|
396
|
+
|
|
353
397
|
def language_for_path(path: str) -> str | None:
|
|
354
398
|
"""Return the scanner for a file's contents, or ``None`` when it is not code."""
|
|
355
399
|
language = language_for(Path(path).suffix.lstrip("."))
|
|
@@ -363,22 +407,40 @@ class ReadTool(ToolHandler):
|
|
|
363
407
|
|
|
364
408
|
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
365
409
|
"""Show the file, and highlight the body as the language it is."""
|
|
366
|
-
path =
|
|
410
|
+
path = file_path(arguments, "(unknown file)")
|
|
367
411
|
return ToolRow(f"Read {path}", language_for_path(path))
|
|
368
412
|
|
|
369
413
|
def open_tool(
|
|
370
414
|
self, name: str, arguments: Mapping[str, object], *, id: int, call_id: str, started_at: float
|
|
371
415
|
) -> ToolEntry:
|
|
372
|
-
"""A read row, which knows the language its body is in."""
|
|
416
|
+
"""A read row, which knows the language its body is in, and whether the file is a picture."""
|
|
373
417
|
row = self.describe(name, arguments)
|
|
418
|
+
path = file_path(arguments)
|
|
374
419
|
return ReadEntry(
|
|
375
|
-
id=id,
|
|
420
|
+
id=id,
|
|
421
|
+
call_id=call_id,
|
|
422
|
+
tool=name,
|
|
423
|
+
title=row.title,
|
|
424
|
+
language=row.language,
|
|
425
|
+
picture=is_picture_path(path),
|
|
426
|
+
started_at=started_at,
|
|
376
427
|
)
|
|
377
428
|
|
|
378
429
|
def body(self, name: str, output: str) -> str:
|
|
379
430
|
"""Drop the continuation hint; the row bounds the text it applies to."""
|
|
380
431
|
return CONTINUATION.sub("", output).rstrip()
|
|
381
432
|
|
|
433
|
+
def finished(self, entry: ToolEntry, text: str, status: EntryStatus) -> None:
|
|
434
|
+
"""Count what the read returned, on the row, if it returned text and the call worked.
|
|
435
|
+
|
|
436
|
+
The count is of what came back — a range asked for is not the same number — and of the whole of it, not of
|
|
437
|
+
the preview the row keeps, so a read past the output limit is counted in full. A picture carries no lines,
|
|
438
|
+
and a failed call's body is the error message rather than the file, so neither is counted.
|
|
439
|
+
"""
|
|
440
|
+
if status is not EntryStatus.COMPLETED or not isinstance(entry, ReadEntry) or entry.picture:
|
|
441
|
+
return
|
|
442
|
+
entry.lines = len(text.splitlines())
|
|
443
|
+
|
|
382
444
|
|
|
383
445
|
class WriteTool(ToolHandler):
|
|
384
446
|
"""``write_file``: the file being written."""
|
|
@@ -387,7 +449,7 @@ class WriteTool(ToolHandler):
|
|
|
387
449
|
|
|
388
450
|
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
389
451
|
"""Show the file being written: the row is the call, so it says what the call does, not what it did."""
|
|
390
|
-
return ToolRow(f"Write {
|
|
452
|
+
return ToolRow(f"Write {file_path(arguments, '(unknown file)')}")
|
|
391
453
|
|
|
392
454
|
def open_tool(
|
|
393
455
|
self, name: str, arguments: Mapping[str, object], *, id: int, call_id: str, started_at: float
|
|
@@ -399,7 +461,7 @@ class WriteTool(ToolHandler):
|
|
|
399
461
|
call_id=call_id,
|
|
400
462
|
tool=name,
|
|
401
463
|
title=self.describe(name, arguments).title,
|
|
402
|
-
path=
|
|
464
|
+
path=file_path(arguments),
|
|
403
465
|
added=written_lines(content),
|
|
404
466
|
diff=write_diff(content),
|
|
405
467
|
started_at=started_at,
|
|
@@ -407,25 +469,21 @@ class WriteTool(ToolHandler):
|
|
|
407
469
|
|
|
408
470
|
|
|
409
471
|
class EditTool(ToolHandler):
|
|
410
|
-
"""``edit``: the file and
|
|
472
|
+
"""``edit``: the file, and the size of the one change the call makes.
|
|
411
473
|
|
|
412
|
-
A session stored before the tool was renamed calls it ``replace_in_file``, and is still shown the same way
|
|
474
|
+
A session stored before the tool was renamed calls it ``replace_in_file``, and is still shown the same way; one
|
|
475
|
+
stored before a call carried a single change gave it a list under ``edits``, and that list is read too.
|
|
413
476
|
"""
|
|
414
477
|
|
|
415
478
|
names = frozenset({"edit", "replace_in_file"})
|
|
416
479
|
|
|
417
480
|
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
418
|
-
"""
|
|
481
|
+
"""Show the file; the size of the change is drawn beside the title, before it lands.
|
|
419
482
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
a tweak or a rewrite, which is the question a reader asks the row.
|
|
483
|
+
`+12 -3` beside the file says whether this is a tweak or a rewrite, which is the question a reader asks
|
|
484
|
+
the row. One change a call is why the title counts nothing: a count of one is noise on every row.
|
|
423
485
|
"""
|
|
424
|
-
|
|
425
|
-
edits = arguments.get("edits")
|
|
426
|
-
count = len(edits) if isinstance(edits, Sequence) and not isinstance(edits, str) else 0
|
|
427
|
-
suffix = f" ({count} edit{'' if count == 1 else 's'})" if count else ""
|
|
428
|
-
return ToolRow(f"Edited {path}{suffix}")
|
|
486
|
+
return ToolRow(f"Edit {file_path(arguments, '(unknown file)')}")
|
|
429
487
|
|
|
430
488
|
def open_tool(
|
|
431
489
|
self, name: str, arguments: Mapping[str, object], *, id: int, call_id: str, started_at: float
|
|
@@ -435,17 +493,17 @@ class EditTool(ToolHandler):
|
|
|
435
493
|
The counts ride outside the title: they are drawn in the colours an addition and a
|
|
436
494
|
deletion get, which a title made of one string cannot be.
|
|
437
495
|
"""
|
|
438
|
-
|
|
439
|
-
added, removed = edited_lines(
|
|
496
|
+
changes = edit_changes(arguments)
|
|
497
|
+
added, removed = edited_lines(changes)
|
|
440
498
|
return EditEntry(
|
|
441
499
|
id=id,
|
|
442
500
|
call_id=call_id,
|
|
443
501
|
tool=name,
|
|
444
502
|
title=self.describe(name, arguments).title,
|
|
445
|
-
path=
|
|
503
|
+
path=file_path(arguments),
|
|
446
504
|
added=added,
|
|
447
505
|
removed=removed,
|
|
448
|
-
diff=edit_diff(
|
|
506
|
+
diff=edit_diff(changes),
|
|
449
507
|
started_at=started_at,
|
|
450
508
|
)
|
|
451
509
|
|
|
@@ -457,7 +515,7 @@ class DeleteTool(ToolHandler):
|
|
|
457
515
|
|
|
458
516
|
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
459
517
|
"""Show the file being removed."""
|
|
460
|
-
return ToolRow(f"Deleted {
|
|
518
|
+
return ToolRow(f"Deleted {file_path(arguments, '(unknown file)')}")
|
|
461
519
|
|
|
462
520
|
def open_tool(
|
|
463
521
|
self, name: str, arguments: Mapping[str, object], *, id: int, call_id: str, started_at: float
|
|
@@ -468,19 +526,21 @@ class DeleteTool(ToolHandler):
|
|
|
468
526
|
call_id=call_id,
|
|
469
527
|
tool=name,
|
|
470
528
|
title=self.describe(name, arguments).title,
|
|
471
|
-
path=
|
|
529
|
+
path=file_path(arguments),
|
|
472
530
|
started_at=started_at,
|
|
473
531
|
)
|
|
474
532
|
|
|
475
533
|
|
|
476
534
|
class ShellTool(ToolHandler):
|
|
477
|
-
"""``bash``: the command itself,
|
|
535
|
+
"""``bash``: the command itself, and the directory it runs in when the call names one."""
|
|
478
536
|
|
|
479
537
|
names = frozenset({"bash"})
|
|
480
538
|
|
|
481
539
|
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
482
|
-
"""Show the command, not a JSON copy of it."""
|
|
483
|
-
|
|
540
|
+
"""Show the command, not a JSON copy of it, and where it runs when that is not the workspace."""
|
|
541
|
+
command = argument(arguments, "command", "(unknown command)")
|
|
542
|
+
where = argument(arguments, "work_dir")
|
|
543
|
+
return ToolRow(f"Bash {command}" + (f" {SEPARATOR} {where}" if where else ""))
|
|
484
544
|
|
|
485
545
|
|
|
486
546
|
class SearchTool(ToolHandler):
|
|
@@ -493,7 +553,7 @@ class SearchTool(ToolHandler):
|
|
|
493
553
|
pattern = argument(arguments, "pattern")
|
|
494
554
|
if name == "glob":
|
|
495
555
|
return ToolRow(f"Listed {pattern}" if pattern else "Listed files")
|
|
496
|
-
where = argument(arguments, "path")
|
|
556
|
+
where = argument(arguments, "file_pattern") or argument(arguments, "path")
|
|
497
557
|
return ToolRow(f"Searched {pattern or 'the workspace'}" + (f" in {where}" if where else ""))
|
|
498
558
|
|
|
499
559
|
|
|
@@ -631,6 +691,33 @@ class MessageTool(ToolHandler):
|
|
|
631
691
|
)
|
|
632
692
|
|
|
633
693
|
|
|
694
|
+
class NamespacedTool(ToolHandler):
|
|
695
|
+
"""A tool whose name carries where it came from: ``server__tool``, so the row names the server.
|
|
696
|
+
|
|
697
|
+
The server is part of what the call was: the same ``search`` in two servers is two different tools, with
|
|
698
|
+
different reach and different answers, and a row that drops the server makes them look like one.
|
|
699
|
+
|
|
700
|
+
The name is all a renderer knows, so the row says what the name says — ``github/create_issue`` — and not that
|
|
701
|
+
the tool speaks MCP: a plugin registers a namespaced name of its own with the same shape, and the runtime does
|
|
702
|
+
not mark its remote tools here. A name without both parts is shown as it arrived.
|
|
703
|
+
"""
|
|
704
|
+
|
|
705
|
+
def handles(self, kind: str, name: str = "") -> bool:
|
|
706
|
+
"""Claim a namespaced name that no earlier link claimed."""
|
|
707
|
+
return kind == TOOL and "__" in name.strip("_") and not name.startswith("__")
|
|
708
|
+
|
|
709
|
+
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
710
|
+
"""Show ``server/tool``, so the row names where the capability came from.
|
|
711
|
+
|
|
712
|
+
The runtime names a remote tool ``<server>__<tool>``; a name that prefixed the family with ``mcp`` —
|
|
713
|
+
``mcp__github__create_issue`` — is read the same way, and one without both parts keeps its own name.
|
|
714
|
+
"""
|
|
715
|
+
remote = name[5:] if name.startswith("mcp__") else name
|
|
716
|
+
server, _, tool = remote.partition("__")
|
|
717
|
+
label = f"{server}/{tool}" if server and tool else name
|
|
718
|
+
return ToolRow(f"{label} {summarise(arguments)}".rstrip())
|
|
719
|
+
|
|
720
|
+
|
|
634
721
|
class FallbackTool(ToolHandler):
|
|
635
722
|
"""Anything no other link claims: its name and flat arguments, never JSON."""
|
|
636
723
|
|
|
@@ -639,15 +726,8 @@ class FallbackTool(ToolHandler):
|
|
|
639
726
|
return kind == TOOL
|
|
640
727
|
|
|
641
728
|
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
642
|
-
"""Show ``name key=value`` so an unknown tool still reads as a sentence.
|
|
643
|
-
|
|
644
|
-
An MCP tool is namespaced ``<server>__<tool>``; the server is worth
|
|
645
|
-
keeping because it says where the capability comes from, but it reads
|
|
646
|
-
better after the tool than in front of it.
|
|
647
|
-
"""
|
|
648
|
-
server, _, tool = name.partition("__")
|
|
649
|
-
label = f"{tool} (via {server})" if tool else name
|
|
650
|
-
return ToolRow(f"{label} {summarise(arguments)}".rstrip())
|
|
729
|
+
"""Show ``name key=value`` so an unknown tool still reads as a sentence."""
|
|
730
|
+
return ToolRow(f"{name} {summarise(arguments)}".rstrip())
|
|
651
731
|
|
|
652
732
|
|
|
653
733
|
class ReasoningHandler(RenderHandler):
|
|
@@ -705,6 +785,7 @@ DEFAULT_RENDERERS = Renderers(
|
|
|
705
785
|
SkillTool(),
|
|
706
786
|
TaskTool(),
|
|
707
787
|
MessageTool(),
|
|
788
|
+
NamespacedTool(),
|
|
708
789
|
FallbackTool(),
|
|
709
790
|
ReasoningHandler(),
|
|
710
791
|
AnswerHandler(),
|
|
@@ -164,7 +164,11 @@ class StreamMixin(TurnMixin):
|
|
|
164
164
|
return
|
|
165
165
|
# An unknown status is refused before the row is touched at all.
|
|
166
166
|
outcome = EntryStatus(status)
|
|
167
|
-
|
|
167
|
+
body = self.renderers.body(entry.tool, output)
|
|
168
|
+
entry.text = limit_output(body)
|
|
169
|
+
# The result, unbounded: a count is of what came back, not of the preview the row keeps, and what it says about
|
|
170
|
+
# the body depends on whether the call worked.
|
|
171
|
+
self.renderers.finished(entry, body, outcome)
|
|
168
172
|
entry.finish(outcome, self.clock())
|
|
169
173
|
self._changed(entry)
|
|
170
174
|
# The loop calls the model again once the batch has produced its
|
|
@@ -101,19 +101,24 @@ class CommandHost(Protocol):
|
|
|
101
101
|
async def run_side_question(self, question: str) -> None:
|
|
102
102
|
"""Answer a question that is kept out of the conversation."""
|
|
103
103
|
|
|
104
|
-
async def prompt(self, text: str) -> bool:
|
|
104
|
+
async def prompt(self, text: str, *, shown: str | None = None) -> bool:
|
|
105
105
|
"""Run ``text`` as one turn of this conversation, exactly as if the reader had typed it.
|
|
106
106
|
|
|
107
|
-
The text reaches the model the way a draft does:
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
The text reaches the model the way a draft does: stored in the session, streamed as it is answered, and
|
|
108
|
+
settled by the same row. ``@`` references are resolved.
|
|
109
|
+
|
|
110
|
+
A command whose instructions are longer than what it wants to show passes the two apart: ``text`` is what
|
|
111
|
+
the model reads, ``shown`` is what the transcript draws in its place. A reader who typed ``/code-review``
|
|
112
|
+
sees that, not the brief it stands for — while the model reads the brief with that turn, then and in every
|
|
113
|
+
later turn, because the brief is part of the message the session keeps. Only its display is short.
|
|
110
114
|
|
|
111
115
|
The turn is awaited, so the command that asked for it holds the request slot until it has been answered,
|
|
112
116
|
and the row the shell shows is the command's own.
|
|
113
117
|
|
|
114
118
|
Args:
|
|
115
|
-
text: What to ask.
|
|
116
|
-
|
|
119
|
+
text: What to ask. With no ``shown``, this is also what the reader sees.
|
|
120
|
+
shown: What the transcript shows, when it differs from ``text``. The message the session keeps is
|
|
121
|
+
the same either way: ``text`` is in it, and ``shown`` is what a reader is shown of it.
|
|
117
122
|
|
|
118
123
|
Returns:
|
|
119
124
|
Whether a turn was started and has ended. A request already running, a blank text and a session held
|
|
@@ -247,7 +252,7 @@ class Command:
|
|
|
247
252
|
its sessions, or the model, and is handled by
|
|
248
253
|
:class:`~zettcode.app.agent.TerminalAgent`; ``"plugin"`` comes from
|
|
249
254
|
an installed plugin, registered through
|
|
250
|
-
:class:`~zettcode.
|
|
255
|
+
:class:`~zettcode.engine.plugins.context.PluginContext`.
|
|
251
256
|
handler: Runs the command with a :class:`CommandContext` — the trimmed
|
|
252
257
|
argument and the UI surfaces it may write to while it works — and
|
|
253
258
|
returns a :class:`CommandResult` describing anything left to show.
|
|
@@ -7,7 +7,7 @@ it, moves it, and picks the mood.
|
|
|
7
7
|
|
|
8
8
|
Pets are offered the way palettes are: a :class:`PetSource` names one and builds it only when it is asked for,
|
|
9
9
|
the built-in plugin offers the ones that ship, and any other plugin offers its own through
|
|
10
|
-
``
|
|
10
|
+
``ctx.tui.register_pet``. :class:`PetCatalog` is what ``/pet`` and the start-up read.
|
|
11
11
|
|
|
12
12
|
Every frame of a pet is the same size, and its height is an even number of pixel rows: two pixels fit in
|
|
13
13
|
one terminal cell.
|