Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5db86ee15 | ||
|
|
14f450bd25 | ||
|
|
5a1da39def | ||
|
|
24d065c43a | ||
|
|
fd72ce5ce7 | ||
|
|
043b1a3377 | ||
|
|
512952f66d | ||
|
|
d9723e76ab | ||
|
|
212baa6674 | ||
|
|
1c76e0513a | ||
|
|
c8cc94cd3c | ||
|
|
20cca35157 | ||
|
|
81d27afadb | ||
|
|
6cb2f3031c | ||
|
|
f116ea1d43 | ||
|
|
6aa0674000 | ||
|
|
2b828624a0 | ||
|
|
e60ccb93fb | ||
|
|
aa244e8098 | ||
|
|
6f60f03433 |
342
.opencode/command/remove-deadcode.md
Normal file
342
.opencode/command/remove-deadcode.md
Normal 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>
|
||||
17
AGENTS.md
17
AGENTS.md
@@ -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
|
||||
|
||||
|
||||
28
bun.lock
28
bun.lock
@@ -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=="],
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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)
|
||||
|
||||
16
package.json
16
package.json
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
`
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
@@ -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.`
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user