diff --git a/apps/backend/internal/agent/agents/managed_npm_runtime_versions.json b/apps/backend/internal/agent/agents/managed_npm_runtime_versions.json index 464cd74f8a..ec737fd247 100644 --- a/apps/backend/internal/agent/agents/managed_npm_runtime_versions.json +++ b/apps/backend/internal/agent/agents/managed_npm_runtime_versions.json @@ -1,8 +1,8 @@ { - "@agentclientprotocol/claude-agent-acp": "0.75.1", - "@agentclientprotocol/codex-acp": "1.10.0", + "@agentclientprotocol/claude-agent-acp": "0.76.0", + "@agentclientprotocol/codex-acp": "1.11.0", "@github/copilot": "1.0.83", - "@google/gemini-cli": "0.58.0", - "opencode-ai": "1.18.29", + "@google/gemini-cli": "0.59.0", + "opencode-ai": "1.18.30", "pi-acp": "0.0.33" } diff --git a/apps/backend/internal/agentctl/server/api/processes_test.go b/apps/backend/internal/agentctl/server/api/processes_test.go index a637528519..2c4b6aef58 100644 --- a/apps/backend/internal/agentctl/server/api/processes_test.go +++ b/apps/backend/internal/agentctl/server/api/processes_test.go @@ -311,9 +311,9 @@ func TestHandleStopProcess_UnknownIDIsIdempotentSuccess(t *testing.T) { } // TestHandleStopProcess_RetiresRunningProcess asserts the stop reaches a -// long-running command: the call returns success and the process is retired -// from both the per-session list and the per-id fetch. A `sleep 120` that was -// merely marked stopped would still be listed. +// long-running command: the call returns success and the process is eventually +// retired from both the per-session list and the per-id fetch. Stop signals the +// process before its background waiter completes the final reap. func TestHandleStopProcess_RetiresRunningProcess(t *testing.T) { srv := newTestServer(t) id := startTestProcess(t, srv, "session-d", "sleep 120") @@ -339,6 +339,8 @@ func TestHandleStopProcess_RetiresRunningProcess(t *testing.T) { t.Fatalf("stop reported failure: %+v", body) } + awaitProcessRetired(t, srv, id) + after := processRequest(t, srv, http.MethodGet, "/api/v1/processes?session_id=session-d", nil) var remaining []process.ProcessInfo if err := json.Unmarshal(after.Body.Bytes(), &remaining); err != nil { @@ -347,9 +349,6 @@ func TestHandleStopProcess_RetiresRunningProcess(t *testing.T) { if len(remaining) != 0 { t.Errorf("list after stop = %+v, want it emptied", remaining) } - if got := processRequest(t, srv, http.MethodGet, "/api/v1/processes/"+id, nil); got.Code != http.StatusNotFound { - t.Errorf("get after stop status = %d, want 404 (body %s)", got.Code, got.Body.String()) - } } func startTestProcess(t *testing.T, srv *Server, sessionID, command string) string { diff --git a/apps/web/components/task/chat/tiptap-suggestion.test.ts b/apps/web/components/task/chat/tiptap-suggestion.test.ts index 205fd23aeb..b7586947cc 100644 --- a/apps/web/components/task/chat/tiptap-suggestion.test.ts +++ b/apps/web/components/task/chat/tiptap-suggestion.test.ts @@ -7,7 +7,7 @@ import { handleEntityReferenceMenuKeyDown, isEntityReferenceQueryAllowed, } from "./tiptap-entity-reference-suggestion"; -import { createMentionSuggestion } from "./tiptap-suggestion"; +import { createMentionSuggestion, MentionSuggestionPluginKey } from "./tiptap-suggestion"; import * as entityReferenceSuggestions from "./tiptap-entity-reference-suggestion"; function createSuggestionPositioningProps() { @@ -306,6 +306,29 @@ describe("entity reference suggestion lifecycle", () => { }); describe("createMentionSuggestion", () => { + it("exits TipTap suggestion state when Escape closes the menu", () => { + const suggestion = createMentionSuggestion( + { getItems: vi.fn().mockResolvedValue([]), onSelect: vi.fn() }, + vi.fn(), + vi.fn(), + ); + const lifecycle = suggestion.render?.(); + const transaction = { setMeta: vi.fn(() => ({ id: "exit" })) }; + const dispatch = vi.fn(); + + const handled = lifecycle?.onKeyDown?.({ + view: { state: { tr: transaction }, dispatch } as never, + event: new KeyboardEvent("keydown", { key: "Escape" }), + range: { from: 1, to: 2 }, + }); + + expect(handled).toBe(true); + expect(transaction.setMeta).toHaveBeenCalledWith(MentionSuggestionPluginKey, { + exit: true, + }); + expect(dispatch).toHaveBeenCalledWith({ id: "exit" }); + }); + it("keeps Kandev task discovery in the @ menu", async () => { const file: MentionItem = { id: "src/app.ts", diff --git a/apps/web/components/task/chat/tiptap-suggestion.tsx b/apps/web/components/task/chat/tiptap-suggestion.tsx index d1e5659256..efca880d5a 100644 --- a/apps/web/components/task/chat/tiptap-suggestion.tsx +++ b/apps/web/components/task/chat/tiptap-suggestion.tsx @@ -1,6 +1,7 @@ "use client"; import { PluginKey } from "@tiptap/pm/state"; +import { exitSuggestion } from "@tiptap/suggestion"; import type { SuggestionOptions, SuggestionProps, @@ -122,6 +123,7 @@ export function createMentionSuggestion( // keypress. kd.event.stopPropagation(); setMenuState(EMPTY_MENTION_STATE); + exitSuggestion(kd.view, MentionSuggestionPluginKey); return true; } return onKeyDown(kd.event); diff --git a/apps/web/e2e/fixtures/kubernetes-tools.ts b/apps/web/e2e/fixtures/kubernetes-tools.ts index 703d11eae5..22a90785bf 100644 --- a/apps/web/e2e/fixtures/kubernetes-tools.ts +++ b/apps/web/e2e/fixtures/kubernetes-tools.ts @@ -849,7 +849,9 @@ export async function provisionKubernetesCluster( try { const port = await waitForPortForward(proc); const baseUrl = `http://127.0.0.1:${port}`; - await waitForHealth(`${baseUrl}/health`, 30_000, proc); + // /health only proves that the listener is bound; /ready gates API + // requests until the in-cluster backend has finished wiring routes. + await waitForHealth(`${baseUrl}/ready`, 30_000, proc); const context: InClusterBackend = { baseUrl, frontendUrl: baseUrl,