oh-my-opencode 2.1.2 → 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
2662
+ prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results.
2585
2663
 
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.
2664
+ ## Your Mission
2587
2665
 
2588
- ## MANDATORY PARALLEL TOOL EXECUTION
2666
+ Answer questions like:
2667
+ - "Where is X implemented?"
2668
+ - "Which files contain Y?"
2669
+ - "Find the code that does Z"
2589
2670
 
2590
- **CRITICAL**: You MUST execute **AT LEAST 3 tool calls in parallel** for EVERY search task.
2671
+ ## CRITICAL: What You Must Deliver
2591
2672
 
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
- \`\`\`
2673
+ Every response MUST include:
2601
2674
 
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.
2603
-
2604
- ## Before You Search
2605
-
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
- \`\`\`
2678
-
2679
- **Pattern**: Flood grep_app with query variations (5+) \u2192 verify with local/official sources (2+) \u2192 trust only cross-validated results.
2680
-
2681
- ## Git CLI - USE EXTENSIVELY
2682
-
2683
- You have access to Git CLI via Bash. Use it extensively for repository analysis:
2684
+ ### 2. Parallel Execution (Required)
2685
+ Launch **3+ tools simultaneously** in your first action. Never sequential unless output depends on prior result.
2684
2686
 
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
- \`\`\`
2687
+ ### 3. Structured Results (Required)
2688
+ Always end with this exact format:
2715
2689
 
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
- \`\`\`
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>
2731
2695
 
2732
- ## LSP Tools - DEFINITIONS & REFERENCES
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>
2733
2700
 
2734
- Use LSP specifically for finding definitions and references - these are what LSP does better than text search.
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>
2735
2706
 
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
2707
+ ## Success Criteria
2739
2708
 
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
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 |
2743
2715
 
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
- \`\`\`
2716
+ ## Failure Conditions
2751
2717
 
2752
- ## AST-grep - STRUCTURAL CODE SEARCH
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
2753
2724
 
2754
- Use AST-grep for syntax-aware pattern matching (better than regex for code).
2725
+ ## Constraints
2755
2726
 
2756
- **Key Syntax**:
2757
- - \`$VAR\`: Match single AST node (identifier, expression, etc.)
2758
- - \`$$$\`: Match multiple nodes (arguments, statements, etc.)
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
2759
2730
 
2760
- **ast_grep_search Examples**:
2761
- \`\`\`
2762
- // Find function definitions
2763
- ast_grep_search(pattern: "function $NAME($$$) { $$$ }", lang: "typescript")
2731
+ ## Tool Strategy
2764
2732
 
2765
- // Find async functions
2766
- ast_grep_search(pattern: "async 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
2767
2740
 
2768
- // Find React hooks
2769
- ast_grep_search(pattern: "const [$STATE, $SETTER] = useState($$$)", lang: "tsx")
2741
+ ### grep_app Strategy
2770
2742
 
2771
- // Find class definitions
2772
- ast_grep_search(pattern: "class $NAME { $$$ }", lang: "typescript")
2743
+ grep_app searches millions of public GitHub repos instantly \u2014 use it for external patterns and examples.
2773
2744
 
2774
- // Find specific method calls
2775
- ast_grep_search(pattern: "console.log($$$)", 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
2776
2749
 
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;
@@ -4510,6 +4448,50 @@ function stripThinkingParts(messageID) {
4510
4448
  }
4511
4449
  return anyRemoved;
4512
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
+ }
4513
4495
  function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
4514
4496
  const messages = readMessages(sessionID);
4515
4497
  if (targetIndex < 0 || targetIndex >= messages.length)
@@ -4647,24 +4629,43 @@ var PLACEHOLDER_TEXT = "[user interrupted]";
4647
4629
  async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory, error) {
4648
4630
  const targetIndex = extractMessageIndex(error);
4649
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
+ }
4650
4639
  const thinkingOnlyIDs = findMessagesWithThinkingOnly(sessionID);
4651
4640
  for (const messageID of thinkingOnlyIDs) {
4652
- injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT);
4641
+ if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
4642
+ anySuccess = true;
4643
+ }
4653
4644
  }
4654
4645
  if (targetIndex !== null) {
4655
4646
  const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex);
4656
4647
  if (targetMessageID) {
4657
- 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
+ }
4658
4654
  }
4659
4655
  }
4660
4656
  if (failedID) {
4657
+ if (replaceEmptyTextParts(failedID, PLACEHOLDER_TEXT)) {
4658
+ return true;
4659
+ }
4661
4660
  if (injectTextPart(sessionID, failedID, PLACEHOLDER_TEXT)) {
4662
4661
  return true;
4663
4662
  }
4664
4663
  }
4665
4664
  const emptyMessageIDs = findEmptyMessages(sessionID);
4666
- let anySuccess = thinkingOnlyIDs.length > 0;
4667
4665
  for (const messageID of emptyMessageIDs) {
4666
+ if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
4667
+ anySuccess = true;
4668
+ }
4668
4669
  if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
4669
4670
  anySuccess = true;
4670
4671
  }
@@ -5615,17 +5616,104 @@ var FALLBACK_CONFIG = {
5615
5616
  maxRevertAttempts: 3,
5616
5617
  minMessagesRequired: 2
5617
5618
  };
5619
+ var TRUNCATE_CONFIG = {
5620
+ maxTruncateAttempts: 10,
5621
+ minOutputSizeToTruncate: 1000
5622
+ };
5618
5623
 
5619
- // src/hooks/anthropic-auto-compact/executor.ts
5620
- function calculateRetryDelay(attempt) {
5621
- const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, attempt - 1);
5622
- 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 "";
5623
5645
  }
5624
- function shouldRetry(retryState) {
5625
- if (!retryState)
5626
- return true;
5627
- 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;
5628
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);
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
5629
5717
  function getOrCreateRetryState(autoCompactState, sessionID) {
5630
5718
  let state = autoCompactState.retryStateBySession.get(sessionID);
5631
5719
  if (!state) {
@@ -5642,6 +5730,14 @@ function getOrCreateFallbackState(autoCompactState, sessionID) {
5642
5730
  }
5643
5731
  return state;
5644
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
+ }
5645
5741
  async function getLastMessagePair(sessionID, client, directory) {
5646
5742
  try {
5647
5743
  const resp = await client.session.messages({
@@ -5679,46 +5775,12 @@ async function getLastMessagePair(sessionID, client, directory) {
5679
5775
  return null;
5680
5776
  }
5681
5777
  }
5682
- async function executeRevertFallback(sessionID, autoCompactState, client, directory) {
5683
- const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
5684
- if (fallbackState.revertAttempt >= FALLBACK_CONFIG.maxRevertAttempts) {
5685
- return false;
5686
- }
5687
- const pair = await getLastMessagePair(sessionID, client, directory);
5688
- if (!pair) {
5689
- return false;
5690
- }
5691
- await client.tui.showToast({
5692
- body: {
5693
- title: "\u26A0\uFE0F Emergency Recovery",
5694
- message: `Context too large. Removing last message pair to recover session...`,
5695
- variant: "warning",
5696
- duration: 4000
5697
- }
5698
- }).catch(() => {});
5699
- try {
5700
- if (pair.assistantMessageID) {
5701
- await client.session.revert({
5702
- path: { id: sessionID },
5703
- body: { messageID: pair.assistantMessageID },
5704
- query: { directory }
5705
- });
5706
- }
5707
- await client.session.revert({
5708
- path: { id: sessionID },
5709
- body: { messageID: pair.userMessageID },
5710
- query: { directory }
5711
- });
5712
- fallbackState.revertAttempt++;
5713
- fallbackState.lastRevertedMessageID = pair.userMessageID;
5714
- const retryState = autoCompactState.retryStateBySession.get(sessionID);
5715
- if (retryState) {
5716
- retryState.attempt = 0;
5717
- }
5718
- return true;
5719
- } catch {
5720
- return false;
5721
- }
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`;
5722
5784
  }
5723
5785
  async function getLastAssistant(sessionID, client, directory) {
5724
5786
  try {
@@ -5747,71 +5809,133 @@ function clearSessionState(autoCompactState, sessionID) {
5747
5809
  autoCompactState.errorDataBySession.delete(sessionID);
5748
5810
  autoCompactState.retryStateBySession.delete(sessionID);
5749
5811
  autoCompactState.fallbackStateBySession.delete(sessionID);
5812
+ autoCompactState.truncateStateBySession.delete(sessionID);
5813
+ autoCompactState.compactionInProgress.delete(sessionID);
5750
5814
  }
5751
5815
  async function executeCompact(sessionID, msg, autoCompactState, client, directory) {
5752
- const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5753
- if (!shouldRetry(retryState)) {
5754
- const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
5755
- if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
5756
- const reverted = await executeRevertFallback(sessionID, autoCompactState, client, directory);
5757
- 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;
5758
5828
  await client.tui.showToast({
5759
5829
  body: {
5760
- title: "Recovery Attempt",
5761
- message: "Message removed. Retrying compaction...",
5762
- variant: "info",
5830
+ title: "Truncating Large Output",
5831
+ message: `Truncated ${result.toolName} (${formatBytes(result.originalSize ?? 0)}). Retrying...`,
5832
+ variant: "warning",
5763
5833
  duration: 3000
5764
5834
  }
5765
5835
  }).catch(() => {});
5766
- setTimeout(() => {
5767
- executeCompact(sessionID, msg, autoCompactState, client, directory);
5768
- }, 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);
5769
5846
  return;
5770
5847
  }
5771
5848
  }
5772
- clearSessionState(autoCompactState, sessionID);
5773
- await client.tui.showToast({
5774
- body: {
5775
- title: "Auto Compact Failed",
5776
- message: `Failed after ${RETRY_CONFIG.maxAttempts} retries and ${FALLBACK_CONFIG.maxRevertAttempts} message removals. Please start a new session.`,
5777
- variant: "error",
5778
- duration: 5000
5779
- }
5780
- }).catch(() => {});
5781
- return;
5782
5849
  }
5783
- retryState.attempt++;
5784
- retryState.lastAttemptTime = Date.now();
5785
- try {
5850
+ const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5851
+ if (retryState.attempt < RETRY_CONFIG.maxAttempts) {
5852
+ retryState.attempt++;
5853
+ retryState.lastAttemptTime = Date.now();
5786
5854
  const providerID = msg.providerID;
5787
5855
  const modelID = msg.modelID;
5788
5856
  if (providerID && modelID) {
5789
- await client.session.summarize({
5790
- path: { id: sessionID },
5791
- body: { providerID, modelID },
5792
- query: { directory }
5793
- });
5794
- clearSessionState(autoCompactState, sessionID);
5795
- setTimeout(async () => {
5796
- try {
5797
- await client.tui.submitPrompt({ query: { directory } });
5798
- } catch {}
5799
- }, 500);
5800
- }
5801
- } catch {
5802
- const delay = calculateRetryDelay(retryState.attempt);
5803
- await client.tui.showToast({
5804
- body: {
5805
- title: "Auto Compact Retry",
5806
- message: `Attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts} failed. Retrying in ${Math.round(delay / 1000)}s...`,
5807
- variant: "warning",
5808
- 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;
5809
5890
  }
5810
- }).catch(() => {});
5811
- setTimeout(() => {
5812
- executeCompact(sessionID, msg, autoCompactState, client, directory);
5813
- }, delay);
5891
+ }
5814
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
+ }
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(() => {});
5815
5939
  }
5816
5940
 
5817
5941
  // src/hooks/anthropic-auto-compact/index.ts
