tau-coding-agent 0.6.1__tar.gz → 0.6.2__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.
- {tau_coding_agent-0.6.1/tau_coding_agent.egg-info → tau_coding_agent-0.6.2}/PKG-INFO +1 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/pyproject.toml +1 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/agent/service.py +1 -8
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/ask_user/component.py +177 -62
- tau_coding_agent-0.6.2/tau/builtins/extensions/ask_user/schema.py +142 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/ask_user/tool.py +181 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/sandbox/__init__.py +109 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/sandbox/manager.py +138 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/sandbox/manifest.json +69 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/sandbox/sandbox_tool.py +232 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/__init__.py +28 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/context-builder.md +47 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/delegate.md +9 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/oracle.md +68 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/planner.md +49 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/researcher.md +48 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/reviewer.md +72 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/scout.md +41 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents/worker.md +36 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/agents.py +118 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/manifest.json +18 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/subagent_schema.py +75 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/subagent/subagent_tool.py +813 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/todo/__init__.py +165 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/todo/manifest.json +18 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/todo/todo_schema.py +69 -0
- tau_coding_agent-0.6.2/tau/builtins/extensions/todo/todo_tool.py +425 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/tools/fetch.py +1 -8
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/skills/code-review/SKILL.md +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/skills/debug/SKILL.md +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/skills/git-commit/SKILL.md +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/utils.py +1 -10
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/console/cli.py +17 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/extensions.py +21 -2
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/session.py +6 -26
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/config_selector.py +1 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/extension_selector.py +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/message_list.py +20 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/model_selector.py +4 -11
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/oauth_selector.py +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/session_selector.py +6 -9
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/settings_selector.py +3 -3
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/theme_selector.py +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/thinking_selector.py +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/voice_selector.py +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/session/manager.py +8 -6
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/themes/loader.py +1 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/simple_picker.py +2 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/markdown.py +2 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/theme.py +7 -0
- tau_coding_agent-0.6.2/tau/utils/format.py +40 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2/tau_coding_agent.egg-info}/PKG-INFO +1 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau_coding_agent.egg-info/SOURCES.txt +23 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_ask_user_extension.py +1 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_gemini_generate.py +2 -1
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_session_manager.py +21 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_session_trust_persist.py +50 -0
- tau_coding_agent-0.6.2/tests/test_subagent_context.py +288 -0
- tau_coding_agent-0.6.1/tau/builtins/extensions/ask_user/schema.py +0 -41
- tau_coding_agent-0.6.1/tau/builtins/extensions/ask_user/tool.py +0 -124
- tau_coding_agent-0.6.1/tests/test_subagents_extension.py +0 -271
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/LICENSE +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/MANIFEST.in +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/README.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/architecture.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/auth.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/cli-reference.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/creating-tools.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/development.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/docs.json +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/engine.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/extension-settings.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/extensions.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/http-proxy.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/index.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/inference-providers.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/inference.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/installation.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/keybindings.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/messages.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/project-context.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/project-structure.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/prompts.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/python-api.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/quickstart.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/sessions.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/settings.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/skills.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/themes.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/tools.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/tui.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/docs/usage.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/setup.cfg +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/agent/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/agent/prompt/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/agent/prompt/builder.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/agent/prompt/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/agent/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/auth/manager.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/auth/storage.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/auth/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/commands/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/commands/clear.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/commands/compact.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/commands/reload.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/commands/session.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/ask_user/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/ask_user/manifest.json +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/btw/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/footer/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/footer/git.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/footer/model.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/footer/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/header/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/watch/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/engines/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/engines/base.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/engines/ddgs_engine.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/engines/exa_engine.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/engines/jina_engine.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/engines/tavily_engine.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/manifest.json +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/tools/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/web/tools/search.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/models/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/models/audio.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/models/image.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/models/text.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/models/video.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/prompts/commit.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/prompts/docs.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/prompts/explain.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/prompts/fix.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/prompts/refactor.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/prompts/review.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/prompts/test.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/providers/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/providers/audio.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/providers/image.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/providers/text.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/providers/video.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/skills/skill-creator/LICENSE.txt +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/skills/skill-creator/SKILL.md +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/ayu-dark.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/catppuccin.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/dark.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/dracula.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/everforest.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/gruvbox.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/horizon.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/kanagawa.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/light.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/material-ocean.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/monokai.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/night-owl.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/nord.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/one-dark.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/rose-pine.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/solarized-dark.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/themes/tokyo-night.yaml +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/edit.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/glob.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/grep.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/ls.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/read.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/terminal.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/tools/write.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/commands/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/commands/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/commands/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/console/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/console/commands/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/console/commands/auth.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/console/commands/packages.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/console/commands/update.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/core/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/core/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/engine/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/engine/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/engine/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/extensions/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/extensions/api.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/extensions/context.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/extensions/loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/extensions/runtime.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/extensions/settings.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/engine.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/inference.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/runtime.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/session.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/tui.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/hooks/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/base.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/builtins.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/elevenlabs_audio.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/gemini_audio.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/openai_audio.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/sarvam_audio.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/audio/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/availability.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/base.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/builtins.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/gemini_image.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/openai_image.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/openrouter.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/image/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/anthropic_claude_code.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/anthropic_messages.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/anthropic_vertex.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/base.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/builtins.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/dialect.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/gemini_generate.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/github_copilot_chat.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/google_antigravity.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/google_vertex.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/mistral_chat.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/ollama_chat.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/openai_codex_responses.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/openai_completions.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/openai_responses.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/openai_vertex.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/text/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/base.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/builtins.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/fal_video.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/openrouter_video.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/api/video/zai_video.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/model/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/model/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/model/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/anthropic_claude_code.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/github_copilot.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/google_antigravity.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/openai_codex.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/pkce.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/oauth/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/provider/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/inference/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/message/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/message/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/message/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/agent_hooks.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/app.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/appearance.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/auth.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/context.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/misc.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/commands/model.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/command_palette.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/file_picker.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/layout.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/overlays.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/selector_controller.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/tree_selector.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/components/trust_screen.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/input_handler.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/interactive/ui_context.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/print/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/rpc/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/rpc/mode.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/modes/rpc/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/packages/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/packages/manager.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/packages/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/packages/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/prompts/expand.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/prompts/loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/prompts/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/prompts/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/resources/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/resources/loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/resources/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/runtime/dependencies.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/runtime/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/runtime/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/session/branch_summarization.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/session/compaction.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/session/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/session/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/settings/manager.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/settings/paths.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/settings/storage.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/settings/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/settings/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/skills/loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/skills/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/skills/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/telemetry/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/telemetry/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/telemetry/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/themes/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/themes/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tool/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tool/registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tool/render.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tool/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/trust/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/trust/manager.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/trust/types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/trust/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/ansi_bridge.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/autocomplete.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/backend.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/buffer.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/component.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/box.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/editor.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/image.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/select_list.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/spinner.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/text_input.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/components/widget_bridge.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/frame.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/geometry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/input.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/keybinding_hints.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/layout.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/palette.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/service.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/style.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/terminal.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/testing.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/text.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widget.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/barchart.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/block.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/calendar.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/canvas.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/chart.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/clear.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/gauge.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/list.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/paragraph.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/scrollbar.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/sparkline.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/symbols.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/table.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/tui/widgets/tabs.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/utils/__init__.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/utils/http_proxy.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/utils/image_processing.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/utils/secrets.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/utils/version_check.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau_coding_agent.egg-info/dependency_links.txt +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau_coding_agent.egg-info/entry_points.txt +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau_coding_agent.egg-info/requires.txt +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau_coding_agent.egg-info/top_level.txt +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_agent_compaction.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_agent_hooks.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_agent_prompt.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_agent_types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_ansi.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_ansi_bridge_legacy_image.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_audio_transcription_apis.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_auth_command.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_auth_manager.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_auth_storage.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_autocomplete_modifiers.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_branch_navigation.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_branch_summarization.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_builtin_tools.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_builtin_watch.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_cli_inputs.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_command_execution_policy.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_command_registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_compaction.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_component_buffer_bridge.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_diff.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_editor_protocol.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_engine_execution.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_engine_steering.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_engine_types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_escape_behavior.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_extension_api.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_extension_lifecycle.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_footer_model.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_footer_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_fuzzy.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_http_proxy.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_image_processing.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_inference_api_text_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_inference_dialect.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_inference_error_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_inference_types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_inference_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_keybindings.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_layout_render_cells.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_markdown.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_mcp_extension.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_message_list_frozen.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_message_types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_message_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_model_availability.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_model_command.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_model_registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_model_selector_modal.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_model_types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_oauth_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_package_resources.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_packages_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_peer_extension.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_pkce.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_prompts_expand.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_prompts_loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_prompts_registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_provider_registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_resource_loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_rpc_mode.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_runtime_sdk.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_scrollback_raw_writes.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_scrollback_terminal.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_secrets.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_select_list_render_cells.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_selector_controller.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_session_types.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_session_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_settings_manager.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_settings_paths.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_settings_storage.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_settings_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_skills_loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_skills_registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_telemetry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_text_input.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_text_input_render_cells.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_text_llm_invoke.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_theme_loader.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_themes_registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_thinking_render.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tool_registry.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tool_render.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_trust_manager.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_trust_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_capabilities.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_child_rows.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_input.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_layout_sizing.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_public_api.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_renderer.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_select_list.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_theme.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_tui_utils.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_ui_context.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_utils_hashline.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_version_check.py +0 -0
- {tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tests/test_voice_selector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tau-coding-agent
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: A self-extensible agent CLI with terminal UI, multi-provider LLM support, session management with branching, and a plugin system for tools and commands
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tau-coding-agent"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.2"
|
|
8
8
|
description = "A self-extensible agent CLI with terminal UI, multi-provider LLM support, session management with branching, and a plugin system for tools and commands"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -20,6 +20,7 @@ from tau.message.utils import strip_unusable_trailing_assistant
|
|
|
20
20
|
from tau.session.compaction import CompactionSettings
|
|
21
21
|
from tau.session.utils import to_llm_messages as _to_llm_messages
|
|
22
22
|
from tau.tool.types import ToolInvocation, ToolResult
|
|
23
|
+
from tau.utils.format import human_size as _fmt_size
|
|
23
24
|
|
|
24
25
|
_log = logging.getLogger(__name__)
|
|
25
26
|
|
|
@@ -33,14 +34,6 @@ _TOOL_CAP_LINES = 2000 # DEFAULT_MAX_LINES
|
|
|
33
34
|
_TOOL_LINE_CAP_BYTES = 2 * 1024 # 2 KB — max bytes for a single line
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def _fmt_size(n: int) -> str:
|
|
37
|
-
if n < 1024:
|
|
38
|
-
return f"{n}B"
|
|
39
|
-
if n < 1024 * 1024:
|
|
40
|
-
return f"{n / 1024:.1f}KB"
|
|
41
|
-
return f"{n / (1024 * 1024):.1f}MB"
|
|
42
|
-
|
|
43
|
-
|
|
44
37
|
if TYPE_CHECKING:
|
|
45
38
|
from tau.engine.service import Engine
|
|
46
39
|
from tau.runtime.service import Runtime
|
{tau_coding_agent-0.6.1 → tau_coding_agent-0.6.2}/tau/builtins/extensions/ask_user/component.py
RENAMED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import textwrap
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
|
-
from schema import AskUserOption # type: ignore[import-not-found]
|
|
6
|
+
from schema import FREEFORM_LABEL, AskUserOption # type: ignore[import-not-found]
|
|
6
7
|
|
|
7
8
|
from tau.tui.component import Component
|
|
8
9
|
from tau.tui.input import InputEvent, KeyEvent
|
|
@@ -11,8 +12,6 @@ if TYPE_CHECKING:
|
|
|
11
12
|
from tau.tui.buffer import Buffer
|
|
12
13
|
from tau.tui.geometry import Rect
|
|
13
14
|
|
|
14
|
-
FREEFORM_LABEL = "Type something…"
|
|
15
|
-
|
|
16
15
|
|
|
17
16
|
def _typed_char(event: KeyEvent) -> str | None:
|
|
18
17
|
"""Return the printable character a key event represents, or None.
|
|
@@ -34,6 +33,12 @@ class _AskUserComponent(Component):
|
|
|
34
33
|
ML_VISIBLE_ROWS = 8
|
|
35
34
|
ML_MIN_VISIBLE_ROWS = 4 # pad short buffers so the box visibly reads as multi-line
|
|
36
35
|
|
|
36
|
+
# Below this total width there's no room for a legible side-by-side preview
|
|
37
|
+
# column, so the option list just falls back to full width with no preview.
|
|
38
|
+
PREVIEW_MIN_TOTAL_WIDTH = 70
|
|
39
|
+
PREVIEW_GAP = 2
|
|
40
|
+
PREVIEW_MIN_BOX_HEIGHT = 6
|
|
41
|
+
|
|
37
42
|
def __init__(
|
|
38
43
|
self,
|
|
39
44
|
question: str,
|
|
@@ -85,96 +90,206 @@ class _AskUserComponent(Component):
|
|
|
85
90
|
|
|
86
91
|
# ── Render ────────────────────────────────────────────────────────────
|
|
87
92
|
|
|
93
|
+
def _has_preview_capable_options(self) -> bool:
|
|
94
|
+
return not self._allow_multiple and any(o.preview for o in self._options)
|
|
95
|
+
|
|
88
96
|
def render_cells(self, area: Rect, buf: Buffer) -> int:
|
|
89
97
|
from tau.tui.ansi_bridge import parse_ansi_wrapped_into
|
|
90
98
|
|
|
91
|
-
def
|
|
99
|
+
def _write(lines: list[str], x: int, y: int, width: int) -> int:
|
|
92
100
|
row = 0
|
|
93
101
|
for line in lines:
|
|
94
|
-
row += parse_ansi_wrapped_into(buf,
|
|
102
|
+
row += parse_ansi_wrapped_into(buf, x, y + row, line, width)
|
|
95
103
|
return row
|
|
96
104
|
|
|
97
|
-
|
|
105
|
+
header: list[str] = []
|
|
98
106
|
if self._context:
|
|
99
107
|
for line in self._context.splitlines():
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
header.append(f" \x1b[2m{line}\x1b[0m")
|
|
109
|
+
header.append("")
|
|
110
|
+
header.append(f" \x1b[1m{self._question}\x1b[0m")
|
|
111
|
+
header.append("")
|
|
112
|
+
|
|
113
|
+
header_rows = _write(header, area.x, area.y, area.width)
|
|
114
|
+
body_y = area.y + header_rows
|
|
115
|
+
|
|
116
|
+
show_preview = (
|
|
117
|
+
self._has_preview_capable_options() and area.width >= self.PREVIEW_MIN_TOTAL_WIDTH
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
left_width = min(46, max(30, area.width // 3)) if show_preview else area.width
|
|
121
|
+
content_lines, footer_lines = self._build_body_lines(left_width)
|
|
122
|
+
|
|
123
|
+
if not show_preview:
|
|
124
|
+
body_rows = _write(content_lines, area.x, body_y, area.width)
|
|
125
|
+
footer_rows = _write(footer_lines, area.x, body_y + body_rows, area.width)
|
|
126
|
+
return header_rows + body_rows + footer_rows
|
|
127
|
+
|
|
128
|
+
right_width = area.width - left_width - self.PREVIEW_GAP
|
|
129
|
+
|
|
130
|
+
preview: str | None = None
|
|
131
|
+
if self._cursor != self._freeform_index and self._options:
|
|
132
|
+
preview = self._options[self._cursor].preview
|
|
133
|
+
|
|
134
|
+
box_height = max(len(content_lines), self.PREVIEW_MIN_BOX_HEIGHT)
|
|
135
|
+
preview_box = self._build_preview_box(preview, right_width, box_height)
|
|
136
|
+
|
|
137
|
+
left_rows = _write(content_lines, area.x, body_y, left_width)
|
|
138
|
+
right_rows = _write(preview_box, area.x + left_width + self.PREVIEW_GAP, body_y, right_width)
|
|
139
|
+
col_rows = max(left_rows, right_rows)
|
|
140
|
+
|
|
141
|
+
# Footer spans the FULL width beneath both columns — it's not part of
|
|
142
|
+
# either column, so it never wraps just because the left column is
|
|
143
|
+
# narrow, and it visually separates from the preview box like the
|
|
144
|
+
# bottom nav-hint line in the reference screenshot.
|
|
145
|
+
footer_rows = _write(footer_lines, area.x, body_y + col_rows, area.width)
|
|
146
|
+
return header_rows + col_rows + footer_rows
|
|
147
|
+
|
|
148
|
+
def _build_body_lines(self, width: int) -> tuple[list[str], list[str]]:
|
|
149
|
+
"""Everything below the header: the option list, or whichever freeform
|
|
150
|
+
editor is active. ``width`` is whatever column this ends up rendered
|
|
151
|
+
in (full width, or the left column next to a preview pane) — used to
|
|
152
|
+
wrap option descriptions so they don't run into the next row.
|
|
153
|
+
|
|
154
|
+
Returns ``(content_lines, footer_lines)``. The footer is kept separate
|
|
155
|
+
from the content so the caller can always render it full-width, even
|
|
156
|
+
when the content itself is confined to a narrow left column.
|
|
157
|
+
"""
|
|
105
158
|
if self._mode == "freeform" and self._multiline:
|
|
106
|
-
self.
|
|
107
|
-
limit = self._ml_scroll_top + self.ML_VISIBLE_ROWS
|
|
108
|
-
visible = self._ml_lines[self._ml_scroll_top : limit]
|
|
109
|
-
for ri, line in enumerate(visible):
|
|
110
|
-
abs_row = self._ml_scroll_top + ri
|
|
111
|
-
if abs_row == self._ml_cursor_row:
|
|
112
|
-
before = line[: self._ml_cursor_col]
|
|
113
|
-
after = line[self._ml_cursor_col :]
|
|
114
|
-
inner.append(f" {before}█{after}")
|
|
115
|
-
else:
|
|
116
|
-
inner.append(f" {line}")
|
|
117
|
-
# Pad with blank rows (display-only, not part of the buffer) so an
|
|
118
|
-
# empty/short answer still shows several rows — a visible cue that
|
|
119
|
-
# this is a multi-line editor, not a single-line box.
|
|
120
|
-
for _ in range(self.ML_MIN_VISIBLE_ROWS - len(visible)):
|
|
121
|
-
inner.append("")
|
|
122
|
-
total = len(self._ml_lines)
|
|
123
|
-
if total > self.ML_VISIBLE_ROWS:
|
|
124
|
-
pct = int(self._ml_scroll_top / max(1, total - self.ML_VISIBLE_ROWS) * 100)
|
|
125
|
-
inner.append(f" \x1b[2m↕ {pct}%\x1b[0m")
|
|
126
|
-
else:
|
|
127
|
-
inner.append("")
|
|
128
|
-
back = "Esc to cancel" if not self._options else "Esc to go back"
|
|
129
|
-
inner.append(
|
|
130
|
-
" \x1b[2mEnter to submit · \\+Enter or Shift+Enter for newline · "
|
|
131
|
-
f"{back}\x1b[0m"
|
|
132
|
-
)
|
|
133
|
-
return _plain(inner)
|
|
134
|
-
|
|
159
|
+
return self._build_multiline_body()
|
|
135
160
|
if self._mode == "freeform":
|
|
136
|
-
|
|
161
|
+
return self._build_singleline_body()
|
|
162
|
+
return self._build_list_body(width)
|
|
163
|
+
|
|
164
|
+
def _build_multiline_body(self) -> tuple[list[str], list[str]]:
|
|
165
|
+
inner: list[str] = []
|
|
166
|
+
self._clamp_ml_scroll()
|
|
167
|
+
limit = self._ml_scroll_top + self.ML_VISIBLE_ROWS
|
|
168
|
+
visible = self._ml_lines[self._ml_scroll_top : limit]
|
|
169
|
+
for ri, line in enumerate(visible):
|
|
170
|
+
abs_row = self._ml_scroll_top + ri
|
|
171
|
+
if abs_row == self._ml_cursor_row:
|
|
172
|
+
before = line[: self._ml_cursor_col]
|
|
173
|
+
after = line[self._ml_cursor_col :]
|
|
174
|
+
inner.append(f" {before}█{after}")
|
|
175
|
+
else:
|
|
176
|
+
inner.append(f" {line}")
|
|
177
|
+
# Pad with blank rows (display-only, not part of the buffer) so an
|
|
178
|
+
# empty/short answer still shows several rows — a visible cue that
|
|
179
|
+
# this is a multi-line editor, not a single-line box.
|
|
180
|
+
for _ in range(self.ML_MIN_VISIBLE_ROWS - len(visible)):
|
|
181
|
+
inner.append("")
|
|
182
|
+
total = len(self._ml_lines)
|
|
183
|
+
if total > self.ML_VISIBLE_ROWS:
|
|
184
|
+
pct = int(self._ml_scroll_top / max(1, total - self.ML_VISIBLE_ROWS) * 100)
|
|
185
|
+
inner.append(f" \x1b[2m↕ {pct}%\x1b[0m")
|
|
186
|
+
else:
|
|
137
187
|
inner.append("")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
188
|
+
back = "Esc to cancel" if not self._options else "Esc to go back"
|
|
189
|
+
footer = [
|
|
190
|
+
"",
|
|
191
|
+
" \x1b[2mEnter to submit · \\+Enter or Shift+Enter for newline · "
|
|
192
|
+
f"{back}\x1b[0m",
|
|
193
|
+
]
|
|
194
|
+
return inner, footer
|
|
195
|
+
|
|
196
|
+
def _build_singleline_body(self) -> tuple[list[str], list[str]]:
|
|
197
|
+
back = "Esc to cancel" if not self._options else "Esc to go back"
|
|
198
|
+
content = [f" {self._freeform_value}█"]
|
|
199
|
+
footer = ["", f" \x1b[2mEnter to submit · {back}\x1b[0m"]
|
|
200
|
+
return content, footer
|
|
201
|
+
|
|
202
|
+
# Left margin + hanging indent for wrapped description lines beneath a
|
|
203
|
+
# title row — matches the " ❯ 1. " prefix width closely enough to read as
|
|
204
|
+
# aligned without having to compute each row's exact glyph width.
|
|
205
|
+
DESC_INDENT = " "
|
|
206
|
+
|
|
207
|
+
def _build_list_body(self, width: int) -> tuple[list[str], list[str]]:
|
|
208
|
+
inner: list[str] = []
|
|
209
|
+
desc_width = max(width - len(self.DESC_INDENT), 10)
|
|
141
210
|
|
|
142
211
|
for i in range(self._row_count):
|
|
143
212
|
is_freeform_row = i == self._freeform_index
|
|
144
213
|
title = FREEFORM_LABEL if is_freeform_row else self._options[i].title
|
|
145
214
|
desc = "" if is_freeform_row else (self._options[i].description or "")
|
|
146
215
|
is_cursor = i == self._cursor
|
|
147
|
-
cursor_mark = "
|
|
148
|
-
|
|
149
|
-
if
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
|
|
216
|
+
cursor_mark = "❯" if is_cursor else " "
|
|
217
|
+
|
|
218
|
+
if is_freeform_row:
|
|
219
|
+
# Synthetic action row, not a countable option — keeps its own
|
|
220
|
+
# "❯" marker instead of a number.
|
|
221
|
+
marker = " ❯ "
|
|
222
|
+
elif self._allow_multiple:
|
|
223
|
+
# Same tick glyphs as the /extensions config panel, paired with
|
|
224
|
+
# the option's ordinal number. Cursor rows get wrapped in
|
|
225
|
+
# reverse-video below, so leave them uncolored here — an
|
|
226
|
+
# embedded reset would cut the highlight short.
|
|
153
227
|
if i in self._checked:
|
|
154
228
|
box = "✔" if is_cursor else "\x1b[32m✔\x1b[0m"
|
|
155
229
|
else:
|
|
156
230
|
box = "✖" if is_cursor else "\x1b[2m✖\x1b[0m"
|
|
157
|
-
|
|
158
|
-
# Radio-style circles: filled at the cursor position, hollow
|
|
159
|
-
# elsewhere. Same reverse-video caveat as the checkbox ticks
|
|
160
|
-
# above — leave the cursor row's glyph uncolored.
|
|
161
|
-
box = "●" if is_cursor else "\x1b[2m○\x1b[0m"
|
|
231
|
+
marker = f"{i + 1}. {box}"
|
|
162
232
|
else:
|
|
163
|
-
|
|
233
|
+
marker = f"{i + 1}."
|
|
164
234
|
|
|
165
|
-
row = f" {cursor_mark} {
|
|
235
|
+
row = f" {cursor_mark} {marker} {title}"
|
|
166
236
|
if is_cursor:
|
|
167
237
|
row = f"\x1b[7m{row}\x1b[0m"
|
|
168
|
-
if desc:
|
|
169
|
-
row += f" \x1b[2m{desc}\x1b[0m"
|
|
170
238
|
inner.append(row)
|
|
171
239
|
|
|
172
|
-
|
|
240
|
+
# Description on its own hanging-indented line(s) below the title,
|
|
241
|
+
# not packed onto the title row — at left-column widths that
|
|
242
|
+
# would run straight into the next option's row.
|
|
243
|
+
for line in textwrap.wrap(desc, desc_width) if desc else []:
|
|
244
|
+
inner.append(f"{self.DESC_INDENT}\x1b[2m{line}\x1b[0m")
|
|
245
|
+
|
|
173
246
|
hints = ["↑/↓ move", "Enter confirm", "Esc cancel"]
|
|
174
247
|
if self._allow_multiple:
|
|
175
248
|
hints.insert(1, "Space toggle")
|
|
176
|
-
|
|
177
|
-
return
|
|
249
|
+
footer = ["", " \x1b[2m" + " · ".join(hints) + "\x1b[0m"]
|
|
250
|
+
return inner, footer
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def _build_preview_box(preview: str | None, width: int, height: int) -> list[str]:
|
|
254
|
+
"""A bordered box of exactly ``height`` lines, sized to ``width``.
|
|
255
|
+
|
|
256
|
+
Content beyond the available rows is truncated with a "N lines hidden"
|
|
257
|
+
footer rather than growing the box — the box height is pinned to the
|
|
258
|
+
paired option list so the two columns stay aligned row-for-row.
|
|
259
|
+
"""
|
|
260
|
+
dim, reset = "\x1b[2m", "\x1b[0m"
|
|
261
|
+
inner_width = max(width - 4, 4)
|
|
262
|
+
top = f"{dim}┌{'─' * (width - 2)}┐{reset}"
|
|
263
|
+
bottom = f"{dim}└{'─' * (width - 2)}┘{reset}"
|
|
264
|
+
content_rows = max(height - 2, 1)
|
|
265
|
+
|
|
266
|
+
def pad(text: str) -> str:
|
|
267
|
+
return text + " " * max(0, inner_width - len(text))
|
|
268
|
+
|
|
269
|
+
def framed(text: str) -> str:
|
|
270
|
+
return f"{dim}│{reset} {pad(text)} {dim}│{reset}"
|
|
271
|
+
|
|
272
|
+
if not preview:
|
|
273
|
+
body = [framed("(no preview for this option)" if content_rows else "")]
|
|
274
|
+
body += [framed("") for _ in range(content_rows - len(body))]
|
|
275
|
+
return [top, *body[:content_rows], bottom]
|
|
276
|
+
|
|
277
|
+
import textwrap
|
|
278
|
+
|
|
279
|
+
wrapped: list[str] = []
|
|
280
|
+
for src_line in preview.splitlines() or [""]:
|
|
281
|
+
wrapped.extend(textwrap.wrap(src_line, inner_width) or [""])
|
|
282
|
+
|
|
283
|
+
if len(wrapped) > content_rows:
|
|
284
|
+
visible = wrapped[: max(content_rows - 1, 1)]
|
|
285
|
+
hidden = len(wrapped) - len(visible)
|
|
286
|
+
footer = f"✂ {hidden} lines hidden".center(inner_width)
|
|
287
|
+
body = [framed(line) for line in visible] + [framed(footer)]
|
|
288
|
+
else:
|
|
289
|
+
body = [framed(line) for line in wrapped]
|
|
290
|
+
body += [framed("") for _ in range(content_rows - len(body))]
|
|
291
|
+
|
|
292
|
+
return [top, *body[:content_rows], bottom]
|
|
178
293
|
|
|
179
294
|
# ── Input ─────────────────────────────────────────────────────────────
|
|
180
295
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
MAX_QUESTIONS = 4
|
|
6
|
+
MIN_OPTIONS = 2
|
|
7
|
+
MAX_OPTIONS = 4
|
|
8
|
+
MAX_LABEL_LENGTH = 60
|
|
9
|
+
|
|
10
|
+
FREEFORM_LABEL = "Type something…"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AskUserOption(BaseModel):
|
|
14
|
+
title: str
|
|
15
|
+
description: str | None = None
|
|
16
|
+
preview: str | None = Field(
|
|
17
|
+
default=None,
|
|
18
|
+
description=(
|
|
19
|
+
"Optional preview content shown when this option is focused — an ASCII "
|
|
20
|
+
"mockup, code snippet, diagram, or config example the user needs to see "
|
|
21
|
+
"to compare choices. Markdown/plain text, multi-line supported. Only "
|
|
22
|
+
"supported on single-select questions (allow_multiple=False)."
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class AskUserQuestion(BaseModel):
|
|
28
|
+
question: str = Field(..., description="The question to ask the user")
|
|
29
|
+
context: str | None = Field(
|
|
30
|
+
default=None, description="Relevant context summary shown before the question"
|
|
31
|
+
)
|
|
32
|
+
options: list[str | AskUserOption] | None = Field(
|
|
33
|
+
default=None,
|
|
34
|
+
description=(
|
|
35
|
+
f"Multiple-choice options ({MIN_OPTIONS}-{MAX_OPTIONS} when provided). "
|
|
36
|
+
"Each needs a concise title (1-5 words); a description explaining the "
|
|
37
|
+
"trade-off is recommended. Do not author a 'Type something…' option "
|
|
38
|
+
"yourself — it is appended automatically when allow_freeform is true. "
|
|
39
|
+
"If you recommend a specific option, list it first and append "
|
|
40
|
+
'"(Recommended)" to its title.'
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
allow_multiple: bool = Field(default=False, description="Allow selecting more than one option")
|
|
44
|
+
allow_freeform: bool = Field(
|
|
45
|
+
default=True, description="Offer a 'Type something' freeform option"
|
|
46
|
+
)
|
|
47
|
+
multiline: bool = Field(
|
|
48
|
+
default=False,
|
|
49
|
+
description=(
|
|
50
|
+
"Use a multi-line text editor for the freeform answer instead of a single "
|
|
51
|
+
"line — set this for open-ended, long-form answers (e.g. 'write your bio', "
|
|
52
|
+
"'describe the requirements'). Supports arrow-key navigation between lines, "
|
|
53
|
+
"Enter for a newline, Ctrl+S/Ctrl+Enter to submit."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AskUserParams(BaseModel):
|
|
59
|
+
questions: list[AskUserQuestion] = Field(
|
|
60
|
+
...,
|
|
61
|
+
min_length=1,
|
|
62
|
+
max_length=MAX_QUESTIONS,
|
|
63
|
+
description=(
|
|
64
|
+
f"One or more questions to ask, in order (max {MAX_QUESTIONS} per call — "
|
|
65
|
+
"group all clarifying questions into one invocation instead of stacking "
|
|
66
|
+
"several ask_user calls back-to-back). Each is shown and answered before "
|
|
67
|
+
"the next appears — like a short interview. Use a single-item list for a "
|
|
68
|
+
"standalone question."
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
timeout: int | None = Field(
|
|
72
|
+
default=None,
|
|
73
|
+
description=(
|
|
74
|
+
"Auto-dismiss after N ms of inactivity on the current question and "
|
|
75
|
+
"cancel the rest of the sequence if it times out"
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
AskUserQuestion.model_rebuild()
|
|
81
|
+
AskUserParams.model_rebuild()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class QuestionValidationError(Exception):
|
|
85
|
+
"""Raised when a question sequence fails validation before any UI is shown."""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def normalize_options(raw: list[str | AskUserOption] | None) -> list[AskUserOption]:
|
|
89
|
+
return [AskUserOption(title=o) if isinstance(o, str) else o for o in (raw or [])]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def validate_questions(questions: list[AskUserQuestion]) -> None:
|
|
93
|
+
"""Reject malformed questionnaires up front, before rendering any UI.
|
|
94
|
+
|
|
95
|
+
Mirrors the guardrails of the reference ask-user-question implementation:
|
|
96
|
+
bounded option counts, no reserved/duplicate labels, no duplicate questions,
|
|
97
|
+
and previews restricted to single-select.
|
|
98
|
+
"""
|
|
99
|
+
if len(questions) > MAX_QUESTIONS:
|
|
100
|
+
raise QuestionValidationError(
|
|
101
|
+
f"ask_user supports at most {MAX_QUESTIONS} questions per call; got "
|
|
102
|
+
f"{len(questions)}. Group them into fewer, related questions."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
seen_questions: set[str] = set()
|
|
106
|
+
for q in questions:
|
|
107
|
+
if q.question in seen_questions:
|
|
108
|
+
raise QuestionValidationError(f"Duplicate question: {q.question!r}")
|
|
109
|
+
seen_questions.add(q.question)
|
|
110
|
+
|
|
111
|
+
if not q.options:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
if not (MIN_OPTIONS <= len(q.options) <= MAX_OPTIONS):
|
|
115
|
+
raise QuestionValidationError(
|
|
116
|
+
f"Question {q.question!r} must have between {MIN_OPTIONS} and "
|
|
117
|
+
f"{MAX_OPTIONS} options when options are provided; got {len(q.options)}."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
seen_titles: set[str] = set()
|
|
121
|
+
for opt in q.options:
|
|
122
|
+
title = opt if isinstance(opt, str) else opt.title
|
|
123
|
+
preview = None if isinstance(opt, str) else opt.preview
|
|
124
|
+
|
|
125
|
+
if len(title) > MAX_LABEL_LENGTH:
|
|
126
|
+
raise QuestionValidationError(
|
|
127
|
+
f"Option title {title!r} exceeds {MAX_LABEL_LENGTH} characters."
|
|
128
|
+
)
|
|
129
|
+
if title == FREEFORM_LABEL:
|
|
130
|
+
raise QuestionValidationError(
|
|
131
|
+
f"{FREEFORM_LABEL!r} is reserved for the auto-appended freeform "
|
|
132
|
+
"row — do not author it as an option."
|
|
133
|
+
)
|
|
134
|
+
if title in seen_titles:
|
|
135
|
+
raise QuestionValidationError(f"Duplicate option title: {title!r}")
|
|
136
|
+
seen_titles.add(title)
|
|
137
|
+
|
|
138
|
+
if preview and q.allow_multiple:
|
|
139
|
+
raise QuestionValidationError(
|
|
140
|
+
"Option preview is only supported on single-select questions "
|
|
141
|
+
f"(question {q.question!r} has allow_multiple=True)."
|
|
142
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from component import _AskUserComponent # type: ignore[import-not-found]
|
|
7
|
+
from schema import ( # type: ignore[import-not-found]
|
|
8
|
+
AskUserParams,
|
|
9
|
+
QuestionValidationError,
|
|
10
|
+
normalize_options,
|
|
11
|
+
validate_questions,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from tau.tool.render import call_line
|
|
15
|
+
from tau.tool.types import (
|
|
16
|
+
Tool,
|
|
17
|
+
ToolContext,
|
|
18
|
+
ToolExecutionMode,
|
|
19
|
+
ToolInvocation,
|
|
20
|
+
ToolKind,
|
|
21
|
+
ToolResult,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _render_call(args: dict, _streaming: bool = False) -> list[str]:
|
|
26
|
+
questions = args.get("questions") or []
|
|
27
|
+
if len(questions) <= 1:
|
|
28
|
+
label = questions[0].get("question", "") if questions else ""
|
|
29
|
+
else:
|
|
30
|
+
label = f"{len(questions)} questions"
|
|
31
|
+
return call_line("ask_user", label)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _render_result(content: str, opts: Any) -> list[str]:
|
|
35
|
+
return content.splitlines() or [content]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AskUserTool(Tool):
|
|
39
|
+
def __init__(self, runtime_ref: Any) -> None:
|
|
40
|
+
self._runtime_ref = runtime_ref
|
|
41
|
+
super().__init__(
|
|
42
|
+
name="ask_user",
|
|
43
|
+
description=(
|
|
44
|
+
"Ask the human one or more focused questions and wait for their decision "
|
|
45
|
+
"before proceeding. Pass multiple questions to run them as a sequence — "
|
|
46
|
+
"each is shown and answered in turn, like a short interview — instead of "
|
|
47
|
+
"issuing separate calls. Do not stack multiple ask_user calls back-to-back; "
|
|
48
|
+
"group all clarifying questions into one invocation. Use for high-impact "
|
|
49
|
+
"architectural trade-offs, ambiguous or conflicting requirements, or "
|
|
50
|
+
"assumptions that would materially change the implementation. Each "
|
|
51
|
+
"question supports single-select, multi-select, and freeform text "
|
|
52
|
+
"answers, and up to 4 options when options are given. If you recommend a "
|
|
53
|
+
"specific option, list it first and append \"(Recommended)\" to its "
|
|
54
|
+
"title. Only available in an interactive TUI session."
|
|
55
|
+
),
|
|
56
|
+
schema=AskUserParams,
|
|
57
|
+
kind=ToolKind.Read,
|
|
58
|
+
execution_mode=ToolExecutionMode.Sequential,
|
|
59
|
+
render_call=_render_call,
|
|
60
|
+
render_result=_render_result,
|
|
61
|
+
render_shell="default",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
async def execute(
|
|
65
|
+
self,
|
|
66
|
+
invocation: ToolInvocation,
|
|
67
|
+
tool_execution_update_callback=None,
|
|
68
|
+
signal=None,
|
|
69
|
+
context: ToolContext | None = None,
|
|
70
|
+
) -> ToolResult:
|
|
71
|
+
params = AskUserParams.model_validate(invocation.params)
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
validate_questions(params.questions)
|
|
75
|
+
except QuestionValidationError as e:
|
|
76
|
+
return ToolResult.error(invocation.id, str(e))
|
|
77
|
+
|
|
78
|
+
runtime = self._runtime_ref.runtime if self._runtime_ref is not None else None
|
|
79
|
+
if runtime is None:
|
|
80
|
+
return ToolResult.error(invocation.id, "ask_user unavailable: runtime not ready")
|
|
81
|
+
|
|
82
|
+
from tau.extensions.context import ExtensionContext
|
|
83
|
+
|
|
84
|
+
ext_ctx = ExtensionContext.from_runtime(runtime)
|
|
85
|
+
ui = ext_ctx.ui
|
|
86
|
+
if ui is None:
|
|
87
|
+
return ToolResult.error(
|
|
88
|
+
invocation.id,
|
|
89
|
+
"ask_user requires an interactive TUI session and is "
|
|
90
|
+
"unavailable in headless/RPC mode",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
answers: list[dict] = []
|
|
94
|
+
|
|
95
|
+
for question in params.questions:
|
|
96
|
+
options = normalize_options(question.options)
|
|
97
|
+
response = await self._ask_one(ui, question, options, params.timeout)
|
|
98
|
+
|
|
99
|
+
if response is None:
|
|
100
|
+
return ToolResult.ok(
|
|
101
|
+
invocation.id,
|
|
102
|
+
self._format_answers(answers, cancelled_at=question.question),
|
|
103
|
+
metadata={
|
|
104
|
+
"cancelled": True,
|
|
105
|
+
"answers": answers,
|
|
106
|
+
"cancelled_question": question.question,
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if response["kind"] == "freeform":
|
|
111
|
+
content = response["text"]
|
|
112
|
+
else:
|
|
113
|
+
content = ", ".join(response["selections"])
|
|
114
|
+
|
|
115
|
+
answers.append(
|
|
116
|
+
{"question": question.question, "response": content, "raw": response}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return ToolResult.ok(
|
|
120
|
+
invocation.id,
|
|
121
|
+
self._format_answers(answers),
|
|
122
|
+
metadata={"cancelled": False, "answers": answers},
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
async def _ask_one(
|
|
126
|
+
self,
|
|
127
|
+
ui: Any,
|
|
128
|
+
question: Any,
|
|
129
|
+
options: Any,
|
|
130
|
+
timeout: int | None,
|
|
131
|
+
) -> dict | None:
|
|
132
|
+
loop = asyncio.get_running_loop()
|
|
133
|
+
fut: asyncio.Future[dict | None] = loop.create_future()
|
|
134
|
+
timeout_task_ref: list[asyncio.Task | None] = [None]
|
|
135
|
+
|
|
136
|
+
def _on_done(value: dict | None) -> None:
|
|
137
|
+
t = timeout_task_ref[0]
|
|
138
|
+
if t is not None and not t.done():
|
|
139
|
+
t.cancel()
|
|
140
|
+
if not fut.done():
|
|
141
|
+
fut.set_result(value)
|
|
142
|
+
|
|
143
|
+
def _factory(_tui, _theme, _kb, done):
|
|
144
|
+
component = _AskUserComponent(
|
|
145
|
+
question=question.question,
|
|
146
|
+
context=question.context,
|
|
147
|
+
options=options,
|
|
148
|
+
allow_multiple=question.allow_multiple,
|
|
149
|
+
allow_freeform=question.allow_freeform,
|
|
150
|
+
multiline=question.multiline,
|
|
151
|
+
on_done=lambda v: (_on_done(v), done(v)),
|
|
152
|
+
)
|
|
153
|
+
if timeout:
|
|
154
|
+
timeout_ms = timeout
|
|
155
|
+
|
|
156
|
+
async def _auto_dismiss() -> None:
|
|
157
|
+
await asyncio.sleep(timeout_ms / 1000)
|
|
158
|
+
_on_done(None)
|
|
159
|
+
done(None)
|
|
160
|
+
|
|
161
|
+
timeout_task_ref[0] = asyncio.ensure_future(_auto_dismiss())
|
|
162
|
+
return component
|
|
163
|
+
|
|
164
|
+
await ui.custom_inline(_factory, kind="ask_user")
|
|
165
|
+
return await fut
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _format_answers(answers: list[dict], cancelled_at: str | None = None) -> str:
|
|
169
|
+
if not answers and cancelled_at is None:
|
|
170
|
+
return "The user cancelled the question without answering."
|
|
171
|
+
|
|
172
|
+
lines: list[str] = []
|
|
173
|
+
for i, item in enumerate(answers, start=1):
|
|
174
|
+
lines.append(f"Q{i}: {item['question']}")
|
|
175
|
+
lines.append(f"A{i}: {item['response']}")
|
|
176
|
+
lines.append("")
|
|
177
|
+
|
|
178
|
+
if cancelled_at is not None:
|
|
179
|
+
lines.append(f"(Cancelled before answering: {cancelled_at})")
|
|
180
|
+
|
|
181
|
+
return "\n".join(lines).rstrip()
|