diff --git a/codeagent-wrapper/executor.go b/codeagent-wrapper/executor.go index 1cd3860..4d9704c 100644 --- a/codeagent-wrapper/executor.go +++ b/codeagent-wrapper/executor.go @@ -17,6 +17,7 @@ import ( ) const postMessageTerminateDelay = 1 * time.Second +const forceKillWaitTimeout = 5 * time.Second // commandRunner abstracts exec.Cmd for testability type commandRunner interface { @@ -1109,7 +1110,8 @@ func runCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe waitLoop: for { select { - case waitErr = <-waitCh: + case err := <-waitCh: + waitErr = err break waitLoop case <-ctx.Done(): ctxCancelled = true @@ -1120,8 +1122,17 @@ waitLoop: terminated = true } } - waitErr = <-waitCh - break waitLoop + for { + select { + case err := <-waitCh: + waitErr = err + break waitLoop + case <-time.After(forceKillWaitTimeout): + if proc := cmd.Process(); proc != nil { + _ = proc.Kill() + } + } + } case <-messageTimerCh: forcedAfterComplete = true messageTimerCh = nil @@ -1135,8 +1146,17 @@ waitLoop: // Close pipes to unblock stream readers, then wait for process exit. closeWithReason(stdout, "terminate") closeWithReason(stderr, "terminate") - waitErr = <-waitCh - break waitLoop + for { + select { + case err := <-waitCh: + waitErr = err + break waitLoop + case <-time.After(forceKillWaitTimeout): + if proc := cmd.Process(); proc != nil { + _ = proc.Kill() + } + } + } case <-completeSeen: completeSeenObserved = true if messageTimer != nil { diff --git a/codeagent-wrapper/signal_windows.go b/codeagent-wrapper/signal_windows.go index f7c9032..cafcaa0 100644 --- a/codeagent-wrapper/signal_windows.go +++ b/codeagent-wrapper/signal_windows.go @@ -9,6 +9,7 @@ import ( "os/exec" "path/filepath" "strconv" + "strings" ) // sendTermSignal on Windows directly kills the process. @@ -31,6 +32,56 @@ func sendTermSignal(proc processHandle) error { if err := cmd.Run(); err == nil { return nil } + if err := killProcessTree(pid); err == nil { + return nil + } } return proc.Kill() } + +func killProcessTree(pid int) error { + if pid <= 0 { + return nil + } + + wmic := "wmic" + if root := os.Getenv("SystemRoot"); root != "" { + wmic = filepath.Join(root, "System32", "wbem", "WMIC.exe") + } + + queryChildren := "(ParentProcessId=" + strconv.Itoa(pid) + ")" + listCmd := exec.Command(wmic, "process", "where", queryChildren, "get", "ProcessId", "/VALUE") + listCmd.Stderr = io.Discard + out, err := listCmd.Output() + if err == nil { + for _, childPID := range parseWMICPIDs(out) { + _ = killProcessTree(childPID) + } + } + + querySelf := "(ProcessId=" + strconv.Itoa(pid) + ")" + termCmd := exec.Command(wmic, "process", "where", querySelf, "call", "terminate") + termCmd.Stdout = io.Discard + termCmd.Stderr = io.Discard + if termErr := termCmd.Run(); termErr != nil && err == nil { + err = termErr + } + return err +} + +func parseWMICPIDs(out []byte) []int { + const prefix = "ProcessId=" + var pids []int + for _, line := range strings.Split(string(out), "\n") { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, prefix) { + continue + } + n, err := strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(line, prefix))) + if err != nil || n <= 0 { + continue + } + pids = append(pids, n) + } + return pids +} diff --git a/skills/omo/README.md b/skills/omo/README.md index 0d2fba8..c463ae8 100644 --- a/skills/omo/README.md +++ b/skills/omo/README.md @@ -1,6 +1,12 @@ # OmO Multi-Agent Orchestration -OmO (Oh-My-OpenCode) is a multi-agent orchestration skill that uses Sisyphus as the primary coordinator to delegate tasks to specialized agents. +OmO (Oh-My-OpenCode) is a multi-agent orchestration skill that delegates tasks to specialized agents based on routing signals. + +## Installation + +```bash +python3 install.py --module omo +``` ## Quick Start @@ -12,19 +18,17 @@ OmO (Oh-My-OpenCode) is a multi-agent orchestration skill that uses Sisyphus as | Agent | Role | Backend | Model | |-------|------|---------|-------| -| sisyphus | Primary orchestrator | claude | claude-sonnet-4-20250514 | -| oracle | Technical advisor (EXPENSIVE) | claude | claude-sonnet-4-20250514 | -| librarian | External research | claude | claude-sonnet-4-5-20250514 | -| explore | Codebase search (FREE) | opencode | opencode/grok-code | -| develop | Code implementation | codex | (default) | -| frontend-ui-ux-engineer | UI/UX specialist | gemini | gemini-3-pro-preview | -| document-writer | Documentation | gemini | gemini-3-flash-preview | +| oracle | Technical advisor | claude | claude-opus-4-5-20251101 | +| librarian | External research | claude | claude-sonnet-4-5-20250929 | +| explore | Codebase search | opencode | opencode/grok-code | +| develop | Code implementation | codex | gpt-5.2 | +| frontend-ui-ux-engineer | UI/UX specialist | gemini | gemini-3-pro-high | +| document-writer | Documentation | gemini | gemini-3-flash | ## How It Works -1. `/omo` loads Sisyphus as the entry point -2. Sisyphus analyzes your request via routing signals -3. Based on task type, Sisyphus either: +1. `/omo` analyzes your request via routing signals +2. Based on task type, it either: - Answers directly (analysis/explanation tasks - no code changes) - Delegates to specialized agents (implementation tasks) - Fires parallel agents (exploration + research) @@ -44,7 +48,7 @@ OmO (Oh-My-OpenCode) is a multi-agent orchestration skill that uses Sisyphus as ## Agent Delegation -Sisyphus delegates via codeagent-wrapper with full Context Pack: +Delegates via codeagent-wrapper with full Context Pack: ```bash codeagent-wrapper --agent oracle - . <<'EOF' @@ -70,11 +74,43 @@ Agent-model mappings are configured in `~/.codeagent/models.json`: ```json { - "default_backend": "opencode", - "default_model": "opencode/grok-code", + "default_backend": "codex", + "default_model": "gpt-5.2", "agents": { - "sisyphus": {"backend": "claude", "model": "claude-sonnet-4-20250514"}, - "oracle": {"backend": "claude", "model": "claude-sonnet-4-20250514"} + "oracle": { + "backend": "claude", + "model": "claude-opus-4-5-20251101", + "description": "Technical advisor", + "yolo": true + }, + "librarian": { + "backend": "claude", + "model": "claude-sonnet-4-5-20250929", + "description": "Researcher", + "yolo": true + }, + "explore": { + "backend": "opencode", + "model": "opencode/grok-code", + "description": "Code search" + }, + "frontend-ui-ux-engineer": { + "backend": "gemini", + "model": "gemini-3-pro-high", + "description": "Frontend engineer" + }, + "document-writer": { + "backend": "gemini", + "model": "gemini-3-flash", + "description": "Documentation" + }, + "develop": { + "backend": "codex", + "model": "gpt-5.2", + "description": "codex develop", + "yolo": true, + "reasoning": "xhigh" + } } } ``` @@ -82,4 +118,4 @@ Agent-model mappings are configured in `~/.codeagent/models.json`: ## Requirements - codeagent-wrapper with `--agent` support -- Backend CLIs: claude, opencode, gemini +- Backend CLIs: claude, opencode, codex, gemini