|
| 1 | +# Custom Compact Commands: Analysis & Requirements |
| 2 | + |
| 3 | +## Core Requirements |
| 4 | + |
| 5 | +1. **Shift "/compact" to be implemented in terms of a generic custom "compact"-type command capability, similar to "/init"** |
| 6 | + - Users should be able to create custom compact commands just like they create custom agents |
| 7 | + - The mechanism should follow the same pattern as `/init` with custom agents |
| 8 | + |
| 9 | +2. **Users should be able to define such commands in opencode config as markdown files** |
| 10 | + - Commands defined in `.opencode/command/*.md` |
| 11 | + - Metadata-driven configuration (frontmatter) |
| 12 | + - Template-based prompt customization |
| 13 | + |
| 14 | +3. **Example: How to implement "/handover"** |
| 15 | + - Create `.opencode/command/handover.md` |
| 16 | + - Use metadata to mark it as a compact-type command |
| 17 | + - Define custom template for handover-specific summaries |
| 18 | + - Write this example to globally-ignored directory `config.ignore/command/handover.md` for testing. |
| 19 | + |
| 20 | +4. **Remove all other custom compact prompt changes** |
| 21 | + - Remove deprecated `compactPrompt` config field |
| 22 | + - Clean up any legacy compact customization mechanisms |
| 23 | + - Consolidate to a single, clear pattern |
| 24 | + |
| 25 | +5. **Refactor until minimal possible changes** |
| 26 | + - Reuse existing patterns and infrastructure |
| 27 | + - Avoid special-case logic |
| 28 | + - Keep changes localized and clean |
| 29 | + |
| 30 | +## How `/init` Works (Reference Pattern) |
| 31 | + |
| 32 | +1. `/init` is defined as a regular command with a template |
| 33 | +2. When user types `/init`, autocomplete shows it as an option |
| 34 | +3. User can add custom text after it (e.g., `/init Explain the codebase`) |
| 35 | +4. When submitted, the full text is sent as a normal message |
| 36 | +5. The command's `agent` and `model` fields are used during execution |
| 37 | +6. No special API calls - it follows the standard message flow |
| 38 | +7. The agent processes the message according to its instructions |
| 39 | + |
| 40 | +## Key Architectural Insight |
| 41 | + |
| 42 | +The `/init` pattern works because: |
| 43 | + |
| 44 | +- Commands are just text templates that get inserted into the prompt |
| 45 | +- The `agent` field determines which agent processes the message |
| 46 | +- No special branching or API calls in autocomplete |
| 47 | +- Clean separation: command definition → text insertion → message handling |
| 48 | + |
| 49 | +## Challenges Encountered |
| 50 | + |
| 51 | +### Challenge 1: Initial Implementation Took Wrong Approach |
| 52 | + |
| 53 | +**What Happened:** |
| 54 | + |
| 55 | +- Implemented compact commands with special `compact: boolean` metadata field |
| 56 | +- Added special-case logic in autocomplete to detect compact commands |
| 57 | +- Made compact commands call compaction API directly, bypassing normal flow |
| 58 | +- Created dual mechanisms: global `compactPrompt` config + per-command templates |
| 59 | + |
| 60 | +**Why This Was Wrong:** |
| 61 | + |
| 62 | +- Doesn't follow `/init` pattern (direct API call vs text insertion) |
| 63 | +- Can't accept arguments like `/compact Focus on database changes` |
| 64 | +- Special-case logic breaks architectural consistency |
| 65 | +- Agent and model fields are ignored |
| 66 | +- More complex than the pattern it's supposed to mirror |
| 67 | + |
| 68 | +### Challenge 2: TUI Crash on Startup |
| 69 | + |
| 70 | +**What Happened:** |
| 71 | + |
| 72 | +- Original error: `undefined is not an object (evaluating 'x.data.providers')` |
| 73 | +- Error occurred in `packages/opencode/src/cli/cmd/tui/context/sync.tsx:148` |
| 74 | +- App crashed immediately on startup before even testing compact commands |
| 75 | + |
| 76 | +**Root Cause (from subagent analysis):** |
| 77 | + |
| 78 | +- Race condition during initialization |
| 79 | +- Event listener calling `setStore()` while component is being cleaned up |
| 80 | +- SolidJS reactive graph being torn down during async operations |
| 81 | +- Abort signal shared between event stream and all API calls |
| 82 | +- When event stream aborts, all in-flight API calls fail |
| 83 | + |
| 84 | +**Attempted Fixes Led to Cascading Issues:** |
| 85 | + |
| 86 | +1. Added safe fallbacks (`x.data?.providers ?? []`) |
| 87 | +2. This made agents array empty |
| 88 | +3. App tried to access `agents()[0].name` → crash |
| 89 | +4. Fixed that, then `local.agent.current().name` → crash |
| 90 | +5. Fixed that, but agents being empty breaks fundamental app functionality |
| 91 | + |
| 92 | +**The Deeper Problem:** |
| 93 | + |
| 94 | +- The app fundamentally requires agents to function |
| 95 | +- Safe fallbacks that return empty arrays just move the crash elsewhere |
| 96 | +- The real issue is WHY the API calls are failing in the first place |
| 97 | +- Went down a rabbit hole of fixing symptoms instead of root cause |
| 98 | + |
| 99 | +### Challenge 3: Too Many Changes, Lost Focus |
| 100 | + |
| 101 | +**What Happened:** |
| 102 | + |
| 103 | +- Started with compact command feature |
| 104 | +- Hit unrelated TUI crash |
| 105 | +- Made many changes trying to fix the crash |
| 106 | +- Now have changes across multiple files: |
| 107 | + - `sync.tsx` - extensive error handling |
| 108 | + - `sdk.tsx` - exposed abort signal |
| 109 | + - `local.tsx` - safe agent access |
| 110 | + - Plus all the compact command changes |
| 111 | +- Lost ability to isolate which changes are for which problem |
| 112 | + |
| 113 | +**The Right Approach Should Have Been:** |
| 114 | + |
| 115 | +1. Test compact command implementation on a working branch first |
| 116 | +2. If crash occurs, isolate it as a separate issue |
| 117 | +3. Fix crash in isolation, verify fix works |
| 118 | +4. Then return to feature implementation |
| 119 | +5. Keep changes minimal and focused |
| 120 | + |
| 121 | +## Key Files Involved in Compact Commands |
| 122 | + |
| 123 | +### Schema/Config Files |
| 124 | + |
| 125 | +- `packages/opencode/src/config/config.ts` - Command schema with `compact` field |
| 126 | +- `packages/opencode/src/command/index.ts` - Command.Info type and default commands |
| 127 | + |
| 128 | +### Execution Files |
| 129 | + |
| 130 | +- `packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx` - Special handling for compact commands |
| 131 | +- `packages/opencode/src/session/system.ts` - Compact prompt customization |
| 132 | +- `packages/opencode/src/session/compaction.ts` - Where compaction actually happens |
| 133 | + |
| 134 | +### SDK |
| 135 | + |
| 136 | +- `packages/sdk/js/src/gen/types.gen.ts` - Generated types including Command and Config |
| 137 | + |
| 138 | +### Example |
| 139 | + |
| 140 | +- `config.ignore/command/handover.md` - Example custom compact command |
| 141 | + |
| 142 | +## Key Files Involved in TUI Crash |
| 143 | + |
| 144 | +### Context Providers |
| 145 | + |
| 146 | +- `packages/opencode/src/cli/cmd/tui/context/sync.tsx` - Main initialization and state sync |
| 147 | +- `packages/opencode/src/cli/cmd/tui/context/sdk.tsx` - SDK client wrapper with abort signal |
| 148 | +- `packages/opencode/src/cli/cmd/tui/context/local.tsx` - Local state including current agent |
| 149 | + |
| 150 | +### Components That Use Agents |
| 151 | + |
| 152 | +- `packages/opencode/src/cli/cmd/tui/app.tsx` - Main app, displays current agent |
| 153 | +- `packages/opencode/src/cli/cmd/tui/component/dialog-agent.tsx` - Agent selection dialog |
| 154 | +- `packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx` - Prompt component |
| 155 | + |
| 156 | +## Questions That Need Answers |
| 157 | + |
| 158 | +1. **Why is `/config/providers` returning undefined in the first place?** |
| 159 | + - Is there a configuration issue? |
| 160 | + - Is the server not starting properly? |
| 161 | + - Is there a timing issue? |
| 162 | + |
| 163 | +2. **Should compact commands really call the API directly or should they follow `/init` pattern?** |
| 164 | + - Direct API call: simpler but inconsistent with `/init` |
| 165 | + - Text insertion: consistent but requires message routing logic |
| 166 | + |
| 167 | +3. **Where should message routing happen if we follow the `/init` pattern?** |
| 168 | + - At message send time? |
| 169 | + - In the session handler? |
| 170 | + - Via a special compact agent? |
| 171 | + |
| 172 | +4. **What is the minimal set of changes needed?** |
| 173 | + - Can we reuse more existing infrastructure? |
| 174 | + - What can be deleted vs added? |
| 175 | + |
| 176 | +## Recommended Next Steps |
| 177 | + |
| 178 | +1. **Reset branch to clean state** |
| 179 | + - Start fresh from `dev` branch |
| 180 | + - Clear separation between feature work and bug fixes |
| 181 | + |
| 182 | +2. **First: Verify `/config/providers` works on dev branch** |
| 183 | + - Test that app starts cleanly |
| 184 | + - Understand why it might fail |
| 185 | + - Fix any startup issues in isolation |
| 186 | + |
| 187 | +3. **Second: Design the right compact command approach** |
| 188 | + - Decide: direct API call vs `/init` pattern |
| 189 | + - Document the architecture before coding |
| 190 | + - Identify exactly which files need changes |
| 191 | + |
| 192 | +4. **Third: Implement incrementally** |
| 193 | + - One change at a time |
| 194 | + - Test after each change |
| 195 | + - Keep diff minimal |
| 196 | + |
| 197 | +5. **Fourth: Test thoroughly** |
| 198 | + - Verify `/compact` still works |
| 199 | + - Test custom compact commands |
| 200 | + - Ensure no regressions |
| 201 | + |
| 202 | +## Success Criteria |
| 203 | + |
| 204 | +- [ ] User can create `.opencode/command/handover.md` with custom compact template |
| 205 | +- [ ] `/handover` command works like `/compact` but with custom template |
| 206 | +- [ ] Implementation follows same pattern as `/init` with custom agents |
| 207 | +- [ ] No deprecated `compactPrompt` config field |
| 208 | +- [ ] Minimal code changes (additions are localized, deletions exceed additions) |
| 209 | +- [ ] No special-case logic in autocomplete |
| 210 | +- [ ] App starts without crashing |
| 211 | +- [ ] All existing compact functionality preserved |
0 commit comments