feat(chat): MCP server + WebSocket streaming, replace XML actions
- Backend: kanban binary gana subcomando `kanban mcp` que actua como MCP
server via stdio. Tools = mismo set que executeTool (14). El subprocess
llama de vuelta al backend via /api/tool/{name} con token interno.
- Backend: nuevo endpoint POST /api/tool/{name} (auth: X-Internal-Token).
- Backend: chat.go refactor — POST /api/chat reemplazado por GET
/api/chat/ws (WebSocket). Lanza claude -p con --output-format stream-json
--verbose --mcp-config y reenvia eventos (delta/tool_use/tool_result/
result/done/error) como mensajes JSON al cliente.
- Backend: usa funciones nuevas del registry claude_stream_go_core (spawn
+ parser NDJSON) y mcp_server_stdio_go_infra (JSON-RPC stdio).
- Frontend: streamChat sobre WebSocket. ChatPanel renderiza deltas en
vivo, chips para tool_use, badges teal/red para tool_result.
- Borrado: extractActions, actionsBlockMarker, XML system prompt.
- Tests: 7 nuevos en backend (chat_ws_test.go + endpoint /api/tool).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -339,27 +339,6 @@ func toolFindCards(db *DB, input json.RawMessage) ToolResult {
|
||||
return okResult(out)
|
||||
}
|
||||
|
||||
// chatActionsRegex matches an <actions>...</actions> block (DOTALL mode).
|
||||
// Used by chat.go to extract tool invocations from the assistant's response.
|
||||
var actionsBlockMarker = struct{ Open, Close string }{Open: "<actions>", Close: "</actions>"}
|
||||
|
||||
func extractActions(text string) (jsonBlock string, stripped string, found bool) {
|
||||
openIdx := strings.Index(text, actionsBlockMarker.Open)
|
||||
if openIdx < 0 {
|
||||
return "", text, false
|
||||
}
|
||||
closeIdx := strings.Index(text[openIdx:], actionsBlockMarker.Close)
|
||||
if closeIdx < 0 {
|
||||
return "", text, false
|
||||
}
|
||||
closeIdx += openIdx
|
||||
jsonBlock = strings.TrimSpace(text[openIdx+len(actionsBlockMarker.Open) : closeIdx])
|
||||
before := strings.TrimRight(text[:openIdx], " \n\t")
|
||||
after := strings.TrimLeft(text[closeIdx+len(actionsBlockMarker.Close):], " \n\t")
|
||||
stripped = strings.TrimSpace(before + "\n" + after)
|
||||
return jsonBlock, stripped, true
|
||||
}
|
||||
|
||||
// validateToolName fails fast with clearer error than the dispatch's default.
|
||||
func validateToolName(name string) error {
|
||||
known := map[string]bool{
|
||||
|
||||
Reference in New Issue
Block a user