From 54f6631c28a8b85f6f874e17822549faba916a38 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 09:38:06 +0000 Subject: feat(executor): per-task agent MCP server + token registry (Phase 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the agent-facing MCP transport foundation: a Registry that mints a per-task bearer token bound to a freshly built MCP server exposing the four agent tools (ask_user, report_summary, spawn_subtask, record_progress), and an HTTP handler (StreamableHTTP) that resolves the token to that server. The server never trusts an agent-supplied task ID — context comes from the token. The default storeChannel now buffers summary and question signals under a mutex (an MCP tool call lands on an HTTP-handler goroutine mid-run), exposing ReportedSummary/PendingQuestion. The pool flushes the buffered summary onto the execution after the run, replacing the runner's direct exec.Summary write and keeping the read race-free. ask_user follows the record-and-resume model: it buffers the question, returns ErrAgentBlocked, and the tool tells the agent to end its turn; the run blocks and resumes later via claude --resume (no live slot held). Tests cover registry lifecycle, in-memory tool dispatch, and HTTP end-to-end with bearer auth (valid token dispatches; invalid token rejected). Not yet wired into the runners or mounted on the API server — next increment. https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39 --- internal/executor/gemini_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'internal/executor/gemini_test.go') diff --git a/internal/executor/gemini_test.go b/internal/executor/gemini_test.go index c8f7422..a906f2c 100644 --- a/internal/executor/gemini_test.go +++ b/internal/executor/gemini_test.go @@ -125,7 +125,7 @@ func TestGeminiRunner_Run_InaccessibleProjectDir_ReturnsError(t *testing.T) { } exec := &storage.Execution{ID: "test-exec"} - err := r.Run(context.Background(), tk, exec, newStoreChannel(nil, tk.ID, exec)) + err := r.Run(context.Background(), tk, exec, newStoreChannel(nil, tk.ID)) if err == nil { t.Fatal("expected error for inaccessible project_dir, got nil") @@ -213,7 +213,7 @@ func TestGeminiRunner_Run_ProjectDir_RunsInSandbox(t *testing.T) { } e := &storage.Execution{ID: "sandbox-exec", TaskID: "task-1"} - if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID, e)); err != nil { + if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID)); err != nil { t.Fatalf("Run: %v", err) } @@ -261,7 +261,7 @@ fi } e := &storage.Execution{ID: "blocked-gemini-exec", TaskID: "task-1"} - err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID, e)) + err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID)) var blocked *BlockedError if !errors.As(err, &blocked) { @@ -301,7 +301,7 @@ func TestGeminiRunner_Run_ExecError_PreservesSandbox(t *testing.T) { } e := &storage.Execution{ID: "err-gemini-exec", TaskID: "task-1"} - err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID, e)) + err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID)) if err == nil { t.Fatal("expected error from failing gemini exit") } @@ -352,7 +352,7 @@ func TestGeminiRunner_Run_ResumeUsesStoredSandboxDir(t *testing.T) { SandboxDir: sandboxDir, } - if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID, e)); err != nil { + if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID)); err != nil { t.Fatalf("Run with preserved sandbox: %v", err) } @@ -400,7 +400,7 @@ func TestGeminiRunner_Run_StaleSandboxDir_ClonesAfresh(t *testing.T) { SandboxDir: staleSandbox, } - if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID, e)); err != nil { + if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID)); err != nil { t.Fatalf("Run with stale sandbox: %v", err) } @@ -438,7 +438,7 @@ func TestGeminiRunner_Run_NoProjectDir_SkipsSandbox(t *testing.T) { } e := &storage.Execution{ID: "no-pd-gemini", TaskID: "task-nopd"} - if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID, e)); err != nil { + if err := r.Run(context.Background(), tk, e, newStoreChannel(nil, tk.ID)); err != nil { t.Fatalf("Run without project_dir: %v", err) } if e.SandboxDir != "" { -- cgit v1.2.3