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:
@@ -340,37 +340,6 @@ func TestExecuteTool_Unknown(t *testing.T) {
|
||||
mustErr(t, res, "unknown tool")
|
||||
}
|
||||
|
||||
// --- extractActions ---
|
||||
|
||||
func TestExtractActions(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
stripOK string
|
||||
found bool
|
||||
}{
|
||||
{"with block", "Hola\n<actions>[{\"tool\":\"x\"}]</actions>\nHecho", `[{"tool":"x"}]`, "Hola\nHecho", true},
|
||||
{"only block", "<actions>[]</actions>", `[]`, "", true},
|
||||
{"no block", "Solo texto", "", "Solo texto", false},
|
||||
{"unclosed", "<actions>foo", "", "<actions>foo", false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
got, stripped, found := extractActions(c.in)
|
||||
if found != c.found {
|
||||
t.Fatalf("found = %v want %v", found, c.found)
|
||||
}
|
||||
if got != c.want {
|
||||
t.Fatalf("got %q want %q", got, c.want)
|
||||
}
|
||||
if stripped != c.stripOK {
|
||||
t.Fatalf("stripped = %q want %q", stripped, c.stripOK)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- chat logger ---
|
||||
|
||||
func TestChatLogger_AppendsJSONLines(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user