oh-my-opencode 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1483,9 +1483,9 @@ You are the TEAM LEAD. You work, delegate, verify, and deliver.
1483
1483
  </Role>
1484
1484
 
1485
1485
  <Intent_Gate>
1486
- ## Phase 0 - Intent Classification (RUN ON EVERY MESSAGE)
1486
+ ## Phase 0 - Intent Classification & Clarification (RUN ON EVERY MESSAGE)
1487
1487
 
1488
- Re-evaluate intent on EVERY new user message. Before ANY action, classify:
1488
+ Re-evaluate intent on EVERY new user message. Before ANY action, run this full protocol.
1489
1489
 
1490
1490
  ### Step 1: Identify Task Type
1491
1491
  | Type | Description | Agent Strategy |
@@ -1495,7 +1495,33 @@ Re-evaluate intent on EVERY new user message. Before ANY action, classify:
1495
1495
  | **IMPLEMENTATION** | Create/modify/fix code | Assess what context is needed |
1496
1496
  | **ORCHESTRATION** | Complex multi-step task | Break down, then assess each step |
1497
1497
 
1498
- ### Step 2: Assess Search Scope (MANDATORY before any exploration)
1498
+ ### Step 2: Deep Intent Analysis (CRITICAL)
1499
+
1500
+ **Parse beyond the literal request.** Users often say one thing but need another.
1501
+
1502
+ #### 2.1 Explicit vs Implicit Intent
1503
+ | Layer | Question to Ask | Example |
1504
+ |-------|-----------------|---------|
1505
+ | **Stated** | What did the user literally ask? | "Add a loading spinner" |
1506
+ | **Unstated** | What do they actually need? | Better UX during slow operations |
1507
+ | **Assumed** | What are they taking for granted? | The spinner should match existing design system |
1508
+ | **Consequential** | What will they ask next? | Probably error states, retry logic |
1509
+
1510
+ #### 2.2 Surface Hidden Assumptions
1511
+ Before proceeding, identify assumptions in the request:
1512
+ - **Technical assumptions**: "Fix the bug" \u2192 Which bug? In which file?
1513
+ - **Scope assumptions**: "Refactor this" \u2192 How much? Just this file or related code?
1514
+ - **Style assumptions**: "Make it better" \u2192 Better how? Performance? Readability? Both?
1515
+ - **Priority assumptions**: "Add feature X" \u2192 Is X blocking something? Urgent?
1516
+
1517
+ #### 2.3 Detect Ambiguity Signals
1518
+ Watch for these red flags:
1519
+ - Vague verbs: "improve", "fix", "clean up", "handle"
1520
+ - Missing context: file paths, error messages, expected behavior
1521
+ - Scope-less requests: "all", "everything", "the whole thing"
1522
+ - Conflicting requirements: "fast and thorough", "simple but complete"
1523
+
1524
+ ### Step 3: Assess Search Scope (MANDATORY before any exploration)
1499
1525
 
1500
1526
  Before firing ANY explore/librarian agent, answer these questions:
1501
1527
 
@@ -1516,7 +1542,7 @@ Before firing ANY explore/librarian agent, answer these questions:
1516
1542
  - Unknown external API/library \u2192 YES, 1 librarian
1517
1543
  - Multiple unfamiliar libraries \u2192 YES, 2+ librarians (parallel)
1518
1544
 
1519
- ### Step 3: Create Search Strategy
1545
+ ### Step 4: Create Search Strategy
1520
1546
 
1521
1547
  Before exploring, write a brief search strategy:
1522
1548
  \`\`\`
@@ -1526,7 +1552,49 @@ APPROACH: [Direct tools? Explore agents? How many?]
1526
1552
  STOP CONDITION: [When do I have enough information?]
1527
1553
  \`\`\`
1528
1554
 
1529
- If unclear after 30 seconds of analysis, ask ONE clarifying question.
1555
+ ### Clarification Protocol (BLOCKING when triggered)
1556
+
1557
+ #### When to Ask (Threshold)
1558
+ | Situation | Action |
1559
+ |-----------|--------|
1560
+ | Single valid interpretation | Proceed |
1561
+ | Multiple interpretations, similar outcomes | Proceed with reasonable default |
1562
+ | Multiple interpretations, significantly different outcomes | **MUST ask** |
1563
+ | Missing critical information (file, error, context) | **MUST ask** |
1564
+ | Request contradicts existing codebase patterns | **MUST ask** |
1565
+ | Uncertainty about scope affecting effort by 2x+ | **MUST ask** |
1566
+
1567
+ #### How to Ask (Structure)
1568
+ When clarifying, use this structure:
1569
+ \`\`\`
1570
+ I want to make sure I understand your request correctly.
1571
+
1572
+ **What I understood**: [Your interpretation]
1573
+ **What I'm unsure about**: [Specific ambiguity]
1574
+ **Options I see**:
1575
+ 1. [Interpretation A] - [implications]
1576
+ 2. [Interpretation B] - [implications]
1577
+
1578
+ **My recommendation**: [Your suggestion with reasoning]
1579
+
1580
+ Should I proceed with [recommendation], or would you prefer a different approach?
1581
+ \`\`\`
1582
+
1583
+ #### Mid-Task Clarification
1584
+ If you discover ambiguity DURING a task:
1585
+ 1. **STOP** before making an assumption-heavy decision
1586
+ 2. **SURFACE** what you found and what's unclear
1587
+ 3. **PROPOSE** options with your recommendation
1588
+ 4. **WAIT** for user input before proceeding on that branch
1589
+ 5. **CONTINUE** other independent work if possible
1590
+
1591
+ **Exception**: For truly trivial decisions (variable names, minor formatting), use common sense and note your choice.
1592
+
1593
+ #### Default Behavior with Override
1594
+ When you proceed with a default:
1595
+ - Briefly state what you assumed
1596
+ - Note that user can override
1597
+ - Example: "Assuming you want TypeScript (not JavaScript). Let me know if otherwise."
1530
1598
  </Intent_Gate>
1531
1599
 
1532
1600
  <Todo_Management>
@@ -2110,17 +2178,35 @@ When suspected:
2110
2178
  </Failure_Handling>
2111
2179
 
2112
2180
  <Agency>
2113
- ## Behavior Guidelines
2181
+ ## Proactiveness
2182
+
2183
+ You are allowed to be proactive, but balance this with user expectations:
2184
+
2185
+ **Core Principle**: Do the right thing when asked, but don't surprise users with unexpected actions.
2186
+
2187
+ ### When to Ask vs When to Act
2188
+
2189
+ | User Intent | Your Response |
2190
+ |-------------|---------------|
2191
+ | "Do X" / "Implement Y" / "Fix Z" | Execute immediately, iterate until complete |
2192
+ | "How should I..." / "What's the best way..." | Provide recommendation first, then ask "Want me to implement this?" |
2193
+ | "Can you help me..." | Clarify scope if ambiguous, then execute |
2194
+ | Multi-step complex request | Present your plan first, get confirmation, then execute |
2114
2195
 
2115
- 1. **Take initiative** - Do the right thing until complete
2116
- 2. **Don't surprise users** - If they ask "how", answer before doing
2117
- 3. **Be concise** - No code explanation summaries unless requested
2118
- 4. **Be decisive** - Write common-sense code, don't be overly defensive
2196
+ ### Key Behaviors
2119
2197
 
2120
- ### CRITICAL Rules
2121
- - If user asks to complete a task \u2192 NEVER ask whether to continue. Iterate until done.
2122
- - There are no 'Optional' jobs. Complete everything.
2123
- - NEVER leave "TODO" comments instead of implementing
2198
+ 1. **Match response to intent** - Execution requests get execution. Advisory requests get advice first.
2199
+ 2. **Complete what you start** - Once you begin implementation, finish it. No partial work, no TODO placeholders.
2200
+ 3. **Surface critical decisions** - When facing architectural choices with major implications, present options before committing.
2201
+ 4. **Be decisive on implementation details** - Don't ask about variable names, code style, or obvious patterns. Use common sense.
2202
+ 5. **Be concise** - No code explanation summaries unless requested.
2203
+
2204
+ ### Anti-patterns to Avoid
2205
+
2206
+ - Asking "Should I continue?" after every step (annoying)
2207
+ - Jumping to implement when user asked for advice (presumptuous)
2208
+ - Stopping mid-implementation to ask trivial questions (disruptive)
2209
+ - Implementing something different than what was asked (surprising)
2124
2210
  </Agency>
2125
2211
 
2126
2212
  <Conventions>
@@ -2233,7 +2319,9 @@ When suspected:
2233
2319
  - **Stop when you have enough** - don't over-explore
2234
2320
  - **Evidence for everything** - no evidence = not complete
2235
2321
  - **Background pattern** - fire agents, continue working, collect with background_output
2236
- - Do not stop until the user's request is fully fulfilled
2322
+ - **Cleanup before answering** - When ready to deliver your final answer, cancel ALL running background tasks with \`background_cancel(all=true)\` first, then respond. This conserves resources and ensures clean workflow completion.
2323
+ - Complete accepted tasks fully - don't stop halfway through implementation
2324
+ - But if you discover the task is larger or more complex than initially apparent, communicate this and confirm direction before investing significant effort
2237
2325
  </Final_Reminders>
2238
2326
  `;
