zettcode 0.1.2__tar.gz → 0.1.3__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.3/PKG-INFO +142 -0
- zettcode-0.1.3/README.md +111 -0
- zettcode-0.1.3/README.zh-CN.md +106 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/pyproject.toml +10 -1
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/__init__.py +1 -1
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/agent.py +48 -2
- zettcode-0.1.3/src/zettcode/app/agent/ask.py +96 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/blocks.py +30 -44
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/compaction.py +14 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/entries.py +3 -1
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/export.py +5 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/projection.py +18 -4
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/replay.py +7 -2
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/runtime.py +52 -15
- zettcode-0.1.3/src/zettcode/app/agent/side.py +109 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/store.py +16 -1
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/subagents.py +40 -23
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/transcript.py +17 -6
- zettcode-0.1.3/src/zettcode/app/brand.py +63 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/commands.py +12 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/app.py +164 -12
- zettcode-0.1.3/src/zettcode/app/ui/boot.py +101 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/commands.py +21 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/keys.py +7 -0
- zettcode-0.1.3/src/zettcode/app/ui/labels.py +69 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/notices.py +81 -2
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/rows.py +3 -3
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/shell.py +24 -0
- zettcode-0.1.3/src/zettcode/app/ui/updating.py +97 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/__init__.py +12 -0
- zettcode-0.1.3/src/zettcode/app/ui/widgets/ask_page.py +386 -0
- zettcode-0.1.3/src/zettcode/app/ui/widgets/update_page.py +62 -0
- zettcode-0.1.3/src/zettcode/app/ui/widgets/welcome.py +21 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/cli.py +134 -3
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/config.py +24 -19
- zettcode-0.1.3/src/zettcode/paths.py +29 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/builtins.py +8 -9
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/app.py +26 -1
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/theme.py +8 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/decoder.py +22 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/events.py +5 -1
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/runner.py +28 -17
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/terminal.py +11 -24
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/theme_file.py +1 -0
- zettcode-0.1.3/src/zettcode/update.py +301 -0
- zettcode-0.1.2/PKG-INFO +0 -136
- zettcode-0.1.2/README.md +0 -107
- zettcode-0.1.2/README.zh-CN.md +0 -98
- zettcode-0.1.2/src/zettcode/app/ui/widgets/welcome.py +0 -19
- {zettcode-0.1.2 → zettcode-0.1.3}/.gitignore +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/LICENSE +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/_compat.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/approval.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/capabilities.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/context.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/mcp.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/mentions.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/rendering.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/rows.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/metadata.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/storage/records.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/title.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/trace/script.js +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/trace/style.css +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/trace/template.html +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/agent/usage.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/registry.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/clipboard.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/demo.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/sessions.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/settings.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/approval_page.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/completer.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/composer.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/context_page.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/model_page.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/panel.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/root.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/sessions_page.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/steering.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/app/ui/widgets/transcript.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/container.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/extension.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/loader.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/mixins.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/plugin.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/plugins/state.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/capabilities.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/console.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/events.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/focus.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/geometry.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/host.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/keymap.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/scheduler.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/screen.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/core/widget.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/gallery.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/glyphs.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/bridge.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/keys.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/input/reader.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/box.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/overlay.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/scroll.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/solver.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/layout/spacing.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/canvas.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/code.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/color.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/renderer.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/rich_text.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/style.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/sweep.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/render/text.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/testing/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/testing/harness.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/testing/snapshot.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/__init__.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/collapsible.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/completion.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/dialog.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/diff.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/list.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/list_page.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/markdown.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/progress.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/rich_text.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/status.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/table.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/tasks.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/text.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/textarea.py +0 -0
- {zettcode-0.1.2 → zettcode-0.1.3}/src/zettcode/tui/widgets/toast.py +0 -0
zettcode-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zettcode
|
|
3
|
+
Version: 0.1.3
|
|
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
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: agent,coding-agent,llm,terminal,tui
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Operating System :: MacOS
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: exceptiongroup<2,>=1.2; python_version < '3.11'
|
|
24
|
+
Requires-Dist: pyperclip<2,>=1.9
|
|
25
|
+
Requires-Dist: setproctitle<2,>=1.3.7; os_name == 'posix'
|
|
26
|
+
Requires-Dist: tomli<3,>=2; python_version < '3.11'
|
|
27
|
+
Requires-Dist: truststore<1,>=0.10
|
|
28
|
+
Requires-Dist: wcwidth<1,>=0.2
|
|
29
|
+
Requires-Dist: zett-agent<0.2,>=0.1.11
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
<p align="center">
|
|
33
|
+
<img src="docs/public/logo.svg" width="88" height="88" alt="ZettCode pixel robot with a warm heart" />
|
|
34
|
+
</p>
|
|
35
|
+
<h1 align="center">ZettCode</h1>
|
|
36
|
+
<p align="center"><strong>Your terminal. Your coding partner.</strong></p>
|
|
37
|
+
<p align="center">A focused coding agent that reads your project, shows its work, and keeps you in control.</p>
|
|
38
|
+
<p align="center">
|
|
39
|
+
<strong>English</strong> · <a href="README.zh-CN.md">简体中文</a><br />
|
|
40
|
+
<a href="https://chang-lehung.github.io/zettcode/guide/getting-started">Get started</a> ·
|
|
41
|
+
<a href="https://chang-lehung.github.io/zettcode/">Documentation</a> ·
|
|
42
|
+
<a href="https://chang-lehung.github.io/zettcode/guide/commands">Commands</a>
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<img src="docs/public/terminal.svg" width="1000" alt="ZettCode showing a task, thinking, file reads, a code edit, passing tests, and a Markdown answer" />
|
|
47
|
+
</p>
|
|
48
|
+
<p align="center"><sub>Illustrative conversation, rendered with ZettCode’s actual terminal widgets.</sub></p>
|
|
49
|
+
|
|
50
|
+
## Get running
|
|
51
|
+
|
|
52
|
+
You need **Python 3.10+**, a UTF-8 terminal, and an **OpenAI-compatible API**.
|
|
53
|
+
Works on macOS, Linux, and Windows (use Windows Terminal).
|
|
54
|
+
|
|
55
|
+
### 1. Install
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uv tool install zettcode
|
|
59
|
+
# or: pip install zettcode
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Connect a model
|
|
63
|
+
|
|
64
|
+
Create `~/.zettcode/config.toml` (and its parent directory) with one model:
|
|
65
|
+
|
|
66
|
+
```toml
|
|
67
|
+
[[models]]
|
|
68
|
+
model = "deepseek-v4-pro" # the model id sent to DeepSeek
|
|
69
|
+
display_model = "DeepSeek Pro" # the name shown in ZettCode
|
|
70
|
+
token = "sk-..." # replace with your DeepSeek API key
|
|
71
|
+
base_url = "https://api.deepseek.com" # DeepSeek's OpenAI-compatible API root
|
|
72
|
+
context_window = 1000000 # verify against the endpoint's current limits
|
|
73
|
+
multimodal = false # this model accepts text, not images
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Get a key from the [DeepSeek platform](https://platform.deepseek.com/), not from
|
|
77
|
+
the chat website. Alternatively, omit `token` and set `OPENAI_API_KEY` to that
|
|
78
|
+
key. Add more `[[models]]` entries to switch with `/model`; the first is selected
|
|
79
|
+
at startup. See the
|
|
80
|
+
[configuration reference](https://chang-lehung.github.io/zettcode/guide/config)
|
|
81
|
+
for complete examples, defaults, and troubleshooting.
|
|
82
|
+
|
|
83
|
+
### 3. Start a task
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cd /path/to/project
|
|
87
|
+
zettcode
|
|
88
|
+
# or: zettcode --workspace /path/to/project
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Describe a change and press **Enter**. Type `/` for commands, `@` to reference a
|
|
92
|
+
skill, and **Ctrl-C** to stop a request. **Ctrl-D** with an empty composer exits
|
|
93
|
+
and prints the command to resume your session.
|
|
94
|
+
|
|
95
|
+
## Built for the way you work
|
|
96
|
+
|
|
97
|
+
| | |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| **See every step** | Reasoning, answers, and tool calls stream as readable rows — not raw JSON. |
|
|
100
|
+
| **Keep the decision** | Review shell commands before they run. When the agent needs your input, choose an option or type an answer. |
|
|
101
|
+
| **Keep moving** | Send a steering message during a reply. Use `/btw` for a side question that stays out of later context. |
|
|
102
|
+
| **Come back later** | `/resume` restores a conversation; `/export` saves a readable HTML record. |
|
|
103
|
+
| **Understand the context** | Monitor token usage and caching, inspect `/context`, and compact when needed. |
|
|
104
|
+
| **Bring your tools** | Use project `AGENTS.md` instructions, skills, MCP servers, and Python plugins. |
|
|
105
|
+
|
|
106
|
+
The palette follows your terminal’s background, or your own `theme.toml`.
|
|
107
|
+
Select and copy text, scroll through answers, and paste images when your model
|
|
108
|
+
supports them.
|
|
109
|
+
|
|
110
|
+
## Find your next step
|
|
111
|
+
|
|
112
|
+
The **[user guide](https://chang-lehung.github.io/zettcode/guide/overview)** is
|
|
113
|
+
available in English and [简体中文](https://chang-lehung.github.io/zettcode/zh/guide/overview).
|
|
114
|
+
|
|
115
|
+
| First time | Everyday use | Configuration & extensions |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| [Getting started](https://chang-lehung.github.io/zettcode/guide/getting-started) | [Keys & mouse](https://chang-lehung.github.io/zettcode/guide/keys) | [Configuration](https://chang-lehung.github.io/zettcode/guide/config) |
|
|
118
|
+
| [The interface](https://chang-lehung.github.io/zettcode/guide/interface) | [Commands](https://chang-lehung.github.io/zettcode/guide/commands) | [Models & context](https://chang-lehung.github.io/zettcode/guide/models) |
|
|
119
|
+
| [Problems & questions](https://chang-lehung.github.io/zettcode/guide/faq) | [Sessions](https://chang-lehung.github.io/zettcode/guide/sessions) | [Project instructions](https://chang-lehung.github.io/zettcode/guide/instructions) |
|
|
120
|
+
| | | [Skills & MCP](https://chang-lehung.github.io/zettcode/guide/skills-and-mcp) · [Plugins](https://chang-lehung.github.io/zettcode/guide/plugins) |
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uv sync # install dependencies
|
|
126
|
+
make check # Ruff, mypy, and tests
|
|
127
|
+
make hooks # enforce mypy before commits
|
|
128
|
+
make demo # browse terminal widgets
|
|
129
|
+
make docs # preview the user guide locally
|
|
130
|
+
uv run zettcode --workspace . # run from the checkout
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
ZettCode provides the terminal application; the agent runtime is
|
|
134
|
+
[`zett-agent`](https://github.com/Chang-LeHung/zett-agent). Contributor notes and
|
|
135
|
+
invariants live in [`AGENTS.md`](AGENTS.md) and
|
|
136
|
+
[`docs/internal/`](docs/internal/), separately from the user guide. See
|
|
137
|
+
[`docs/internal/site-design.md`](docs/internal/site-design.md) for preview and
|
|
138
|
+
asset-generation instructions.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT. See [`LICENSE`](LICENSE).
|
zettcode-0.1.3/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/public/logo.svg" width="88" height="88" alt="ZettCode pixel robot with a warm heart" />
|
|
3
|
+
</p>
|
|
4
|
+
<h1 align="center">ZettCode</h1>
|
|
5
|
+
<p align="center"><strong>Your terminal. Your coding partner.</strong></p>
|
|
6
|
+
<p align="center">A focused coding agent that reads your project, shows its work, and keeps you in control.</p>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>English</strong> · <a href="README.zh-CN.md">简体中文</a><br />
|
|
9
|
+
<a href="https://chang-lehung.github.io/zettcode/guide/getting-started">Get started</a> ·
|
|
10
|
+
<a href="https://chang-lehung.github.io/zettcode/">Documentation</a> ·
|
|
11
|
+
<a href="https://chang-lehung.github.io/zettcode/guide/commands">Commands</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="docs/public/terminal.svg" width="1000" alt="ZettCode showing a task, thinking, file reads, a code edit, passing tests, and a Markdown answer" />
|
|
16
|
+
</p>
|
|
17
|
+
<p align="center"><sub>Illustrative conversation, rendered with ZettCode’s actual terminal widgets.</sub></p>
|
|
18
|
+
|
|
19
|
+
## Get running
|
|
20
|
+
|
|
21
|
+
You need **Python 3.10+**, a UTF-8 terminal, and an **OpenAI-compatible API**.
|
|
22
|
+
Works on macOS, Linux, and Windows (use Windows Terminal).
|
|
23
|
+
|
|
24
|
+
### 1. Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv tool install zettcode
|
|
28
|
+
# or: pip install zettcode
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. Connect a model
|
|
32
|
+
|
|
33
|
+
Create `~/.zettcode/config.toml` (and its parent directory) with one model:
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
[[models]]
|
|
37
|
+
model = "deepseek-v4-pro" # the model id sent to DeepSeek
|
|
38
|
+
display_model = "DeepSeek Pro" # the name shown in ZettCode
|
|
39
|
+
token = "sk-..." # replace with your DeepSeek API key
|
|
40
|
+
base_url = "https://api.deepseek.com" # DeepSeek's OpenAI-compatible API root
|
|
41
|
+
context_window = 1000000 # verify against the endpoint's current limits
|
|
42
|
+
multimodal = false # this model accepts text, not images
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Get a key from the [DeepSeek platform](https://platform.deepseek.com/), not from
|
|
46
|
+
the chat website. Alternatively, omit `token` and set `OPENAI_API_KEY` to that
|
|
47
|
+
key. Add more `[[models]]` entries to switch with `/model`; the first is selected
|
|
48
|
+
at startup. See the
|
|
49
|
+
[configuration reference](https://chang-lehung.github.io/zettcode/guide/config)
|
|
50
|
+
for complete examples, defaults, and troubleshooting.
|
|
51
|
+
|
|
52
|
+
### 3. Start a task
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd /path/to/project
|
|
56
|
+
zettcode
|
|
57
|
+
# or: zettcode --workspace /path/to/project
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Describe a change and press **Enter**. Type `/` for commands, `@` to reference a
|
|
61
|
+
skill, and **Ctrl-C** to stop a request. **Ctrl-D** with an empty composer exits
|
|
62
|
+
and prints the command to resume your session.
|
|
63
|
+
|
|
64
|
+
## Built for the way you work
|
|
65
|
+
|
|
66
|
+
| | |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| **See every step** | Reasoning, answers, and tool calls stream as readable rows — not raw JSON. |
|
|
69
|
+
| **Keep the decision** | Review shell commands before they run. When the agent needs your input, choose an option or type an answer. |
|
|
70
|
+
| **Keep moving** | Send a steering message during a reply. Use `/btw` for a side question that stays out of later context. |
|
|
71
|
+
| **Come back later** | `/resume` restores a conversation; `/export` saves a readable HTML record. |
|
|
72
|
+
| **Understand the context** | Monitor token usage and caching, inspect `/context`, and compact when needed. |
|
|
73
|
+
| **Bring your tools** | Use project `AGENTS.md` instructions, skills, MCP servers, and Python plugins. |
|
|
74
|
+
|
|
75
|
+
The palette follows your terminal’s background, or your own `theme.toml`.
|
|
76
|
+
Select and copy text, scroll through answers, and paste images when your model
|
|
77
|
+
supports them.
|
|
78
|
+
|
|
79
|
+
## Find your next step
|
|
80
|
+
|
|
81
|
+
The **[user guide](https://chang-lehung.github.io/zettcode/guide/overview)** is
|
|
82
|
+
available in English and [简体中文](https://chang-lehung.github.io/zettcode/zh/guide/overview).
|
|
83
|
+
|
|
84
|
+
| First time | Everyday use | Configuration & extensions |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| [Getting started](https://chang-lehung.github.io/zettcode/guide/getting-started) | [Keys & mouse](https://chang-lehung.github.io/zettcode/guide/keys) | [Configuration](https://chang-lehung.github.io/zettcode/guide/config) |
|
|
87
|
+
| [The interface](https://chang-lehung.github.io/zettcode/guide/interface) | [Commands](https://chang-lehung.github.io/zettcode/guide/commands) | [Models & context](https://chang-lehung.github.io/zettcode/guide/models) |
|
|
88
|
+
| [Problems & questions](https://chang-lehung.github.io/zettcode/guide/faq) | [Sessions](https://chang-lehung.github.io/zettcode/guide/sessions) | [Project instructions](https://chang-lehung.github.io/zettcode/guide/instructions) |
|
|
89
|
+
| | | [Skills & MCP](https://chang-lehung.github.io/zettcode/guide/skills-and-mcp) · [Plugins](https://chang-lehung.github.io/zettcode/guide/plugins) |
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uv sync # install dependencies
|
|
95
|
+
make check # Ruff, mypy, and tests
|
|
96
|
+
make hooks # enforce mypy before commits
|
|
97
|
+
make demo # browse terminal widgets
|
|
98
|
+
make docs # preview the user guide locally
|
|
99
|
+
uv run zettcode --workspace . # run from the checkout
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
ZettCode provides the terminal application; the agent runtime is
|
|
103
|
+
[`zett-agent`](https://github.com/Chang-LeHung/zett-agent). Contributor notes and
|
|
104
|
+
invariants live in [`AGENTS.md`](AGENTS.md) and
|
|
105
|
+
[`docs/internal/`](docs/internal/), separately from the user guide. See
|
|
106
|
+
[`docs/internal/site-design.md`](docs/internal/site-design.md) for preview and
|
|
107
|
+
asset-generation instructions.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/public/logo.svg" width="88" height="88" alt="ZettCode 像素机器人,中间是一颗暖色的心" />
|
|
3
|
+
</p>
|
|
4
|
+
<h1 align="center">ZettCode</h1>
|
|
5
|
+
<p align="center"><strong>你的终端。你的编码伙伴。</strong></p>
|
|
6
|
+
<p align="center">专注的编码智能体:读懂项目、展示过程,把决定权留给你。</p>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="README.md">English</a> · <strong>简体中文</strong><br />
|
|
9
|
+
<a href="https://chang-lehung.github.io/zettcode/zh/guide/getting-started">快速开始</a> ·
|
|
10
|
+
<a href="https://chang-lehung.github.io/zettcode/zh/">使用文档</a> ·
|
|
11
|
+
<a href="https://chang-lehung.github.io/zettcode/zh/guide/commands">命令参考</a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="docs/public/terminal.svg" width="1000" alt="ZettCode 依次展示任务、思考、读文件、改代码、通过的测试和 Markdown 回答" />
|
|
16
|
+
</p>
|
|
17
|
+
<p align="center"><sub>示例对话由 ZettCode 的实际终端组件渲染。</sub></p>
|
|
18
|
+
|
|
19
|
+
## 先跑起来
|
|
20
|
+
|
|
21
|
+
需要 **Python 3.10+**、支持 UTF-8 的终端和 **OpenAI 兼容接口**。
|
|
22
|
+
支持 macOS、Linux 和 Windows(请使用 Windows Terminal)。
|
|
23
|
+
|
|
24
|
+
### 1. 安装
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv tool install zettcode
|
|
28
|
+
# 或者:pip install zettcode
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 2. 连接模型
|
|
32
|
+
|
|
33
|
+
创建 `~/.zettcode/config.toml`(以及父目录),先配置一个模型:
|
|
34
|
+
|
|
35
|
+
```toml
|
|
36
|
+
[[models]]
|
|
37
|
+
model = "deepseek-v4-pro" # 发给 DeepSeek 的模型 id
|
|
38
|
+
display_model = "DeepSeek Pro" # ZettCode 界面中显示的名字
|
|
39
|
+
token = "sk-..." # 替换成你的 DeepSeek API key
|
|
40
|
+
base_url = "https://api.deepseek.com" # DeepSeek 的 OpenAI 兼容接口根地址
|
|
41
|
+
context_window = 1000000 # 以接入点当前公布的实际限制为准
|
|
42
|
+
multimodal = false # 这个模型接受文本,不接受图片
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
在 [DeepSeek 开放平台](https://platform.deepseek.com/)创建 API key,不是聊天网页的登录信息。
|
|
46
|
+
也可以省略 `token`,改用 `OPENAI_API_KEY` 环境变量保存这个 key。
|
|
47
|
+
添加更多 `[[models]]`,就能用 `/model` 切换;启动时默认选择第一项。
|
|
48
|
+
完整示例、默认值和排错步骤见[配置参考](https://chang-lehung.github.io/zettcode/zh/guide/config)。
|
|
49
|
+
|
|
50
|
+
### 3. 发出任务
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd /path/to/project
|
|
54
|
+
zettcode
|
|
55
|
+
# 或者:zettcode --workspace /path/to/project
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
描述要改什么,按 **Enter**。`/` 打开命令菜单,`@` 引用 skill,**Ctrl-C** 停止请求。
|
|
59
|
+
输入框为空时按 **Ctrl-D** 退出,终端会打印恢复当前会话的命令。
|
|
60
|
+
|
|
61
|
+
## 按你的方式工作
|
|
62
|
+
|
|
63
|
+
| | |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| **看清每一步** | 思考、回答和工具调用实时呈现为可读的行,不是原始 JSON。 |
|
|
66
|
+
| **决定权在你手上** | 执行 shell 命令前先审阅;智能体需要你的决定时,可以选选项,也可以打字回答。 |
|
|
67
|
+
| **工作不中断** | 回复过程中发送 steering(引导)消息;用 `/btw` 问一句,不让它进入后续上下文。 |
|
|
68
|
+
| **随时接着做** | `/resume` 恢复对话,`/export` 导出便于阅读的 HTML。 |
|
|
69
|
+
| **看懂上下文** | 查看 token 与缓存统计,用 `/context` 检查占用,在需要时压缩。 |
|
|
70
|
+
| **接入你的工具** | 使用项目 `AGENTS.md` 指令、skills、MCP 服务器和 Python 插件。 |
|
|
71
|
+
|
|
72
|
+
主题跟随终端背景,也可以使用你自己的 `theme.toml`。
|
|
73
|
+
选中复制文字、滚动查看回答;模型支持多模态时,还能粘贴图片。
|
|
74
|
+
|
|
75
|
+
## 找到你的下一步
|
|
76
|
+
|
|
77
|
+
**[用户指南](https://chang-lehung.github.io/zettcode/zh/guide/overview)** 提供简体中文与
|
|
78
|
+
[English](https://chang-lehung.github.io/zettcode/guide/overview) 两个版本。
|
|
79
|
+
|
|
80
|
+
| 第一次使用 | 日常使用 | 配置与扩展 |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| [快速开始](https://chang-lehung.github.io/zettcode/zh/guide/getting-started) | [快捷键与鼠标](https://chang-lehung.github.io/zettcode/zh/guide/keys) | [配置参考](https://chang-lehung.github.io/zettcode/zh/guide/config) |
|
|
83
|
+
| [认识界面](https://chang-lehung.github.io/zettcode/zh/guide/interface) | [命令参考](https://chang-lehung.github.io/zettcode/zh/guide/commands) | [模型与上下文](https://chang-lehung.github.io/zettcode/zh/guide/models) |
|
|
84
|
+
| [常见问题](https://chang-lehung.github.io/zettcode/zh/guide/faq) | [会话管理](https://chang-lehung.github.io/zettcode/zh/guide/sessions) | [项目指令](https://chang-lehung.github.io/zettcode/zh/guide/instructions) |
|
|
85
|
+
| | | [Skills 与 MCP](https://chang-lehung.github.io/zettcode/zh/guide/skills-and-mcp) · [插件](https://chang-lehung.github.io/zettcode/zh/guide/plugins) |
|
|
86
|
+
|
|
87
|
+
## 开发
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv sync # 安装依赖
|
|
91
|
+
make check # Ruff、mypy 与测试
|
|
92
|
+
make hooks # 每次提交前强制检查 mypy
|
|
93
|
+
make demo # 浏览终端组件
|
|
94
|
+
make docs # 本地预览使用文档
|
|
95
|
+
uv run zettcode --workspace . # 从源码运行
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
ZettCode 提供终端应用,智能体运行时来自
|
|
99
|
+
[`zett-agent`](https://github.com/Chang-LeHung/zett-agent)。贡献者约定和不变量放在
|
|
100
|
+
[`AGENTS.md`](AGENTS.md) 与 [`docs/internal/`](docs/internal/),不进入用户指南。
|
|
101
|
+
本地预览、重新生成效果图的方法见
|
|
102
|
+
[`docs/internal/site-design.md`](docs/internal/site-design.md)。
|
|
103
|
+
|
|
104
|
+
## 许可证
|
|
105
|
+
|
|
106
|
+
MIT,见 [`LICENSE`](LICENSE)。
|
|
@@ -25,9 +25,18 @@ dependencies = [
|
|
|
25
25
|
# The stdlib gained both of these in 3.11; older runtimes take the backports.
|
|
26
26
|
"exceptiongroup>=1.2,<2; python_version < '3.11'",
|
|
27
27
|
"pyperclip>=1.9,<2",
|
|
28
|
+
"setproctitle>=1.3.7,<2; os_name == 'posix'",
|
|
28
29
|
"tomli>=2,<3; python_version < '3.11'",
|
|
30
|
+
# The system trust store, so the release check works behind a proxy.
|
|
31
|
+
"truststore>=0.10,<1",
|
|
29
32
|
"wcwidth>=0.2,<1",
|
|
30
|
-
|
|
33
|
+
# A range, not a pin: 0.1.11 is the oldest runtime with the APIs this client
|
|
34
|
+
# uses (``AgentsMdExtension`` among them) and the first whose subagent
|
|
35
|
+
# extension imports its SQLite store lazily, so warming a runtime no longer
|
|
36
|
+
# loads SQLAlchemy. An exact pin would make zettcode fight every other tool
|
|
37
|
+
# that shares the dependency — including an upgrade of zett-agent itself,
|
|
38
|
+
# which pip would downgrade to satisfy it.
|
|
39
|
+
"zett-agent>=0.1.11,<0.2",
|
|
31
40
|
]
|
|
32
41
|
|
|
33
42
|
[project.scripts]
|
|
@@ -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.3"
|
|
11
11
|
|
|
12
12
|
#: Where each public name lives; read by :func:`__getattr__` on first use.
|
|
13
13
|
_EXPORTS = {
|
|
@@ -29,11 +29,13 @@ from zett_agent.model import ReasoningEffort, ToolDefinition
|
|
|
29
29
|
from ...config import ModelConfig, ZettCodeConfig
|
|
30
30
|
from ...plugins import UiRow
|
|
31
31
|
from ..commands import Command, CommandContext, CommandResult
|
|
32
|
+
from .ask import ASK_USER_RESPONSE_EVENT_NAME, AskUserQuestion, answer_payload, decline_payload
|
|
32
33
|
from .context import ContextReport
|
|
33
34
|
from .export import build_trace, render_html, write_export
|
|
34
35
|
from .mentions import MentionProvider, MentionRegistry
|
|
35
36
|
from .replay import replay
|
|
36
37
|
from .runtime import ZettCodeRuntime, build_system_prompt
|
|
38
|
+
from .side import SIDE, question
|
|
37
39
|
from .storage import Session, SessionInfo
|
|
38
40
|
from .title import summarize_title
|
|
39
41
|
from .transcript import Transcript
|
|
@@ -256,6 +258,7 @@ class ZettCodeAgent:
|
|
|
256
258
|
parts: Sequence[PromptPart],
|
|
257
259
|
*,
|
|
258
260
|
hint: str | None = None,
|
|
261
|
+
side: bool = False,
|
|
259
262
|
) -> AsyncIterator[AgentEvent]:
|
|
260
263
|
"""Run one turn with the selected session, model, and reasoning effort.
|
|
261
264
|
|
|
@@ -267,11 +270,13 @@ class ZettCodeAgent:
|
|
|
267
270
|
reference contributes. The text the user typed then rides along
|
|
268
271
|
as the message's ``prompt`` attribute, so the stored message is
|
|
269
272
|
what was typed and a restored session shows it.
|
|
273
|
+
side: Ask without letting the exchange join the conversation; see
|
|
274
|
+
:mod:`zettcode.app.agent.side`.
|
|
270
275
|
"""
|
|
271
276
|
await self.runtime.start()
|
|
272
277
|
client = self.runtime.started
|
|
273
278
|
async for event in client.stream(
|
|
274
|
-
self.request(parts, hint=hint),
|
|
279
|
+
self.request(parts, hint=hint, side=side),
|
|
275
280
|
config=AgentRunConfig(session_id=self.session_id),
|
|
276
281
|
model=self.runtime.provider,
|
|
277
282
|
reasoning_effort=self.runtime.effort,
|
|
@@ -279,7 +284,7 @@ class ZettCodeAgent:
|
|
|
279
284
|
yield event
|
|
280
285
|
|
|
281
286
|
@staticmethod
|
|
282
|
-
def request(parts: Sequence[PromptPart], *, hint: str | None = None) -> str | UserMessage:
|
|
287
|
+
def request(parts: Sequence[PromptPart], *, hint: str | None = None, side: bool = False) -> str | UserMessage:
|
|
283
288
|
"""Return the user turn those ordered parts make up.
|
|
284
289
|
|
|
285
290
|
Text and images keep the order they were written in inside one
|
|
@@ -288,7 +293,23 @@ class ZettCodeAgent:
|
|
|
288
293
|
``hint`` is appended last — as a trailing part when an image is in the
|
|
289
294
|
turn — and the typed text then rides along as the message's ``prompt``
|
|
290
295
|
attribute, so storage and a restored session keep it.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
parts: The turn in the order it was written.
|
|
299
|
+
hint: Instructions a ``@`` reference contributes.
|
|
300
|
+
side: Mark the turn as a side question, which the store records but
|
|
301
|
+
never replays.
|
|
291
302
|
"""
|
|
303
|
+
message = ZettCodeAgent.turn(parts, hint=hint)
|
|
304
|
+
if not side:
|
|
305
|
+
return message
|
|
306
|
+
if isinstance(message, UserMessage):
|
|
307
|
+
return replace(message, attributes={**message.attributes, SIDE: True}, include_in_messages=False)
|
|
308
|
+
return question(message)
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def turn(parts: Sequence[PromptPart], *, hint: str | None = None) -> str | UserMessage:
|
|
312
|
+
"""Return the message those parts make up, before any side marking."""
|
|
292
313
|
if not carries_image(parts):
|
|
293
314
|
text = plain_text(parts)
|
|
294
315
|
if hint is None:
|
|
@@ -551,6 +572,31 @@ class ZettCodeAgent:
|
|
|
551
572
|
config=AgentRunConfig(session_id=session_id),
|
|
552
573
|
)
|
|
553
574
|
|
|
575
|
+
def answer_ask(self, question: AskUserQuestion, answer: str) -> None:
|
|
576
|
+
"""Answer the model's question; the suspended tool call resumes with it.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
question: The question being answered, which carries the session and
|
|
580
|
+
the tool call the runtime routes the reply on.
|
|
581
|
+
answer: What the reader typed, exactly as it should reach the model.
|
|
582
|
+
"""
|
|
583
|
+
self._respond_to_ask(question, answer_payload(question, answer))
|
|
584
|
+
|
|
585
|
+
def decline_ask(self, question: AskUserQuestion) -> None:
|
|
586
|
+
"""Tell the model the reader cancelled, rather than leaving it waiting.
|
|
587
|
+
|
|
588
|
+
``ask_user`` has no rejection channel in the runtime, so the tool result
|
|
589
|
+
says the question was declined and why; the model carries on from that.
|
|
590
|
+
"""
|
|
591
|
+
self._respond_to_ask(question, decline_payload(question))
|
|
592
|
+
|
|
593
|
+
def _respond_to_ask(self, question: AskUserQuestion, payload: dict[str, object]) -> None:
|
|
594
|
+
"""Emit one ``ask_user_response`` for the session the question came from."""
|
|
595
|
+
self.runtime.started.agent.emit_external_event(
|
|
596
|
+
ExternalEvent(name=ASK_USER_RESPONSE_EVENT_NAME, payload=dict(payload)),
|
|
597
|
+
config=AgentRunConfig(session_id=question.session_id),
|
|
598
|
+
)
|
|
599
|
+
|
|
554
600
|
def steer(self, text: str) -> bool:
|
|
555
601
|
"""Queue an urgent user message for the request that is running.
|
|
556
602
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""The ``ask_user`` protocol: what the model asked, and what answers it.
|
|
2
|
+
|
|
3
|
+
``zett-agent`` owns the tool, the suspension and the routing; this module owns
|
|
4
|
+
the little the shell and the agent have to agree on — the question parsed out of
|
|
5
|
+
the event payload, and the payloads that answer it. Keeping both here means the
|
|
6
|
+
panel never reads a raw payload and the agent never invents one.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from zett_agent.events import AgentEvent
|
|
15
|
+
from zett_agent.extensions.ask_user import ASK_USER_EVENT_NAME, ASK_USER_RESPONSE_EVENT_NAME
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ASK_USER_RESPONSE_EVENT_NAME",
|
|
19
|
+
"AskUserQuestion",
|
|
20
|
+
"answer_payload",
|
|
21
|
+
"decline_payload",
|
|
22
|
+
"question_from",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class AskUserQuestion:
|
|
28
|
+
"""One question the model is waiting on.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
session_id: Session the question belongs to, echoed back with the answer.
|
|
32
|
+
call_id: Tool call the answer resolves; the agent routes on it.
|
|
33
|
+
question: The question, trimmed.
|
|
34
|
+
options: Choices the model offered, in display order; empty when it
|
|
35
|
+
offered none.
|
|
36
|
+
allow_multiple: Whether more than one option may be chosen.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
session_id: str
|
|
40
|
+
call_id: str
|
|
41
|
+
question: str
|
|
42
|
+
options: tuple[str, ...] = ()
|
|
43
|
+
allow_multiple: bool = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _options(value: object) -> tuple[str, ...]:
|
|
47
|
+
"""Return the usable choices in a payload field, dropping anything else."""
|
|
48
|
+
if not isinstance(value, list):
|
|
49
|
+
return ()
|
|
50
|
+
return tuple(item.strip() for item in value if isinstance(item, str) and item.strip())
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def question_from(event: AgentEvent) -> AskUserQuestion | None:
|
|
54
|
+
"""Return the question an event carries, or ``None`` when it is not one.
|
|
55
|
+
|
|
56
|
+
The payload comes from the runtime, but a malformed one must not take the
|
|
57
|
+
shell down: it is read defensively and reported as "not a question".
|
|
58
|
+
"""
|
|
59
|
+
if event.name != ASK_USER_EVENT_NAME:
|
|
60
|
+
return None
|
|
61
|
+
payload = event.payload if isinstance(event.payload, dict) else {}
|
|
62
|
+
question = payload.get("question")
|
|
63
|
+
call_id = payload.get("tool_call_id")
|
|
64
|
+
if not isinstance(question, str) or not question.strip():
|
|
65
|
+
return None
|
|
66
|
+
if not isinstance(call_id, str) or not call_id:
|
|
67
|
+
return None
|
|
68
|
+
session_id = payload.get("session_id") or event.session_id
|
|
69
|
+
return AskUserQuestion(
|
|
70
|
+
session_id=str(session_id or ""),
|
|
71
|
+
call_id=call_id,
|
|
72
|
+
question=question.strip(),
|
|
73
|
+
options=_options(payload.get("options")),
|
|
74
|
+
allow_multiple=bool(payload.get("allow_multiple")),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def answer_payload(question: AskUserQuestion, answer: str) -> dict[str, Any]:
|
|
79
|
+
"""Return the response payload for a question the reader answered."""
|
|
80
|
+
return {"tool_call_id": question.call_id, "answer": answer}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def decline_payload(question: AskUserQuestion) -> dict[str, Any]:
|
|
84
|
+
"""Return the response payload for a question the reader cancelled.
|
|
85
|
+
|
|
86
|
+
``ask_user`` has no rejection channel: the tool returns whatever payload the
|
|
87
|
+
UI sends. A cancel therefore answers with a result that says the question was
|
|
88
|
+
declined — the model reads that and carries on, instead of a suspended call
|
|
89
|
+
waiting for a reply that is never coming.
|
|
90
|
+
"""
|
|
91
|
+
return {
|
|
92
|
+
"tool_call_id": question.call_id,
|
|
93
|
+
"answer": None,
|
|
94
|
+
"declined": True,
|
|
95
|
+
"reason": "the user cancelled the question",
|
|
96
|
+
}
|