diff options
Diffstat (limited to 'internal/executor/local.go')
| -rw-r--r-- | internal/executor/local.go | 42 |
1 files changed, 38 insertions, 4 deletions
diff --git a/internal/executor/local.go b/internal/executor/local.go index 6a240a4..43e76d3 100644 --- a/internal/executor/local.go +++ b/internal/executor/local.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "os" + "os/exec" "path/filepath" "strings" "time" @@ -73,6 +74,27 @@ func (r *LocalRunner) Run(ctx context.Context, t *task.Task, e *storage.Executio } defer stdout.Close() + // --- Git sandbox setup --- + var workDir string + if t.Agent.ProjectDir != "" { + // Reuse existing sandbox on resume (BLOCKED → QUEUED path). + if e.SandboxDir != "" { + workDir = e.SandboxDir + } else { + tmpDir, tmpErr := os.MkdirTemp("", "claudomator-local-*") + if tmpErr != nil { + return fmt.Errorf("local runner: create sandbox temp dir: %w", tmpErr) + } + cloneCmd := exec.CommandContext(ctx, "git", "clone", t.Agent.ProjectDir, tmpDir) + if cloneOut, cloneErr := cloneCmd.CombinedOutput(); cloneErr != nil { + os.RemoveAll(tmpDir) + return fmt.Errorf("local runner: git clone: %w\n%s", cloneErr, cloneOut) + } + workDir = tmpDir + e.SandboxDir = workDir + } + } + temperature := t.Agent.Temperature if temperature == nil && r.DefaultTemperature > 0 { v := r.DefaultTemperature @@ -140,7 +162,7 @@ func (r *LocalRunner) Run(ctx context.Context, t *task.Task, e *storage.Executio // Re-feed: record the assistant's tool-call turn, then each tool result. messages = append(messages, llm.Message{Role: "assistant", Content: resp.Content, ToolCalls: resp.ToolCalls}) for _, tc := range resp.ToolCalls { - result, didBlock, toolErr := dispatchAgentTool(ctx, ch, tc.Function.Name, tc.Function.Arguments) + result, didBlock, toolErr := dispatchAgentTool(ctx, ch, workDir, tc.Function.Name, tc.Function.Arguments) if toolErr != nil { writeResultLine(stdout, "error", toolErr.Error(), totalIn, totalOut) return fmt.Errorf("local runner: tool %s: %w", tc.Function.Name, toolErr) @@ -178,13 +200,25 @@ func (r *LocalRunner) Run(ctx context.Context, t *task.Task, e *storage.Executio ) } - // If the model asked the user, stop and block. Local resume re-feeds the - // conversation (per-runner sovereign state) — not yet wired, so the session - // ID is empty and resume starts fresh for now. + // If the model asked the user, stop and block. Preserve the sandbox so + // resume can reuse it. if q, isBlocked := channelPendingQuestion(ch); isBlocked { + // workDir is already stored in e.SandboxDir; don't clean up. return &BlockedError{QuestionJSON: q} } + // Push any commits made inside the sandbox back to the origin (project_dir). + if workDir != "" { + pushCmd := exec.CommandContext(ctx, "git", "-C", workDir, "push", "origin", "HEAD") + if pushOut, pushErr := pushCmd.CombinedOutput(); pushErr != nil { + if r.Logger != nil { + r.Logger.Warn("local runner: git push failed", "err", pushErr, "output", string(pushOut)) + } + } + os.RemoveAll(workDir) + e.SandboxDir = "" + } + // For tool-less models report_summary is never called, so synthesise a // summary from the raw assistant output so handleRunResult has something to // store. |