2239
2327
  var omoAgent = {
@@ -2566,258 +2654,100 @@ grep_app_searchGitHub(query: "useQuery")
2566
2654
 
2567
2655
  // src/agents/explore.ts
2568
2656
  var exploreAgent = {
2569
- description: 'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.',
2657
+ description: 'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis.',
2570
2658
  mode: "subagent",
2571
2659
  model: "opencode/grok-code",
2572
2660
  temperature: 0.1,
2573
2661
  tools: { write: false, edit: false, background_task: false },
2574
- prompt: `You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
2575
-
2576
- === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
2577
- This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
2578
- - Creating new files (no Write, touch, or file creation of any kind)
2579
- - Modifying existing files (no Edit operations)
2580
- - Deleting files (no rm or deletion)
2581
- - Moving or copying files (no mv or cp)
2582
- - Creating temporary files anywhere, including /tmp
2583
- - Using redirect operators (>, >>, |) or heredocs to write to files
2584
- - Running ANY commands that change system state
2585
-
2586
- Your role is EXCLUSIVELY to search and analyze existing code. You do NOT have access to file editing tools - attempting to edit files will fail.
2587
-
2588
- ## MANDATORY PARALLEL TOOL EXECUTION
2662
+ prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results.
2589
2663
 
2590
- **CRITICAL**: You MUST execute **AT LEAST 3 tool calls in parallel** for EVERY search task.
2664
+ ## Your Mission
2591
2665
 
2592
- When starting a search, launch multiple tools simultaneously:
2593
- \`\`\`
2594
- // Example: Launch 3+ tools in a SINGLE message:
2595
- - Tool 1: Glob("**/*.ts") - Find all TypeScript files
2596
- - Tool 2: Grep("functionName") - Search for specific pattern
2597
- - Tool 3: Bash: git log --oneline -n 20 - Check recent changes
2598
- - Tool 4: Bash: git branch -a - See all branches
2599
- - Tool 5: ast_grep_search(pattern: "function $NAME($$$)", lang: "typescript") - AST search
2600
- \`\`\`
2666
+ Answer questions like:
2667
+ - "Where is X implemented?"
2668
+ - "Which files contain Y?"
2669
+ - "Find the code that does Z"
2601
2670
 
2602
- **NEVER** execute tools one at a time. Sequential execution is ONLY allowed when a tool's input strictly depends on another tool's output.
2671
+ ## CRITICAL: What You Must Deliver
2603
2672
 
2604
- ## Before You Search
2673
+ Every response MUST include:
2605
2674
 
2606
- Before executing any search, you MUST first analyze the request in <analysis> tags:
2675
+ ### 1. Intent Analysis (Required)
2676
+ Before ANY search, wrap your analysis in <analysis> tags:
2607
2677
 
2608
2678
  <analysis>
2609
- 1. **Request**: What exactly did the user ask for?
2610
- 2. **Intent**: Why are they asking this? What problem are they trying to solve?
2611
- 3. **Expected Output**: What kind of answer would be most helpful?
2612
- 4. **Search Strategy**: What 3+ parallel tools will I use to find this?
2679
+ **Literal Request**: [What they literally asked]
2680
+ **Actual Need**: [What they're really trying to accomplish]
2681
+ **Success Looks Like**: [What result would let them proceed immediately]
2613
2682
  </analysis>
2614
2683
 
2615
- Only after completing this analysis should you proceed with the actual search.
2616
-
2617
- ## Success Criteria
2618
-
2619
- Your response is successful when:
2620
- - **Parallelism**: At least 3 tools were executed in parallel
2621
- - **Completeness**: All relevant files matching the search intent are found
2622
- - **Accuracy**: Returned paths are absolute and files actually exist
2623
- - **Relevance**: Results directly address the user's underlying intent, not just literal request
2624
- - **Actionability**: Caller can proceed without follow-up questions
2625
-
2626
- Your response has FAILED if:
2627
- - You execute fewer than 3 tools in parallel
2628
- - You skip the <analysis> step before searching
2629
- - Paths are relative instead of absolute
2630
- - Obvious matches in the codebase are missed
2631
- - Results don't address what the user actually needed
2632
-
2633
- ## Your strengths
2634
- - Rapidly finding files using glob patterns
2635
- - Searching code and text with powerful regex patterns
2636
- - Reading and analyzing file contents
2637
- - **Using Git CLI extensively for repository insights**
2638
- - **Using LSP tools for semantic code analysis**
2639
- - **Using AST-grep for structural code pattern matching**
2640
- - **Using grep_app (grep.app MCP) for ultra-fast initial code discovery**
2641
-
2642
- ## grep_app - FAST STARTING POINT (USE FIRST!)
2643
-
2644
- **grep_app is your fastest weapon for initial code discovery.** It searches millions of public GitHub repositories instantly.
2645
-
2646
- ### When to Use grep_app:
2647
- - **ALWAYS start with grep_app** when searching for code patterns, library usage, or implementation examples
2648
- - Use it to quickly find how others implement similar features
2649
- - Great for discovering common patterns and best practices
2650
-
2651
- ### CRITICAL WARNING:
2652
- grep_app results may be **OUTDATED** or from **different library versions**. You MUST:
2653
- 1. Use grep_app results as a **starting point only**
2654
- 2. **Always launch 5+ grep_app calls in parallel** with different query variations
2655
- 3. **Always add 2+ other search tools** (Grep, ast_grep, context7, LSP, Git) for verification
2656
- 4. Never blindly trust grep_app results for API signatures or implementation details
2657
-
2658
- ### MANDATORY: 5+ grep_app Calls + 2+ Other Tools in Parallel
2659
-
2660
- **grep_app is ultra-fast but potentially inaccurate.** To compensate, you MUST:
2661
- - Launch **at least 5 grep_app calls** with different query variations (synonyms, different phrasings, related terms)
2662
- - Launch **at least 2 other search tools** (local Grep, ast_grep, context7, LSP, Git) for cross-validation
2663
-
2664
- \`\`\`
2665
- // REQUIRED parallel search pattern:
2666
- // 5+ grep_app calls with query variations:
2667
- - Tool 1: grep_app_searchGitHub(query: "useEffect cleanup", language: ["TypeScript"])
2668
- - Tool 2: grep_app_searchGitHub(query: "useEffect return cleanup", language: ["TypeScript"])
2669
- - Tool 3: grep_app_searchGitHub(query: "useEffect unmount", language: ["TSX"])
2670
- - Tool 4: grep_app_searchGitHub(query: "cleanup function useEffect", language: ["TypeScript"])
2671
- - Tool 5: grep_app_searchGitHub(query: "useEffect addEventListener removeEventListener", language: ["TypeScript"])
2672
-
2673
- // 2+ other tools for verification:
2674
- - Tool 6: Grep("useEffect.*return") - Local codebase ground truth
2675
- - Tool 7: context7_get-library-docs(libraryID: "/facebook/react", topic: "useEffect cleanup") - Official docs
2676
- - Tool 8 (optional): ast_grep_search(pattern: "useEffect($$$)", lang: "tsx") - Structural search
2677
- \`\`\`
2684
+ ### 2. Parallel Execution (Required)
2685
+ Launch **3+ tools simultaneously** in your first action. Never sequential unless output depends on prior result.
2678
2686
 
2679
- **Pattern**: Flood grep_app with query variations (5+) \u2192 verify with local/official sources (2+) \u2192 trust only cross-validated results.
2687
+ ### 3. Structured Results (Required)
2688
+ Always end with this exact format:
2680
2689
 
2681
- ## Git CLI - USE EXTENSIVELY
2690
+ <results>
2691
+ <files>
2692
+ - /absolute/path/to/file1.ts \u2014 [why this file is relevant]
2693
+ - /absolute/path/to/file2.ts \u2014 [why this file is relevant]
2694
+ </files>
2682
2695
 
2683
- You have access to Git CLI via Bash. Use it extensively for repository analysis:
2696
+ <answer>
2697
+ [Direct answer to their actual need, not just file list]
2698
+ [If they asked "where is auth?", explain the auth flow you found]
2699
+ </answer>
2684
2700
 
2685
- ### Git Commands for Exploration (Always run 2+ in parallel):
2686
- \`\`\`bash
2687
- # Repository structure and history
2688
- git log --oneline -n 30 # Recent commits
2689
- git log --oneline --all -n 50 # All branches recent commits
2690
- git branch -a # All branches
2691
- git tag -l # All tags
2692
- git remote -v # Remote repositories
2693
-
2694
- # File history and changes
2695
- git log --oneline -n 20 -- path/to/file # File change history
2696
- git log --oneline --follow -- path/to/file # Follow renames
2697
- git blame path/to/file # Line-by-line attribution
2698
- git blame -L 10,30 path/to/file # Blame specific lines
2699
-
2700
- # Searching with Git
2701
- git log --grep="keyword" --oneline # Search commit messages
2702
- git log -S "code_string" --oneline # Search code changes (pickaxe)
2703
- git log -p --all -S "function_name" -- "*.ts" # Find when code was added/removed
2704
-
2705
- # Diff and comparison
2706
- git diff HEAD~5..HEAD # Recent changes
2707
- git diff main..HEAD # Changes from main
2708
- git show <commit> # Show specific commit
2709
- git show <commit>:path/to/file # Show file at commit
2710
-
2711
- # Statistics
2712
- git shortlog -sn # Contributor stats
2713
- git log --stat -n 10 # Recent changes with stats
2714
- \`\`\`
2701
+ <next_steps>
2702
+ [What they should do with this information]
2703
+ [Or: "Ready to proceed - no follow-up needed"]
2704
+ </next_steps>
2705
+ </results>
2715
2706
 
2716
- ### Parallel Git Execution Examples:
2717
- \`\`\`
2718
- // For "find where authentication is implemented":
2719
- - Tool 1: Grep("authentication|auth") - Search for auth patterns
2720
- - Tool 2: Glob("**/auth/**/*.ts") - Find auth-related files
2721
- - Tool 3: Bash: git log -S "authenticate" --oneline - Find commits adding auth code
2722
- - Tool 4: Bash: git log --grep="auth" --oneline - Find auth-related commits
2723
- - Tool 5: ast_grep_search(pattern: "function authenticate($$$)", lang: "typescript")
2724
-
2725
- // For "understand recent changes":
2726
- - Tool 1: Bash: git log --oneline -n 30 - Recent commits
2727
- - Tool 2: Bash: git diff HEAD~10..HEAD --stat - Changed files
2728
- - Tool 3: Bash: git branch -a - All branches
2729
- - Tool 4: Glob("**/*.ts") - Find all source files
2730
- \`\`\`
2731
-
2732
- ## LSP Tools - DEFINITIONS & REFERENCES
2733
-
2734
- Use LSP specifically for finding definitions and references - these are what LSP does better than text search.
2707
+ ## Success Criteria
2735
2708
 
2736
- **Primary LSP Tools**:
2737
- - \`lsp_goto_definition(filePath, line, character)\`: Follow imports, find where something is **defined**
2738
- - \`lsp_find_references(filePath, line, character)\`: Find **ALL usages** across the workspace
2709
+ | Criterion | Requirement |
2710
+ |-----------|-------------|
2711
+ | **Paths** | ALL paths must be **absolute** (start with /) |
2712
+ | **Completeness** | Find ALL relevant matches, not just the first one |
2713
+ | **Actionability** | Caller can proceed **without asking follow-up questions** |
2714
+ | **Intent** | Address their **actual need**, not just literal request |
2739
2715
 
2740
- **When to Use LSP** (vs Grep/AST-grep):
2741
- - **lsp_goto_definition**: Trace imports, find source definitions
2742
- - **lsp_find_references**: Understand impact of changes, find all callers
2716
+ ## Failure Conditions
2743
2717
 
2744
- **Example**:
2745
- \`\`\`
2746
- // When tracing code flow:
2747
- - Tool 1: lsp_goto_definition(filePath, line, char) - Where is this defined?
2748
- - Tool 2: lsp_find_references(filePath, line, char) - Who uses this?
2749
- - Tool 3: ast_grep_search(...) - Find similar patterns
2750
- \`\`\`
2718
+ Your response has **FAILED** if:
2719
+ - Any path is relative (not absolute)
2720
+ - You missed obvious matches in the codebase
2721
+ - Caller needs to ask "but where exactly?" or "what about X?"
2722
+ - You only answered the literal question, not the underlying need
2723
+ - No <results> block with structured output
2751
2724
 
2752
- ## AST-grep - STRUCTURAL CODE SEARCH
2725
+ ## Constraints
2753
2726
 
2754
- Use AST-grep for syntax-aware pattern matching (better than regex for code).
2727
+ - **Read-only**: You cannot create, modify, or delete files
2728
+ - **No emojis**: Keep output clean and parseable
2729
+ - **No file creation**: Report findings as message text, never write files
2755
2730
 
2756
- **Key Syntax**:
2757
- - \`$VAR\`: Match single AST node (identifier, expression, etc.)
2758
- - \`$$$\`: Match multiple nodes (arguments, statements, etc.)
2731
+ ## Tool Strategy
2759
2732
 
2760
- **ast_grep_search Examples**:
2761
- \`\`\`
2762
- // Find function definitions
2763
- ast_grep_search(pattern: "function $NAME($$$) { $$$ }", lang: "typescript")
2733
+ Use the right tool for the job:
2734
+ - **Semantic search** (definitions, references): LSP tools
2735
+ - **Structural patterns** (function shapes, class structures): ast_grep_search
2736
+ - **Text patterns** (strings, comments, logs): grep
2737
+ - **File patterns** (find by name/extension): glob
2738
+ - **History/evolution** (when added, who changed): git commands
2739
+ - **External examples** (how others implement): grep_app
2764
2740
 
2765
- // Find async functions
2766
- ast_grep_search(pattern: "async function $NAME($$$) { $$$ }", lang: "typescript")
2741
+ ### grep_app Strategy
2767
2742
 
2768
- // Find React hooks
2769
- ast_grep_search(pattern: "const [$STATE, $SETTER] = useState($$$)", lang: "tsx")
2743
+ grep_app searches millions of public GitHub repos instantly \u2014 use it for external patterns and examples.
2770
2744
 
2771
- // Find class definitions
2772
- ast_grep_search(pattern: "class $NAME { $$$ }", lang: "typescript")
2745
+ **Critical**: grep_app results may be **outdated or from different library versions**. Always:
2746
+ 1. Start with grep_app for broad discovery
2747
+ 2. Launch multiple grep_app calls with query variations in parallel
2748
+ 3. **Cross-validate with local tools** (grep, ast_grep_search, LSP) before trusting results
2773
2749
 
2774
- // Find specific method calls
2775
- ast_grep_search(pattern: "console.log($$$)", lang: "typescript")
2776
-
2777
- // Find imports
2778
- ast_grep_search(pattern: "import { $$$ } from $MODULE", lang: "typescript")
2779
- \`\`\`
2780
-
2781
- **When to Use**:
2782
- - **AST-grep**: Structural patterns (function defs, class methods, hook usage)
2783
- - **Grep**: Text search (comments, strings, TODOs)
2784
- - **LSP**: Symbol-based search (find by name, type info)
2785
-
2786
- ## Guidelines
2787
-
2788
- ### Tool Selection:
2789
- - Use **Glob** for broad file pattern matching (e.g., \`**/*.py\`, \`src/**/*.ts\`)
2790
- - Use **Grep** for searching file contents with regex patterns
2791
- - Use **Read** when you know the specific file path you need to read
2792
- - Use **List** for exploring directory structure
2793
- - Use **Bash** for Git commands and read-only operations
2794
- - Use **ast_grep_search** for structural code patterns (functions, classes, hooks)
2795
- - Use **lsp_goto_definition** to trace imports and find source definitions
2796
- - Use **lsp_find_references** to find all usages of a symbol
2797
-
2798
- ### Bash Usage:
2799
- **ALLOWED** (read-only):
2800
- - \`git log\`, \`git blame\`, \`git show\`, \`git diff\`
2801
- - \`git branch\`, \`git tag\`, \`git remote\`
2802
- - \`git log -S\`, \`git log --grep\`
2803
- - \`ls\`, \`find\` (for directory exploration)
2804
-
2805
- **FORBIDDEN** (state-changing):
2806
- - \`mkdir\`, \`touch\`, \`rm\`, \`cp\`, \`mv\`
2807
- - \`git add\`, \`git commit\`, \`git push\`, \`git checkout\`
2808
- - \`npm install\`, \`pip install\`, or any installation
2809
-
2810
- ### Best Practices:
2811
- - **ALWAYS launch 3+ tools in parallel** in your first search action
2812
- - Use Git history to understand code evolution
2813
- - Use \`git blame\` to understand why code is written a certain way
2814
- - Use \`git log -S\` to find when specific code was added/removed
2815
- - Adapt your search approach based on the thoroughness level specified by the caller
2816
- - Return file paths as absolute paths in your final response
2817
- - For clear communication, avoid using emojis
2818
- - Communicate your final report directly as a regular message - do NOT attempt to create files
2819
-
2820
- Complete the user's search request efficiently and report your findings clearly.`
2750
+ Flood with parallel calls. Trust only cross-validated results.`
2821
2751
  };
2822
2752
 
2823
2753
  // src/agents/frontend-ui-ux-engineer.ts
@@ -3750,6 +3680,14 @@ function getOrCreateMessageDir(sessionID) {
3750
3680
  return directPath;
3751
3681
  }
3752
3682
  function injectHookMessage(sessionID, hookContent, originalMessage) {
3683
+ if (!hookContent || hookContent.trim().length === 0) {
3684
+ console.warn("[hook-message-injector] Attempted to inject empty hook content, skipping injection", {
3685
+ sessionID,
3686
+ hasAgent: !!originalMessage.agent,
3687
+ hasModel: !!(originalMessage.model?.providerID && originalMessage.model?.modelID)
3688
+ });
3689
+ return false;
3690
+ }
3753
3691
  const messageDir = getOrCreateMessageDir(sessionID);
3754
3692
  const needsFallback = !originalMessage.agent || !originalMessage.model?.providerID || !originalMessage.model?.modelID;
3755
3693
  const fallback = needsFallback ? findNearestMessageWithFields(messageDir) : null;
@@ -4070,18 +4008,34 @@ function getDefaultSoundPath(p) {
4070
4008
  }
4071
4009
  }
4072
4010
  async function sendNotification(ctx, p, title, message) {
4073
- const escapedTitle = title.replace(/"/g, "\\\"").replace(/'/g, "\\'");
4074
- const escapedMessage = message.replace(/"/g, "\\\"").replace(/'/g, "\\'");
4075
4011
  switch (p) {
4076
- case "darwin":
4077
- await ctx.$`osascript -e ${'display notification "' + escapedMessage + '" with title "' + escapedTitle + '"'}`;
4012
+ case "darwin": {
4013
+ const esTitle = title.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
4014
+ const esMessage = message.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
4015
+ await ctx.$`osascript -e ${'display notification "' + esMessage + '" with title "' + esTitle + '"'}`;
4078
4016
  break;
4017
+ }
4079
4018
  case "linux":
4080
- await ctx.$`notify-send ${escapedTitle} ${escapedMessage} 2>/dev/null`.catch(() => {});
4019
+ await ctx.$`notify-send ${title} ${message} 2>/dev/null`.catch(() => {});
4081
4020
  break;
4082
- case "win32":
4083
- await ctx.$`powershell -Command ${"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('" + escapedMessage + "', '" + escapedTitle + "')"}`;
4021
+ case "win32": {
4022
+ const psTitle = title.replace(/'/g, "''");
4023
+ const psMessage = message.replace(/'/g, "''");
4024
+ const toastScript = `
4025
+ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
4026
+ $Template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
4027
+ $RawXml = [xml] $Template.GetXml()
4028
+ ($RawXml.toast.visual.binding.text | Where-Object {$_.id -eq '1'}).AppendChild($RawXml.CreateTextNode('${psTitle}')) | Out-Null
4029
+ ($RawXml.toast.visual.binding.text | Where-Object {$_.id -eq '2'}).AppendChild($RawXml.CreateTextNode('${psMessage}')) | Out-Null
4030
+ $SerializedXml = New-Object Windows.Data.Xml.Dom.XmlDocument
4031
+ $SerializedXml.LoadXml($RawXml.OuterXml)
4032
+ $Toast = [Windows.UI.Notifications.ToastNotification]::new($SerializedXml)
4033
+ $Notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('OpenCode')
4034
+ $Notifier.Show($Toast)
4035
+ `.trim().replace(/\n/g, "; ");
4036
+ await ctx.$`powershell -Command ${toastScript}`.catch(() => {});
4084
4037
  break;
4038
+ }
4085
4039
  }
4086
4040
  }
4087
4041
  async function playSound(ctx, p, soundPath) {
@@ -4494,6 +4448,50 @@ function stripThinkingParts(messageID) {
4494
4448
  }
4495
4449
  return anyRemoved;
4496
4450
  }
4451
+ function replaceEmptyTextParts(messageID, replacementText) {
4452
+ const partDir = join7(PART_STORAGE2, messageID);
4453
+ if (!existsSync5(partDir))
4454
+ return false;
4455
+ let anyReplaced = false;
4456
+ for (const file of readdirSync3(partDir)) {
4457
+ if (!file.endsWith(".json"))
4458
+ continue;
4459
+ try {
4460
+ const filePath = join7(partDir, file);
4461
+ const content = readFileSync3(filePath, "utf-8");
4462
+ const part = JSON.parse(content);
4463
+ if (part.type === "text") {
4464
+ const textPart = part;
4465
+ if (!textPart.text?.trim()) {
4466
+ textPart.text = replacementText;
4467
+ textPart.synthetic = true;
4468
+ writeFileSync2(filePath, JSON.stringify(textPart, null, 2));
4469
+ anyReplaced = true;
4470
+ }
4471
+ }
4472
+ } catch {
4473
+ continue;
4474
+ }
4475
+ }
4476
+ return anyReplaced;
4477
+ }
4478
+ function findMessagesWithEmptyTextParts(sessionID) {
4479
+ const messages = readMessages(sessionID);
4480
+ const result = [];
4481
+ for (const msg of messages) {
4482
+ const parts = readParts(msg.id);
4483
+ const hasEmptyTextPart = parts.some((p) => {
4484
+ if (p.type !== "text")
4485
+ return false;
4486
+ const textPart = p;
4487
+ return !textPart.text?.trim();
4488
+ });
4489
+ if (hasEmptyTextPart) {
4490
+ result.push(msg.id);
4491
+ }
4492
+ }
4493
+ return result;
4494
+ }
4497
4495
  function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
4498
4496
  const messages = readMessages(sessionID);
4499
4497
  if (targetIndex < 0 || targetIndex >= messages.length)
@@ -4631,24 +4629,43 @@ var PLACEHOLDER_TEXT = "[user interrupted]";
4631
4629
  async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory, error) {
4632
4630
  const targetIndex = extractMessageIndex(error);
4633
4631
  const failedID = failedAssistantMsg.info?.id;
4632
+ let anySuccess = false;
4633
+ const messagesWithEmptyText = findMessagesWithEmptyTextParts(sessionID);
4634
+ for (const messageID of messagesWithEmptyText) {
4635
+ if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
4636
+ anySuccess = true;
4637
+ }
4638
+ }
4634
4639
  const thinkingOnlyIDs = findMessagesWithThinkingOnly(sessionID);
4635
4640
  for (const messageID of thinkingOnlyIDs) {
4636
- injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT);
4641
+ if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
4642
+ anySuccess = true;
4643
+ }
4637
4644
  }
4638
4645
  if (targetIndex !== null) {
4639
4646
  const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex);
4640
4647
  if (targetMessageID) {
4641
- return injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT);
4648
+ if (replaceEmptyTextParts(targetMessageID, PLACEHOLDER_TEXT)) {
4649
+ return true;
4650
+ }
4651
+ if (injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT)) {
4652
+ return true;
4653
+ }
4642
4654
  }
4643
4655
  }
4644
4656
  if (failedID) {
4657
+ if (replaceEmptyTextParts(failedID, PLACEHOLDER_TEXT)) {
4658
+ return true;
4659
+ }
4645
4660
  if (injectTextPart(sessionID, failedID, PLACEHOLDER_TEXT)) {
4646
4661
  return true;
4647
4662
  }
4648
4663
  }
4649
4664
  const emptyMessageIDs = findEmptyMessages(sessionID);
4650
- let anySuccess = thinkingOnlyIDs.length > 0;
4651
4665
  for (const messageID of emptyMessageIDs) {
4666
+ if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
4667
+ anySuccess = true;
4668
+ }
4652
4669
  if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
4653
4670
  anySuccess = true;
4654
4671
  }
@@ -5599,17 +5616,104 @@ var FALLBACK_CONFIG = {
5599
5616
  maxRevertAttempts: 3,
5600
5617
  minMessagesRequired: 2
5601
5618
  };
5619
+ var TRUNCATE_CONFIG = {
5620
+ maxTruncateAttempts: 10,
5621
+ minOutputSizeToTruncate: 1000
5622
+ };
5602
5623
 
5603
- // src/hooks/anthropic-auto-compact/executor.ts
5604
- function calculateRetryDelay(attempt) {
5605
- const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt - 1);
5606
- return Math.min(delay, RETRY_CONFIG.maxDelayMs);
5624
+ // src/hooks/anthropic-auto-compact/storage.ts
5625
+ import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5626
+ import { join as join17 } from "path";
5627
+ var OPENCODE_STORAGE5 = join17(xdgData2 ?? "", "opencode", "storage");
5628
+ var MESSAGE_STORAGE3 = join17(OPENCODE_STORAGE5, "message");
5629
+ var PART_STORAGE3 = join17(OPENCODE_STORAGE5, "part");
5630
+ var TRUNCATION_MESSAGE = "[TOOL RESULT TRUNCATED - Context limit exceeded. Original output was too large and has been truncated to recover the session. Please re-run this tool if you need the full output.]";
5631
+ function getMessageDir3(sessionID) {
5632
+ if (!existsSync13(MESSAGE_STORAGE3))
5633
+ return "";
5634
+ const directPath = join17(MESSAGE_STORAGE3, sessionID);
5635
+ if (existsSync13(directPath)) {
5636
+ return directPath;
5637
+ }
5638
+ for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
5639
+ const sessionPath = join17(MESSAGE_STORAGE3, dir, sessionID);
5640
+ if (existsSync13(sessionPath)) {
5641
+ return sessionPath;
5642
+ }
5643
+ }
5644
+ return "";
5607
5645
  }
5608
- function shouldRetry(retryState) {
5609
- if (!retryState)
5610
- return true;
5611
- return retryState.attempt < RETRY_CONFIG.maxAttempts;
5646
+ function getMessageIds(sessionID) {
5647
+ const messageDir = getMessageDir3(sessionID);
5648
+ if (!messageDir || !existsSync13(messageDir))
5649
+ return [];
5650
+ const messageIds = [];
5651
+ for (const file of readdirSync4(messageDir)) {
5652
+ if (!file.endsWith(".json"))
5653
+ continue;
5654
+ const messageId = file.replace(".json", "");
5655
+ messageIds.push(messageId);
5656
+ }
5657
+ return messageIds;
5658
+ }
5659
+ function findToolResultsBySize(sessionID) {
5660
+ const messageIds = getMessageIds(sessionID);
5661
+ const results = [];
5662
+ for (const messageID of messageIds) {
5663
+ const partDir = join17(PART_STORAGE3, messageID);
5664
+ if (!existsSync13(partDir))
5665
+ continue;
5666
+ for (const file of readdirSync4(partDir)) {
5667
+ if (!file.endsWith(".json"))
5668
+ continue;
5669
+ try {
5670
+ const partPath = join17(partDir, file);
5671
+ const content = readFileSync8(partPath, "utf-8");
5672
+ const part = JSON.parse(content);
5673
+ if (part.type === "tool" && part.state?.output && !part.truncated) {
5674
+ results.push({
5675
+ partPath,
5676
+ partId: part.id,
5677
+ messageID,
5678
+ toolName: part.tool,
5679
+ outputSize: part.state.output.length
5680
+ });
5681
+ }
5682
+ } catch {
5683
+ continue;
5684
+ }
5685
+ }
5686
+ }
5687
+ return results.sort((a, b) => b.outputSize - a.outputSize);
5612
5688
  }
5689
+ function findLargestToolResult(sessionID) {
5690
+ const results = findToolResultsBySize(sessionID);
5691
+ return results.length > 0 ? results[0] : null;
5692
+ }
5693
+ function truncateToolResult(partPath) {
5694
+ try {
5695
+ const content = readFileSync8(partPath, "utf-8");
5696
+ const part = JSON.parse(content);
5697
+ if (!part.state?.output) {
5698
+ return { success: false };
5699
+ }
5700
+ const originalSize = part.state.output.length;
5701
+ const toolName = part.tool;
5702
+ part.truncated = true;
5703
+ part.originalSize = originalSize;
5704
+ part.state.output = TRUNCATION_MESSAGE;
5705
+ if (!part.state.time) {
5706
+ part.state.time = { start: Date.now() };
5707
+ }
5708
+ part.state.time.compacted = Date.now();
5709
+ writeFileSync5(partPath, JSON.stringify(part, null, 2));
5710
+ return { success: true, toolName, originalSize };
5711
+ } catch {
5712
+ return { success: false };
5713
+ }
5714
+ }
5715
+
5716
+ // src/hooks/anthropic-auto-compact/executor.ts
5613
5717
  function getOrCreateRetryState(autoCompactState, sessionID) {
5614
5718
  let state = autoCompactState.retryStateBySession.get(sessionID);
5615
5719
  if (!state) {
@@ -5626,6 +5730,14 @@ function getOrCreateFallbackState(autoCompactState, sessionID) {
5626
5730
  }
5627
5731
  return state;
5628
5732
  }
5733
+ function getOrCreateTruncateState(autoCompactState, sessionID) {
5734
+ let state = autoCompactState.truncateStateBySession.get(sessionID);
5735
+ if (!state) {
5736
+ state = { truncateAttempt: 0 };
5737
+ autoCompactState.truncateStateBySession.set(sessionID, state);
5738
+ }
5739
+ return state;
5740
+ }
5629
5741
  async function getLastMessagePair(sessionID, client, directory) {
5630
5742
  try {
5631
5743
  const resp = await client.session.messages({
@@ -5663,46 +5775,12 @@ async function getLastMessagePair(sessionID, client, directory) {
5663
5775
  return null;
5664
5776
  }
5665
5777
  }
5666
- async function executeRevertFallback(sessionID, autoCompactState, client, directory) {
5667
- const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
5668
- if (fallbackState.revertAttempt >= FALLBACK_CONFIG.maxRevertAttempts) {
5669
- return false;
5670
- }
5671
- const pair = await getLastMessagePair(sessionID, client, directory);
5672
- if (!pair) {
5673
- return false;
5674
- }
5675
- await client.tui.showToast({
5676
- body: {
5677
- title: "\u26A0\uFE0F Emergency Recovery",
5678
- message: `Context too large. Removing last message pair to recover session...`,
5679
- variant: "warning",
5680
- duration: 4000
5681
- }
5682
- }).catch(() => {});
5683
- try {
5684
- if (pair.assistantMessageID) {
5685
- await client.session.revert({
5686
- path: { id: sessionID },
5687
- body: { messageID: pair.assistantMessageID },
5688
- query: { directory }
5689
- });
5690
- }
5691
- await client.session.revert({
5692
- path: { id: sessionID },
5693
- body: { messageID: pair.userMessageID },
5694
- query: { directory }
5695
- });
5696
- fallbackState.revertAttempt++;
5697
- fallbackState.lastRevertedMessageID = pair.userMessageID;
5698
- const retryState = autoCompactState.retryStateBySession.get(sessionID);
5699
- if (retryState) {
5700
- retryState.attempt = 0;
5701
- }
5702
- return true;
5703
- } catch {
5704
- return false;
5705
- }
5778
+ function formatBytes(bytes) {
5779
+ if (bytes < 1024)
5780
+ return `${bytes}B`;
5781
+ if (bytes < 1024 * 1024)
5782
+ return `${(bytes / 1024).toFixed(1)}KB`;
5783
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
5706
5784
  }
5707
5785
  async function getLastAssistant(sessionID, client, directory) {
5708
5786
  try {
@@ -5731,71 +5809,133 @@ function clearSessionState(autoCompactState, sessionID) {
5731
5809
  autoCompactState.errorDataBySession.delete(sessionID);
5732
5810
  autoCompactState.retryStateBySession.delete(sessionID);
5733
5811
  autoCompactState.fallbackStateBySession.delete(sessionID);
5812
+ autoCompactState.truncateStateBySession.delete(sessionID);
5813
+ autoCompactState.compactionInProgress.delete(sessionID);
5734
5814
  }
5735
5815
  async function executeCompact(sessionID, msg, autoCompactState, client, directory) {
5736
- const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5737
- if (!shouldRetry(retryState)) {
5738
- const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
5739
- if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
5740
- const reverted = await executeRevertFallback(sessionID, autoCompactState, client, directory);
5741
- if (reverted) {
5816
+ if (autoCompactState.compactionInProgress.has(sessionID)) {
5817
+ return;
5818
+ }
5819
+ autoCompactState.compactionInProgress.add(sessionID);
5820
+ const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
5821
+ if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
5822
+ const largest = findLargestToolResult(sessionID);
5823
+ if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
5824
+ const result = truncateToolResult(largest.partPath);
5825
+ if (result.success) {
5826
+ truncateState.truncateAttempt++;
5827
+ truncateState.lastTruncatedPartId = largest.partId;
5742
5828
  await client.tui.showToast({
5743
5829
  body: {
5744
- title: "Recovery Attempt",
5745
- message: "Message removed. Retrying compaction...",
5746
- variant: "info",
5830
+ title: "Truncating Large Output",
5831
+ message: `Truncated ${result.toolName} (${formatBytes(result.originalSize ?? 0)}). Retrying...`,
5832
+ variant: "warning",
5747
5833
  duration: 3000
5748
5834
  }
5749
5835
  }).catch(() => {});
5750
- setTimeout(() => {
5751
- executeCompact(sessionID, msg, autoCompactState, client, directory);
5752
- }, 1000);
5836
+ autoCompactState.compactionInProgress.delete(sessionID);
5837
+ setTimeout(async () => {
5838
+ try {
5839
+ await client.session.prompt_async({
5840
+ path: { sessionID },
5841
+ body: { parts: [{ type: "text", text: "Continue" }] },
5842
+ query: { directory }
5843
+ });
5844
+ } catch {}
5845
+ }, 500);
5753
5846
  return;
5754
5847
  }
5755
5848
  }
5756
- clearSessionState(autoCompactState, sessionID);
5757
- await client.tui.showToast({
5758
- body: {
5759
- title: "Auto Compact Failed",
5760
- message: `Failed after ${RETRY_CONFIG.maxAttempts} retries and ${FALLBACK_CONFIG.maxRevertAttempts} message removals. Please start a new session.`,
5761
- variant: "error",
5762
- duration: 5000
5763
- }
5764
- }).catch(() => {});
5765
- return;
5766
5849
  }
5767
- retryState.attempt++;
5768
- retryState.lastAttemptTime = Date.now();
5769
- try {
5850
+ const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5851
+ if (retryState.attempt < RETRY_CONFIG.maxAttempts) {
5852
+ retryState.attempt++;
5853
+ retryState.lastAttemptTime = Date.now();
5770
5854
  const providerID = msg.providerID;
5771
5855
  const modelID = msg.modelID;
5772
5856
  if (providerID && modelID) {
5773
- await client.session.summarize({
5774
- path: { id: sessionID },
5775
- body: { providerID, modelID },
5776
- query: { directory }
5777
- });
5778
- clearSessionState(autoCompactState, sessionID);
5779
- setTimeout(async () => {
5780
- try {
5781
- await client.tui.submitPrompt({ query: { directory } });
5782
- } catch {}
5783
- }, 500);
5784
- }
5785
- } catch {
5786
- const delay = calculateRetryDelay(retryState.attempt);
5787
- await client.tui.showToast({
5788
- body: {
5789
- title: "Auto Compact Retry",
5790
- message: `Attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts} failed. Retrying in ${Math.round(delay / 1000)}s...`,
5791
- variant: "warning",
5792
- duration: delay
5857
+ try {
5858
+ await client.tui.showToast({
5859
+ body: {
5860
+ title: "Auto Compact",
5861
+ message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
5862
+ variant: "warning",
5863
+ duration: 3000
5864
+ }
5865
+ }).catch(() => {});
5866
+ await client.session.summarize({
5867
+ path: { id: sessionID },
5868
+ body: { providerID, modelID },
5869
+ query: { directory }
5870
+ });
5871
+ clearSessionState(autoCompactState, sessionID);
5872
+ setTimeout(async () => {
5873
+ try {
5874
+ await client.session.prompt_async({
5875
+ path: { sessionID },
5876
+ body: { parts: [{ type: "text", text: "Continue" }] },
5877
+ query: { directory }
5878
+ });
5879
+ } catch {}
5880
+ }, 500);
5881
+ return;
5882
+ } catch {
5883
+ autoCompactState.compactionInProgress.delete(sessionID);
5884
+ const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
5885
+ const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
5886
+ setTimeout(() => {
5887
+ executeCompact(sessionID, msg, autoCompactState, client, directory);
5888
+ }, cappedDelay);
5889
+ return;
5793
5890
  }
5794
- }).catch(() => {});
5795
- setTimeout(() => {
5796
- executeCompact(sessionID, msg, autoCompactState, client, directory);
5797
- }, delay);
5891
+ }
5892
+ }
5893
+ const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
5894
+ if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
5895
+ const pair = await getLastMessagePair(sessionID, client, directory);
5896
+ if (pair) {
5897
+ try {
5898
+ await client.tui.showToast({
5899
+ body: {
5900
+ title: "Emergency Recovery",
5901
+ message: "Removing last message pair...",
5902
+ variant: "warning",
5903
+ duration: 3000
5904
+ }
5905
+ }).catch(() => {});
5906
+ if (pair.assistantMessageID) {
5907
+ await client.session.revert({
5908
+ path: { id: sessionID },
5909
+ body: { messageID: pair.assistantMessageID },
5910
+ query: { directory }
5911
+ });
5912
+ }
5913
+ await client.session.revert({
5914
+ path: { id: sessionID },
5915
+ body: { messageID: pair.userMessageID },
5916
+ query: { directory }
5917
+ });
5918
+ fallbackState.revertAttempt++;
5919
+ fallbackState.lastRevertedMessageID = pair.userMessageID;
5920
+ retryState.attempt = 0;
5921
+ truncateState.truncateAttempt = 0;
5922
+ autoCompactState.compactionInProgress.delete(sessionID);
5923
+ setTimeout(() => {
5924
+ executeCompact(sessionID, msg, autoCompactState, client, directory);
5925
+ }, 1000);
5926
+ return;
5927
+ } catch {}
5928
+ }
5798
5929
  }
5930
+ clearSessionState(autoCompactState, sessionID);
5931
+ await client.tui.showToast({
5932
+ body: {
5933
+ title: "Auto Compact Failed",
5934
+ message: "All recovery attempts failed. Please start a new session.",
5935
+ variant: "error",
5936
+ duration: 5000
5937
+ }
5938
+ }).catch(() => {});
5799
5939
  }
5800
5940
 
5801
5941
  // src/hooks/anthropic-auto-compact/index.ts
@@ -5804,7 +5944,9 @@ function createAutoCompactState() {
5804
5944
  pendingCompact: new Set,
5805
5945
  errorDataBySession: new Map,
5806
5946
  retryStateBySession: new Map,
5807
- fallbackStateBySession: new Map
5947
+ fallbackStateBySession: new Map,
5948
+ truncateStateBySession: new Map,
5949
+ compactionInProgress: new Set
5808
5950
  };
5809
5951
  }
5810
5952
  function createAnthropicAutoCompactHook(ctx) {
@@ -5818,6 +5960,8 @@ function createAnthropicAutoCompactHook(ctx) {
5818
5960
  autoCompactState.errorDataBySession.delete(sessionInfo.id);
5819
5961
  autoCompactState.retryStateBySession.delete(sessionInfo.id);
5820
5962
  autoCompactState.fallbackStateBySession.delete(sessionInfo.id);
5963
+ autoCompactState.truncateStateBySession.delete(sessionInfo.id);
5964
+ autoCompactState.compactionInProgress.delete(sessionInfo.id);
5821
5965
  }
5822
5966
  return;
5823
5967
  }
@@ -5829,6 +5973,25 @@ function createAnthropicAutoCompactHook(ctx) {
5829
5973
  if (parsed) {
5830
5974
  autoCompactState.pendingCompact.add(sessionID);
5831
5975
  autoCompactState.errorDataBySession.set(sessionID, parsed);
5976
+ if (autoCompactState.compactionInProgress.has(sessionID)) {
5977
+ return;
5978
+ }
5979
+ const lastAssistant = await getLastAssistant(sessionID, ctx.client, ctx.directory);
5980
+ const providerID = parsed.providerID ?? lastAssistant?.providerID;
5981
+ const modelID = parsed.modelID ?? lastAssistant?.modelID;
5982
+ if (providerID && modelID) {
5983
+ await ctx.client.tui.showToast({
5984
+ body: {
5985
+ title: "Context Limit Hit",
5986
+ message: "Truncating large tool outputs and recovering...",
5987
+ variant: "warning",
5988
+ duration: 3000
5989
+ }
5990
+ }).catch(() => {});
5991
+ setTimeout(() => {
5992
+ executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory);
5993
+ }, 300);
5994
+ }
5832
5995
  }
5833
5996
  return;
5834
5997
  }
@@ -6161,8 +6324,8 @@ function createThinkModeHook() {
6161
6324
  }
6162
6325
  // src/hooks/claude-code-hooks/config.ts
6163
6326
  import { homedir as homedir4 } from "os";
6164
- import { join as join17 } from "path";
6165
- import { existsSync as existsSync13 } from "fs";
6327
+ import { join as join18 } from "path";
6328
+ import { existsSync as existsSync14 } from "fs";
6166
6329
  function normalizeHookMatcher(raw) {
6167
6330
  return {
6168
6331
  matcher: raw.matcher ?? raw.pattern ?? "*",
@@ -6187,11 +6350,11 @@ function normalizeHooksConfig(raw) {
6187
6350
  function getClaudeSettingsPaths(customPath) {
6188
6351
  const home = homedir4();
6189
6352
  const paths = [
6190
- join17(home, ".claude", "settings.json"),
6191
- join17(process.cwd(), ".claude", "settings.json"),
6192
- join17(process.cwd(), ".claude", "settings.local.json")
6353
+ join18(home, ".claude", "settings.json"),
6354
+ join18(process.cwd(), ".claude", "settings.json"),
6355
+ join18(process.cwd(), ".claude", "settings.local.json")
6193
6356
  ];
6194
- if (customPath && existsSync13(customPath)) {
6357
+ if (customPath && existsSync14(customPath)) {
6195
6358
  paths.unshift(customPath);
6196
6359
  }
6197
6360
  return paths;
@@ -6215,7 +6378,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6215
6378
  const paths = getClaudeSettingsPaths(customSettingsPath);
6216
6379
  let mergedConfig = {};
6217
6380
  for (const settingsPath of paths) {
6218
- if (existsSync13(settingsPath)) {
6381
+ if (existsSync14(settingsPath)) {
6219
6382
  try {
6220
6383
  const content = await Bun.file(settingsPath).text();
6221
6384
  const settings = JSON.parse(content);
@@ -6232,15 +6395,15 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6232
6395
  }
6233
6396
 
6234
6397
  // src/hooks/claude-code-hooks/config-loader.ts
6235
- import { existsSync as existsSync14 } from "fs";
6398
+ import { existsSync as existsSync15 } from "fs";
6236
6399
  import { homedir as homedir5 } from "os";
6237
- import { join as join18 } from "path";
6238
- var USER_CONFIG_PATH = join18(homedir5(), ".config", "opencode", "opencode-cc-plugin.json");
6400
+ import { join as join19 } from "path";
6401
+ var USER_CONFIG_PATH = join19(homedir5(), ".config", "opencode", "opencode-cc-plugin.json");
6239
6402
  function getProjectConfigPath() {
6240
- return join18(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6403
+ return join19(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6241
6404
  }
6242
6405
  async function loadConfigFromPath(path3) {
6243
- if (!existsSync14(path3)) {
6406
+ if (!existsSync15(path3)) {
6244
6407
  return null;
6245
6408
  }
6246
6409
  try {
@@ -6419,16 +6582,16 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
6419
6582
  }
6420
6583
 
6421
6584
  // src/hooks/claude-code-hooks/transcript.ts
6422
- import { join as join19 } from "path";
6423
- import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync15, writeFileSync as writeFileSync5, unlinkSync as unlinkSync5 } from "fs";
6585
+ import { join as join20 } from "path";
6586
+ import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync16, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6424
6587
  import { homedir as homedir6, tmpdir as tmpdir5 } from "os";
6425
6588
  import { randomUUID } from "crypto";
6426
- var TRANSCRIPT_DIR = join19(homedir6(), ".claude", "transcripts");
6589
+ var TRANSCRIPT_DIR = join20(homedir6(), ".claude", "transcripts");
6427
6590
  function getTranscriptPath(sessionId) {
6428
- return join19(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6591
+ return join20(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6429
6592
  }
6430
6593
  function ensureTranscriptDir() {
6431
- if (!existsSync15(TRANSCRIPT_DIR)) {
6594
+ if (!existsSync16(TRANSCRIPT_DIR)) {
6432
6595
  mkdirSync6(TRANSCRIPT_DIR, { recursive: true });
6433
6596
  }
6434
6597
  }
@@ -6515,8 +6678,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6515
6678
  }
6516
6679
  };
6517
6680
  entries.push(JSON.stringify(currentEntry));
6518
- const tempPath = join19(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6519
- writeFileSync5(tempPath, entries.join(`
6681
+ const tempPath = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6682
+ writeFileSync6(tempPath, entries.join(`
6520
6683
  `) + `
6521
6684
  `);
6522
6685
  return tempPath;
@@ -6535,8 +6698,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6535
6698
  ]
