fix(02): revise plans based on checker feedback

Address 3 blockers and 1 warning:
- Clarify on_tool_use responsibility split (subprocess passes raw data, bot.py formats)
- Make verify step 6 concrete with code inspection pattern
- Add explicit subprocess auto-start pseudo-code with double-start guard
- Reframe must_haves truth from implementation detail to capability

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mikkel Georgsen 2026-02-04 19:07:32 +00:00
parent 36fabb41a6
commit 92318e79ab
2 changed files with 26 additions and 5 deletions

View file

@ -12,7 +12,7 @@ autonomous: true
must_haves:
truths:
- "Persistent Claude Code subprocess accepts multiple messages without respawning"
- "Stream-json events from Claude include tool_use events parseable for progress notifications"
- "Subprocess emits tool_use events (tool_name + tool_input dict) that bot.py can consume for progress notifications"
- "Long messages are split at smart boundaries without breaking MarkdownV2 code blocks"
- "MarkdownV2 special characters are properly escaped before sending to Telegram"
- "Typing indicator can be maintained for arbitrarily long operations via re-send loop"
@ -86,6 +86,7 @@ Refactor ClaudeSubprocess to maintain a single long-lived process per session us
3. **Add on_tool_use callback:** New callback `on_tool_use(tool_name: str, tool_input: dict)` for progress notifications.
In `_handle_stdout_line()`, parse `content_block_start` events with `type: "tool_use"` to extract tool name and input.
Stream-json emits these as separate events during assistant turns.
**Responsibility split:** `claude_subprocess.py` extracts `tool_name` (e.g. "Read", "Bash", "Edit") and passes the raw `tool_input` dict (e.g. `{"file_path": "/foo/bar"}`, `{"command": "ls -la"}`) directly to the callback. It does NOT format human-readable descriptions -- that is bot.py's job in Plan 02 (Part B, item 5). This keeps the subprocess layer format-agnostic.
4. **Update _handle_stdout_line():** Handle the stream-json event types from persistent mode:
- `assistant`: Extract text blocks, call on_output with final text
@ -127,7 +128,7 @@ Refactor ClaudeSubprocess to maintain a single long-lived process per session us
3. Verify on_tool_use callback parameter exists in __init__
4. Verify --input-format stream-json appears in the command construction
5. Verify stdin.write + drain pattern in send_message
6. Verify stdout reader does NOT exit on result events (stays alive for persistent process)
6. Verify `_read_stdout()` loop continues after result events: inspect code to confirm the loop only exits on empty readline (i.e. `line = await self._process.stdout.readline(); if not line: break`), NOT on receiving a result event. Result events should set `_busy = False` and call `on_complete` but NOT break the reader loop.
</verify>
<done>
ClaudeSubprocess maintains a persistent process that accepts NDJSON messages on stdin, emits stream-json events on stdout, routes tool_use events to on_tool_use callback, and tracks busy state via result events instead of process completion. No fresh process spawned per turn.

View file

@ -150,9 +150,29 @@ Overhaul the message handler to use typing indicators and message batching:
- On message: `await batcher.add_message(text)` instead of direct `subprocess.send_message()`
- Typing indicator starts immediately on first message, stops on Claude response
3. Subprocess auto-start:
- When no subprocess exists for active session, create ClaudeSubprocess and call `await subprocess.start()`
- Pass all 5 callbacks (on_output, on_error, on_complete, on_status, on_tool_use)
3. Subprocess auto-start (integrate into existing handle_message after session lookup, before batcher):
```python
# In handle_message(), after resolving active session:
session_id = session_manager.get_active_session(user_id)
# Get or create subprocess for this session (avoid double-start)
if session_id not in self.subprocesses or not self.subprocesses[session_id].is_alive:
callbacks = make_callbacks(bot, chat_id, stop_typing_event)
subprocess = ClaudeSubprocess(
session_dir=session_dir,
on_output=callbacks['on_output'],
on_error=callbacks['on_error'],
on_complete=callbacks['on_complete'],
on_status=callbacks['on_status'],
on_tool_use=callbacks['on_tool_use'],
)
await subprocess.start()
self.subprocesses[session_id] = subprocess
else:
subprocess = self.subprocesses[session_id]
```
- The `is_alive` check prevents double-start: only creates and starts if no subprocess exists for session or previous one died
- `self.subprocesses` is a dict[str, ClaudeSubprocess] stored on the handler/application context (same pattern as existing subprocess tracking in bot.py)
**Part D: Update handle_photo() and handle_document()**