mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-18 13:42:21 +00:00
90 lines
4.0 KiB
Markdown
90 lines
4.0 KiB
Markdown
---
|
|
llm: google/gemini-3.1-pro-preview
|
|
status: draft
|
|
---
|
|
|
|
# Multi-model git worktree support plan
|
|
|
|
## Goal
|
|
|
|
Enhance the `multi-model` tool to:
|
|
1. Create a unique git worktree for each session + modelName.
|
|
2. Store worktree folders inside `~/.local/share/opencode/multi-model/<sessionName>/<windowName>`.
|
|
3. Initialize `opencode` with that specific worktree to avoid conflicts.
|
|
|
|
## Implementation Details
|
|
|
|
- **Target file:** `.opencode/tools/multi-model.ts`
|
|
- **Path Sanitization:**
|
|
- Sanitize `sessionName` (e.g., replace non-alphanumeric with hyphens) to ensure safe paths and branch names. `windowName` is already sanitized by existing code.
|
|
- **Worktree Path Construction:**
|
|
- `worktreePath`: `~/.local/share/opencode/multi-model/<sanitizedSessionName>/<windowName>`.
|
|
- **Git Repository Check:**
|
|
- Run `git rev-parse --is-inside-work-tree` at the start of `execute`.
|
|
- If false, gracefully fail with an error `multi-model requires a git repository`.
|
|
- **Creating Worktrees:**
|
|
- Iterate through `windowPlans` to create tmux windows:
|
|
- Generate `worktreePath`.
|
|
- Generate `branchName` as `opencode/<sanitizedSessionName>/<windowName>`.
|
|
- Handle edge cases (Fail Fast approach):
|
|
- Run `fs.existsSync(worktreePath)` to check if the path exists. If it does, fail fast with an error indicating the path collision.
|
|
- Run `git show-ref --verify --quiet refs/heads/<branchName>` to check if the branch exists. If it does, fail fast with an error indicating the branch collision.
|
|
- Run `git worktree add -b <branchName> <worktreePath>` to create the new branch and worktree.
|
|
- In `tmux new-session` and `tmux new-window`, pass the newly created `worktreePath` to the `-c` argument instead of `context.directory`.
|
|
- **Cleanup Strategy:**
|
|
- On launch failure: If `tmux` or `opencode` launch fails *after* worktree creation, run `git worktree remove -f <worktreePath>` and `git branch -D <branchName>`. Keep track of successful and failed cleanups (undos) and report them in the tool output if the undo process fails.
|
|
- On success: Output a command snippet to the user on how to clean up the session's worktrees and branches when done:
|
|
`rm -rf ~/.local/share/opencode/multi-model/<sanitizedSessionName> && git worktree prune && git branch -d $(git branch --format='%(refname:short)' --list 'opencode/<sanitizedSessionName>/*')`
|
|
|
|
## Non-obvious Code Blocks
|
|
|
|
**Path Sanitization & Generation (Example):**
|
|
```ts
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import * as fs from "node:fs";
|
|
|
|
function sanitizeName(name: string): string {
|
|
return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
}
|
|
|
|
function getWorktreePath(safeSession: string, windowName: string): string {
|
|
return path.join(os.homedir(), ".local/share/opencode/multi-model", safeSession, windowName);
|
|
}
|
|
```
|
|
|
|
**Creating Worktree (Example):**
|
|
```ts
|
|
const safeSession = sanitizeName(sessionName);
|
|
const branchName = `opencode/${safeSession}/${plan.windowName}`;
|
|
|
|
if (fs.existsSync(worktreePath)) {
|
|
return `Error: Worktree path already exists at ${worktreePath}. Clean it up first.`;
|
|
}
|
|
|
|
const branchExists = await runCommand(["git", "show-ref", "--verify", "--quiet", `refs/heads/${branchName}`]);
|
|
if (branchExists.ok) {
|
|
return `Error: Branch '${branchName}' already exists.`;
|
|
}
|
|
|
|
const worktreeCmd = ["git", "worktree", "add", "-b", branchName, worktreePath];
|
|
const result = await runCommand(worktreeCmd);
|
|
|
|
if (!result.ok) {
|
|
return `Error: Failed to create worktree for ${plan.model}: ${result.stderr}`;
|
|
}
|
|
```
|
|
|
|
**Cleanup on Failure (Example):**
|
|
```ts
|
|
async function undoWorktree(worktreePath: string, branchName: string): Promise<string[]> {
|
|
const errors: string[] = [];
|
|
const removeRes = await runCommand(["git", "worktree", "remove", "-f", worktreePath]);
|
|
if (!removeRes.ok) errors.push(`Failed to remove worktree ${worktreePath}: ${removeRes.stderr}`);
|
|
|
|
const branchRes = await runCommand(["git", "branch", "-D", branchName]);
|
|
if (!branchRes.ok) errors.push(`Failed to delete branch ${branchName}: ${branchRes.stderr}`);
|
|
return errors;
|
|
}
|
|
```
|