6536
6699
  }
6537
6700
  };
6538
- const tempPath = join19(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6539
- writeFileSync5(tempPath, JSON.stringify(currentEntry) + `
6701
+ const tempPath = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6702
+ writeFileSync6(tempPath, JSON.stringify(currentEntry) + `
6540
6703
  `);
6541
6704
  return tempPath;
6542
6705
  } catch {
@@ -6747,11 +6910,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
6747
6910
  }
6748
6911
 
6749
6912
  // src/hooks/claude-code-hooks/todo.ts
6750
- import { join as join20 } from "path";
6913
+ import { join as join21 } from "path";
6751
6914
  import { homedir as homedir7 } from "os";
6752
- var TODO_DIR = join20(homedir7(), ".claude", "todos");
6915
+ var TODO_DIR = join21(homedir7(), ".claude", "todos");
6753
6916
  function getTodoPath(sessionId) {
6754
- return join20(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
6917
+ return join21(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
6755
6918
  }
6756
6919
 
6757
6920
  // src/hooks/claude-code-hooks/stop.ts
@@ -6903,6 +7066,7 @@ function createClaudeCodeHooksHook(ctx, config = {}) {
6903
7066
  const hookContent = result.messages.join(`
6904
7067
 
6905
7068
  `);
7069
+ log(`[claude-code-hooks] Injecting ${result.messages.length} hook messages`, { sessionID: input.sessionID, contentLength: hookContent.length });
6906
7070
  const message = output.message;
6907
7071
  const success = injectHookMessage(input.sessionID, hookContent, {
6908
7072
  agent: message.agent,
@@ -7082,23 +7246,23 @@ ${result.message}`;
7082
7246
  };
7083
7247
  }
