summaryrefslogtreecommitdiff
path: root/internal/executor/gemini.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-05-02 22:10:48 -1000
committerGitHub <noreply@github.com>2026-05-02 22:10:48 -1000
commitb32bfe1bc6bcbc45d7d1549d6ae6412bc3e4424f (patch)
tree4dc822097b7f32a37ad655a550da1abada79ce85 /internal/executor/gemini.go
parent1ec3f87c392145580a62858110d9fd10638203db (diff)
parente7b382bf177cbe518af3d86c3ee6c49344d225f4 (diff)
Merge pull request #3 from thepeterstone/claude/deferred-work
Close deferred work — real GeminiRunner subprocess, Local UI option
Diffstat (limited to 'internal/executor/gemini.go')
-rw-r--r--internal/executor/gemini.go61
1 files changed, 45 insertions, 16 deletions
diff --git a/internal/executor/gemini.go b/internal/executor/gemini.go
index 7f2f54f..04382ae 100644
--- a/internal/executor/gemini.go
+++ b/internal/executor/gemini.go
@@ -7,9 +7,11 @@ import (
"io"
"log/slog"
"os"
+ "os/exec"
"path/filepath"
"strings"
"sync"
+ "syscall"
"github.com/thepeterstone/claudomator/internal/storage"
"github.com/thepeterstone/claudomator/internal/task"
@@ -84,8 +86,18 @@ func (r *GeminiRunner) Run(ctx context.Context, t *task.Task, e *storage.Executi
}
func (r *GeminiRunner) execOnce(ctx context.Context, args []string, workingDir, projectDir string, e *storage.Execution) error {
- // Temporarily bypass external command execution to debug pipe.
- // We will simulate outputting to stdoutW directly.
+ cmd := exec.CommandContext(ctx, r.binaryPath(), args...)
+ cmd.Env = append(os.Environ(),
+ "CLAUDOMATOR_API_URL="+r.APIURL,
+ "CLAUDOMATOR_TASK_ID="+e.TaskID,
+ "CLAUDOMATOR_PROJECT_DIR="+projectDir,
+ "CLAUDOMATOR_QUESTION_FILE="+filepath.Join(e.ArtifactDir, "question.json"),
+ "CLAUDOMATOR_SUMMARY_FILE="+filepath.Join(e.ArtifactDir, "summary.txt"),
+ )
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ if workingDir != "" {
+ cmd.Dir = workingDir
+ }
stdoutFile, err := os.Create(e.StdoutPath)
if err != nil {
@@ -103,22 +115,27 @@ func (r *GeminiRunner) execOnce(ctx context.Context, args []string, workingDir,
if err != nil {
return fmt.Errorf("creating stdout pipe: %w", err)
}
+ cmd.Stdout = stdoutW
+ cmd.Stderr = stderrFile
- // Simulate writing to stdoutW
+ if err := cmd.Start(); err != nil {
+ stdoutW.Close()
+ stdoutR.Close()
+ return fmt.Errorf("starting gemini: %w", err)
+ }
+ stdoutW.Close()
+
+ killDone := make(chan struct{})
go func() {
- defer stdoutW.Close() // Close the writer when done.
- fmt.Fprintf(stdoutW, "```json\n")
- fmt.Fprintf(stdoutW, "{\"type\":\"content_block_start\",\"content_block\":{\"text\":\"Hello, Gemini!\",\"type\":\"text\"}}\n")
- fmt.Fprintf(stdoutW, "{\"type\":\"content_block_delta\",\"content_block\":{\"text\":\" How are you?\"}}\n")
- fmt.Fprintf(stdoutW, "{\"type\":\"content_block_end\"}\n")
- fmt.Fprintf(stdoutW, "{\"type\":\"message_delta\",\"message\":{\"role\":\"model\"}}\n")
- fmt.Fprintf(stdoutW, "{\"type\":\"message_end\"}\n")
- fmt.Fprintf(stdoutW, "```\n")
+ select {
+ case <-ctx.Done():
+ syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
+ case <-killDone:
+ }
}()
-
- var streamErr error
var streamCost float64
+ var streamErr error
var wg sync.WaitGroup
wg.Add(1)
go func() {
@@ -127,14 +144,26 @@ func (r *GeminiRunner) execOnce(ctx context.Context, args []string, workingDir,
stdoutR.Close()
}()
- wg.Wait() // Wait for parseGeminiStream to finish
+ waitErr := cmd.Wait()
+ close(killDone)
+ wg.Wait()
if streamCost > 0 {
e.CostUSD = streamCost
}
- // Set a dummy exit code for this simulated run
- e.ExitCode = 0
+ if waitErr != nil {
+ if exitErr, ok := waitErr.(*exec.ExitError); ok {
+ e.ExitCode = exitErr.ExitCode()
+ }
+ if streamErr != nil {
+ return streamErr
+ }
+ if tail := tailFile(e.StderrPath, 20); tail != "" {
+ return fmt.Errorf("gemini exited with error: %w\nstderr:\n%s", waitErr, tail)
+ }
+ return fmt.Errorf("gemini exited with error: %w", waitErr)
+ }
if streamErr != nil {
return streamErr