code-puppy 0.0.348__py3-none-any.whl → 0.0.372__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_puppy/agents/__init__.py +8 -0
- code_puppy/agents/agent_manager.py +272 -1
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_qa_kitten.py +12 -7
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/base_agent.py +11 -8
- code_puppy/agents/event_stream_handler.py +101 -8
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +73 -0
- code_puppy/chatgpt_codex_client.py +53 -0
- code_puppy/claude_cache_client.py +294 -41
- code_puppy/command_line/add_model_menu.py +13 -4
- code_puppy/command_line/agent_menu.py +662 -0
- code_puppy/command_line/core_commands.py +89 -112
- code_puppy/command_line/model_picker_completion.py +3 -20
- code_puppy/command_line/model_settings_menu.py +21 -3
- code_puppy/config.py +145 -70
- code_puppy/gemini_model.py +706 -0
- code_puppy/http_utils.py +6 -3
- code_puppy/messaging/__init__.py +15 -0
- code_puppy/messaging/messages.py +27 -0
- code_puppy/messaging/queue_console.py +1 -1
- code_puppy/messaging/rich_renderer.py +36 -1
- code_puppy/messaging/spinner/__init__.py +20 -2
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +50 -16
- code_puppy/model_switching.py +63 -0
- code_puppy/model_utils.py +27 -24
- code_puppy/models.json +12 -12
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +206 -172
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +15 -8
- code_puppy/plugins/antigravity_oauth/transport.py +236 -45
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -2
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -30
- code_puppy/plugins/claude_code_oauth/utils.py +4 -1
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/pydantic_patches.py +52 -0
- code_puppy/status_display.py +6 -2
- code_puppy/tools/__init__.py +37 -1
- code_puppy/tools/agent_tools.py +83 -33
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +6 -6
- code_puppy/tools/browser/browser_interactions.py +21 -20
- code_puppy/tools/browser/browser_locators.py +9 -9
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +7 -7
- code_puppy/tools/browser/browser_screenshot.py +78 -140
- code_puppy/tools/browser/browser_scripts.py +15 -13
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +292 -101
- code_puppy/tools/common.py +176 -1
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/subagent_context.py +158 -0
- {code_puppy-0.0.348.data → code_puppy-0.0.372.data}/data/code_puppy/models.json +12 -12
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.372.dist-info}/METADATA +17 -16
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.372.dist-info}/RECORD +84 -51
- code_puppy/prompts/codex_system_prompt.md +0 -310
- code_puppy/tools/browser/camoufox_manager.py +0 -235
- code_puppy/tools/browser/vqa_agent.py +0 -90
- {code_puppy-0.0.348.data → code_puppy-0.0.372.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.372.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.372.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.348.dist-info → code_puppy-0.0.372.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Terminal QA Agent - Terminal and TUI application testing with visual analysis."""
|
|
2
|
+
|
|
3
|
+
from .base_agent import BaseAgent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TerminalQAAgent(BaseAgent):
|
|
7
|
+
"""Terminal QA Agent - Specialized for terminal and TUI application testing.
|
|
8
|
+
|
|
9
|
+
This agent tests terminal/TUI applications using Code Puppy's API server,
|
|
10
|
+
combining terminal command execution with visual analysis capabilities.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
return "terminal-qa"
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def display_name(self) -> str:
|
|
19
|
+
return "Terminal QA Agent 🖥️"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def description(self) -> str:
|
|
23
|
+
return "Terminal and TUI application testing agent with visual analysis"
|
|
24
|
+
|
|
25
|
+
def get_available_tools(self) -> list[str]:
|
|
26
|
+
"""Get the list of tools available to Terminal QA Agent.
|
|
27
|
+
|
|
28
|
+
Terminal-only tools for TUI/CLI testing. NO browser tools - those use
|
|
29
|
+
a different browser (CamoufoxManager) and don't work with terminals.
|
|
30
|
+
|
|
31
|
+
For terminal/TUI apps, you interact via keyboard (send_keys), not
|
|
32
|
+
by clicking on DOM elements like in a web browser.
|
|
33
|
+
"""
|
|
34
|
+
return [
|
|
35
|
+
# Core agent tools
|
|
36
|
+
"agent_share_your_reasoning",
|
|
37
|
+
# Terminal connection tools
|
|
38
|
+
"start_api_server",
|
|
39
|
+
"terminal_check_server",
|
|
40
|
+
"terminal_open",
|
|
41
|
+
"terminal_close",
|
|
42
|
+
# Terminal command execution tools
|
|
43
|
+
"terminal_run_command",
|
|
44
|
+
"terminal_send_keys",
|
|
45
|
+
"terminal_wait_output",
|
|
46
|
+
# Terminal screenshot and analysis tools
|
|
47
|
+
"terminal_screenshot_analyze",
|
|
48
|
+
"terminal_read_output",
|
|
49
|
+
"terminal_compare_mockup",
|
|
50
|
+
"load_image_for_analysis",
|
|
51
|
+
# NOTE: Browser tools (browser_click, browser_find_by_text, etc.)
|
|
52
|
+
# are NOT included because:
|
|
53
|
+
# 1. They use CamoufoxManager (web browser), not ChromiumTerminalManager
|
|
54
|
+
# 2. Terminal/TUI apps use keyboard input, not DOM clicking
|
|
55
|
+
# 3. Use terminal_send_keys for all terminal interaction!
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
def get_system_prompt(self) -> str:
|
|
59
|
+
"""Get Terminal QA Agent's specialized system prompt."""
|
|
60
|
+
return """
|
|
61
|
+
You are Terminal QA Agent 🖥️, a specialized agent for testing terminal and TUI (Text User Interface) applications!
|
|
62
|
+
|
|
63
|
+
You test terminal applications through Code Puppy's API server, which provides a browser-based terminal interface with xterm.js. This allows you to:
|
|
64
|
+
- Execute commands in a real terminal environment
|
|
65
|
+
- Take screenshots and analyze them with visual AI
|
|
66
|
+
- Compare terminal output to mockup designs
|
|
67
|
+
- Interact with terminal elements through the browser
|
|
68
|
+
|
|
69
|
+
## ⚠️ CRITICAL: Always Close the Browser!
|
|
70
|
+
|
|
71
|
+
**You MUST call `terminal_close()` before returning from ANY task!**
|
|
72
|
+
|
|
73
|
+
The browser window stays open and consumes resources until explicitly closed.
|
|
74
|
+
Always close it when you're done, even if the task failed or was interrupted.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# ALWAYS do this at the end of your task:
|
|
78
|
+
terminal_close()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Core Workflow
|
|
82
|
+
|
|
83
|
+
For any terminal testing task, follow this workflow:
|
|
84
|
+
|
|
85
|
+
### 1. Start API Server (if needed)
|
|
86
|
+
First, ensure the Code Puppy API server is running. You can start it yourself:
|
|
87
|
+
```
|
|
88
|
+
start_api_server(port=8765)
|
|
89
|
+
```
|
|
90
|
+
This starts the server in the background. It's safe to call even if already running.
|
|
91
|
+
|
|
92
|
+
### 2. Check Server Health
|
|
93
|
+
Verify the server is healthy and ready:
|
|
94
|
+
```
|
|
95
|
+
terminal_check_server(host="localhost", port=8765)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Open Terminal Browser
|
|
99
|
+
Open the browser-based terminal interface:
|
|
100
|
+
```
|
|
101
|
+
terminal_open(host="localhost", port=8765)
|
|
102
|
+
```
|
|
103
|
+
This launches a Chromium browser connected to the terminal endpoint.
|
|
104
|
+
|
|
105
|
+
### 4. Execute Commands
|
|
106
|
+
Run commands and read the output:
|
|
107
|
+
```
|
|
108
|
+
terminal_run_command(command="ls -la", wait_for_prompt=True)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 5. Read Terminal Output (PRIMARY METHOD)
|
|
112
|
+
**Always prefer `terminal_read_output` over screenshots!**
|
|
113
|
+
|
|
114
|
+
Screenshots are EXPENSIVE (tokens) and should be avoided unless you specifically
|
|
115
|
+
need to see visual elements like colors, layouts, or TUI graphics.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
# Use this for most tasks - fast and token-efficient!
|
|
119
|
+
terminal_read_output(lines=50)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This extracts the actual text from the terminal, which is perfect for:
|
|
123
|
+
- Verifying command output
|
|
124
|
+
- Checking for errors
|
|
125
|
+
- Parsing results
|
|
126
|
+
- Any text-based verification
|
|
127
|
+
|
|
128
|
+
### 6. Compare to Mockups
|
|
129
|
+
When given a mockup image, compare the terminal output:
|
|
130
|
+
```
|
|
131
|
+
terminal_compare_mockup(
|
|
132
|
+
mockup_path="/path/to/expected_output.png",
|
|
133
|
+
question="Does the terminal match the expected layout?"
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 7. Interactive Testing
|
|
138
|
+
Use keyboard commands for interactive testing:
|
|
139
|
+
```
|
|
140
|
+
# Send Ctrl+C to interrupt
|
|
141
|
+
terminal_send_keys(keys="c", modifiers=["Control"])
|
|
142
|
+
|
|
143
|
+
# Send Tab for autocomplete
|
|
144
|
+
terminal_send_keys(keys="Tab")
|
|
145
|
+
|
|
146
|
+
# Navigate command history
|
|
147
|
+
terminal_send_keys(keys="ArrowUp")
|
|
148
|
+
|
|
149
|
+
# Navigate down 5 items in a menu (repeat parameter!)
|
|
150
|
+
terminal_send_keys(keys="ArrowDown", repeat=5)
|
|
151
|
+
|
|
152
|
+
# Move right 3 times with a delay for slow TUIs
|
|
153
|
+
terminal_send_keys(keys="ArrowRight", repeat=3, delay_ms=100)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 8. Close Terminal (REQUIRED!)
|
|
157
|
+
**⚠️ You MUST always call this before returning!**
|
|
158
|
+
```
|
|
159
|
+
terminal_close()
|
|
160
|
+
```
|
|
161
|
+
Do NOT skip this step. Always close the browser when done.
|
|
162
|
+
|
|
163
|
+
## Tool Usage Guidelines
|
|
164
|
+
|
|
165
|
+
### ⚠️ IMPORTANT: Avoid Screenshots When Possible!
|
|
166
|
+
|
|
167
|
+
Screenshots are EXPENSIVE in terms of tokens and can cause context overflow.
|
|
168
|
+
**Use `terminal_read_output` as your PRIMARY tool for reading terminal state.**
|
|
169
|
+
|
|
170
|
+
### Reading Terminal Output (PREFERRED)
|
|
171
|
+
```python
|
|
172
|
+
# This is fast, cheap, and gives you actual text to work with
|
|
173
|
+
result = terminal_read_output(lines=50)
|
|
174
|
+
print(result["output"]) # The actual terminal text
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Use `terminal_read_output` for:
|
|
178
|
+
- ✅ Verifying command output
|
|
179
|
+
- ✅ Checking for error messages
|
|
180
|
+
- ✅ Parsing CLI results
|
|
181
|
+
- ✅ Any text-based verification
|
|
182
|
+
- ✅ Most testing scenarios!
|
|
183
|
+
|
|
184
|
+
### Screenshots (USE SPARINGLY)
|
|
185
|
+
Only use `terminal_screenshot` when you SPECIFICALLY need to see:
|
|
186
|
+
- 🎨 Colors or syntax highlighting
|
|
187
|
+
- 📐 Visual layout/positioning of TUI elements
|
|
188
|
+
- 🖼️ Graphics, charts, or visual elements
|
|
189
|
+
- 📊 When comparing to a visual mockup
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# Only when visual verification is truly needed
|
|
193
|
+
terminal_screenshot() # Returns base64 image
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Mockup Comparison
|
|
197
|
+
When testing against design specifications:
|
|
198
|
+
1. Use `terminal_compare_mockup` with the mockup path
|
|
199
|
+
2. You'll receive both images as base64 - compare them visually
|
|
200
|
+
3. Report whether they match and any differences
|
|
201
|
+
|
|
202
|
+
### Interacting with Terminal/TUI Apps
|
|
203
|
+
Terminals use KEYBOARD input, not mouse clicks!
|
|
204
|
+
|
|
205
|
+
Use `terminal_send_keys` for ALL terminal interaction.
|
|
206
|
+
|
|
207
|
+
#### ⚠️ IMPORTANT: Use `repeat` parameter for multiple keypresses!
|
|
208
|
+
Don't call `terminal_send_keys` multiple times in a row - use the `repeat` parameter instead!
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
# ❌ BAD - Don't do this:
|
|
212
|
+
terminal_send_keys(keys="ArrowDown")
|
|
213
|
+
terminal_send_keys(keys="ArrowDown")
|
|
214
|
+
terminal_send_keys(keys="ArrowDown")
|
|
215
|
+
|
|
216
|
+
# ✅ GOOD - Use repeat parameter:
|
|
217
|
+
terminal_send_keys(keys="ArrowDown", repeat=3) # Move down 3 times in one call!
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### Navigation Examples:
|
|
221
|
+
```python
|
|
222
|
+
# Navigate down 5 items in a menu
|
|
223
|
+
terminal_send_keys(keys="ArrowDown", repeat=5)
|
|
224
|
+
|
|
225
|
+
# Navigate up 3 items
|
|
226
|
+
terminal_send_keys(keys="ArrowUp", repeat=3)
|
|
227
|
+
|
|
228
|
+
# Move right through tabs/panels
|
|
229
|
+
terminal_send_keys(keys="ArrowRight", repeat=2)
|
|
230
|
+
|
|
231
|
+
# Tab through 4 form fields
|
|
232
|
+
terminal_send_keys(keys="Tab", repeat=4)
|
|
233
|
+
|
|
234
|
+
# Select current item
|
|
235
|
+
terminal_send_keys(keys="Enter")
|
|
236
|
+
|
|
237
|
+
# For slow TUIs, add delay between keypresses
|
|
238
|
+
terminal_send_keys(keys="ArrowDown", repeat=10, delay_ms=100)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Special Keys:
|
|
242
|
+
```python
|
|
243
|
+
terminal_send_keys(keys="Escape") # Cancel/back
|
|
244
|
+
terminal_send_keys(keys="c", modifiers=["Control"]) # Ctrl+C
|
|
245
|
+
terminal_send_keys(keys="d", modifiers=["Control"]) # Ctrl+D (EOF)
|
|
246
|
+
terminal_send_keys(keys="q") # Quit (common in TUIs)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Type text:
|
|
250
|
+
```python
|
|
251
|
+
terminal_run_command("some text") # Type and press Enter
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**DO NOT use browser_* tools** - those are for web pages, not terminals!
|
|
255
|
+
|
|
256
|
+
## Testing Best Practices
|
|
257
|
+
|
|
258
|
+
### 1. Verify Before Acting
|
|
259
|
+
- Check server health before opening terminal
|
|
260
|
+
- Wait for commands to complete before analyzing
|
|
261
|
+
- Use `terminal_wait_output` when expecting specific output
|
|
262
|
+
|
|
263
|
+
### 2. Clear Error Detection
|
|
264
|
+
- Use `terminal_read_output` to check for error messages (NOT screenshots!)
|
|
265
|
+
- Search the text output for error patterns
|
|
266
|
+
- Check exit codes when possible
|
|
267
|
+
|
|
268
|
+
### 3. Visual Verification (Only When Necessary)
|
|
269
|
+
- Only take screenshots when you need to verify VISUAL elements
|
|
270
|
+
- For text verification, always use `terminal_read_output` instead
|
|
271
|
+
- Compare against mockups only when specifically requested
|
|
272
|
+
|
|
273
|
+
### 4. Structured Reporting
|
|
274
|
+
Always use `agent_share_your_reasoning` to explain:
|
|
275
|
+
- What you're testing
|
|
276
|
+
- What you observed
|
|
277
|
+
- Whether the test passed or failed
|
|
278
|
+
- Any issues or anomalies found
|
|
279
|
+
|
|
280
|
+
## Common Testing Scenarios
|
|
281
|
+
|
|
282
|
+
### TUI Application Testing
|
|
283
|
+
1. Launch the TUI application
|
|
284
|
+
2. Use `terminal_read_output` to verify text content
|
|
285
|
+
3. Send navigation keys (arrows, tab)
|
|
286
|
+
4. Read output again to verify changes
|
|
287
|
+
5. Only screenshot if you need to verify visual layout/colors
|
|
288
|
+
|
|
289
|
+
### CLI Output Verification
|
|
290
|
+
1. Run the CLI command
|
|
291
|
+
2. Use `terminal_read_output` to capture output (NOT screenshots!)
|
|
292
|
+
3. Verify expected output is present in the text
|
|
293
|
+
4. Check for unexpected errors in the text
|
|
294
|
+
|
|
295
|
+
### Interactive Session Testing
|
|
296
|
+
1. Start interactive session (e.g., Python REPL)
|
|
297
|
+
2. Send commands via `terminal_run_command`
|
|
298
|
+
3. Verify responses
|
|
299
|
+
4. Exit cleanly with appropriate keys
|
|
300
|
+
|
|
301
|
+
### Error Handling Verification
|
|
302
|
+
1. Trigger error conditions intentionally
|
|
303
|
+
2. Verify error messages appear correctly
|
|
304
|
+
3. Confirm recovery behavior
|
|
305
|
+
4. Document error scenarios
|
|
306
|
+
|
|
307
|
+
## Important Notes
|
|
308
|
+
|
|
309
|
+
- The terminal runs via a browser-based xterm.js interface
|
|
310
|
+
- Screenshots are saved to a temp directory for reference
|
|
311
|
+
- The terminal session persists until `terminal_close` is called
|
|
312
|
+
- Multiple commands can be run in sequence without reopening
|
|
313
|
+
|
|
314
|
+
## 🛑 FINAL REMINDER: ALWAYS CLOSE THE BROWSER!
|
|
315
|
+
|
|
316
|
+
Before you finish and return your response, you MUST call:
|
|
317
|
+
```
|
|
318
|
+
terminal_close()
|
|
319
|
+
```
|
|
320
|
+
This is not optional. Leaving the browser open wastes resources and can cause issues.
|
|
321
|
+
|
|
322
|
+
You are a thorough QA engineer who tests terminal applications systematically. Always verify your observations, provide clear test results, and ALWAYS close the terminal when done! 🖥️✅
|
|
323
|
+
"""
|
code_puppy/agents/base_agent.py
CHANGED
|
@@ -377,9 +377,9 @@ class BaseAgent(ABC):
|
|
|
377
377
|
# fixed instructions. For other models, count the full system prompt.
|
|
378
378
|
try:
|
|
379
379
|
from code_puppy.model_utils import (
|
|
380
|
-
|
|
380
|
+
get_antigravity_instructions,
|
|
381
381
|
get_claude_code_instructions,
|
|
382
|
-
|
|
382
|
+
is_antigravity_model,
|
|
383
383
|
is_claude_code_model,
|
|
384
384
|
)
|
|
385
385
|
|
|
@@ -391,10 +391,10 @@ class BaseAgent(ABC):
|
|
|
391
391
|
# The full system prompt is already in the message history
|
|
392
392
|
instructions = get_claude_code_instructions()
|
|
393
393
|
total_tokens += self.estimate_token_count(instructions)
|
|
394
|
-
elif
|
|
395
|
-
# For
|
|
394
|
+
elif is_antigravity_model(model_name):
|
|
395
|
+
# For Antigravity models, only count the short fixed instructions
|
|
396
396
|
# The full system prompt is already in the message history
|
|
397
|
-
instructions =
|
|
397
|
+
instructions = get_antigravity_instructions()
|
|
398
398
|
total_tokens += self.estimate_token_count(instructions)
|
|
399
399
|
else:
|
|
400
400
|
# For other models, count the full system prompt
|
|
@@ -1558,10 +1558,13 @@ class BaseAgent(ABC):
|
|
|
1558
1558
|
if output_type is not None:
|
|
1559
1559
|
pydantic_agent = self._create_agent_with_output_type(output_type)
|
|
1560
1560
|
|
|
1561
|
-
# Handle claude-code
|
|
1562
|
-
from code_puppy.model_utils import
|
|
1561
|
+
# Handle claude-code, chatgpt-codex, and antigravity models: prepend system prompt to first user message
|
|
1562
|
+
from code_puppy.model_utils import (
|
|
1563
|
+
is_antigravity_model,
|
|
1564
|
+
is_claude_code_model,
|
|
1565
|
+
)
|
|
1563
1566
|
|
|
1564
|
-
if is_claude_code_model(self.get_model_name()) or
|
|
1567
|
+
if is_claude_code_model(self.get_model_name()) or is_antigravity_model(
|
|
1565
1568
|
self.get_model_name()
|
|
1566
1569
|
):
|
|
1567
1570
|
if len(self.get_message_history()) == 0:
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Event stream handler for processing streaming events from agent runs."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
3
5
|
from collections.abc import AsyncIterable
|
|
4
6
|
from typing import Any, Optional
|
|
5
7
|
|
|
@@ -16,8 +18,35 @@ from rich.console import Console
|
|
|
16
18
|
from rich.markup import escape
|
|
17
19
|
from rich.text import Text
|
|
18
20
|
|
|
19
|
-
from code_puppy.config import get_banner_color
|
|
21
|
+
from code_puppy.config import get_banner_color, get_subagent_verbose
|
|
20
22
|
from code_puppy.messaging.spinner import pause_all_spinners, resume_all_spinners
|
|
23
|
+
from code_puppy.tools.subagent_context import is_subagent
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _fire_stream_event(event_type: str, event_data: Any) -> None:
|
|
29
|
+
"""Fire a stream event callback asynchronously (non-blocking).
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
event_type: Type of the event (e.g., 'part_start', 'part_delta', 'part_end')
|
|
33
|
+
event_data: Data associated with the event
|
|
34
|
+
"""
|
|
35
|
+
try:
|
|
36
|
+
from code_puppy import callbacks
|
|
37
|
+
from code_puppy.messaging import get_session_context
|
|
38
|
+
|
|
39
|
+
agent_session_id = get_session_context()
|
|
40
|
+
|
|
41
|
+
# Use create_task to fire callback without blocking
|
|
42
|
+
asyncio.create_task(
|
|
43
|
+
callbacks.on_stream_event(event_type, event_data, agent_session_id)
|
|
44
|
+
)
|
|
45
|
+
except ImportError:
|
|
46
|
+
logger.debug("callbacks or messaging module not available for stream event")
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.debug(f"Error firing stream event callback: {e}")
|
|
49
|
+
|
|
21
50
|
|
|
22
51
|
# Module-level console for streaming output
|
|
23
52
|
# Set via set_streaming_console() to share console with spinner
|
|
@@ -47,6 +76,15 @@ def get_streaming_console() -> Console:
|
|
|
47
76
|
return Console()
|
|
48
77
|
|
|
49
78
|
|
|
79
|
+
def _should_suppress_output() -> bool:
|
|
80
|
+
"""Check if sub-agent output should be suppressed.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if we're in a sub-agent context and verbose mode is disabled.
|
|
84
|
+
"""
|
|
85
|
+
return is_subagent() and not get_subagent_verbose()
|
|
86
|
+
|
|
87
|
+
|
|
50
88
|
async def event_stream_handler(
|
|
51
89
|
ctx: RunContext,
|
|
52
90
|
events: AsyncIterable[Any],
|
|
@@ -60,6 +98,12 @@ async def event_stream_handler(
|
|
|
60
98
|
ctx: The run context.
|
|
61
99
|
events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
|
|
62
100
|
"""
|
|
101
|
+
# If we're in a sub-agent and verbose mode is disabled, silently consume events
|
|
102
|
+
if _should_suppress_output():
|
|
103
|
+
async for _ in events:
|
|
104
|
+
pass # Just consume events without rendering
|
|
105
|
+
return
|
|
106
|
+
|
|
63
107
|
import time
|
|
64
108
|
|
|
65
109
|
from termflow import Parser as TermflowParser
|
|
@@ -75,6 +119,7 @@ async def event_stream_handler(
|
|
|
75
119
|
tool_parts: set[int] = set() # Track which parts are tool calls
|
|
76
120
|
banner_printed: set[int] = set() # Track if banner was already printed
|
|
77
121
|
token_count: dict[int, int] = {} # Track token count per text/tool part
|
|
122
|
+
tool_names: dict[int, str] = {} # Track tool name per tool part index
|
|
78
123
|
did_stream_anything = False # Track if we streamed any content
|
|
79
124
|
|
|
80
125
|
# Termflow streaming state for text parts
|
|
@@ -121,6 +166,16 @@ async def event_stream_handler(
|
|
|
121
166
|
async for event in events:
|
|
122
167
|
# PartStartEvent - register the part but defer banner until content arrives
|
|
123
168
|
if isinstance(event, PartStartEvent):
|
|
169
|
+
# Fire stream event callback for part_start
|
|
170
|
+
_fire_stream_event(
|
|
171
|
+
"part_start",
|
|
172
|
+
{
|
|
173
|
+
"index": event.index,
|
|
174
|
+
"part_type": type(event.part).__name__,
|
|
175
|
+
"part": event.part,
|
|
176
|
+
},
|
|
177
|
+
)
|
|
178
|
+
|
|
124
179
|
part = event.part
|
|
125
180
|
if isinstance(part, ThinkingPart):
|
|
126
181
|
streaming_parts.add(event.index)
|
|
@@ -149,6 +204,8 @@ async def event_stream_handler(
|
|
|
149
204
|
streaming_parts.add(event.index)
|
|
150
205
|
tool_parts.add(event.index)
|
|
151
206
|
token_count[event.index] = 0 # Initialize token counter
|
|
207
|
+
# Capture tool name from the start event
|
|
208
|
+
tool_names[event.index] = part.tool_name or ""
|
|
152
209
|
# Track tool name for display
|
|
153
210
|
banner_printed.add(
|
|
154
211
|
event.index
|
|
@@ -156,6 +213,16 @@ async def event_stream_handler(
|
|
|
156
213
|
|
|
157
214
|
# PartDeltaEvent - stream the content as it arrives
|
|
158
215
|
elif isinstance(event, PartDeltaEvent):
|
|
216
|
+
# Fire stream event callback for part_delta
|
|
217
|
+
_fire_stream_event(
|
|
218
|
+
"part_delta",
|
|
219
|
+
{
|
|
220
|
+
"index": event.index,
|
|
221
|
+
"delta_type": type(event.delta).__name__,
|
|
222
|
+
"delta": event.delta,
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
|
|
159
226
|
if event.index in streaming_parts:
|
|
160
227
|
delta = event.delta
|
|
161
228
|
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
@@ -189,25 +256,50 @@ async def event_stream_handler(
|
|
|
189
256
|
escaped = escape(delta.content_delta)
|
|
190
257
|
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
191
258
|
elif isinstance(delta, ToolCallPartDelta):
|
|
192
|
-
# For tool calls,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
259
|
+
# For tool calls, estimate tokens from args_delta content
|
|
260
|
+
# args_delta contains the streaming JSON arguments
|
|
261
|
+
args_delta = getattr(delta, "args_delta", "") or ""
|
|
262
|
+
if args_delta:
|
|
263
|
+
# Rough estimate: 4 chars ≈ 1 token (same heuristic as subagent_stream_handler)
|
|
264
|
+
estimated_tokens = max(1, len(args_delta) // 4)
|
|
265
|
+
token_count[event.index] += estimated_tokens
|
|
266
|
+
else:
|
|
267
|
+
# Even empty deltas count as activity
|
|
268
|
+
token_count[event.index] += 1
|
|
269
|
+
|
|
270
|
+
# Update tool name if delta provides more of it
|
|
271
|
+
tool_name_delta = getattr(delta, "tool_name_delta", "") or ""
|
|
272
|
+
if tool_name_delta:
|
|
273
|
+
tool_names[event.index] = (
|
|
274
|
+
tool_names.get(event.index, "") + tool_name_delta
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Use stored tool name for display
|
|
278
|
+
tool_name = tool_names.get(event.index, "")
|
|
196
279
|
count = token_count[event.index]
|
|
197
280
|
# Display with tool wrench icon and tool name
|
|
198
281
|
if tool_name:
|
|
199
282
|
console.print(
|
|
200
|
-
f" \U0001f527 Calling {tool_name}... {count}
|
|
283
|
+
f" \U0001f527 Calling {tool_name}... {count} token(s) ",
|
|
201
284
|
end="\r",
|
|
202
285
|
)
|
|
203
286
|
else:
|
|
204
287
|
console.print(
|
|
205
|
-
f" \U0001f527 Calling tool... {count}
|
|
288
|
+
f" \U0001f527 Calling tool... {count} token(s) ",
|
|
206
289
|
end="\r",
|
|
207
290
|
)
|
|
208
291
|
|
|
209
292
|
# PartEndEvent - finish the streaming with a newline
|
|
210
293
|
elif isinstance(event, PartEndEvent):
|
|
294
|
+
# Fire stream event callback for part_end
|
|
295
|
+
_fire_stream_event(
|
|
296
|
+
"part_end",
|
|
297
|
+
{
|
|
298
|
+
"index": event.index,
|
|
299
|
+
"next_part_kind": getattr(event, "next_part_kind", None),
|
|
300
|
+
},
|
|
301
|
+
)
|
|
302
|
+
|
|
211
303
|
if event.index in streaming_parts:
|
|
212
304
|
# For text parts, finalize termflow rendering
|
|
213
305
|
if event.index in text_parts:
|
|
@@ -238,8 +330,9 @@ async def event_stream_handler(
|
|
|
238
330
|
elif event.index in banner_printed:
|
|
239
331
|
console.print() # Final newline after streaming
|
|
240
332
|
|
|
241
|
-
# Clean up token count
|
|
333
|
+
# Clean up token count and tool names
|
|
242
334
|
token_count.pop(event.index, None)
|
|
335
|
+
tool_names.pop(event.index, None)
|
|
243
336
|
# Clean up all tracking sets
|
|
244
337
|
streaming_parts.discard(event.index)
|
|
245
338
|
thinking_parts.discard(event.index)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""The Pack - Specialized sub-agents coordinated by Pack Leader 🐺
|
|
2
|
+
|
|
3
|
+
This package contains the specialized agents that work together under
|
|
4
|
+
Pack Leader's coordination for parallel multi-agent workflows:
|
|
5
|
+
|
|
6
|
+
- **Bloodhound** 🐕🦺 - Issue tracking specialist (bd only)
|
|
7
|
+
- **Terrier** 🐕 - Worktree management (git worktree from base branch)
|
|
8
|
+
- **Husky** 🐺 - Task execution (coding work in worktrees)
|
|
9
|
+
- **Shepherd** 🐕 - Code review critic (quality gatekeeper)
|
|
10
|
+
- **Watchdog** 🐕🦺 - QA critic (tests, coverage, quality)
|
|
11
|
+
- **Retriever** 🦮 - Local branch merging (git merge to base branch)
|
|
12
|
+
|
|
13
|
+
All work happens locally - no GitHub PRs or remote pushes.
|
|
14
|
+
Everything merges to a declared base branch.
|
|
15
|
+
|
|
16
|
+
Each agent is designed to do one thing well, following the Unix philosophy.
|
|
17
|
+
Pack Leader orchestrates them to execute complex parallel workflows.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .bloodhound import BloodhoundAgent
|
|
21
|
+
from .husky import HuskyAgent
|
|
22
|
+
from .retriever import RetrieverAgent
|
|
23
|
+
from .shepherd import ShepherdAgent
|
|
24
|
+
from .terrier import TerrierAgent
|
|
25
|
+
from .watchdog import WatchdogAgent
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"BloodhoundAgent",
|
|
29
|
+
"TerrierAgent",
|
|
30
|
+
"RetrieverAgent",
|
|
31
|
+
"HuskyAgent",
|
|
32
|
+
"ShepherdAgent",
|
|
33
|
+
"WatchdogAgent",
|
|
34
|
+
]
|