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