1import {
2 WorktreeMcpFailure,
3 OrchestratorMcpFailure,
4 VcsListRefsInput,
5 VcsListRefsResult,
6 WorktreeMcpHandoffInput,
7 WorktreeMcpHandoffResult,
8 WorktreeMcpStatusResult,
9 ThreadId,
10} from "@t3tools/contracts";
11import * as Schema from "effect/Schema";
12import * as GitWorkflowService from "../../../git/GitWorkflowService.ts";
13import * as ProjectService from "../../../project/ProjectService.ts";
14import * as ThreadManagementService from "../../../orchestration-v2/ThreadManagementService.ts";
15import { Tool, Toolkit } from "effect/ai";
16
17import * as McpInvocationContext from "../../McpInvocationContext.ts";
18import * as WorktreeMcpService from "../../WorktreeMcpService.ts";
19
20const dependencies = [
21 McpInvocationContext.McpInvocationContext,
22 ThreadManagementService.ThreadManagementService,
23 WorktreeMcpService.WorktreeMcpService,
24];
25
26/** What handoff and status fail with, including the access gate's refusal. */
27const WorktreeToolFailure = Schema.Union([WorktreeMcpFailure, OrchestratorMcpFailure]);
28
29const WorktreeHandoffTool = Tool.make("t3_worktree_handoff", {
30 description:
31 "Needs an agent running inside a T3 thread. Move this agent thread into a new git worktree. To launch a separate agent already bound to a new or existing worktree, use t3_thread_launch with workspaceStrategy instead. Creates the worktree branch (optionally from origin), re-points the thread at the worktree, and by default runs the project's setup script there. Changing the workspace detaches the live provider session, so the current turn ends shortly after the handoff is recorded; call this as the last action of the turn. To keep working after the handoff, pass continuationPrompt with the remaining work: it is queued as the thread's next message and starts a new turn inside the worktree with the conversation preserved. Without it the thread stays idle until the next message. The worktree is not removed automatically when the thread is deleted. Fails if the thread is already attached to a worktree.",
32 parameters: WorktreeMcpHandoffInput,
33 success: WorktreeMcpHandoffResult,
34 failure: WorktreeToolFailure,
35 failureMode: "return",
36 dependencies,
37})
38 .annotate(Tool.Title, "Hand off thread to a git worktree")
39 .annotate(Tool.Readonly, false)
40 .annotate(Tool.Destructive, true)
41 .annotate(Tool.Idempotent, false)
42 .annotate(Tool.OpenWorld, true);
43
44const WorktreeStatusTool = Tool.make("t3_worktree_status", {
45 description:
46 "Needs an agent running inside a T3 thread. Report this agent thread's worktree binding: whether it is attached to a git worktree, the worktree path and branch, the project's main workspace root, and the server default for t3_worktree_handoff's startFromOrigin. Call this before t3_worktree_handoff to check whether a handoff is possible or has already happened.",
47 // No `parameters`: Tool.make defaults to Tool.EmptyParams, which serializes
48 // to a top-level `type: "object"` JSON Schema. An explicit empty
49 // Schema.Struct({}) serializes to `anyOf: [object, array]`, which is not a
50 // valid MCP tool input schema and makes clients reject the whole server.
51 success: WorktreeMcpStatusResult,
52 failure: WorktreeToolFailure,
53 failureMode: "return",
54 dependencies,
55})
56 .annotate(Tool.Title, "Get thread worktree status")
57 .annotate(Tool.Readonly, true)
58 .annotate(Tool.Destructive, false)
59 .annotate(Tool.Idempotent, true)
60 .annotate(Tool.OpenWorld, false);
61
62const WorktreeListTool = Tool.make("t3_worktree_list", {
63 description:
64 "List branch refs and their associated checkout paths for a thread's workspace (omit threadId for this thread) using the app's ref inventory. Detached worktrees without a branch are not included. Use t3_worktree_status for the thread binding and t3_worktree_handoff to create a new worktree.",
65 parameters: Schema.Struct({
66 threadId: Schema.optional(ThreadId),
67 query: VcsListRefsInput.fields.query,
68 cursor: VcsListRefsInput.fields.cursor,
69 limit: VcsListRefsInput.fields.limit,
70 refKind: VcsListRefsInput.fields.refKind,
71 includeMatchingRemoteRefs: VcsListRefsInput.fields.includeMatchingRemoteRefs,
72 }),
73 success: VcsListRefsResult,
74 failure: OrchestratorMcpFailure,
75 failureMode: "return",
76 dependencies: [
77 McpInvocationContext.McpInvocationContext,
78 ThreadManagementService.ThreadManagementService,
79 ProjectService.ProjectService,
80 GitWorkflowService.GitWorkflowService,
81 ],
82})
83 .annotate(Tool.Readonly, true)
84 .annotate(Tool.Destructive, false);
85export const WorktreeToolkit = Toolkit.make(
86 WorktreeHandoffTool,
87 WorktreeStatusTool,
88 WorktreeListTool,
89);