feat: Major UI improvements and SSH-only mode
- Tool rendering: Unified tool_use/tool_result cards with collapsible results - Special rendering for WebSearch, WebFetch, Task, Write tools - File upload support with drag & drop - Permission dialog for tool approvals - Status bar with session stats and permission mode toggle - SSH-only mode: Removed local container execution - Host switching disabled during active session with visual indicator - Directory browser: Browse remote directories via SSH - Recent directories dropdown with localStorage persistence - Follow-up messages during generation - Improved scroll behavior with "back to bottom" button 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
129
PLAN-MODE-BUG-FIX.md
Normal file
129
PLAN-MODE-BUG-FIX.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Plan Mode Bug Fix - Claude WebUI
|
||||
|
||||
**Datum:** 2025-12-16
|
||||
**Status:** DEPLOYED
|
||||
|
||||
## Das Problem
|
||||
|
||||
Beim Plan Mode Approval im WebUI kam ein API Error:
|
||||
```
|
||||
API Error: 400 {"type":"error","error":{"type":"invalid_request_error",
|
||||
"message":"messages.98.content.0: unexpected tool_use_id found in tool_result blocks:
|
||||
toolu_01R6yjX7gqduKgMAuYuPzMeG. Each tool_result block must have a corresponding
|
||||
tool_use block in the previous message."}}
|
||||
```
|
||||
|
||||
### Ursache
|
||||
- Wenn das WebUI ein Plan Approval sendet, muss die `tool_use_id` mit einem vorherigen `tool_use` Block matchen
|
||||
- Bei **Message History Compaction** gehen die ursprünglichen `tool_use` Blöcke verloren
|
||||
- Das WebUI sendete `tool_result` für Permissions, aber die zugehörigen `tool_use` IDs existierten nicht mehr
|
||||
|
||||
## Die Lösung
|
||||
|
||||
### 1. Scope-Problem gefixt
|
||||
`setPermissionModeViaControl` wurde aus `startClaudeSession` in den äußeren WebSocket-Scope verschoben:
|
||||
|
||||
**Datei:** `backend/server.js` (Zeile ~307)
|
||||
```javascript
|
||||
// Helper to set permission mode via control protocol (needs claudeProcess)
|
||||
const setPermissionModeViaControl = (mode) => {
|
||||
if (!claudeProcess) return;
|
||||
const modeRequestId = generateRequestId();
|
||||
const modeRequest = {
|
||||
type: 'control_request',
|
||||
request_id: modeRequestId,
|
||||
request: {
|
||||
subtype: 'set_permission_mode',
|
||||
mode: mode
|
||||
}
|
||||
};
|
||||
console.log(`[${sessionId}] Auto-switching permission mode to: ${mode}`);
|
||||
claudeProcess.stdin.write(JSON.stringify(modeRequest) + '\n');
|
||||
pendingControlRequests.set(modeRequestId, { type: 'set_permission_mode', mode });
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Permission Responses als `control_response` statt `tool_result`
|
||||
|
||||
**Datei:** `backend/server.js` (Zeile ~641)
|
||||
```javascript
|
||||
// All permission responses (including ExitPlanMode/plan approval) use control_response
|
||||
// This avoids tool_result issues when message history is compacted
|
||||
const permResponse = {
|
||||
type: 'control_response',
|
||||
response: {
|
||||
subtype: 'success',
|
||||
request_id: data.requestId,
|
||||
response: data.allow
|
||||
? { behavior: 'allow', updated_input: data.updatedInput || null }
|
||||
: { behavior: 'deny', message: data.message || 'User denied permission' }
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Plan Mode State Tracking
|
||||
|
||||
**Datei:** `backend/server.js` (Zeile ~302)
|
||||
```javascript
|
||||
let inPlanMode = false; // Track if we're in plan mode
|
||||
```
|
||||
|
||||
Bei `ExitPlanMode` Permission Request (Zeile ~441):
|
||||
```javascript
|
||||
// Track plan mode state for ExitPlanMode
|
||||
if (isPlanApproval && !inPlanMode) {
|
||||
savedPermissionMode = currentPermissionMode;
|
||||
inPlanMode = true;
|
||||
console.log(`[${sessionId}] Entering plan mode, saved mode: ${savedPermissionMode}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Nach Code-Änderungen muss das Backend-Image neu gebaut werden:
|
||||
|
||||
```bash
|
||||
cd /home/sumdex/projects/claude-web-ui
|
||||
docker compose build backend
|
||||
docker compose up -d backend
|
||||
```
|
||||
|
||||
**Wichtig:** Der Backend-Code ist NICHT als Volume gemountet, sondern im Image gebaut!
|
||||
|
||||
### Verifizieren dass der Fix deployed ist:
|
||||
```bash
|
||||
docker exec claude-webui-backend cat /app/server.js | grep "setPermissionModeViaControl"
|
||||
```
|
||||
|
||||
## Relevante Dateien
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `backend/server.js` | Haupt-Backend, WebSocket Handler, Permission Handling |
|
||||
| `frontend/src/hooks/useClaudeSession.js` | Frontend Hook für Claude Session |
|
||||
| `frontend/src/components/PermissionDialog.jsx` | Permission UI Dialog |
|
||||
| `docker-compose.yml` | Container Konfiguration |
|
||||
|
||||
## Session-Dump
|
||||
|
||||
Falls weitere Analyse nötig:
|
||||
```
|
||||
/home/sumdex/projects/webui-workspace/session-dump-20251216-164148.jsonl
|
||||
```
|
||||
|
||||
## Technischer Hintergrund
|
||||
|
||||
### Control Protocol vs Tool Results
|
||||
- **tool_result:** Muss immer mit einem `tool_use` in der vorherigen Nachricht matchen
|
||||
- **control_response:** Hat diese Einschränkung nicht, funktioniert unabhängig von Message History
|
||||
|
||||
### Message Compaction
|
||||
Claude Code kompaktiert die Message History um Context zu sparen. Dabei können `tool_use` Blöcke entfernt werden, was zu ID-Mismatches führt wenn später `tool_result` mit diesen IDs gesendet wird.
|
||||
|
||||
## Test
|
||||
|
||||
1. WebUI öffnen: http://100.105.142.13:3000
|
||||
2. Neue Session starten
|
||||
3. Eine Anfrage stellen die Plan Mode triggert (z.B. "Implementiere Feature X")
|
||||
4. Plan Mode Approval im UI bestätigen
|
||||
5. Es sollte KEIN `unexpected tool_use_id` Error mehr kommen
|
||||
Reference in New Issue
Block a user