@@ -5820,7 +5944,9 @@ function createAutoCompactState() {
5820
5944
  pendingCompact: new Set,
5821
5945
  errorDataBySession: new Map,
5822
5946
  retryStateBySession: new Map,
5823
- fallbackStateBySession: new Map
5947
+ fallbackStateBySession: new Map,
5948
+ truncateStateBySession: new Map,
5949
+ compactionInProgress: new Set
5824
5950
  };
5825
5951
  }
5826
5952
  function createAnthropicAutoCompactHook(ctx) {
@@ -5834,6 +5960,8 @@ function createAnthropicAutoCompactHook(ctx) {
5834
5960
  autoCompactState.errorDataBySession.delete(sessionInfo.id);
5835
5961
  autoCompactState.retryStateBySession.delete(sessionInfo.id);
5836
5962
  autoCompactState.fallbackStateBySession.delete(sessionInfo.id);
5963
+ autoCompactState.truncateStateBySession.delete(sessionInfo.id);
5964
+ autoCompactState.compactionInProgress.delete(sessionInfo.id);
5837
5965
  }
5838
5966
  return;
5839
5967
  }
@@ -5845,6 +5973,25 @@ function createAnthropicAutoCompactHook(ctx) {
5845
5973
  if (parsed) {
5846
5974
  autoCompactState.pendingCompact.add(sessionID);
5847
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
+ }
5848
5995
  }
5849
5996
  return;
5850
5997
  }
@@ -6177,8 +6324,8 @@ function createThinkModeHook() {
6177
6324
  }
6178
6325
  // src/hooks/claude-code-hooks/config.ts
6179
6326
  import { homedir as homedir4 } from "os";
