feat: implement worktree setup commands management

- Introduced `readFile` and `writeFile` methods in the FilesAPI for reading and writing files.
- Added `execCommands` method to execute shell commands in a specified directory.
- Implemented `runWorktreeSetupCommands` function to handle setup commands for worktrees.
- Created `OpenChamberConfig` service for managing project-specific configuration, including setup commands.
- Enhanced `useMultiRunStore` to save and execute setup commands during multi-run creation.
- Updated VSCode bridge to handle file read/write and command execution requests.
- Added server endpoints for reading and writing files, and executing shell commands.
This commit is contained in:
Bohdan Triapitsyn
2026-01-07 22:01:49 +02:00
parent b8463b787b
commit 4433888903
16 changed files with 1789 additions and 280 deletions
+37 -2
View File
@@ -2,7 +2,8 @@ import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import type { CreateMultiRunParams, CreateMultiRunResult } from '@/types/multirun';
import { opencodeClient } from '@/lib/opencode/client';
import { createWorktree } from '@/lib/git/worktreeService';
import { createWorktree, runWorktreeSetupCommands } from '@/lib/git/worktreeService';
import { saveWorktreeSetupCommands } from '@/lib/openchamberConfig';
import { checkIsGitRepository } from '@/lib/gitApi';
import { useSessionStore } from './sessionStore';
import { useDirectoryStore } from './useDirectoryStore';
@@ -100,7 +101,7 @@ export const useMultiRunStore = create<MultiRunStore>()(
createMultiRun: async (params: CreateMultiRunParams) => {
const groupName = params.name.trim();
const prompt = params.prompt.trim();
const { models, agent, files } = params;
const { models, agent, files, setupCommands } = params;
if (!groupName) {
set({ error: 'Group name is required' });
@@ -151,6 +152,8 @@ export const useMultiRunStore = create<MultiRunStore>()(
modelID: string;
}> = [];
const commandsToRun = setupCommands?.filter((cmd) => cmd.trim().length > 0) ?? [];
// Count occurrences of each model to handle duplicates
const modelCounts = new Map<string, number>();
for (const model of models) {
@@ -212,12 +215,21 @@ export const useMultiRunStore = create<MultiRunStore>()(
providerID: model.providerID,
modelID: model.modelID,
});
} catch (error) {
// Best-effort: allow partial success
console.warn('[MultiRun] Failed to create session:', error);
}
}
// Save setup commands to config if any were provided (for future worktree creation)
const commandsToSave = setupCommands?.filter(cmd => cmd.trim().length > 0) ?? [];
if (commandsToSave.length > 0) {
saveWorktreeSetupCommands(directory, commandsToSave).catch(() => {
console.warn('[MultiRun] Failed to save worktree setup commands');
});
}
const sessionIds = createdRuns.map((r) => r.sessionId);
const firstSessionId = createdRuns[0]?.sessionId ?? null;
@@ -236,6 +248,29 @@ export const useMultiRunStore = create<MultiRunStore>()(
url: f.url,
}));
// Refresh sessions list so sidebar shows the new sessions immediately
try {
await useSessionStore.getState().loadSessions();
} catch {
// Ignore refresh errors
}
// Kick off setup commands after sessions are visible.
if (commandsToRun.length > 0) {
for (const run of createdRuns) {
void runWorktreeSetupCommands(run.worktreePath, directory, commandsToRun)
.then((result) => {
if (!result.success) {
const failed = result.results.filter((r) => !r.success);
console.warn(`[MultiRun] Setup commands failed for ${run.worktreePath}:`, failed);
}
})
.catch((err) => {
console.warn(`[MultiRun] Setup commands error for ${run.worktreePath}:`, err);
});
}
}
void (async () => {
try {
await Promise.allSettled(