mirror of
https://github.com/cexll/myclaude.git
synced 2026-02-12 03:27:47 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7aeaa5c7e | ||
|
|
c8f75faf84 | ||
|
|
b8b06257ff | ||
|
|
369a3319f9 |
@@ -13,6 +13,7 @@ type AgentModelConfig struct {
|
|||||||
PromptFile string `json:"prompt_file,omitempty"`
|
PromptFile string `json:"prompt_file,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Yolo bool `json:"yolo,omitempty"`
|
Yolo bool `json:"yolo,omitempty"`
|
||||||
|
Reasoning string `json:"reasoning,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModelsConfig struct {
|
type ModelsConfig struct {
|
||||||
@@ -25,15 +26,15 @@ var defaultModelsConfig = ModelsConfig{
|
|||||||
DefaultBackend: "opencode",
|
DefaultBackend: "opencode",
|
||||||
DefaultModel: "opencode/grok-code",
|
DefaultModel: "opencode/grok-code",
|
||||||
Agents: map[string]AgentModelConfig{
|
Agents: map[string]AgentModelConfig{
|
||||||
"sisyphus": {Backend: "claude", Model: "claude-sonnet-4-20250514", PromptFile: "~/.claude/skills/omo/references/sisyphus.md", Description: "Primary orchestrator"},
|
"sisyphus": {Backend: "claude", Model: "claude-sonnet-4-20250514", PromptFile: "~/.claude/skills/omo/references/sisyphus.md", Description: "Primary orchestrator"},
|
||||||
"oracle": {Backend: "claude", Model: "claude-sonnet-4-20250514", PromptFile: "~/.claude/skills/omo/references/oracle.md", Description: "Technical advisor"},
|
"oracle": {Backend: "claude", Model: "claude-sonnet-4-20250514", PromptFile: "~/.claude/skills/omo/references/oracle.md", Description: "Technical advisor"},
|
||||||
"librarian": {Backend: "claude", Model: "claude-sonnet-4-5-20250514", PromptFile: "~/.claude/skills/omo/references/librarian.md", Description: "Researcher"},
|
"librarian": {Backend: "claude", Model: "claude-sonnet-4-5-20250514", PromptFile: "~/.claude/skills/omo/references/librarian.md", Description: "Researcher"},
|
||||||
"explore": {Backend: "opencode", Model: "opencode/grok-code", PromptFile: "~/.claude/skills/omo/references/explore.md", Description: "Code search"},
|
"explore": {Backend: "opencode", Model: "opencode/grok-code", PromptFile: "~/.claude/skills/omo/references/explore.md", Description: "Code search"},
|
||||||
"develop": {Backend: "codex", Model: "", PromptFile: "~/.claude/skills/omo/references/develop.md", Description: "Code development"},
|
"develop": {Backend: "codex", Model: "", PromptFile: "~/.claude/skills/omo/references/develop.md", Description: "Code development"},
|
||||||
"frontend-ui-ux-engineer": {Backend: "gemini", Model: "gemini-3-pro-preview", PromptFile: "~/.claude/skills/omo/references/frontend-ui-ux-engineer.md", Description: "Frontend engineer"},
|
"frontend-ui-ux-engineer": {Backend: "gemini", Model: "", PromptFile: "~/.claude/skills/omo/references/frontend-ui-ux-engineer.md", Description: "Frontend engineer"},
|
||||||
"document-writer": {Backend: "gemini", Model: "gemini-3-flash-preview", PromptFile: "~/.claude/skills/omo/references/document-writer.md", Description: "Documentation"},
|
"document-writer": {Backend: "gemini", Model: "", PromptFile: "~/.claude/skills/omo/references/document-writer.md", Description: "Documentation"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadModelsConfig() *ModelsConfig {
|
func loadModelsConfig() *ModelsConfig {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
@@ -70,10 +71,10 @@ func loadModelsConfig() *ModelsConfig {
|
|||||||
return &cfg
|
return &cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveAgentConfig(agentName string) (backend, model, promptFile string, yolo bool) {
|
func resolveAgentConfig(agentName string) (backend, model, promptFile, reasoning string, yolo bool) {
|
||||||
cfg := loadModelsConfig()
|
cfg := loadModelsConfig()
|
||||||
if agent, ok := cfg.Agents[agentName]; ok {
|
if agent, ok := cfg.Agents[agentName]; ok {
|
||||||
return agent.Backend, agent.Model, agent.PromptFile, agent.Yolo
|
return agent.Backend, agent.Model, agent.PromptFile, agent.Reasoning, agent.Yolo
|
||||||
}
|
}
|
||||||
return cfg.DefaultBackend, cfg.DefaultModel, "", false
|
return cfg.DefaultBackend, cfg.DefaultModel, "", "", false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,17 +19,17 @@ func TestResolveAgentConfig_Defaults(t *testing.T) {
|
|||||||
wantModel string
|
wantModel string
|
||||||
wantPromptFile string
|
wantPromptFile string
|
||||||
}{
|
}{
|
||||||
{"sisyphus", "claude", "claude-sonnet-4-20250514", "~/.claude/skills/omo/references/sisyphus.md"},
|
{"sisyphus", "claude", "claude-sonnet-4-20250514", "~/.claude/skills/omo/references/sisyphus.md"},
|
||||||
{"oracle", "claude", "claude-sonnet-4-20250514", "~/.claude/skills/omo/references/oracle.md"},
|
{"oracle", "claude", "claude-sonnet-4-20250514", "~/.claude/skills/omo/references/oracle.md"},
|
||||||
{"librarian", "claude", "claude-sonnet-4-5-20250514", "~/.claude/skills/omo/references/librarian.md"},
|
{"librarian", "claude", "claude-sonnet-4-5-20250514", "~/.claude/skills/omo/references/librarian.md"},
|
||||||
{"explore", "opencode", "opencode/grok-code", "~/.claude/skills/omo/references/explore.md"},
|
{"explore", "opencode", "opencode/grok-code", "~/.claude/skills/omo/references/explore.md"},
|
||||||
{"frontend-ui-ux-engineer", "gemini", "gemini-3-pro-preview", "~/.claude/skills/omo/references/frontend-ui-ux-engineer.md"},
|
{"frontend-ui-ux-engineer", "gemini", "", "~/.claude/skills/omo/references/frontend-ui-ux-engineer.md"},
|
||||||
{"document-writer", "gemini", "gemini-3-flash-preview", "~/.claude/skills/omo/references/document-writer.md"},
|
{"document-writer", "gemini", "", "~/.claude/skills/omo/references/document-writer.md"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.agent, func(t *testing.T) {
|
t.Run(tt.agent, func(t *testing.T) {
|
||||||
backend, model, promptFile, _ := resolveAgentConfig(tt.agent)
|
backend, model, promptFile, _, _ := resolveAgentConfig(tt.agent)
|
||||||
if backend != tt.wantBackend {
|
if backend != tt.wantBackend {
|
||||||
t.Errorf("backend = %q, want %q", backend, tt.wantBackend)
|
t.Errorf("backend = %q, want %q", backend, tt.wantBackend)
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ func TestResolveAgentConfig_UnknownAgent(t *testing.T) {
|
|||||||
t.Setenv("HOME", home)
|
t.Setenv("HOME", home)
|
||||||
t.Setenv("USERPROFILE", home)
|
t.Setenv("USERPROFILE", home)
|
||||||
|
|
||||||
backend, model, promptFile, _ := resolveAgentConfig("unknown-agent")
|
backend, model, promptFile, _, _ := resolveAgentConfig("unknown-agent")
|
||||||
if backend != "opencode" {
|
if backend != "opencode" {
|
||||||
t.Errorf("unknown agent backend = %q, want %q", backend, "opencode")
|
t.Errorf("unknown agent backend = %q, want %q", backend, "opencode")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,51 @@ func loadMinimalEnvSettings() map[string]string {
|
|||||||
return settings.Env
|
return settings.Env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadGeminiEnv loads environment variables from ~/.gemini/.env
|
||||||
|
// Supports GEMINI_API_KEY, GEMINI_MODEL, GOOGLE_GEMINI_BASE_URL
|
||||||
|
// Also sets GEMINI_API_KEY_AUTH_MECHANISM=bearer for third-party API compatibility
|
||||||
|
func loadGeminiEnv() map[string]string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil || home == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
envPath := filepath.Join(home, ".gemini", ".env")
|
||||||
|
data, err := os.ReadFile(envPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
env := make(map[string]string)
|
||||||
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idx := strings.IndexByte(line, '=')
|
||||||
|
if idx <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.TrimSpace(line[:idx])
|
||||||
|
value := strings.TrimSpace(line[idx+1:])
|
||||||
|
if key != "" && value != "" {
|
||||||
|
env[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bearer auth mechanism for third-party API compatibility
|
||||||
|
if _, ok := env["GEMINI_API_KEY"]; ok {
|
||||||
|
if _, hasAuth := env["GEMINI_API_KEY_AUTH_MECHANISM"]; !hasAuth {
|
||||||
|
env["GEMINI_API_KEY_AUTH_MECHANISM"] = "bearer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(env) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
func buildClaudeArgs(cfg *Config, targetArg string) []string {
|
func buildClaudeArgs(cfg *Config, targetArg string) []string {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ type Config struct {
|
|||||||
SessionID string
|
SessionID string
|
||||||
WorkDir string
|
WorkDir string
|
||||||
Model string
|
Model string
|
||||||
|
ReasoningEffort string
|
||||||
ExplicitStdin bool
|
ExplicitStdin bool
|
||||||
Timeout int
|
Timeout int
|
||||||
Backend string
|
Backend string
|
||||||
@@ -35,18 +36,19 @@ type ParallelConfig struct {
|
|||||||
|
|
||||||
// TaskSpec describes an individual task entry in the parallel config
|
// TaskSpec describes an individual task entry in the parallel config
|
||||||
type TaskSpec struct {
|
type TaskSpec struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Task string `json:"task"`
|
Task string `json:"task"`
|
||||||
WorkDir string `json:"workdir,omitempty"`
|
WorkDir string `json:"workdir,omitempty"`
|
||||||
Dependencies []string `json:"dependencies,omitempty"`
|
Dependencies []string `json:"dependencies,omitempty"`
|
||||||
SessionID string `json:"session_id,omitempty"`
|
SessionID string `json:"session_id,omitempty"`
|
||||||
Backend string `json:"backend,omitempty"`
|
Backend string `json:"backend,omitempty"`
|
||||||
Model string `json:"model,omitempty"`
|
Model string `json:"model,omitempty"`
|
||||||
Agent string `json:"agent,omitempty"`
|
ReasoningEffort string `json:"reasoning_effort,omitempty"`
|
||||||
PromptFile string `json:"prompt_file,omitempty"`
|
Agent string `json:"agent,omitempty"`
|
||||||
Mode string `json:"-"`
|
PromptFile string `json:"prompt_file,omitempty"`
|
||||||
UseStdin bool `json:"-"`
|
Mode string `json:"-"`
|
||||||
Context context.Context `json:"-"`
|
UseStdin bool `json:"-"`
|
||||||
|
Context context.Context `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TaskResult captures the execution outcome of a task
|
// TaskResult captures the execution outcome of a task
|
||||||
@@ -190,6 +192,8 @@ func parseParallelConfig(data []byte) (*ParallelConfig, error) {
|
|||||||
task.Backend = value
|
task.Backend = value
|
||||||
case "model":
|
case "model":
|
||||||
task.Model = value
|
task.Model = value
|
||||||
|
case "reasoning_effort":
|
||||||
|
task.ReasoningEffort = value
|
||||||
case "agent":
|
case "agent":
|
||||||
agentSpecified = true
|
agentSpecified = true
|
||||||
task.Agent = value
|
task.Agent = value
|
||||||
@@ -214,13 +218,16 @@ func parseParallelConfig(data []byte) (*ParallelConfig, error) {
|
|||||||
if err := validateAgentName(task.Agent); err != nil {
|
if err := validateAgentName(task.Agent); err != nil {
|
||||||
return nil, fmt.Errorf("task block #%d invalid agent name: %w", taskIndex, err)
|
return nil, fmt.Errorf("task block #%d invalid agent name: %w", taskIndex, err)
|
||||||
}
|
}
|
||||||
backend, model, promptFile, _ := resolveAgentConfig(task.Agent)
|
backend, model, promptFile, reasoning, _ := resolveAgentConfig(task.Agent)
|
||||||
if task.Backend == "" {
|
if task.Backend == "" {
|
||||||
task.Backend = backend
|
task.Backend = backend
|
||||||
}
|
}
|
||||||
if task.Model == "" {
|
if task.Model == "" {
|
||||||
task.Model = model
|
task.Model = model
|
||||||
}
|
}
|
||||||
|
if task.ReasoningEffort == "" {
|
||||||
|
task.ReasoningEffort = reasoning
|
||||||
|
}
|
||||||
task.PromptFile = promptFile
|
task.PromptFile = promptFile
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +264,7 @@ func parseArgs() (*Config, error) {
|
|||||||
|
|
||||||
backendName := defaultBackendName
|
backendName := defaultBackendName
|
||||||
model := ""
|
model := ""
|
||||||
|
reasoningEffort := ""
|
||||||
agentName := ""
|
agentName := ""
|
||||||
promptFile := ""
|
promptFile := ""
|
||||||
promptFileExplicit := false
|
promptFileExplicit := false
|
||||||
@@ -277,12 +285,15 @@ func parseArgs() (*Config, error) {
|
|||||||
if err := validateAgentName(value); err != nil {
|
if err := validateAgentName(value); err != nil {
|
||||||
return nil, fmt.Errorf("--agent flag invalid value: %w", err)
|
return nil, fmt.Errorf("--agent flag invalid value: %w", err)
|
||||||
}
|
}
|
||||||
resolvedBackend, resolvedModel, resolvedPromptFile, resolvedYolo := resolveAgentConfig(value)
|
resolvedBackend, resolvedModel, resolvedPromptFile, resolvedReasoning, resolvedYolo := resolveAgentConfig(value)
|
||||||
backendName = resolvedBackend
|
backendName = resolvedBackend
|
||||||
model = resolvedModel
|
model = resolvedModel
|
||||||
if !promptFileExplicit {
|
if !promptFileExplicit {
|
||||||
promptFile = resolvedPromptFile
|
promptFile = resolvedPromptFile
|
||||||
}
|
}
|
||||||
|
if reasoningEffort == "" {
|
||||||
|
reasoningEffort = resolvedReasoning
|
||||||
|
}
|
||||||
yolo = resolvedYolo
|
yolo = resolvedYolo
|
||||||
agentName = value
|
agentName = value
|
||||||
i++
|
i++
|
||||||
@@ -295,12 +306,15 @@ func parseArgs() (*Config, error) {
|
|||||||
if err := validateAgentName(value); err != nil {
|
if err := validateAgentName(value); err != nil {
|
||||||
return nil, fmt.Errorf("--agent flag invalid value: %w", err)
|
return nil, fmt.Errorf("--agent flag invalid value: %w", err)
|
||||||
}
|
}
|
||||||
resolvedBackend, resolvedModel, resolvedPromptFile, resolvedYolo := resolveAgentConfig(value)
|
resolvedBackend, resolvedModel, resolvedPromptFile, resolvedReasoning, resolvedYolo := resolveAgentConfig(value)
|
||||||
backendName = resolvedBackend
|
backendName = resolvedBackend
|
||||||
model = resolvedModel
|
model = resolvedModel
|
||||||
if !promptFileExplicit {
|
if !promptFileExplicit {
|
||||||
promptFile = resolvedPromptFile
|
promptFile = resolvedPromptFile
|
||||||
}
|
}
|
||||||
|
if reasoningEffort == "" {
|
||||||
|
reasoningEffort = resolvedReasoning
|
||||||
|
}
|
||||||
yolo = resolvedYolo
|
yolo = resolvedYolo
|
||||||
agentName = value
|
agentName = value
|
||||||
continue
|
continue
|
||||||
@@ -355,6 +369,24 @@ func parseArgs() (*Config, error) {
|
|||||||
}
|
}
|
||||||
model = value
|
model = value
|
||||||
continue
|
continue
|
||||||
|
case arg == "--reasoning-effort":
|
||||||
|
if i+1 >= len(args) {
|
||||||
|
return nil, fmt.Errorf("--reasoning-effort flag requires a value")
|
||||||
|
}
|
||||||
|
value := strings.TrimSpace(args[i+1])
|
||||||
|
if value == "" {
|
||||||
|
return nil, fmt.Errorf("--reasoning-effort flag requires a value")
|
||||||
|
}
|
||||||
|
reasoningEffort = value
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
case strings.HasPrefix(arg, "--reasoning-effort="):
|
||||||
|
value := strings.TrimSpace(strings.TrimPrefix(arg, "--reasoning-effort="))
|
||||||
|
if value == "" {
|
||||||
|
return nil, fmt.Errorf("--reasoning-effort flag requires a value")
|
||||||
|
}
|
||||||
|
reasoningEffort = value
|
||||||
|
continue
|
||||||
case strings.HasPrefix(arg, "--skip-permissions="):
|
case strings.HasPrefix(arg, "--skip-permissions="):
|
||||||
skipPermissions = parseBoolFlag(strings.TrimPrefix(arg, "--skip-permissions="), skipPermissions)
|
skipPermissions = parseBoolFlag(strings.TrimPrefix(arg, "--skip-permissions="), skipPermissions)
|
||||||
continue
|
continue
|
||||||
@@ -370,7 +402,7 @@ func parseArgs() (*Config, error) {
|
|||||||
}
|
}
|
||||||
args = filtered
|
args = filtered
|
||||||
|
|
||||||
cfg := &Config{WorkDir: defaultWorkdir, Backend: backendName, Agent: agentName, PromptFile: promptFile, PromptFileExplicit: promptFileExplicit, SkipPermissions: skipPermissions, Yolo: yolo, Model: strings.TrimSpace(model)}
|
cfg := &Config{WorkDir: defaultWorkdir, Backend: backendName, Agent: agentName, PromptFile: promptFile, PromptFileExplicit: promptFileExplicit, SkipPermissions: skipPermissions, Yolo: yolo, Model: strings.TrimSpace(model), ReasoningEffort: strings.TrimSpace(reasoningEffort)}
|
||||||
cfg.MaxParallelWorkers = resolveMaxParallelWorkers()
|
cfg.MaxParallelWorkers = resolveMaxParallelWorkers()
|
||||||
|
|
||||||
if args[0] == "resume" {
|
if args[0] == "resume" {
|
||||||
|
|||||||
@@ -764,6 +764,10 @@ func buildCodexArgs(cfg *Config, targetArg string) []string {
|
|||||||
args = append(args, "--model", model)
|
args = append(args, "--model", model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reasoningEffort := strings.TrimSpace(cfg.ReasoningEffort); reasoningEffort != "" {
|
||||||
|
args = append(args, "--reasoning-effort", reasoningEffort)
|
||||||
|
}
|
||||||
|
|
||||||
args = append(args, "--skip-git-repo-check")
|
args = append(args, "--skip-git-repo-check")
|
||||||
|
|
||||||
if isResume {
|
if isResume {
|
||||||
@@ -804,12 +808,13 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
|
|||||||
logger := injectedLogger
|
logger := injectedLogger
|
||||||
|
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Mode: taskSpec.Mode,
|
Mode: taskSpec.Mode,
|
||||||
Task: taskSpec.Task,
|
Task: taskSpec.Task,
|
||||||
SessionID: taskSpec.SessionID,
|
SessionID: taskSpec.SessionID,
|
||||||
WorkDir: taskSpec.WorkDir,
|
WorkDir: taskSpec.WorkDir,
|
||||||
Model: taskSpec.Model,
|
Model: taskSpec.Model,
|
||||||
Backend: defaultBackendName,
|
ReasoningEffort: taskSpec.ReasoningEffort,
|
||||||
|
Backend: defaultBackendName,
|
||||||
}
|
}
|
||||||
|
|
||||||
commandName := codexCommand
|
commandName := codexCommand
|
||||||
@@ -846,6 +851,12 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load gemini env from ~/.gemini/.env if exists
|
||||||
|
var geminiEnv map[string]string
|
||||||
|
if cfg.Backend == "gemini" {
|
||||||
|
geminiEnv = loadGeminiEnv()
|
||||||
|
}
|
||||||
|
|
||||||
useStdin := taskSpec.UseStdin
|
useStdin := taskSpec.UseStdin
|
||||||
targetArg := taskSpec.Task
|
targetArg := taskSpec.Task
|
||||||
if useStdin {
|
if useStdin {
|
||||||
@@ -948,6 +959,9 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
|
|||||||
if cfg.Backend == "claude" && len(claudeEnv) > 0 {
|
if cfg.Backend == "claude" && len(claudeEnv) > 0 {
|
||||||
cmd.SetEnv(claudeEnv)
|
cmd.SetEnv(claudeEnv)
|
||||||
}
|
}
|
||||||
|
if cfg.Backend == "gemini" && len(geminiEnv) > 0 {
|
||||||
|
cmd.SetEnv(geminiEnv)
|
||||||
|
}
|
||||||
|
|
||||||
// For backends that don't support -C flag (claude, gemini), set working directory via cmd.Dir
|
// For backends that don't support -C flag (claude, gemini), set working directory via cmd.Dir
|
||||||
// Codex passes workdir via -C flag, so we skip setting Dir for it to avoid conflicts
|
// Codex passes workdir via -C flag, so we skip setting Dir for it to avoid conflicts
|
||||||
|
|||||||
@@ -434,12 +434,13 @@ func run() (exitCode int) {
|
|||||||
logInfo(fmt.Sprintf("%s running...", cfg.Backend))
|
logInfo(fmt.Sprintf("%s running...", cfg.Backend))
|
||||||
|
|
||||||
taskSpec := TaskSpec{
|
taskSpec := TaskSpec{
|
||||||
Task: taskText,
|
Task: taskText,
|
||||||
WorkDir: cfg.WorkDir,
|
WorkDir: cfg.WorkDir,
|
||||||
Mode: cfg.Mode,
|
Mode: cfg.Mode,
|
||||||
SessionID: cfg.SessionID,
|
SessionID: cfg.SessionID,
|
||||||
Model: cfg.Model,
|
Model: cfg.Model,
|
||||||
UseStdin: useStdin,
|
ReasoningEffort: cfg.ReasoningEffort,
|
||||||
|
UseStdin: useStdin,
|
||||||
}
|
}
|
||||||
|
|
||||||
result := runTaskFn(taskSpec, false, cfg.Timeout)
|
result := runTaskFn(taskSpec, false, cfg.Timeout)
|
||||||
|
|||||||
@@ -637,9 +637,13 @@ func (f *fakeCmd) StdinContents() string {
|
|||||||
func createFakeCodexScript(t *testing.T, threadID, message string) string {
|
func createFakeCodexScript(t *testing.T, threadID, message string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
scriptPath := filepath.Join(t.TempDir(), "codex.sh")
|
scriptPath := filepath.Join(t.TempDir(), "codex.sh")
|
||||||
|
// Add small sleep to ensure parser goroutine has time to read stdout before
|
||||||
|
// the process exits and closes the pipe. This prevents race conditions in CI
|
||||||
|
// where fast shell script execution can close stdout before parsing completes.
|
||||||
script := fmt.Sprintf(`#!/bin/sh
|
script := fmt.Sprintf(`#!/bin/sh
|
||||||
printf '%%s\n' '{"type":"thread.started","thread_id":"%s"}'
|
printf '%%s\n' '{"type":"thread.started","thread_id":"%s"}'
|
||||||
printf '%%s\n' '{"type":"item.completed","item":{"type":"agent_message","text":"%s"}}'
|
printf '%%s\n' '{"type":"item.completed","item":{"type":"agent_message","text":"%s"}}'
|
||||||
|
sleep 0.05
|
||||||
`, threadID, message)
|
`, threadID, message)
|
||||||
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
|
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
|
||||||
t.Fatalf("failed to create fake codex script: %v", err)
|
t.Fatalf("failed to create fake codex script: %v", err)
|
||||||
@@ -1290,6 +1294,65 @@ func TestBackendParseArgs_ModelFlag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBackendParseArgs_ReasoningEffortFlag(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "reasoning-effort flag",
|
||||||
|
args: []string{"codeagent-wrapper", "--reasoning-effort", "low", "task"},
|
||||||
|
want: "low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reasoning-effort equals syntax",
|
||||||
|
args: []string{"codeagent-wrapper", "--reasoning-effort=medium", "task"},
|
||||||
|
want: "medium",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reasoning-effort trimmed",
|
||||||
|
args: []string{"codeagent-wrapper", "--reasoning-effort", " high ", "task"},
|
||||||
|
want: "high",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reasoning-effort with resume mode",
|
||||||
|
args: []string{"codeagent-wrapper", "--reasoning-effort", "low", "resume", "sid", "task"},
|
||||||
|
want: "low",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing reasoning-effort value",
|
||||||
|
args: []string{"codeagent-wrapper", "--reasoning-effort"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reasoning-effort equals missing value",
|
||||||
|
args: []string{"codeagent-wrapper", "--reasoning-effort=", "task"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
os.Args = tt.args
|
||||||
|
cfg, err := parseArgs()
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error, got nil")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.ReasoningEffort != tt.want {
|
||||||
|
t.Fatalf("ReasoningEffort = %q, want %q", cfg.ReasoningEffort, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBackendParseArgs_PromptFileFlag(t *testing.T) {
|
func TestBackendParseArgs_PromptFileFlag(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -1829,6 +1892,28 @@ func TestRun_PromptFilePrefixesTask(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRun_PassesReasoningEffortToTaskSpec(t *testing.T) {
|
||||||
|
defer resetTestHooks()
|
||||||
|
cleanupLogsFn = func() (CleanupStats, error) { return CleanupStats{}, nil }
|
||||||
|
|
||||||
|
stdinReader = strings.NewReader("")
|
||||||
|
isTerminalFn = func() bool { return true }
|
||||||
|
|
||||||
|
var got TaskSpec
|
||||||
|
runTaskFn = func(task TaskSpec, silent bool, timeout int) TaskResult {
|
||||||
|
got = task
|
||||||
|
return TaskResult{ExitCode: 0, Message: "ok"}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Args = []string{"codeagent-wrapper", "--reasoning-effort", "high", "task"}
|
||||||
|
if code := run(); code != 0 {
|
||||||
|
t.Fatalf("run exit = %d, want 0", code)
|
||||||
|
}
|
||||||
|
if got.ReasoningEffort != "high" {
|
||||||
|
t.Fatalf("ReasoningEffort = %q, want %q", got.ReasoningEffort, "high")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunBuildCodexArgs_NewMode(t *testing.T) {
|
func TestRunBuildCodexArgs_NewMode(t *testing.T) {
|
||||||
const key = "CODEX_BYPASS_SANDBOX"
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
t.Setenv(key, "false")
|
t.Setenv(key, "false")
|
||||||
@@ -1852,6 +1937,64 @@ func TestRunBuildCodexArgs_NewMode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunBuildCodexArgs_NewMode_WithReasoningEffort(t *testing.T) {
|
||||||
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
|
t.Setenv(key, "false")
|
||||||
|
|
||||||
|
cfg := &Config{Mode: "new", WorkDir: "/test/dir", ReasoningEffort: "high"}
|
||||||
|
args := buildCodexArgs(cfg, "my task")
|
||||||
|
expected := []string{
|
||||||
|
"e",
|
||||||
|
"--reasoning-effort", "high",
|
||||||
|
"--skip-git-repo-check",
|
||||||
|
"-C", "/test/dir",
|
||||||
|
"--json",
|
||||||
|
"my task",
|
||||||
|
}
|
||||||
|
if len(args) != len(expected) {
|
||||||
|
t.Fatalf("len mismatch")
|
||||||
|
}
|
||||||
|
for i := range args {
|
||||||
|
if args[i] != expected[i] {
|
||||||
|
t.Fatalf("args[%d]=%s, want %s", i, args[i], expected[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunCodexTaskWithContext_CodexReasoningEffort(t *testing.T) {
|
||||||
|
defer resetTestHooks()
|
||||||
|
t.Setenv("CODEX_BYPASS_SANDBOX", "false")
|
||||||
|
|
||||||
|
var gotArgs []string
|
||||||
|
origRunner := newCommandRunner
|
||||||
|
newCommandRunner = func(ctx context.Context, name string, args ...string) commandRunner {
|
||||||
|
gotArgs = append([]string(nil), args...)
|
||||||
|
return newFakeCmd(fakeCmdConfig{
|
||||||
|
PID: 123,
|
||||||
|
StdoutPlan: []fakeStdoutEvent{
|
||||||
|
{Data: "{\"type\":\"result\",\"session_id\":\"sid\",\"result\":\"ok\"}\n"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { newCommandRunner = origRunner })
|
||||||
|
|
||||||
|
res := runCodexTaskWithContext(context.Background(), TaskSpec{Task: "hi", Mode: "new", WorkDir: defaultWorkdir, ReasoningEffort: "high"}, nil, nil, false, true, 5)
|
||||||
|
if res.ExitCode != 0 || res.Message != "ok" {
|
||||||
|
t.Fatalf("unexpected result: %+v", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for i := 0; i+1 < len(gotArgs); i++ {
|
||||||
|
if gotArgs[i] == "--reasoning-effort" && gotArgs[i+1] == "high" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("expected --reasoning-effort high in args, got %v", gotArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRunBuildCodexArgs_ResumeMode(t *testing.T) {
|
func TestRunBuildCodexArgs_ResumeMode(t *testing.T) {
|
||||||
const key = "CODEX_BYPASS_SANDBOX"
|
const key = "CODEX_BYPASS_SANDBOX"
|
||||||
t.Setenv(key, "false")
|
t.Setenv(key, "false")
|
||||||
|
|||||||
@@ -119,12 +119,6 @@
|
|||||||
"target": "skills/omo/SKILL.md",
|
"target": "skills/omo/SKILL.md",
|
||||||
"description": "Install omo skill"
|
"description": "Install omo skill"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "copy_file",
|
|
||||||
"source": "skills/omo/references/sisyphus.md",
|
|
||||||
"target": "skills/omo/references/sisyphus.md",
|
|
||||||
"description": "Install sisyphus agent prompt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "copy_file",
|
"type": "copy_file",
|
||||||
"source": "skills/omo/references/oracle.md",
|
"source": "skills/omo/references/oracle.md",
|
||||||
|
|||||||
Reference in New Issue
Block a user