zettcode 0.1.8__tar.gz → 0.1.10__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.8 → zettcode-0.1.10}/.gitignore +3 -0
- zettcode-0.1.10/PKG-INFO +60 -0
- zettcode-0.1.10/README.md +23 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/pyproject.toml +3 -48
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/__init__.py +1 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/_compat.py +1 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/settings.py +36 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/presenters.py +14 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/projection.py +58 -10
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/rendering.py +59 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/replay.py +32 -2
- zettcode-0.1.10/src/zettcode/app/agent/conversation/server_tools.py +157 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/text.py +3 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/tips.py +4 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/transcript/stream.py +18 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/dialogue/ask.py +17 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/dialogue/side.py +11 -2
- zettcode-0.1.10/src/zettcode/app/agent/extensions/capabilities.py +68 -0
- zettcode-0.1.10/src/zettcode/app/agent/extensions/files.py +101 -0
- zettcode-0.1.10/src/zettcode/app/agent/extensions/harness.py +89 -0
- zettcode-0.1.10/src/zettcode/app/agent/extensions/harness_switch.py +18 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/mcp.py +14 -6
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/mcp_notice.py +15 -3
- zettcode-0.1.10/src/zettcode/app/agent/extensions/relay.py +93 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/subagents.py +96 -22
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/tool_output.py +8 -7
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/permission/__init__.py +47 -15
- zettcode-0.1.10/src/zettcode/app/agent/permission/sandbox.py +124 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/runtime.py +97 -17
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/storage/metadata.py +7 -2
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/storage/records.py +3 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/storage/store.py +47 -0
- zettcode-0.1.10/src/zettcode/app/agent/tools/__init__.py +7 -0
- zettcode-0.1.10/src/zettcode/app/agent/tools/boundary.py +51 -0
- zettcode-0.1.10/src/zettcode/app/agent/tools/files.py +267 -0
- zettcode-0.1.10/src/zettcode/app/agent/tools/images.py +32 -0
- zettcode-0.1.10/src/zettcode/app/agent/tools/output.py +74 -0
- zettcode-0.1.10/src/zettcode/app/agent/tools/shell.py +171 -0
- zettcode-0.1.10/src/zettcode/app/agent/tools/workspace.py +268 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/app.py +38 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/bars.py +1 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/config_form.py +63 -8
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/keys.py +28 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/notices.py +3 -3
- zettcode-0.1.10/src/zettcode/app/ui/providers.py +614 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/settings.py +5 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/shell.py +1 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/__init__.py +14 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/approval_page.py +9 -9
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/completer.py +3 -4
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/follow_up.py +4 -6
- zettcode-0.1.10/src/zettcode/app/ui/widgets/form_page.py +380 -0
- zettcode-0.1.10/src/zettcode/app/ui/widgets/model_settings_page.py +654 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/pet.py +44 -19
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/statusline_page.py +31 -18
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/transcript.py +47 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/usage_page.py +25 -7
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/cli.py +150 -20
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/config.py +183 -11
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/config_edit.py +27 -1
- zettcode-0.1.10/src/zettcode/oneshot.py +200 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/paths.py +4 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/builtin_agent.py +92 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/builtin_commands.py +46 -5
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/services.py +1 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/state.py +2 -2
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/providers.py +6 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/__init__.py +38 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/_translate.py +89 -5
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/client.py +161 -4
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/events.py +3 -0
- zettcode-0.1.10/src/zettcode/sdk/models.py +148 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/session.py +92 -22
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/types.py +188 -3
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/__init__.py +3 -1
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/capabilities.py +6 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/theme.py +8 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/renderer.py +45 -7
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/theme_file.py +17 -7
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/textarea.py +1 -1
- zettcode-0.1.8/LICENSE +0 -21
- zettcode-0.1.8/PKG-INFO +0 -149
- zettcode-0.1.8/README.md +0 -112
- zettcode-0.1.8/README.zh-CN.md +0 -106
- zettcode-0.1.8/src/zettcode/app/agent/extensions/capabilities.py +0 -50
- zettcode-0.1.8/src/zettcode/app/ui/providers.py +0 -567
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/base.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/commands.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/context.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/exports.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/interaction.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/prompt.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/sessions.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/agent/turns.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/changes.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/effort_gauge.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/entries.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/pulse.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/shimmer.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/stops.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/transcript/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/transcript/fold.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/transcript/log.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/transcript/oneoff.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/conversation/transcript/turn.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/dialogue/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/dialogue/mentions.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/dialogue/relay.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/dialogue/title.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/errors.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/export/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/export/trace/script.js +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/export/trace/style.css +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/export/trace/template.html +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/compaction.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/context.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/messaging.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/messenger.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/extensions/usage.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/permission/approval.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/permission/readonly.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/storage/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/storage/layout.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/storage/ledger.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/agent/storage/locks.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/brand.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/commandline/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/commandline/arguments.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/commandline/completion.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/commandline/history.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/commandline/runner.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/commands.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/cat.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/chibi.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/dog.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/kangaroo.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/model.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/penguin.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/polarbear.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/scarfy.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/seagull.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/slime.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/snowbear.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/sprout.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/tiger.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pets/whale.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/pipeline.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/registry.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/boot.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/clipboard.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/commands.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/demo.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/followups.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/gc_policy.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/labels.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/messaging.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/sessions.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/updating.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/ask_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/bash_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/composer.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/context_charts.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/context_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/effort_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/input_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/model_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/panel.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/pet_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/root.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/self_compact_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/send_kind_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/sessions_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/steering.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/update_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/usage_charts.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/app/ui/widgets/welcome.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/__main__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/client.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/files.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/finder.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/hub.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/log.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/models.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/process.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/protocol.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/roster.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/runner.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/server.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/text.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/broker/web.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/collector.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/doctor.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/filelock.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/builtins.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/container.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/extension.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/loader.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/mixins.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/plugins/plugin.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/py.typed +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/errors.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/sdk/handlers.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/statusline.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/clipboard.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/console.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/app.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/events.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/focus.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/geometry.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/host.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/keymap.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/scheduler.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/screen.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/core/widget.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/gallery.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/glyphs.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/input/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/input/bridge.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/input/decoder.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/input/events.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/input/keys.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/input/reader.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/layout/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/layout/box.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/layout/overlay.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/layout/scroll.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/layout/solver.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/layout/spacing.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/ansi.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/canvas.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/code.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/color.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/rich_text.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/style.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/sweep.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/render/text.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/runner.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/terminal.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/testing/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/testing/harness.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/testing/snapshot.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/ayu-mirage.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/catppuccin-latte.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/catppuccin-mocha.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/dracula.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/github-dimmed.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/github-light.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/gruvbox-dark.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/gruvbox-light.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/kanagawa.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/nord.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/one-dark.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/rose-pine-dawn.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/rose-pine.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/solarized-dark.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/solarized-light.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/themes/tokyo-night.toml +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/__init__.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/collapsible.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/completion.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/console.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/dialog.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/diff.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/list.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/list_page.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/markdown.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/progress.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/rich_text.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/status.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/table.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/tasks.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/text.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/tui/widgets/toast.py +0 -0
- {zettcode-0.1.8 → zettcode-0.1.10}/src/zettcode/update.py +0 -0
|
@@ -21,6 +21,9 @@ docs/.vitepress/dist/
|
|
|
21
21
|
docs/.vitepress/cache/
|
|
22
22
|
docs/.vitepress/.temp/
|
|
23
23
|
|
|
24
|
+
# The TypeScript client is built from its sources; only dist/ is published.
|
|
25
|
+
clients/typescript/dist/
|
|
26
|
+
|
|
24
27
|
# The palette gallery is generated by `make palettes`: every widget painted in
|
|
25
28
|
# every palette, written by docs/scripts/palette_gallery.py. Regenerate it
|
|
26
29
|
# rather than committing a 2 MB copy that goes stale the moment a colour moves.
|
zettcode-0.1.10/PKG-INFO
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zettcode
|
|
3
|
+
Version: 0.1.10
|
|
4
|
+
Summary: A focused terminal coding agent powered by zett-agent
|
|
5
|
+
Project-URL: Repository, https://github.com/Chang-LeHung/zettcode
|
|
6
|
+
Project-URL: Issues, https://github.com/Chang-LeHung/zettcode/issues
|
|
7
|
+
Author: Chang-LeHung
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agent,coding-agent,llm,terminal,tui
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: exceptiongroup<2,>=1.2; python_version < '3.11'
|
|
24
|
+
Requires-Dist: httpx<1,>=0.27
|
|
25
|
+
Requires-Dist: pydantic<3,>=2
|
|
26
|
+
Requires-Dist: pyperclip<2,>=1.9
|
|
27
|
+
Requires-Dist: setproctitle<2,>=1.3.7; os_name == 'posix'
|
|
28
|
+
Requires-Dist: starlette<2,>=0.48
|
|
29
|
+
Requires-Dist: tiktoken<1,>=0.12
|
|
30
|
+
Requires-Dist: tomli<3,>=2; python_version < '3.11'
|
|
31
|
+
Requires-Dist: truststore<1,>=0.10
|
|
32
|
+
Requires-Dist: uvicorn<1,>=0.35
|
|
33
|
+
Requires-Dist: wcwidth<1,>=0.2
|
|
34
|
+
Requires-Dist: websockets<17,>=15
|
|
35
|
+
Requires-Dist: zett-agent<0.3,>=0.2.0
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# ZettCode
|
|
39
|
+
|
|
40
|
+
A focused terminal coding agent powered by [zett-agent](https://pypi.org/project/zett-agent/): it reads and edits your
|
|
41
|
+
code, runs commands with your permission, and is driven by a model you choose. The repository also ships a Python SDK
|
|
42
|
+
and an HTTP server.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install zettcode
|
|
46
|
+
zettcode
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- [The guide](https://chang-lehung.github.io/zettcode/) — commands, keys, plugins, configuration
|
|
50
|
+
- [The SDK](https://chang-lehung.github.io/zettcode/sdk/) — drive the agent from Python
|
|
51
|
+
- [The server](https://chang-lehung.github.io/zettcode/server/) — drive it over HTTP
|
|
52
|
+
|
|
53
|
+
The full README, with the screenshots, is at the root of the repository.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
import zettcode.sdk as zc
|
|
57
|
+
|
|
58
|
+
result = await zc.prompt("summarize the README", permission="deny")
|
|
59
|
+
print(result.text)
|
|
60
|
+
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# ZettCode
|
|
2
|
+
|
|
3
|
+
A focused terminal coding agent powered by [zett-agent](https://pypi.org/project/zett-agent/): it reads and edits your
|
|
4
|
+
code, runs commands with your permission, and is driven by a model you choose. The repository also ships a Python SDK
|
|
5
|
+
and an HTTP server.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install zettcode
|
|
9
|
+
zettcode
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- [The guide](https://chang-lehung.github.io/zettcode/) — commands, keys, plugins, configuration
|
|
13
|
+
- [The SDK](https://chang-lehung.github.io/zettcode/sdk/) — drive the agent from Python
|
|
14
|
+
- [The server](https://chang-lehung.github.io/zettcode/server/) — drive it over HTTP
|
|
15
|
+
|
|
16
|
+
The full README, with the screenshots, is at the root of the repository.
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import zettcode.sdk as zc
|
|
20
|
+
|
|
21
|
+
result = await zc.prompt("summarize the README", permission="deny")
|
|
22
|
+
print(result.text)
|
|
23
|
+
```
|
|
@@ -5,7 +5,6 @@ readme = "README.md"
|
|
|
5
5
|
requires-python = ">=3.10"
|
|
6
6
|
dynamic = ["version"]
|
|
7
7
|
license = "MIT"
|
|
8
|
-
license-files = ["LICENSE"]
|
|
9
8
|
authors = [{name = "Chang-LeHung"}]
|
|
10
9
|
keywords = ["agent", "llm", "tui", "terminal", "coding-agent"]
|
|
11
10
|
classifiers = [
|
|
@@ -19,6 +18,7 @@ classifiers = [
|
|
|
19
18
|
"Programming Language :: Python :: 3.12",
|
|
20
19
|
"Programming Language :: Python :: 3.13",
|
|
21
20
|
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Programming Language :: Python :: 3.15",
|
|
22
22
|
"Typing :: Typed",
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
@@ -53,7 +53,7 @@ dependencies = [
|
|
|
53
53
|
# than it did. An exact pin would make zettcode fight every other tool that
|
|
54
54
|
# shares the dependency — including an upgrade of zett-agent itself, which pip
|
|
55
55
|
# would downgrade to satisfy it.
|
|
56
|
-
"zett-agent>=0.
|
|
56
|
+
"zett-agent>=0.2.0,<0.3",
|
|
57
57
|
]
|
|
58
58
|
|
|
59
59
|
[project.scripts]
|
|
@@ -75,53 +75,8 @@ path = "src/zettcode/__init__.py"
|
|
|
75
75
|
packages = ["src/zettcode"]
|
|
76
76
|
|
|
77
77
|
[tool.hatch.build.targets.sdist]
|
|
78
|
-
include = ["/src", "/README.md", "/
|
|
79
|
-
|
|
80
|
-
[dependency-groups]
|
|
81
|
-
dev = [
|
|
82
|
-
# Unlike the conditional runtime deps, these are installed on every
|
|
83
|
-
# interpreter so mypy can check the 3.10 branch of ``_compat`` for real.
|
|
84
|
-
"exceptiongroup>=1.2,<2",
|
|
85
|
-
"mypy>=1.14,<2",
|
|
86
|
-
"pytest>=8,<9",
|
|
87
|
-
"pytest-asyncio>=0.26,<2",
|
|
88
|
-
"pytest-cov>=7.1.0",
|
|
89
|
-
"ruff>=0.11,<1",
|
|
90
|
-
"tomli>=2,<3",
|
|
91
|
-
"viztracer>=1.1.1",
|
|
92
|
-
]
|
|
78
|
+
include = ["/src", "/README.md", "/pyproject.toml"]
|
|
93
79
|
|
|
94
80
|
[tool.pytest.ini_options]
|
|
95
81
|
testpaths = ["tests"]
|
|
96
82
|
asyncio_mode = "auto"
|
|
97
|
-
|
|
98
|
-
[tool.mypy]
|
|
99
|
-
python_version = "3.10"
|
|
100
|
-
files = ["src", "perf"]
|
|
101
|
-
# Check the pydantic storage models as models, not as plain classes.
|
|
102
|
-
plugins = ["pydantic.mypy"]
|
|
103
|
-
warn_unused_ignores = true
|
|
104
|
-
warn_redundant_casts = true
|
|
105
|
-
no_implicit_optional = true
|
|
106
|
-
|
|
107
|
-
[[tool.mypy.overrides]]
|
|
108
|
-
# Ships no stubs; the runner only calls the two documented functions.
|
|
109
|
-
module = ["pyperclip"]
|
|
110
|
-
ignore_missing_imports = true
|
|
111
|
-
|
|
112
|
-
[tool.ruff]
|
|
113
|
-
line-length = 120
|
|
114
|
-
target-version = "py310"
|
|
115
|
-
src = ["src", "tests", "perf"]
|
|
116
|
-
|
|
117
|
-
[tool.ruff.lint]
|
|
118
|
-
select = ["E", "F", "I", "UP", "B"]
|
|
119
|
-
ignore = ["E501"]
|
|
120
|
-
|
|
121
|
-
[tool.ruff.lint.isort]
|
|
122
|
-
known-first-party = ["zettcode"]
|
|
123
|
-
|
|
124
|
-
[tool.ruff.format]
|
|
125
|
-
quote-style = "double"
|
|
126
|
-
indent-style = "space"
|
|
127
|
-
line-ending = "lf"
|
|
@@ -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.1.
|
|
10
|
+
__version__ = "0.1.10"
|
|
11
11
|
|
|
12
12
|
#: Where each public name lives; read by :func:`__getattr__` on first use.
|
|
13
13
|
_EXPORTS = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Small shims for the language versions this project supports.
|
|
2
2
|
|
|
3
|
-
ZettCode runs on Python 3.10 through 3.
|
|
3
|
+
ZettCode runs on Python 3.10 through 3.15, so the few things the standard
|
|
4
4
|
library grew in between live here instead of behind version checks at each use.
|
|
5
5
|
"""
|
|
6
6
|
|
|
@@ -14,6 +14,7 @@ from zettcode.config_edit import (
|
|
|
14
14
|
remove_model,
|
|
15
15
|
replace_model,
|
|
16
16
|
set_pet,
|
|
17
|
+
set_pet_position,
|
|
17
18
|
set_self_compaction,
|
|
18
19
|
set_statusline,
|
|
19
20
|
set_theme,
|
|
@@ -208,6 +209,29 @@ class SettingsMixin(AgentBase):
|
|
|
208
209
|
self.runtime.config = replace(self.runtime.config, pet=name)
|
|
209
210
|
return self.config_file
|
|
210
211
|
|
|
212
|
+
def remember_pet_position(self, across: float, down: float) -> Path:
|
|
213
|
+
"""Write ``[ui] pet_position`` into the config file, so the next start puts the pet back where it was left.
|
|
214
|
+
|
|
215
|
+
The file is global to the reader (``~/.zettcode/config.toml``), not to a project: a pet is the reader's
|
|
216
|
+
companion, and it does not move house when they open another folder. Only that line changes, and a
|
|
217
|
+
config file that does not exist is not created for it.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
The file written.
|
|
221
|
+
|
|
222
|
+
Raises:
|
|
223
|
+
ConfigEditError: When there is no config file, it cannot be read or edited safely, or it cannot be
|
|
224
|
+
written. Nothing is written then.
|
|
225
|
+
"""
|
|
226
|
+
text = set_pet_position(self._read_config_file(), across, down, where=self.config_file)
|
|
227
|
+
try:
|
|
228
|
+
write_text_atomically(self.config_file, text)
|
|
229
|
+
except OSError as error:
|
|
230
|
+
raise ConfigEditError(f"{self.config_file} could not be written: {error}") from error
|
|
231
|
+
position = (round(min(1.0, max(0.0, across)), 3), round(min(1.0, max(0.0, down)), 3))
|
|
232
|
+
self.runtime.config = replace(self.runtime.config, pet_position=position)
|
|
233
|
+
return self.config_file
|
|
234
|
+
|
|
211
235
|
@property
|
|
212
236
|
def statusline_hidden(self) -> frozenset[str]:
|
|
213
237
|
"""Return the names of the header and status-line parts the reader switched off."""
|
|
@@ -322,6 +346,15 @@ class SettingsMixin(AgentBase):
|
|
|
322
346
|
"""Return the permission levels, strictest first."""
|
|
323
347
|
return tuple(level.value for level in PERMISSION_LEVELS)
|
|
324
348
|
|
|
325
|
-
def use_permission(self, name: str | Permission) -> Permission:
|
|
326
|
-
"""Set how much of the shell may run without asking,
|
|
327
|
-
return self.runtime.use_permission(name)
|
|
349
|
+
def use_permission(self, name: str | Permission, *, remember: bool = True) -> Permission:
|
|
350
|
+
"""Set how much of the shell may run without asking, and by default remember it for this session."""
|
|
351
|
+
return self.runtime.use_permission(name, remember=remember)
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def harness(self) -> bool:
|
|
355
|
+
"""Return whether the just-in-time harness is on for the next request."""
|
|
356
|
+
return self.runtime.harness.on
|
|
357
|
+
|
|
358
|
+
def use_harness(self, on: bool) -> bool:
|
|
359
|
+
"""Turn the just-in-time harness on or off for later requests, for the rest of this run."""
|
|
360
|
+
return self.runtime.use_harness(on)
|
|
@@ -106,6 +106,8 @@ TOOL_ROLES = {
|
|
|
106
106
|
"view_image": "image",
|
|
107
107
|
"glob": "search",
|
|
108
108
|
"grep": "search",
|
|
109
|
+
"web_search": "web-search",
|
|
110
|
+
"ask_user": "ask-user",
|
|
109
111
|
"write_file": "write",
|
|
110
112
|
"edit": "write",
|
|
111
113
|
"replace_in_file": "write", # what a session stored before the rename called it
|
|
@@ -120,8 +122,12 @@ TOOL_ROLES = {
|
|
|
120
122
|
|
|
121
123
|
|
|
122
124
|
def tool_color(tool: str, theme: Theme) -> str:
|
|
123
|
-
"""Return the colour one tool paints its rows with.
|
|
124
|
-
|
|
125
|
+
"""Return the colour one tool paints its rows with.
|
|
126
|
+
|
|
127
|
+
A role spelled with a hyphen (``web-search``) names the theme field it stands for (``web_search``).
|
|
128
|
+
"""
|
|
129
|
+
role = TOOL_ROLES.get(tool, "").replace("-", "_")
|
|
130
|
+
return getattr(theme.tools, role, theme.accent)
|
|
125
131
|
|
|
126
132
|
|
|
127
133
|
#: The entry fields that are not part of how a row looks: its identity, and the
|
|
@@ -575,7 +581,12 @@ class ToolPresenter(Presenter[T]):
|
|
|
575
581
|
}.get(entry.status, STATUS)
|
|
576
582
|
)
|
|
577
583
|
hue = tool_color(entry.tool, theme)
|
|
578
|
-
|
|
584
|
+
# A failure is the error colour, and a row that did not happen at all — a skipped tool, a cancelled
|
|
585
|
+
# question — is a caution rather than a result, so it is amber and not the tool's own hue.
|
|
586
|
+
colour = {
|
|
587
|
+
EntryStatus.FAILED: theme.error,
|
|
588
|
+
EntryStatus.SKIPPED: theme.warning,
|
|
589
|
+
}.get(entry.status, hue)
|
|
579
590
|
marker = Style(foreground=colour)
|
|
580
591
|
label = Style(foreground=colour)
|
|
581
592
|
muted = Style(foreground=theme.muted)
|
|
@@ -10,11 +10,12 @@ from zett_agent.events import AgentEvent
|
|
|
10
10
|
from zett_agent.extensions.shell_approval import SHELL_APPROVAL_EVENT_NAME
|
|
11
11
|
from zett_agent.messages import ToolMessage
|
|
12
12
|
|
|
13
|
-
from ..dialogue.ask import AskUserQuestion, question_from
|
|
13
|
+
from ..dialogue.ask import AskUserQuestion, declined_answer, question_from
|
|
14
14
|
from ..dialogue.relay import Relayed
|
|
15
15
|
from ..extensions.mcp_notice import MCP_NOTICE_EVENT_NAME, notice_from
|
|
16
16
|
from ..extensions.usage import USAGE_EVENT_NAME
|
|
17
17
|
from .entries import EntryStatus
|
|
18
|
+
from .server_tools import ServerToolRun, describe_output
|
|
18
19
|
from .stops import stop_text
|
|
19
20
|
from .transcript import Transcript
|
|
20
21
|
|
|
@@ -82,6 +83,10 @@ class TranscriptProjector(AgentEventDispatcher):
|
|
|
82
83
|
self.on_usage = on_usage
|
|
83
84
|
self.on_steering_started = on_steering_started
|
|
84
85
|
self.on_steering_interrupted = on_steering_interrupted
|
|
86
|
+
#: What a hosted tool was called with while it streams: a start may carry the whole input, or only its
|
|
87
|
+
#: opening and then fragments. Kept per request and dropped when the call reports back.
|
|
88
|
+
self._server_inputs: dict[str, dict[str, object]] = {}
|
|
89
|
+
self._server_fragments: dict[str, str] = {}
|
|
85
90
|
|
|
86
91
|
def begin_turn(self, prompt: str, *, side: bool = False, relayed: Relayed | None = None) -> None:
|
|
87
92
|
"""Echo the prompt into the transcript before the run starts.
|
|
@@ -171,9 +176,19 @@ class TranscriptProjector(AgentEventDispatcher):
|
|
|
171
176
|
self.transcript.start_tool(call.id, call.name, call.arguments)
|
|
172
177
|
|
|
173
178
|
async def on_tool_completed_event(self, event: AgentEvent) -> None:
|
|
174
|
-
"""Attach the returned message to the row of the call it answers.
|
|
179
|
+
"""Attach the returned message to the row of the call it answers.
|
|
180
|
+
|
|
181
|
+
A question the reader cancelled comes back as a result, not a failure: the model reads that it was declined
|
|
182
|
+
and carries on. The row is marked skipped so it does not look like a question that was answered.
|
|
183
|
+
"""
|
|
175
184
|
if isinstance(event.message, ToolMessage):
|
|
176
|
-
|
|
185
|
+
output = tool_output(event.message)
|
|
186
|
+
declined = event.message.name == "ask_user" and declined_answer(output)
|
|
187
|
+
self.transcript.complete_tool(
|
|
188
|
+
event.message.tool_call_id,
|
|
189
|
+
output,
|
|
190
|
+
status=EntryStatus.SKIPPED if declined else EntryStatus.COMPLETED,
|
|
191
|
+
)
|
|
177
192
|
|
|
178
193
|
async def on_tool_failed_event(self, event: AgentEvent) -> None:
|
|
179
194
|
"""Fail every call in the batch and record the error text."""
|
|
@@ -188,23 +203,56 @@ class TranscriptProjector(AgentEventDispatcher):
|
|
|
188
203
|
self.transcript.complete_tool(call.id, output, status=EntryStatus.SKIPPED)
|
|
189
204
|
|
|
190
205
|
async def on_server_tool_started_event(self, event: AgentEvent) -> None:
|
|
191
|
-
"""Open a row for a tool the server runs on the agent's behalf."""
|
|
192
|
-
|
|
193
|
-
|
|
206
|
+
"""Open a row for a tool the server runs on the agent's behalf, with what it was asked."""
|
|
207
|
+
call = event.server_tool_call
|
|
208
|
+
if call is None:
|
|
209
|
+
return
|
|
210
|
+
arguments = dict(call.input or {})
|
|
211
|
+
self._server_inputs[call.id] = arguments
|
|
212
|
+
self._server_fragments.pop(call.id, None)
|
|
213
|
+
self.transcript.start_tool(call.id, call.name, arguments)
|
|
214
|
+
|
|
215
|
+
async def on_server_tool_input_delta_event(self, event: AgentEvent) -> None:
|
|
216
|
+
"""Keep the fragments of a hosted call's input; the query a search ran is in there."""
|
|
217
|
+
delta = event.server_tool_input_delta
|
|
218
|
+
if delta is not None:
|
|
219
|
+
self._server_fragments[delta.call_id] = self._server_fragments.get(delta.call_id, "") + delta.delta
|
|
220
|
+
|
|
221
|
+
def _server_arguments(self, call_id: str) -> dict[str, object]:
|
|
222
|
+
"""Return what a hosted call turned out to be called with: its opening input and its fragments."""
|
|
223
|
+
arguments = dict(self._server_inputs.pop(call_id, {}))
|
|
224
|
+
fragments = self._server_fragments.pop(call_id, "")
|
|
225
|
+
if fragments:
|
|
226
|
+
try:
|
|
227
|
+
parsed = json.loads(fragments)
|
|
228
|
+
except ValueError:
|
|
229
|
+
parsed = None
|
|
230
|
+
if isinstance(parsed, dict):
|
|
231
|
+
arguments.update(parsed)
|
|
232
|
+
return arguments
|
|
194
233
|
|
|
195
234
|
async def on_server_tool_completed_event(self, event: AgentEvent) -> None:
|
|
196
|
-
"""Attach a
|
|
235
|
+
"""Attach a hosted tool's result to its row: the pages it found, never the provider's payload."""
|
|
197
236
|
result = event.server_tool_result
|
|
198
237
|
if result is not None:
|
|
199
|
-
self.
|
|
238
|
+
arguments = self._server_arguments(result.call_id)
|
|
239
|
+
if arguments:
|
|
240
|
+
# The row opened before its input had streamed in; now it knows what was searched for.
|
|
241
|
+
self.transcript.restate_tool(result.call_id, arguments)
|
|
242
|
+
run = ServerToolRun(result.call_id, result.name, arguments, output=result.output, error=result.error_code)
|
|
243
|
+
self.transcript.complete_tool(result.call_id, describe_output(run) or serialize(result.output))
|
|
200
244
|
|
|
201
245
|
async def on_server_tool_failed_event(self, event: AgentEvent) -> None:
|
|
202
|
-
"""Attach a
|
|
246
|
+
"""Attach a hosted tool's failure, preferring the error code over the payload."""
|
|
203
247
|
result = event.server_tool_result
|
|
204
248
|
if result is not None:
|
|
249
|
+
arguments = self._server_arguments(result.call_id)
|
|
250
|
+
if arguments:
|
|
251
|
+
self.transcript.restate_tool(result.call_id, arguments)
|
|
252
|
+
run = ServerToolRun(result.call_id, result.name, arguments, output=result.output, error=result.error_code)
|
|
205
253
|
self.transcript.complete_tool(
|
|
206
254
|
result.call_id,
|
|
207
|
-
|
|
255
|
+
describe_output(run) or serialize(result.output),
|
|
208
256
|
status=EntryStatus.FAILED,
|
|
209
257
|
)
|
|
210
258
|
|
|
@@ -34,7 +34,7 @@ from zettcode.tui import ELLIPSIS, SEPARATOR
|
|
|
34
34
|
from zettcode.tui.render import GENERIC, language_for
|
|
35
35
|
|
|
36
36
|
from .entries import DeleteEntry, EditEntry, MessageEntry, ReadEntry, ToolEntry, WriteEntry
|
|
37
|
-
from .text import limit_output
|
|
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.
|
|
40
40
|
TOOL = "tool"
|
|
@@ -386,8 +386,8 @@ class WriteTool(ToolHandler):
|
|
|
386
386
|
names = frozenset({"write_file"})
|
|
387
387
|
|
|
388
388
|
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
389
|
-
"""Show the file being written."""
|
|
390
|
-
return ToolRow(f"
|
|
389
|
+
"""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 {argument(arguments, 'path', '(unknown file)')}")
|
|
391
391
|
|
|
392
392
|
def open_tool(
|
|
393
393
|
self, name: str, arguments: Mapping[str, object], *, id: int, call_id: str, started_at: float
|
|
@@ -497,6 +497,60 @@ class SearchTool(ToolHandler):
|
|
|
497
497
|
return ToolRow(f"Searched {pattern or 'the workspace'}" + (f" in {where}" if where else ""))
|
|
498
498
|
|
|
499
499
|
|
|
500
|
+
class WebSearchTool(ToolHandler):
|
|
501
|
+
"""``web_search``: a search the endpoint ran, so the row says what was searched for."""
|
|
502
|
+
|
|
503
|
+
names = frozenset({"web_search"})
|
|
504
|
+
|
|
505
|
+
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
506
|
+
"""Name the query; the call is the endpoint's, so nothing else of it is ours to show."""
|
|
507
|
+
query = argument(arguments, "query")
|
|
508
|
+
return ToolRow(f'Searched the web for "{query}"' if query else "Searched the web")
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class AskUserTool(ToolHandler):
|
|
512
|
+
"""``ask_user``: the one row that waited for a person, so it is named as a thing, not as a verb.
|
|
513
|
+
|
|
514
|
+
The body is what the person did, not the protocol: the tool answers with the payload the shell sent, and that
|
|
515
|
+
JSON is neither readable nor useful in a transcript.
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
names = frozenset({"ask_user"})
|
|
519
|
+
|
|
520
|
+
def describe(self, name: str, arguments: Mapping[str, object]) -> ToolRow:
|
|
521
|
+
"""Show the question that was put, under the name the shell gives that tool."""
|
|
522
|
+
question = argument(arguments, "question")
|
|
523
|
+
return ToolRow(f"Ask-User: {question}" if question else "Ask-User")
|
|
524
|
+
|
|
525
|
+
def body(self, name: str, output: str) -> str:
|
|
526
|
+
"""Say what the person did: declined, answered, or answered with a picture.
|
|
527
|
+
|
|
528
|
+
Anything that is not the answer payload is left exactly as it came, so a failure or another tool's result
|
|
529
|
+
is never rewritten by this row.
|
|
530
|
+
"""
|
|
531
|
+
payload = _ask_payload(output)
|
|
532
|
+
if payload is None:
|
|
533
|
+
return output
|
|
534
|
+
if payload.get("declined") is True:
|
|
535
|
+
return DECLINED_NOTE
|
|
536
|
+
answer = payload.get("answer")
|
|
537
|
+
if isinstance(answer, str) and answer.strip():
|
|
538
|
+
return f"You answered: {answer.strip()}"
|
|
539
|
+
if isinstance(payload.get("parts"), list) and payload["parts"]:
|
|
540
|
+
return "You answered with a picture."
|
|
541
|
+
return output
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def _ask_payload(output: str) -> Mapping[str, object] | None:
|
|
545
|
+
"""Return the payload of an ``ask_user`` result, or ``None`` when the text is not one."""
|
|
546
|
+
try:
|
|
547
|
+
result = json.loads(output)
|
|
548
|
+
except ValueError:
|
|
549
|
+
return None
|
|
550
|
+
payload = result.get("payload") if isinstance(result, Mapping) else None
|
|
551
|
+
return payload if isinstance(payload, Mapping) else None
|
|
552
|
+
|
|
553
|
+
|
|
500
554
|
class TodoTool(ToolHandler):
|
|
501
555
|
"""``todo_write``: a plan, so the number of steps is the story."""
|
|
502
556
|
|
|
@@ -645,6 +699,8 @@ DEFAULT_RENDERERS = Renderers(
|
|
|
645
699
|
DeleteTool(),
|
|
646
700
|
ShellTool(),
|
|
647
701
|
SearchTool(),
|
|
702
|
+
WebSearchTool(),
|
|
703
|
+
AskUserTool(),
|
|
648
704
|
TodoTool(),
|
|
649
705
|
SkillTool(),
|
|
650
706
|
TaskTool(),
|
|
@@ -19,6 +19,7 @@ from zett_agent.messages import AnyMessage, AssistantMessage, SystemMessage, Too
|
|
|
19
19
|
|
|
20
20
|
from zettcode.config import DEFAULT_TRANSCRIPT_MAX_ENTRIES
|
|
21
21
|
|
|
22
|
+
from ..dialogue.ask import declined_answer
|
|
22
23
|
from ..dialogue.relay import relay_of
|
|
23
24
|
from ..dialogue.side import is_side_line
|
|
24
25
|
from ..extensions.usage import UsageSnapshot
|
|
@@ -26,6 +27,7 @@ from ..storage import MessageLine, Session
|
|
|
26
27
|
from .entries import EntryStatus
|
|
27
28
|
from .presenters import DEFAULT_PRESENTERS, Presenters
|
|
28
29
|
from .rendering import DEFAULT_RENDERERS, Renderers
|
|
30
|
+
from .server_tools import ServerToolRun, answer_after_tools, describe_output, runs_in_blocks
|
|
29
31
|
from .stops import stop_text
|
|
30
32
|
from .transcript import Transcript
|
|
31
33
|
|
|
@@ -75,7 +77,13 @@ def entries_added_by(message: AnyMessage) -> int:
|
|
|
75
77
|
return 1
|
|
76
78
|
case AssistantMessage():
|
|
77
79
|
stopped = message.stop is not None and not message.stop.ok
|
|
78
|
-
return
|
|
80
|
+
return (
|
|
81
|
+
bool(message.reasoning)
|
|
82
|
+
+ bool(message.content)
|
|
83
|
+
+ len(message.tool_calls)
|
|
84
|
+
+ len(runs_in_blocks(message.replay_blocks))
|
|
85
|
+
+ stopped
|
|
86
|
+
)
|
|
79
87
|
case _:
|
|
80
88
|
return 0
|
|
81
89
|
|
|
@@ -152,7 +160,26 @@ def replay(
|
|
|
152
160
|
case AssistantMessage():
|
|
153
161
|
if message.reasoning:
|
|
154
162
|
transcript.restore_thinking(message.reasoning, line.timing.reasoning_duration_ns)
|
|
155
|
-
|
|
163
|
+
runs = runs_in_blocks(message.replay_blocks)
|
|
164
|
+
|
|
165
|
+
def hosted(rows: Sequence[ServerToolRun] = runs) -> None:
|
|
166
|
+
"""Add the rows of the tools the endpoint ran, each closed by its own result."""
|
|
167
|
+
for run in rows:
|
|
168
|
+
transcript.start_tool(run.call_id, run.name, run.arguments)
|
|
169
|
+
transcript.complete_tool(
|
|
170
|
+
run.call_id,
|
|
171
|
+
describe_output(run),
|
|
172
|
+
status=EntryStatus.FAILED if run.error else EntryStatus.COMPLETED,
|
|
173
|
+
wait=False,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# A hosted tool has no message of its own: its call and result are both in the answer's blocks, kept
|
|
177
|
+
# because the provider needs them back. Where the answer's text sits in those blocks is where the row
|
|
178
|
+
# goes relative to it, so a reopened session reads in the order the run did.
|
|
179
|
+
if not answer_after_tools(message.replay_blocks) and message.content:
|
|
180
|
+
transcript.append_answer(message.content)
|
|
181
|
+
hosted()
|
|
182
|
+
if answer_after_tools(message.replay_blocks) and message.content:
|
|
156
183
|
transcript.append_answer(message.content)
|
|
157
184
|
for call in message.tool_calls:
|
|
158
185
|
transcript.start_tool(call.id, call.name, call.arguments)
|
|
@@ -160,6 +187,9 @@ def replay(
|
|
|
160
187
|
transcript.error(stop_text(message.stop))
|
|
161
188
|
case ToolMessage():
|
|
162
189
|
status = EntryStatus.COMPLETED if message.success else EntryStatus.FAILED
|
|
190
|
+
if message.name == "ask_user" and declined_answer(message.text):
|
|
191
|
+
# A cancelled question is an answer of its own, not a failure.
|
|
192
|
+
status = EntryStatus.SKIPPED
|
|
163
193
|
transcript.complete_tool(message.tool_call_id, message.text, status=status, wait=False)
|
|
164
194
|
case _:
|
|
165
195
|
continue
|