Compare commits

..

20 Commits

Author SHA1 Message Date
github-actions[bot]
a5db86ee15 release: v3.0.1 2026-01-25 05:04:20 +00:00
justsisyphus
14f450bd25 refactor: sync delegate_task schema with OpenCode Task tool (resume→session_id, add command param) 2026-01-25 13:57:45 +09:00
justsisyphus
5a1da39def refactor(ultrawork): replace vague plan agent references with explicit delegate_task(subagent_type="plan") invocation syntax 2026-01-25 13:57:45 +09:00
Sisyphus
24d065c43a fix: update documentation to use load_skills instead of skills parameter (#1088)
All documentation, agent prompts, and skill descriptions were still
referencing the old 'skills' parameter name for delegate_task, but the
tool implementation requires 'load_skills' (renamed in commit aa2b052).
This caused confusion and errors for users following the docs.

Fixes #1008

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
2026-01-25 13:45:00 +09:00
justsisyphus
fd72ce5ce7 docs: update AGENTS.md knowledge base (043b1a33)
- Add 7 missing hooks, remove deleted background-compaction
- Update line counts (atlas 572, sisyphus 450, config-manager 664)
- Add 18 undocumented shared utilities, remove stale references
- Add task-toast-manager, remove-deadcode command
- Update test count 90→95, add 4 complexity hotspots
2026-01-25 13:12:40 +09:00
justsisyphus
043b1a3377 refactor: remove dead re-exports from tools barrel (getTmuxPath, DelegateTaskToolOptions, DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS) 2026-01-25 12:59:19 +09:00
justsisyphus
512952f66d refactor: remove deprecated config-path.ts (dead code, 0 references) 2026-01-25 12:58:40 +09:00
justsisyphus
d9723e76ab refactor: remove unused background-compaction hook module 2026-01-25 12:58:05 +09:00
justsisyphus
212baa6674 feat(commands): add /remove-deadcode slash command for LSP-verified dead code removal 2026-01-25 12:46:37 +09:00
justsisyphus
1c76e0513a fix: add missing name property in loadBuiltinCommands causing TypeError on slashcommand 2026-01-25 12:46:03 +09:00
justsisyphus
c8cc94cd3c fix: remove github-copilot association from gpt-5-nano model mapping
explore agent uses opencode/gpt-5-nano exclusively — github-copilot
should not be associated with gpt-5-nano in docs, tests, or fallback chains.
2026-01-25 12:46:03 +09:00
Sisyphus
20cca35157 fix(ralph-loop): skip user messages in transcript completion detection (#622) (#1086)
* fix(ralph-loop): skip user messages in transcript completion detection (#622)

The transcript-based completion detection was searching the entire JSONL
file for <promise>DONE</promise>, including user message entries. The
RALPH_LOOP_TEMPLATE instructional text contains this literal pattern,
which gets recorded as a user message, causing false positive completion
detection on every iteration. This made the loop always terminate at
iteration 1.

Fix: Parse JSONL entries line-by-line and skip entries with type 'user'
so only tool_result/assistant entries are checked for the completion
promise. Also remove the hardcoded <promise>DONE</promise> from the
template exit conditions as defense-in-depth.

* chore: changes by sisyphus-dev-ai

---------

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
2026-01-25 12:34:42 +09:00
sisyphus-dev-ai
81d27afadb chore: changes by sisyphus-dev-ai 2026-01-25 03:27:56 +00:00
github-actions[bot]
6cb2f3031c @kvokka has signed the CLA in code-yeongyu/oh-my-opencode#1084 2026-01-25 03:14:31 +00:00
github-actions[bot]
f116ea1d43 @potb has signed the CLA in code-yeongyu/oh-my-opencode#1083 2026-01-25 02:38:28 +00:00
github-actions[bot]
6aa0674000 @jsl9208 has signed the CLA in code-yeongyu/oh-my-opencode#1082 2026-01-24 21:44:22 +00:00
github-actions[bot]
2b828624a0 @sadnow has signed the CLA in code-yeongyu/oh-my-opencode#1080 2026-01-24 20:49:38 +00:00
github-actions[bot]
e60ccb93fb @ThanhNguyxn has signed the CLA in code-yeongyu/oh-my-opencode#1075 2026-01-24 17:42:03 +00:00
justsisyphus
aa244e8098 docs: fix atlas agent name case in example config 2026-01-24 22:46:40 +09:00
github-actions[bot]
6f60f03433 @AamiRobin has signed the CLA in code-yeongyu/oh-my-opencode#1067 2026-01-24 13:28:32 +00:00
48 changed files with 847 additions and 369 deletions

View File

@@ -0,0 +1,342 @@
---
description: Remove unused code from this project with ultrawork mode, LSP-verified safety, atomic commits
---
<command-instruction>
You are a dead code removal specialist. Execute the FULL dead code removal workflow using ultrawork mode.
Your core weapon: **LSP FindReferences**. If a symbol has ZERO external references, it's dead. Remove it.
## CRITICAL RULES
1. **LSP is law.** Never guess. Always verify with `LspFindReferences` before removing ANYTHING.
2. **One removal = one commit.** Every dead code removal gets its own atomic commit.
3. **Test after every removal.** Run `bun test` after each. If it fails, REVERT and skip.
4. **Leaf-first order.** Remove deepest unused symbols first, then work up the dependency chain. Removing a leaf may expose new dead code upstream.
5. **Never remove entry points.** `src/index.ts`, `src/cli/index.ts`, test files, config files, and files in `packages/` are off-limits unless explicitly targeted.
---
## STEP 0: REGISTER TODO LIST (MANDATORY FIRST ACTION)
```
TodoWrite([
{"id": "scan", "content": "PHASE 1: Scan codebase for dead code candidates using LSP + explore agents", "status": "pending", "priority": "high"},
{"id": "verify", "content": "PHASE 2: Verify each candidate with LspFindReferences - zero false positives", "status": "pending", "priority": "high"},
{"id": "plan", "content": "PHASE 3: Plan removal order (leaf-first dependency order)", "status": "pending", "priority": "high"},
{"id": "remove", "content": "PHASE 4: Remove dead code one-by-one (remove -> test -> commit loop)", "status": "pending", "priority": "high"},
{"id": "final", "content": "PHASE 5: Final verification - full test suite + build + typecheck", "status": "pending", "priority": "high"}
])
```
---
## PHASE 1: SCAN FOR DEAD CODE CANDIDATES
**Mark scan as in_progress.**
### 1.1: Launch Parallel Explore Agents (ALL BACKGROUND)
Fire ALL simultaneously:
```
// Agent 1: Find all exported symbols
delegate_task(subagent_type="explore", run_in_background=true,
prompt="Find ALL exported functions, classes, types, interfaces, and constants across src/.
List each with: file path, line number, symbol name, export type (named/default).
EXCLUDE: src/index.ts root exports, test files.
Return as structured list.")
// Agent 2: Find potentially unused files
delegate_task(subagent_type="explore", run_in_background=true,
prompt="Find files in src/ that are NOT imported by any other file.
Check import/require statements across the entire codebase.
EXCLUDE: index.ts files, test files, entry points, config files, .md files.
Return list of potentially orphaned files.")
// Agent 3: Find unused imports within files
delegate_task(subagent_type="explore", run_in_background=true,
prompt="Find unused imports across src/**/*.ts files.
Look for import statements where the imported symbol is never referenced in the file body.
Return: file path, line number, imported symbol name.")
// Agent 4: Find functions/variables only used in their own declaration
delegate_task(subagent_type="explore", run_in_background=true,
prompt="Find private/non-exported functions, variables, and types in src/**/*.ts that appear
to have zero usage beyond their declaration. Return: file path, line number, symbol name.")
```
### 1.2: Direct AST-Grep Scans (WHILE AGENTS RUN)
```typescript
// Find unused imports pattern
ast_grep_search(pattern="import { $NAME } from '$PATH'", lang="typescript", paths=["src/"])
// Find empty export objects
ast_grep_search(pattern="export {}", lang="typescript", paths=["src/"])
```
### 1.3: Collect All Results
Collect background agent results. Compile into a master candidate list:
```
## DEAD CODE CANDIDATES
| # | File | Line | Symbol | Type | Confidence |
|---|------|------|--------|------|------------|
| 1 | src/foo.ts | 42 | unusedFunc | function | HIGH |
| 2 | src/bar.ts | 10 | OldType | type | MEDIUM |
```
**Mark scan as completed.**
---
## PHASE 2: VERIFY WITH LSP (ZERO FALSE POSITIVES)
**Mark verify as in_progress.**
For EVERY candidate from Phase 1, run this verification:
### 2.1: The LSP Verification Protocol
For each candidate symbol:
```typescript
// Step 1: Find the symbol's exact position
LspDocumentSymbols(filePath) // Get line/character of the symbol
// Step 2: Find ALL references across the ENTIRE workspace
LspFindReferences(filePath, line, character, includeDeclaration=false)
// includeDeclaration=false → only counts USAGES, not the definition itself
// Step 3: Evaluate
// 0 references → CONFIRMED DEAD CODE
// 1+ references → NOT dead, remove from candidate list
```
### 2.2: False Positive Guards
**NEVER mark as dead code if:**
- Symbol is in `src/index.ts` (package entry point)
- Symbol is in any `index.ts` that re-exports (barrel file check: look if it's re-exported)
- Symbol is referenced in test files (tests are valid consumers)
- Symbol has `@public` or `@api` JSDoc tags
- Symbol is in a file listed in `package.json` exports
- Symbol is a hook factory (`createXXXHook`) registered in `src/index.ts`
- Symbol is a tool factory (`createXXXTool`) registered in tool loading
- Symbol is an agent definition registered in `agentSources`
- File is a command template, skill definition, or MCP config
### 2.3: Build Confirmed Dead Code List
After verification, produce:
```
## CONFIRMED DEAD CODE (LSP-verified, 0 external references)
| # | File | Line | Symbol | Type | Safe to Remove |
|---|------|------|--------|------|----------------|
| 1 | src/foo.ts | 42 | unusedFunc | function | YES |
```
**If ZERO confirmed dead code found: Report "No dead code found" and STOP.**
**Mark verify as completed.**
---
## PHASE 3: PLAN REMOVAL ORDER
**Mark plan as in_progress.**
### 3.1: Dependency Analysis
For each confirmed dead symbol:
1. Check if removing it would expose other dead code
2. Check if other dead symbols depend on this one
3. Build removal dependency graph
### 3.2: Order by Leaf-First
```
Removal Order:
1. [Leaf symbols - no other dead code depends on them]
2. [Intermediate symbols - depended on only by already-removed dead code]
3. [Dead files - entire files with no live exports]
```
### 3.3: Register Granular Todos
Create one todo per removal:
```
TodoWrite([
{"id": "remove-1", "content": "Remove unusedFunc from src/foo.ts:42", "status": "pending", "priority": "high"},
{"id": "remove-2", "content": "Remove OldType from src/bar.ts:10", "status": "pending", "priority": "high"},
// ... one per confirmed dead symbol
])
```
**Mark plan as completed.**
---
## PHASE 4: ITERATIVE REMOVAL LOOP
**Mark remove as in_progress.**
For EACH dead code item, execute this exact loop:
### 4.1: Pre-Removal Check
```typescript
// Re-verify it's still dead (previous removals may have changed things)
LspFindReferences(filePath, line, character, includeDeclaration=false)
// If references > 0 now → SKIP (previous removal exposed a new consumer)
```
### 4.2: Remove the Dead Code
Use appropriate tool:
**For unused imports:**
```typescript
Edit(filePath, oldString="import { deadSymbol } from '...';\n", newString="")
// Or if it's one of many imports, remove just the symbol from the import list
```
**For unused functions/classes/types:**
```typescript
// Read the full symbol extent first
Read(filePath, offset=startLine, limit=endLine-startLine+1)
// Then remove it
Edit(filePath, oldString="[full symbol text]", newString="")
```
**For dead files:**
```bash
# Only after confirming ZERO imports point to this file
rm "path/to/dead-file.ts"
```
**After removal, also clean up:**
- Remove any imports that were ONLY used by the removed code
- Remove any now-empty import statements
- Fix any trailing whitespace / double blank lines left behind
### 4.3: Post-Removal Verification
```typescript
// 1. LSP diagnostics on changed file
LspDiagnostics(filePath, severity="error")
// Must be clean (or only pre-existing errors)
// 2. Run tests
bash("bun test")
// Must pass
// 3. Typecheck
bash("bun run typecheck")
// Must pass
```
### 4.4: Handle Failures
If ANY verification fails:
1. **REVERT** the change immediately (`git checkout -- [file]`)
2. Mark this removal todo as `cancelled` with note: "Removal caused [error]. Skipped."
3. Proceed to next item
### 4.5: Commit
```bash
git add [changed-files]
git commit -m "refactor: remove unused [symbolType] [symbolName] from [filePath]"
```
Mark this removal todo as `completed`.
### 4.6: Re-scan After Removal
After removing a symbol, check if its removal exposed NEW dead code:
- Were there imports that only existed to serve the removed symbol?
- Are there other symbols in the same file now unreferenced?
If new dead code is found, add it to the removal queue.
**Repeat 4.1-4.6 for every item. Mark remove as completed when done.**
---
## PHASE 5: FINAL VERIFICATION
**Mark final as in_progress.**
### 5.1: Full Test Suite
```bash
bun test
```
### 5.2: Full Typecheck
```bash
bun run typecheck
```
### 5.3: Full Build
```bash
bun run build
```
### 5.4: Summary Report
```markdown
## Dead Code Removal Complete
### Removed
| # | Symbol | File | Type | Commit |
|---|--------|------|------|--------|
| 1 | unusedFunc | src/foo.ts | function | abc1234 |
### Skipped (caused failures)
| # | Symbol | File | Reason |
|---|--------|------|--------|
| 1 | riskyFunc | src/bar.ts | Test failure: [details] |
### Verification
- Tests: PASSED (X/Y passing)
- Typecheck: CLEAN
- Build: SUCCESS
- Total dead code removed: N symbols across M files
- Total commits: K atomic commits
```
**Mark final as completed.**
---
## SCOPE CONTROL
**If $ARGUMENTS is provided**, narrow the scan to the specified scope:
- File path: Only scan that file
- Directory: Only scan that directory
- Symbol name: Only check that specific symbol
- "all" or empty: Full project scan (default)
## ABORT CONDITIONS
**STOP and report to user if:**
- 3 consecutive removals cause test failures
- Build breaks and cannot be fixed by reverting
- More than 50 candidates found (ask user to narrow scope)
## LANGUAGE
Use English for commit messages and technical output.
</command-instruction>
<user-request>
$ARGUMENTS
</user-request>

View File

@@ -1,7 +1,7 @@
# PROJECT KNOWLEDGE BASE
**Generated:** 2026-01-23T15:59:00+09:00
**Commit:** 599fad0e
**Generated:** 2026-01-25T13:10:00+09:00
**Commit:** 043b1a33
**Branch:** dev
## OVERVIEW
@@ -21,7 +21,7 @@ oh-my-opencode/
│ ├── cli/ # CLI installer, doctor - see src/cli/AGENTS.md
│ ├── mcp/ # Built-in MCPs - see src/mcp/AGENTS.md
│ ├── config/ # Zod schema, TypeScript types
│ └── index.ts # Main plugin entry (593 lines)
│ └── index.ts # Main plugin entry (601 lines)
├── script/ # build-schema.ts, build-binaries.ts
├── packages/ # 7 platform-specific binaries
└── dist/ # Build output (ESM + .d.ts)
@@ -36,6 +36,7 @@ oh-my-opencode/
| Add tool | `src/tools/` | Dir with index/types/constants/tools.ts |
| Add MCP | `src/mcp/` | Create config, add to index.ts |
| Add skill | `src/features/builtin-skills/` | Create dir with SKILL.md |
| Add command | `src/features/builtin-commands/` | Add template + register in commands.ts |
| Config schema | `src/config/schema.ts` | Zod schema, run `bun run build:schema` |
| Background agents | `src/features/background-agent/` | manager.ts (1335 lines) |
| Orchestrator | `src/hooks/atlas/` | Main orchestration hook (773 lines) |
@@ -60,7 +61,7 @@ oh-my-opencode/
- **Build**: `bun build` (ESM) + `tsc --emitDeclarationOnly`
- **Exports**: Barrel pattern via index.ts
- **Naming**: kebab-case dirs, `createXXXHook`/`createXXXTool` factories
- **Testing**: BDD comments, 90 test files
- **Testing**: BDD comments, 95 test files
- **Temperature**: 0.1 for code agents, max 0.3
## ANTI-PATTERNS
@@ -99,7 +100,7 @@ oh-my-opencode/
bun run typecheck # Type check
bun run build # ESM + declarations + schema
bun run rebuild # Clean + Build
bun test # 90 test files
bun test # 95 test files
```
## DEPLOYMENT
@@ -118,7 +119,11 @@ bun test # 90 test files
| `src/agents/prometheus-prompt.ts` | 1196 | Planning agent |
| `src/tools/delegate-task/tools.ts` | 1039 | Category-based delegation |
| `src/hooks/atlas/index.ts` | 773 | Orchestrator hook |
| `src/cli/config-manager.ts` | 641 | JSONC config parsing |
| `src/cli/config-manager.ts` | 664 | JSONC config parsing |
| `src/features/builtin-commands/templates/refactor.ts` | 619 | Refactor command template |
| `src/index.ts` | 601 | Main plugin entry |
| `src/tools/lsp/client.ts` | 596 | LSP JSON-RPC client |
| `src/agents/atlas.ts` | 572 | Atlas orchestrator agent |
## MCP ARCHITECTURE

View File

@@ -27,13 +27,13 @@
"typescript": "^5.7.3",
},
"optionalDependencies": {
"oh-my-opencode-darwin-arm64": "3.0.0-beta.16",
"oh-my-opencode-darwin-x64": "3.0.0-beta.16",
"oh-my-opencode-linux-arm64": "3.0.0-beta.16",
"oh-my-opencode-linux-arm64-musl": "3.0.0-beta.16",
"oh-my-opencode-linux-x64": "3.0.0-beta.16",
"oh-my-opencode-linux-x64-musl": "3.0.0-beta.16",
"oh-my-opencode-windows-x64": "3.0.0-beta.16",
"oh-my-opencode-darwin-arm64": "3.0.0",
"oh-my-opencode-darwin-x64": "3.0.0",
"oh-my-opencode-linux-arm64": "3.0.0",
"oh-my-opencode-linux-arm64-musl": "3.0.0",
"oh-my-opencode-linux-x64": "3.0.0",
"oh-my-opencode-linux-x64-musl": "3.0.0",
"oh-my-opencode-windows-x64": "3.0.0",
},
},
},
@@ -225,19 +225,19 @@
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.0.0-beta.16", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-1gfnTsKpYxTMXpbuV98wProR3RMe6BI/muuSVa3Xy68EEkBJsuRAne6IzFq/yxIMbx9OiQaS5cTE0mxFtxcCGA=="],
"oh-my-opencode-darwin-arm64": ["oh-my-opencode-darwin-arm64@3.0.0", "", { "os": "darwin", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-zelvb7qz5GsS+Dhyz9rACZrkUMtWbAZGijiHSQqmRcjlN/sRPNhXtsL55VheDjlPM3VP+t3+psv+se0WA/aw5w=="],
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.0.0-beta.16", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-/h7kBZAN5Ut9kL7gEtwVVZ49Kw4gZoSVJdrpnh7Wij0a3mlOwqbkgGilK7oUiJ2N8fsxvxEBbTscYOLAdhyVBw=="],
"oh-my-opencode-darwin-x64": ["oh-my-opencode-darwin-x64@3.0.0", "", { "os": "darwin", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-dRMD1U5zIrb6BsiKQJZtAFtuD8clAQquZyU2LajMoFTHBNhcBDIgsaBBwvMBIq7dTe8rnFq91ExiFA8OfdrzBA=="],
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.0.0-beta.16", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-jW7pl76WerBa7FucKCYcthpbKbhJQSVe6rqUFSbVobjOP9VWslrGdxc9Y8BeiMx9SJEFYwA8/2ROhnOHpH3TxA=="],
"oh-my-opencode-linux-arm64": ["oh-my-opencode-linux-arm64@3.0.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-Wx6Cx2Nu2T69mfZa3FQ3gk0OFONvMh48rMVYK0Cp8VX5W4Zb/GZgTUFmZlYsApyxqP+7J9m18skd46qPOhzuEQ=="],
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.0.0-beta.16", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-cXXka0zQDBiFu9mmxa45o3g812w8q/jZRYgdwJsLbj3nm24WXv6uRP7nnVVoZiVmJ2GQbLE1nyGCMkBXFwRGGA=="],
"oh-my-opencode-linux-arm64-musl": ["oh-my-opencode-linux-arm64-musl@3.0.0", "", { "os": "linux", "cpu": "arm64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-mfOlptgLoXLVuhFRcXgZU7BYGuL1axZOMOOjONgncNzOp/BQYU5B9BRFihBUXdDsWGmeMiLowrYGBhVpSv3NlA=="],
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.0.0-beta.16", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-4VS1V6DiXdWHQ/AGc3rB1sCxFUlD1REex0Ai/y4tEgA2M0FD0Bu+tjXHhDghUvC8f0kQBRfijnTrtc1Lh7hIrA=="],
"oh-my-opencode-linux-x64": ["oh-my-opencode-linux-x64@3.0.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-vVjshfaz0UC9NrGD9FfjlYK5NvckIW0sZaE/wRv/LKjrukHFH1jJpJa5KKXxBWLsEJjt6ooJRguXXxtfNXpAWw=="],
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.0.0-beta.16", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-PGVe7vyUK3hjSNfvu1fBXTbgbe0OPh7JgB/TZR2U5R54X1k3NBkb1VHX9yxEUSA0VsNR+inE2x+DfEA+7KIruQ=="],
"oh-my-opencode-linux-x64-musl": ["oh-my-opencode-linux-x64-musl@3.0.0", "", { "os": "linux", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode" } }, "sha512-N6cNJ7+Dj0a5dWqPf6OKfB39o8HWw5HQ3hB4omgYqc6Gzo6nChA4KIiVefEC3+tIL98x4XvMeD7OU+UYgwxHnQ=="],
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.0.0-beta.16", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-1lN/8y4laQnSJDvyARuV5YaETAwBb+PK06QHQzpoK/0asiFoEIBcKNgjaRwau+nBsdRUrQocE2xc6g2ZNH4HUw=="],
"oh-my-opencode-windows-x64": ["oh-my-opencode-windows-x64@3.0.0", "", { "os": "win32", "cpu": "x64", "bin": { "oh-my-opencode": "bin/oh-my-opencode.exe" } }, "sha512-TaC0hiHpnsS42GWTVUKoTwCb+QzNLBlQtTkIQ0PjlkDYFjlEC2LuR2FFcscik055PRRIGishyB9A1n/8XAgcvA=="],
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],

View File

@@ -70,12 +70,12 @@ A Skill is a mechanism that injects **specialized knowledge (Context)** and **to
### Usage
Add desired skill names to the `skills` array.
Add desired skill names to the `load_skills` array.
```typescript
delegate_task(
category="quick",
skills=["git-master"],
load_skills=["git-master"],
prompt="Commit current changes. Follow commit message style."
)
```
@@ -110,17 +110,17 @@ You can create powerful specialized agents by combining Categories and Skills.
### 🎨 The Designer (UI Implementation)
- **Category**: `visual-engineering`
- **Skills**: `["frontend-ui-ux", "playwright"]`
- **load_skills**: `["frontend-ui-ux", "playwright"]`
- **Effect**: Implements aesthetic UI and verifies rendering results directly in browser.
### 🏗️ The Architect (Design Review)
- **Category**: `ultrabrain`
- **Skills**: `[]` (pure reasoning)
- **load_skills**: `[]` (pure reasoning)
- **Effect**: Leverages GPT-5.2's logical reasoning for in-depth system architecture analysis.
### ⚡ The Maintainer (Quick Fixes)
- **Category**: `quick`
- **Skills**: `["git-master"]`
- **load_skills**: `["git-master"]`
- **Effect**: Uses cost-effective models to quickly fix code and generate clean commits.
---
@@ -131,7 +131,7 @@ When delegating, **clear and specific** prompts are essential. Include these 7 e
1. **TASK**: What needs to be done? (single objective)
2. **EXPECTED OUTCOME**: What is the deliverable?
3. **REQUIRED SKILLS**: Which skills should be used?
3. **REQUIRED SKILLS**: Which skills should be loaded via `load_skills`?
4. **REQUIRED TOOLS**: Which tools must be used? (whitelist)
5. **MUST DO**: What must be done (constraints)
6. **MUST NOT DO**: What must never be done

View File

@@ -83,7 +83,7 @@ When both `oh-my-opencode.jsonc` and `oh-my-opencode.json` files exist, `.jsonc`
## Google Auth
**Recommended**: For Google Gemini authentication, install the [`opencode-antigravity-auth`](https://github.com/NoeFabris/opencode-antigravity-auth) plugin. It provides multi-account load balancing, more models (including Claude via Antigravity), and active maintenance. See [Installation > Google Gemini](../README.md#google-gemini-antigravity-oauth).
**Recommended**: For Google Gemini authentication, install the [`opencode-antigravity-auth`](https://github.com/NoeFabris/opencode-antigravity-auth) plugin (`@latest`). It provides multi-account load balancing, variant-based thinking levels, dual quota system (Antigravity + Gemini CLI), and active maintenance. See [Installation > Google Gemini](docs/guide/installation.md#google-gemini-antigravity-oauth).
## Agents
@@ -160,7 +160,7 @@ Available agents: `oracle`, `librarian`, `explore`, `multimodal-looker`
Oh My OpenCode includes built-in skills that provide additional capabilities:
- **playwright**: Browser automation with Playwright MCP. Use for web scraping, testing, screenshots, and browser interactions.
- **git-master**: Git expert for atomic commits, rebase/squash, and history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with `delegate_task(category='quick', skills=['git-master'], ...)` to save context.
- **git-master**: Git expert for atomic commits, rebase/squash, and history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with `delegate_task(category='quick', load_skills=['git-master'], ...)` to save context.
Disable built-in skills via `disabled_skills` in `~/.config/opencode/oh-my-opencode.json` or `.opencode/oh-my-opencode.json`:
@@ -404,7 +404,7 @@ Each agent has a defined provider priority chain. The system tries providers in
| **Sisyphus** | `claude-opus-4-5` | anthropic → github-copilot → opencode → antigravity → google |
| **oracle** | `gpt-5.2` | openai → anthropic → google → github-copilot → opencode |
| **librarian** | `big-pickle` | opencode → github-copilot → anthropic |
| **explore** | `gpt-5-nano` | opencode → anthropic → github-copilot |
| **explore** | `gpt-5-nano` | anthropic → opencode |
| **multimodal-looker** | `gemini-3-flash` | google → openai → zai-coding-plan → anthropic → opencode |
| **Prometheus (Planner)** | `claude-opus-4-5` | anthropic → github-copilot → opencode → antigravity → google |
| **Metis (Plan Consultant)** | `claude-sonnet-4-5` | anthropic → github-copilot → opencode → antigravity → google |

View File

@@ -132,7 +132,7 @@ First, add the opencode-antigravity-auth plugin:
{
"plugin": [
"oh-my-opencode",
"opencode-antigravity-auth@1.2.8"
"opencode-antigravity-auth@latest"
]
}
```
@@ -140,7 +140,7 @@ First, add the opencode-antigravity-auth plugin:
##### Model Configuration
You'll also need full model settings in `opencode.json`.
Read the [opencode-antigravity-auth documentation](https://github.com/NoeFabris/opencode-antigravity-auth), copy provider/models config from the README, and merge carefully to avoid breaking the user's existing setup.
Read the [opencode-antigravity-auth documentation](https://github.com/NoeFabris/opencode-antigravity-auth), copy the full model configuration from the README, and merge carefully to avoid breaking the user's existing setup. The plugin now uses a **variant system** — models like `antigravity-gemini-3-pro` support `low`/`high` variants instead of separate `-low`/`-high` model entries.
##### oh-my-opencode Agent Model Override
@@ -154,7 +154,17 @@ The `opencode-antigravity-auth` plugin uses different model names than the built
}
```
**Available model names**: `google/antigravity-gemini-3-pro-high`, `google/antigravity-gemini-3-pro-low`, `google/antigravity-gemini-3-flash`, `google/antigravity-claude-sonnet-4-5`, `google/antigravity-claude-sonnet-4-5-thinking-low`, `google/antigravity-claude-sonnet-4-5-thinking-medium`, `google/antigravity-claude-sonnet-4-5-thinking-high`, `google/antigravity-claude-opus-4-5-thinking-low`, `google/antigravity-claude-opus-4-5-thinking-medium`, `google/antigravity-claude-opus-4-5-thinking-high`, `google/gemini-3-pro`, `google/gemini-3-flash`, `google/gemini-2.5-pro`, `google/gemini-2.5-flash`
**Available models (Antigravity quota)**:
- `google/antigravity-gemini-3-pro` — variants: `low`, `high`
- `google/antigravity-gemini-3-flash` — variants: `minimal`, `low`, `medium`, `high`
- `google/antigravity-claude-sonnet-4-5` — no variants
- `google/antigravity-claude-sonnet-4-5-thinking` — variants: `low`, `max`
- `google/antigravity-claude-opus-4-5-thinking` — variants: `low`, `max`
**Available models (Gemini CLI quota)**:
- `google/gemini-2.5-flash`, `google/gemini-2.5-pro`, `google/gemini-3-flash-preview`, `google/gemini-3-pro-preview`
> **Note**: Legacy tier-suffixed names like `google/antigravity-gemini-3-pro-high` still work but variants are recommended. Use `--variant=high` with the base model name instead.
Then authenticate:
@@ -183,7 +193,7 @@ When GitHub Copilot is the best available provider, oh-my-opencode uses these mo
| ------------- | -------------------------------- |
| **Sisyphus** | `github-copilot/claude-opus-4.5` |
| **Oracle** | `github-copilot/gpt-5.2` |
| **Explore** | `github-copilot/gpt-5-nano-fast-1`|
| **Explore** | `opencode/gpt-5-nano` |
| **Librarian** | `zai-coding-plan/glm-4.7` (if Z.ai available) or fallback |
GitHub Copilot acts as a proxy provider, routing requests to underlying models based on your subscription.

View File

@@ -128,7 +128,7 @@ Here's a real-world config for a user with **Claude, OpenAI, Gemini, and Z.ai**
"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json",
"agents": {
// Override specific agents only - rest use fallback chain
"Atlas": { "model": "anthropic/claude-sonnet-4-5", "variant": "max" },
"atlas": { "model": "anthropic/claude-sonnet-4-5", "variant": "max" },
"librarian": { "model": "zai-coding-plan/glm-4.7" },
"explore": { "model": "opencode/gpt-5-nano" },
"multimodal-looker": { "model": "zai-coding-plan/glm-4.6v" }

View File

@@ -326,13 +326,13 @@ Skills prepend specialized instructions to subagent prompts:
// Category + Skill combination
delegate_task(
category="visual-engineering",
skills=["frontend-ui-ux"], // Adds UI/UX expertise
load_skills=["frontend-ui-ux"], // Adds UI/UX expertise
prompt="..."
)
delegate_task(
category="general",
skills=["playwright"], // Adds browser automation expertise
load_skills=["playwright"], // Adds browser automation expertise
prompt="..."
)
```
@@ -341,8 +341,8 @@ delegate_task(
| Before | After |
|--------|-------|
| Hardcoded: `frontend-ui-ux-engineer` (Gemini 3 Pro) | `category="visual-engineering" + skills=["frontend-ui-ux"]` |
| One-size-fits-all | `category="visual-engineering" + skills=["unity-master"]` |
| Hardcoded: `frontend-ui-ux-engineer` (Gemini 3 Pro) | `category="visual-engineering" + load_skills=["frontend-ui-ux"]` |
| One-size-fits-all | `category="visual-engineering" + load_skills=["unity-master"]` |
| Model bias | Category-based: model abstraction eliminates bias |
---
@@ -365,7 +365,7 @@ sequenceDiagram
Note over Orchestrator: Prompt Structure:<br/>1. TASK (exact checkbox)<br/>2. EXPECTED OUTCOME<br/>3. REQUIRED SKILLS<br/>4. REQUIRED TOOLS<br/>5. MUST DO<br/>6. MUST NOT DO<br/>7. CONTEXT + Wisdom
Orchestrator->>Junior: delegate_task(category, skills, prompt)
Orchestrator->>Junior: delegate_task(category, load_skills, prompt)
Junior->>Junior: Create todos, execute
Junior->>Junior: Verify (lsp_diagnostics, tests)

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode",
"version": "3.0.0",
"version": "3.0.1",
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
"main": "dist/index.js",
"types": "dist/index.d.ts",
@@ -73,13 +73,13 @@
"typescript": "^5.7.3"
},
"optionalDependencies": {
"oh-my-opencode-darwin-arm64": "3.0.0",
"oh-my-opencode-darwin-x64": "3.0.0",
"oh-my-opencode-linux-arm64": "3.0.0",
"oh-my-opencode-linux-arm64-musl": "3.0.0",
"oh-my-opencode-linux-x64": "3.0.0",
"oh-my-opencode-linux-x64-musl": "3.0.0",
"oh-my-opencode-windows-x64": "3.0.0"
"oh-my-opencode-darwin-arm64": "3.0.1",
"oh-my-opencode-darwin-x64": "3.0.1",
"oh-my-opencode-linux-arm64": "3.0.1",
"oh-my-opencode-linux-arm64-musl": "3.0.1",
"oh-my-opencode-linux-x64": "3.0.1",
"oh-my-opencode-linux-x64-musl": "3.0.1",
"oh-my-opencode-windows-x64": "3.0.1"
},
"trustedDependencies": [
"@ast-grep/cli",

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-darwin-arm64",
"version": "3.0.0",
"version": "3.0.1",
"description": "Platform-specific binary for oh-my-opencode (darwin-arm64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-darwin-x64",
"version": "3.0.0",
"version": "3.0.1",
"description": "Platform-specific binary for oh-my-opencode (darwin-x64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-arm64-musl",
"version": "3.0.0",
"version": "3.0.1",
"description": "Platform-specific binary for oh-my-opencode (linux-arm64-musl)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-arm64",
"version": "3.0.0",
"version": "3.0.1",
"description": "Platform-specific binary for oh-my-opencode (linux-arm64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64-musl",
"version": "3.0.0",
"version": "3.0.1",
"description": "Platform-specific binary for oh-my-opencode (linux-x64-musl)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-linux-x64",
"version": "3.0.0",
"version": "3.0.1",
"description": "Platform-specific binary for oh-my-opencode (linux-x64)",
"license": "MIT",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode-windows-x64",
"version": "3.0.0",
"version": "3.0.1",
"description": "Platform-specific binary for oh-my-opencode (windows-x64)",
"license": "MIT",
"repository": {

View File

@@ -767,6 +767,54 @@
"created_at": "2026-01-24T04:41:46Z",
"repoId": 1108837393,
"pullRequestNo": 1042
},
{
"name": "AamiRobin",
"id": 22963668,
"comment_id": 3794632200,
"created_at": "2026-01-24T13:28:22Z",
"repoId": 1108837393,
"pullRequestNo": 1067
},
{
"name": "ThanhNguyxn",
"id": 74597207,
"comment_id": 3795232176,
"created_at": "2026-01-24T17:41:53Z",
"repoId": 1108837393,
"pullRequestNo": 1075
},
{
"name": "sadnow",
"id": 87896100,
"comment_id": 3795495342,
"created_at": "2026-01-24T20:49:29Z",
"repoId": 1108837393,
"pullRequestNo": 1080
},
{
"name": "jsl9208",
"id": 4048787,
"comment_id": 3795582626,
"created_at": "2026-01-24T21:41:24Z",
"repoId": 1108837393,
"pullRequestNo": 1082
},
{
"name": "potb",
"id": 10779093,
"comment_id": 3795856573,
"created_at": "2026-01-25T02:38:16Z",
"repoId": 1108837393,
"pullRequestNo": 1083
},
{
"name": "kvokka",
"id": 15954013,
"comment_id": 3795884358,
"created_at": "2026-01-25T03:13:52Z",
"repoId": 1108837393,
"pullRequestNo": 1084
}
]
}

View File

@@ -239,7 +239,7 @@ Ask yourself:
I will use delegate_task with:
- **Category**: [selected-category-name]
- **Why this category**: [how category description matches task domain]
- **Skills**: [list of selected skills]
- **load_skills**: [list of selected skills]
- **Skill evaluation**:
- [skill-1]: INCLUDED because [reason based on skill description]
- [skill-2]: OMITTED because [reason why skill domain doesn't apply]
@@ -256,7 +256,7 @@ I will use delegate_task with:
I will use delegate_task with:
- **Category**: [category-name]
- **Why this category**: Category description says "[quote description]" which matches this task's requirements
- **Skills**: ["skill-a", "skill-b"]
- **load_skills**: ["skill-a", "skill-b"]
- **Skill evaluation**:
- skill-a: INCLUDED - description says "[quote]" which applies to this task
- skill-b: INCLUDED - description says "[quote]" which is needed here
@@ -265,7 +265,7 @@ I will use delegate_task with:
delegate_task(
category="[category-name]",
skills=["skill-a", "skill-b"],
load_skills=["skill-a", "skill-b"],
prompt="..."
)
```
@@ -276,12 +276,12 @@ delegate_task(
I will use delegate_task with:
- **Agent**: [agent-name]
- **Reason**: This requires [agent's specialty] based on agent description
- **Skills**: [] (agents have built-in expertise)
- **load_skills**: [] (agents have built-in expertise)
- **Expected Outcome**: [what agent should return]
delegate_task(
subagent_type="[agent-name]",
skills=[],
load_skills=[],
prompt="..."
)
```
@@ -292,13 +292,13 @@ delegate_task(
I will use delegate_task with:
- **Agent**: explore
- **Reason**: Need to find all authentication implementations across the codebase - this is contextual grep
- **Skills**: []
- **load_skills**: []
- **Expected Outcome**: List of files containing auth patterns
delegate_task(
subagent_type="explore",
run_in_background=true,
skills=[],
load_skills=[],
prompt="Find all authentication implementations in the codebase"
)
```
@@ -306,7 +306,7 @@ delegate_task(
**WRONG: No Skill Evaluation**
```
delegate_task(category="...", skills=[], prompt="...") // Where's the justification?
delegate_task(category="...", load_skills=[], prompt="...") // Where's the justification?
```
**WRONG: Vague Category Selection**
@@ -329,11 +329,11 @@ I'll use this category because it seems right.
```typescript
// CORRECT: Always background, always parallel
// Contextual Grep (internal)
delegate_task(subagent_type="explore", run_in_background=true, skills=[], prompt="Find auth implementations in our codebase...")
delegate_task(subagent_type="explore", run_in_background=true, skills=[], prompt="Find error handling patterns here...")
delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="Find auth implementations in our codebase...")
delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="Find error handling patterns here...")
// Reference Grep (external)
delegate_task(subagent_type="librarian", run_in_background=true, skills=[], prompt="Find JWT best practices in official docs...")
delegate_task(subagent_type="librarian", run_in_background=true, skills=[], prompt="Find how production apps handle auth in Express...")
delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="Find JWT best practices in official docs...")
delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="Find how production apps handle auth in Express...")
// Continue working immediately. Collect with background_output when needed.
// WRONG: Sequential or blocking
@@ -416,7 +416,7 @@ Skills inject specialized instructions into the subagent. Read the description t
For EVERY skill listed above, ask yourself:
> "Does this skill's expertise domain overlap with my task?"
- If YES → INCLUDE in `skills=[...]`
- If YES → INCLUDE in `load_skills=[...]`
- If NO → You MUST justify why (see below)
**STEP 3: Justify Omissions**
@@ -444,14 +444,14 @@ SKILL EVALUATION for "[skill-name]":
```typescript
delegate_task(
category="[selected-category]",
skills=["skill-1", "skill-2"], // Include ALL relevant skills
load_skills=["skill-1", "skill-2"], // Include ALL relevant skills
prompt="..."
)
```
**ANTI-PATTERN (will produce poor results):**
```typescript
delegate_task(category="...", skills=[], prompt="...") // Empty skills without justification
delegate_task(category="...", load_skills=[], prompt="...") // Empty load_skills without justification
```
### Delegation Table:
@@ -724,7 +724,7 @@ If the user's approach seems problematic:
| **Error Handling** | Empty catch blocks `catch(e) {}` |
| **Testing** | Deleting failing tests to "pass" |
| **Search** | Firing agents for single-line typos or obvious syntax errors |
| **Delegation** | Using `skills=[]` without justifying why no skills apply |
| **Delegation** | Using `load_skills=[]` without justifying why no skills apply |
| **Debugging** | Shotgun debugging, random changes |
## Soft Guidelines

View File

@@ -8,17 +8,17 @@
```
agents/
├── atlas.ts # Master Orchestrator (543 lines)
├── sisyphus.ts # Main prompt (615 lines)
├── sisyphus-junior.ts # Delegated task executor
├── dynamic-agent-prompt-builder.ts # Dynamic prompt generation
├── atlas.ts # Master Orchestrator (572 lines)
├── sisyphus.ts # Main prompt (450 lines)
├── sisyphus-junior.ts # Delegated task executor (135 lines)
├── dynamic-agent-prompt-builder.ts # Dynamic prompt generation (359 lines)
├── oracle.ts # Strategic advisor (GPT-5.2)
├── librarian.ts # Multi-repo research (GLM-4.7-free)
├── librarian.ts # Multi-repo research (326 lines)
├── explore.ts # Fast grep (Grok Code)
├── multimodal-looker.ts # Media analyzer (Gemini 3 Flash)
├── prometheus-prompt.ts # Planning (1196 lines)
├── metis.ts # Plan consultant
├── momus.ts # Plan reviewer
├── metis.ts # Plan consultant (315 lines)
├── momus.ts # Plan reviewer (444 lines)
├── types.ts # AgentModelConfig, AgentPromptMetadata
├── utils.ts # createBuiltinAgents(), resolveModelWithFallback()
└── index.ts # builtinAgents export

View File

@@ -58,7 +58,7 @@ Categories spawn \`Sisyphus-Junior-{category}\` with optimized settings:
${categoryRows.join("\n")}
\`\`\`typescript
delegate_task(category="[category-name]", skills=[...], prompt="...")
delegate_task(category="[category-name]", load_skills=[...], prompt="...")
\`\`\``
}
@@ -84,12 +84,12 @@ ${skillRows.join("\n")}
**MANDATORY: Evaluate ALL skills for relevance to your task.**
Read each skill's description and ask: "Does this skill's domain overlap with my task?"
- If YES: INCLUDE in skills=[...]
- If YES: INCLUDE in load_skills=[...]
- If NO: You MUST justify why in your pre-delegation declaration
**Usage:**
\`\`\`typescript
delegate_task(category="[category]", skills=["skill-1", "skill-2"], prompt="...")
delegate_task(category="[category]", load_skills=["skill-1", "skill-2"], prompt="...")
\`\`\`
**IMPORTANT:**
@@ -102,7 +102,7 @@ function buildDecisionMatrix(agents: AvailableAgent[], userCategories?: Record<s
const allCategories = { ...DEFAULT_CATEGORIES, ...userCategories }
const categoryRows = Object.entries(allCategories).map(([name]) =>
`| ${getCategoryDescription(name, userCategories)} | \`category="${name}", skills=[...]\` |`
`| ${getCategoryDescription(name, userCategories)} | \`category="${name}", load_skills=[...]\` |`
)
const agentRows = agents.map((a) => {
@@ -323,7 +323,7 @@ delegate_task(
**If verification fails**: Resume the SAME session with the ACTUAL error output:
\`\`\`typescript
delegate_task(
resume="ses_xyz789", // ALWAYS use the session from the failed task
session_id="ses_xyz789", // ALWAYS use the session from the failed task
load_skills=[...],
prompt="Verification failed: {actual error}. Fix."
)
@@ -331,24 +331,24 @@ delegate_task(
### 3.5 Handle Failures (USE RESUME)
**CRITICAL: When re-delegating, ALWAYS use \`resume\` parameter.**
**CRITICAL: When re-delegating, ALWAYS use \`session_id\` parameter.**
Every \`delegate_task()\` output includes a session_id. STORE IT.
If task fails:
1. Identify what went wrong
2. **Resume the SAME session** - subagent has full context already:
\`\`\`typescript
delegate_task(
resume="ses_xyz789", // Session from failed task
load_skills=[...],
prompt="FAILED: {error}. Fix by: {specific instruction}"
)
\`\`\`
\`\`\`typescript
delegate_task(
session_id="ses_xyz789", // Session from failed task
load_skills=[...],
prompt="FAILED: {error}. Fix by: {specific instruction}"
)
\`\`\`
3. Maximum 3 retry attempts with the SAME session
4. If blocked after 3 attempts: Document and continue to independent tasks
**Why resume is MANDATORY for failures:**
**Why session_id is MANDATORY for failures:**
- Subagent already read all files, knows the context
- No repeated exploration = 70%+ token savings
- Subagent knows what approaches already failed
@@ -493,7 +493,7 @@ You are the QA gate. Subagents lie. Verify EVERYTHING.
- Parallelize independent tasks
- Verify with your own tools
- **Store session_id from every delegation output**
- **Use \`resume="{session_id}"\` for retries, fixes, and follow-ups**
- **Use \`session_id="{session_id}"\` for retries, fixes, and follow-ups**
</critical_overrides>
`

View File

@@ -144,11 +144,11 @@ ${librarianSection}
\`\`\`typescript
// CORRECT: Always background, always parallel
// Contextual Grep (internal)
delegate_task(subagent_type="explore", run_in_background=true, skills=[], prompt="Find auth implementations in our codebase...")
delegate_task(subagent_type="explore", run_in_background=true, skills=[], prompt="Find error handling patterns here...")
delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="Find auth implementations in our codebase...")
delegate_task(subagent_type="explore", run_in_background=true, load_skills=[], prompt="Find error handling patterns here...")
// Reference Grep (external)
delegate_task(subagent_type="librarian", run_in_background=true, skills=[], prompt="Find JWT best practices in official docs...")
delegate_task(subagent_type="librarian", run_in_background=true, skills=[], prompt="Find how production apps handle auth in Express...")
delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="Find JWT best practices in official docs...")
delegate_task(subagent_type="librarian", run_in_background=true, load_skills=[], prompt="Find how production apps handle auth in Express...")
// Continue working immediately. Collect with background_output when needed.
// WRONG: Sequential or blocking
@@ -209,15 +209,15 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING:
Every \`delegate_task()\` output includes a session_id. **USE IT.**
**ALWAYS resume when:**
**ALWAYS continue when:**
| Scenario | Action |
|----------|--------|
| Task failed/incomplete | \`resume="{session_id}", prompt="Fix: {specific error}"\` |
| Follow-up question on result | \`resume="{session_id}", prompt="Also: {question}"\` |
| Multi-turn with same agent | \`resume="{session_id}"\` - NEVER start fresh |
| Verification failed | \`resume="{session_id}", prompt="Failed verification: {error}. Fix."\` |
| Task failed/incomplete | \`session_id="{session_id}", prompt="Fix: {specific error}"\` |
| Follow-up question on result | \`session_id="{session_id}", prompt="Also: {question}"\` |
| Multi-turn with same agent | \`session_id="{session_id}"\` - NEVER start fresh |
| Verification failed | \`session_id="{session_id}", prompt="Failed verification: {error}. Fix."\` |
**Why resume is CRITICAL:**
**Why session_id is CRITICAL:**
- Subagent has FULL conversation context preserved
- No repeated file reads, exploration, or setup
- Saves 70%+ tokens on follow-ups
@@ -228,10 +228,10 @@ Every \`delegate_task()\` output includes a session_id. **USE IT.**
delegate_task(category="quick", prompt="Fix the type error in auth.ts...")
// CORRECT: Resume preserves everything
delegate_task(resume="ses_abc123", prompt="Fix: Type error on line 42")
delegate_task(session_id="ses_abc123", prompt="Fix: Type error on line 42")
\`\`\`
**After EVERY delegation, STORE the session_id for potential resume.**
**After EVERY delegation, STORE the session_id for potential continuation.**
### Code Changes:
- Match existing patterns (if codebase is disciplined)

View File

@@ -10,8 +10,9 @@ CLI entry: `bunx oh-my-opencode`. Interactive installer, doctor diagnostics. Com
cli/
├── index.ts # Commander.js entry
├── install.ts # Interactive TUI (520 lines)
├── config-manager.ts # JSONC parsing (641 lines)
├── config-manager.ts # JSONC parsing (664 lines)
├── types.ts # InstallArgs, InstallConfig
├── model-fallback.ts # Model fallback configuration
├── doctor/
│ ├── index.ts # Doctor entry
│ ├── runner.ts # Check orchestration
@@ -25,6 +26,7 @@ cli/
│ ├── dependencies.ts # AST-Grep, Comment Checker
│ ├── lsp.ts # LSP connectivity
│ ├── mcp.ts # MCP validation
│ ├── model-resolution.ts # Model resolution check
│ └── gh.ts # GitHub CLI
├── run/
│ └── index.ts # Session launcher

View File

@@ -170,7 +170,7 @@ describe("fetchNpmDistTags", () => {
})
describe("config-manager ANTIGRAVITY_PROVIDER_CONFIG", () => {
test("Gemini models include full spec (limit + modalities)", () => {
test("all models include full spec (limit + modalities + Antigravity label)", () => {
const google = (ANTIGRAVITY_PROVIDER_CONFIG as any).google
expect(google).toBeTruthy()
@@ -178,9 +178,11 @@ describe("config-manager ANTIGRAVITY_PROVIDER_CONFIG", () => {
expect(models).toBeTruthy()
const required = [
"antigravity-gemini-3-pro-high",
"antigravity-gemini-3-pro-low",
"antigravity-gemini-3-pro",
"antigravity-gemini-3-flash",
"antigravity-claude-sonnet-4-5",
"antigravity-claude-sonnet-4-5-thinking",
"antigravity-claude-opus-4-5-thinking",
]
for (const key of required) {
@@ -198,6 +200,43 @@ describe("config-manager ANTIGRAVITY_PROVIDER_CONFIG", () => {
expect(Array.isArray(model.modalities.output)).toBe(true)
}
})
test("Gemini models have variant definitions", () => {
// #given the antigravity provider config
const models = (ANTIGRAVITY_PROVIDER_CONFIG as any).google.models as Record<string, any>
// #when checking Gemini Pro variants
const pro = models["antigravity-gemini-3-pro"]
// #then should have low and high variants
expect(pro.variants).toBeTruthy()
expect(pro.variants.low).toBeTruthy()
expect(pro.variants.high).toBeTruthy()
// #when checking Gemini Flash variants
const flash = models["antigravity-gemini-3-flash"]
// #then should have minimal, low, medium, high variants
expect(flash.variants).toBeTruthy()
expect(flash.variants.minimal).toBeTruthy()
expect(flash.variants.low).toBeTruthy()
expect(flash.variants.medium).toBeTruthy()
expect(flash.variants.high).toBeTruthy()
})
test("Claude thinking models have variant definitions", () => {
// #given the antigravity provider config
const models = (ANTIGRAVITY_PROVIDER_CONFIG as any).google.models as Record<string, any>
// #when checking Claude thinking variants
const sonnetThinking = models["antigravity-claude-sonnet-4-5-thinking"]
const opusThinking = models["antigravity-claude-opus-4-5-thinking"]
// #then both should have low and max variants
for (const model of [sonnetThinking, opusThinking]) {
expect(model.variants).toBeTruthy()
expect(model.variants.low).toBeTruthy()
expect(model.variants.max).toBeTruthy()
}
})
})
describe("generateOmoConfig - model fallback system", () => {

View File

@@ -497,38 +497,61 @@ export async function runBunInstallWithDetails(): Promise<BunInstallResult> {
*
* IMPORTANT: Model names MUST use `antigravity-` prefix for stability.
*
* The opencode-antigravity-auth plugin supports two naming conventions:
* - `antigravity-gemini-3-pro-high` (RECOMMENDED, explicit Antigravity quota routing)
* - `gemini-3-pro-high` (LEGACY, backward compatible but may break in future)
* Since opencode-antigravity-auth v1.3.0, models use a variant system:
* - `antigravity-gemini-3-pro` with variants: low, high
* - `antigravity-gemini-3-flash` with variants: minimal, low, medium, high
*
* Legacy names rely on Gemini CLI using `-preview` suffix for disambiguation.
* If Google removes `-preview`, legacy names may route to wrong quota.
* Legacy tier-suffixed names (e.g., `antigravity-gemini-3-pro-high`) still work
* but variants are the recommended approach.
*
* @see https://github.com/NoeFabris/opencode-antigravity-auth#migration-guide-v127
* @see https://github.com/NoeFabris/opencode-antigravity-auth#models
*/
export const ANTIGRAVITY_PROVIDER_CONFIG = {
google: {
name: "Google",
models: {
"antigravity-gemini-3-pro-high": {
name: "Gemini 3 Pro High (Antigravity)",
thinking: true,
attachment: true,
limit: { context: 1048576, output: 65535 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
},
"antigravity-gemini-3-pro-low": {
name: "Gemini 3 Pro Low (Antigravity)",
thinking: true,
attachment: true,
"antigravity-gemini-3-pro": {
name: "Gemini 3 Pro (Antigravity)",
limit: { context: 1048576, output: 65535 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
variants: {
low: { thinkingLevel: "low" },
high: { thinkingLevel: "high" },
},
},
"antigravity-gemini-3-flash": {
name: "Gemini 3 Flash (Antigravity)",
attachment: true,
limit: { context: 1048576, output: 65536 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
variants: {
minimal: { thinkingLevel: "minimal" },
low: { thinkingLevel: "low" },
medium: { thinkingLevel: "medium" },
high: { thinkingLevel: "high" },
},
},
"antigravity-claude-sonnet-4-5": {
name: "Claude Sonnet 4.5 (Antigravity)",
limit: { context: 200000, output: 64000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
},
"antigravity-claude-sonnet-4-5-thinking": {
name: "Claude Sonnet 4.5 Thinking (Antigravity)",
limit: { context: 200000, output: 64000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
variants: {
low: { thinkingConfig: { thinkingBudget: 8192 } },
max: { thinkingConfig: { thinkingBudget: 32768 } },
},
},
"antigravity-claude-opus-4-5-thinking": {
name: "Claude Opus 4.5 Thinking (Antigravity)",
limit: { context: 200000, output: 64000 },
modalities: { input: ["text", "image", "pdf"], output: ["text"] },
variants: {
low: { thinkingConfig: { thinkingBudget: 8192 } },
max: { thinkingConfig: { thinkingBudget: 32768 } },
},
},
},
},

View File

@@ -12,12 +12,14 @@ features/
│ ├── manager.ts # Launch → poll → complete
│ ├── concurrency.ts # Per-provider limits
│ └── types.ts # BackgroundTask, LaunchInput
├── skill-mcp-manager/ # MCP client lifecycle
├── skill-mcp-manager/ # MCP client lifecycle (520 lines)
│ ├── manager.ts # Lazy loading, cleanup
│ └── types.ts # SkillMcpConfig
├── builtin-skills/ # Playwright, git-master, frontend-ui-ux
│ └── skills.ts # 1203 lines
├── builtin-commands/ # ralph-loop, refactor, init-deep
├── builtin-commands/ # ralph-loop, refactor, init-deep, start-work, remove-deadcode
│ ├── commands.ts # Command registry
│ └── templates/ # Command templates (4 files)
├── claude-code-agent-loader/ # ~/.claude/agents/*.md
├── claude-code-command-loader/ # ~/.claude/commands/*.md
├── claude-code-mcp-loader/ # .mcp.json
@@ -26,7 +28,8 @@ features/
├── opencode-skill-loader/ # Skills from 6 directories
├── context-injector/ # AGENTS.md/README.md injection
├── boulder-state/ # Todo state persistence
── hook-message-injector/ # Message injection
── hook-message-injector/ # Message injection
└── task-toast-manager/ # Background task notifications
```
## LOADER PRIORITY

View File

@@ -81,7 +81,7 @@ export function loadBuiltinCommands(
for (const [name, definition] of Object.entries(BUILTIN_COMMAND_DEFINITIONS)) {
if (!disabled.has(name as BuiltinCommandName)) {
const { argumentHint: _argumentHint, ...openCodeCompatible } = definition
commands[name] = openCodeCompatible as CommandDefinition
commands[name] = { ...openCodeCompatible, name } as CommandDefinition
}
}

View File

@@ -17,7 +17,7 @@ export const RALPH_LOOP_TEMPLATE = `You are starting a Ralph Loop - a self-refer
## Exit Conditions
1. **Completion**: Output \`<promise>DONE</promise>\` (or custom promise text) when fully complete
1. **Completion**: Output your completion promise tag when fully complete
2. **Max Iterations**: Loop stops automatically at limit
3. **Cancel**: User runs \`/cancel-ralph\` command

View File

@@ -1,6 +1,6 @@
---
name: git-master
description: "MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with delegate_task(category='quick', skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'."
description: "MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with delegate_task(category='quick', load_skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'."
---
# Git Master Agent

View File

@@ -95,7 +95,7 @@ Interpret creatively and make unexpected choices that feel genuinely designed fo
const gitMasterSkill: BuiltinSkill = {
name: "git-master",
description:
"MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with delegate_task(category='quick', skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'.",
"MUST USE for ANY git operations. Atomic commits, rebase/squash, history search (blame, bisect, log -S). STRONGLY RECOMMENDED: Use with delegate_task(category='quick', load_skills=['git-master'], ...) to save context. Triggers: 'commit', 'rebase', 'squash', 'who wrote', 'when was X added', 'find the commit that'.",
template: `# Git Master Agent
You are a Git expert combining three specializations:

View File

@@ -10,7 +10,7 @@
hooks/
├── atlas/ # Main orchestration (773 lines)
├── anthropic-context-window-limit-recovery/ # Auto-summarize
├── todo-continuation-enforcer.ts # Force TODO completion
├── todo-continuation-enforcer.ts # Force TODO completion (489 lines)
├── ralph-loop/ # Self-referential dev loop
├── claude-code-hooks/ # settings.json compat layer - see AGENTS.md
├── comment-checker/ # Prevents AI slop
@@ -28,7 +28,15 @@ hooks/
├── prometheus-md-only/ # Planner read-only mode
├── agent-usage-reminder/ # Specialized agent hints
├── auto-update-checker/ # Plugin update check
── tool-output-truncator.ts # Prevents context bloat
── tool-output-truncator.ts # Prevents context bloat
├── compaction-context-injector/ # Injects context on compaction
├── delegate-task-retry/ # Retries failed delegations
├── interactive-bash-session/ # Tmux session management
├── non-interactive-env/ # Non-TTY environment handling
├── start-work/ # Sisyphus work session starter
├── task-resume-info/ # Resume info for cancelled tasks
├── question-label-truncator/ # Auto-truncates question labels >30 chars
└── index.ts # Hook aggregation + registration
```
## HOOK EVENTS

View File

@@ -141,7 +141,7 @@ describe("atlas hook", () => {
// #then - standalone verification reminder appended
expect(output.output).toContain("Task completed successfully")
expect(output.output).toContain("MANDATORY:")
expect(output.output).toContain("delegate_task(resume=")
expect(output.output).toContain("delegate_task(session_id=")
cleanupMessageStorage(sessionID)
})
@@ -180,7 +180,7 @@ describe("atlas hook", () => {
expect(output.output).toContain("SUBAGENT WORK COMPLETED")
expect(output.output).toContain("test-plan")
expect(output.output).toContain("LIE")
expect(output.output).toContain("delegate_task(resume=")
expect(output.output).toContain("delegate_task(session_id=")
cleanupMessageStorage(sessionID)
})
@@ -332,7 +332,7 @@ describe("atlas hook", () => {
cleanupMessageStorage(sessionID)
})
test("should include resume and checkbox instructions in reminder", async () => {
test("should include session_id and checkbox instructions in reminder", async () => {
// #given - boulder state, Atlas caller
const sessionID = "session-resume-test"
setupMessageStorage(sessionID, "atlas")
@@ -361,8 +361,8 @@ describe("atlas hook", () => {
output
)
// #then - should include resume instructions and verification
expect(output.output).toContain("delegate_task(resume=")
// #then - should include session_id instructions and verification
expect(output.output).toContain("delegate_task(session_id=")
expect(output.output).toContain("[x]")
expect(output.output).toContain("MANDATORY:")

View File

@@ -179,13 +179,13 @@ If you were NOT given **exactly ONE atomic task**, you MUST:
`
function buildVerificationReminder(sessionId: string): string {
return `${VERIFICATION_REMINDER}
return `${VERIFICATION_REMINDER}
---
**If ANY verification fails, use this immediately:**
\`\`\`
delegate_task(resume="${sessionId}", prompt="fix: [describe the specific failure]")
delegate_task(session_id="${sessionId}", prompt="fix: [describe the specific failure]")
\`\`\``
}
@@ -711,8 +711,8 @@ export function createAtlasHook(
return
}
const outputStr = output.output && typeof output.output === "string" ? output.output : ""
const isBackgroundLaunch = outputStr.includes("Background task launched") || outputStr.includes("Background task resumed")
const outputStr = output.output && typeof output.output === "string" ? output.output : ""
const isBackgroundLaunch = outputStr.includes("Background task launched") || outputStr.includes("Background task continued")
if (isBackgroundLaunch) {
return

View File

@@ -1,87 +0,0 @@
import type { BackgroundManager } from "../../features/background-agent"
interface CompactingInput {
sessionID: string
}
interface CompactingOutput {
context: string[]
prompt?: string
}
/**
* Background agent compaction hook - preserves task state during context compaction.
*
* When OpenCode compacts session context to save tokens, this hook injects
* information about running and recently completed background tasks so the
* agent doesn't lose awareness of delegated work.
*/
export function createBackgroundCompactionHook(manager: BackgroundManager) {
return {
"experimental.session.compacting": async (
input: CompactingInput,
output: CompactingOutput
): Promise<void> => {
const { sessionID } = input
// Get running tasks for this session
const running = manager.getRunningTasks()
.filter(t => t.parentSessionID === sessionID)
.map(t => ({
id: t.id,
agent: t.agent,
description: t.description,
startedAt: t.startedAt,
}))
// Get recently completed tasks (still in memory within 5-min retention)
const completed = manager.getCompletedTasks()
.filter(t => t.parentSessionID === sessionID)
.slice(-10) // Last 10 completed
.map(t => ({
id: t.id,
agent: t.agent,
description: t.description,
status: t.status,
}))
// Early exit if nothing to preserve
if (running.length === 0 && completed.length === 0) return
const sections: string[] = ["<background-tasks>"]
// Running tasks section
if (running.length > 0) {
sections.push("## Running Background Tasks")
sections.push("")
for (const t of running) {
const elapsed = t.startedAt
? Math.floor((Date.now() - t.startedAt.getTime()) / 1000)
: 0
sections.push(`- **\`${t.id}\`** (${t.agent}): ${t.description} [${elapsed}s elapsed]`)
}
sections.push("")
sections.push("> **Note:** You WILL be notified when tasks complete.")
sections.push("> Do NOT poll - continue productive work.")
sections.push("")
}
// Completed tasks section
if (completed.length > 0) {
sections.push("## Recently Completed Tasks")
sections.push("")
for (const t of completed) {
const statusLabel = t.status === "completed" ? "[DONE]" : t.status === "error" ? "[ERROR]" : "[PENDING]"
sections.push(`- ${statusLabel} **\`${t.id}\`**: ${t.description}`)
}
sections.push("")
}
sections.push("## Retrieval")
sections.push('Use `background_output(task_id="<id>")` to retrieve task results.')
sections.push("</background-tasks>")
output.context.push(sections.join("\n"))
}
}
}

View File

@@ -169,10 +169,10 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
## AGENTS / **CATEGORY + SKILLS** UTILIZATION PRINCIPLES (by capability, not by name)
- **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
- **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
- **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
- MUST USE PLAN AGENT. MUST USE PLAN AGENT. MUST USE PLAN AGENT.
- ALWAYS ASK PLAN AGENT TO WHAT CATEGORY + SKILLS / AGENTS TO LEVERAGE.
- IF IMPLEMENT TASK, MUST ADD TODO NOW: "CONSULT WITH PLAN AGENT WITH CATEGORY + SKILLS"
- **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn the Plan agent for work breakdown
- MUST invoke: \`delegate_task(subagent_type="plan", prompt="<gathered context + user request>")\`
- In your prompt to the Plan agent, ASK it to recommend which CATEGORY + SKILLS / AGENTS to leverage for implementation.
- IF IMPLEMENT TASK, MUST ADD TODO NOW: "Consult Plan agent via delegate_task(subagent_type='plan') for work breakdown with category + skills recommendations"
- **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
- **SPECIAL TASKS COVERED WITH CATEGORY + LOAD_SKILLS**: Delegate to specialized agents with category+skills for design and implementation, as following guide:
- CATEGORY + SKILL GUIDE
@@ -192,7 +192,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
## WORKFLOW
1. Analyze the request and identify required capabilities
2. Spawn exploration/librarian agents via delegate_task(background=true) in PARALLEL (10+ if needed)
3. Always Use Plan agent with gathered context to create detailed work breakdown
3. Spawn Plan agent: \`delegate_task(subagent_type="plan", prompt="<context + request>")\` to create detailed work breakdown
4. Execute with continuous verification against original requirements
## VERIFICATION GUARANTEE (NON-NEGOTIABLE)
@@ -266,9 +266,9 @@ Write these criteria explicitly. Share with user if scope is non-trivial.
THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT.
1. EXPLORES + LIBRARIANS
2. GATHER -> PLAN AGENT SPAWN
3. WORK BY DELEGATING TO ANOTHER AGENTS
1. EXPLORES + LIBRARIANS (background)
2. GATHER -> delegate_task(subagent_type="plan", prompt="<context + request>")
3. WORK BY DELEGATING TO CATEGORY + SKILLS AGENTS
NOW.

View File

@@ -459,7 +459,7 @@ describe("ralph-loop", () => {
})
hook.startLoop("session-123", "Build something", { completionPromise: "COMPLETE" })
writeFileSync(transcriptPath, JSON.stringify({ content: "Task done <promise>COMPLETE</promise>" }))
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "Task done <promise>COMPLETE</promise>" } }) + "\n")
// #when - session goes idle (transcriptPath now derived from sessionID via getTranscriptPath)
await hook.event({
@@ -703,10 +703,105 @@ describe("ralph-loop", () => {
expect(promptCalls[0].text).toContain("2/50")
})
test("should NOT detect completion from user message in transcript (issue #622)", async () => {
// #given - transcript contains user message with template text that includes completion promise
// This reproduces the bug where the RALPH_LOOP_TEMPLATE instructional text
// containing `<promise>DONE</promise>` is recorded as a user message and
// falsely triggers completion detection
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
const templateText = `You are starting a Ralph Loop...
Output <promise>DONE</promise> when fully complete`
const userEntry = JSON.stringify({
type: "user",
timestamp: new Date().toISOString(),
content: templateText,
})
writeFileSync(transcriptPath, userEntry + "\n")
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: () => transcriptPath,
})
hook.startLoop("session-123", "Build something", { completionPromise: "DONE" })
// #when - session goes idle
await hook.event({
event: {
type: "session.idle",
properties: { sessionID: "session-123" },
},
})
// #then - loop should CONTINUE (user message completion promise is instructional, not actual)
expect(promptCalls.length).toBe(1)
expect(hook.getState()?.iteration).toBe(2)
})
test("should NOT detect completion from continuation prompt in transcript (issue #622)", async () => {
// #given - transcript contains continuation prompt (also a user message) with completion promise
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
const continuationText = `RALPH LOOP 2/100
When FULLY complete, output: <promise>DONE</promise>
Original task: Build something`
const userEntry = JSON.stringify({
type: "user",
timestamp: new Date().toISOString(),
content: continuationText,
})
writeFileSync(transcriptPath, userEntry + "\n")
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: () => transcriptPath,
})
hook.startLoop("session-123", "Build something", { completionPromise: "DONE" })
// #when - session goes idle
await hook.event({
event: {
type: "session.idle",
properties: { sessionID: "session-123" },
},
})
// #then - loop should CONTINUE (continuation prompt text is not actual completion)
expect(promptCalls.length).toBe(1)
expect(hook.getState()?.iteration).toBe(2)
})
test("should detect completion from tool_result entry in transcript", async () => {
// #given - transcript contains a tool_result with completion promise
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
const toolResultEntry = JSON.stringify({
type: "tool_result",
timestamp: new Date().toISOString(),
tool_name: "write",
tool_input: {},
tool_output: { output: "Task complete! <promise>DONE</promise>" },
})
writeFileSync(transcriptPath, toolResultEntry + "\n")
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: () => transcriptPath,
})
hook.startLoop("session-123", "Build something", { completionPromise: "DONE" })
// #when - session goes idle
await hook.event({
event: {
type: "session.idle",
properties: { sessionID: "session-123" },
},
})
// #then - loop should complete (tool_result contains actual completion output)
expect(promptCalls.length).toBe(0)
expect(toastCalls.some((t) => t.title === "Ralph Loop Complete!")).toBe(true)
expect(hook.getState()).toBeNull()
})
test("should check transcript BEFORE API to optimize performance", async () => {
// #given - transcript has completion promise
const transcriptPath = join(TEST_DIR, "transcript.jsonl")
writeFileSync(transcriptPath, JSON.stringify({ content: "<promise>DONE</promise>" }))
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "<promise>DONE</promise>" } }) + "\n")
mockSessionMessages = [
{ info: { role: "assistant" }, parts: [{ type: "text", text: "No promise here" }] },
]
@@ -736,7 +831,7 @@ describe("ralph-loop", () => {
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: () => transcriptPath,
})
writeFileSync(transcriptPath, JSON.stringify({ content: "<promise>DONE</promise>" }))
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "<promise>DONE</promise>" } }) + "\n")
hook.startLoop("test-id", "Build API", { ultrawork: true })
// #when - idle event triggered
@@ -754,7 +849,7 @@ describe("ralph-loop", () => {
const hook = createRalphLoopHook(createMockPluginInput(), {
getTranscriptPath: () => transcriptPath,
})
writeFileSync(transcriptPath, JSON.stringify({ content: "<promise>DONE</promise>" }))
writeFileSync(transcriptPath, JSON.stringify({ type: "tool_result", tool_name: "write", tool_output: { output: "<promise>DONE</promise>" } }) + "\n")
hook.startLoop("test-id", "Build API")
// #when - idle event triggered

View File

@@ -100,7 +100,18 @@ export function createRalphLoopHook(
const content = readFileSync(transcriptPath, "utf-8")
const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is")
return pattern.test(content)
const lines = content.split("\n").filter(l => l.trim())
for (const line of lines) {
try {
const entry = JSON.parse(line)
if (entry.type === "user") continue
if (pattern.test(line)) return true
} catch {
continue
}
}
return false
} catch {
return false
}

View File

@@ -16,21 +16,21 @@ function extractSessionId(output: string): string | null {
}
export function createTaskResumeInfoHook() {
const toolExecuteAfter = async (
input: { tool: string; sessionID: string; callID: string },
output: { title: string; output: string; metadata: unknown }
) => {
if (!TARGET_TOOLS.includes(input.tool)) return
if (output.output.startsWith("Error:") || output.output.startsWith("Failed")) return
if (output.output.includes("\nto resume:")) return
const toolExecuteAfter = async (
input: { tool: string; sessionID: string; callID: string },
output: { title: string; output: string; metadata: unknown }
) => {
if (!TARGET_TOOLS.includes(input.tool)) return
if (output.output.startsWith("Error:") || output.output.startsWith("Failed")) return
if (output.output.includes("\nto continue:")) return
const sessionId = extractSessionId(output.output)
if (!sessionId) return
const sessionId = extractSessionId(output.output)
if (!sessionId) return
output.output = output.output.trimEnd() + `\n\nto resume: delegate_task(resume="${sessionId}", prompt="...")`
}
output.output = output.output.trimEnd() + `\n\nto continue: delegate_task(session_id="${sessionId}", prompt="...")`
}
return {
"tool.execute.after": toolExecuteAfter,
}
return {
"tool.execute.after": toolExecuteAfter,
}
}

View File

@@ -2,7 +2,7 @@
## OVERVIEW
50 cross-cutting utilities: path resolution, token truncation, config parsing, model resolution.
34 cross-cutting utilities: path resolution, token truncation, config parsing, model resolution, agent display names.
## STRUCTURE
@@ -19,12 +19,27 @@ shared/
├── migration.ts # Legacy config migration
├── opencode-version.ts # Version comparison
├── external-plugin-detector.ts # OAuth spoofing detection
├── env-expander.ts # ${VAR} expansion
├── model-requirements.ts # Agent/Category requirements
├── model-availability.ts # Models fetch + fuzzy match
├── model-resolver.ts # 3-step resolution
├── model-sanitizer.ts # Model ID normalization
├── shell-env.ts # Cross-platform shell
├── prompt-parts-helper.ts # Prompt manipulation
├── agent-display-names.ts # Agent display name mapping
├── agent-tool-restrictions.ts # Tool restriction helpers
├── agent-variant.ts # Agent variant detection
├── command-executor.ts # Subprocess execution
├── config-errors.ts # Config error types
├── deep-merge.ts # Deep object merge
├── file-reference-resolver.ts # File path resolution
├── file-utils.ts # File utilities
├── hook-disabled.ts # Hook enable/disable check
├── pattern-matcher.ts # Glob pattern matching
├── session-cursor.ts # Session cursor tracking
├── snake-case.ts # String case conversion
├── system-directive.ts # System prompt helpers
├── tool-name.ts # Tool name constants
├── zip-extractor.ts # ZIP file extraction
├── index.ts # Barrel export
└── *.test.ts # Colocated tests
```
@@ -40,6 +55,7 @@ shared/
| Resolve paths | `getOpenCodeConfigDir()` |
| Compare versions | `isOpenCodeVersionAtLeast("1.1.0")` |
| Resolve model | `resolveModelWithFallback()` |
| Agent display name | `getAgentDisplayName(agentName)` |
## PATTERNS

View File

@@ -1,43 +0,0 @@
import * as path from "path"
import * as os from "os"
import * as fs from "fs"
/**
* Returns the user-level config directory based on the OS.
* @deprecated Use getOpenCodeConfigDir() from opencode-config-dir.ts instead.
*/
export function getUserConfigDir(): string {
if (process.platform === "win32") {
const crossPlatformDir = path.join(os.homedir(), ".config")
const crossPlatformConfigPath = path.join(crossPlatformDir, "opencode", "oh-my-opencode.json")
const appdataDir = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming")
const appdataConfigPath = path.join(appdataDir, "opencode", "oh-my-opencode.json")
if (fs.existsSync(crossPlatformConfigPath)) {
return crossPlatformDir
}
if (fs.existsSync(appdataConfigPath)) {
return appdataDir
}
return crossPlatformDir
}
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config")
}
/**
* Returns the full path to the user-level oh-my-opencode config file.
*/
export function getUserConfigPath(): string {
return path.join(getUserConfigDir(), "opencode", "oh-my-opencode.json")
}
/**
* Returns the full path to the project-level oh-my-opencode config file.
*/
export function getProjectConfigPath(directory: string): string {
return path.join(directory, ".opencode", "oh-my-opencode.json")
}

View File

@@ -10,7 +10,6 @@ export * from "./hook-disabled"
export * from "./deep-merge"
export * from "./file-utils"
export * from "./dynamic-truncator"
export * from "./config-path"
export * from "./data-path"
export * from "./config-errors"
export * from "./claude-config-dir"

View File

@@ -236,9 +236,9 @@ describe("resolveModelWithFallback", () => {
// #given
const input: ExtendedModelResolutionInput = {
fallbackChain: [
{ providers: ["anthropic", "opencode", "github-copilot"], model: "gpt-5-nano" },
{ providers: ["anthropic", "opencode"], model: "gpt-5-nano" },
],
availableModels: new Set(["opencode/gpt-5-nano", "github-copilot/gpt-5-nano-preview"]),
availableModels: new Set(["opencode/gpt-5-nano"]),
systemDefaultModel: "google/gemini-3-pro",
}

View File

@@ -25,7 +25,7 @@ tools/
├── skill-mcp/ # Skill MCP operations
├── slashcommand/ # Slash command dispatch
├── call-omo-agent/ # Direct agent invocation
└── background-task/ # background_output, background_cancel
└── background-task/ # background_output, background_cancel (513 lines)
```
## TOOL CATEGORIES
@@ -48,7 +48,7 @@ tools/
## LSP SPECIFICS
- **Client**: `client.ts` manages stdio, JSON-RPC
- **Client**: `client.ts` manages stdio, JSON-RPC (596 lines)
- **Singleton**: `LSPServerManager` with ref counting
- **Capabilities**: definition, references, symbols, diagnostics, rename

View File

@@ -442,18 +442,18 @@ export function createBackgroundCancel(manager: BackgroundManager, client: Openc
.map(t => `| \`${t.id}\` | ${t.description} | ${t.status} | ${t.sessionID ? `\`${t.sessionID}\`` : "(not started)"} |`)
.join("\n")
const resumableTasks = cancelledInfo.filter(t => t.sessionID)
const resumeSection = resumableTasks.length > 0
? `\n## Resume Instructions
const resumableTasks = cancelledInfo.filter(t => t.sessionID)
const resumeSection = resumableTasks.length > 0
? `\n## Continue Instructions
To resume a cancelled task, use:
To continue a cancelled task, use:
\`\`\`
delegate_task(resume="<session_id>", prompt="Continue: <your follow-up>")
delegate_task(session_id="<session_id>", prompt="Continue: <your follow-up>")
\`\`\`
Resumable sessions:
Continuable sessions:
${resumableTasks.map(t => `- \`${t.sessionID}\` (${t.description})`).join("\n")}`
: ""
: ""
return `Cancelled ${cancellableTasks.length} background task(s):

View File

@@ -4,4 +4,4 @@ export const CALL_OMO_AGENT_DESCRIPTION = `Spawn explore/librarian agent. run_in
Available: {agents}
Pass \`resume=session_id\` to continue previous agent with full context. Prompts MUST be in English. Use \`background_output\` for async results.`
Pass \`session_id=<id>\` to continue previous agent with full context. Prompts MUST be in English. Use \`background_output\` for async results.`

View File

@@ -594,16 +594,16 @@ describe("sisyphus-task", () => {
}, { timeout: 20000 })
})
describe("resume with background parameter", () => {
test("resume with background=false should wait for result and return content", async () => {
describe("session_id with background parameter", () => {
test("session_id with background=false should wait for result and return content", async () => {
// Note: This test needs extended timeout because the implementation has MIN_STABILITY_TIME_MS = 5000
// #given
const { createDelegateTask } = require("./tools")
const mockTask = {
id: "task-123",
sessionID: "ses_resume_test",
description: "Resumed task",
sessionID: "ses_continue_test",
description: "Continued task",
agent: "explore",
status: "running",
}
@@ -620,7 +620,7 @@ describe("sisyphus-task", () => {
data: [
{
info: { role: "assistant", time: { created: Date.now() } },
parts: [{ type: "text", text: "This is the resumed task result" }],
parts: [{ type: "text", text: "This is the continued task result" }],
},
],
}),
@@ -646,28 +646,28 @@ describe("sisyphus-task", () => {
// #when
const result = await tool.execute(
{
description: "Resume test",
description: "Continue test",
prompt: "Continue the task",
resume: "ses_resume_test",
session_id: "ses_continue_test",
run_in_background: false,
load_skills: ["git-master"],
},
toolContext
)
// #then - should contain actual result, not just "Background task resumed"
expect(result).toContain("This is the resumed task result")
expect(result).not.toContain("Background task resumed")
// #then - should contain actual result, not just "Background task continued"
expect(result).toContain("This is the continued task result")
expect(result).not.toContain("Background task continued")
}, { timeout: 10000 })
test("resume with background=true should return immediately without waiting", async () => {
test("session_id with background=true should return immediately without waiting", async () => {
// #given
const { createDelegateTask } = require("./tools")
const mockTask = {
id: "task-456",
sessionID: "ses_bg_resume",
description: "Background resumed task",
sessionID: "ses_bg_continue",
description: "Background continued task",
agent: "explore",
status: "running",
}
@@ -701,9 +701,9 @@ describe("sisyphus-task", () => {
// #when
const result = await tool.execute(
{
description: "Resume bg test",
description: "Continue bg test",
prompt: "Continue in background",
resume: "ses_bg_resume",
session_id: "ses_bg_continue",
run_in_background: true,
load_skills: ["git-master"],
},
@@ -711,7 +711,7 @@ describe("sisyphus-task", () => {
)
// #then - should return background message
expect(result).toContain("Background task resumed")
expect(result).toContain("Background task continued")
expect(result).toContain("task-456")
})
})

View File

@@ -86,8 +86,8 @@ function formatDetailedError(error: unknown, ctx: ErrorContext): string {
lines.push(`- subagent_type: ${ctx.args.subagent_type ?? "(none)"}`)
lines.push(`- run_in_background: ${ctx.args.run_in_background}`)
lines.push(`- load_skills: [${ctx.args.load_skills?.join(", ") ?? ""}]`)
if (ctx.args.resume) {
lines.push(`- resume: ${ctx.args.resume}`)
if (ctx.args.session_id) {
lines.push(`- session_id: ${ctx.args.session_id}`)
}
}
@@ -194,7 +194,7 @@ export function createDelegateTask(options: DelegateTaskToolOptions): ToolDefini
const description = `Spawn agent task with category-based or direct agent selection.
MUTUALLY EXCLUSIVE: Provide EITHER category OR subagent_type, not both (unless resuming).
MUTUALLY EXCLUSIVE: Provide EITHER category OR subagent_type, not both (unless continuing a session).
- load_skills: ALWAYS REQUIRED. Pass at least one skill name (e.g., ["playwright"], ["git-master", "frontend-ui-ux"]).
- category: Use predefined category → Spawns Sisyphus-Junior with category config
@@ -202,12 +202,13 @@ MUTUALLY EXCLUSIVE: Provide EITHER category OR subagent_type, not both (unless r
${categoryList}
- subagent_type: Use specific agent directly (e.g., "oracle", "explore")
- run_in_background: true=async (returns task_id), false=sync (waits for result). Default: false. Use background=true ONLY for parallel exploration with 5+ independent queries.
- resume: Session ID to resume (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity.
- session_id: Existing Task session to continue (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity.
- command: The command that triggered this task (optional, for slash command tracking).
**WHEN TO USE resume:**
- Task failed/incomplete → resume with "fix: [specific issue]"
- Need follow-up on previous result → resume with additional question
- Multi-turn conversation with same agent → always resume instead of new task
**WHEN TO USE session_id:**
- Task failed/incomplete → session_id with "fix: [specific issue]"
- Need follow-up on previous result → session_id with additional question
- Multi-turn conversation with same agent → always session_id instead of new task
Prompts MUST be in English.`
@@ -220,7 +221,8 @@ Prompts MUST be in English.`
run_in_background: tool.schema.boolean().describe("true=async (returns task_id), false=sync (waits). Default: false"),
category: tool.schema.string().optional().describe(`Category (e.g., ${categoryExamples}). Mutually exclusive with subagent_type.`),
subagent_type: tool.schema.string().optional().describe("Agent name (e.g., 'oracle', 'explore'). Mutually exclusive with category."),
resume: tool.schema.string().optional().describe("Session ID to resume"),
session_id: tool.schema.string().optional().describe("Existing Task session to continue"),
command: tool.schema.string().optional().describe("The command that triggered this task"),
},
async execute(args: DelegateTaskArgs, toolContext) {
const ctx = toolContext as ToolContextWithMetadata
@@ -265,11 +267,11 @@ Prompts MUST be in English.`
? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID }
: undefined
if (args.resume) {
if (args.session_id) {
if (runInBackground) {
try {
const task = await manager.resume({
sessionId: args.resume,
sessionId: args.session_id,
prompt: args.prompt,
parentSessionID: ctx.sessionID,
parentMessageID: ctx.messageID,
@@ -278,7 +280,7 @@ Prompts MUST be in English.`
})
ctx.metadata?.({
title: `Resume: ${task.description}`,
title: `Continue: ${task.description}`,
metadata: {
prompt: args.prompt,
agent: task.agent,
@@ -286,10 +288,11 @@ Prompts MUST be in English.`
description: args.description,
run_in_background: args.run_in_background,
sessionId: task.sessionID,
command: args.command,
},
})
return `Background task resumed.
return `Background task continued.
Task ID: ${task.id}
Session ID: ${task.sessionID}
@@ -301,35 +304,36 @@ Agent continues with full previous context preserved.
Use \`background_output\` with task_id="${task.id}" to check progress.`
} catch (error) {
return formatDetailedError(error, {
operation: "Resume background task",
operation: "Continue background task",
args,
sessionID: args.resume,
sessionID: args.session_id,
})
}
}
const toastManager = getTaskToastManager()
const taskId = `resume_sync_${args.resume.slice(0, 8)}`
const taskId = `resume_sync_${args.session_id.slice(0, 8)}`
const startTime = new Date()
if (toastManager) {
toastManager.addTask({
id: taskId,
description: args.description,
agent: "resume",
agent: "continue",
isBackground: false,
})
}
ctx.metadata?.({
title: `Resume: ${args.description}`,
title: `Continue: ${args.description}`,
metadata: {
prompt: args.prompt,
load_skills: args.load_skills,
description: args.description,
run_in_background: args.run_in_background,
sessionId: args.resume,
sessionId: args.session_id,
sync: true,
command: args.command,
},
})
@@ -338,7 +342,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
let resumeModel: { providerID: string; modelID: string } | undefined
try {
const messagesResp = await client.session.messages({ path: { id: args.resume } })
const messagesResp = await client.session.messages({ path: { id: args.session_id } })
const messages = (messagesResp.data ?? []) as Array<{
info?: { agent?: string; model?: { providerID: string; modelID: string }; modelID?: string; providerID?: string }
}>
@@ -351,7 +355,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
}
}
} catch {
const resumeMessageDir = getMessageDir(args.resume)
const resumeMessageDir = getMessageDir(args.session_id)
const resumeMessage = resumeMessageDir ? findNearestMessageWithFields(resumeMessageDir) : null
resumeAgent = resumeMessage?.agent
resumeModel = resumeMessage?.model?.providerID && resumeMessage?.model?.modelID
@@ -360,7 +364,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
}
await client.session.prompt({
path: { id: args.resume },
path: { id: args.session_id },
body: {
...(resumeAgent !== undefined ? { agent: resumeAgent } : {}),
...(resumeModel !== undefined ? { model: resumeModel } : {}),
@@ -378,7 +382,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
toastManager.removeTask(taskId)
}
const errorMessage = promptError instanceof Error ? promptError.message : String(promptError)
return `Failed to send resume prompt: ${errorMessage}\n\nSession ID: ${args.resume}`
return `Failed to send continuation prompt: ${errorMessage}\n\nSession ID: ${args.session_id}`
}
// Wait for message stability after prompt completes
@@ -395,7 +399,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
const elapsed = Date.now() - pollStart
if (elapsed < MIN_STABILITY_TIME_MS) continue
const messagesCheck = await client.session.messages({ path: { id: args.resume } })
const messagesCheck = await client.session.messages({ path: { id: args.session_id } })
const msgs = ((messagesCheck as { data?: unknown }).data ?? messagesCheck) as Array<unknown>
const currentMsgCount = msgs.length
@@ -409,14 +413,14 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
}
const messagesResult = await client.session.messages({
path: { id: args.resume },
path: { id: args.session_id },
})
if (messagesResult.error) {
if (toastManager) {
toastManager.removeTask(taskId)
}
return `Error fetching result: ${messagesResult.error}\n\nSession ID: ${args.resume}`
return `Error fetching result: ${messagesResult.error}\n\nSession ID: ${args.session_id}`
}
const messages = ((messagesResult as { data?: unknown }).data ?? messagesResult) as Array<{
@@ -434,7 +438,7 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
}
if (!lastMessage) {
return `No assistant response found.\n\nSession ID: ${args.resume}`
return `No assistant response found.\n\nSession ID: ${args.session_id}`
}
// Extract text from both "text" and "reasoning" parts (thinking models use "reasoning")
@@ -443,16 +447,16 @@ Use \`background_output\` with task_id="${task.id}" to check progress.`
const duration = formatDuration(startTime)
return `Task resumed and completed in ${duration}.
return `Task continued and completed in ${duration}.
Session ID: ${args.resume}
Session ID: ${args.session_id}
---
${textContent || "(No text output)"}
---
To resume this session: resume="${args.resume}"`
To continue this session: session_id="${args.session_id}"`
}
if (args.category && args.subagent_type) {
@@ -618,6 +622,7 @@ To resume this session: resume="${args.resume}"`
description: args.description,
run_in_background: args.run_in_background,
sessionId: sessionID,
command: args.command,
},
})
@@ -705,7 +710,7 @@ RESULT:
${textContent || "(No text output)"}
---
To resume this session: resume="${sessionID}"`
To continue this session: session_id="${sessionID}"`
} catch (error) {
return formatDetailedError(error, {
operation: "Launch monitored background task",
@@ -788,6 +793,7 @@ Sisyphus-Junior is spawned automatically when you specify a category. Pick the a
description: args.description,
run_in_background: args.run_in_background,
sessionId: task.sessionID,
command: args.command,
},
})
@@ -800,7 +806,7 @@ Agent: ${task.agent}${args.category ? ` (category: ${args.category})` : ""}
Status: ${task.status}
System notifies on completion. Use \`background_output\` with task_id="${task.id}" to check.
To resume this session: resume="${task.sessionID}"`
To continue this session: session_id="${task.sessionID}"`
} catch (error) {
return formatDetailedError(error, {
operation: "Launch background task",
@@ -864,6 +870,7 @@ To resume this session: resume="${task.sessionID}"`
run_in_background: args.run_in_background,
sessionId: sessionID,
sync: true,
command: args.command,
},
})
@@ -1018,7 +1025,7 @@ Session ID: ${sessionID}
${textContent || "(No text output)"}
---
To resume this session: resume="${sessionID}"`
To continue this session: session_id="${sessionID}"`
} catch (error) {
if (toastManager && taskId !== undefined) {
toastManager.removeTask(taskId)

View File

@@ -4,6 +4,7 @@ export interface DelegateTaskArgs {
category?: string
subagent_type?: string
run_in_background: boolean
resume?: string
session_id?: string
command?: string
load_skills: string[]
}

View File

@@ -30,7 +30,6 @@ export { sessionExists } from "./session-manager/storage"
export { interactive_bash, startBackgroundCheck as startTmuxCheck } from "./interactive-bash"
export { createSkillTool } from "./skill"
export { getTmuxPath } from "./interactive-bash/utils"
export { createSkillMcpTool } from "./skill-mcp"
import {
@@ -45,7 +44,7 @@ type OpencodeClient = PluginInput["client"]
export { createCallOmoAgent } from "./call-omo-agent"
export { createLookAt } from "./look-at"
export { createDelegateTask, type DelegateTaskToolOptions, DEFAULT_CATEGORIES, CATEGORY_PROMPT_APPENDS } from "./delegate-task"
export { createDelegateTask } from "./delegate-task"
export function createBackgroundTools(manager: BackgroundManager, client: OpencodeClient): Record<string, ToolDefinition> {
return {