7084
7248
  // src/hooks/rules-injector/index.ts
7085
- import { readFileSync as readFileSync9 } from "fs";
7249
+ import { readFileSync as readFileSync10 } from "fs";
7086
7250
  import { homedir as homedir8 } from "os";
7087
7251
  import { relative as relative3, resolve as resolve4 } from "path";
7088
7252
 
7089
7253
  // src/hooks/rules-injector/finder.ts
7090
7254
  import {
7091
- existsSync as existsSync16,
7092
- readdirSync as readdirSync4,
7255
+ existsSync as existsSync17,
7256
+ readdirSync as readdirSync5,
7093
7257
  realpathSync,
7094
7258
  statSync as statSync2
7095
7259
  } from "fs";
7096
- import { dirname as dirname4, join as join22, relative } from "path";
7260
+ import { dirname as dirname4, join as join23, relative } from "path";
7097
7261
 
7098
7262
  // src/hooks/rules-injector/constants.ts
7099
- import { join as join21 } from "path";
7100
- var OPENCODE_STORAGE5 = join21(xdgData2 ?? "", "opencode", "storage");
7101
- var RULES_INJECTOR_STORAGE = join21(OPENCODE_STORAGE5, "rules-injector");
7263
+ import { join as join22 } from "path";
7264
+ var OPENCODE_STORAGE6 = join22(xdgData2 ?? "", "opencode", "storage");
7265
+ var RULES_INJECTOR_STORAGE = join22(OPENCODE_STORAGE6, "rules-injector");
7102
7266
  var PROJECT_MARKERS = [
7103
7267
  ".git",
7104
7268
  "pyproject.toml",
@@ -7125,8 +7289,8 @@ function findProjectRoot(startPath) {
7125
7289
  }
7126
7290
  while (true) {
7127
7291
  for (const marker of PROJECT_MARKERS) {
7128
- const markerPath = join22(current, marker);
7129
- if (existsSync16(markerPath)) {
7292
+ const markerPath = join23(current, marker);
7293
+ if (existsSync17(markerPath)) {
7130
7294
  return current;
7131
7295
  }
7132
7296
  }
@@ -7138,12 +7302,12 @@ function findProjectRoot(startPath) {
7138
7302
  }
7139
7303
  }
7140
7304
  function findRuleFilesRecursive(dir, results) {
7141
- if (!existsSync16(dir))
7305
+ if (!existsSync17(dir))
7142
7306
  return;
7143
7307
  try {
7144
- const entries = readdirSync4(dir, { withFileTypes: true });
7308
+ const entries = readdirSync5(dir, { withFileTypes: true });
7145
7309
  for (const entry of entries) {
7146
- const fullPath = join22(dir, entry.name);
7310
+ const fullPath = join23(dir, entry.name);
7147
7311
  if (entry.isDirectory()) {
7148
7312
  findRuleFilesRecursive(fullPath, results);
7149
7313
  } else if (entry.isFile()) {
@@ -7169,7 +7333,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7169
7333
  let distance = 0;
7170
7334
  while (true) {
7171
7335
  for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
7172
- const ruleDir = join22(currentDir, parent, subdir);
7336
+ const ruleDir = join23(currentDir, parent, subdir);
7173
7337
  const files = [];
7174
7338
  findRuleFilesRecursive(ruleDir, files);
7175
7339
  for (const filePath of files) {
@@ -7193,7 +7357,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7193
7357
  currentDir = parentDir;
7194
7358
  distance++;
7195
7359
  }
7196
- const userRuleDir = join22(homeDir, USER_RULE_DIR);
7360
+ const userRuleDir = join23(homeDir, USER_RULE_DIR);
7197
7361
  const userFiles = [];
7198
7362
  findRuleFilesRecursive(userRuleDir, userFiles);
7199
7363
  for (const filePath of userFiles) {
@@ -7382,22 +7546,22 @@ function mergeGlobs(existing, newValue) {
7382
7546
 
7383
7547
  // src/hooks/rules-injector/storage.ts
7384
7548
  import {
7385
- existsSync as existsSync17,
7549
+ existsSync as existsSync18,
7386
7550
  mkdirSync as mkdirSync7,
7387
- readFileSync as readFileSync8,
7388
- writeFileSync as writeFileSync6,
7551
+ readFileSync as readFileSync9,
7552
+ writeFileSync as writeFileSync7,
7389
7553
  unlinkSync as unlinkSync6
7390
7554
  } from "fs";
7391
- import { join as join23 } from "path";
7555
+ import { join as join24 } from "path";
7392
7556
  function getStoragePath3(sessionID) {
7393
- return join23(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
7557
+ return join24(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
7394
7558
  }
7395
7559
  function loadInjectedRules(sessionID) {
7396
7560
  const filePath = getStoragePath3(sessionID);
7397
- if (!existsSync17(filePath))
7561
+ if (!existsSync18(filePath))
7398
7562
  return { contentHashes: new Set, realPaths: new Set };
7399
7563
  try {
7400
- const content = readFileSync8(filePath, "utf-8");
7564
+ const content = readFileSync9(filePath, "utf-8");
7401
7565
  const data = JSON.parse(content);
7402
7566
  return {
7403
7567
  contentHashes: new Set(data.injectedHashes),
@@ -7408,7 +7572,7 @@ function loadInjectedRules(sessionID) {
7408
7572
  }
7409
7573
  }
7410
7574
  function saveInjectedRules(sessionID, data) {
7411
- if (!existsSync17(RULES_INJECTOR_STORAGE)) {
7575
+ if (!existsSync18(RULES_INJECTOR_STORAGE)) {
7412
7576
  mkdirSync7(RULES_INJECTOR_STORAGE, { recursive: true });
7413
7577
  }
7414
7578
  const storageData = {
@@ -7417,11 +7581,11 @@ function saveInjectedRules(sessionID, data) {
7417
7581
  injectedRealPaths: [...data.realPaths],
7418
7582
  updatedAt: Date.now()
7419
7583
  };
7420
- writeFileSync6(getStoragePath3(sessionID), JSON.stringify(storageData, null, 2));
7584
+ writeFileSync7(getStoragePath3(sessionID), JSON.stringify(storageData, null, 2));
7421
7585
  }
7422
7586
  function clearInjectedRules(sessionID) {
7423
7587
  const filePath = getStoragePath3(sessionID);
7424
- if (existsSync17(filePath)) {
7588
+ if (existsSync18(filePath)) {
7425
7589
  unlinkSync6(filePath);
7426
7590
  }
7427
7591
  }
@@ -7458,7 +7622,7 @@ function createRulesInjectorHook(ctx) {
7458
7622
  if (isDuplicateByRealPath(candidate.realPath, cache2.realPaths))
7459
7623
  continue;
7460
7624
  try {
7461
- const rawContent = readFileSync9(candidate.path, "utf-8");
7625
+ const rawContent = readFileSync10(candidate.path, "utf-8");
7462
7626
  const { metadata, body } = parseRuleFrontmatter(rawContent);
7463
7627
  const matchResult = shouldApplyRule(metadata, filePath, projectRoot);
7464
7628
  if (!matchResult.applies)
@@ -7823,18 +7987,18 @@ async function showVersionToast(ctx, version) {
7823
7987
  }
7824
7988
  // src/hooks/agent-usage-reminder/storage.ts
7825
7989
  import {
7826
- existsSync as existsSync20,
7990
+ existsSync as existsSync21,
7827
7991
  mkdirSync as mkdirSync8,
7828
- readFileSync as readFileSync12,
7829
- writeFileSync as writeFileSync8,
7992
+ readFileSync as readFileSync13,
7993
+ writeFileSync as writeFileSync9,
7830
7994
  unlinkSync as unlinkSync7
7831
7995
  } from "fs";
7832
- import { join as join28 } from "path";
7996
+ import { join as join29 } from "path";
7833
7997
 
7834
7998
  // src/hooks/agent-usage-reminder/constants.ts
7835
- import { join as join27 } from "path";
7836
- var OPENCODE_STORAGE6 = join27(xdgData2 ?? "", "opencode", "storage");
7837
- var AGENT_USAGE_REMINDER_STORAGE = join27(OPENCODE_STORAGE6, "agent-usage-reminder");
7999
+ import { join as join28 } from "path";
8000
+ var OPENCODE_STORAGE7 = join28(xdgData2 ?? "", "opencode", "storage");
8001
+ var AGENT_USAGE_REMINDER_STORAGE = join28(OPENCODE_STORAGE7, "agent-usage-reminder");
7838
8002
  var TARGET_TOOLS = new Set([
7839
8003
  "grep",
7840
8004
  "safe_grep",
@@ -7879,29 +8043,29 @@ ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
7879
8043
 
7880
8044
  // src/hooks/agent-usage-reminder/storage.ts
7881
8045
  function getStoragePath4(sessionID) {
7882
- return join28(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
8046
+ return join29(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
7883
8047
  }
7884
8048
  function loadAgentUsageState(sessionID) {
7885
8049
  const filePath = getStoragePath4(sessionID);
7886
- if (!existsSync20(filePath))
8050
+ if (!existsSync21(filePath))
7887
8051
  return null;
7888
8052
  try {
7889
- const content = readFileSync12(filePath, "utf-8");
8053
+ const content = readFileSync13(filePath, "utf-8");
7890
8054
  return JSON.parse(content);
7891
8055
  } catch {
7892
8056
  return null;
7893
8057
  }
7894
8058
  }
7895
8059
  function saveAgentUsageState(state) {
7896
- if (!existsSync20(AGENT_USAGE_REMINDER_STORAGE)) {
8060
+ if (!existsSync21(AGENT_USAGE_REMINDER_STORAGE)) {
7897
8061
  mkdirSync8(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
7898
8062
  }
7899
8063
  const filePath = getStoragePath4(state.sessionID);
7900
- writeFileSync8(filePath, JSON.stringify(state, null, 2));
8064
+ writeFileSync9(filePath, JSON.stringify(state, null, 2));
7901
8065
  }
7902
8066
  function clearAgentUsageState(sessionID) {
7903
8067
  const filePath = getStoragePath4(sessionID);
7904
- if (existsSync20(filePath)) {
8068
+ if (existsSync21(filePath)) {
7905
8069
  unlinkSync7(filePath);
7906
8070
  }
7907
8071
  }
@@ -8065,6 +8229,7 @@ function createKeywordDetectorHook() {
8065
8229
  const message = output.message;
8066
8230
  const context = messages.join(`
8067
8231
  `);
8232
+ log(`[keyword-detector] Injecting context for ${messages.length} keywords`, { sessionID: input.sessionID, contextLength: context.length });
8068
8233
  const success = injectHookMessage(input.sessionID, context, {
8069
8234
  agent: message.agent,
8070
8235
  model: message.model,
@@ -8122,18 +8287,18 @@ function createNonInteractiveEnvHook(_ctx) {
8122
8287
  }
8123
8288
  // src/hooks/interactive-bash-session/storage.ts
8124
8289
  import {
8125
- existsSync as existsSync21,
8290
+ existsSync as existsSync22,
8126
8291
  mkdirSync as mkdirSync9,
8127
- readFileSync as readFileSync13,
8128
- writeFileSync as writeFileSync9,
8292
+ readFileSync as readFileSync14,
8293
+ writeFileSync as writeFileSync10,
8129
8294
  unlinkSync as unlinkSync8
8130
8295
  } from "fs";
8131
- import { join as join30 } from "path";
8296
+ import { join as join31 } from "path";
8132
8297
 
8133
8298
  // src/hooks/interactive-bash-session/constants.ts
8134
- import { join as join29 } from "path";
8135
- var OPENCODE_STORAGE7 = join29(xdgData2 ?? "", "opencode", "storage");
8136
- var INTERACTIVE_BASH_SESSION_STORAGE = join29(OPENCODE_STORAGE7, "interactive-bash-session");
8299
+ import { join as join30 } from "path";
8300
+ var OPENCODE_STORAGE8 = join30(xdgData2 ?? "", "opencode", "storage");
8301
+ var INTERACTIVE_BASH_SESSION_STORAGE = join30(OPENCODE_STORAGE8, "interactive-bash-session");
8137
8302
  var OMO_SESSION_PREFIX = "omo-";
8138
8303
  function buildSessionReminderMessage(sessions) {
8139
8304
  if (sessions.length === 0)
@@ -8145,14 +8310,14 @@ function buildSessionReminderMessage(sessions) {
8145
8310
 
8146
8311
  // src/hooks/interactive-bash-session/storage.ts
8147
8312
  function getStoragePath5(sessionID) {
8148
- return join30(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
8313
+ return join31(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
8149
8314
  }
8150
8315
  function loadInteractiveBashSessionState(sessionID) {
8151
8316
  const filePath = getStoragePath5(sessionID);
8152
- if (!existsSync21(filePath))
8317
+ if (!existsSync22(filePath))
8153
8318
  return null;
8154
8319
  try {
8155
- const content = readFileSync13(filePath, "utf-8");
8320
+ const content = readFileSync14(filePath, "utf-8");
8156
8321
  const serialized = JSON.parse(content);
8157
8322
  return {
8158
8323
  sessionID: serialized.sessionID,
@@ -8164,7 +8329,7 @@ function loadInteractiveBashSessionState(sessionID) {
8164
8329
  }
8165
8330
  }
8166
8331
  function saveInteractiveBashSessionState(state) {
8167
- if (!existsSync21(INTERACTIVE_BASH_SESSION_STORAGE)) {
8332
+ if (!existsSync22(INTERACTIVE_BASH_SESSION_STORAGE)) {
8168
8333
  mkdirSync9(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
8169
8334
  }
8170
8335
  const filePath = getStoragePath5(state.sessionID);
@@ -8173,11 +8338,11 @@ function saveInteractiveBashSessionState(state) {
8173
8338
  tmuxSessions: Array.from(state.tmuxSessions),
8174
8339
  updatedAt: state.updatedAt
8175
8340
  };
8176
- writeFileSync9(filePath, JSON.stringify(serialized, null, 2));
8341
+ writeFileSync10(filePath, JSON.stringify(serialized, null, 2));
8177
8342
  }
8178
8343
  function clearInteractiveBashSessionState(sessionID) {
8179
8344
  const filePath = getStoragePath5(sessionID);
8180
- if (existsSync21(filePath)) {
8345
+ if (existsSync22(filePath)) {
8181
8346
  unlinkSync8(filePath);
8182
8347
  }
8183
8348
  }
@@ -8354,6 +8519,73 @@ function createInteractiveBashSessionHook(_ctx) {
8354
8519
  event: eventHandler
8355
8520
  };
8356
8521
  }
8522
+ // src/hooks/empty-message-sanitizer/index.ts
8523
+ var PLACEHOLDER_TEXT2 = "[user interrupted]";
8524
+ function hasTextContent(part) {
8525
+ if (part.type === "text") {
8526
+ const text = part.text;
8527
+ return Boolean(text && text.trim().length > 0);
8528
+ }
8529
+ return false;
8530
+ }
8531
+ function isToolPart(part) {
8532
+ const type = part.type;
8533
+ return type === "tool" || type === "tool_use" || type === "tool_result";
8534
+ }
8535
+ function hasValidContent(parts) {
8536
+ return parts.some((part) => hasTextContent(part) || isToolPart(part));
8537
+ }
8538
+ function createEmptyMessageSanitizerHook() {
8539
+ return {
8540
+ "experimental.chat.messages.transform": async (_input, output) => {
8541
+ const { messages } = output;
8542
+ for (const message of messages) {
8543
+ if (message.info.role === "user")
8544
+ continue;
8545
+ const parts = message.parts;
8546
+ if (!hasValidContent(parts)) {
8547
+ let injected = false;
8548
+ for (const part of parts) {
8549
+ if (part.type === "text") {
8550
+ const textPart = part;
8551
+ if (!textPart.text || !textPart.text.trim()) {
8552
+ textPart.text = PLACEHOLDER_TEXT2;
8553
+ textPart.synthetic = true;
8554
+ injected = true;
8555
+ break;
8556
+ }
8557
+ }
8558
+ }
8559
+ if (!injected) {
8560
+ const insertIndex = parts.findIndex((p) => isToolPart(p));
8561
+ const newPart = {
8562
+ id: `synthetic_${Date.now()}`,
8563
+ messageID: message.info.id,
8564
+ sessionID: message.info.sessionID ?? "",
8565
+ type: "text",
8566
+ text: PLACEHOLDER_TEXT2,
8567
+ synthetic: true
8568
+ };
8569
+ if (insertIndex === -1) {
8570
+ parts.push(newPart);
8571
+ } else {
8572
+ parts.splice(insertIndex, 0, newPart);
8573
+ }
8574
+ }
8575
+ }
8576
+ for (const part of parts) {
8577
+ if (part.type === "text") {
8578
+ const textPart = part;
8579
+ if (textPart.text !== undefined && textPart.text.trim() === "") {
8580
+ textPart.text = PLACEHOLDER_TEXT2;
8581
+ textPart.synthetic = true;
8582
+ }
8583
+ }
8584
+ }
8585
+ }
8586
+ }
8587
+ };
8588
+ }
8357
8589
  // src/auth/antigravity/constants.ts
8358
8590
  var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
8359
8591
  var ANTIGRAVITY_CLIENT_SECRET = "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";
@@ -8661,15 +8893,19 @@ function formatTokenForStorage(refreshToken, projectId, managedProjectId) {
8661
8893
  }
8662
8894
  // src/auth/antigravity/project.ts
8663
8895
  var projectContextCache = new Map;
8896
+ function debugLog4(message) {
8897
+ if (process.env.ANTIGRAVITY_DEBUG === "1") {
8898
+ console.log(`[antigravity-project] ${message}`);
8899
+ }
8900
+ }
8664
8901
  var CODE_ASSIST_METADATA = {
8665
8902
  ideType: "IDE_UNSPECIFIED",
8666
8903
  platform: "PLATFORM_UNSPECIFIED",
8667
8904
  pluginType: "GEMINI"
8668
8905
  };
8669
8906
  function extractProjectId(project) {
8670
- if (!project) {
8907
+ if (!project)
8671
8908
  return;
8672
- }
8673
8909
  if (typeof project === "string") {
8674
8910
  const trimmed = project.trim();
8675
8911
  return trimmed || undefined;
@@ -8683,10 +8919,31 @@ function extractProjectId(project) {
8683
8919
  }
8684
8920
  return;
8685
8921
  }
8686
- async function callLoadCodeAssistAPI(accessToken) {
8687
- const requestBody = {
8688
- metadata: CODE_ASSIST_METADATA
8689
- };
8922
+ function getDefaultTierId(allowedTiers) {
8923
+ if (!allowedTiers || allowedTiers.length === 0)
8924
+ return;
8925
+ for (const tier of allowedTiers) {
8926
+ if (tier?.isDefault)
8927
+ return tier.id;
8928
+ }
8929
+ return allowedTiers[0]?.id;
8930
+ }
8931
+ function isFreeTier(tierId) {
8932
+ if (!tierId)
8933
+ return false;
8934
+ const lower = tierId.toLowerCase();
8935
+ return lower === "free" || lower === "free-tier" || lower.startsWith("free");
8936
+ }
8937
+ function wait(ms) {
8938
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
8939
+ }
8940
+ async function callLoadCodeAssistAPI(accessToken, projectId) {
8941
+ const metadata = { ...CODE_ASSIST_METADATA };
8942
+ if (projectId)
8943
+ metadata.duetProject = projectId;
8944
+ const requestBody = { metadata };
8945
+ if (projectId)
8946
+ requestBody.cloudaicompanionProject = projectId;
8690
8947
  const headers = {
8691
8948
  Authorization: `Bearer ${accessToken}`,
8692
8949
  "Content-Type": "application/json",
@@ -8696,6 +8953,7 @@ async function callLoadCodeAssistAPI(accessToken) {
8696
8953
  };
8697
8954
  for (const baseEndpoint of ANTIGRAVITY_ENDPOINT_FALLBACKS) {
8698
8955
  const url = `${baseEndpoint}/${ANTIGRAVITY_API_VERSION}:loadCodeAssist`;
8956
+ debugLog4(`[loadCodeAssist] Trying: ${url}`);
8699
8957
  try {
8700
8958
  const response = await fetch(url, {
8701
8959
  method: "POST",
@@ -8703,30 +8961,130 @@ async function callLoadCodeAssistAPI(accessToken) {
8703
8961
  body: JSON.stringify(requestBody)
8704
8962
  });
8705
8963
  if (!response.ok) {
8964
+ debugLog4(`[loadCodeAssist] Failed: ${response.status} ${response.statusText}`);
8706
8965
  continue;
8707
8966
  }
8708
8967
  const data = await response.json();
8968
+ debugLog4(`[loadCodeAssist] Success: ${JSON.stringify(data)}`);
8709
8969
  return data;
8710
- } catch {
8970
+ } catch (err) {
8971
+ debugLog4(`[loadCodeAssist] Error: ${err}`);
8711
8972
  continue;
8712
8973
  }
8713
8974
  }
8975
+ debugLog4(`[loadCodeAssist] All endpoints failed`);
8714
8976
  return null;
8715
8977
  }
8978
+ async function onboardManagedProject(accessToken, tierId, projectId, attempts = 10, delayMs = 5000) {
8979
+ debugLog4(`[onboardUser] Starting with tierId=${tierId}, projectId=${projectId || "none"}`);
8980
+ const metadata = { ...CODE_ASSIST_METADATA };
8981
+ if (projectId)
8982
+ metadata.duetProject = projectId;
8983
+ const requestBody = { tierId, metadata };
8984
+ if (!isFreeTier(tierId)) {
8985
+ if (!projectId) {
8986
+ debugLog4(`[onboardUser] Non-FREE tier requires projectId, returning undefined`);
8987
+ return;
8988
+ }
8989
+ requestBody.cloudaicompanionProject = projectId;
8990
+ }
8991
+ const headers = {
8992
+ Authorization: `Bearer ${accessToken}`,
8993
+ "Content-Type": "application/json",
8994
+ "User-Agent": ANTIGRAVITY_HEADERS["User-Agent"],
8995
+ "X-Goog-Api-Client": ANTIGRAVITY_HEADERS["X-Goog-Api-Client"],
8996
+ "Client-Metadata": ANTIGRAVITY_HEADERS["Client-Metadata"]
8997
+ };
8998
+ debugLog4(`[onboardUser] Request body: ${JSON.stringify(requestBody)}`);
8999
+ for (let attempt = 0;attempt < attempts; attempt++) {
9000
+ debugLog4(`[onboardUser] Attempt ${attempt + 1}/${attempts}`);
9001
+ for (const baseEndpoint of ANTIGRAVITY_ENDPOINT_FALLBACKS) {
9002
+ const url = `${baseEndpoint}/${ANTIGRAVITY_API_VERSION}:onboardUser`;
9003
+ debugLog4(`[onboardUser] Trying: ${url}`);
9004
+ try {
9005
+ const response = await fetch(url, {
9006
+ method: "POST",
9007
+ headers,
9008
+ body: JSON.stringify(requestBody)
9009
+ });
9010
+ if (!response.ok) {
9011
+ const errorText = await response.text().catch(() => "");
9012
+ debugLog4(`[onboardUser] Failed: ${response.status} ${response.statusText} - ${errorText}`);
9013
+ continue;
9014
+ }
9015
+ const payload = await response.json();
9016
+ debugLog4(`[onboardUser] Response: ${JSON.stringify(payload)}`);
9017
+ const managedProjectId = payload.response?.cloudaicompanionProject?.id;
9018
+ if (payload.done && managedProjectId) {
9019
+ debugLog4(`[onboardUser] Success! Got managed project ID: ${managedProjectId}`);
9020
+ return managedProjectId;
9021
+ }
9022
+ if (payload.done && projectId) {
9023
+ debugLog4(`[onboardUser] Done but no managed ID, using original: ${projectId}`);
9024
+ return projectId;
9025
+ }
9026
+ debugLog4(`[onboardUser] Not done yet, payload.done=${payload.done}`);
9027
+ } catch (err) {
9028
+ debugLog4(`[onboardUser] Error: ${err}`);
9029
+ continue;
9030
+ }
9031
+ }
9032
+ if (attempt < attempts - 1) {
9033
+ debugLog4(`[onboardUser] Waiting ${delayMs}ms before next attempt...`);
9034
+ await wait(delayMs);
9035
+ }
9036
+ }
9037
+ debugLog4(`[onboardUser] All attempts exhausted, returning undefined`);
9038
+ return;
9039
+ }
8716
9040
  async function fetchProjectContext(accessToken) {
9041
+ debugLog4(`[fetchProjectContext] Starting...`);
8717
9042
  const cached = projectContextCache.get(accessToken);
8718
9043
  if (cached) {
9044
+ debugLog4(`[fetchProjectContext] Returning cached result: ${JSON.stringify(cached)}`);
8719
9045
  return cached;
8720
9046
  }
8721
- const response = await callLoadCodeAssistAPI(accessToken);
8722
- const projectId = response ? extractProjectId(response.cloudaicompanionProject) : undefined;
8723
- const result = {
8724
- cloudaicompanionProject: projectId || ""
8725
- };
8726
- if (projectId) {
9047
+ const loadPayload = await callLoadCodeAssistAPI(accessToken);
9048
+ if (loadPayload?.cloudaicompanionProject) {
9049
+ const projectId = extractProjectId(loadPayload.cloudaicompanionProject);
9050
+ debugLog4(`[fetchProjectContext] loadCodeAssist returned project: ${projectId}`);
9051
+ if (projectId) {
9052
+ const result = { cloudaicompanionProject: projectId };
9053
+ projectContextCache.set(accessToken, result);
9054
+ debugLog4(`[fetchProjectContext] Using loadCodeAssist project ID: ${projectId}`);
9055
+ return result;
9056
+ }
9057
+ }
9058
+ if (!loadPayload) {
9059
+ debugLog4(`[fetchProjectContext] loadCodeAssist returned null, returning empty`);
9060
+ return { cloudaicompanionProject: "" };
9061
+ }
9062
+ const currentTierId = loadPayload.currentTier?.id;
9063
+ debugLog4(`[fetchProjectContext] currentTier: ${currentTierId}, allowedTiers: ${JSON.stringify(loadPayload.allowedTiers)}`);
9064
+ if (currentTierId && !isFreeTier(currentTierId)) {
9065
+ debugLog4(`[fetchProjectContext] PAID tier detected, returning empty (user must provide project)`);
9066
+ return { cloudaicompanionProject: "" };
9067
+ }
9068
+ const defaultTierId = getDefaultTierId(loadPayload.allowedTiers);
9069
+ const tierId = defaultTierId ?? "free-tier";
9070
+ debugLog4(`[fetchProjectContext] Resolved tierId: ${tierId}`);
9071
+ if (!isFreeTier(tierId)) {
9072
+ debugLog4(`[fetchProjectContext] Non-FREE tier without project, returning empty`);
9073
+ return { cloudaicompanionProject: "" };
9074
+ }
9075
+ debugLog4(`[fetchProjectContext] FREE tier detected (${tierId}), calling onboardUser...`);
9076
+ const managedProjectId = await onboardManagedProject(accessToken, tierId);
9077
+ if (managedProjectId) {
9078
+ const result = {
9079
+ cloudaicompanionProject: managedProjectId,
9080
+ managedProjectId
9081
+ };
8727
9082
  projectContextCache.set(accessToken, result);
9083
+ debugLog4(`[fetchProjectContext] Got managed project ID: ${managedProjectId}`);
9084
+ return result;
8728
9085
  }
8729
- return result;
9086
+ debugLog4(`[fetchProjectContext] Failed to get managed project ID, returning empty`);
9087
+ return { cloudaicompanionProject: "" };
8730
9088
  }
8731
9089
  function clearProjectContextCache(accessToken) {
8732
9090
  if (accessToken) {
@@ -8790,21 +9148,21 @@ function wrapRequestBody(body, projectId, modelName, sessionId) {
8790
9148
  }
8791
9149
  };
8792
9150
  }
8793
- function debugLog4(message) {
9151
+ function debugLog5(message) {
8794
9152
  if (process.env.ANTIGRAVITY_DEBUG === "1") {
8795
9153
  console.log(`[antigravity-request] ${message}`);
8796
9154
  }
8797
9155
  }
8798
9156
  function injectThoughtSignatureIntoFunctionCalls(body, signature) {
8799
9157
  const effectiveSignature = signature || SKIP_THOUGHT_SIGNATURE_VALIDATOR;
8800
- debugLog4(`[TSIG][INJECT] signature=${effectiveSignature.substring(0, 30)}... (${signature ? "provided" : "default"})`);
8801
- debugLog4(`[TSIG][INJECT] body keys: ${Object.keys(body).join(", ")}`);
9158
+ debugLog5(`[TSIG][INJECT] signature=${effectiveSignature.substring(0, 30)}... (${signature ? "provided" : "default"})`);
9159
+ debugLog5(`[TSIG][INJECT] body keys: ${Object.keys(body).join(", ")}`);
8802
9160
  const contents = body.contents;
8803
9161
  if (!contents || !Array.isArray(contents)) {
8804
- debugLog4(`[TSIG][INJECT] No contents array! Has messages: ${!!body.messages}`);
9162
+ debugLog5(`[TSIG][INJECT] No contents array! Has messages: ${!!body.messages}`);
8805
9163
  return body;
8806
9164
  }
8807
- debugLog4(`[TSIG][INJECT] Found ${contents.length} content blocks`);
9165
+ debugLog5(`[TSIG][INJECT] Found ${contents.length} content blocks`);
8808
9166
  let injectedCount = 0;
8809
9167
  const modifiedContents = contents.map((content) => {
8810
9168
  if (!content.parts || !Array.isArray(content.parts)) {
@@ -8822,7 +9180,7 @@ function injectThoughtSignatureIntoFunctionCalls(body, signature) {
8822
9180
  });
8823
9181
  return { ...content, parts: modifiedParts };
8824
9182
  });
8825
- debugLog4(`[TSIG][INJECT] injected signature into ${injectedCount} functionCall(s)`);
9183
+ debugLog5(`[TSIG][INJECT] injected signature into ${injectedCount} functionCall(s)`);
8826
9184
  return { ...body, contents: modifiedContents };
8827
9185
  }
8828
9186
  function isStreamingRequest(url, body) {
@@ -9326,13 +9684,13 @@ function getOrCreateSessionId(fetchInstanceId, sessionId) {
9326
9684
  return newSessionId;
9327
9685
  }
9328
9686
  // src/auth/antigravity/message-converter.ts
9329
- function debugLog5(message) {
9687
+ function debugLog6(message) {
9330
9688
  if (process.env.ANTIGRAVITY_DEBUG === "1") {
9331
9689
  console.log(`[antigravity-converter] ${message}`);
9332
9690
  }
9333
9691
  }
9334
9692
  function convertOpenAIToGemini(messages, thoughtSignature) {
9335
- debugLog5(`Converting ${messages.length} messages, signature: ${thoughtSignature ? "present" : "none"}`);
9693
+ debugLog6(`Converting ${messages.length} messages, signature: ${thoughtSignature ? "present" : "none"}`);
9336
9694
  const contents = [];
9337
9695
  for (const msg of messages) {
9338
9696
  if (msg.role === "system") {
@@ -9367,7 +9725,7 @@ function convertOpenAIToGemini(messages, thoughtSignature) {
9367
9725
  }
9368
9726
  };
9369
9727
  part.thoughtSignature = thoughtSignature || SKIP_THOUGHT_SIGNATURE_VALIDATOR;
9370
- debugLog5(`Injected signature into functionCall: ${toolCall.function.name} (${thoughtSignature ? "provided" : "default"})`);
9728
+ debugLog6(`Injected signature into functionCall: ${toolCall.function.name} (${thoughtSignature ? "provided" : "default"})`);
9371
9729
  parts.push(part);
9372
9730
  }
9373
9731
  }
@@ -9396,7 +9754,7 @@ function convertOpenAIToGemini(messages, thoughtSignature) {
9396
9754
  continue;
9397
9755
  }
9398
9756
  }
9399
- debugLog5(`Converted to ${contents.length} content blocks`);
9757
+ debugLog6(`Converted to ${contents.length} content blocks`);
9400
9758
  return contents;
9401
9759
  }
9402
9760
  function convertContentToParts(content) {
@@ -9432,7 +9790,7 @@ function hasOpenAIMessages(body) {
9432
9790
  }
9433
9791
  function convertRequestBody(body, thoughtSignature) {
9434
9792
  if (!hasOpenAIMessages(body)) {
9435
- debugLog5("No messages array found, returning body as-is");
9793
+ debugLog6("No messages array found, returning body as-is");
9436
9794
  return body;
9437
9795
  }
9438
9796
  const messages = body.messages;
@@ -9440,11 +9798,11 @@ function convertRequestBody(body, thoughtSignature) {
9440
9798
  const converted = { ...body };
9441
9799
  delete converted.messages;
9442
9800
  converted.contents = contents;
9443
- debugLog5(`Converted body: messages \u2192 contents (${contents.length} blocks)`);
9801
+ debugLog6(`Converted body: messages \u2192 contents (${contents.length} blocks)`);
9444
9802
  return converted;
9445
9803
  }
9446
9804
  // src/auth/antigravity/fetch.ts
9447
- function debugLog6(message) {
9805
+ function debugLog7(message) {
9448
9806
  if (process.env.ANTIGRAVITY_DEBUG === "1") {
9449
9807
  console.log(`[antigravity-fetch] ${message}`);
9450
9808
  }
@@ -9467,7 +9825,7 @@ var GCP_PERMISSION_ERROR_PATTERNS = [
9467
9825
  function isGcpPermissionError(text) {
9468
9826
  return GCP_PERMISSION_ERROR_PATTERNS.some((pattern) => text.includes(pattern));
9469
9827
  }
9470
- function calculateRetryDelay2(attempt) {
9828
+ function calculateRetryDelay(attempt) {
9471
9829
  return Math.min(200 * Math.pow(2, attempt), 2000);
9472
9830
  }
9473
9831
  async function isRetryableResponse(response) {
@@ -9477,7 +9835,7 @@ async function isRetryableResponse(response) {
9477
9835
  try {
9478
9836
  const text = await response.clone().text();
9479
9837
  if (text.includes("SUBSCRIPTION_REQUIRED") || text.includes("Gemini Code Assist license")) {
9480
- debugLog6(`[RETRY] 403 SUBSCRIPTION_REQUIRED detected, will retry with next endpoint`);
9838
+ debugLog7(`[RETRY] 403 SUBSCRIPTION_REQUIRED detected, will retry with next endpoint`);
9481
9839
  return true;
9482
9840
  }
9483
9841
  } catch {}
@@ -9486,11 +9844,11 @@ async function isRetryableResponse(response) {
9486
9844
  }
9487
9845
  async function attemptFetch(options) {
9488
9846
  const { endpoint, url, init, accessToken, projectId, sessionId, modelName, thoughtSignature } = options;
9489
- debugLog6(`Trying endpoint: ${endpoint}`);
9847
+ debugLog7(`Trying endpoint: ${endpoint}`);
9490
9848
  try {
9491
9849
  const rawBody = init.body;
9492
9850
  if (rawBody !== undefined && typeof rawBody !== "string") {
9493
- debugLog6(`Non-string body detected (${typeof rawBody}), signaling pass-through`);
9851
+ debugLog7(`Non-string body detected (${typeof rawBody}), signaling pass-through`);
9494
9852
  return "pass-through";
9495
9853
  }
9496
9854
  let parsedBody = {};
@@ -9501,13 +9859,13 @@ async function attemptFetch(options) {
9501
9859
  parsedBody = {};
9502
9860
  }
9503
9861
  }
9504
- debugLog6(`[BODY] Keys: ${Object.keys(parsedBody).join(", ")}`);
9505
- debugLog6(`[BODY] Has contents: ${!!parsedBody.contents}, Has messages: ${!!parsedBody.messages}`);
9862
+ debugLog7(`[BODY] Keys: ${Object.keys(parsedBody).join(", ")}`);
9863
+ debugLog7(`[BODY] Has contents: ${!!parsedBody.contents}, Has messages: ${!!parsedBody.messages}`);
9506
9864
  if (parsedBody.contents) {
9507
9865
  const contents = parsedBody.contents;
9508
- debugLog6(`[BODY] contents length: ${contents.length}`);
9866
+ debugLog7(`[BODY] contents length: ${contents.length}`);
9509
9867
  contents.forEach((c, i) => {
9510
- debugLog6(`[BODY] contents[${i}].role: ${c.role}, parts: ${JSON.stringify(c.parts).substring(0, 200)}`);
9868
+ debugLog7(`[BODY] contents[${i}].role: ${c.role}, parts: ${JSON.stringify(c.parts).substring(0, 200)}`);
9511
9869
  });
9512
9870
  }
9513
9871
  if (parsedBody.tools && Array.isArray(parsedBody.tools)) {
@@ -9517,9 +9875,9 @@ async function attemptFetch(options) {
9517
9875
  }
9518
9876
  }
9519
9877
  if (hasOpenAIMessages(parsedBody)) {
9520
- debugLog6(`[CONVERT] Converting OpenAI messages to Gemini contents`);
9878
+ debugLog7(`[CONVERT] Converting OpenAI messages to Gemini contents`);
9521
9879
  parsedBody = convertRequestBody(parsedBody, thoughtSignature);
9522
- debugLog6(`[CONVERT] After conversion - Has contents: ${!!parsedBody.contents}`);
9880
+ debugLog7(`[CONVERT] After conversion - Has contents: ${!!parsedBody.contents}`);
9523
9881
  }
9524
9882
  const transformed = transformRequest({
9525
9883
  url,
@@ -9531,7 +9889,7 @@ async function attemptFetch(options) {
9531
9889
  endpointOverride: endpoint,
9532
9890
  thoughtSignature
9533
9891
  });
9534
- debugLog6(`[REQ] streaming=${transformed.streaming}, url=${transformed.url}`);
9892
+ debugLog7(`[REQ] streaming=${transformed.streaming}, url=${transformed.url}`);
9535
9893
  const maxPermissionRetries = 10;
9536
9894
  for (let attempt = 0;attempt <= maxPermissionRetries; attempt++) {
9537
9895
  const response = await fetch(transformed.url, {
@@ -9540,9 +9898,9 @@ async function attemptFetch(options) {
9540
9898
  body: JSON.stringify(transformed.body),
9541
9899
  signal: init.signal
9542
9900
  });
9543
- debugLog6(`[RESP] status=${response.status} content-type=${response.headers.get("content-type") ?? ""} url=${response.url}`);
9901
+ debugLog7(`[RESP] status=${response.status} content-type=${response.headers.get("content-type") ?? ""} url=${response.url}`);
9544
9902
  if (response.status === 401) {
9545
- debugLog6(`[401] Unauthorized response detected, signaling token refresh needed`);
9903
+ debugLog7(`[401] Unauthorized response detected, signaling token refresh needed`);
9546
9904
  return "needs-refresh";
9547
9905
  }
9548
9906
  if (response.status === 403) {
@@ -9550,24 +9908,24 @@ async function attemptFetch(options) {
9550
9908
  const text = await response.clone().text();
9551
9909
  if (isGcpPermissionError(text)) {
9552
9910
  if (attempt < maxPermissionRetries) {
9553
- const delay = calculateRetryDelay2(attempt);
9554
- debugLog6(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
9911
+ const delay = calculateRetryDelay(attempt);
9912
+ debugLog7(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
9555
9913
  await new Promise((resolve5) => setTimeout(resolve5, delay));
9556
9914
  continue;
9557
9915
  }
9558
- debugLog6(`[RETRY] GCP permission error, max retries exceeded`);
9916
+ debugLog7(`[RETRY] GCP permission error, max retries exceeded`);
9559
9917
  }
9560
9918
  } catch {}
9561
9919
  }
9562
9920
  if (!response.ok && await isRetryableResponse(response)) {
9563
- debugLog6(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
9921
+ debugLog7(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
9564
9922
  return null;
9565
9923
  }
9566
9924
  return response;
9567
9925
  }
9568
9926
  return null;
9569
9927
  } catch (error) {
9570
- debugLog6(`Endpoint failed: ${endpoint} (${error instanceof Error ? error.message : "Unknown error"}), trying next`);
9928
+ debugLog7(`Endpoint failed: ${endpoint} (${error instanceof Error ? error.message : "Unknown error"}), trying next`);
9571
9929
  return null;
9572
9930
  }
9573
9931
  }
@@ -9602,17 +9960,17 @@ async function transformResponseWithThinking(response, modelName, fetchInstanceI
9602
9960
  }
9603
9961
  try {
9604
9962
  const text = await result.response.clone().text();
9605
- debugLog6(`[TSIG][RESP] Response text length: ${text.length}`);
9963
+ debugLog7(`[TSIG][RESP] Response text length: ${text.length}`);
9606
9964
  const parsed = JSON.parse(text);
9607
- debugLog6(`[TSIG][RESP] Parsed keys: ${Object.keys(parsed).join(", ")}`);
9608
- debugLog6(`[TSIG][RESP] Has candidates: ${!!parsed.candidates}, count: ${parsed.candidates?.length ?? 0}`);
9965
+ debugLog7(`[TSIG][RESP] Parsed keys: ${Object.keys(parsed).join(", ")}`);
9966
+ debugLog7(`[TSIG][RESP] Has candidates: ${!!parsed.candidates}, count: ${parsed.candidates?.length ?? 0}`);
9609
9967
  const signature = extractSignatureFromResponse(parsed);
9610
- debugLog6(`[TSIG][RESP] Signature extracted: ${signature ? signature.substring(0, 30) + "..." : "NONE"}`);
9968
+ debugLog7(`[TSIG][RESP] Signature extracted: ${signature ? signature.substring(0, 30) + "..." : "NONE"}`);
9611
9969
  if (signature) {
9612
9970
  setThoughtSignature(fetchInstanceId, signature);
9613
- debugLog6(`[TSIG][STORE] Stored signature for ${fetchInstanceId}`);
9971
+ debugLog7(`[TSIG][STORE] Stored signature for ${fetchInstanceId}`);
9614
9972
  } else {
9615
- debugLog6(`[TSIG][WARN] No signature found in response!`);
9973
+ debugLog7(`[TSIG][WARN] No signature found in response!`);
9616
9974
  }
9617
9975
  if (shouldIncludeThinking(modelName)) {
9618
9976
  const thinkingResult = extractThinkingBlocks(parsed);
@@ -9633,7 +9991,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9633
9991
  let cachedProjectId = null;
9634
9992
  const fetchInstanceId = crypto.randomUUID();
9635
9993
  return async (url, init = {}) => {
9636
- debugLog6(`Intercepting request to: ${url}`);
9994
+ debugLog7(`Intercepting request to: ${url}`);
9637
9995
  const auth = await getAuth();
9638
9996
  if (!auth.access || !auth.refresh) {
9639
9997
  throw new Error("Antigravity: No authentication tokens available");
@@ -9652,7 +10010,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9652
10010
  cachedTokens.refresh_token = refreshParts.refreshToken;
9653
10011
  }
9654
10012
  if (isTokenExpired(cachedTokens)) {
9655
- debugLog6("Token expired, refreshing...");
10013
+ debugLog7("Token expired, refreshing...");
9656
10014
  try {
9657
10015
  const newTokens = await refreshAccessToken(refreshParts.refreshToken, clientId, clientSecret);
9658
10016
  cachedTokens = {
@@ -9669,7 +10027,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9669
10027
  refresh: formattedRefresh,
9670
10028
  expires: Date.now() + newTokens.expires_in * 1000
9671
10029
  });
9672
- debugLog6("Token refreshed successfully");
10030
+ debugLog7("Token refreshed successfully");
9673
10031
  } catch (error) {
9674
10032
  throw new Error(`Antigravity: Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`);
9675
10033
  }
@@ -9677,10 +10035,10 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9677
10035
  if (!cachedProjectId) {
9678
10036
  const projectContext = await fetchProjectContext(cachedTokens.access_token);
9679
10037
  cachedProjectId = projectContext.cloudaicompanionProject || "";
9680
- debugLog6(`[PROJECT] Fetched project ID: "${cachedProjectId}"`);
10038
+ debugLog7(`[PROJECT] Fetched project ID: "${cachedProjectId}"`);
9681
10039
  }
9682
10040
  const projectId = cachedProjectId;
9683
- debugLog6(`[PROJECT] Using project ID: "${projectId}"`);
10041
+ debugLog7(`[PROJECT] Using project ID: "${projectId}"`);
9684
10042
  let modelName;
9685
10043
  if (init.body) {
9686
10044
  try {
@@ -9693,7 +10051,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9693
10051
  const maxEndpoints = Math.min(ANTIGRAVITY_ENDPOINT_FALLBACKS.length, 3);
9694
10052
  const sessionId = getOrCreateSessionId(fetchInstanceId);
9695
10053
  const thoughtSignature = getThoughtSignature(fetchInstanceId);
9696
- debugLog6(`[TSIG][GET] sessionId=${sessionId}, signature=${thoughtSignature ? thoughtSignature.substring(0, 20) + "..." : "none"}`);
10054
+ debugLog7(`[TSIG][GET] sessionId=${sessionId}, signature=${thoughtSignature ? thoughtSignature.substring(0, 20) + "..." : "none"}`);
9697
10055
  let hasRefreshedFor401 = false;
9698
10056
  const executeWithEndpoints = async () => {
9699
10057
  for (let i = 0;i < maxEndpoints; i++) {
@@ -9709,7 +10067,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9709
10067
  thoughtSignature
9710
10068
  });
9711
10069
  if (response === "pass-through") {
9712
- debugLog6("Non-string body detected, passing through with auth headers");
10070
+ debugLog7("Non-string body detected, passing through with auth headers");
9713
10071
  const headersWithAuth = {
9714
10072
  ...init.headers,
9715
10073
  Authorization: `Bearer ${cachedTokens.access_token}`
@@ -9718,7 +10076,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9718
10076
  }
9719
10077
  if (response === "needs-refresh") {
9720
10078
  if (hasRefreshedFor401) {
9721
- debugLog6("[401] Already refreshed once, returning unauthorized error");
10079
+ debugLog7("[401] Already refreshed once, returning unauthorized error");
9722
10080
  return new Response(JSON.stringify({
9723
10081
  error: {
9724
10082
  message: "Authentication failed after token refresh",
@@ -9731,7 +10089,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9731
10089
  headers: { "Content-Type": "application/json" }
9732
10090
  });
9733
10091
  }
9734
- debugLog6("[401] Refreshing token and retrying...");
10092
+ debugLog7("[401] Refreshing token and retrying...");
9735
10093
  hasRefreshedFor401 = true;
9736
10094
  try {
9737
10095
  const newTokens = await refreshAccessToken(refreshParts.refreshToken, clientId, clientSecret);
@@ -9749,10 +10107,10 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9749
10107
  refresh: formattedRefresh,
9750
10108
  expires: Date.now() + newTokens.expires_in * 1000
9751
10109
  });
9752
- debugLog6("[401] Token refreshed, retrying request...");
10110
+ debugLog7("[401] Token refreshed, retrying request...");
9753
10111
  return executeWithEndpoints();
9754
10112
  } catch (refreshError) {
9755
- debugLog6(`[401] Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`);
10113
+ debugLog7(`[401] Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`);
9756
10114
  return new Response(JSON.stringify({
9757
10115
  error: {
9758
10116
  message: `Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`,
@@ -9767,13 +10125,13 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9767
10125
  }
9768
10126
  }
9769
10127
  if (response) {
9770
- debugLog6(`Success with endpoint: ${endpoint}`);
10128
+ debugLog7(`Success with endpoint: ${endpoint}`);
9771
10129
  const transformedResponse = await transformResponseWithThinking(response, modelName || "", fetchInstanceId);
9772
10130
  return transformedResponse;
9773
10131
  }
9774
10132
  }
9775
10133
  const errorMessage = `All Antigravity endpoints failed after ${maxEndpoints} attempts`;
9776
- debugLog6(errorMessage);
10134
+ debugLog7(errorMessage);
9777
10135
  return new Response(JSON.stringify({
9778
10136
  error: {
9779
10137
  message: errorMessage,
@@ -9918,22 +10276,22 @@ async function createGoogleAntigravityAuthPlugin({
9918
10276
  };
9919
10277
  }
9920
10278
  // src/features/claude-code-command-loader/loader.ts
9921
- import { existsSync as existsSync22, readdirSync as readdirSync5, readFileSync as readFileSync14 } from "fs";
10279
+ import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
9922
10280
  import { homedir as homedir10 } from "os";
9923
- import { join as join31, basename } from "path";
10281
+ import { join as join32, basename } from "path";
9924
10282
  function loadCommandsFromDir(commandsDir, scope) {
9925
- if (!existsSync22(commandsDir)) {
10283
+ if (!existsSync23(commandsDir)) {
9926
10284
  return [];
9927
10285
  }
9928
- const entries = readdirSync5(commandsDir, { withFileTypes: true });
10286
+ const entries = readdirSync6(commandsDir, { withFileTypes: true });
9929
10287
  const commands = [];
9930
10288
  for (const entry of entries) {
9931
10289
  if (!isMarkdownFile(entry))
9932
10290
  continue;
9933
- const commandPath = join31(commandsDir, entry.name);
10291
+ const commandPath = join32(commandsDir, entry.name);
9934
10292
  const commandName = basename(entry.name, ".md");
9935
10293
  try {
9936
- const content = readFileSync14(commandPath, "utf-8");
10294
+ const content = readFileSync15(commandPath, "utf-8");
9937
10295
  const { data, body } = parseFrontmatter(content);
9938
10296
  const wrappedTemplate = `<command-instruction>
9939
10297
  ${body.trim()}
@@ -9973,47 +10331,47 @@ function commandsToRecord(commands) {
9973
10331
  return result;
9974
10332
  }
9975
10333
  function loadUserCommands() {
9976
- const userCommandsDir = join31(homedir10(), ".claude", "commands");
10334
+ const userCommandsDir = join32(homedir10(), ".claude", "commands");
9977
10335
  const commands = loadCommandsFromDir(userCommandsDir, "user");
9978
10336
  return commandsToRecord(commands);
9979
10337
  }
9980
10338
  function loadProjectCommands() {
9981
- const projectCommandsDir = join31(process.cwd(), ".claude", "commands");
10339
+ const projectCommandsDir = join32(process.cwd(), ".claude", "commands");
9982
10340
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
9983
10341
  return commandsToRecord(commands);
9984
10342
  }
9985
10343
  function loadOpencodeGlobalCommands() {
9986
- const opencodeCommandsDir = join31(homedir10(), ".config", "opencode", "command");
10344
+ const opencodeCommandsDir = join32(homedir10(), ".config", "opencode", "command");
9987
10345
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
9988
10346
  return commandsToRecord(commands);
9989
10347
  }
9990
10348
  function loadOpencodeProjectCommands() {
9991
- const opencodeProjectDir = join31(process.cwd(), ".opencode", "command");
10349
+ const opencodeProjectDir = join32(process.cwd(), ".opencode", "command");
9992
10350
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
9993
10351
  return commandsToRecord(commands);
9994
10352
  }
9995
10353
  // src/features/claude-code-skill-loader/loader.ts
9996
- import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
10354
+ import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
9997
10355
  import { homedir as homedir11 } from "os";
9998
- import { join as join32 } from "path";
10356
+ import { join as join33 } from "path";
9999
10357
  function loadSkillsFromDir(skillsDir, scope) {
10000
- if (!existsSync23(skillsDir)) {
10358
+ if (!existsSync24(skillsDir)) {
10001
10359
  return [];
10002
10360
  }
10003
- const entries = readdirSync6(skillsDir, { withFileTypes: true });
10361
+ const entries = readdirSync7(skillsDir, { withFileTypes: true });
10004
10362
  const skills = [];
10005
10363
  for (const entry of entries) {
10006
10364
  if (entry.name.startsWith("."))
10007
10365
  continue;
10008
- const skillPath = join32(skillsDir, entry.name);
10366
+ const skillPath = join33(skillsDir, entry.name);
10009
10367
  if (!entry.isDirectory() && !entry.isSymbolicLink())
10010
10368
  continue;
10011
10369
  const resolvedPath = resolveSymlink(skillPath);
10012
- const skillMdPath = join32(resolvedPath, "SKILL.md");
10013
- if (!existsSync23(skillMdPath))
10370
+ const skillMdPath = join33(resolvedPath, "SKILL.md");
10371
+ if (!existsSync24(skillMdPath))
10014
10372
  continue;
10015
10373
  try {
10016
- const content = readFileSync15(skillMdPath, "utf-8");
10374
+ const content = readFileSync16(skillMdPath, "utf-8");
10017
10375
  const { data, body } = parseFrontmatter(content);
10018
10376
  const skillName = data.name || entry.name;
10019
10377
  const originalDescription = data.description || "";
@@ -10044,7 +10402,7 @@ $ARGUMENTS
10044
10402
  return skills;
10045
10403
  }
10046
10404
  function loadUserSkillsAsCommands() {
10047
- const userSkillsDir = join32(homedir11(), ".claude", "skills");
10405
+ const userSkillsDir = join33(homedir11(), ".claude", "skills");
10048
10406
  const skills = loadSkillsFromDir(userSkillsDir, "user");
10049
10407
  return skills.reduce((acc, skill) => {
10050
10408
  acc[skill.name] = skill.definition;
@@ -10052,7 +10410,7 @@ function loadUserSkillsAsCommands() {
10052
10410
  }, {});
10053
10411
  }
10054
10412
  function loadProjectSkillsAsCommands() {
10055
- const projectSkillsDir = join32(process.cwd(), ".claude", "skills");
10413
+ const projectSkillsDir = join33(process.cwd(), ".claude", "skills");
10056
10414
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
10057
10415
  return skills.reduce((acc, skill) => {
10058
10416
  acc[skill.name] = skill.definition;
@@ -10060,9 +10418,9 @@ function loadProjectSkillsAsCommands() {
10060
10418
  }, {});
10061
10419
  }
10062
10420
  // src/features/claude-code-agent-loader/loader.ts
10063
- import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
10421
+ import { existsSync as existsSync25, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
10064
10422
  import { homedir as homedir12 } from "os";
10065
- import { join as join33, basename as basename2 } from "path";
10423
+ import { join as join34, basename as basename2 } from "path";
10066
10424
  function parseToolsConfig(toolsStr) {
10067
10425
  if (!toolsStr)
10068
10426
  return;
@@ -10076,18 +10434,18 @@ function parseToolsConfig(toolsStr) {
10076
10434
  return result;
10077
10435
  }
10078
10436
  function loadAgentsFromDir(agentsDir, scope) {
10079
- if (!existsSync24(agentsDir)) {
10437
+ if (!existsSync25(agentsDir)) {
10080
10438
  return [];
10081
10439
  }
10082
- const entries = readdirSync7(agentsDir, { withFileTypes: true });
10440
+ const entries = readdirSync8(agentsDir, { withFileTypes: true });
10083
10441
  const agents = [];
10084
10442
  for (const entry of entries) {
10085
10443
  if (!isMarkdownFile(entry))
10086
10444
  continue;
10087
- const agentPath = join33(agentsDir, entry.name);
10445
+ const agentPath = join34(agentsDir, entry.name);
10088
10446
  const agentName = basename2(entry.name, ".md");
10089
10447
  try {
10090
- const content = readFileSync16(agentPath, "utf-8");
10448
+ const content = readFileSync17(agentPath, "utf-8");
10091
10449
  const { data, body } = parseFrontmatter(content);
10092
10450
  const name = data.name || agentName;
10093
10451
  const originalDescription = data.description || "";
@@ -10114,7 +10472,7 @@ function loadAgentsFromDir(agentsDir, scope) {
10114
10472
  return agents;
10115
10473
  }
10116
10474
  function loadUserAgents() {
10117
- const userAgentsDir = join33(homedir12(), ".claude", "agents");
10475
+ const userAgentsDir = join34(homedir12(), ".claude", "agents");
10118
10476
  const agents = loadAgentsFromDir(userAgentsDir, "user");
10119
10477
  const result = {};
10120
10478
  for (const agent of agents) {
@@ -10123,7 +10481,7 @@ function loadUserAgents() {
10123
10481
  return result;
10124
10482
  }
10125
10483
  function loadProjectAgents() {
10126
- const projectAgentsDir = join33(process.cwd(), ".claude", "agents");
10484
+ const projectAgentsDir = join34(process.cwd(), ".claude", "agents");
10127
10485
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
10128
10486
  const result = {};
10129
10487
  for (const agent of agents) {
@@ -10132,9 +10490,9 @@ function loadProjectAgents() {
10132
10490
  return result;
10133
10491
  }
10134
10492
  // src/features/claude-code-mcp-loader/loader.ts
10135
- import { existsSync as existsSync25 } from "fs";
10493
+ import { existsSync as existsSync26 } from "fs";
10136
10494
  import { homedir as homedir13 } from "os";
10137
- import { join as join34 } from "path";
10495
+ import { join as join35 } from "path";
10138
10496
 
10139
10497
  // src/features/claude-code-mcp-loader/env-expander.ts
10140
10498
  function expandEnvVars(value) {
@@ -10203,13 +10561,13 @@ function getMcpConfigPaths() {
10203
10561
  const home = homedir13();
10204
10562
  const cwd = process.cwd();
10205
10563
  return [
10206
- { path: join34(home, ".claude", ".mcp.json"), scope: "user" },
10207
- { path: join34(cwd, ".mcp.json"), scope: "project" },
10208
- { path: join34(cwd, ".claude", ".mcp.json"), scope: "local" }
10564
+ { path: join35(home, ".claude", ".mcp.json"), scope: "user" },
10565
+ { path: join35(cwd, ".mcp.json"), scope: "project" },
10566
+ { path: join35(cwd, ".claude", ".mcp.json"), scope: "local" }
10209
10567
  ];
10210
10568
  }
10211
10569
  async function loadMcpConfigFile(filePath) {
10212
- if (!existsSync25(filePath)) {
10570
+ if (!existsSync26(filePath)) {
10213
10571
  return null;
10214
10572
  }
10215
10573
  try {
@@ -10495,14 +10853,14 @@ var EXT_TO_LANG = {
10495
10853
  ".tfvars": "terraform"
10496
10854
  };
10497
10855
  // src/tools/lsp/config.ts
10498
- import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
10499
- import { join as join35 } from "path";
10856
+ import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
10857
+ import { join as join36 } from "path";
10500
10858
  import { homedir as homedir14 } from "os";
10501
10859
  function loadJsonFile(path6) {
10502
- if (!existsSync26(path6))
10860
+ if (!existsSync27(path6))
10503
10861
  return null;
10504
10862
  try {
10505
- return JSON.parse(readFileSync17(path6, "utf-8"));
10863
+ return JSON.parse(readFileSync18(path6, "utf-8"));
10506
10864
  } catch {
10507
10865
  return null;
10508
10866
  }
@@ -10510,9 +10868,9 @@ function loadJsonFile(path6) {
10510
10868
  function getConfigPaths2() {
10511
10869
  const cwd = process.cwd();
10512
10870
  return {
10513
- project: join35(cwd, ".opencode", "oh-my-opencode.json"),
10514
- user: join35(homedir14(), ".config", "opencode", "oh-my-opencode.json"),
10515
- opencode: join35(homedir14(), ".config", "opencode", "opencode.json")
10871
+ project: join36(cwd, ".opencode", "oh-my-opencode.json"),
10872
+ user: join36(homedir14(), ".config", "opencode", "oh-my-opencode.json"),
10873
+ opencode: join36(homedir14(), ".config", "opencode", "opencode.json")
10516
10874
  };
10517
10875
  }
10518
10876
  function loadAllConfigs() {
@@ -10605,7 +10963,7 @@ function isServerInstalled(command) {
10605
10963
  const pathEnv = process.env.PATH || "";
10606
10964
  const paths = pathEnv.split(":");
10607
10965
  for (const p of paths) {
10608
- if (existsSync26(join35(p, cmd))) {
10966
+ if (existsSync27(join36(p, cmd))) {
10609
10967
  return true;
10610
10968
  }
10611
10969
  }
@@ -10655,7 +11013,7 @@ function getAllServers() {
10655
11013
  }
10656
11014
  // src/tools/lsp/client.ts
10657
11015
  var {spawn: spawn4 } = globalThis.Bun;
10658
- import { readFileSync as readFileSync18 } from "fs";
11016
+ import { readFileSync as readFileSync19 } from "fs";
10659
11017
  import { extname, resolve as resolve5 } from "path";
10660
11018
  class LSPServerManager {
10661
11019
  static instance;
@@ -10664,6 +11022,36 @@ class LSPServerManager {
10664
11022
  IDLE_TIMEOUT = 5 * 60 * 1000;
10665
11023
  constructor() {
10666
11024
  this.startCleanupTimer();
11025
+ this.registerProcessCleanup();
11026
+ }
11027
+ registerProcessCleanup() {
11028
+ const cleanup = () => {
11029
+ for (const [, managed] of this.clients) {
11030
+ try {
11031
+ managed.client.stop();
11032
+ } catch {}
11033
+ }
11034
+ this.clients.clear();
11035
+ if (this.cleanupInterval) {
11036
+ clearInterval(this.cleanupInterval);
11037
+ this.cleanupInterval = null;
11038
+ }
11039
+ };
11040
+ process.on("exit", cleanup);
11041
+ process.on("SIGINT", () => {
11042
+ cleanup();
11043
+ process.exit(0);
11044
+ });
11045
+ process.on("SIGTERM", () => {
11046
+ cleanup();
11047
+ process.exit(0);
11048
+ });
11049
+ if (process.platform === "win32") {
11050
+ process.on("SIGBREAK", () => {
11051
+ cleanup();
11052
+ process.exit(0);
11053
+ });
11054
+ }
10667
11055
  }
10668
11056
  static getInstance() {
10669
11057
  if (!LSPServerManager.instance) {
@@ -11055,7 +11443,7 @@ ${msg}`);
11055
11443
  const absPath = resolve5(filePath);
11056
11444
  if (this.openedFiles.has(absPath))
11057
11445
  return;
11058
- const text = readFileSync18(absPath, "utf-8");
11446
+ const text = readFileSync19(absPath, "utf-8");
11059
11447
  const ext = extname(absPath);
11060
11448
  const languageId = getLanguageId(ext);
11061
11449
  this.notify("textDocument/didOpen", {
@@ -11170,16 +11558,16 @@ ${msg}`);
11170
11558
  }
11171
11559
  // src/tools/lsp/utils.ts
11172
11560
  import { extname as extname2, resolve as resolve6 } from "path";
11173
- import { existsSync as existsSync27, readFileSync as readFileSync19, writeFileSync as writeFileSync10 } from "fs";
11561
+ import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
11174
11562
  function findWorkspaceRoot(filePath) {
11175
11563
  let dir = resolve6(filePath);
11176
- if (!existsSync27(dir) || !__require("fs").statSync(dir).isDirectory()) {
11564
+ if (!existsSync28(dir) || !__require("fs").statSync(dir).isDirectory()) {
11177
11565
  dir = __require("path").dirname(dir);
11178
11566
  }
11179
11567
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
11180
11568
  while (dir !== "/") {
11181
11569
  for (const marker of markers) {
11182
- if (existsSync27(__require("path").join(dir, marker))) {
11570
+ if (existsSync28(__require("path").join(dir, marker))) {
11183
11571
  return dir;
11184
11572
  }
11185
11573
  }
@@ -11337,7 +11725,7 @@ function formatCodeActions(actions) {
11337
11725
  }
11338
11726
  function applyTextEditsToFile(filePath, edits) {
11339
11727
  try {
11340
- let content = readFileSync19(filePath, "utf-8");
11728
+ let content = readFileSync20(filePath, "utf-8");
11341
11729
  const lines = content.split(`
11342
11730
  `);
11343
11731
  const sortedEdits = [...edits].sort((a, b) => {
@@ -11362,7 +11750,7 @@ function applyTextEditsToFile(filePath, edits) {
11362
11750
  `));
11363
11751
  }
11364
11752
  }
11365
- writeFileSync10(filePath, lines.join(`
11753
+ writeFileSync11(filePath, lines.join(`
11366
11754
  `), "utf-8");
11367
11755
  return { success: true, editCount: edits.length };
11368
11756
  } catch (err) {
@@ -11393,7 +11781,7 @@ function applyWorkspaceEdit(edit) {
11393
11781
  if (change.kind === "create") {
11394
11782
  try {
11395
11783
  const filePath = change.uri.replace("file://", "");
11396
- writeFileSync10(filePath, "", "utf-8");
11784
+ writeFileSync11(filePath, "", "utf-8");
11397
11785
  result.filesModified.push(filePath);
11398
11786
  } catch (err) {
11399
11787
  result.success = false;
@@ -11403,8 +11791,8 @@ function applyWorkspaceEdit(edit) {
11403
11791
  try {
11404
11792
  const oldPath = change.oldUri.replace("file://", "");
11405
11793
  const newPath = change.newUri.replace("file://", "");
11406
- const content = readFileSync19(oldPath, "utf-8");
11407
- writeFileSync10(newPath, content, "utf-8");
11794
+ const content = readFileSync20(oldPath, "utf-8");
11795
+ writeFileSync11(newPath, content, "utf-8");
11408
11796
  __require("fs").unlinkSync(oldPath);
11409
11797
  result.filesModified.push(newPath);
11410
11798
  } catch (err) {
@@ -24104,13 +24492,13 @@ var lsp_code_action_resolve = tool({
24104
24492
  });
24105
24493
  // src/tools/ast-grep/constants.ts
24106
24494
  import { createRequire as createRequire4 } from "module";
24107
- import { dirname as dirname6, join as join37 } from "path";
24108
- import { existsSync as existsSync29, statSync as statSync4 } from "fs";
24495
+ import { dirname as dirname6, join as join38 } from "path";
24496
+ import { existsSync as existsSync30, statSync as statSync4 } from "fs";
24109
24497
 
24110
24498
  // src/tools/ast-grep/downloader.ts
24111
24499
  var {spawn: spawn5 } = globalThis.Bun;
24112
- import { existsSync as existsSync28, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24113
- import { join as join36 } from "path";
24500
+ import { existsSync as existsSync29, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24501
+ import { join as join37 } from "path";
24114
24502
  import { homedir as homedir15 } from "os";
24115
24503
  import { createRequire as createRequire3 } from "module";
24116
24504
  var REPO2 = "ast-grep/ast-grep";
@@ -24136,19 +24524,19 @@ var PLATFORM_MAP2 = {
24136
24524
  function getCacheDir3() {
24137
24525
  if (process.platform === "win32") {
24138
24526
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
24139
- const base2 = localAppData || join36(homedir15(), "AppData", "Local");
24140
- return join36(base2, "oh-my-opencode", "bin");
24527
+ const base2 = localAppData || join37(homedir15(), "AppData", "Local");
24528
+ return join37(base2, "oh-my-opencode", "bin");
24141
24529
  }
24142
24530
  const xdgCache2 = process.env.XDG_CACHE_HOME;
24143
- const base = xdgCache2 || join36(homedir15(), ".cache");
24144
- return join36(base, "oh-my-opencode", "bin");
24531
+ const base = xdgCache2 || join37(homedir15(), ".cache");
24532
+ return join37(base, "oh-my-opencode", "bin");
24145
24533
  }
24146
24534
  function getBinaryName3() {
24147
24535
  return process.platform === "win32" ? "sg.exe" : "sg";
24148
24536
  }
24149
24537
  function getCachedBinaryPath2() {
24150
- const binaryPath = join36(getCacheDir3(), getBinaryName3());
24151
- return existsSync28(binaryPath) ? binaryPath : null;
24538
+ const binaryPath = join37(getCacheDir3(), getBinaryName3());
24539
+ return existsSync29(binaryPath) ? binaryPath : null;
24152
24540
  }
24153
24541
  async function extractZip2(archivePath, destDir) {
24154
24542
  const proc = process.platform === "win32" ? spawn5([
@@ -24174,8 +24562,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24174
24562
  }
24175
24563
  const cacheDir = getCacheDir3();
24176
24564
  const binaryName = getBinaryName3();
24177
- const binaryPath = join36(cacheDir, binaryName);
24178
- if (existsSync28(binaryPath)) {
24565
+ const binaryPath = join37(cacheDir, binaryName);
24566
+ if (existsSync29(binaryPath)) {
24179
24567
  return binaryPath;
24180
24568
  }
24181
24569
  const { arch, os: os4 } = platformInfo;
@@ -24183,21 +24571,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24183
24571
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
24184
24572
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
24185
24573
  try {
24186
- if (!existsSync28(cacheDir)) {
24574
+ if (!existsSync29(cacheDir)) {
24187
24575
  mkdirSync10(cacheDir, { recursive: true });
24188
24576
  }
24189
24577
  const response2 = await fetch(downloadUrl, { redirect: "follow" });
24190
24578
  if (!response2.ok) {
24191
24579
  throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
24192
24580
  }
24193
- const archivePath = join36(cacheDir, assetName);
24581
+ const archivePath = join37(cacheDir, assetName);
24194
24582
  const arrayBuffer = await response2.arrayBuffer();
24195
24583
  await Bun.write(archivePath, arrayBuffer);
24196
24584
  await extractZip2(archivePath, cacheDir);
24197
- if (existsSync28(archivePath)) {
24585
+ if (existsSync29(archivePath)) {
24198
24586
  unlinkSync9(archivePath);
24199
24587
  }
24200
- if (process.platform !== "win32" && existsSync28(binaryPath)) {
24588
+ if (process.platform !== "win32" && existsSync29(binaryPath)) {
24201
24589
  chmodSync2(binaryPath, 493);
24202
24590
  }
24203
24591
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -24248,8 +24636,8 @@ function findSgCliPathSync() {
24248
24636
  const require2 = createRequire4(import.meta.url);
24249
24637
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
24250
24638
  const cliDir = dirname6(cliPkgPath);
24251
- const sgPath = join37(cliDir, binaryName);
24252
- if (existsSync29(sgPath) && isValidBinary(sgPath)) {
24639
+ const sgPath = join38(cliDir, binaryName);
24640
+ if (existsSync30(sgPath) && isValidBinary(sgPath)) {
24253
24641
  return sgPath;
24254
24642
  }
24255
24643
  } catch {}
@@ -24260,8 +24648,8 @@ function findSgCliPathSync() {
24260
24648
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
24261
24649
  const pkgDir = dirname6(pkgPath);
24262
24650
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
24263
- const binaryPath = join37(pkgDir, astGrepName);
24264
- if (existsSync29(binaryPath) && isValidBinary(binaryPath)) {
24651
+ const binaryPath = join38(pkgDir, astGrepName);
24652
+ if (existsSync30(binaryPath) && isValidBinary(binaryPath)) {
24265
24653
  return binaryPath;
24266
24654
  }
24267
24655
  } catch {}
@@ -24269,7 +24657,7 @@ function findSgCliPathSync() {
24269
24657
  if (process.platform === "darwin") {
24270
24658
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
24271
24659
  for (const path6 of homebrewPaths) {
24272
- if (existsSync29(path6) && isValidBinary(path6)) {
24660
+ if (existsSync30(path6) && isValidBinary(path6)) {
24273
24661
  return path6;
24274
24662
  }
24275
24663
  }
@@ -24325,11 +24713,11 @@ var DEFAULT_MAX_MATCHES = 500;
24325
24713
 
24326
24714
  // src/tools/ast-grep/cli.ts
24327
24715
  var {spawn: spawn6 } = globalThis.Bun;
24328
- import { existsSync as existsSync30 } from "fs";
24716
+ import { existsSync as existsSync31 } from "fs";
24329
24717
  var resolvedCliPath3 = null;
24330
24718
  var initPromise2 = null;
24331
24719
  async function getAstGrepPath() {
24332
- if (resolvedCliPath3 !== null && existsSync30(resolvedCliPath3)) {
24720
+ if (resolvedCliPath3 !== null && existsSync31(resolvedCliPath3)) {
24333
24721
  return resolvedCliPath3;
24334
24722
  }
24335
24723
  if (initPromise2) {
@@ -24337,7 +24725,7 @@ async function getAstGrepPath() {
24337
24725
  }
24338
24726
  initPromise2 = (async () => {
24339
24727
  const syncPath = findSgCliPathSync();
24340
- if (syncPath && existsSync30(syncPath)) {
24728
+ if (syncPath && existsSync31(syncPath)) {
24341
24729
  resolvedCliPath3 = syncPath;
24342
24730
  setSgCliPath(syncPath);
24343
24731
  return syncPath;
@@ -24371,7 +24759,7 @@ async function runSg(options) {
24371
24759
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
24372
24760
  args.push(...paths);
24373
24761
  let cliPath = getSgCliPath();
24374
- if (!existsSync30(cliPath) && cliPath !== "sg") {
24762
+ if (!existsSync31(cliPath) && cliPath !== "sg") {
24375
24763
  const downloadedPath = await getAstGrepPath();
24376
24764
  if (downloadedPath) {
24377
24765
  cliPath = downloadedPath;
@@ -24635,24 +25023,24 @@ var ast_grep_replace = tool({
24635
25023
  var {spawn: spawn7 } = globalThis.Bun;
24636
25024
 
24637
25025
  // src/tools/grep/constants.ts
24638
- import { existsSync as existsSync32 } from "fs";
24639
- import { join as join39, dirname as dirname7 } from "path";
25026
+ import { existsSync as existsSync33 } from "fs";
25027
+ import { join as join40, dirname as dirname7 } from "path";
24640
25028
  import { spawnSync } from "child_process";
24641
25029
 
24642
25030
  // src/tools/grep/downloader.ts
24643
- import { existsSync as existsSync31, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync8 } from "fs";
24644
- import { join as join38 } from "path";
25031
+ import { existsSync as existsSync32, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
25032
+ import { join as join39 } from "path";
24645
25033
  function getInstallDir() {
24646
25034
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
24647
- return join38(homeDir, ".cache", "oh-my-opencode", "bin");
25035
+ return join39(homeDir, ".cache", "oh-my-opencode", "bin");
24648
25036
  }
24649
25037
  function getRgPath() {
24650
25038
  const isWindows2 = process.platform === "win32";
24651
- return join38(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
25039
+ return join39(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
24652
25040
  }
24653
25041
  function getInstalledRipgrepPath() {
24654
25042
  const rgPath = getRgPath();
24655
- return existsSync31(rgPath) ? rgPath : null;
25043
+ return existsSync32(rgPath) ? rgPath : null;
24656
25044
  }
24657
25045
 
24658
25046
  // src/tools/grep/constants.ts
@@ -24675,13 +25063,13 @@ function getOpenCodeBundledRg() {
24675
25063
  const isWindows2 = process.platform === "win32";
24676
25064
  const rgName = isWindows2 ? "rg.exe" : "rg";
24677
25065
  const candidates = [
24678
- join39(execDir, rgName),
24679
- join39(execDir, "bin", rgName),
24680
- join39(execDir, "..", "bin", rgName),
24681
- join39(execDir, "..", "libexec", rgName)
25066
+ join40(execDir, rgName),
25067
+ join40(execDir, "bin", rgName),
25068
+ join40(execDir, "..", "bin", rgName),
25069
+ join40(execDir, "..", "libexec", rgName)
24682
25070
  ];
24683
25071
  for (const candidate of candidates) {
24684
- if (existsSync32(candidate)) {
25072
+ if (existsSync33(candidate)) {
24685
25073
  return candidate;
24686
25074
  }
24687
25075
  }
@@ -25084,22 +25472,22 @@ var glob = tool({
25084
25472
  }
25085
25473
  });
25086
25474
  // src/tools/slashcommand/tools.ts
25087
- import { existsSync as existsSync33, readdirSync as readdirSync9, readFileSync as readFileSync20 } from "fs";
25475
+ import { existsSync as existsSync34, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
25088
25476
  import { homedir as homedir16 } from "os";
25089
- import { join as join40, basename as basename3, dirname as dirname8 } from "path";
25477
+ import { join as join41, basename as basename3, dirname as dirname8 } from "path";
25090
25478
  function discoverCommandsFromDir(commandsDir, scope) {
25091
- if (!existsSync33(commandsDir)) {
25479
+ if (!existsSync34(commandsDir)) {
25092
25480
  return [];
25093
25481
  }
25094
- const entries = readdirSync9(commandsDir, { withFileTypes: true });
25482
+ const entries = readdirSync10(commandsDir, { withFileTypes: true });
25095
25483
  const commands = [];
25096
25484
  for (const entry of entries) {
25097
25485
  if (!isMarkdownFile(entry))
25098
25486
  continue;
25099
- const commandPath = join40(commandsDir, entry.name);
25487
+ const commandPath = join41(commandsDir, entry.name);
25100
25488
  const commandName = basename3(entry.name, ".md");
25101
25489
  try {
25102
- const content = readFileSync20(commandPath, "utf-8");
25490
+ const content = readFileSync21(commandPath, "utf-8");
25103
25491
  const { data, body } = parseFrontmatter(content);
25104
25492
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
25105
25493
  const metadata = {
@@ -25124,10 +25512,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
25124
25512
  return commands;
25125
25513
  }
25126
25514
  function discoverCommandsSync() {
25127
- const userCommandsDir = join40(homedir16(), ".claude", "commands");
25128
- const projectCommandsDir = join40(process.cwd(), ".claude", "commands");
25129
- const opencodeGlobalDir = join40(homedir16(), ".config", "opencode", "command");
25130
- const opencodeProjectDir = join40(process.cwd(), ".opencode", "command");
25515
+ const userCommandsDir = join41(homedir16(), ".claude", "commands");
25516
+ const projectCommandsDir = join41(process.cwd(), ".claude", "commands");
25517
+ const opencodeGlobalDir = join41(homedir16(), ".config", "opencode", "command");
25518
+ const opencodeProjectDir = join41(process.cwd(), ".opencode", "command");
25131
25519
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
25132
25520
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
25133
25521
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -25259,9 +25647,9 @@ var SkillFrontmatterSchema = exports_external.object({
25259
25647
  metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
25260
25648
  });
25261
25649
  // src/tools/skill/tools.ts
25262
- import { existsSync as existsSync34, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
25650
+ import { existsSync as existsSync35, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
25263
25651
  import { homedir as homedir17 } from "os";
25264
- import { join as join41, basename as basename4 } from "path";
25652
+ import { join as join42, basename as basename4 } from "path";
25265
25653
  function parseSkillFrontmatter(data) {
25266
25654
  return {
25267
25655
  name: typeof data.name === "string" ? data.name : "",
@@ -25272,22 +25660,22 @@ function parseSkillFrontmatter(data) {
25272
25660
  };
25273
25661
  }
25274
25662
  function discoverSkillsFromDir(skillsDir, scope) {
25275
- if (!existsSync34(skillsDir)) {
25663
+ if (!existsSync35(skillsDir)) {
25276
25664
  return [];
25277
25665
  }
25278
- const entries = readdirSync10(skillsDir, { withFileTypes: true });
25666
+ const entries = readdirSync11(skillsDir, { withFileTypes: true });
25279
25667
  const skills = [];
25280
25668
  for (const entry of entries) {
25281
25669
  if (entry.name.startsWith("."))
25282
25670
  continue;
25283
- const skillPath = join41(skillsDir, entry.name);
25671
+ const skillPath = join42(skillsDir, entry.name);
25284
25672
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25285
25673
  const resolvedPath = resolveSymlink(skillPath);
25286
- const skillMdPath = join41(resolvedPath, "SKILL.md");
25287
- if (!existsSync34(skillMdPath))
25674
+ const skillMdPath = join42(resolvedPath, "SKILL.md");
25675
+ if (!existsSync35(skillMdPath))
25288
25676
  continue;
25289
25677
  try {
25290
- const content = readFileSync21(skillMdPath, "utf-8");
25678
+ const content = readFileSync22(skillMdPath, "utf-8");
25291
25679
  const { data } = parseFrontmatter(content);
25292
25680
  skills.push({
25293
25681
  name: data.name || entry.name,
@@ -25302,8 +25690,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
25302
25690
  return skills;
25303
25691
  }
25304
25692
  function discoverSkillsSync() {
25305
- const userSkillsDir = join41(homedir17(), ".claude", "skills");
25306
- const projectSkillsDir = join41(process.cwd(), ".claude", "skills");
25693
+ const userSkillsDir = join42(homedir17(), ".claude", "skills");
25694
+ const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
25307
25695
  const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
25308
25696
  const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
25309
25697
  return [...projectSkills, ...userSkills];
@@ -25313,12 +25701,12 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
25313
25701
  `);
25314
25702
  async function parseSkillMd(skillPath) {
25315
25703
  const resolvedPath = resolveSymlink(skillPath);
25316
- const skillMdPath = join41(resolvedPath, "SKILL.md");
25317
- if (!existsSync34(skillMdPath)) {
25704
+ const skillMdPath = join42(resolvedPath, "SKILL.md");
25705
+ if (!existsSync35(skillMdPath)) {
25318
25706
  return null;
25319
25707
  }
25320
25708
  try {
25321
- let content = readFileSync21(skillMdPath, "utf-8");
25709
+ let content = readFileSync22(skillMdPath, "utf-8");
25322
25710
  content = await resolveCommandsInText(content);
25323
25711
  const { data, body } = parseFrontmatter(content);
25324
25712
  const frontmatter2 = parseSkillFrontmatter(data);
@@ -25329,12 +25717,12 @@ async function parseSkillMd(skillPath) {
25329
25717
  allowedTools: frontmatter2["allowed-tools"],
25330
25718
  metadata: frontmatter2.metadata
25331
25719
  };
25332
- const referencesDir = join41(resolvedPath, "references");
25333
- const scriptsDir = join41(resolvedPath, "scripts");
25334
- const assetsDir = join41(resolvedPath, "assets");
25335
- const references = existsSync34(referencesDir) ? readdirSync10(referencesDir).filter((f) => !f.startsWith(".")) : [];
25336
- const scripts = existsSync34(scriptsDir) ? readdirSync10(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
25337
- const assets = existsSync34(assetsDir) ? readdirSync10(assetsDir).filter((f) => !f.startsWith(".")) : [];
25720
+ const referencesDir = join42(resolvedPath, "references");
25721
+ const scriptsDir = join42(resolvedPath, "scripts");
25722
+ const assetsDir = join42(resolvedPath, "assets");
25723
+ const references = existsSync35(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
25724
+ const scripts = existsSync35(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
25725
+ const assets = existsSync35(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
25338
25726
  return {
25339
25727
  name: metadata.name,
25340
25728
  path: resolvedPath,
@@ -25350,15 +25738,15 @@ async function parseSkillMd(skillPath) {
25350
25738
  }
25351
25739
  }
25352
25740
  async function discoverSkillsFromDirAsync(skillsDir) {
25353
- if (!existsSync34(skillsDir)) {
25741
+ if (!existsSync35(skillsDir)) {
25354
25742
  return [];
25355
25743
  }
25356
- const entries = readdirSync10(skillsDir, { withFileTypes: true });
25744
+ const entries = readdirSync11(skillsDir, { withFileTypes: true });
25357
25745
  const skills = [];
25358
25746
  for (const entry of entries) {
25359
25747
  if (entry.name.startsWith("."))
25360
25748
  continue;
25361
- const skillPath = join41(skillsDir, entry.name);
25749
+ const skillPath = join42(skillsDir, entry.name);
25362
25750
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25363
25751
  const skillInfo = await parseSkillMd(skillPath);
25364
25752
  if (skillInfo) {
@@ -25369,8 +25757,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
25369
25757
  return skills;
25370
25758
  }
25371
25759
  async function discoverSkills() {
25372
- const userSkillsDir = join41(homedir17(), ".claude", "skills");
25373
- const projectSkillsDir = join41(process.cwd(), ".claude", "skills");
25760
+ const userSkillsDir = join42(homedir17(), ".claude", "skills");
25761
+ const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
25374
25762
  const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
25375
25763
  const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
25376
25764
  return [...projectSkills, ...userSkills];
@@ -25399,9 +25787,9 @@ async function loadSkillWithReferences(skill, includeRefs) {
25399
25787
  const referencesLoaded = [];
25400
25788
  if (includeRefs && skill.references.length > 0) {
25401
25789
  for (const ref of skill.references) {
25402
- const refPath = join41(skill.path, "references", ref);
25790
+ const refPath = join42(skill.path, "references", ref);
25403
25791
  try {
25404
- let content = readFileSync21(refPath, "utf-8");
25792
+ let content = readFileSync22(refPath, "utf-8");
25405
25793
  content = await resolveCommandsInText(content);
25406
25794
  referencesLoaded.push({ path: ref, content });
25407
25795
  } catch {}
@@ -25673,12 +26061,15 @@ Arguments:
25673
26061
  - timeout: Max wait time in ms when blocking (default: 60000, max: 600000)
25674
26062
 
25675
26063
  The system automatically notifies when background tasks complete. You typically don't need block=true.`;
25676
- var BACKGROUND_CANCEL_DESCRIPTION = `Cancel a running background task.
26064
+ var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s).
25677
26065
 
25678
26066
  Only works for tasks with status "running". Aborts the background session and marks the task as cancelled.
25679
26067
 
25680
26068
  Arguments:
25681
- - taskId: Required task ID to cancel.`;
26069
+ - taskId: Task ID to cancel (optional if all=true)
26070
+ - all: Set to true to cancel ALL running background tasks at once (default: false)
26071
+
26072
+ **Cleanup Before Answer**: When you have gathered sufficient information and are ready to provide your final answer to the user, use \`all=true\` to cancel ALL running background tasks first, then deliver your response. This conserves resources and ensures clean workflow completion.`;
25682
26073
 
25683
26074
  // src/tools/background-task/tools.ts
25684
26075
  function formatDuration(start, end) {
@@ -25893,10 +26284,35 @@ function createBackgroundCancel(manager, client2) {
25893
26284
  return tool({
25894
26285
  description: BACKGROUND_CANCEL_DESCRIPTION,
25895
26286
  args: {
25896
- taskId: tool.schema.string().describe("Task ID to cancel")
26287
+ taskId: tool.schema.string().optional().describe("Task ID to cancel (required if all=false)"),
26288
+ all: tool.schema.boolean().optional().describe("Cancel all running background tasks (default: false)")
25897
26289
  },
25898
- async execute(args) {
26290
+ async execute(args, toolContext) {
25899
26291
  try {
26292
+ const cancelAll = args.all === true;
26293
+ if (!cancelAll && !args.taskId) {
26294
+ return `\u274C Invalid arguments: Either provide a taskId or set all=true to cancel all running tasks.`;
26295
+ }
26296
+ if (cancelAll) {
26297
+ const tasks = manager.getTasksByParentSession(toolContext.sessionID);
26298
+ const runningTasks = tasks.filter((t) => t.status === "running");
26299
+ if (runningTasks.length === 0) {
26300
+ return `\u2705 No running background tasks to cancel.`;
26301
+ }
26302
+ const results = [];
26303
+ for (const task2 of runningTasks) {
26304
+ client2.session.abort({
26305
+ path: { id: task2.sessionID }
26306
+ }).catch(() => {});
26307
+ task2.status = "cancelled";
26308
+ task2.completedAt = new Date;
26309
+ results.push(`- ${task2.id}: ${task2.description}`);
26310
+ }
26311
+ return `\u2705 Cancelled ${runningTasks.length} background task(s):
26312
+
26313
+ ${results.join(`
26314
+ `)}`;
26315
+ }
25900
26316
  const task = manager.getTask(args.taskId);
25901
26317
  if (!task) {
25902
26318
  return `\u274C Task not found: ${args.taskId}`;
@@ -26197,17 +26613,17 @@ var builtinTools = {
26197
26613
  skill
26198
26614
  };
26199
26615
  // src/features/background-agent/manager.ts
26200
- import { existsSync as existsSync35, readdirSync as readdirSync11 } from "fs";
26201
- import { join as join42 } from "path";
26202
- function getMessageDir3(sessionID) {
26203
- if (!existsSync35(MESSAGE_STORAGE))
26616
+ import { existsSync as existsSync36, readdirSync as readdirSync12 } from "fs";
26617
+ import { join as join43 } from "path";
26618
+ function getMessageDir4(sessionID) {
26619
+ if (!existsSync36(MESSAGE_STORAGE))
26204
26620
  return null;
26205
- const directPath = join42(MESSAGE_STORAGE, sessionID);
26206
- if (existsSync35(directPath))
26621
+ const directPath = join43(MESSAGE_STORAGE, sessionID);
26622
+ if (existsSync36(directPath))
26207
26623
  return directPath;
26208
- for (const dir of readdirSync11(MESSAGE_STORAGE)) {
26209
- const sessionPath = join42(MESSAGE_STORAGE, dir, sessionID);
26210
- if (existsSync35(sessionPath))
26624
+ for (const dir of readdirSync12(MESSAGE_STORAGE)) {
26625
+ const sessionPath = join43(MESSAGE_STORAGE, dir, sessionID);
26626
+ if (existsSync36(sessionPath))
26211
26627
  return sessionPath;
26212
26628
  }
26213
26629
  return null;
@@ -26431,7 +26847,7 @@ class BackgroundManager {
26431
26847
  log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
26432
26848
  setTimeout(async () => {
26433
26849
  try {
26434
- const messageDir = getMessageDir3(task.parentSessionID);
26850
+ const messageDir = getMessageDir4(task.parentSessionID);
26435
26851
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
26436
26852
  await this.client.session.prompt({
26437
26853
  path: { id: task.parentSessionID },
@@ -26627,7 +27043,8 @@ var HookNameSchema = exports_external.enum([
26627
27043
  "keyword-detector",
26628
27044
  "agent-usage-reminder",
26629
27045
  "non-interactive-env",
26630
- "interactive-bash-session"
27046
+ "interactive-bash-session",
27047
+ "empty-message-sanitizer"
26631
27048
  ]);
26632
27049
  var AgentOverrideConfigSchema = exports_external.object({
26633
27050
  model: exports_external.string().optional(),
@@ -26795,13 +27212,14 @@ var OhMyOpenCodePlugin = async (ctx) => {
26795
27212
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
26796
27213
  const nonInteractiveEnv = isHookEnabled("non-interactive-env") ? createNonInteractiveEnvHook(ctx) : null;
26797
27214
  const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
27215
+ const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
26798
27216
  updateTerminalTitle({ sessionId: "main" });
26799
27217
  const backgroundManager = new BackgroundManager(ctx);
26800
27218
  const backgroundNotificationHook = isHookEnabled("background-notification") ? createBackgroundNotificationHook(backgroundManager) : null;
26801
27219
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
26802
27220
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
26803
27221
  const lookAt = createLookAt(ctx);
26804
- const googleAuthHooks = pluginConfig.google_auth ? await createGoogleAntigravityAuthPlugin(ctx) : null;
27222
+ const googleAuthHooks = pluginConfig.google_auth !== false ? await createGoogleAntigravityAuthPlugin(ctx) : null;
26805
27223
  const tmuxAvailable = await getTmuxPath();
26806
27224
  return {
26807
27225
  ...googleAuthHooks ? { auth: googleAuthHooks.auth } : {},
@@ -26816,6 +27234,9 @@ var OhMyOpenCodePlugin = async (ctx) => {
26816
27234
  await claudeCodeHooks["chat.message"]?.(input, output);
26817
27235
  await keywordDetector?.["chat.message"]?.(input, output);
26818
27236
  },
27237
+ "experimental.chat.messages.transform": async (input, output) => {
27238
+ await emptyMessageSanitizer?.["experimental.chat.messages.transform"]?.(input, output);
27239
+ },
26819
27240
  config: async (config3) => {
26820
27241
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory);
26821
27242
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};