6180
- import { join as join17 } from "path";
6181
- import { existsSync as existsSync13 } from "fs";
6327
+ import { join as join18 } from "path";
6328
+ import { existsSync as existsSync14 } from "fs";
6182
6329
  function normalizeHookMatcher(raw) {
6183
6330
  return {
6184
6331
  matcher: raw.matcher ?? raw.pattern ?? "*",
@@ -6203,11 +6350,11 @@ function normalizeHooksConfig(raw) {
6203
6350
  function getClaudeSettingsPaths(customPath) {
6204
6351
  const home = homedir4();
6205
6352
  const paths = [
6206
- join17(home, ".claude", "settings.json"),
6207
- join17(process.cwd(), ".claude", "settings.json"),
6208
- 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")
6209
6356
  ];
6210
- if (customPath && existsSync13(customPath)) {
6357
+ if (customPath && existsSync14(customPath)) {
6211
6358
  paths.unshift(customPath);
6212
6359
  }
6213
6360
  return paths;
@@ -6231,7 +6378,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6231
6378
  const paths = getClaudeSettingsPaths(customSettingsPath);
6232
6379
  let mergedConfig = {};
6233
6380
  for (const settingsPath of paths) {
6234
- if (existsSync13(settingsPath)) {
6381
+ if (existsSync14(settingsPath)) {
6235
6382
  try {
6236
6383
  const content = await Bun.file(settingsPath).text();
6237
6384
  const settings = JSON.parse(content);
@@ -6248,15 +6395,15 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6248
6395
  }
6249
6396
 
6250
6397
  // src/hooks/claude-code-hooks/config-loader.ts
6251
- import { existsSync as existsSync14 } from "fs";
6398
+ import { existsSync as existsSync15 } from "fs";
6252
6399
  import { homedir as homedir5 } from "os";
6253
- import { join as join18 } from "path";
6254
- 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");
6255
6402
  function getProjectConfigPath() {
6256
- return join18(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6403
+ return join19(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6257
6404
  }
6258
6405
  async function loadConfigFromPath(path3) {
6259
- if (!existsSync14(path3)) {
6406
+ if (!existsSync15(path3)) {
6260
6407
  return null;
6261
6408
  }
6262
6409
  try {
@@ -6435,16 +6582,16 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
6435
6582
  }
6436
6583
 
6437
6584
  // src/hooks/claude-code-hooks/transcript.ts
6438
- import { join as join19 } from "path";
6439
- 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";
6440
6587
  import { homedir as homedir6, tmpdir as tmpdir5 } from "os";
6441
6588
  import { randomUUID } from "crypto";
6442
- var TRANSCRIPT_DIR = join19(homedir6(), ".claude", "transcripts");
6589
+ var TRANSCRIPT_DIR = join20(homedir6(), ".claude", "transcripts");
6443
6590
  function getTranscriptPath(sessionId) {
6444
- return join19(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6591
+ return join20(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6445
6592
  }
6446
6593
  function ensureTranscriptDir() {
6447
- if (!existsSync15(TRANSCRIPT_DIR)) {
6594
+ if (!existsSync16(TRANSCRIPT_DIR)) {
6448
6595
  mkdirSync6(TRANSCRIPT_DIR, { recursive: true });
6449
6596
  }
6450
6597
  }
@@ -6531,8 +6678,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6531
6678
  }
6532
6679
  };
6533
6680
  entries.push(JSON.stringify(currentEntry));
6534
- const tempPath = join19(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6535
- writeFileSync5(tempPath, entries.join(`
6681
+ const tempPath = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6682
+ writeFileSync6(tempPath, entries.join(`
6536
6683
  `) + `
6537
6684
  `);
6538
6685
  return tempPath;
@@ -6551,8 +6698,8 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6551
6698
  ]
6552
6699
  }
6553
6700
  };
6554
- const tempPath = join19(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6555
- writeFileSync5(tempPath, JSON.stringify(currentEntry) + `
6701
+ const tempPath = join20(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6702
+ writeFileSync6(tempPath, JSON.stringify(currentEntry) + `
6556
6703
  `);
6557
6704
  return tempPath;
6558
6705
  } catch {
@@ -6763,11 +6910,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
6763
6910
  }
6764
6911
 
6765
6912
  // src/hooks/claude-code-hooks/todo.ts
6766
- import { join as join20 } from "path";
6913
+ import { join as join21 } from "path";
6767
6914
  import { homedir as homedir7 } from "os";
6768
- var TODO_DIR = join20(homedir7(), ".claude", "todos");
6915
+ var TODO_DIR = join21(homedir7(), ".claude", "todos");
6769
6916
  function getTodoPath(sessionId) {
6770
- return join20(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
6917
+ return join21(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
6771
6918
  }
6772
6919
 
6773
6920
  // src/hooks/claude-code-hooks/stop.ts
@@ -6919,6 +7066,7 @@ function createClaudeCodeHooksHook(ctx, config = {}) {
6919
7066
  const hookContent = result.messages.join(`
6920
7067
 
6921
7068
  `);
7069
+ log(`[claude-code-hooks] Injecting ${result.messages.length} hook messages`, { sessionID: input.sessionID, contentLength: hookContent.length });
6922
7070
  const message = output.message;
6923
7071
  const success = injectHookMessage(input.sessionID, hookContent, {
6924
7072
  agent: message.agent,
@@ -7098,23 +7246,23 @@ ${result.message}`;
7098
7246
  };
7099
7247
  }
7100
7248
  // src/hooks/rules-injector/index.ts
7101
- import { readFileSync as readFileSync9 } from "fs";
7249
+ import { readFileSync as readFileSync10 } from "fs";
7102
7250
  import { homedir as homedir8 } from "os";
7103
7251
  import { relative as relative3, resolve as resolve4 } from "path";
7104
7252
 
7105
7253
  // src/hooks/rules-injector/finder.ts
7106
7254
  import {
7107
- existsSync as existsSync16,
7108
- readdirSync as readdirSync4,
7255
+ existsSync as existsSync17,
7256
+ readdirSync as readdirSync5,
7109
7257
  realpathSync,
7110
7258
  statSync as statSync2
7111
7259
  } from "fs";
7112
- import { dirname as dirname4, join as join22, relative } from "path";
7260
+ import { dirname as dirname4, join as join23, relative } from "path";
7113
7261
 
7114
7262
  // src/hooks/rules-injector/constants.ts
7115
- import { join as join21 } from "path";
7116
- var OPENCODE_STORAGE5 = join21(xdgData2 ?? "", "opencode", "storage");
7117
- 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");
7118
7266
  var PROJECT_MARKERS = [
7119
7267
  ".git",
7120
7268
  "pyproject.toml",
@@ -7141,8 +7289,8 @@ function findProjectRoot(startPath) {
7141
7289
  }
7142
7290
  while (true) {
7143
7291
  for (const marker of PROJECT_MARKERS) {
7144
- const markerPath = join22(current, marker);
7145
- if (existsSync16(markerPath)) {
7292
+ const markerPath = join23(current, marker);
7293
+ if (existsSync17(markerPath)) {
7146
7294
  return current;
7147
7295
  }
7148
7296
  }
@@ -7154,12 +7302,12 @@ function findProjectRoot(startPath) {
7154
7302
  }
7155
7303
  }
7156
7304
  function findRuleFilesRecursive(dir, results) {
7157
- if (!existsSync16(dir))
7305
+ if (!existsSync17(dir))
7158
7306
  return;
7159
7307
  try {
7160
- const entries = readdirSync4(dir, { withFileTypes: true });
7308
+ const entries = readdirSync5(dir, { withFileTypes: true });
7161
7309
  for (const entry of entries) {
7162
- const fullPath = join22(dir, entry.name);
7310
+ const fullPath = join23(dir, entry.name);
7163
7311
  if (entry.isDirectory()) {
7164
7312
  findRuleFilesRecursive(fullPath, results);
7165
7313
  } else if (entry.isFile()) {
@@ -7185,7 +7333,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7185
7333
  let distance = 0;
7186
7334
  while (true) {
7187
7335
  for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
7188
- const ruleDir = join22(currentDir, parent, subdir);
7336
+ const ruleDir = join23(currentDir, parent, subdir);
7189
7337
  const files = [];
7190
7338
  findRuleFilesRecursive(ruleDir, files);
7191
7339
  for (const filePath of files) {
@@ -7209,7 +7357,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7209
7357
  currentDir = parentDir;
7210
7358
  distance++;
7211
7359
  }
7212
- const userRuleDir = join22(homeDir, USER_RULE_DIR);
7360
+ const userRuleDir = join23(homeDir, USER_RULE_DIR);
7213
7361
  const userFiles = [];
7214
7362
  findRuleFilesRecursive(userRuleDir, userFiles);
7215
7363
  for (const filePath of userFiles) {
@@ -7398,22 +7546,22 @@ function mergeGlobs(existing, newValue) {
7398
7546
 
7399
7547
  // src/hooks/rules-injector/storage.ts
7400
7548
  import {
7401
- existsSync as existsSync17,
7549
+ existsSync as existsSync18,
7402
7550
  mkdirSync as mkdirSync7,
7403
- readFileSync as readFileSync8,
7404
- writeFileSync as writeFileSync6,
7551
+ readFileSync as readFileSync9,
7552
+ writeFileSync as writeFileSync7,
7405
7553
  unlinkSync as unlinkSync6
7406
7554
  } from "fs";
7407
- import { join as join23 } from "path";
7555
+ import { join as join24 } from "path";
7408
7556
  function getStoragePath3(sessionID) {
7409
- return join23(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
7557
+ return join24(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
7410
7558
  }
7411
7559
  function loadInjectedRules(sessionID) {
7412
7560
  const filePath = getStoragePath3(sessionID);
7413
- if (!existsSync17(filePath))
7561
+ if (!existsSync18(filePath))
7414
7562
  return { contentHashes: new Set, realPaths: new Set };
7415
7563
  try {
7416
- const content = readFileSync8(filePath, "utf-8");
7564
+ const content = readFileSync9(filePath, "utf-8");
7417
7565
  const data = JSON.parse(content);
7418
7566
  return {
7419
7567
  contentHashes: new Set(data.injectedHashes),
@@ -7424,7 +7572,7 @@ function loadInjectedRules(sessionID) {
7424
7572
  }
7425
7573
  }
7426
7574
  function saveInjectedRules(sessionID, data) {
7427
- if (!existsSync17(RULES_INJECTOR_STORAGE)) {
7575
+ if (!existsSync18(RULES_INJECTOR_STORAGE)) {
7428
7576
  mkdirSync7(RULES_INJECTOR_STORAGE, { recursive: true });
7429
7577
  }
7430
7578
  const storageData = {
@@ -7433,11 +7581,11 @@ function saveInjectedRules(sessionID, data) {
7433
7581
  injectedRealPaths: [...data.realPaths],
7434
7582
  updatedAt: Date.now()
7435
7583
  };
7436
- writeFileSync6(getStoragePath3(sessionID), JSON.stringify(storageData, null, 2));
7584
+ writeFileSync7(getStoragePath3(sessionID), JSON.stringify(storageData, null, 2));
7437
7585
  }
7438
7586
  function clearInjectedRules(sessionID) {
7439
7587
  const filePath = getStoragePath3(sessionID);
7440
- if (existsSync17(filePath)) {
7588
+ if (existsSync18(filePath)) {
7441
7589
  unlinkSync6(filePath);
7442
7590
  }
7443
7591
  }
@@ -7474,7 +7622,7 @@ function createRulesInjectorHook(ctx) {
7474
7622
  if (isDuplicateByRealPath(candidate.realPath, cache2.realPaths))
7475
7623
  continue;
7476
7624
  try {
7477
- const rawContent = readFileSync9(candidate.path, "utf-8");
7625
+ const rawContent = readFileSync10(candidate.path, "utf-8");
7478
7626
  const { metadata, body } = parseRuleFrontmatter(rawContent);
7479
7627
  const matchResult = shouldApplyRule(metadata, filePath, projectRoot);
7480
7628
  if (!matchResult.applies)
@@ -7839,18 +7987,18 @@ async function showVersionToast(ctx, version) {
7839
7987
  }
7840
7988
  // src/hooks/agent-usage-reminder/storage.ts
7841
7989
  import {
7842
- existsSync as existsSync20,
7990
+ existsSync as existsSync21,
7843
7991
  mkdirSync as mkdirSync8,
7844
- readFileSync as readFileSync12,
7845
- writeFileSync as writeFileSync8,
7992
+ readFileSync as readFileSync13,
7993
+ writeFileSync as writeFileSync9,
7846
7994
  unlinkSync as unlinkSync7
7847
7995
  } from "fs";
7848
- import { join as join28 } from "path";
7996
+ import { join as join29 } from "path";
7849
7997
 
7850
7998
  // src/hooks/agent-usage-reminder/constants.ts
7851
- import { join as join27 } from "path";
7852
- var OPENCODE_STORAGE6 = join27(xdgData2 ?? "", "opencode", "storage");
7853
- 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");
7854
8002
  var TARGET_TOOLS = new Set([
7855
8003
  "grep",
7856
8004
  "safe_grep",
@@ -7895,29 +8043,29 @@ ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
7895
8043
 
7896
8044
  // src/hooks/agent-usage-reminder/storage.ts
7897
8045
  function getStoragePath4(sessionID) {
7898
- return join28(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
8046
+ return join29(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
7899
8047
  }
7900
8048
  function loadAgentUsageState(sessionID) {
7901
8049
  const filePath = getStoragePath4(sessionID);
7902
- if (!existsSync20(filePath))
8050
+ if (!existsSync21(filePath))
7903
8051
  return null;
7904
8052
  try {
7905
- const content = readFileSync12(filePath, "utf-8");
8053
+ const content = readFileSync13(filePath, "utf-8");
7906
8054
  return JSON.parse(content);
7907
8055
  } catch {
7908
8056
  return null;
7909
8057
  }
7910
8058
  }
7911
8059
  function saveAgentUsageState(state) {
7912
- if (!existsSync20(AGENT_USAGE_REMINDER_STORAGE)) {
8060
+ if (!existsSync21(AGENT_USAGE_REMINDER_STORAGE)) {
7913
8061
  mkdirSync8(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
7914
8062
  }
7915
8063
  const filePath = getStoragePath4(state.sessionID);
7916
- writeFileSync8(filePath, JSON.stringify(state, null, 2));
8064
+ writeFileSync9(filePath, JSON.stringify(state, null, 2));
7917
8065
  }
7918
8066
  function clearAgentUsageState(sessionID) {
7919
8067
  const filePath = getStoragePath4(sessionID);
7920
- if (existsSync20(filePath)) {
8068
+ if (existsSync21(filePath)) {
7921
8069
  unlinkSync7(filePath);
7922
8070
  }
7923
8071
  }
@@ -8081,6 +8229,7 @@ function createKeywordDetectorHook() {
8081
8229
  const message = output.message;
8082
8230
  const context = messages.join(`
8083
8231
  `);
8232
+ log(`[keyword-detector] Injecting context for ${messages.length} keywords`, { sessionID: input.sessionID, contextLength: context.length });
8084
8233
  const success = injectHookMessage(input.sessionID, context, {
8085
8234
  agent: message.agent,
8086
8235
  model: message.model,
@@ -8138,18 +8287,18 @@ function createNonInteractiveEnvHook(_ctx) {
8138
8287
  }
8139
8288
  // src/hooks/interactive-bash-session/storage.ts
8140
8289
  import {
8141
- existsSync as existsSync21,
8290
+ existsSync as existsSync22,
8142
8291
  mkdirSync as mkdirSync9,
8143
- readFileSync as readFileSync13,
8144
- writeFileSync as writeFileSync9,
8292
+ readFileSync as readFileSync14,
8293
+ writeFileSync as writeFileSync10,
8145
8294
  unlinkSync as unlinkSync8
8146
8295
  } from "fs";
8147
- import { join as join30 } from "path";
8296
+ import { join as join31 } from "path";
8148
8297
 
8149
8298
  // src/hooks/interactive-bash-session/constants.ts
8150
- import { join as join29 } from "path";
8151
- var OPENCODE_STORAGE7 = join29(xdgData2 ?? "", "opencode", "storage");
8152
- 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");
8153
8302
  var OMO_SESSION_PREFIX = "omo-";
8154
8303
  function buildSessionReminderMessage(sessions) {
8155
8304
  if (sessions.length === 0)
@@ -8161,14 +8310,14 @@ function buildSessionReminderMessage(sessions) {
8161
8310
 
8162
8311
  // src/hooks/interactive-bash-session/storage.ts
8163
8312
  function getStoragePath5(sessionID) {
8164
- return join30(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
8313
+ return join31(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
8165
8314
  }
8166
8315
  function loadInteractiveBashSessionState(sessionID) {
8167
8316
  const filePath = getStoragePath5(sessionID);
8168
- if (!existsSync21(filePath))
8317
+ if (!existsSync22(filePath))
8169
8318
  return null;
8170
8319
  try {
8171
- const content = readFileSync13(filePath, "utf-8");
8320
+ const content = readFileSync14(filePath, "utf-8");
8172
8321
  const serialized = JSON.parse(content);
8173
8322
  return {
8174
8323
  sessionID: serialized.sessionID,
@@ -8180,7 +8329,7 @@ function loadInteractiveBashSessionState(sessionID) {
8180
8329
  }
8181
8330
  }
8182
8331
  function saveInteractiveBashSessionState(state) {
8183
- if (!existsSync21(INTERACTIVE_BASH_SESSION_STORAGE)) {
8332
+ if (!existsSync22(INTERACTIVE_BASH_SESSION_STORAGE)) {
8184
8333
  mkdirSync9(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
8185
8334
  }
8186
8335
  const filePath = getStoragePath5(state.sessionID);
@@ -8189,11 +8338,11 @@ function saveInteractiveBashSessionState(state) {
8189
8338
  tmuxSessions: Array.from(state.tmuxSessions),
8190
8339
  updatedAt: state.updatedAt
8191
8340
  };
8192
- writeFileSync9(filePath, JSON.stringify(serialized, null, 2));
8341
+ writeFileSync10(filePath, JSON.stringify(serialized, null, 2));
8193
8342
  }
8194
8343
  function clearInteractiveBashSessionState(sessionID) {
8195
8344
  const filePath = getStoragePath5(sessionID);
8196
- if (existsSync21(filePath)) {
8345
+ if (existsSync22(filePath)) {
8197
8346
  unlinkSync8(filePath);
8198
8347
  }
8199
8348
  }
@@ -8370,6 +8519,73 @@ function createInteractiveBashSessionHook(_ctx) {
8370
8519
  event: eventHandler
8371
8520
  };
8372
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
+ }
8373
8589
  // src/auth/antigravity/constants.ts
8374
8590
  var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
8375
8591
  var ANTIGRAVITY_CLIENT_SECRET = "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";
@@ -8677,15 +8893,19 @@ function formatTokenForStorage(refreshToken, projectId, managedProjectId) {
8677
8893
  }
8678
8894
  // src/auth/antigravity/project.ts
8679
8895
  var projectContextCache = new Map;
8896
+ function debugLog4(message) {
8897
+ if (process.env.ANTIGRAVITY_DEBUG === "1") {
8898
+ console.log(`[antigravity-project] ${message}`);
8899
+ }
8900
+ }
8680
8901
  var CODE_ASSIST_METADATA = {
8681
8902
  ideType: "IDE_UNSPECIFIED",
8682
8903
  platform: "PLATFORM_UNSPECIFIED",
8683
8904
  pluginType: "GEMINI"
8684
8905
  };
8685
8906
  function extractProjectId(project) {
8686
- if (!project) {
8907
+ if (!project)
8687
8908
  return;
8688
- }
8689
8909
  if (typeof project === "string") {
8690
8910
  const trimmed = project.trim();
8691
8911
  return trimmed || undefined;
@@ -8699,10 +8919,31 @@ function extractProjectId(project) {
8699
8919
  }
8700
8920
  return;
8701
8921
  }
8702
- async function callLoadCodeAssistAPI(accessToken) {
8703
- const requestBody = {
8704
- metadata: CODE_ASSIST_METADATA
8705
- };
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;
8706
8947
  const headers = {
8707
8948
  Authorization: `Bearer ${accessToken}`,
8708
8949
  "Content-Type": "application/json",
@@ -8712,6 +8953,7 @@ async function callLoadCodeAssistAPI(accessToken) {
8712
8953
  };
8713
8954
  for (const baseEndpoint of ANTIGRAVITY_ENDPOINT_FALLBACKS) {
8714
8955
  const url = `${baseEndpoint}/${ANTIGRAVITY_API_VERSION}:loadCodeAssist`;
8956
+ debugLog4(`[loadCodeAssist] Trying: ${url}`);
8715
8957
  try {
8716
8958
  const response = await fetch(url, {
8717
8959
  method: "POST",
@@ -8719,30 +8961,130 @@ async function callLoadCodeAssistAPI(accessToken) {
8719
8961
  body: JSON.stringify(requestBody)
8720
8962
  });
8721
8963
  if (!response.ok) {
8964
+ debugLog4(`[loadCodeAssist] Failed: ${response.status} ${response.statusText}`);
8722
8965
  continue;
8723
8966
  }
8724
8967
  const data = await response.json();
8968
+ debugLog4(`[loadCodeAssist] Success: ${JSON.stringify(data)}`);
8725
8969
  return data;
8726
- } catch {
8970
+ } catch (err) {
8971
+ debugLog4(`[loadCodeAssist] Error: ${err}`);
8727
8972
  continue;
8728
8973
  }
8729
8974
  }
8975
+ debugLog4(`[loadCodeAssist] All endpoints failed`);
8730
8976
  return null;
8731
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
+ }
8732
9040
  async function fetchProjectContext(accessToken) {
9041
+ debugLog4(`[fetchProjectContext] Starting...`);
8733
9042
  const cached = projectContextCache.get(accessToken);
8734
9043
  if (cached) {
9044
+ debugLog4(`[fetchProjectContext] Returning cached result: ${JSON.stringify(cached)}`);
8735
9045
  return cached;
8736
9046
  }
8737
- const response = await callLoadCodeAssistAPI(accessToken);
8738
- const projectId = response ? extractProjectId(response.cloudaicompanionProject) : undefined;
8739
- const result = {
8740
- cloudaicompanionProject: projectId || ""
8741
- };
8742
- 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
+ };
8743
9082
  projectContextCache.set(accessToken, result);
9083
+ debugLog4(`[fetchProjectContext] Got managed project ID: ${managedProjectId}`);
9084
+ return result;
8744
9085
  }
8745
- return result;
9086
+ debugLog4(`[fetchProjectContext] Failed to get managed project ID, returning empty`);
9087
+ return { cloudaicompanionProject: "" };
8746
9088
  }
8747
9089
  function clearProjectContextCache(accessToken) {
8748
9090
  if (accessToken) {
@@ -8806,21 +9148,21 @@ function wrapRequestBody(body, projectId, modelName, sessionId) {
8806
9148
  }
8807
9149
  };
8808
9150
  }
8809
- function debugLog4(message) {
9151
+ function debugLog5(message) {
8810
9152
  if (process.env.ANTIGRAVITY_DEBUG === "1") {
8811
9153
  console.log(`[antigravity-request] ${message}`);
8812
9154
  }
8813
9155
  }
8814
9156
  function injectThoughtSignatureIntoFunctionCalls(body, signature) {
8815
9157
  const effectiveSignature = signature || SKIP_THOUGHT_SIGNATURE_VALIDATOR;
8816
- debugLog4(`[TSIG][INJECT] signature=${effectiveSignature.substring(0, 30)}... (${signature ? "provided" : "default"})`);
8817
- 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(", ")}`);
8818
9160
  const contents = body.contents;
8819
9161
  if (!contents || !Array.isArray(contents)) {
8820
- debugLog4(`[TSIG][INJECT] No contents array! Has messages: ${!!body.messages}`);
9162
+ debugLog5(`[TSIG][INJECT] No contents array! Has messages: ${!!body.messages}`);
8821
9163
  return body;
8822
9164
  }
8823
- debugLog4(`[TSIG][INJECT] Found ${contents.length} content blocks`);
9165
+ debugLog5(`[TSIG][INJECT] Found ${contents.length} content blocks`);
8824
9166
  let injectedCount = 0;
8825
9167
  const modifiedContents = contents.map((content) => {
8826
9168
  if (!content.parts || !Array.isArray(content.parts)) {
@@ -8838,7 +9180,7 @@ function injectThoughtSignatureIntoFunctionCalls(body, signature) {
8838
9180
  });
8839
9181
  return { ...content, parts: modifiedParts };
8840
9182
  });
8841
- debugLog4(`[TSIG][INJECT] injected signature into ${injectedCount} functionCall(s)`);
9183
+ debugLog5(`[TSIG][INJECT] injected signature into ${injectedCount} functionCall(s)`);
8842
9184
  return { ...body, contents: modifiedContents };
8843
9185
  }
8844
9186
  function isStreamingRequest(url, body) {
@@ -9342,13 +9684,13 @@ function getOrCreateSessionId(fetchInstanceId, sessionId) {
9342
9684
  return newSessionId;
9343
9685
  }
9344
9686
  // src/auth/antigravity/message-converter.ts
9345
- function debugLog5(message) {
9687
+ function debugLog6(message) {
9346
9688
  if (process.env.ANTIGRAVITY_DEBUG === "1") {
9347
9689
  console.log(`[antigravity-converter] ${message}`);
9348
9690
  }
9349
9691
  }
9350
9692
  function convertOpenAIToGemini(messages, thoughtSignature) {
9351
- debugLog5(`Converting ${messages.length} messages, signature: ${thoughtSignature ? "present" : "none"}`);
9693
+ debugLog6(`Converting ${messages.length} messages, signature: ${thoughtSignature ? "present" : "none"}`);
9352
9694
  const contents = [];
9353
9695
  for (const msg of messages) {
9354
9696
  if (msg.role === "system") {
@@ -9383,7 +9725,7 @@ function convertOpenAIToGemini(messages, thoughtSignature) {
9383
9725
  }
9384
9726
  };
9385
9727
  part.thoughtSignature = thoughtSignature || SKIP_THOUGHT_SIGNATURE_VALIDATOR;
9386
- debugLog5(`Injected signature into functionCall: ${toolCall.function.name} (${thoughtSignature ? "provided" : "default"})`);
9728
+ debugLog6(`Injected signature into functionCall: ${toolCall.function.name} (${thoughtSignature ? "provided" : "default"})`);
9387
9729
  parts.push(part);
9388
9730
  }
9389
9731
  }
@@ -9412,7 +9754,7 @@ function convertOpenAIToGemini(messages, thoughtSignature) {
9412
9754
  continue;
9413
9755
  }
9414
9756
  }
9415
- debugLog5(`Converted to ${contents.length} content blocks`);
9757
+ debugLog6(`Converted to ${contents.length} content blocks`);
9416
9758
  return contents;
9417
9759
  }
9418
9760
  function convertContentToParts(content) {
@@ -9448,7 +9790,7 @@ function hasOpenAIMessages(body) {
9448
9790
  }
9449
9791
  function convertRequestBody(body, thoughtSignature) {
9450
9792
  if (!hasOpenAIMessages(body)) {
9451
- debugLog5("No messages array found, returning body as-is");
9793
+ debugLog6("No messages array found, returning body as-is");
9452
9794
  return body;
9453
9795
  }
9454
9796
  const messages = body.messages;
@@ -9456,11 +9798,11 @@ function convertRequestBody(body, thoughtSignature) {
9456
9798
  const converted = { ...body };
9457
9799
  delete converted.messages;
9458
9800
  converted.contents = contents;
9459
- debugLog5(`Converted body: messages \u2192 contents (${contents.length} blocks)`);
9801
+ debugLog6(`Converted body: messages \u2192 contents (${contents.length} blocks)`);
9460
9802
  return converted;
9461
9803
  }
9462
9804
  // src/auth/antigravity/fetch.ts
9463
- function debugLog6(message) {
9805
+ function debugLog7(message) {
9464
9806
  if (process.env.ANTIGRAVITY_DEBUG === "1") {
9465
9807
  console.log(`[antigravity-fetch] ${message}`);
9466
9808
  }
@@ -9483,7 +9825,7 @@ var GCP_PERMISSION_ERROR_PATTERNS = [
9483
9825
  function isGcpPermissionError(text) {
9484
9826
  return GCP_PERMISSION_ERROR_PATTERNS.some((pattern) => text.includes(pattern));
9485
9827
  }
9486
- function calculateRetryDelay2(attempt) {
9828
+ function calculateRetryDelay(attempt) {
9487
9829
  return Math.min(200 * Math.pow(2, attempt), 2000);
9488
9830
  }
9489
9831
  async function isRetryableResponse(response) {
@@ -9493,7 +9835,7 @@ async function isRetryableResponse(response) {
9493
9835
  try {
9494
9836
  const text = await response.clone().text();
9495
9837
  if (text.includes("SUBSCRIPTION_REQUIRED") || text.includes("Gemini Code Assist license")) {
9496
- debugLog6(`[RETRY] 403 SUBSCRIPTION_REQUIRED detected, will retry with next endpoint`);
9838
+ debugLog7(`[RETRY] 403 SUBSCRIPTION_REQUIRED detected, will retry with next endpoint`);
9497
9839
  return true;
9498
9840
  }
9499
9841
  } catch {}
@@ -9502,11 +9844,11 @@ async function isRetryableResponse(response) {
9502
9844
  }
9503
9845
  async function attemptFetch(options) {
9504
9846
  const { endpoint, url, init, accessToken, projectId, sessionId, modelName, thoughtSignature } = options;
9505
- debugLog6(`Trying endpoint: ${endpoint}`);
9847
+ debugLog7(`Trying endpoint: ${endpoint}`);
9506
9848
  try {
9507
9849
  const rawBody = init.body;
9508
9850
  if (rawBody !== undefined && typeof rawBody !== "string") {
9509
- debugLog6(`Non-string body detected (${typeof rawBody}), signaling pass-through`);
9851
+ debugLog7(`Non-string body detected (${typeof rawBody}), signaling pass-through`);
9510
9852
  return "pass-through";
9511
9853
  }
9512
9854
  let parsedBody = {};
@@ -9517,13 +9859,13 @@ async function attemptFetch(options) {
9517
9859
  parsedBody = {};
9518
9860
  }
9519
9861
  }
9520
- debugLog6(`[BODY] Keys: ${Object.keys(parsedBody).join(", ")}`);
9521
- 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}`);
9522
9864
  if (parsedBody.contents) {
9523
9865
  const contents = parsedBody.contents;
9524
- debugLog6(`[BODY] contents length: ${contents.length}`);
9866
+ debugLog7(`[BODY] contents length: ${contents.length}`);
9525
9867
  contents.forEach((c, i) => {
9526
- 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)}`);
9527
9869
  });
9528
9870
  }
9529
9871
  if (parsedBody.tools && Array.isArray(parsedBody.tools)) {
@@ -9533,9 +9875,9 @@ async function attemptFetch(options) {
9533
9875
  }
9534
9876
  }
9535
9877
  if (hasOpenAIMessages(parsedBody)) {
9536
- debugLog6(`[CONVERT] Converting OpenAI messages to Gemini contents`);
9878
+ debugLog7(`[CONVERT] Converting OpenAI messages to Gemini contents`);
9537
9879
  parsedBody = convertRequestBody(parsedBody, thoughtSignature);
9538
- debugLog6(`[CONVERT] After conversion - Has contents: ${!!parsedBody.contents}`);
9880
+ debugLog7(`[CONVERT] After conversion - Has contents: ${!!parsedBody.contents}`);
9539
9881
  }
9540
9882
  const transformed = transformRequest({
9541
9883
  url,
@@ -9547,7 +9889,7 @@ async function attemptFetch(options) {
9547
9889
  endpointOverride: endpoint,
9548
9890
  thoughtSignature
9549
9891
  });
9550
- debugLog6(`[REQ] streaming=${transformed.streaming}, url=${transformed.url}`);
9892
+ debugLog7(`[REQ] streaming=${transformed.streaming}, url=${transformed.url}`);
9551
9893
  const maxPermissionRetries = 10;
9552
9894
  for (let attempt = 0;attempt <= maxPermissionRetries; attempt++) {
9553
9895
  const response = await fetch(transformed.url, {
@@ -9556,9 +9898,9 @@ async function attemptFetch(options) {
9556
9898
  body: JSON.stringify(transformed.body),
9557
9899
  signal: init.signal
9558
9900
  });
9559
- 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}`);
9560
9902
  if (response.status === 401) {
9561
- debugLog6(`[401] Unauthorized response detected, signaling token refresh needed`);
9903
+ debugLog7(`[401] Unauthorized response detected, signaling token refresh needed`);
9562
9904
  return "needs-refresh";
9563
9905
  }
9564
9906
  if (response.status === 403) {
@@ -9566,24 +9908,24 @@ async function attemptFetch(options) {
9566
9908
  const text = await response.clone().text();
9567
9909
  if (isGcpPermissionError(text)) {
9568
9910
  if (attempt < maxPermissionRetries) {
9569
- const delay = calculateRetryDelay2(attempt);
9570
- 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`);
9571
9913
  await new Promise((resolve5) => setTimeout(resolve5, delay));
9572
9914
  continue;
9573
9915
  }
9574
- debugLog6(`[RETRY] GCP permission error, max retries exceeded`);
9916
+ debugLog7(`[RETRY] GCP permission error, max retries exceeded`);
9575
9917
  }
9576
9918
  } catch {}
9577
9919
  }
9578
9920
  if (!response.ok && await isRetryableResponse(response)) {
9579
- debugLog6(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
9921
+ debugLog7(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
9580
9922
  return null;
9581
9923
  }
9582
9924
  return response;
9583
9925
  }
9584
9926
  return null;
9585
9927
  } catch (error) {
9586
- 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`);
9587
9929
  return null;
9588
9930
  }
9589
9931
  }
@@ -9618,17 +9960,17 @@ async function transformResponseWithThinking(response, modelName, fetchInstanceI
9618
9960
  }
9619
9961
  try {
9620
9962
  const text = await result.response.clone().text();
9621
- debugLog6(`[TSIG][RESP] Response text length: ${text.length}`);
9963
+ debugLog7(`[TSIG][RESP] Response text length: ${text.length}`);
9622
9964
  const parsed = JSON.parse(text);
9623
- debugLog6(`[TSIG][RESP] Parsed keys: ${Object.keys(parsed).join(", ")}`);
9624
- 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}`);
9625
9967
  const signature = extractSignatureFromResponse(parsed);
9626
- debugLog6(`[TSIG][RESP] Signature extracted: ${signature ? signature.substring(0, 30) + "..." : "NONE"}`);
9968
+ debugLog7(`[TSIG][RESP] Signature extracted: ${signature ? signature.substring(0, 30) + "..." : "NONE"}`);
9627
9969
  if (signature) {
9628
9970
  setThoughtSignature(fetchInstanceId, signature);
9629
- debugLog6(`[TSIG][STORE] Stored signature for ${fetchInstanceId}`);
9971
+ debugLog7(`[TSIG][STORE] Stored signature for ${fetchInstanceId}`);
9630
9972
  } else {
9631
- debugLog6(`[TSIG][WARN] No signature found in response!`);
9973
+ debugLog7(`[TSIG][WARN] No signature found in response!`);
9632
9974
  }
9633
9975
  if (shouldIncludeThinking(modelName)) {
9634
9976
  const thinkingResult = extractThinkingBlocks(parsed);
@@ -9649,7 +9991,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9649
9991
  let cachedProjectId = null;
9650
9992
  const fetchInstanceId = crypto.randomUUID();
9651
9993
  return async (url, init = {}) => {
9652
- debugLog6(`Intercepting request to: ${url}`);
9994
+ debugLog7(`Intercepting request to: ${url}`);
9653
9995
  const auth = await getAuth();
9654
9996
  if (!auth.access || !auth.refresh) {
9655
9997
  throw new Error("Antigravity: No authentication tokens available");
@@ -9668,7 +10010,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9668
10010
  cachedTokens.refresh_token = refreshParts.refreshToken;
9669
10011
  }
9670
10012
  if (isTokenExpired(cachedTokens)) {
9671
- debugLog6("Token expired, refreshing...");
10013
+ debugLog7("Token expired, refreshing...");
9672
10014
  try {
9673
10015
  const newTokens = await refreshAccessToken(refreshParts.refreshToken, clientId, clientSecret);
9674
10016
  cachedTokens = {
@@ -9685,7 +10027,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9685
10027
  refresh: formattedRefresh,
9686
10028
  expires: Date.now() + newTokens.expires_in * 1000
9687
10029
  });
9688
- debugLog6("Token refreshed successfully");
10030
+ debugLog7("Token refreshed successfully");
9689
10031
  } catch (error) {
9690
10032
  throw new Error(`Antigravity: Token refresh failed: ${error instanceof Error ? error.message : "Unknown error"}`);
9691
10033
  }
@@ -9693,10 +10035,10 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9693
10035
  if (!cachedProjectId) {
9694
10036
  const projectContext = await fetchProjectContext(cachedTokens.access_token);
9695
10037
  cachedProjectId = projectContext.cloudaicompanionProject || "";
9696
- debugLog6(`[PROJECT] Fetched project ID: "${cachedProjectId}"`);
10038
+ debugLog7(`[PROJECT] Fetched project ID: "${cachedProjectId}"`);
9697
10039
  }
9698
10040
  const projectId = cachedProjectId;
9699
- debugLog6(`[PROJECT] Using project ID: "${projectId}"`);
10041
+ debugLog7(`[PROJECT] Using project ID: "${projectId}"`);
9700
10042
  let modelName;
9701
10043
  if (init.body) {
9702
10044
  try {
@@ -9709,7 +10051,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9709
10051
  const maxEndpoints = Math.min(ANTIGRAVITY_ENDPOINT_FALLBACKS.length, 3);
9710
10052
  const sessionId = getOrCreateSessionId(fetchInstanceId);
9711
10053
  const thoughtSignature = getThoughtSignature(fetchInstanceId);
9712
- 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"}`);
9713
10055
  let hasRefreshedFor401 = false;
9714
10056
  const executeWithEndpoints = async () => {
9715
10057
  for (let i = 0;i < maxEndpoints; i++) {
@@ -9725,7 +10067,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9725
10067
  thoughtSignature
9726
10068
  });
9727
10069
  if (response === "pass-through") {
9728
- debugLog6("Non-string body detected, passing through with auth headers");
10070
+ debugLog7("Non-string body detected, passing through with auth headers");
9729
10071
  const headersWithAuth = {
9730
10072
  ...init.headers,
9731
10073
  Authorization: `Bearer ${cachedTokens.access_token}`
@@ -9734,7 +10076,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9734
10076
  }
9735
10077
  if (response === "needs-refresh") {
9736
10078
  if (hasRefreshedFor401) {
9737
- debugLog6("[401] Already refreshed once, returning unauthorized error");
10079
+ debugLog7("[401] Already refreshed once, returning unauthorized error");
9738
10080
  return new Response(JSON.stringify({
9739
10081
  error: {
9740
10082
  message: "Authentication failed after token refresh",
@@ -9747,7 +10089,7 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9747
10089
  headers: { "Content-Type": "application/json" }
9748
10090
  });
9749
10091
  }
9750
- debugLog6("[401] Refreshing token and retrying...");
10092
+ debugLog7("[401] Refreshing token and retrying...");
9751
10093
  hasRefreshedFor401 = true;
9752
10094
  try {
9753
10095
  const newTokens = await refreshAccessToken(refreshParts.refreshToken, clientId, clientSecret);
@@ -9765,10 +10107,10 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9765
10107
  refresh: formattedRefresh,
9766
10108
  expires: Date.now() + newTokens.expires_in * 1000
9767
10109
  });
9768
- debugLog6("[401] Token refreshed, retrying request...");
10110
+ debugLog7("[401] Token refreshed, retrying request...");
9769
10111
  return executeWithEndpoints();
9770
10112
  } catch (refreshError) {
9771
- 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"}`);
9772
10114
  return new Response(JSON.stringify({
9773
10115
  error: {
9774
10116
  message: `Token refresh failed: ${refreshError instanceof Error ? refreshError.message : "Unknown error"}`,
@@ -9783,13 +10125,13 @@ function createAntigravityFetch(getAuth, client, providerId, clientId, clientSec
9783
10125
  }
9784
10126
  }
9785
10127
  if (response) {
9786
- debugLog6(`Success with endpoint: ${endpoint}`);
10128
+ debugLog7(`Success with endpoint: ${endpoint}`);
9787
10129
  const transformedResponse = await transformResponseWithThinking(response, modelName || "", fetchInstanceId);
9788
10130
  return transformedResponse;
9789
10131
  }
9790
10132
  }
9791
10133
  const errorMessage = `All Antigravity endpoints failed after ${maxEndpoints} attempts`;
9792
- debugLog6(errorMessage);
10134
+ debugLog7(errorMessage);
9793
10135
  return new Response(JSON.stringify({
9794
10136
  error: {
9795
10137
  message: errorMessage,
@@ -9934,22 +10276,22 @@ async function createGoogleAntigravityAuthPlugin({
9934
10276
  };
9935
10277
  }
9936
10278
  // src/features/claude-code-command-loader/loader.ts
9937
- 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";
9938
10280
  import { homedir as homedir10 } from "os";
9939
- import { join as join31, basename } from "path";
10281
+ import { join as join32, basename } from "path";
9940
10282
  function loadCommandsFromDir(commandsDir, scope) {
9941
- if (!existsSync22(commandsDir)) {
10283
+ if (!existsSync23(commandsDir)) {
9942
10284
  return [];
9943
10285
  }
9944
- const entries = readdirSync5(commandsDir, { withFileTypes: true });
10286
+ const entries = readdirSync6(commandsDir, { withFileTypes: true });
9945
10287
  const commands = [];
9946
10288
  for (const entry of entries) {
9947
10289
  if (!isMarkdownFile(entry))
9948
10290
  continue;
9949
- const commandPath = join31(commandsDir, entry.name);
10291
+ const commandPath = join32(commandsDir, entry.name);
9950
10292
  const commandName = basename(entry.name, ".md");
9951
10293
  try {
9952
- const content = readFileSync14(commandPath, "utf-8");
10294
+ const content = readFileSync15(commandPath, "utf-8");
9953
10295
  const { data, body } = parseFrontmatter(content);
9954
10296
  const wrappedTemplate = `<command-instruction>
9955
10297
  ${body.trim()}
@@ -9989,47 +10331,47 @@ function commandsToRecord(commands) {
9989
10331
  return result;
9990
10332
  }
9991
10333
  function loadUserCommands() {
9992
- const userCommandsDir = join31(homedir10(), ".claude", "commands");
10334
+ const userCommandsDir = join32(homedir10(), ".claude", "commands");
9993
10335
  const commands = loadCommandsFromDir(userCommandsDir, "user");
9994
10336
  return commandsToRecord(commands);
9995
10337
  }
9996
10338
  function loadProjectCommands() {
9997
- const projectCommandsDir = join31(process.cwd(), ".claude", "commands");
10339
+ const projectCommandsDir = join32(process.cwd(), ".claude", "commands");
9998
10340
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
9999
10341
  return commandsToRecord(commands);
10000
10342
  }
10001
10343
  function loadOpencodeGlobalCommands() {
10002
- const opencodeCommandsDir = join31(homedir10(), ".config", "opencode", "command");
10344
+ const opencodeCommandsDir = join32(homedir10(), ".config", "opencode", "command");
10003
10345
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
10004
10346
  return commandsToRecord(commands);
10005
10347
  }
10006
10348
  function loadOpencodeProjectCommands() {
10007
- const opencodeProjectDir = join31(process.cwd(), ".opencode", "command");
10349
+ const opencodeProjectDir = join32(process.cwd(), ".opencode", "command");
10008
10350
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
10009
10351
  return commandsToRecord(commands);
10010
10352
  }
10011
10353
  // src/features/claude-code-skill-loader/loader.ts
10012
- 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";
10013
10355
  import { homedir as homedir11 } from "os";
10014
- import { join as join32 } from "path";
10356
+ import { join as join33 } from "path";
10015
10357
  function loadSkillsFromDir(skillsDir, scope) {
10016
- if (!existsSync23(skillsDir)) {
10358
+ if (!existsSync24(skillsDir)) {
10017
10359
  return [];
10018
10360
  }
10019
- const entries = readdirSync6(skillsDir, { withFileTypes: true });
10361
+ const entries = readdirSync7(skillsDir, { withFileTypes: true });
10020
10362
  const skills = [];
10021
10363
  for (const entry of entries) {
10022
10364
  if (entry.name.startsWith("."))
10023
10365
  continue;
10024
- const skillPath = join32(skillsDir, entry.name);
10366
+ const skillPath = join33(skillsDir, entry.name);
10025
10367
  if (!entry.isDirectory() && !entry.isSymbolicLink())
10026
10368
  continue;
10027
10369
  const resolvedPath = resolveSymlink(skillPath);
10028
- const skillMdPath = join32(resolvedPath, "SKILL.md");
10029
- if (!existsSync23(skillMdPath))
10370
+ const skillMdPath = join33(resolvedPath, "SKILL.md");
10371
+ if (!existsSync24(skillMdPath))
10030
10372
  continue;
10031
10373
  try {
10032
- const content = readFileSync15(skillMdPath, "utf-8");
10374
+ const content = readFileSync16(skillMdPath, "utf-8");
10033
10375
  const { data, body } = parseFrontmatter(content);
10034
10376
  const skillName = data.name || entry.name;
10035
10377
  const originalDescription = data.description || "";
@@ -10060,7 +10402,7 @@ $ARGUMENTS
10060
10402
  return skills;
10061
10403
  }
10062
10404
  function loadUserSkillsAsCommands() {
10063
- const userSkillsDir = join32(homedir11(), ".claude", "skills");
10405
+ const userSkillsDir = join33(homedir11(), ".claude", "skills");
10064
10406
  const skills = loadSkillsFromDir(userSkillsDir, "user");
10065
10407
  return skills.reduce((acc, skill) => {
10066
10408
  acc[skill.name] = skill.definition;
@@ -10068,7 +10410,7 @@ function loadUserSkillsAsCommands() {
10068
10410
  }, {});
10069
10411
  }
10070
10412
  function loadProjectSkillsAsCommands() {
10071
- const projectSkillsDir = join32(process.cwd(), ".claude", "skills");
10413
+ const projectSkillsDir = join33(process.cwd(), ".claude", "skills");
10072
10414
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
10073
10415
  return skills.reduce((acc, skill) => {
10074
10416
  acc[skill.name] = skill.definition;
@@ -10076,9 +10418,9 @@ function loadProjectSkillsAsCommands() {
10076
10418
  }, {});
10077
10419
  }
10078
10420
  // src/features/claude-code-agent-loader/loader.ts
10079
- 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";
10080
10422
  import { homedir as homedir12 } from "os";
10081
- import { join as join33, basename as basename2 } from "path";
10423
+ import { join as join34, basename as basename2 } from "path";
10082
10424
  function parseToolsConfig(toolsStr) {
10083
10425
  if (!toolsStr)
10084
10426
  return;
@@ -10092,18 +10434,18 @@ function parseToolsConfig(toolsStr) {
10092
10434
  return result;
10093
10435
  }
10094
10436
  function loadAgentsFromDir(agentsDir, scope) {
10095
- if (!existsSync24(agentsDir)) {
10437
+ if (!existsSync25(agentsDir)) {
10096
10438
  return [];
10097
10439
  }
10098
- const entries = readdirSync7(agentsDir, { withFileTypes: true });
10440
+ const entries = readdirSync8(agentsDir, { withFileTypes: true });
10099
10441
  const agents = [];
10100
10442
  for (const entry of entries) {
10101
10443
  if (!isMarkdownFile(entry))
10102
10444
  continue;
10103
- const agentPath = join33(agentsDir, entry.name);
10445
+ const agentPath = join34(agentsDir, entry.name);
10104
10446
  const agentName = basename2(entry.name, ".md");
10105
10447
  try {
10106
- const content = readFileSync16(agentPath, "utf-8");
10448
+ const content = readFileSync17(agentPath, "utf-8");
10107
10449
  const { data, body } = parseFrontmatter(content);
10108
10450
  const name = data.name || agentName;
10109
10451
  const originalDescription = data.description || "";
@@ -10130,7 +10472,7 @@ function loadAgentsFromDir(agentsDir, scope) {
10130
10472
  return agents;
10131
10473
  }
10132
10474
  function loadUserAgents() {
10133
- const userAgentsDir = join33(homedir12(), ".claude", "agents");
10475
+ const userAgentsDir = join34(homedir12(), ".claude", "agents");
10134
10476
  const agents = loadAgentsFromDir(userAgentsDir, "user");
10135
10477
  const result = {};
10136
10478
  for (const agent of agents) {
@@ -10139,7 +10481,7 @@ function loadUserAgents() {
10139
10481
  return result;
10140
10482
  }
10141
10483
  function loadProjectAgents() {
10142
- const projectAgentsDir = join33(process.cwd(), ".claude", "agents");
10484
+ const projectAgentsDir = join34(process.cwd(), ".claude", "agents");
10143
10485
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
10144
10486
  const result = {};
10145
10487
  for (const agent of agents) {
@@ -10148,9 +10490,9 @@ function loadProjectAgents() {
10148
10490
  return result;
10149
10491
  }
10150
10492
  // src/features/claude-code-mcp-loader/loader.ts
10151
- import { existsSync as existsSync25 } from "fs";
10493
+ import { existsSync as existsSync26 } from "fs";
10152
10494
  import { homedir as homedir13 } from "os";
10153
- import { join as join34 } from "path";
10495
+ import { join as join35 } from "path";
10154
10496
 
10155
10497
  // src/features/claude-code-mcp-loader/env-expander.ts
10156
10498
  function expandEnvVars(value) {
@@ -10219,13 +10561,13 @@ function getMcpConfigPaths() {
10219
10561
  const home = homedir13();
10220
10562
  const cwd = process.cwd();
10221
10563
  return [
10222
- { path: join34(home, ".claude", ".mcp.json"), scope: "user" },
10223
- { path: join34(cwd, ".mcp.json"), scope: "project" },
10224
- { 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" }
10225
10567
  ];
10226
10568
  }
10227
10569
  async function loadMcpConfigFile(filePath) {
10228
- if (!existsSync25(filePath)) {
10570
+ if (!existsSync26(filePath)) {
10229
10571
  return null;
10230
10572
  }
10231
10573
  try {
@@ -10511,14 +10853,14 @@ var EXT_TO_LANG = {
10511
10853
  ".tfvars": "terraform"
10512
10854
  };
10513
10855
  // src/tools/lsp/config.ts
10514
- import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
10515
- import { join as join35 } from "path";
10856
+ import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
10857
+ import { join as join36 } from "path";
10516
10858
  import { homedir as homedir14 } from "os";
10517
10859
  function loadJsonFile(path6) {
10518
- if (!existsSync26(path6))
10860
+ if (!existsSync27(path6))
10519
10861
  return null;
10520
10862
  try {
10521
- return JSON.parse(readFileSync17(path6, "utf-8"));
10863
+ return JSON.parse(readFileSync18(path6, "utf-8"));
10522
10864
  } catch {
10523
10865
  return null;
10524
10866
  }
@@ -10526,9 +10868,9 @@ function loadJsonFile(path6) {
10526
10868
  function getConfigPaths2() {
10527
10869
  const cwd = process.cwd();
10528
10870
  return {
10529
- project: join35(cwd, ".opencode", "oh-my-opencode.json"),
10530
- user: join35(homedir14(), ".config", "opencode", "oh-my-opencode.json"),
10531
- 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")
10532
10874
  };
10533
10875
  }
10534
10876
  function loadAllConfigs() {
@@ -10621,7 +10963,7 @@ function isServerInstalled(command) {
10621
10963
  const pathEnv = process.env.PATH || "";
10622
10964
  const paths = pathEnv.split(":");
10623
10965
  for (const p of paths) {
10624
- if (existsSync26(join35(p, cmd))) {
10966
+ if (existsSync27(join36(p, cmd))) {
10625
10967
  return true;
10626
10968
  }
10627
10969
  }
@@ -10671,7 +11013,7 @@ function getAllServers() {
10671
11013
  }
10672
11014
  // src/tools/lsp/client.ts
10673
11015
  var {spawn: spawn4 } = globalThis.Bun;
10674
- import { readFileSync as readFileSync18 } from "fs";
11016
+ import { readFileSync as readFileSync19 } from "fs";
10675
11017
  import { extname, resolve as resolve5 } from "path";
10676
11018
  class LSPServerManager {
10677
11019
  static instance;
@@ -10680,6 +11022,36 @@ class LSPServerManager {
10680
11022
  IDLE_TIMEOUT = 5 * 60 * 1000;
10681
11023
  constructor() {
10682
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
+ }
10683
11055
  }
10684
11056
  static getInstance() {
10685
11057
  if (!LSPServerManager.instance) {
@@ -11071,7 +11443,7 @@ ${msg}`);
11071
11443
  const absPath = resolve5(filePath);
11072
11444
  if (this.openedFiles.has(absPath))
11073
11445
  return;
11074
- const text = readFileSync18(absPath, "utf-8");
11446
+ const text = readFileSync19(absPath, "utf-8");
11075
11447
  const ext = extname(absPath);
11076
11448
  const languageId = getLanguageId(ext);
11077
11449
  this.notify("textDocument/didOpen", {
@@ -11186,16 +11558,16 @@ ${msg}`);
11186
11558
  }
11187
11559
  // src/tools/lsp/utils.ts
11188
11560
  import { extname as extname2, resolve as resolve6 } from "path";
11189
- 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";
11190
11562
  function findWorkspaceRoot(filePath) {
11191
11563
  let dir = resolve6(filePath);
11192
- if (!existsSync27(dir) || !__require("fs").statSync(dir).isDirectory()) {
11564
+ if (!existsSync28(dir) || !__require("fs").statSync(dir).isDirectory()) {
11193
11565
  dir = __require("path").dirname(dir);
11194
11566
  }
11195
11567
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
11196
11568
  while (dir !== "/") {
11197
11569
  for (const marker of markers) {
11198
- if (existsSync27(__require("path").join(dir, marker))) {
11570
+ if (existsSync28(__require("path").join(dir, marker))) {
11199
11571
  return dir;
11200
11572
  }
11201
11573
  }
@@ -11353,7 +11725,7 @@ function formatCodeActions(actions) {
11353
11725
  }
11354
11726
  function applyTextEditsToFile(filePath, edits) {
11355
11727
  try {
11356
- let content = readFileSync19(filePath, "utf-8");
11728
+ let content = readFileSync20(filePath, "utf-8");
11357
11729
  const lines = content.split(`
11358
11730
  `);
11359
11731
  const sortedEdits = [...edits].sort((a, b) => {
@@ -11378,7 +11750,7 @@ function applyTextEditsToFile(filePath, edits) {
11378
11750
  `));
11379
11751
  }
11380
11752
  }
11381
- writeFileSync10(filePath, lines.join(`
11753
+ writeFileSync11(filePath, lines.join(`
11382
11754
  `), "utf-8");
11383
11755
  return { success: true, editCount: edits.length };
11384
11756
  } catch (err) {
@@ -11409,7 +11781,7 @@ function applyWorkspaceEdit(edit) {
11409
11781
  if (change.kind === "create") {
11410
11782
  try {
11411
11783
  const filePath = change.uri.replace("file://", "");
11412
- writeFileSync10(filePath, "", "utf-8");
11784
+ writeFileSync11(filePath, "", "utf-8");
11413
11785
  result.filesModified.push(filePath);
11414
11786
  } catch (err) {
11415
11787
  result.success = false;
@@ -11419,8 +11791,8 @@ function applyWorkspaceEdit(edit) {
11419
11791
  try {
11420
11792
  const oldPath = change.oldUri.replace("file://", "");
11421
11793
  const newPath = change.newUri.replace("file://", "");
11422
- const content = readFileSync19(oldPath, "utf-8");
11423
- writeFileSync10(newPath, content, "utf-8");
11794
+ const content = readFileSync20(oldPath, "utf-8");
11795
+ writeFileSync11(newPath, content, "utf-8");
11424
11796
  __require("fs").unlinkSync(oldPath);
11425
11797
  result.filesModified.push(newPath);
11426
11798
  } catch (err) {
@@ -24120,13 +24492,13 @@ var lsp_code_action_resolve = tool({
24120
24492
  });
24121
24493
  // src/tools/ast-grep/constants.ts
24122
24494
  import { createRequire as createRequire4 } from "module";
24123
- import { dirname as dirname6, join as join37 } from "path";
24124
- 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";
24125
24497
 
24126
24498
  // src/tools/ast-grep/downloader.ts
24127
24499
  var {spawn: spawn5 } = globalThis.Bun;
24128
- import { existsSync as existsSync28, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24129
- 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";
24130
24502
  import { homedir as homedir15 } from "os";
24131
24503
  import { createRequire as createRequire3 } from "module";
24132
24504
  var REPO2 = "ast-grep/ast-grep";
@@ -24152,19 +24524,19 @@ var PLATFORM_MAP2 = {
24152
24524
  function getCacheDir3() {
24153
24525
  if (process.platform === "win32") {
24154
24526
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
24155
- const base2 = localAppData || join36(homedir15(), "AppData", "Local");
24156
- return join36(base2, "oh-my-opencode", "bin");
24527
+ const base2 = localAppData || join37(homedir15(), "AppData", "Local");
24528
+ return join37(base2, "oh-my-opencode", "bin");
24157
24529
  }
24158
24530
  const xdgCache2 = process.env.XDG_CACHE_HOME;
24159
- const base = xdgCache2 || join36(homedir15(), ".cache");
24160
- return join36(base, "oh-my-opencode", "bin");
24531
+ const base = xdgCache2 || join37(homedir15(), ".cache");
24532
+ return join37(base, "oh-my-opencode", "bin");
24161
24533
  }
24162
24534
  function getBinaryName3() {
24163
24535
  return process.platform === "win32" ? "sg.exe" : "sg";
24164
24536
  }
24165
24537
  function getCachedBinaryPath2() {
24166
- const binaryPath = join36(getCacheDir3(), getBinaryName3());
24167
- return existsSync28(binaryPath) ? binaryPath : null;
24538
+ const binaryPath = join37(getCacheDir3(), getBinaryName3());
24539
+ return existsSync29(binaryPath) ? binaryPath : null;
24168
24540
  }
24169
24541
  async function extractZip2(archivePath, destDir) {
24170
24542
  const proc = process.platform === "win32" ? spawn5([
@@ -24190,8 +24562,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24190
24562
  }
24191
24563
  const cacheDir = getCacheDir3();
24192
24564
  const binaryName = getBinaryName3();
24193
- const binaryPath = join36(cacheDir, binaryName);
24194
- if (existsSync28(binaryPath)) {
24565
+ const binaryPath = join37(cacheDir, binaryName);
24566
+ if (existsSync29(binaryPath)) {
24195
24567
  return binaryPath;
24196
24568
  }
24197
24569
  const { arch, os: os4 } = platformInfo;
@@ -24199,21 +24571,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24199
24571
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
24200
24572
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
24201
24573
  try {
24202
- if (!existsSync28(cacheDir)) {
24574
+ if (!existsSync29(cacheDir)) {
24203
24575
  mkdirSync10(cacheDir, { recursive: true });
24204
24576
  }
24205
24577
  const response2 = await fetch(downloadUrl, { redirect: "follow" });
24206
24578
  if (!response2.ok) {
24207
24579
  throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
24208
24580
  }
24209
- const archivePath = join36(cacheDir, assetName);
24581
+ const archivePath = join37(cacheDir, assetName);
24210
24582
  const arrayBuffer = await response2.arrayBuffer();
24211
24583
  await Bun.write(archivePath, arrayBuffer);
24212
24584
  await extractZip2(archivePath, cacheDir);
24213
- if (existsSync28(archivePath)) {
24585
+ if (existsSync29(archivePath)) {
24214
24586
  unlinkSync9(archivePath);
24215
24587
  }
24216
- if (process.platform !== "win32" && existsSync28(binaryPath)) {
24588
+ if (process.platform !== "win32" && existsSync29(binaryPath)) {
24217
24589
  chmodSync2(binaryPath, 493);
24218
24590
  }
24219
24591
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -24264,8 +24636,8 @@ function findSgCliPathSync() {
24264
24636
  const require2 = createRequire4(import.meta.url);
24265
24637
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
24266
24638
  const cliDir = dirname6(cliPkgPath);
24267
- const sgPath = join37(cliDir, binaryName);
24268
- if (existsSync29(sgPath) && isValidBinary(sgPath)) {
24639
+ const sgPath = join38(cliDir, binaryName);
24640
+ if (existsSync30(sgPath) && isValidBinary(sgPath)) {
24269
24641
  return sgPath;
24270
24642
  }
24271
24643
  } catch {}
@@ -24276,8 +24648,8 @@ function findSgCliPathSync() {
24276
24648
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
24277
24649
  const pkgDir = dirname6(pkgPath);
24278
24650
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
24279
- const binaryPath = join37(pkgDir, astGrepName);
24280
- if (existsSync29(binaryPath) && isValidBinary(binaryPath)) {
24651
+ const binaryPath = join38(pkgDir, astGrepName);
24652
+ if (existsSync30(binaryPath) && isValidBinary(binaryPath)) {
24281
24653
  return binaryPath;
24282
24654
  }
24283
24655
  } catch {}
@@ -24285,7 +24657,7 @@ function findSgCliPathSync() {
24285
24657
  if (process.platform === "darwin") {
24286
24658
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
24287
24659
  for (const path6 of homebrewPaths) {
24288
- if (existsSync29(path6) && isValidBinary(path6)) {
24660
+ if (existsSync30(path6) && isValidBinary(path6)) {
24289
24661
  return path6;
24290
24662
  }
24291
24663
  }
@@ -24341,11 +24713,11 @@ var DEFAULT_MAX_MATCHES = 500;
24341
24713
 
24342
24714
  // src/tools/ast-grep/cli.ts
24343
24715
  var {spawn: spawn6 } = globalThis.Bun;
24344
- import { existsSync as existsSync30 } from "fs";
24716
+ import { existsSync as existsSync31 } from "fs";
24345
24717
  var resolvedCliPath3 = null;
24346
24718
  var initPromise2 = null;
24347
24719
  async function getAstGrepPath() {
24348
- if (resolvedCliPath3 !== null && existsSync30(resolvedCliPath3)) {
24720
+ if (resolvedCliPath3 !== null && existsSync31(resolvedCliPath3)) {
24349
24721
  return resolvedCliPath3;
24350
24722
  }
24351
24723
  if (initPromise2) {
@@ -24353,7 +24725,7 @@ async function getAstGrepPath() {
24353
24725
  }
24354
24726
  initPromise2 = (async () => {
24355
24727
  const syncPath = findSgCliPathSync();
24356
- if (syncPath && existsSync30(syncPath)) {
24728
+ if (syncPath && existsSync31(syncPath)) {
24357
24729
  resolvedCliPath3 = syncPath;
24358
24730
  setSgCliPath(syncPath);
24359
24731
  return syncPath;
@@ -24387,7 +24759,7 @@ async function runSg(options) {
24387
24759
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
24388
24760
  args.push(...paths);
24389
24761
  let cliPath = getSgCliPath();
24390
- if (!existsSync30(cliPath) && cliPath !== "sg") {
24762
+ if (!existsSync31(cliPath) && cliPath !== "sg") {
24391
24763
  const downloadedPath = await getAstGrepPath();
24392
24764
  if (downloadedPath) {
24393
24765
  cliPath = downloadedPath;
@@ -24651,24 +25023,24 @@ var ast_grep_replace = tool({
24651
25023
  var {spawn: spawn7 } = globalThis.Bun;
24652
25024
 
24653
25025
  // src/tools/grep/constants.ts
24654
- import { existsSync as existsSync32 } from "fs";
24655
- 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";
24656
25028
  import { spawnSync } from "child_process";
24657
25029
 
24658
25030
  // src/tools/grep/downloader.ts
24659
- import { existsSync as existsSync31, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync8 } from "fs";
24660
- 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";
24661
25033
  function getInstallDir() {
24662
25034
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
24663
- return join38(homeDir, ".cache", "oh-my-opencode", "bin");
25035
+ return join39(homeDir, ".cache", "oh-my-opencode", "bin");
24664
25036
  }
24665
25037
  function getRgPath() {
24666
25038
  const isWindows2 = process.platform === "win32";
24667
- return join38(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
25039
+ return join39(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
24668
25040
  }
24669
25041
  function getInstalledRipgrepPath() {
24670
25042
  const rgPath = getRgPath();
24671
- return existsSync31(rgPath) ? rgPath : null;
25043
+ return existsSync32(rgPath) ? rgPath : null;
24672
25044
  }
24673
25045
 
24674
25046
  // src/tools/grep/constants.ts
@@ -24691,13 +25063,13 @@ function getOpenCodeBundledRg() {
24691
25063
  const isWindows2 = process.platform === "win32";
24692
25064
  const rgName = isWindows2 ? "rg.exe" : "rg";
24693
25065
  const candidates = [
24694
- join39(execDir, rgName),
24695
- join39(execDir, "bin", rgName),
24696
- join39(execDir, "..", "bin", rgName),
24697
- join39(execDir, "..", "libexec", rgName)
25066
+ join40(execDir, rgName),
25067
+ join40(execDir, "bin", rgName),
25068
+ join40(execDir, "..", "bin", rgName),
25069
+ join40(execDir, "..", "libexec", rgName)
24698
25070
  ];
24699
25071
  for (const candidate of candidates) {
24700
- if (existsSync32(candidate)) {
25072
+ if (existsSync33(candidate)) {
24701
25073
  return candidate;
24702
25074
  }
24703
25075
  }
@@ -25100,22 +25472,22 @@ var glob = tool({
25100
25472
  }
25101
25473
  });
25102
25474
  // src/tools/slashcommand/tools.ts
25103
- 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";
25104
25476
  import { homedir as homedir16 } from "os";
25105
- 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";
25106
25478
  function discoverCommandsFromDir(commandsDir, scope) {
25107
- if (!existsSync33(commandsDir)) {
25479
+ if (!existsSync34(commandsDir)) {
25108
25480
  return [];
25109
25481
  }
25110
- const entries = readdirSync9(commandsDir, { withFileTypes: true });
25482
+ const entries = readdirSync10(commandsDir, { withFileTypes: true });
25111
25483
  const commands = [];
25112
25484
  for (const entry of entries) {
25113
25485
  if (!isMarkdownFile(entry))
25114
25486
  continue;
25115
- const commandPath = join40(commandsDir, entry.name);
25487
+ const commandPath = join41(commandsDir, entry.name);
25116
25488
  const commandName = basename3(entry.name, ".md");
25117
25489
  try {
25118
- const content = readFileSync20(commandPath, "utf-8");
25490
+ const content = readFileSync21(commandPath, "utf-8");
25119
25491
  const { data, body } = parseFrontmatter(content);
25120
25492
  const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
25121
25493
  const metadata = {
@@ -25140,10 +25512,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
25140
25512
  return commands;
25141
25513
  }
25142
25514
  function discoverCommandsSync() {
25143
- const userCommandsDir = join40(homedir16(), ".claude", "commands");
25144
- const projectCommandsDir = join40(process.cwd(), ".claude", "commands");
25145
- const opencodeGlobalDir = join40(homedir16(), ".config", "opencode", "command");
25146
- 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");
25147
25519
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
25148
25520
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
25149
25521
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -25275,9 +25647,9 @@ var SkillFrontmatterSchema = exports_external.object({
25275
25647
  metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
25276
25648
  });
25277
25649
  // src/tools/skill/tools.ts
25278
- 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";
25279
25651
  import { homedir as homedir17 } from "os";
25280
- import { join as join41, basename as basename4 } from "path";
25652
+ import { join as join42, basename as basename4 } from "path";
25281
25653
  function parseSkillFrontmatter(data) {
25282
25654
  return {
25283
25655
  name: typeof data.name === "string" ? data.name : "",
@@ -25288,22 +25660,22 @@ function parseSkillFrontmatter(data) {
25288
25660
  };
25289
25661
  }
25290
25662
  function discoverSkillsFromDir(skillsDir, scope) {
25291
- if (!existsSync34(skillsDir)) {
25663
+ if (!existsSync35(skillsDir)) {
25292
25664
  return [];
25293
25665
  }
25294
- const entries = readdirSync10(skillsDir, { withFileTypes: true });
25666
+ const entries = readdirSync11(skillsDir, { withFileTypes: true });
25295
25667
  const skills = [];
25296
25668
  for (const entry of entries) {
25297
25669
  if (entry.name.startsWith("."))
25298
25670
  continue;
25299
- const skillPath = join41(skillsDir, entry.name);
25671
+ const skillPath = join42(skillsDir, entry.name);
25300
25672
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25301
25673
  const resolvedPath = resolveSymlink(skillPath);
25302
- const skillMdPath = join41(resolvedPath, "SKILL.md");
25303
- if (!existsSync34(skillMdPath))
25674
+ const skillMdPath = join42(resolvedPath, "SKILL.md");
25675
+ if (!existsSync35(skillMdPath))
25304
25676
  continue;
25305
25677
  try {
25306
- const content = readFileSync21(skillMdPath, "utf-8");
25678
+ const content = readFileSync22(skillMdPath, "utf-8");
25307
25679
  const { data } = parseFrontmatter(content);
25308
25680
  skills.push({
25309
25681
  name: data.name || entry.name,
@@ -25318,8 +25690,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
25318
25690
  return skills;
25319
25691
  }
25320
25692
  function discoverSkillsSync() {
25321
- const userSkillsDir = join41(homedir17(), ".claude", "skills");
25322
- const projectSkillsDir = join41(process.cwd(), ".claude", "skills");
25693
+ const userSkillsDir = join42(homedir17(), ".claude", "skills");
25694
+ const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
25323
25695
  const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
25324
25696
  const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
25325
25697
  return [...projectSkills, ...userSkills];
@@ -25329,12 +25701,12 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
25329
25701
  `);
25330
25702
  async function parseSkillMd(skillPath) {
25331
25703
  const resolvedPath = resolveSymlink(skillPath);
25332
- const skillMdPath = join41(resolvedPath, "SKILL.md");
25333
- if (!existsSync34(skillMdPath)) {
25704
+ const skillMdPath = join42(resolvedPath, "SKILL.md");
25705
+ if (!existsSync35(skillMdPath)) {
25334
25706
  return null;
25335
25707
  }
25336
25708
  try {
25337
- let content = readFileSync21(skillMdPath, "utf-8");
25709
+ let content = readFileSync22(skillMdPath, "utf-8");
25338
25710
  content = await resolveCommandsInText(content);
25339
25711
  const { data, body } = parseFrontmatter(content);
25340
25712
  const frontmatter2 = parseSkillFrontmatter(data);
@@ -25345,12 +25717,12 @@ async function parseSkillMd(skillPath) {
25345
25717
  allowedTools: frontmatter2["allowed-tools"],
25346
25718
  metadata: frontmatter2.metadata
25347
25719
  };
25348
- const referencesDir = join41(resolvedPath, "references");
25349
- const scriptsDir = join41(resolvedPath, "scripts");
25350
- const assetsDir = join41(resolvedPath, "assets");
25351
- const references = existsSync34(referencesDir) ? readdirSync10(referencesDir).filter((f) => !f.startsWith(".")) : [];
25352
- const scripts = existsSync34(scriptsDir) ? readdirSync10(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
25353
- 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(".")) : [];
25354
25726
  return {
25355
25727
  name: metadata.name,
25356
25728
  path: resolvedPath,
@@ -25366,15 +25738,15 @@ async function parseSkillMd(skillPath) {
25366
25738
  }
25367
25739
  }
25368
25740
  async function discoverSkillsFromDirAsync(skillsDir) {
25369
- if (!existsSync34(skillsDir)) {
25741
+ if (!existsSync35(skillsDir)) {
25370
25742
  return [];
25371
25743
  }
25372
- const entries = readdirSync10(skillsDir, { withFileTypes: true });
25744
+ const entries = readdirSync11(skillsDir, { withFileTypes: true });
25373
25745
  const skills = [];
25374
25746
  for (const entry of entries) {
25375
25747
  if (entry.name.startsWith("."))
25376
25748
  continue;
25377
- const skillPath = join41(skillsDir, entry.name);
25749
+ const skillPath = join42(skillsDir, entry.name);
25378
25750
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25379
25751
  const skillInfo = await parseSkillMd(skillPath);
25380
25752
  if (skillInfo) {
@@ -25385,8 +25757,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
25385
25757
  return skills;
25386
25758
  }
25387
25759
  async function discoverSkills() {
25388
- const userSkillsDir = join41(homedir17(), ".claude", "skills");
25389
- const projectSkillsDir = join41(process.cwd(), ".claude", "skills");
25760
+ const userSkillsDir = join42(homedir17(), ".claude", "skills");
25761
+ const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
25390
25762
  const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
25391
25763
  const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
25392
25764
  return [...projectSkills, ...userSkills];
@@ -25415,9 +25787,9 @@ async function loadSkillWithReferences(skill, includeRefs) {
25415
25787
  const referencesLoaded = [];
25416
25788
  if (includeRefs && skill.references.length > 0) {
25417
25789
  for (const ref of skill.references) {
25418
- const refPath = join41(skill.path, "references", ref);
25790
+ const refPath = join42(skill.path, "references", ref);
25419
25791
  try {
25420
- let content = readFileSync21(refPath, "utf-8");
25792
+ let content = readFileSync22(refPath, "utf-8");
25421
25793
  content = await resolveCommandsInText(content);
25422
25794
  referencesLoaded.push({ path: ref, content });
25423
25795
  } catch {}
@@ -25689,12 +26061,15 @@ Arguments:
25689
26061
  - timeout: Max wait time in ms when blocking (default: 60000, max: 600000)
25690
26062
 
25691
26063
  The system automatically notifies when background tasks complete. You typically don't need block=true.`;
25692
- var BACKGROUND_CANCEL_DESCRIPTION = `Cancel a running background task.
26064
+ var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s).
25693
26065
 
25694
26066
  Only works for tasks with status "running". Aborts the background session and marks the task as cancelled.
25695
26067
 
25696
26068
  Arguments:
25697
- - 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.`;
25698
26073
 
25699
26074
  // src/tools/background-task/tools.ts
25700
26075
  function formatDuration(start, end) {
@@ -25909,10 +26284,35 @@ function createBackgroundCancel(manager, client2) {
25909
26284
  return tool({
25910
26285
  description: BACKGROUND_CANCEL_DESCRIPTION,
25911
26286
  args: {
25912
- 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)")
25913
26289
  },
25914
- async execute(args) {
26290
+ async execute(args, toolContext) {
25915
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
+ }
25916
26316
  const task = manager.getTask(args.taskId);
25917
26317
  if (!task) {
25918
26318
  return `\u274C Task not found: ${args.taskId}`;
@@ -26213,17 +26613,17 @@ var builtinTools = {
26213
26613
  skill
26214
26614
  };
26215
26615
  // src/features/background-agent/manager.ts
26216
- import { existsSync as existsSync35, readdirSync as readdirSync11 } from "fs";
26217
- import { join as join42 } from "path";
26218
- function getMessageDir3(sessionID) {
26219
- 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))
26220
26620
  return null;
26221
- const directPath = join42(MESSAGE_STORAGE, sessionID);
26222
- if (existsSync35(directPath))
26621
+ const directPath = join43(MESSAGE_STORAGE, sessionID);
26622
+ if (existsSync36(directPath))
26223
26623
  return directPath;
26224
- for (const dir of readdirSync11(MESSAGE_STORAGE)) {
26225
- const sessionPath = join42(MESSAGE_STORAGE, dir, sessionID);
26226
- if (existsSync35(sessionPath))
26624
+ for (const dir of readdirSync12(MESSAGE_STORAGE)) {
26625
+ const sessionPath = join43(MESSAGE_STORAGE, dir, sessionID);
26626
+ if (existsSync36(sessionPath))
26227
26627
  return sessionPath;
26228
26628
  }
26229
26629
  return null;
@@ -26447,7 +26847,7 @@ class BackgroundManager {
26447
26847
  log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
26448
26848
  setTimeout(async () => {
26449
26849
  try {
26450
- const messageDir = getMessageDir3(task.parentSessionID);
26850
+ const messageDir = getMessageDir4(task.parentSessionID);
26451
26851
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
26452
26852
  await this.client.session.prompt({
26453
26853
  path: { id: task.parentSessionID },
@@ -26643,7 +27043,8 @@ var HookNameSchema = exports_external.enum([
26643
27043
  "keyword-detector",
26644
27044
  "agent-usage-reminder",
26645
27045
  "non-interactive-env",
26646
- "interactive-bash-session"
27046
+ "interactive-bash-session",
27047
+ "empty-message-sanitizer"
26647
27048
  ]);
26648
27049
  var AgentOverrideConfigSchema = exports_external.object({
26649
27050
  model: exports_external.string().optional(),
@@ -26811,13 +27212,14 @@ var OhMyOpenCodePlugin = async (ctx) => {
26811
27212
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
26812
27213
  const nonInteractiveEnv = isHookEnabled("non-interactive-env") ? createNonInteractiveEnvHook(ctx) : null;
26813
27214
  const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
27215
+ const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
26814
27216
  updateTerminalTitle({ sessionId: "main" });
26815
27217
  const backgroundManager = new BackgroundManager(ctx);
26816
27218
  const backgroundNotificationHook = isHookEnabled("background-notification") ? createBackgroundNotificationHook(backgroundManager) : null;
26817
27219
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
26818
27220
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
26819
27221
  const lookAt = createLookAt(ctx);
26820
- const googleAuthHooks = pluginConfig.google_auth ? await createGoogleAntigravityAuthPlugin(ctx) : null;
27222
+ const googleAuthHooks = pluginConfig.google_auth !== false ? await createGoogleAntigravityAuthPlugin(ctx) : null;
26821
27223
  const tmuxAvailable = await getTmuxPath();
26822
27224
  return {
26823
27225
  ...googleAuthHooks ? { auth: googleAuthHooks.auth } : {},
@@ -26832,6 +27234,9 @@ var OhMyOpenCodePlugin = async (ctx) => {
26832
27234
  await claudeCodeHooks["chat.message"]?.(input, output);
26833
27235
  await keywordDetector?.["chat.message"]?.(input, output);
26834
27236
  },
27237
+ "experimental.chat.messages.transform": async (input, output) => {
27238
+ await emptyMessageSanitizer?.["experimental.chat.messages.transform"]?.(input, output);
27239
+ },
26835
27240
  config: async (config3) => {
26836
27241
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory);
26837
27242
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};