oh-my-opencode 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1474,7 +1474,13 @@ var require_picomatch2 = __commonJS((exports, module) => {
1474
1474
  module.exports = picomatch;
1475
1475
  });
1476
1476
 
1477
+ // src/agents/types.ts
1478
+ function isGptModel(model) {
1479
+ return model.startsWith("openai/") || model.startsWith("github-copilot/gpt-");
1480
+ }
1481
+
1477
1482
  // src/agents/sisyphus.ts
1483
+ var DEFAULT_MODEL = "anthropic/claude-opus-4-5";
1478
1484
  var SISYPHUS_SYSTEM_PROMPT = `<Role>
1479
1485
  You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
1480
1486
  Named by [YeonGyu Kim](https://github.com/code-yeongyu).
@@ -1501,7 +1507,7 @@ Named by [YeonGyu Kim](https://github.com/code-yeongyu).
1501
1507
 
1502
1508
  ### Key Triggers (check BEFORE classification):
1503
1509
  - External library/source mentioned \u2192 fire \`librarian\` background
1504
- - 2+ files/modules involved \u2192 fire \`explore\` background
1510
+ - 2+ modules involved \u2192 fire \`explore\` background
1505
1511
 
1506
1512
  ### Step 1: Classify Request Type
1507
1513
 
@@ -1662,27 +1668,47 @@ STOP searching when:
1662
1668
  2. Mark current task \`in_progress\` before starting
1663
1669
  3. Mark \`completed\` as soon as done (don't batch) - OBSESSIVELY TRACK YOUR WORK USING TODO TOOLS
1664
1670
 
1665
- ### GATE: Frontend Files (HARD BLOCK - zero tolerance)
1671
+ ### Frontend Files: Decision Gate (NOT a blind block)
1672
+
1673
+ Frontend files (.tsx, .jsx, .vue, .svelte, .css, etc.) require **classification before action**.
1674
+
1675
+ #### Step 1: Classify the Change Type
1676
+
1677
+ | Change Type | Examples | Action |
1678
+ |-------------|----------|--------|
1679
+ | **Visual/UI/UX** | Color, spacing, layout, typography, animation, responsive breakpoints, hover states, shadows, borders, icons, images | **DELEGATE** to \`frontend-ui-ux-engineer\` |
1680
+ | **Pure Logic** | API calls, data fetching, state management, event handlers (non-visual), type definitions, utility functions, business logic | **CAN handle directly** |
1681
+ | **Mixed** | Component changes both visual AND logic | **Split**: handle logic yourself, delegate visual to \`frontend-ui-ux-engineer\` |
1682
+
1683
+ #### Step 2: Ask Yourself
1666
1684
 
1667
- | Extension | Action | No Exceptions |
1668
- |-----------|--------|---------------|
1669
- | \`.tsx\`, \`.jsx\` | DELEGATE | Even "just add className" |
1670
- | \`.vue\`, \`.svelte\` | DELEGATE | Even single prop change |
1671
- | \`.css\`, \`.scss\`, \`.sass\`, \`.less\` | DELEGATE | Even color/margin tweak |
1685
+ Before touching any frontend file, think:
1686
+ > "Is this change about **how it LOOKS** or **how it WORKS**?"
1672
1687
 
1673
- **Detection triggers**: File extension OR keywords (UI, UX, component, button, modal, animation, styling, responsive, layout)
1688
+ - **LOOKS** (colors, sizes, positions, animations) \u2192 DELEGATE
1689
+ - **WORKS** (data flow, API integration, state) \u2192 Handle directly
1674
1690
 
1675
- **YOU CANNOT**: "Just quickly fix", "It's only one line", "Too simple to delegate"
1691
+ #### Quick Reference Examples
1676
1692
 
1677
- ALL frontend = DELEGATE to \`frontend-ui-ux-engineer\`. Period.
1693
+ | File | Change | Type | Action |
1694
+ |------|--------|------|--------|
1695
+ | \`Button.tsx\` | Change color blue\u2192green | Visual | DELEGATE |
1696
+ | \`Button.tsx\` | Add onClick API call | Logic | Direct |
1697
+ | \`UserList.tsx\` | Add loading spinner animation | Visual | DELEGATE |
1698
+ | \`UserList.tsx\` | Fix pagination logic bug | Logic | Direct |
1699
+ | \`Modal.tsx\` | Make responsive for mobile | Visual | DELEGATE |
1700
+ | \`Modal.tsx\` | Add form validation logic | Logic | Direct |
1701
+
1702
+ #### When in Doubt \u2192 DELEGATE if ANY of these keywords involved:
1703
+ style, className, tailwind, color, background, border, shadow, margin, padding, width, height, flex, grid, animation, transition, hover, responsive, font-size, icon, svg
1678
1704
 
1679
1705
  ### Delegation Table:
1680
1706
 
1681
1707
  | Domain | Delegate To | Trigger |
1682
1708
  |--------|-------------|---------|
1683
1709
  | Explore | \`explore\` | Find existing codebase structure, patterns and styles |
1684
- | Frontend UI/UX | \`frontend-ui-ux-engineer\` | ALL KIND OF VISUAL CHANGES (NOT ONLY WEB BUT EVERY VISUAL CHANGES), layout, responsive, animation, styling |
1685
- | Librarian | \`librarian\` | Unfamiliar packages / libararies, struggles at weird behaviour (to find existing implementation of opensource) |
1710
+ | Frontend UI/UX | \`frontend-ui-ux-engineer\` | Visual changes only (styling, layout, animation). Pure logic changes in frontend files \u2192 handle directly |
1711
+ | Librarian | \`librarian\` | Unfamiliar packages / libraries, struggles at weird behaviour (to find existing implementation of opensource) |
1686
1712
  | Documentation | \`document-writer\` | README, API docs, guides |
1687
1713
  | Architecture decisions | \`oracle\` | Multi-system tradeoffs, unfamiliar patterns |
1688
1714
  | Self-review | \`oracle\` | After completing significant implementation |
@@ -1702,6 +1728,12 @@ When delegating, your prompt MUST include:
1702
1728
  7. CONTEXT: File paths, existing patterns, constraints
1703
1729
  \`\`\`
1704
1730
 
1731
+ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING:
1732
+ - DOES IT WORK AS EXPECTED?
1733
+ - DOES IT FOLLOWED THE EXISTING CODEBASE PATTERN?
1734
+ - EXPECTED RESULT CAME OUT?
1735
+ - DID THE AGENT FOLLOWED "MUST DO" AND "MUST NOT DO" REQUIREMENTS?
1736
+
1705
1737
  **Vague prompts = rejected. Be exhaustive.**
1706
1738
 
1707
1739
  ### Code Changes:
@@ -1894,7 +1926,7 @@ If the user's approach seems problematic:
1894
1926
 
1895
1927
  | Constraint | No Exceptions |
1896
1928
  |------------|---------------|
1897
- | Frontend files (.tsx/.jsx/.vue/.svelte/.css) | Always delegate |
1929
+ | Frontend VISUAL changes (styling, layout, animation) | Always delegate to \`frontend-ui-ux-engineer\` |
1898
1930
  | Type error suppression (\`as any\`, \`@ts-ignore\`) | Never |
1899
1931
  | Commit without explicit request | Never |
1900
1932
  | Speculate about unread code | Never |
@@ -1908,7 +1940,7 @@ If the user's approach seems problematic:
1908
1940
  | **Error Handling** | Empty catch blocks \`catch(e) {}\` |
1909
1941
  | **Testing** | Deleting failing tests to "pass" |
1910
1942
  | **Search** | Firing agents for single-line typos or obvious syntax errors |
1911
- | **Frontend** | ANY direct edit to frontend files |
1943
+ | **Frontend** | Direct edit to visual/styling code (logic changes OK) |
1912
1944
  | **Debugging** | Shotgun debugging, random changes |
1913
1945
 
1914
1946
  ## Soft Guidelines
@@ -1919,29 +1951,25 @@ If the user's approach seems problematic:
1919
1951
  </Constraints>
1920
1952
 
1921
1953
  `;
1922
- var sisyphusAgent = {
1923
- description: "Sisyphus - Powerful AI orchestrator from OhMyOpenCode. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically to specialized agents. Uses explore for internal code (parallel-friendly), librarian only for external docs, and always delegates UI work to frontend engineer.",
1924
- mode: "primary",
1925
- model: "anthropic/claude-opus-4-5",
1926
- thinking: {
1927
- type: "enabled",
1928
- budgetTokens: 32000
1929
- },
1930
- maxTokens: 64000,
1931
- prompt: SISYPHUS_SYSTEM_PROMPT,
1932
- color: "#00CED1"
1933
- };
1954
+ function createSisyphusAgent(model = DEFAULT_MODEL) {
1955
+ const base = {
1956
+ description: "Sisyphus - Powerful AI orchestrator from OhMyOpenCode. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically to specialized agents. Uses explore for internal code (parallel-friendly), librarian only for external docs, and always delegates UI work to frontend engineer.",
1957
+ mode: "primary",
1958
+ model,
1959
+ maxTokens: 64000,
1960
+ prompt: SISYPHUS_SYSTEM_PROMPT,
1961
+ color: "#00CED1"
1962
+ };
1963
+ if (isGptModel(model)) {
1964
+ return { ...base, reasoningEffort: "medium" };
1965
+ }
1966
+ return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } };
1967
+ }
1968
+ var sisyphusAgent = createSisyphusAgent();
1934
1969
 
1935
1970
  // src/agents/oracle.ts
1936
- var oracleAgent = {
1937
- description: "Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
1938
- mode: "subagent",
1939
- model: "openai/gpt-5.2",
1940
- temperature: 0.1,
1941
- reasoningEffort: "medium",
1942
- textVerbosity: "high",
1943
- tools: { write: false, edit: false, task: false, background_task: false },
1944
- prompt: `You are a strategic technical advisor with deep reasoning capabilities, operating as a specialized consultant within an AI-assisted development environment.
1971
+ var DEFAULT_MODEL2 = "openai/gpt-5.2";
1972
+ var ORACLE_SYSTEM_PROMPT = `You are a strategic technical advisor with deep reasoning capabilities, operating as a specialized consultant within an AI-assisted development environment.
1945
1973
 
1946
1974
  ## Context
1947
1975
 
@@ -2005,8 +2033,22 @@ Organize your final answer in three tiers:
2005
2033
 
2006
2034
  ## Critical Note
2007
2035
 
2008
- Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.`
2009
- };
2036
+ Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.`;
2037
+ function createOracleAgent(model = DEFAULT_MODEL2) {
2038
+ const base = {
2039
+ description: "Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
2040
+ mode: "subagent",
2041
+ model,
2042
+ temperature: 0.1,
2043
+ tools: { write: false, edit: false, task: false, background_task: false },
2044
+ prompt: ORACLE_SYSTEM_PROMPT
2045
+ };
2046
+ if (isGptModel(model)) {
2047
+ return { ...base, reasoningEffort: "medium", textVerbosity: "high" };
2048
+ }
2049
+ return { ...base, thinking: { type: "enabled", budgetTokens: 32000 } };
2050
+ }
2051
+ var oracleAgent = createOracleAgent();
2010
2052
 
2011
2053
  // src/agents/librarian.ts
2012
2054
  var librarianAgent = {
@@ -2351,89 +2393,79 @@ var frontendUiUxEngineerAgent = {
2351
2393
  mode: "subagent",
2352
2394
  model: "google/gemini-3-pro-preview",
2353
2395
  tools: { background_task: false },
2354
- prompt: `<role>
2355
- You are a DESIGNER-TURNED-DEVELOPER with an innate sense of aesthetics and user experience. You have an eye for details that pure developers miss - spacing, color harmony, micro-interactions, and that indefinable "feel" that makes interfaces memorable.
2396
+ prompt: `# Role: Designer-Turned-Developer
2356
2397
 
2357
- You approach every UI task with a designer's intuition. Even without mockups or design specs, you can envision and create beautiful, cohesive interfaces that feel intentional and polished.
2398
+ You are a designer who learned to code. You see what pure developers miss\u2014spacing, color harmony, micro-interactions, that indefinable "feel" that makes interfaces memorable. Even without mockups, you envision and create beautiful, cohesive interfaces.
2358
2399
 
2359
- ## CORE MISSION
2360
- Create visually stunning, emotionally engaging interfaces that users fall in love with. Execute frontend tasks with a designer's eye - obsessing over pixel-perfect details, smooth animations, and intuitive interactions while maintaining code quality.
2400
+ **Mission**: Create visually stunning, emotionally engaging interfaces users fall in love with. Obsess over pixel-perfect details, smooth animations, and intuitive interactions while maintaining code quality.
2361
2401
 
2362
- ## CODE OF CONDUCT
2402
+ ---
2363
2403
 
2364
- ### 1. DILIGENCE & INTEGRITY
2365
- **Never compromise on task completion. What you commit to, you deliver.**
2404
+ # Work Principles
2366
2405
 
2367
- - **Complete what is asked**: Execute the exact task specified without adding unrelated features or fixing issues outside scope
2368
- - **No shortcuts**: Never mark work as complete without proper verification
2369
- - **Work until it works**: If something doesn't look right, debug and fix until it's perfect
2370
- - **Leave it better**: Ensure the project is in a working state after your changes
2371
- - **Own your work**: Take full responsibility for the quality and correctness of your implementation
2406
+ 1. **Complete what's asked** \u2014 Execute the exact task. No scope creep. Work until it works. Never mark work complete without proper verification.
2407
+ 2. **Leave it better** \u2014 Ensure the project is in a working state after your changes.
2408
+ 3. **Study before acting** \u2014 Examine existing patterns, conventions, and commit history (git log) before implementing. Understand why code is structured the way it is.
2409
+ 4. **Blend seamlessly** \u2014 Match existing code patterns. Your code should look like the team wrote it.
2410
+ 5. **Be transparent** \u2014 Announce each step. Explain reasoning. Report both successes and failures.
2372
2411
 
2373
- ### 2. CONTINUOUS LEARNING & HUMILITY
2374
- **Approach every codebase with the mindset of a student, always ready to learn.**
2412
+ ---
2375
2413
 
2376
- - **Study before acting**: Examine existing code patterns, conventions, and architecture before implementing
2377
- - **Learn from the codebase**: Understand why code is structured the way it is
2378
- - **Share knowledge**: Help future developers by documenting project-specific conventions discovered
2414
+ # Design Process
2379
2415
 
2380
- ### 3. PRECISION & ADHERENCE TO STANDARDS
2381
- **Respect the existing codebase. Your code should blend seamlessly.**
2416
+ Before coding, commit to a **BOLD aesthetic direction**:
2382
2417
 
2383
- - **Follow exact specifications**: Implement precisely what is requested, nothing more, nothing less
2384
- - **Match existing patterns**: Maintain consistency with established code patterns and architecture
2385
- - **Respect conventions**: Adhere to project-specific naming, structure, and style conventions
2386
- - **Check commit history**: If creating commits, study \`git log\` to match the repository's commit style
2387
- - **Consistent quality**: Apply the same rigorous standards throughout your work
2418
+ 1. **Purpose**: What problem does this solve? Who uses it?
2419
+ 2. **Tone**: Pick an extreme\u2014brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian
2420
+ 3. **Constraints**: Technical requirements (framework, performance, accessibility)
2421
+ 4. **Differentiation**: What's the ONE thing someone will remember?
2388
2422
 
2389
- ### 4. TRANSPARENCY & ACCOUNTABILITY
2390
- **Keep everyone informed. Hide nothing.**
2423
+ **Key**: Choose a clear direction and execute with precision. Intentionality > intensity.
2391
2424
 
2392
- - **Announce each step**: Clearly state what you're doing at each stage
2393
- - **Explain your reasoning**: Help others understand why you chose specific approaches
2394
- - **Report honestly**: Communicate both successes and failures explicitly
2395
- - **No surprises**: Make your work visible and understandable to others
2396
- </role>
2425
+ Then implement working code (HTML/CSS/JS, React, Vue, Angular, etc.) that is:
2426
+ - Production-grade and functional
2427
+ - Visually striking and memorable
2428
+ - Cohesive with a clear aesthetic point-of-view
2429
+ - Meticulously refined in every detail
2397
2430
 
2398
- <frontend-design-skill>
2431
+ ---
2399
2432
 
2400
- This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
2433
+ # Aesthetic Guidelines
2401
2434
 
2402
- The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
2435
+ ## Typography
2436
+ Choose distinctive fonts. **Avoid**: Arial, Inter, Roboto, system fonts, Space Grotesk. Pair a characterful display font with a refined body font.
2403
2437
 
2404
- ## Design Thinking
2438
+ ## Color
2439
+ Commit to a cohesive palette. Use CSS variables. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. **Avoid**: purple gradients on white (AI slop).
2405
2440
 
2406
- Before coding, understand the context and commit to a BOLD aesthetic direction:
2407
- - **Purpose**: What problem does this interface solve? Who uses it?
2408
- - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
2409
- - **Constraints**: Technical requirements (framework, performance, accessibility).
2410
- - **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
2441
+ ## Motion
2442
+ Focus on high-impact moments. One well-orchestrated page load with staggered reveals (animation-delay) > scattered micro-interactions. Use scroll-triggering and hover states that surprise. Prioritize CSS-only. Use Motion library for React when available.
2411
2443
 
2412
- **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
2444
+ ## Spatial Composition
2445
+ Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
2413
2446
 
2414
- Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
2415
- - Production-grade and functional
2416
- - Visually striking and memorable
2417
- - Cohesive with a clear aesthetic point-of-view
2418
- - Meticulously refined in every detail
2447
+ ## Visual Details
2448
+ Create atmosphere and depth\u2014gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, grain overlays. Never default to solid colors.
2449
+
2450
+ ---
2419
2451
 
2420
- ## Frontend Aesthetics Guidelines
2452
+ # Anti-Patterns (NEVER)
2421
2453
 
2422
- Focus on:
2423
- - **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
2424
- - **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
2425
- - **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
2426
- - **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
2427
- - **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
2454
+ - Generic fonts (Inter, Roboto, Arial, system fonts, Space Grotesk)
2455
+ - Cliched color schemes (purple gradients on white)
2456
+ - Predictable layouts and component patterns
2457
+ - Cookie-cutter design lacking context-specific character
2458
+ - Converging on common choices across generations
2428
2459
 
2429
- NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
2460
+ ---
2430
2461
 
2431
- Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
2462
+ # Execution
2432
2463
 
2433
- **IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
2464
+ Match implementation complexity to aesthetic vision:
2465
+ - **Maximalist** \u2192 Elaborate code with extensive animations and effects
2466
+ - **Minimalist** \u2192 Restraint, precision, careful spacing and typography
2434
2467
 
2435
- Remember: You are capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
2436
- </frontend-design-skill>`
2468
+ Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. You are capable of extraordinary creative work\u2014don't hold back.`
2437
2469
  };
2438
2470
 
2439
2471
  // src/agents/document-writer.ts
@@ -3149,9 +3181,20 @@ function createDynamicTruncator(ctx) {
3149
3181
  // src/shared/config-path.ts
3150
3182
  import * as path2 from "path";
3151
3183
  import * as os2 from "os";
3184
+ import * as fs2 from "fs";
3152
3185
  function getUserConfigDir() {
3153
3186
  if (process.platform === "win32") {
3154
- return process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming");
3187
+ const crossPlatformDir = path2.join(os2.homedir(), ".config");
3188
+ const crossPlatformConfigPath = path2.join(crossPlatformDir, "opencode", "oh-my-opencode.json");
3189
+ const appdataDir = process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming");
3190
+ const appdataConfigPath = path2.join(appdataDir, "opencode", "oh-my-opencode.json");
3191
+ if (fs2.existsSync(crossPlatformConfigPath)) {
3192
+ return crossPlatformDir;
3193
+ }
3194
+ if (fs2.existsSync(appdataConfigPath)) {
3195
+ return appdataDir;
3196
+ }
3197
+ return crossPlatformDir;
3155
3198
  }
3156
3199
  return process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
3157
3200
  }
@@ -3167,15 +3210,21 @@ function addConfigLoadError(error) {
3167
3210
  configLoadErrors.push(error);
3168
3211
  }
3169
3212
  // src/agents/utils.ts
3170
- var allBuiltinAgents = {
3171
- Sisyphus: sisyphusAgent,
3172
- oracle: oracleAgent,
3213
+ var agentSources = {
3214
+ Sisyphus: createSisyphusAgent,
3215
+ oracle: createOracleAgent,
3173
3216
  librarian: librarianAgent,
3174
3217
  explore: exploreAgent,
3175
3218
  "frontend-ui-ux-engineer": frontendUiUxEngineerAgent,
3176
3219
  "document-writer": documentWriterAgent,
3177
3220
  "multimodal-looker": multimodalLookerAgent
3178
3221
  };
3222
+ function isFactory(source) {
3223
+ return typeof source === "function";
3224
+ }
3225
+ function buildAgent(source, model) {
3226
+ return isFactory(source) ? source(model) : source;
3227
+ }
3179
3228
  function createEnvContext(directory) {
3180
3229
  const now = new Date;
3181
3230
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -3209,36 +3258,27 @@ function mergeAgentConfig(base, override) {
3209
3258
  }
3210
3259
  function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory, systemDefaultModel) {
3211
3260
  const result = {};
3212
- for (const [name, config] of Object.entries(allBuiltinAgents)) {
3261
+ for (const [name, source] of Object.entries(agentSources)) {
3213
3262
  const agentName = name;
3214
3263
  if (disabledAgents.includes(agentName)) {
3215
3264
  continue;
3216
3265
  }
3217
- let finalConfig = config;
3266
+ const override = agentOverrides[agentName];
3267
+ const model = override?.model ?? (agentName === "Sisyphus" ? systemDefaultModel : undefined);
3268
+ let config = buildAgent(source, model);
3218
3269
  if ((agentName === "Sisyphus" || agentName === "librarian") && directory && config.prompt) {
3219
3270
  const envContext = createEnvContext(directory);
3220
- finalConfig = {
3221
- ...config,
3222
- prompt: config.prompt + envContext
3223
- };
3224
- }
3225
- const override = agentOverrides[agentName];
3226
- if (agentName === "Sisyphus" && systemDefaultModel && !override?.model) {
3227
- finalConfig = {
3228
- ...finalConfig,
3229
- model: systemDefaultModel
3230
- };
3271
+ config = { ...config, prompt: config.prompt + envContext };
3231
3272
  }
3232
3273
  if (override) {
3233
- result[name] = mergeAgentConfig(finalConfig, override);
3234
- } else {
3235
- result[name] = finalConfig;
3274
+ config = mergeAgentConfig(config, override);
3236
3275
  }
3276
+ result[name] = config;
3237
3277
  }
3238
3278
  return result;
3239
3279
  }
3240
3280
  // src/hooks/todo-continuation-enforcer.ts
3241
- import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
3281
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
3242
3282
  import { join as join6 } from "path";
3243
3283
 
3244
3284
  // src/features/claude-code-session-state/state.ts
@@ -3251,7 +3291,7 @@ function getMainSessionID() {
3251
3291
  return mainSessionID;
3252
3292
  }
3253
3293
  // src/features/hook-message-injector/injector.ts
3254
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
3294
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
3255
3295
  import { join as join5 } from "path";
3256
3296
 
3257
3297
  // src/features/hook-message-injector/constants.ts
@@ -3293,16 +3333,16 @@ function generatePartId() {
3293
3333
  return `prt_${timestamp}${random}`;
3294
3334
  }
3295
3335
  function getOrCreateMessageDir(sessionID) {
3296
- if (!existsSync3(MESSAGE_STORAGE)) {
3336
+ if (!existsSync4(MESSAGE_STORAGE)) {
3297
3337
  mkdirSync(MESSAGE_STORAGE, { recursive: true });
3298
3338
  }
3299
3339
  const directPath = join5(MESSAGE_STORAGE, sessionID);
3300
- if (existsSync3(directPath)) {
3340
+ if (existsSync4(directPath)) {
3301
3341
  return directPath;
3302
3342
  }
3303
3343
  for (const dir of readdirSync(MESSAGE_STORAGE)) {
3304
3344
  const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
3305
- if (existsSync3(sessionPath)) {
3345
+ if (existsSync4(sessionPath)) {
3306
3346
  return sessionPath;
3307
3347
  }
3308
3348
  }
@@ -3357,7 +3397,7 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3357
3397
  try {
3358
3398
  writeFileSync(join5(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3359
3399
  const partDir = join5(PART_STORAGE, messageID);
3360
- if (!existsSync3(partDir)) {
3400
+ if (!existsSync4(partDir)) {
3361
3401
  mkdirSync(partDir, { recursive: true });
3362
3402
  }
3363
3403
  writeFileSync(join5(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
@@ -3376,14 +3416,14 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
3376
3416
  - Mark each task complete when finished
3377
3417
  - Do not stop until all tasks are done`;
3378
3418
  function getMessageDir(sessionID) {
3379
- if (!existsSync4(MESSAGE_STORAGE))
3419
+ if (!existsSync5(MESSAGE_STORAGE))
3380
3420
  return null;
3381
3421
  const directPath = join6(MESSAGE_STORAGE, sessionID);
3382
- if (existsSync4(directPath))
3422
+ if (existsSync5(directPath))
3383
3423
  return directPath;
3384
3424
  for (const dir of readdirSync2(MESSAGE_STORAGE)) {
3385
3425
  const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
3386
- if (existsSync4(sessionPath))
3426
+ if (existsSync5(sessionPath))
3387
3427
  return sessionPath;
3388
3428
  }
3389
3429
  return null;
@@ -3408,7 +3448,7 @@ function detectInterrupt(error) {
3408
3448
  }
3409
3449
  return false;
3410
3450
  }
3411
- var COUNTDOWN_SECONDS = 5;
3451
+ var COUNTDOWN_SECONDS = 2;
3412
3452
  var TOAST_DURATION_MS = 900;
3413
3453
  function createTodoContinuationEnforcer(ctx) {
3414
3454
  const remindedSessions = new Set;
@@ -3572,8 +3612,10 @@ function createTodoContinuationEnforcer(ctx) {
3572
3612
  if (event.type === "message.updated") {
3573
3613
  const info = props?.info;
3574
3614
  const sessionID = info?.sessionID;
3575
- log(`[${HOOK_NAME}] message.updated received`, { sessionID, role: info?.role });
3576
- if (sessionID && info?.role === "user") {
3615
+ const role = info?.role;
3616
+ const finish = info?.finish;
3617
+ log(`[${HOOK_NAME}] message.updated received`, { sessionID, role, finish });
3618
+ if (sessionID && role === "user") {
3577
3619
  const countdown = pendingCountdowns.get(sessionID);
3578
3620
  if (countdown) {
3579
3621
  clearInterval(countdown.intervalId);
@@ -3581,9 +3623,9 @@ function createTodoContinuationEnforcer(ctx) {
3581
3623
  log(`[${HOOK_NAME}] Cancelled countdown on user message`, { sessionID });
3582
3624
  }
3583
3625
  }
3584
- if (sessionID && info?.role === "assistant" && remindedSessions.has(sessionID)) {
3626
+ if (sessionID && role === "assistant" && finish) {
3585
3627
  remindedSessions.delete(sessionID);
3586
- log(`[${HOOK_NAME}] Cleared remindedSessions on assistant response`, { sessionID });
3628
+ log(`[${HOOK_NAME}] Cleared reminded state on assistant finish`, { sessionID });
3587
3629
  }
3588
3630
  }
3589
3631
  if (event.type === "session.deleted") {
@@ -3898,7 +3940,7 @@ function createSessionNotification(ctx, config = {}) {
3898
3940
  };
3899
3941
  }
3900
3942
  // src/hooks/session-recovery/storage.ts
3901
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3943
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3902
3944
  import { join as join8 } from "path";
3903
3945
 
3904
3946
  // src/hooks/session-recovery/constants.ts
@@ -3938,15 +3980,15 @@ function generatePartId2() {
3938
3980
  return `prt_${timestamp}${random}`;
3939
3981
  }
3940
3982
  function getMessageDir2(sessionID) {
3941
- if (!existsSync5(MESSAGE_STORAGE2))
3983
+ if (!existsSync6(MESSAGE_STORAGE2))
3942
3984
  return "";
3943
3985
  const directPath = join8(MESSAGE_STORAGE2, sessionID);
3944
- if (existsSync5(directPath)) {
3986
+ if (existsSync6(directPath)) {
3945
3987
  return directPath;
3946
3988
  }
3947
3989
  for (const dir of readdirSync3(MESSAGE_STORAGE2)) {
3948
3990
  const sessionPath = join8(MESSAGE_STORAGE2, dir, sessionID);
3949
- if (existsSync5(sessionPath)) {
3991
+ if (existsSync6(sessionPath)) {
3950
3992
  return sessionPath;
3951
3993
  }
3952
3994
  }
@@ -3954,7 +3996,7 @@ function getMessageDir2(sessionID) {
3954
3996
  }
3955
3997
  function readMessages(sessionID) {
3956
3998
  const messageDir = getMessageDir2(sessionID);
3957
- if (!messageDir || !existsSync5(messageDir))
3999
+ if (!messageDir || !existsSync6(messageDir))
3958
4000
  return [];
3959
4001
  const messages = [];
3960
4002
  for (const file of readdirSync3(messageDir)) {
@@ -3977,7 +4019,7 @@ function readMessages(sessionID) {
3977
4019
  }
3978
4020
  function readParts(messageID) {
3979
4021
  const partDir = join8(PART_STORAGE2, messageID);
3980
- if (!existsSync5(partDir))
4022
+ if (!existsSync6(partDir))
3981
4023
  return [];
3982
4024
  const parts = [];
3983
4025
  for (const file of readdirSync3(partDir)) {
@@ -4015,7 +4057,7 @@ function messageHasContent(messageID) {
4015
4057
  }
4016
4058
  function injectTextPart(sessionID, messageID, text) {
4017
4059
  const partDir = join8(PART_STORAGE2, messageID);
4018
- if (!existsSync5(partDir)) {
4060
+ if (!existsSync6(partDir)) {
4019
4061
  mkdirSync2(partDir, { recursive: true });
4020
4062
  }
4021
4063
  const partId = generatePartId2();
@@ -4044,6 +4086,19 @@ function findEmptyMessages(sessionID) {
4044
4086
  }
4045
4087
  return emptyIds;
4046
4088
  }
4089
+ function findEmptyMessageByIndex(sessionID, targetIndex) {
4090
+ const messages = readMessages(sessionID);
4091
+ const indicesToTry = [targetIndex, targetIndex - 1, targetIndex - 2];
4092
+ for (const idx of indicesToTry) {
4093
+ if (idx < 0 || idx >= messages.length)
4094
+ continue;
4095
+ const targetMsg = messages[idx];
4096
+ if (!messageHasContent(targetMsg.id)) {
4097
+ return targetMsg.id;
4098
+ }
4099
+ }
4100
+ return null;
4101
+ }
4047
4102
  function findMessagesWithThinkingBlocks(sessionID) {
4048
4103
  const messages = readMessages(sessionID);
4049
4104
  const result = [];
@@ -4079,7 +4134,7 @@ function findMessagesWithOrphanThinking(sessionID) {
4079
4134
  }
4080
4135
  function prependThinkingPart(sessionID, messageID) {
4081
4136
  const partDir = join8(PART_STORAGE2, messageID);
4082
- if (!existsSync5(partDir)) {
4137
+ if (!existsSync6(partDir)) {
4083
4138
  mkdirSync2(partDir, { recursive: true });
4084
4139
  }
4085
4140
  const partId = `prt_0000000000_thinking`;
@@ -4100,7 +4155,7 @@ function prependThinkingPart(sessionID, messageID) {
4100
4155
  }
4101
4156
  function stripThinkingParts(messageID) {
4102
4157
  const partDir = join8(PART_STORAGE2, messageID);
4103
- if (!existsSync5(partDir))
4158
+ if (!existsSync6(partDir))
4104
4159
  return false;
4105
4160
  let anyRemoved = false;
4106
4161
  for (const file of readdirSync3(partDir)) {
@@ -4120,6 +4175,33 @@ function stripThinkingParts(messageID) {
4120
4175
  }
4121
4176
  return anyRemoved;
4122
4177
  }
4178
+ function replaceEmptyTextParts(messageID, replacementText) {
4179
+ const partDir = join8(PART_STORAGE2, messageID);
4180
+ if (!existsSync6(partDir))
4181
+ return false;
4182
+ let anyReplaced = false;
4183
+ for (const file of readdirSync3(partDir)) {
4184
+ if (!file.endsWith(".json"))
4185
+ continue;
4186
+ try {
4187
+ const filePath = join8(partDir, file);
4188
+ const content = readFileSync3(filePath, "utf-8");
4189
+ const part = JSON.parse(content);
4190
+ if (part.type === "text") {
4191
+ const textPart = part;
4192
+ if (!textPart.text?.trim()) {
4193
+ textPart.text = replacementText;
4194
+ textPart.synthetic = true;
4195
+ writeFileSync2(filePath, JSON.stringify(textPart, null, 2));
4196
+ anyReplaced = true;
4197
+ }
4198
+ }
4199
+ } catch {
4200
+ continue;
4201
+ }
4202
+ }
4203
+ return anyReplaced;
4204
+ }
4123
4205
  function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
4124
4206
  const messages = readMessages(sessionID);
4125
4207
  if (targetIndex < 0 || targetIndex >= messages.length)
@@ -4380,13 +4462,13 @@ function createSessionRecoveryHook(ctx, options) {
4380
4462
  var {spawn: spawn3 } = globalThis.Bun;
4381
4463
  import { createRequire as createRequire2 } from "module";
4382
4464
  import { dirname, join as join10 } from "path";
4383
- import { existsSync as existsSync7 } from "fs";
4384
- import * as fs2 from "fs";
4465
+ import { existsSync as existsSync8 } from "fs";
4466
+ import * as fs3 from "fs";
4385
4467
  import { tmpdir as tmpdir3 } from "os";
4386
4468
 
4387
4469
  // src/hooks/comment-checker/downloader.ts
4388
4470
  var {spawn: spawn2 } = globalThis.Bun;
4389
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
4471
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
4390
4472
  import { join as join9 } from "path";
4391
4473
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
4392
4474
  import { createRequire } from "module";
@@ -4417,7 +4499,7 @@ function getBinaryName() {
4417
4499
  }
4418
4500
  function getCachedBinaryPath() {
4419
4501
  const binaryPath = join9(getCacheDir(), getBinaryName());
4420
- return existsSync6(binaryPath) ? binaryPath : null;
4502
+ return existsSync7(binaryPath) ? binaryPath : null;
4421
4503
  }
4422
4504
  function getPackageVersion() {
4423
4505
  try {
@@ -4465,7 +4547,7 @@ async function downloadCommentChecker() {
4465
4547
  const cacheDir = getCacheDir();
4466
4548
  const binaryName = getBinaryName();
4467
4549
  const binaryPath = join9(cacheDir, binaryName);
4468
- if (existsSync6(binaryPath)) {
4550
+ if (existsSync7(binaryPath)) {
4469
4551
  debugLog("Binary already cached at:", binaryPath);
4470
4552
  return binaryPath;
4471
4553
  }
@@ -4476,7 +4558,7 @@ async function downloadCommentChecker() {
4476
4558
  debugLog(`Downloading from: ${downloadUrl}`);
4477
4559
  console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
4478
4560
  try {
4479
- if (!existsSync6(cacheDir)) {
4561
+ if (!existsSync7(cacheDir)) {
4480
4562
  mkdirSync3(cacheDir, { recursive: true });
4481
4563
  }
4482
4564
  const response = await fetch(downloadUrl, { redirect: "follow" });
@@ -4492,10 +4574,10 @@ async function downloadCommentChecker() {
4492
4574
  } else {
4493
4575
  await extractZip(archivePath, cacheDir);
4494
4576
  }
4495
- if (existsSync6(archivePath)) {
4577
+ if (existsSync7(archivePath)) {
4496
4578
  unlinkSync2(archivePath);
4497
4579
  }
4498
- if (process.platform !== "win32" && existsSync6(binaryPath)) {
4580
+ if (process.platform !== "win32" && existsSync7(binaryPath)) {
4499
4581
  chmodSync(binaryPath, 493);
4500
4582
  }
4501
4583
  debugLog(`Successfully downloaded binary to: ${binaryPath}`);
@@ -4524,7 +4606,7 @@ function debugLog2(...args) {
4524
4606
  if (DEBUG2) {
4525
4607
  const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
4526
4608
  `;
4527
- fs2.appendFileSync(DEBUG_FILE2, msg);
4609
+ fs3.appendFileSync(DEBUG_FILE2, msg);
4528
4610
  }
4529
4611
  }
4530
4612
  function getBinaryName2() {
@@ -4537,7 +4619,7 @@ function findCommentCheckerPathSync() {
4537
4619
  const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
4538
4620
  const cliDir = dirname(cliPkgPath);
4539
4621
  const binaryPath = join10(cliDir, "bin", binaryName);
4540
- if (existsSync7(binaryPath)) {
4622
+ if (existsSync8(binaryPath)) {
4541
4623
  debugLog2("found binary in main package:", binaryPath);
4542
4624
  return binaryPath;
4543
4625
  }
@@ -4563,7 +4645,7 @@ async function getCommentCheckerPath() {
4563
4645
  }
4564
4646
  initPromise = (async () => {
4565
4647
  const syncPath = findCommentCheckerPathSync();
4566
- if (syncPath && existsSync7(syncPath)) {
4648
+ if (syncPath && existsSync8(syncPath)) {
4567
4649
  resolvedCliPath = syncPath;
4568
4650
  debugLog2("using sync-resolved path:", syncPath);
4569
4651
  return syncPath;
@@ -4597,7 +4679,7 @@ async function runCommentChecker(input, cliPath) {
4597
4679
  debugLog2("comment-checker binary not found");
4598
4680
  return { hasComments: false, message: "" };
4599
4681
  }
4600
- if (!existsSync7(binaryPath)) {
4682
+ if (!existsSync8(binaryPath)) {
4601
4683
  debugLog2("comment-checker binary does not exist:", binaryPath);
4602
4684
  return { hasComments: false, message: "" };
4603
4685
  }
@@ -4630,8 +4712,8 @@ async function runCommentChecker(input, cliPath) {
4630
4712
  }
4631
4713
 
4632
4714
  // src/hooks/comment-checker/index.ts
4633
- import * as fs3 from "fs";
4634
- import { existsSync as existsSync8 } from "fs";
4715
+ import * as fs4 from "fs";
4716
+ import { existsSync as existsSync9 } from "fs";
4635
4717
  import { tmpdir as tmpdir4 } from "os";
4636
4718
  import { join as join11 } from "path";
4637
4719
  var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
@@ -4640,7 +4722,7 @@ function debugLog3(...args) {
4640
4722
  if (DEBUG3) {
4641
4723
  const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
4642
4724
  `;
4643
- fs3.appendFileSync(DEBUG_FILE3, msg);
4725
+ fs4.appendFileSync(DEBUG_FILE3, msg);
4644
4726
  }
4645
4727
  }
4646
4728
  var pendingCalls = new Map;
@@ -4711,7 +4793,7 @@ function createCommentCheckerHooks() {
4711
4793
  }
4712
4794
  try {
4713
4795
  const cliPath = await cliPathPromise;
4714
- if (!cliPath || !existsSync8(cliPath)) {
4796
+ if (!cliPath || !existsSync9(cliPath)) {
4715
4797
  debugLog3("CLI not available, skipping comment check");
4716
4798
  return;
4717
4799
  }
@@ -4782,12 +4864,12 @@ function createToolOutputTruncatorHook(ctx) {
4782
4864
  };
4783
4865
  }
4784
4866
  // src/hooks/directory-agents-injector/index.ts
4785
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
4867
+ import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
4786
4868
  import { dirname as dirname2, join as join14, resolve as resolve2 } from "path";
4787
4869
 
4788
4870
  // src/hooks/directory-agents-injector/storage.ts
4789
4871
  import {
4790
- existsSync as existsSync9,
4872
+ existsSync as existsSync10,
4791
4873
  mkdirSync as mkdirSync4,
4792
4874
  readFileSync as readFileSync4,
4793
4875
  writeFileSync as writeFileSync3,
@@ -4807,7 +4889,7 @@ function getStoragePath(sessionID) {
4807
4889
  }
4808
4890
  function loadInjectedPaths(sessionID) {
4809
4891
  const filePath = getStoragePath(sessionID);
4810
- if (!existsSync9(filePath))
4892
+ if (!existsSync10(filePath))
4811
4893
  return new Set;
4812
4894
  try {
4813
4895
  const content = readFileSync4(filePath, "utf-8");
@@ -4818,7 +4900,7 @@ function loadInjectedPaths(sessionID) {
4818
4900
  }
4819
4901
  }
4820
4902
  function saveInjectedPaths(sessionID, paths) {
4821
- if (!existsSync9(AGENTS_INJECTOR_STORAGE)) {
4903
+ if (!existsSync10(AGENTS_INJECTOR_STORAGE)) {
4822
4904
  mkdirSync4(AGENTS_INJECTOR_STORAGE, { recursive: true });
4823
4905
  }
4824
4906
  const data = {
@@ -4830,7 +4912,7 @@ function saveInjectedPaths(sessionID, paths) {
4830
4912
  }
4831
4913
  function clearInjectedPaths(sessionID) {
4832
4914
  const filePath = getStoragePath(sessionID);
4833
- if (existsSync9(filePath)) {
4915
+ if (existsSync10(filePath)) {
4834
4916
  unlinkSync3(filePath);
4835
4917
  }
4836
4918
  }
@@ -4838,25 +4920,26 @@ function clearInjectedPaths(sessionID) {
4838
4920
  // src/hooks/directory-agents-injector/index.ts
4839
4921
  function createDirectoryAgentsInjectorHook(ctx) {
4840
4922
  const sessionCaches = new Map;
4923
+ const pendingBatchReads = new Map;
4841
4924
  function getSessionCache(sessionID) {
4842
4925
  if (!sessionCaches.has(sessionID)) {
4843
4926
  sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
4844
4927
  }
4845
4928
  return sessionCaches.get(sessionID);
4846
4929
  }
4847
- function resolveFilePath2(title) {
4848
- if (!title)
4930
+ function resolveFilePath2(path4) {
4931
+ if (!path4)
4849
4932
  return null;
4850
- if (title.startsWith("/"))
4851
- return title;
4852
- return resolve2(ctx.directory, title);
4933
+ if (path4.startsWith("/"))
4934
+ return path4;
4935
+ return resolve2(ctx.directory, path4);
4853
4936
  }
4854
4937
  function findAgentsMdUp(startDir) {
4855
4938
  const found = [];
4856
4939
  let current = startDir;
4857
4940
  while (true) {
4858
4941
  const agentsPath = join14(current, AGENTS_FILENAME);
4859
- if (existsSync10(agentsPath)) {
4942
+ if (existsSync11(agentsPath)) {
4860
4943
  found.push(agentsPath);
4861
4944
  }
4862
4945
  if (current === ctx.directory)
@@ -4870,35 +4953,59 @@ function createDirectoryAgentsInjectorHook(ctx) {
4870
4953
  }
4871
4954
  return found.reverse();
4872
4955
  }
4873
- const toolExecuteAfter = async (input, output) => {
4874
- if (input.tool.toLowerCase() !== "read")
4956
+ function processFilePathForInjection(filePath, sessionID, output) {
4957
+ const resolved = resolveFilePath2(filePath);
4958
+ if (!resolved)
4875
4959
  return;
4876
- const filePath = resolveFilePath2(output.title);
4877
- if (!filePath)
4878
- return;
4879
- const dir = dirname2(filePath);
4880
- const cache = getSessionCache(input.sessionID);
4960
+ const dir = dirname2(resolved);
4961
+ const cache = getSessionCache(sessionID);
4881
4962
  const agentsPaths = findAgentsMdUp(dir);
4882
- const toInject = [];
4883
4963
  for (const agentsPath of agentsPaths) {
4884
4964
  const agentsDir = dirname2(agentsPath);
4885
4965
  if (cache.has(agentsDir))
4886
4966
  continue;
4887
4967
  try {
4888
4968
  const content = readFileSync5(agentsPath, "utf-8");
4889
- toInject.push({ path: agentsPath, content });
4969
+ output.output += `
4970
+
4971
+ [Directory Context: ${agentsPath}]
4972
+ ${content}`;
4890
4973
  cache.add(agentsDir);
4891
4974
  } catch {}
4892
4975
  }
4893
- if (toInject.length === 0)
4976
+ saveInjectedPaths(sessionID, cache);
4977
+ }
4978
+ const toolExecuteBefore = async (input, output) => {
4979
+ if (input.tool.toLowerCase() !== "batch")
4894
4980
  return;
4895
- for (const { path: path4, content } of toInject) {
4896
- output.output += `
4897
-
4898
- [Directory Context: ${path4}]
4899
- ${content}`;
4981
+ const args = output.args;
4982
+ if (!args?.tool_calls)
4983
+ return;
4984
+ const readFilePaths = [];
4985
+ for (const call of args.tool_calls) {
4986
+ if (call.tool.toLowerCase() === "read" && call.parameters?.filePath) {
4987
+ readFilePaths.push(call.parameters.filePath);
4988
+ }
4989
+ }
4990
+ if (readFilePaths.length > 0) {
4991
+ pendingBatchReads.set(input.callID, readFilePaths);
4992
+ }
4993
+ };
4994
+ const toolExecuteAfter = async (input, output) => {
4995
+ const toolName = input.tool.toLowerCase();
4996
+ if (toolName === "read") {
4997
+ processFilePathForInjection(output.title, input.sessionID, output);
4998
+ return;
4999
+ }
5000
+ if (toolName === "batch") {
5001
+ const filePaths = pendingBatchReads.get(input.callID);
5002
+ if (filePaths) {
5003
+ for (const filePath of filePaths) {
5004
+ processFilePathForInjection(filePath, input.sessionID, output);
5005
+ }
5006
+ pendingBatchReads.delete(input.callID);
5007
+ }
4900
5008
  }
4901
- saveInjectedPaths(input.sessionID, cache);
4902
5009
  };
4903
5010
  const eventHandler = async ({ event }) => {
4904
5011
  const props = event.properties;
@@ -4918,17 +5025,18 @@ ${content}`;
4918
5025
  }
4919
5026
  };
4920
5027
  return {
5028
+ "tool.execute.before": toolExecuteBefore,
4921
5029
  "tool.execute.after": toolExecuteAfter,
4922
5030
  event: eventHandler
4923
5031
  };
4924
5032
  }
4925
5033
  // src/hooks/directory-readme-injector/index.ts
4926
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
5034
+ import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
4927
5035
  import { dirname as dirname3, join as join17, resolve as resolve3 } from "path";
4928
5036
 
4929
5037
  // src/hooks/directory-readme-injector/storage.ts
4930
5038
  import {
4931
- existsSync as existsSync11,
5039
+ existsSync as existsSync12,
4932
5040
  mkdirSync as mkdirSync5,
4933
5041
  readFileSync as readFileSync6,
4934
5042
  writeFileSync as writeFileSync4,
@@ -4948,7 +5056,7 @@ function getStoragePath2(sessionID) {
4948
5056
  }
4949
5057
  function loadInjectedPaths2(sessionID) {
4950
5058
  const filePath = getStoragePath2(sessionID);
4951
- if (!existsSync11(filePath))
5059
+ if (!existsSync12(filePath))
4952
5060
  return new Set;
4953
5061
  try {
4954
5062
  const content = readFileSync6(filePath, "utf-8");
@@ -4959,7 +5067,7 @@ function loadInjectedPaths2(sessionID) {
4959
5067
  }
4960
5068
  }
4961
5069
  function saveInjectedPaths2(sessionID, paths) {
4962
- if (!existsSync11(README_INJECTOR_STORAGE)) {
5070
+ if (!existsSync12(README_INJECTOR_STORAGE)) {
4963
5071
  mkdirSync5(README_INJECTOR_STORAGE, { recursive: true });
4964
5072
  }
4965
5073
  const data = {
@@ -4971,7 +5079,7 @@ function saveInjectedPaths2(sessionID, paths) {
4971
5079
  }
4972
5080
  function clearInjectedPaths2(sessionID) {
4973
5081
  const filePath = getStoragePath2(sessionID);
4974
- if (existsSync11(filePath)) {
5082
+ if (existsSync12(filePath)) {
4975
5083
  unlinkSync4(filePath);
4976
5084
  }
4977
5085
  }
@@ -4979,25 +5087,26 @@ function clearInjectedPaths2(sessionID) {
4979
5087
  // src/hooks/directory-readme-injector/index.ts
4980
5088
  function createDirectoryReadmeInjectorHook(ctx) {
4981
5089
  const sessionCaches = new Map;
5090
+ const pendingBatchReads = new Map;
4982
5091
  function getSessionCache(sessionID) {
4983
5092
  if (!sessionCaches.has(sessionID)) {
4984
5093
  sessionCaches.set(sessionID, loadInjectedPaths2(sessionID));
4985
5094
  }
4986
5095
  return sessionCaches.get(sessionID);
4987
5096
  }
4988
- function resolveFilePath2(title) {
4989
- if (!title)
5097
+ function resolveFilePath2(path4) {
5098
+ if (!path4)
4990
5099
  return null;
4991
- if (title.startsWith("/"))
4992
- return title;
4993
- return resolve3(ctx.directory, title);
5100
+ if (path4.startsWith("/"))
5101
+ return path4;
5102
+ return resolve3(ctx.directory, path4);
4994
5103
  }
4995
5104
  function findReadmeMdUp(startDir) {
4996
5105
  const found = [];
4997
5106
  let current = startDir;
4998
5107
  while (true) {
4999
5108
  const readmePath = join17(current, README_FILENAME);
5000
- if (existsSync12(readmePath)) {
5109
+ if (existsSync13(readmePath)) {
5001
5110
  found.push(readmePath);
5002
5111
  }
5003
5112
  if (current === ctx.directory)
@@ -5011,35 +5120,59 @@ function createDirectoryReadmeInjectorHook(ctx) {
5011
5120
  }
5012
5121
  return found.reverse();
5013
5122
  }
5014
- const toolExecuteAfter = async (input, output) => {
5015
- if (input.tool.toLowerCase() !== "read")
5123
+ function processFilePathForInjection(filePath, sessionID, output) {
5124
+ const resolved = resolveFilePath2(filePath);
5125
+ if (!resolved)
5016
5126
  return;
5017
- const filePath = resolveFilePath2(output.title);
5018
- if (!filePath)
5019
- return;
5020
- const dir = dirname3(filePath);
5021
- const cache = getSessionCache(input.sessionID);
5127
+ const dir = dirname3(resolved);
5128
+ const cache = getSessionCache(sessionID);
5022
5129
  const readmePaths = findReadmeMdUp(dir);
5023
- const toInject = [];
5024
5130
  for (const readmePath of readmePaths) {
5025
5131
  const readmeDir = dirname3(readmePath);
5026
5132
  if (cache.has(readmeDir))
5027
5133
  continue;
5028
5134
  try {
5029
5135
  const content = readFileSync7(readmePath, "utf-8");
5030
- toInject.push({ path: readmePath, content });
5136
+ output.output += `
5137
+
5138
+ [Project README: ${readmePath}]
5139
+ ${content}`;
5031
5140
  cache.add(readmeDir);
5032
5141
  } catch {}
5033
5142
  }
5034
- if (toInject.length === 0)
5143
+ saveInjectedPaths2(sessionID, cache);
5144
+ }
5145
+ const toolExecuteBefore = async (input, output) => {
5146
+ if (input.tool.toLowerCase() !== "batch")
5035
5147
  return;
5036
- for (const { path: path4, content } of toInject) {
5037
- output.output += `
5038
-
5039
- [Project README: ${path4}]
5040
- ${content}`;
5148
+ const args = output.args;
5149
+ if (!args?.tool_calls)
5150
+ return;
5151
+ const readFilePaths = [];
5152
+ for (const call of args.tool_calls) {
5153
+ if (call.tool.toLowerCase() === "read" && call.parameters?.filePath) {
5154
+ readFilePaths.push(call.parameters.filePath);
5155
+ }
5156
+ }
5157
+ if (readFilePaths.length > 0) {
5158
+ pendingBatchReads.set(input.callID, readFilePaths);
5159
+ }
5160
+ };
5161
+ const toolExecuteAfter = async (input, output) => {
5162
+ const toolName = input.tool.toLowerCase();
5163
+ if (toolName === "read") {
5164
+ processFilePathForInjection(output.title, input.sessionID, output);
5165
+ return;
5166
+ }
5167
+ if (toolName === "batch") {
5168
+ const filePaths = pendingBatchReads.get(input.callID);
5169
+ if (filePaths) {
5170
+ for (const filePath of filePaths) {
5171
+ processFilePathForInjection(filePath, input.sessionID, output);
5172
+ }
5173
+ pendingBatchReads.delete(input.callID);
5174
+ }
5041
5175
  }
5042
- saveInjectedPaths2(input.sessionID, cache);
5043
5176
  };
5044
5177
  const eventHandler = async ({ event }) => {
5045
5178
  const props = event.properties;
@@ -5059,6 +5192,7 @@ ${content}`;
5059
5192
  }
5060
5193
  };
5061
5194
  return {
5195
+ "tool.execute.before": toolExecuteBefore,
5062
5196
  "tool.execute.after": toolExecuteAfter,
5063
5197
  event: eventHandler
5064
5198
  };
@@ -5102,6 +5236,7 @@ var TOKEN_LIMIT_KEYWORDS = [
5102
5236
  "too many tokens",
5103
5237
  "non-empty content"
5104
5238
  ];
5239
+ var MESSAGE_INDEX_PATTERN = /messages\.(\d+)/;
5105
5240
  function extractTokensFromMessage(message) {
5106
5241
  for (const pattern of TOKEN_LIMIT_PATTERNS) {
5107
5242
  const match = message.match(pattern);
@@ -5113,6 +5248,13 @@ function extractTokensFromMessage(message) {
5113
5248
  }
5114
5249
  return null;
5115
5250
  }
5251
+ function extractMessageIndex2(text) {
5252
+ const match = text.match(MESSAGE_INDEX_PATTERN);
5253
+ if (match) {
5254
+ return parseInt(match[1], 10);
5255
+ }
5256
+ return;
5257
+ }
5116
5258
  function isTokenLimitError(text) {
5117
5259
  const lower = text.toLowerCase();
5118
5260
  return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase()));
@@ -5123,7 +5265,8 @@ function parseAnthropicTokenLimitError(err) {
5123
5265
  return {
5124
5266
  currentTokens: 0,
5125
5267
  maxTokens: 0,
5126
- errorType: "non-empty content"
5268
+ errorType: "non-empty content",
5269
+ messageIndex: extractMessageIndex2(err)
5127
5270
  };
5128
5271
  }
5129
5272
  if (isTokenLimitError(err)) {
@@ -5225,7 +5368,8 @@ function parseAnthropicTokenLimitError(err) {
5225
5368
  return {
5226
5369
  currentTokens: 0,
5227
5370
  maxTokens: 0,
5228
- errorType: "non-empty content"
5371
+ errorType: "non-empty content",
5372
+ messageIndex: extractMessageIndex2(combinedText)
5229
5373
  };
5230
5374
  }
5231
5375
  if (isTokenLimitError(combinedText)) {
@@ -5257,13 +5401,13 @@ var TRUNCATE_CONFIG = {
5257
5401
  };
5258
5402
 
5259
5403
  // src/hooks/anthropic-auto-compact/storage.ts
5260
- import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5404
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5261
5405
  import { homedir as homedir5 } from "os";
5262
5406
  import { join as join18 } from "path";
5263
5407
  var OPENCODE_STORAGE5 = join18(xdgData2 ?? "", "opencode", "storage");
5264
- if (process.platform === "darwin" && !existsSync13(OPENCODE_STORAGE5)) {
5408
+ if (process.platform === "darwin" && !existsSync14(OPENCODE_STORAGE5)) {
5265
5409
  const localShare = join18(homedir5(), ".local", "share", "opencode", "storage");
5266
- if (existsSync13(localShare)) {
5410
+ if (existsSync14(localShare)) {
5267
5411
  OPENCODE_STORAGE5 = localShare;
5268
5412
  }
5269
5413
  }
@@ -5271,15 +5415,15 @@ var MESSAGE_STORAGE3 = join18(OPENCODE_STORAGE5, "message");
5271
5415
  var PART_STORAGE3 = join18(OPENCODE_STORAGE5, "part");
5272
5416
  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.]";
5273
5417
  function getMessageDir3(sessionID) {
5274
- if (!existsSync13(MESSAGE_STORAGE3))
5418
+ if (!existsSync14(MESSAGE_STORAGE3))
5275
5419
  return "";
5276
5420
  const directPath = join18(MESSAGE_STORAGE3, sessionID);
5277
- if (existsSync13(directPath)) {
5421
+ if (existsSync14(directPath)) {
5278
5422
  return directPath;
5279
5423
  }
5280
5424
  for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
5281
5425
  const sessionPath = join18(MESSAGE_STORAGE3, dir, sessionID);
5282
- if (existsSync13(sessionPath)) {
5426
+ if (existsSync14(sessionPath)) {
5283
5427
  return sessionPath;
5284
5428
  }
5285
5429
  }
@@ -5287,7 +5431,7 @@ function getMessageDir3(sessionID) {
5287
5431
  }
5288
5432
  function getMessageIds(sessionID) {
5289
5433
  const messageDir = getMessageDir3(sessionID);
5290
- if (!messageDir || !existsSync13(messageDir))
5434
+ if (!messageDir || !existsSync14(messageDir))
5291
5435
  return [];
5292
5436
  const messageIds = [];
5293
5437
  for (const file of readdirSync4(messageDir)) {
@@ -5303,7 +5447,7 @@ function findToolResultsBySize(sessionID) {
5303
5447
  const results = [];
5304
5448
  for (const messageID of messageIds) {
5305
5449
  const partDir = join18(PART_STORAGE3, messageID);
5306
- if (!existsSync13(partDir))
5450
+ if (!existsSync14(partDir))
5307
5451
  continue;
5308
5452
  for (const file of readdirSync4(partDir)) {
5309
5453
  if (!file.endsWith(".json"))
@@ -5508,32 +5652,59 @@ function clearSessionState(autoCompactState, sessionID) {
5508
5652
  function getOrCreateEmptyContentAttempt(autoCompactState, sessionID) {
5509
5653
  return autoCompactState.emptyContentAttemptBySession.get(sessionID) ?? 0;
5510
5654
  }
5511
- async function fixEmptyMessages(sessionID, autoCompactState, client) {
5655
+ async function fixEmptyMessages(sessionID, autoCompactState, client, messageIndex) {
5512
5656
  const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
5513
5657
  autoCompactState.emptyContentAttemptBySession.set(sessionID, attempt + 1);
5514
- const emptyMessageIds = findEmptyMessages(sessionID);
5515
- if (emptyMessageIds.length === 0) {
5516
- await client.tui.showToast({
5517
- body: {
5518
- title: "Empty Content Error",
5519
- message: "No empty messages found in storage. Cannot auto-recover.",
5520
- variant: "error",
5521
- duration: 5000
5658
+ let fixed = false;
5659
+ const fixedMessageIds = [];
5660
+ if (messageIndex !== undefined) {
5661
+ const targetMessageId = findEmptyMessageByIndex(sessionID, messageIndex);
5662
+ if (targetMessageId) {
5663
+ const replaced = replaceEmptyTextParts(targetMessageId, "[user interrupted]");
5664
+ if (replaced) {
5665
+ fixed = true;
5666
+ fixedMessageIds.push(targetMessageId);
5667
+ } else {
5668
+ const injected = injectTextPart(sessionID, targetMessageId, "[user interrupted]");
5669
+ if (injected) {
5670
+ fixed = true;
5671
+ fixedMessageIds.push(targetMessageId);
5672
+ }
5522
5673
  }
5523
- }).catch(() => {});
5524
- return false;
5674
+ }
5525
5675
  }
5526
- let fixed = false;
5527
- for (const messageID of emptyMessageIds) {
5528
- const success = injectTextPart(sessionID, messageID, "[user interrupted]");
5529
- if (success)
5530
- fixed = true;
5676
+ if (!fixed) {
5677
+ const emptyMessageIds = findEmptyMessages(sessionID);
5678
+ if (emptyMessageIds.length === 0) {
5679
+ await client.tui.showToast({
5680
+ body: {
5681
+ title: "Empty Content Error",
5682
+ message: "No empty messages found in storage. Cannot auto-recover.",
5683
+ variant: "error",
5684
+ duration: 5000
5685
+ }
5686
+ }).catch(() => {});
5687
+ return false;
5688
+ }
5689
+ for (const messageID of emptyMessageIds) {
5690
+ const replaced = replaceEmptyTextParts(messageID, "[user interrupted]");
5691
+ if (replaced) {
5692
+ fixed = true;
5693
+ fixedMessageIds.push(messageID);
5694
+ } else {
5695
+ const injected = injectTextPart(sessionID, messageID, "[user interrupted]");
5696
+ if (injected) {
5697
+ fixed = true;
5698
+ fixedMessageIds.push(messageID);
5699
+ }
5700
+ }
5701
+ }
5531
5702
  }
5532
5703
  if (fixed) {
5533
5704
  await client.tui.showToast({
5534
5705
  body: {
5535
5706
  title: "Session Recovery",
5536
- message: `Fixed ${emptyMessageIds.length} empty messages. Retrying...`,
5707
+ message: `Fixed ${fixedMessageIds.length} empty message(s). Retrying...`,
5537
5708
  variant: "warning",
5538
5709
  duration: 3000
5539
5710
  }
@@ -5642,10 +5813,10 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
5642
5813
  }
5643
5814
  }
5644
5815
  const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5645
- if (experimental?.empty_message_recovery && errorData?.errorType?.includes("non-empty content")) {
5816
+ if (errorData?.errorType?.includes("non-empty content")) {
5646
5817
  const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
5647
5818
  if (attempt < 3) {
5648
- const fixed = await fixEmptyMessages(sessionID, autoCompactState, client);
5819
+ const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
5649
5820
  if (fixed) {
5650
5821
  autoCompactState.compactionInProgress.delete(sessionID);
5651
5822
  setTimeout(() => {
@@ -5888,6 +6059,200 @@ function createAnthropicAutoCompactHook(ctx, options) {
5888
6059
  event: eventHandler
5889
6060
  };
5890
6061
  }
6062
+ // src/hooks/preemptive-compaction/constants.ts
6063
+ var DEFAULT_THRESHOLD = 0.85;
6064
+ var MIN_TOKENS_FOR_COMPACTION = 50000;
6065
+ var COMPACTION_COOLDOWN_MS = 60000;
6066
+
6067
+ // src/hooks/preemptive-compaction/index.ts
6068
+ var CLAUDE_MODEL_PATTERN = /claude-(opus|sonnet|haiku)/i;
6069
+ var CLAUDE_DEFAULT_CONTEXT_LIMIT = 200000;
6070
+ function isSupportedModel(modelID) {
6071
+ return CLAUDE_MODEL_PATTERN.test(modelID);
6072
+ }
6073
+ function createState() {
6074
+ return {
6075
+ lastCompactionTime: new Map,
6076
+ compactionInProgress: new Set
6077
+ };
6078
+ }
6079
+ function createPreemptiveCompactionHook(ctx, options) {
6080
+ const experimental = options?.experimental;
6081
+ const onBeforeSummarize = options?.onBeforeSummarize;
6082
+ const getModelLimit = options?.getModelLimit;
6083
+ const enabled = experimental?.preemptive_compaction !== false;
6084
+ const threshold = experimental?.preemptive_compaction_threshold ?? DEFAULT_THRESHOLD;
6085
+ if (!enabled) {
6086
+ return { event: async () => {} };
6087
+ }
6088
+ const state2 = createState();
6089
+ const checkAndTriggerCompaction = async (sessionID, lastAssistant) => {
6090
+ if (state2.compactionInProgress.has(sessionID))
6091
+ return;
6092
+ const lastCompaction = state2.lastCompactionTime.get(sessionID) ?? 0;
6093
+ if (Date.now() - lastCompaction < COMPACTION_COOLDOWN_MS)
6094
+ return;
6095
+ if (lastAssistant.summary === true)
6096
+ return;
6097
+ const tokens = lastAssistant.tokens;
6098
+ if (!tokens)
6099
+ return;
6100
+ const modelID = lastAssistant.modelID ?? "";
6101
+ const providerID = lastAssistant.providerID ?? "";
6102
+ if (!isSupportedModel(modelID)) {
6103
+ log("[preemptive-compaction] skipping unsupported model", { modelID });
6104
+ return;
6105
+ }
6106
+ const configLimit = getModelLimit?.(providerID, modelID);
6107
+ const contextLimit = configLimit ?? CLAUDE_DEFAULT_CONTEXT_LIMIT;
6108
+ const totalUsed = tokens.input + tokens.cache.read + tokens.output;
6109
+ if (totalUsed < MIN_TOKENS_FOR_COMPACTION)
6110
+ return;
6111
+ const usageRatio = totalUsed / contextLimit;
6112
+ log("[preemptive-compaction] checking", {
6113
+ sessionID,
6114
+ totalUsed,
6115
+ contextLimit,
6116
+ usageRatio: usageRatio.toFixed(2),
6117
+ threshold
6118
+ });
6119
+ if (usageRatio < threshold)
6120
+ return;
6121
+ state2.compactionInProgress.add(sessionID);
6122
+ state2.lastCompactionTime.set(sessionID, Date.now());
6123
+ if (!providerID || !modelID) {
6124
+ state2.compactionInProgress.delete(sessionID);
6125
+ return;
6126
+ }
6127
+ await ctx.client.tui.showToast({
6128
+ body: {
6129
+ title: "Preemptive Compaction",
6130
+ message: `Context at ${(usageRatio * 100).toFixed(0)}% - compacting to prevent overflow...`,
6131
+ variant: "warning",
6132
+ duration: 3000
6133
+ }
6134
+ }).catch(() => {});
6135
+ log("[preemptive-compaction] triggering compaction", { sessionID, usageRatio });
6136
+ try {
6137
+ if (onBeforeSummarize) {
6138
+ await onBeforeSummarize({
6139
+ sessionID,
6140
+ providerID,
6141
+ modelID,
6142
+ usageRatio,
6143
+ directory: ctx.directory
6144
+ });
6145
+ }
6146
+ await ctx.client.session.summarize({
6147
+ path: { id: sessionID },
6148
+ body: { providerID, modelID },
6149
+ query: { directory: ctx.directory }
6150
+ });
6151
+ await ctx.client.tui.showToast({
6152
+ body: {
6153
+ title: "Compaction Complete",
6154
+ message: "Session compacted successfully",
6155
+ variant: "success",
6156
+ duration: 2000
6157
+ }
6158
+ }).catch(() => {});
6159
+ } catch (err) {
6160
+ log("[preemptive-compaction] compaction failed", { sessionID, error: err });
6161
+ } finally {
6162
+ state2.compactionInProgress.delete(sessionID);
6163
+ }
6164
+ };
6165
+ const eventHandler = async ({ event }) => {
6166
+ const props = event.properties;
6167
+ if (event.type === "session.deleted") {
6168
+ const sessionInfo = props?.info;
6169
+ if (sessionInfo?.id) {
6170
+ state2.lastCompactionTime.delete(sessionInfo.id);
6171
+ state2.compactionInProgress.delete(sessionInfo.id);
6172
+ }
6173
+ return;
6174
+ }
6175
+ if (event.type === "message.updated") {
6176
+ const info = props?.info;
6177
+ if (!info)
6178
+ return;
6179
+ if (info.role !== "assistant" || !info.finish)
6180
+ return;
6181
+ const sessionID = info.sessionID;
6182
+ if (!sessionID)
6183
+ return;
6184
+ await checkAndTriggerCompaction(sessionID, info);
6185
+ return;
6186
+ }
6187
+ if (event.type === "session.idle") {
6188
+ const sessionID = props?.sessionID;
6189
+ if (!sessionID)
6190
+ return;
6191
+ try {
6192
+ const resp = await ctx.client.session.messages({
6193
+ path: { id: sessionID },
6194
+ query: { directory: ctx.directory }
6195
+ });
6196
+ const messages = resp.data ?? resp;
6197
+ const assistants = messages.filter((m) => m.info.role === "assistant").map((m) => m.info);
6198
+ if (assistants.length === 0)
6199
+ return;
6200
+ const lastAssistant = assistants[assistants.length - 1];
6201
+ await checkAndTriggerCompaction(sessionID, lastAssistant);
6202
+ } catch {}
6203
+ }
6204
+ };
6205
+ return {
6206
+ event: eventHandler
6207
+ };
6208
+ }
6209
+ // src/hooks/compaction-context-injector/index.ts
6210
+ var SUMMARIZE_CONTEXT_PROMPT = `[COMPACTION CONTEXT INJECTION]
6211
+
6212
+ When summarizing this session, you MUST include the following sections in your summary:
6213
+
6214
+ ## 1. User Requests (As-Is)
6215
+ - List all original user requests exactly as they were stated
6216
+ - Preserve the user's exact wording and intent
6217
+
6218
+ ## 2. Final Goal
6219
+ - What the user ultimately wanted to achieve
6220
+ - The end result or deliverable expected
6221
+
6222
+ ## 3. Work Completed
6223
+ - What has been done so far
6224
+ - Files created/modified
6225
+ - Features implemented
6226
+ - Problems solved
6227
+
6228
+ ## 4. Remaining Tasks
6229
+ - What still needs to be done
6230
+ - Pending items from the original request
6231
+ - Follow-up tasks identified during the work
6232
+
6233
+ ## 5. MUST NOT Do (Critical Constraints)
6234
+ - Things that were explicitly forbidden
6235
+ - Approaches that failed and should not be retried
6236
+ - User's explicit restrictions or preferences
6237
+ - Anti-patterns identified during the session
6238
+
6239
+ This context is critical for maintaining continuity after compaction.
6240
+ `;
6241
+ function createCompactionContextInjector() {
6242
+ return async (ctx) => {
6243
+ log("[compaction-context-injector] injecting context", { sessionID: ctx.sessionID });
6244
+ const success = injectHookMessage(ctx.sessionID, SUMMARIZE_CONTEXT_PROMPT, {
6245
+ agent: "general",
6246
+ model: { providerID: ctx.providerID, modelID: ctx.modelID },
6247
+ path: { cwd: ctx.directory }
6248
+ });
6249
+ if (success) {
6250
+ log("[compaction-context-injector] context injected", { sessionID: ctx.sessionID });
6251
+ } else {
6252
+ log("[compaction-context-injector] injection failed", { sessionID: ctx.sessionID });
6253
+ }
6254
+ };
6255
+ }
5891
6256
  // src/hooks/think-mode/detector.ts
5892
6257
  var ENGLISH_PATTERNS = [/\bultrathink\b/i, /\bthink\b/i];
5893
6258
  var MULTILINGUAL_KEYWORDS = [
@@ -6157,7 +6522,7 @@ function createThinkModeHook() {
6157
6522
  // src/hooks/claude-code-hooks/config.ts
6158
6523
  import { homedir as homedir6 } from "os";
6159
6524
  import { join as join19 } from "path";
6160
- import { existsSync as existsSync14 } from "fs";
6525
+ import { existsSync as existsSync15 } from "fs";
6161
6526
  function normalizeHookMatcher(raw) {
6162
6527
  return {
6163
6528
  matcher: raw.matcher ?? raw.pattern ?? "*",
@@ -6186,7 +6551,7 @@ function getClaudeSettingsPaths(customPath) {
6186
6551
  join19(process.cwd(), ".claude", "settings.json"),
6187
6552
  join19(process.cwd(), ".claude", "settings.local.json")
6188
6553
  ];
6189
- if (customPath && existsSync14(customPath)) {
6554
+ if (customPath && existsSync15(customPath)) {
6190
6555
  paths.unshift(customPath);
6191
6556
  }
6192
6557
  return paths;
@@ -6210,7 +6575,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6210
6575
  const paths = getClaudeSettingsPaths(customSettingsPath);
6211
6576
  let mergedConfig = {};
6212
6577
  for (const settingsPath of paths) {
6213
- if (existsSync14(settingsPath)) {
6578
+ if (existsSync15(settingsPath)) {
6214
6579
  try {
6215
6580
  const content = await Bun.file(settingsPath).text();
6216
6581
  const settings = JSON.parse(content);
@@ -6227,7 +6592,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6227
6592
  }
6228
6593
 
6229
6594
  // src/hooks/claude-code-hooks/config-loader.ts
6230
- import { existsSync as existsSync15 } from "fs";
6595
+ import { existsSync as existsSync16 } from "fs";
6231
6596
  import { homedir as homedir7 } from "os";
6232
6597
  import { join as join20 } from "path";
6233
6598
  var USER_CONFIG_PATH = join20(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
@@ -6235,7 +6600,7 @@ function getProjectConfigPath() {
6235
6600
  return join20(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6236
6601
  }
6237
6602
  async function loadConfigFromPath(path4) {
6238
- if (!existsSync15(path4)) {
6603
+ if (!existsSync16(path4)) {
6239
6604
  return null;
6240
6605
  }
6241
6606
  try {
@@ -6415,7 +6780,7 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
6415
6780
 
6416
6781
  // src/hooks/claude-code-hooks/transcript.ts
6417
6782
  import { join as join21 } from "path";
6418
- import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync16, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6783
+ import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync17, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6419
6784
  import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
6420
6785
  import { randomUUID } from "crypto";
6421
6786
  var TRANSCRIPT_DIR = join21(homedir8(), ".claude", "transcripts");
@@ -6423,7 +6788,7 @@ function getTranscriptPath(sessionId) {
6423
6788
  return join21(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6424
6789
  }
6425
6790
  function ensureTranscriptDir() {
6426
- if (!existsSync16(TRANSCRIPT_DIR)) {
6791
+ if (!existsSync17(TRANSCRIPT_DIR)) {
6427
6792
  mkdirSync6(TRANSCRIPT_DIR, { recursive: true });
6428
6793
  }
6429
6794
  }
@@ -7084,7 +7449,7 @@ import { relative as relative3, resolve as resolve4 } from "path";
7084
7449
 
7085
7450
  // src/hooks/rules-injector/finder.ts
7086
7451
  import {
7087
- existsSync as existsSync17,
7452
+ existsSync as existsSync18,
7088
7453
  readdirSync as readdirSync5,
7089
7454
  realpathSync,
7090
7455
  statSync as statSync2
@@ -7122,7 +7487,7 @@ function findProjectRoot(startPath) {
7122
7487
  while (true) {
7123
7488
  for (const marker of PROJECT_MARKERS) {
7124
7489
  const markerPath = join24(current, marker);
7125
- if (existsSync17(markerPath)) {
7490
+ if (existsSync18(markerPath)) {
7126
7491
  return current;
7127
7492
  }
7128
7493
  }
@@ -7134,7 +7499,7 @@ function findProjectRoot(startPath) {
7134
7499
  }
7135
7500
  }
7136
7501
  function findRuleFilesRecursive(dir, results) {
7137
- if (!existsSync17(dir))
7502
+ if (!existsSync18(dir))
7138
7503
  return;
7139
7504
  try {
7140
7505
  const entries = readdirSync5(dir, { withFileTypes: true });
@@ -7378,7 +7743,7 @@ function mergeGlobs(existing, newValue) {
7378
7743
 
7379
7744
  // src/hooks/rules-injector/storage.ts
7380
7745
  import {
7381
- existsSync as existsSync18,
7746
+ existsSync as existsSync19,
7382
7747
  mkdirSync as mkdirSync7,
7383
7748
  readFileSync as readFileSync9,
7384
7749
  writeFileSync as writeFileSync7,
@@ -7390,7 +7755,7 @@ function getStoragePath3(sessionID) {
7390
7755
  }
7391
7756
  function loadInjectedRules(sessionID) {
7392
7757
  const filePath = getStoragePath3(sessionID);
7393
- if (!existsSync18(filePath))
7758
+ if (!existsSync19(filePath))
7394
7759
  return { contentHashes: new Set, realPaths: new Set };
7395
7760
  try {
7396
7761
  const content = readFileSync9(filePath, "utf-8");
@@ -7404,7 +7769,7 @@ function loadInjectedRules(sessionID) {
7404
7769
  }
7405
7770
  }
7406
7771
  function saveInjectedRules(sessionID, data) {
7407
- if (!existsSync18(RULES_INJECTOR_STORAGE)) {
7772
+ if (!existsSync19(RULES_INJECTOR_STORAGE)) {
7408
7773
  mkdirSync7(RULES_INJECTOR_STORAGE, { recursive: true });
7409
7774
  }
7410
7775
  const storageData = {
@@ -7417,7 +7782,7 @@ function saveInjectedRules(sessionID, data) {
7417
7782
  }
7418
7783
  function clearInjectedRules(sessionID) {
7419
7784
  const filePath = getStoragePath3(sessionID);
7420
- if (existsSync18(filePath)) {
7785
+ if (existsSync19(filePath)) {
7421
7786
  unlinkSync6(filePath);
7422
7787
  }
7423
7788
  }
@@ -7426,29 +7791,28 @@ function clearInjectedRules(sessionID) {
7426
7791
  var TRACKED_TOOLS = ["read", "write", "edit", "multiedit"];
7427
7792
  function createRulesInjectorHook(ctx) {
7428
7793
  const sessionCaches = new Map;
7794
+ const pendingBatchFiles = new Map;
7429
7795
  function getSessionCache(sessionID) {
7430
7796
  if (!sessionCaches.has(sessionID)) {
7431
7797
  sessionCaches.set(sessionID, loadInjectedRules(sessionID));
7432
7798
  }
7433
7799
  return sessionCaches.get(sessionID);
7434
7800
  }
7435
- function resolveFilePath2(title) {
7436
- if (!title)
7801
+ function resolveFilePath2(path4) {
7802
+ if (!path4)
7437
7803
  return null;
7438
- if (title.startsWith("/"))
7439
- return title;
7440
- return resolve4(ctx.directory, title);
7804
+ if (path4.startsWith("/"))
7805
+ return path4;
7806
+ return resolve4(ctx.directory, path4);
7441
7807
  }
7442
- const toolExecuteAfter = async (input, output) => {
7443
- if (!TRACKED_TOOLS.includes(input.tool.toLowerCase()))
7444
- return;
7445
- const filePath = resolveFilePath2(output.title);
7446
- if (!filePath)
7808
+ function processFilePathForInjection(filePath, sessionID, output) {
7809
+ const resolved = resolveFilePath2(filePath);
7810
+ if (!resolved)
7447
7811
  return;
7448
- const projectRoot = findProjectRoot(filePath);
7449
- const cache2 = getSessionCache(input.sessionID);
7812
+ const projectRoot = findProjectRoot(resolved);
7813
+ const cache2 = getSessionCache(sessionID);
7450
7814
  const home = homedir10();
7451
- const ruleFileCandidates = findRuleFiles(projectRoot, home, filePath);
7815
+ const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved);
7452
7816
  const toInject = [];
7453
7817
  for (const candidate of ruleFileCandidates) {
7454
7818
  if (isDuplicateByRealPath(candidate.realPath, cache2.realPaths))
@@ -7456,7 +7820,7 @@ function createRulesInjectorHook(ctx) {
7456
7820
  try {
7457
7821
  const rawContent = readFileSync10(candidate.path, "utf-8");
7458
7822
  const { metadata, body } = parseRuleFrontmatter(rawContent);
7459
- const matchResult = shouldApplyRule(metadata, filePath, projectRoot);
7823
+ const matchResult = shouldApplyRule(metadata, resolved, projectRoot);
7460
7824
  if (!matchResult.applies)
7461
7825
  continue;
7462
7826
  const contentHash = createContentHash(body);
@@ -7483,7 +7847,46 @@ function createRulesInjectorHook(ctx) {
7483
7847
  [Match: ${rule.matchReason}]
7484
7848
  ${rule.content}`;
7485
7849
  }
7486
- saveInjectedRules(input.sessionID, cache2);
7850
+ saveInjectedRules(sessionID, cache2);
7851
+ }
7852
+ function extractFilePathFromToolCall(call) {
7853
+ const params = call.parameters;
7854
+ return params?.filePath ?? params?.file_path ?? params?.path;
7855
+ }
7856
+ const toolExecuteBefore = async (input, output) => {
7857
+ if (input.tool.toLowerCase() !== "batch")
7858
+ return;
7859
+ const args = output.args;
7860
+ if (!args?.tool_calls)
7861
+ return;
7862
+ const filePaths = [];
7863
+ for (const call of args.tool_calls) {
7864
+ if (TRACKED_TOOLS.includes(call.tool.toLowerCase())) {
7865
+ const filePath = extractFilePathFromToolCall(call);
7866
+ if (filePath) {
7867
+ filePaths.push(filePath);
7868
+ }
7869
+ }
7870
+ }
7871
+ if (filePaths.length > 0) {
7872
+ pendingBatchFiles.set(input.callID, filePaths);
7873
+ }
7874
+ };
7875
+ const toolExecuteAfter = async (input, output) => {
7876
+ const toolName = input.tool.toLowerCase();
7877
+ if (TRACKED_TOOLS.includes(toolName)) {
7878
+ processFilePathForInjection(output.title, input.sessionID, output);
7879
+ return;
7880
+ }
7881
+ if (toolName === "batch") {
7882
+ const filePaths = pendingBatchFiles.get(input.callID);
7883
+ if (filePaths) {
7884
+ for (const filePath of filePaths) {
7885
+ processFilePathForInjection(filePath, input.sessionID, output);
7886
+ }
7887
+ pendingBatchFiles.delete(input.callID);
7888
+ }
7889
+ }
7487
7890
  };
7488
7891
  const eventHandler = async ({ event }) => {
7489
7892
  const props = event.properties;
@@ -7503,6 +7906,7 @@ ${rule.content}`;
7503
7906
  }
7504
7907
  };
7505
7908
  return {
7909
+ "tool.execute.before": toolExecuteBefore,
7506
7910
  "tool.execute.after": toolExecuteAfter,
7507
7911
  event: eventHandler
7508
7912
  };
@@ -7517,7 +7921,7 @@ function createBackgroundNotificationHook(manager) {
7517
7921
  };
7518
7922
  }
7519
7923
  // src/hooks/auto-update-checker/checker.ts
7520
- import * as fs4 from "fs";
7924
+ import * as fs5 from "fs";
7521
7925
  import * as path5 from "path";
7522
7926
  import { fileURLToPath } from "url";
7523
7927
 
@@ -7561,9 +7965,9 @@ function getConfigPaths(directory) {
7561
7965
  function getLocalDevPath(directory) {
7562
7966
  for (const configPath of getConfigPaths(directory)) {
7563
7967
  try {
7564
- if (!fs4.existsSync(configPath))
7968
+ if (!fs5.existsSync(configPath))
7565
7969
  continue;
7566
- const content = fs4.readFileSync(configPath, "utf-8");
7970
+ const content = fs5.readFileSync(configPath, "utf-8");
7567
7971
  const config = JSON.parse(stripJsonComments(content));
7568
7972
  const plugins = config.plugin ?? [];
7569
7973
  for (const entry of plugins) {
@@ -7583,13 +7987,13 @@ function getLocalDevPath(directory) {
7583
7987
  }
7584
7988
  function findPackageJsonUp(startPath) {
7585
7989
  try {
7586
- const stat = fs4.statSync(startPath);
7990
+ const stat = fs5.statSync(startPath);
7587
7991
  let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
7588
7992
  for (let i = 0;i < 10; i++) {
7589
7993
  const pkgPath = path5.join(dir, "package.json");
7590
- if (fs4.existsSync(pkgPath)) {
7994
+ if (fs5.existsSync(pkgPath)) {
7591
7995
  try {
7592
- const content = fs4.readFileSync(pkgPath, "utf-8");
7996
+ const content = fs5.readFileSync(pkgPath, "utf-8");
7593
7997
  const pkg = JSON.parse(content);
7594
7998
  if (pkg.name === PACKAGE_NAME)
7595
7999
  return pkgPath;
@@ -7611,7 +8015,7 @@ function getLocalDevVersion(directory) {
7611
8015
  const pkgPath = findPackageJsonUp(localPath);
7612
8016
  if (!pkgPath)
7613
8017
  return null;
7614
- const content = fs4.readFileSync(pkgPath, "utf-8");
8018
+ const content = fs5.readFileSync(pkgPath, "utf-8");
7615
8019
  const pkg = JSON.parse(content);
7616
8020
  return pkg.version ?? null;
7617
8021
  } catch {
@@ -7621,9 +8025,9 @@ function getLocalDevVersion(directory) {
7621
8025
  function findPluginEntry(directory) {
7622
8026
  for (const configPath of getConfigPaths(directory)) {
7623
8027
  try {
7624
- if (!fs4.existsSync(configPath))
8028
+ if (!fs5.existsSync(configPath))
7625
8029
  continue;
7626
- const content = fs4.readFileSync(configPath, "utf-8");
8030
+ const content = fs5.readFileSync(configPath, "utf-8");
7627
8031
  const config = JSON.parse(stripJsonComments(content));
7628
8032
  const plugins = config.plugin ?? [];
7629
8033
  for (const entry of plugins) {
@@ -7644,8 +8048,8 @@ function findPluginEntry(directory) {
7644
8048
  }
7645
8049
  function getCachedVersion() {
7646
8050
  try {
7647
- if (fs4.existsSync(INSTALLED_PACKAGE_JSON)) {
7648
- const content = fs4.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
8051
+ if (fs5.existsSync(INSTALLED_PACKAGE_JSON)) {
8052
+ const content = fs5.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
7649
8053
  const pkg = JSON.parse(content);
7650
8054
  if (pkg.version)
7651
8055
  return pkg.version;
@@ -7655,7 +8059,7 @@ function getCachedVersion() {
7655
8059
  const currentDir = path5.dirname(fileURLToPath(import.meta.url));
7656
8060
  const pkgPath = findPackageJsonUp(currentDir);
7657
8061
  if (pkgPath) {
7658
- const content = fs4.readFileSync(pkgPath, "utf-8");
8062
+ const content = fs5.readFileSync(pkgPath, "utf-8");
7659
8063
  const pkg = JSON.parse(content);
7660
8064
  if (pkg.version)
7661
8065
  return pkg.version;
@@ -7667,7 +8071,7 @@ function getCachedVersion() {
7667
8071
  }
7668
8072
  function updatePinnedVersion(configPath, oldEntry, newVersion) {
7669
8073
  try {
7670
- const content = fs4.readFileSync(configPath, "utf-8");
8074
+ const content = fs5.readFileSync(configPath, "utf-8");
7671
8075
  const newEntry = `${PACKAGE_NAME}@${newVersion}`;
7672
8076
  const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
7673
8077
  if (!pluginMatch || pluginMatch.index === undefined) {
@@ -7699,7 +8103,7 @@ function updatePinnedVersion(configPath, oldEntry, newVersion) {
7699
8103
  log(`[auto-update-checker] No changes made to ${configPath}`);
7700
8104
  return false;
7701
8105
  }
7702
- fs4.writeFileSync(configPath, updatedContent, "utf-8");
8106
+ fs5.writeFileSync(configPath, updatedContent, "utf-8");
7703
8107
  log(`[auto-update-checker] Updated ${configPath}: ${oldEntry} \u2192 ${newEntry}`);
7704
8108
  return true;
7705
8109
  } catch (err) {
@@ -7727,17 +8131,17 @@ async function getLatestVersion() {
7727
8131
  }
7728
8132
 
7729
8133
  // src/hooks/auto-update-checker/cache.ts
7730
- import * as fs5 from "fs";
8134
+ import * as fs6 from "fs";
7731
8135
  import * as path6 from "path";
7732
8136
  function stripTrailingCommas(json) {
7733
8137
  return json.replace(/,(\s*[}\]])/g, "$1");
7734
8138
  }
7735
8139
  function removeFromBunLock(packageName) {
7736
8140
  const lockPath = path6.join(CACHE_DIR, "bun.lock");
7737
- if (!fs5.existsSync(lockPath))
8141
+ if (!fs6.existsSync(lockPath))
7738
8142
  return false;
7739
8143
  try {
7740
- const content = fs5.readFileSync(lockPath, "utf-8");
8144
+ const content = fs6.readFileSync(lockPath, "utf-8");
7741
8145
  const lock = JSON.parse(stripTrailingCommas(content));
7742
8146
  let modified = false;
7743
8147
  if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
@@ -7749,7 +8153,7 @@ function removeFromBunLock(packageName) {
7749
8153
  modified = true;
7750
8154
  }
7751
8155
  if (modified) {
7752
- fs5.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
8156
+ fs6.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
7753
8157
  log(`[auto-update-checker] Removed from bun.lock: ${packageName}`);
7754
8158
  }
7755
8159
  return modified;
@@ -7764,17 +8168,17 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
7764
8168
  let packageRemoved = false;
7765
8169
  let dependencyRemoved = false;
7766
8170
  let lockRemoved = false;
7767
- if (fs5.existsSync(pkgDir)) {
7768
- fs5.rmSync(pkgDir, { recursive: true, force: true });
8171
+ if (fs6.existsSync(pkgDir)) {
8172
+ fs6.rmSync(pkgDir, { recursive: true, force: true });
7769
8173
  log(`[auto-update-checker] Package removed: ${pkgDir}`);
7770
8174
  packageRemoved = true;
7771
8175
  }
7772
- if (fs5.existsSync(pkgJsonPath)) {
7773
- const content = fs5.readFileSync(pkgJsonPath, "utf-8");
8176
+ if (fs6.existsSync(pkgJsonPath)) {
8177
+ const content = fs6.readFileSync(pkgJsonPath, "utf-8");
7774
8178
  const pkgJson = JSON.parse(content);
7775
8179
  if (pkgJson.dependencies?.[packageName]) {
7776
8180
  delete pkgJson.dependencies[packageName];
7777
- fs5.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
8181
+ fs6.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
7778
8182
  log(`[auto-update-checker] Dependency removed from package.json: ${packageName}`);
7779
8183
  dependencyRemoved = true;
7780
8184
  }
@@ -7944,7 +8348,7 @@ async function showLocalDevToast(ctx, version, isSisyphusEnabled) {
7944
8348
  }
7945
8349
  // src/hooks/agent-usage-reminder/storage.ts
7946
8350
  import {
7947
- existsSync as existsSync21,
8351
+ existsSync as existsSync22,
7948
8352
  mkdirSync as mkdirSync8,
7949
8353
  readFileSync as readFileSync13,
7950
8354
  writeFileSync as writeFileSync10,
@@ -8004,7 +8408,7 @@ function getStoragePath4(sessionID) {
8004
8408
  }
8005
8409
  function loadAgentUsageState(sessionID) {
8006
8410
  const filePath = getStoragePath4(sessionID);
8007
- if (!existsSync21(filePath))
8411
+ if (!existsSync22(filePath))
8008
8412
  return null;
8009
8413
  try {
8010
8414
  const content = readFileSync13(filePath, "utf-8");
@@ -8014,7 +8418,7 @@ function loadAgentUsageState(sessionID) {
8014
8418
  }
8015
8419
  }
8016
8420
  function saveAgentUsageState(state2) {
8017
- if (!existsSync21(AGENT_USAGE_REMINDER_STORAGE)) {
8421
+ if (!existsSync22(AGENT_USAGE_REMINDER_STORAGE)) {
8018
8422
  mkdirSync8(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
8019
8423
  }
8020
8424
  const filePath = getStoragePath4(state2.sessionID);
@@ -8022,7 +8426,7 @@ function saveAgentUsageState(state2) {
8022
8426
  }
8023
8427
  function clearAgentUsageState(sessionID) {
8024
8428
  const filePath = getStoragePath4(sessionID);
8025
- if (existsSync21(filePath)) {
8429
+ if (existsSync22(filePath)) {
8026
8430
  unlinkSync7(filePath);
8027
8431
  }
8028
8432
  }
@@ -8249,7 +8653,7 @@ function createNonInteractiveEnvHook(_ctx) {
8249
8653
  }
8250
8654
  // src/hooks/interactive-bash-session/storage.ts
8251
8655
  import {
8252
- existsSync as existsSync22,
8656
+ existsSync as existsSync23,
8253
8657
  mkdirSync as mkdirSync9,
8254
8658
  readFileSync as readFileSync14,
8255
8659
  writeFileSync as writeFileSync11,
@@ -8276,7 +8680,7 @@ function getStoragePath5(sessionID) {
8276
8680
  }
8277
8681
  function loadInteractiveBashSessionState(sessionID) {
8278
8682
  const filePath = getStoragePath5(sessionID);
8279
- if (!existsSync22(filePath))
8683
+ if (!existsSync23(filePath))
8280
8684
  return null;
8281
8685
  try {
8282
8686
  const content = readFileSync14(filePath, "utf-8");
@@ -8291,7 +8695,7 @@ function loadInteractiveBashSessionState(sessionID) {
8291
8695
  }
8292
8696
  }
8293
8697
  function saveInteractiveBashSessionState(state2) {
8294
- if (!existsSync22(INTERACTIVE_BASH_SESSION_STORAGE)) {
8698
+ if (!existsSync23(INTERACTIVE_BASH_SESSION_STORAGE)) {
8295
8699
  mkdirSync9(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
8296
8700
  }
8297
8701
  const filePath = getStoragePath5(state2.sessionID);
@@ -8304,7 +8708,7 @@ function saveInteractiveBashSessionState(state2) {
8304
8708
  }
8305
8709
  function clearInteractiveBashSessionState(sessionID) {
8306
8710
  const filePath = getStoragePath5(sessionID);
8307
- if (existsSync22(filePath)) {
8711
+ if (existsSync23(filePath)) {
8308
8712
  unlinkSync8(filePath);
8309
8713
  }
8310
8714
  }
@@ -10248,11 +10652,11 @@ async function createGoogleAntigravityAuthPlugin({
10248
10652
  };
10249
10653
  }
10250
10654
  // src/features/claude-code-command-loader/loader.ts
10251
- import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
10655
+ import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
10252
10656
  import { homedir as homedir12 } from "os";
10253
10657
  import { join as join33, basename } from "path";
10254
10658
  function loadCommandsFromDir(commandsDir, scope) {
10255
- if (!existsSync23(commandsDir)) {
10659
+ if (!existsSync24(commandsDir)) {
10256
10660
  return [];
10257
10661
  }
10258
10662
  const entries = readdirSync6(commandsDir, { withFileTypes: true });
@@ -10323,11 +10727,11 @@ function loadOpencodeProjectCommands() {
10323
10727
  return commandsToRecord(commands);
10324
10728
  }
10325
10729
  // src/features/claude-code-skill-loader/loader.ts
10326
- import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
10730
+ import { existsSync as existsSync25, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
10327
10731
  import { homedir as homedir13 } from "os";
10328
10732
  import { join as join34 } from "path";
10329
10733
  function loadSkillsFromDir(skillsDir, scope) {
10330
- if (!existsSync24(skillsDir)) {
10734
+ if (!existsSync25(skillsDir)) {
10331
10735
  return [];
10332
10736
  }
10333
10737
  const entries = readdirSync7(skillsDir, { withFileTypes: true });
@@ -10340,7 +10744,7 @@ function loadSkillsFromDir(skillsDir, scope) {
10340
10744
  continue;
10341
10745
  const resolvedPath = resolveSymlink(skillPath);
10342
10746
  const skillMdPath = join34(resolvedPath, "SKILL.md");
10343
- if (!existsSync24(skillMdPath))
10747
+ if (!existsSync25(skillMdPath))
10344
10748
  continue;
10345
10749
  try {
10346
10750
  const content = readFileSync16(skillMdPath, "utf-8");
@@ -10393,7 +10797,7 @@ function loadProjectSkillsAsCommands() {
10393
10797
  }, {});
10394
10798
  }
10395
10799
  // src/features/claude-code-agent-loader/loader.ts
10396
- import { existsSync as existsSync25, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
10800
+ import { existsSync as existsSync26, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
10397
10801
  import { homedir as homedir14 } from "os";
10398
10802
  import { join as join35, basename as basename2 } from "path";
10399
10803
  function parseToolsConfig(toolsStr) {
@@ -10409,7 +10813,7 @@ function parseToolsConfig(toolsStr) {
10409
10813
  return result;
10410
10814
  }
10411
10815
  function loadAgentsFromDir(agentsDir, scope) {
10412
- if (!existsSync25(agentsDir)) {
10816
+ if (!existsSync26(agentsDir)) {
10413
10817
  return [];
10414
10818
  }
10415
10819
  const entries = readdirSync8(agentsDir, { withFileTypes: true });
@@ -10465,7 +10869,7 @@ function loadProjectAgents() {
10465
10869
  return result;
10466
10870
  }
10467
10871
  // src/features/claude-code-mcp-loader/loader.ts
10468
- import { existsSync as existsSync26 } from "fs";
10872
+ import { existsSync as existsSync27 } from "fs";
10469
10873
  import { homedir as homedir15 } from "os";
10470
10874
  import { join as join36 } from "path";
10471
10875
 
@@ -10542,7 +10946,7 @@ function getMcpConfigPaths() {
10542
10946
  ];
10543
10947
  }
10544
10948
  async function loadMcpConfigFile(filePath) {
10545
- if (!existsSync26(filePath)) {
10949
+ if (!existsSync27(filePath)) {
10546
10950
  return null;
10547
10951
  }
10548
10952
  try {
@@ -10637,6 +11041,32 @@ var BUILTIN_SERVERS = {
10637
11041
  command: ["vscode-eslint-language-server", "--stdio"],
10638
11042
  extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue"]
10639
11043
  },
11044
+ oxlint: {
11045
+ command: ["oxlint", "--lsp"],
11046
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue", ".astro", ".svelte"]
11047
+ },
11048
+ biome: {
11049
+ command: ["biome", "lsp-proxy", "--stdio"],
11050
+ extensions: [
11051
+ ".ts",
11052
+ ".tsx",
11053
+ ".js",
11054
+ ".jsx",
11055
+ ".mjs",
11056
+ ".cjs",
11057
+ ".mts",
11058
+ ".cts",
11059
+ ".json",
11060
+ ".jsonc",
11061
+ ".vue",
11062
+ ".astro",
11063
+ ".svelte",
11064
+ ".css",
11065
+ ".graphql",
11066
+ ".gql",
11067
+ ".html"
11068
+ ]
11069
+ },
10640
11070
  gopls: {
10641
11071
  command: ["gopls"],
10642
11072
  extensions: [".go"]
@@ -10653,6 +11083,10 @@ var BUILTIN_SERVERS = {
10653
11083
  command: ["pyright-langserver", "--stdio"],
10654
11084
  extensions: [".py", ".pyi"]
10655
11085
  },
11086
+ ty: {
11087
+ command: ["ty", "server"],
11088
+ extensions: [".py", ".pyi"]
11089
+ },
10656
11090
  ruff: {
10657
11091
  command: ["ruff", "server"],
10658
11092
  extensions: [".py", ".pyi"]
@@ -10669,6 +11103,10 @@ var BUILTIN_SERVERS = {
10669
11103
  command: ["csharp-ls"],
10670
11104
  extensions: [".cs"]
10671
11105
  },
11106
+ fsharp: {
11107
+ command: ["fsautocomplete"],
11108
+ extensions: [".fs", ".fsi", ".fsx", ".fsscript"]
11109
+ },
10672
11110
  "sourcekit-lsp": {
10673
11111
  command: ["sourcekit-lsp"],
10674
11112
  extensions: [".swift", ".objc", ".objcpp"]
@@ -10712,26 +11150,125 @@ var BUILTIN_SERVERS = {
10712
11150
  dart: {
10713
11151
  command: ["dart", "language-server", "--lsp"],
10714
11152
  extensions: [".dart"]
11153
+ },
11154
+ "terraform-ls": {
11155
+ command: ["terraform-ls", "serve"],
11156
+ extensions: [".tf", ".tfvars"]
10715
11157
  }
10716
11158
  };
10717
11159
  var EXT_TO_LANG = {
11160
+ ".abap": "abap",
11161
+ ".bat": "bat",
11162
+ ".bib": "bibtex",
11163
+ ".bibtex": "bibtex",
11164
+ ".clj": "clojure",
11165
+ ".cljs": "clojure",
11166
+ ".cljc": "clojure",
11167
+ ".edn": "clojure",
11168
+ ".coffee": "coffeescript",
11169
+ ".c": "c",
11170
+ ".cpp": "cpp",
11171
+ ".cxx": "cpp",
11172
+ ".cc": "cpp",
11173
+ ".c++": "cpp",
11174
+ ".cs": "csharp",
11175
+ ".css": "css",
11176
+ ".d": "d",
11177
+ ".pas": "pascal",
11178
+ ".pascal": "pascal",
11179
+ ".diff": "diff",
11180
+ ".patch": "diff",
11181
+ ".dart": "dart",
11182
+ ".dockerfile": "dockerfile",
11183
+ ".ex": "elixir",
11184
+ ".exs": "elixir",
11185
+ ".erl": "erlang",
11186
+ ".hrl": "erlang",
11187
+ ".fs": "fsharp",
11188
+ ".fsi": "fsharp",
11189
+ ".fsx": "fsharp",
11190
+ ".fsscript": "fsharp",
11191
+ ".gitcommit": "git-commit",
11192
+ ".gitrebase": "git-rebase",
11193
+ ".go": "go",
11194
+ ".groovy": "groovy",
11195
+ ".gleam": "gleam",
11196
+ ".hbs": "handlebars",
11197
+ ".handlebars": "handlebars",
11198
+ ".hs": "haskell",
11199
+ ".html": "html",
11200
+ ".htm": "html",
11201
+ ".ini": "ini",
11202
+ ".java": "java",
11203
+ ".js": "javascript",
11204
+ ".jsx": "javascriptreact",
11205
+ ".json": "json",
11206
+ ".jsonc": "jsonc",
11207
+ ".tex": "latex",
11208
+ ".latex": "latex",
11209
+ ".less": "less",
11210
+ ".lua": "lua",
11211
+ ".makefile": "makefile",
11212
+ makefile: "makefile",
11213
+ ".md": "markdown",
11214
+ ".markdown": "markdown",
11215
+ ".m": "objective-c",
11216
+ ".mm": "objective-cpp",
11217
+ ".pl": "perl",
11218
+ ".pm": "perl",
11219
+ ".pm6": "perl6",
11220
+ ".php": "php",
11221
+ ".ps1": "powershell",
11222
+ ".psm1": "powershell",
11223
+ ".pug": "jade",
11224
+ ".jade": "jade",
10718
11225
  ".py": "python",
10719
11226
  ".pyi": "python",
11227
+ ".r": "r",
11228
+ ".cshtml": "razor",
11229
+ ".razor": "razor",
11230
+ ".rb": "ruby",
11231
+ ".rake": "ruby",
11232
+ ".gemspec": "ruby",
11233
+ ".ru": "ruby",
11234
+ ".erb": "erb",
11235
+ ".html.erb": "erb",
11236
+ ".js.erb": "erb",
11237
+ ".css.erb": "erb",
11238
+ ".json.erb": "erb",
11239
+ ".rs": "rust",
11240
+ ".scss": "scss",
11241
+ ".sass": "sass",
11242
+ ".scala": "scala",
11243
+ ".shader": "shaderlab",
11244
+ ".sh": "shellscript",
11245
+ ".bash": "shellscript",
11246
+ ".zsh": "shellscript",
11247
+ ".ksh": "shellscript",
11248
+ ".sql": "sql",
11249
+ ".svelte": "svelte",
11250
+ ".swift": "swift",
10720
11251
  ".ts": "typescript",
10721
11252
  ".tsx": "typescriptreact",
10722
11253
  ".mts": "typescript",
10723
11254
  ".cts": "typescript",
10724
- ".js": "javascript",
10725
- ".jsx": "javascriptreact",
11255
+ ".mtsx": "typescriptreact",
11256
+ ".ctsx": "typescriptreact",
11257
+ ".xml": "xml",
11258
+ ".xsl": "xsl",
11259
+ ".yaml": "yaml",
11260
+ ".yml": "yaml",
10726
11261
  ".mjs": "javascript",
10727
11262
  ".cjs": "javascript",
10728
- ".go": "go",
10729
- ".rs": "rust",
10730
- ".c": "c",
10731
- ".cpp": "cpp",
10732
- ".cc": "cpp",
10733
- ".cxx": "cpp",
10734
- ".c++": "cpp",
11263
+ ".vue": "vue",
11264
+ ".zig": "zig",
11265
+ ".zon": "zig",
11266
+ ".astro": "astro",
11267
+ ".ml": "ocaml",
11268
+ ".mli": "ocaml",
11269
+ ".tf": "terraform",
11270
+ ".tfvars": "terraform-vars",
11271
+ ".hcl": "hcl",
10735
11272
  ".h": "c",
10736
11273
  ".hpp": "cpp",
10737
11274
  ".hh": "cpp",
@@ -10739,46 +11276,16 @@ var EXT_TO_LANG = {
10739
11276
  ".h++": "cpp",
10740
11277
  ".objc": "objective-c",
10741
11278
  ".objcpp": "objective-cpp",
10742
- ".java": "java",
10743
- ".rb": "ruby",
10744
- ".rake": "ruby",
10745
- ".gemspec": "ruby",
10746
- ".ru": "ruby",
10747
- ".lua": "lua",
10748
- ".swift": "swift",
10749
- ".cs": "csharp",
10750
- ".php": "php",
10751
- ".dart": "dart",
10752
- ".ex": "elixir",
10753
- ".exs": "elixir",
10754
- ".zig": "zig",
10755
- ".zon": "zig",
10756
- ".vue": "vue",
10757
- ".svelte": "svelte",
10758
- ".astro": "astro",
10759
- ".yaml": "yaml",
10760
- ".yml": "yaml",
10761
- ".json": "json",
10762
- ".jsonc": "jsonc",
10763
- ".html": "html",
10764
- ".htm": "html",
10765
- ".css": "css",
10766
- ".scss": "scss",
10767
- ".less": "less",
10768
- ".sh": "shellscript",
10769
- ".bash": "shellscript",
10770
- ".zsh": "shellscript",
10771
11279
  ".fish": "fish",
10772
- ".md": "markdown",
10773
- ".tf": "terraform",
10774
- ".tfvars": "terraform"
11280
+ ".graphql": "graphql",
11281
+ ".gql": "graphql"
10775
11282
  };
10776
11283
  // src/tools/lsp/config.ts
10777
- import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
11284
+ import { existsSync as existsSync28, readFileSync as readFileSync18 } from "fs";
10778
11285
  import { join as join37 } from "path";
10779
11286
  import { homedir as homedir16 } from "os";
10780
11287
  function loadJsonFile(path7) {
10781
- if (!existsSync27(path7))
11288
+ if (!existsSync28(path7))
10782
11289
  return null;
10783
11290
  try {
10784
11291
  return JSON.parse(readFileSync18(path7, "utf-8"));
@@ -10881,10 +11388,27 @@ function isServerInstalled(command) {
10881
11388
  if (command.length === 0)
10882
11389
  return false;
10883
11390
  const cmd = command[0];
11391
+ const isWindows2 = process.platform === "win32";
11392
+ const ext = isWindows2 ? ".exe" : "";
10884
11393
  const pathEnv = process.env.PATH || "";
10885
- const paths = pathEnv.split(":");
11394
+ const pathSeparator = isWindows2 ? ";" : ":";
11395
+ const paths = pathEnv.split(pathSeparator);
10886
11396
  for (const p of paths) {
10887
- if (existsSync27(join37(p, cmd))) {
11397
+ if (existsSync28(join37(p, cmd)) || existsSync28(join37(p, cmd + ext))) {
11398
+ return true;
11399
+ }
11400
+ }
11401
+ const cwd = process.cwd();
11402
+ const additionalPaths = [
11403
+ join37(cwd, "node_modules", ".bin", cmd),
11404
+ join37(cwd, "node_modules", ".bin", cmd + ext),
11405
+ join37(homedir16(), ".config", "opencode", "bin", cmd),
11406
+ join37(homedir16(), ".config", "opencode", "bin", cmd + ext),
11407
+ join37(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd),
11408
+ join37(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
11409
+ ];
11410
+ for (const p of additionalPaths) {
11411
+ if (existsSync28(p)) {
10888
11412
  return true;
10889
11413
  }
10890
11414
  }
@@ -11479,16 +12003,16 @@ ${msg}`);
11479
12003
  }
11480
12004
  // src/tools/lsp/utils.ts
11481
12005
  import { extname as extname2, resolve as resolve6 } from "path";
11482
- import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
12006
+ import { existsSync as existsSync29, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
11483
12007
  function findWorkspaceRoot(filePath) {
11484
12008
  let dir = resolve6(filePath);
11485
- if (!existsSync28(dir) || !__require("fs").statSync(dir).isDirectory()) {
12009
+ if (!existsSync29(dir) || !__require("fs").statSync(dir).isDirectory()) {
11486
12010
  dir = __require("path").dirname(dir);
11487
12011
  }
11488
12012
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
11489
12013
  while (dir !== "/") {
11490
12014
  for (const marker of markers) {
11491
- if (existsSync28(__require("path").join(dir, marker))) {
12015
+ if (existsSync29(__require("path").join(dir, marker))) {
11492
12016
  return dir;
11493
12017
  }
11494
12018
  }
@@ -24414,11 +24938,11 @@ var lsp_code_action_resolve = tool({
24414
24938
  // src/tools/ast-grep/constants.ts
24415
24939
  import { createRequire as createRequire4 } from "module";
24416
24940
  import { dirname as dirname6, join as join39 } from "path";
24417
- import { existsSync as existsSync30, statSync as statSync4 } from "fs";
24941
+ import { existsSync as existsSync31, statSync as statSync4 } from "fs";
24418
24942
 
24419
24943
  // src/tools/ast-grep/downloader.ts
24420
24944
  var {spawn: spawn5 } = globalThis.Bun;
24421
- import { existsSync as existsSync29, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24945
+ import { existsSync as existsSync30, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24422
24946
  import { join as join38 } from "path";
24423
24947
  import { homedir as homedir17 } from "os";
24424
24948
  import { createRequire as createRequire3 } from "module";
@@ -24457,7 +24981,7 @@ function getBinaryName3() {
24457
24981
  }
24458
24982
  function getCachedBinaryPath2() {
24459
24983
  const binaryPath = join38(getCacheDir3(), getBinaryName3());
24460
- return existsSync29(binaryPath) ? binaryPath : null;
24984
+ return existsSync30(binaryPath) ? binaryPath : null;
24461
24985
  }
24462
24986
  async function extractZip2(archivePath, destDir) {
24463
24987
  const proc = process.platform === "win32" ? spawn5([
@@ -24484,7 +25008,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24484
25008
  const cacheDir = getCacheDir3();
24485
25009
  const binaryName = getBinaryName3();
24486
25010
  const binaryPath = join38(cacheDir, binaryName);
24487
- if (existsSync29(binaryPath)) {
25011
+ if (existsSync30(binaryPath)) {
24488
25012
  return binaryPath;
24489
25013
  }
24490
25014
  const { arch, os: os5 } = platformInfo;
@@ -24492,7 +25016,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24492
25016
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
24493
25017
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
24494
25018
  try {
24495
- if (!existsSync29(cacheDir)) {
25019
+ if (!existsSync30(cacheDir)) {
24496
25020
  mkdirSync10(cacheDir, { recursive: true });
24497
25021
  }
24498
25022
  const response2 = await fetch(downloadUrl, { redirect: "follow" });
@@ -24503,10 +25027,10 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24503
25027
  const arrayBuffer = await response2.arrayBuffer();
24504
25028
  await Bun.write(archivePath, arrayBuffer);
24505
25029
  await extractZip2(archivePath, cacheDir);
24506
- if (existsSync29(archivePath)) {
25030
+ if (existsSync30(archivePath)) {
24507
25031
  unlinkSync9(archivePath);
24508
25032
  }
24509
- if (process.platform !== "win32" && existsSync29(binaryPath)) {
25033
+ if (process.platform !== "win32" && existsSync30(binaryPath)) {
24510
25034
  chmodSync2(binaryPath, 493);
24511
25035
  }
24512
25036
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -24558,7 +25082,7 @@ function findSgCliPathSync() {
24558
25082
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
24559
25083
  const cliDir = dirname6(cliPkgPath);
24560
25084
  const sgPath = join39(cliDir, binaryName);
24561
- if (existsSync30(sgPath) && isValidBinary(sgPath)) {
25085
+ if (existsSync31(sgPath) && isValidBinary(sgPath)) {
24562
25086
  return sgPath;
24563
25087
  }
24564
25088
  } catch {}
@@ -24570,7 +25094,7 @@ function findSgCliPathSync() {
24570
25094
  const pkgDir = dirname6(pkgPath);
24571
25095
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
24572
25096
  const binaryPath = join39(pkgDir, astGrepName);
24573
- if (existsSync30(binaryPath) && isValidBinary(binaryPath)) {
25097
+ if (existsSync31(binaryPath) && isValidBinary(binaryPath)) {
24574
25098
  return binaryPath;
24575
25099
  }
24576
25100
  } catch {}
@@ -24578,7 +25102,7 @@ function findSgCliPathSync() {
24578
25102
  if (process.platform === "darwin") {
24579
25103
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
24580
25104
  for (const path7 of homebrewPaths) {
24581
- if (existsSync30(path7) && isValidBinary(path7)) {
25105
+ if (existsSync31(path7) && isValidBinary(path7)) {
24582
25106
  return path7;
24583
25107
  }
24584
25108
  }
@@ -24634,11 +25158,11 @@ var DEFAULT_MAX_MATCHES = 500;
24634
25158
 
24635
25159
  // src/tools/ast-grep/cli.ts
24636
25160
  var {spawn: spawn6 } = globalThis.Bun;
24637
- import { existsSync as existsSync31 } from "fs";
25161
+ import { existsSync as existsSync32 } from "fs";
24638
25162
  var resolvedCliPath3 = null;
24639
25163
  var initPromise2 = null;
24640
25164
  async function getAstGrepPath() {
24641
- if (resolvedCliPath3 !== null && existsSync31(resolvedCliPath3)) {
25165
+ if (resolvedCliPath3 !== null && existsSync32(resolvedCliPath3)) {
24642
25166
  return resolvedCliPath3;
24643
25167
  }
24644
25168
  if (initPromise2) {
@@ -24646,7 +25170,7 @@ async function getAstGrepPath() {
24646
25170
  }
24647
25171
  initPromise2 = (async () => {
24648
25172
  const syncPath = findSgCliPathSync();
24649
- if (syncPath && existsSync31(syncPath)) {
25173
+ if (syncPath && existsSync32(syncPath)) {
24650
25174
  resolvedCliPath3 = syncPath;
24651
25175
  setSgCliPath(syncPath);
24652
25176
  return syncPath;
@@ -24680,7 +25204,7 @@ async function runSg(options) {
24680
25204
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
24681
25205
  args.push(...paths);
24682
25206
  let cliPath = getSgCliPath();
24683
- if (!existsSync31(cliPath) && cliPath !== "sg") {
25207
+ if (!existsSync32(cliPath) && cliPath !== "sg") {
24684
25208
  const downloadedPath = await getAstGrepPath();
24685
25209
  if (downloadedPath) {
24686
25210
  cliPath = downloadedPath;
@@ -24944,12 +25468,12 @@ var ast_grep_replace = tool({
24944
25468
  var {spawn: spawn7 } = globalThis.Bun;
24945
25469
 
24946
25470
  // src/tools/grep/constants.ts
24947
- import { existsSync as existsSync33 } from "fs";
25471
+ import { existsSync as existsSync34 } from "fs";
24948
25472
  import { join as join41, dirname as dirname7 } from "path";
24949
25473
  import { spawnSync } from "child_process";
24950
25474
 
24951
25475
  // src/tools/grep/downloader.ts
24952
- import { existsSync as existsSync32, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
25476
+ import { existsSync as existsSync33, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
24953
25477
  import { join as join40 } from "path";
24954
25478
  function getInstallDir() {
24955
25479
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
@@ -24961,7 +25485,7 @@ function getRgPath() {
24961
25485
  }
24962
25486
  function getInstalledRipgrepPath() {
24963
25487
  const rgPath = getRgPath();
24964
- return existsSync32(rgPath) ? rgPath : null;
25488
+ return existsSync33(rgPath) ? rgPath : null;
24965
25489
  }
24966
25490
 
24967
25491
  // src/tools/grep/constants.ts
@@ -24990,7 +25514,7 @@ function getOpenCodeBundledRg() {
24990
25514
  join41(execDir, "..", "libexec", rgName)
24991
25515
  ];
24992
25516
  for (const candidate of candidates) {
24993
- if (existsSync33(candidate)) {
25517
+ if (existsSync34(candidate)) {
24994
25518
  return candidate;
24995
25519
  }
24996
25520
  }
@@ -25393,11 +25917,11 @@ var glob = tool({
25393
25917
  }
25394
25918
  });
25395
25919
  // src/tools/slashcommand/tools.ts
25396
- import { existsSync as existsSync34, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
25920
+ import { existsSync as existsSync35, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
25397
25921
  import { homedir as homedir18 } from "os";
25398
25922
  import { join as join42, basename as basename3, dirname as dirname8 } from "path";
25399
25923
  function discoverCommandsFromDir(commandsDir, scope) {
25400
- if (!existsSync34(commandsDir)) {
25924
+ if (!existsSync35(commandsDir)) {
25401
25925
  return [];
25402
25926
  }
25403
25927
  const entries = readdirSync10(commandsDir, { withFileTypes: true });
@@ -25568,7 +26092,7 @@ var SkillFrontmatterSchema = exports_external.object({
25568
26092
  metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
25569
26093
  });
25570
26094
  // src/tools/skill/tools.ts
25571
- import { existsSync as existsSync35, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
26095
+ import { existsSync as existsSync36, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
25572
26096
  import { homedir as homedir19 } from "os";
25573
26097
  import { join as join43, basename as basename4 } from "path";
25574
26098
  function parseSkillFrontmatter(data) {
@@ -25581,7 +26105,7 @@ function parseSkillFrontmatter(data) {
25581
26105
  };
25582
26106
  }
25583
26107
  function discoverSkillsFromDir(skillsDir, scope) {
25584
- if (!existsSync35(skillsDir)) {
26108
+ if (!existsSync36(skillsDir)) {
25585
26109
  return [];
25586
26110
  }
25587
26111
  const entries = readdirSync11(skillsDir, { withFileTypes: true });
@@ -25593,7 +26117,7 @@ function discoverSkillsFromDir(skillsDir, scope) {
25593
26117
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25594
26118
  const resolvedPath = resolveSymlink(skillPath);
25595
26119
  const skillMdPath = join43(resolvedPath, "SKILL.md");
25596
- if (!existsSync35(skillMdPath))
26120
+ if (!existsSync36(skillMdPath))
25597
26121
  continue;
25598
26122
  try {
25599
26123
  const content = readFileSync22(skillMdPath, "utf-8");
@@ -25623,7 +26147,7 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
25623
26147
  async function parseSkillMd(skillPath) {
25624
26148
  const resolvedPath = resolveSymlink(skillPath);
25625
26149
  const skillMdPath = join43(resolvedPath, "SKILL.md");
25626
- if (!existsSync35(skillMdPath)) {
26150
+ if (!existsSync36(skillMdPath)) {
25627
26151
  return null;
25628
26152
  }
25629
26153
  try {
@@ -25641,9 +26165,9 @@ async function parseSkillMd(skillPath) {
25641
26165
  const referencesDir = join43(resolvedPath, "references");
25642
26166
  const scriptsDir = join43(resolvedPath, "scripts");
25643
26167
  const assetsDir = join43(resolvedPath, "assets");
25644
- const references = existsSync35(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
25645
- const scripts = existsSync35(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
25646
- const assets = existsSync35(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
26168
+ const references = existsSync36(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
26169
+ const scripts = existsSync36(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
26170
+ const assets = existsSync36(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
25647
26171
  return {
25648
26172
  name: metadata.name,
25649
26173
  path: resolvedPath,
@@ -25659,7 +26183,7 @@ async function parseSkillMd(skillPath) {
25659
26183
  }
25660
26184
  }
25661
26185
  async function discoverSkillsFromDirAsync(skillsDir) {
25662
- if (!existsSync35(skillsDir)) {
26186
+ if (!existsSync36(skillsDir)) {
25663
26187
  return [];
25664
26188
  }
25665
26189
  const entries = readdirSync11(skillsDir, { withFileTypes: true });
@@ -26474,17 +26998,17 @@ var builtinTools = {
26474
26998
  skill
26475
26999
  };
26476
27000
  // src/features/background-agent/manager.ts
26477
- import { existsSync as existsSync36, readdirSync as readdirSync12 } from "fs";
27001
+ import { existsSync as existsSync37, readdirSync as readdirSync12 } from "fs";
26478
27002
  import { join as join44 } from "path";
26479
27003
  function getMessageDir4(sessionID) {
26480
- if (!existsSync36(MESSAGE_STORAGE))
27004
+ if (!existsSync37(MESSAGE_STORAGE))
26481
27005
  return null;
26482
27006
  const directPath = join44(MESSAGE_STORAGE, sessionID);
26483
- if (existsSync36(directPath))
27007
+ if (existsSync37(directPath))
26484
27008
  return directPath;
26485
27009
  for (const dir of readdirSync12(MESSAGE_STORAGE)) {
26486
27010
  const sessionPath = join44(MESSAGE_STORAGE, dir, sessionID);
26487
- if (existsSync36(sessionPath))
27011
+ if (existsSync37(sessionPath))
26488
27012
  return sessionPath;
26489
27013
  }
26490
27014
  return null;
@@ -26955,8 +27479,9 @@ var SisyphusAgentConfigSchema = exports_external.object({
26955
27479
  });
26956
27480
  var ExperimentalConfigSchema = exports_external.object({
26957
27481
  aggressive_truncation: exports_external.boolean().optional(),
26958
- empty_message_recovery: exports_external.boolean().optional(),
26959
- auto_resume: exports_external.boolean().optional()
27482
+ auto_resume: exports_external.boolean().optional(),
27483
+ preemptive_compaction: exports_external.boolean().optional(),
27484
+ preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional()
26960
27485
  });
26961
27486
  var OhMyOpenCodeConfigSchema = exports_external.object({
26962
27487
  $schema: exports_external.string().optional(),
@@ -27042,7 +27567,7 @@ var PLAN_PERMISSION = {
27042
27567
  };
27043
27568
 
27044
27569
  // src/index.ts
27045
- import * as fs6 from "fs";
27570
+ import * as fs7 from "fs";
27046
27571
  import * as path7 from "path";
27047
27572
  var AGENT_NAME_MAP = {
27048
27573
  omo: "Sisyphus",
@@ -27087,7 +27612,7 @@ function migrateConfigFile(configPath, rawConfig) {
27087
27612
  }
27088
27613
  if (needsWrite) {
27089
27614
  try {
27090
- fs6.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + `
27615
+ fs7.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + `
27091
27616
  `, "utf-8");
27092
27617
  log(`Migrated config file: ${configPath} (OmO \u2192 Sisyphus)`);
27093
27618
  } catch (err) {
@@ -27098,8 +27623,8 @@ function migrateConfigFile(configPath, rawConfig) {
27098
27623
  }
27099
27624
  function loadConfigFromPath2(configPath) {
27100
27625
  try {
27101
- if (fs6.existsSync(configPath)) {
27102
- const content = fs6.readFileSync(configPath, "utf-8");
27626
+ if (fs7.existsSync(configPath)) {
27627
+ const content = fs7.readFileSync(configPath, "utf-8");
27103
27628
  const rawConfig = JSON.parse(content);
27104
27629
  migrateConfigFile(configPath, rawConfig);
27105
27630
  const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
@@ -27166,6 +27691,18 @@ var OhMyOpenCodePlugin = async (ctx) => {
27166
27691
  const pluginConfig = loadPluginConfig(ctx.directory);
27167
27692
  const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
27168
27693
  const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
27694
+ const modelContextLimitsCache = new Map;
27695
+ let anthropicContext1MEnabled = false;
27696
+ const getModelLimit = (providerID, modelID) => {
27697
+ const key = `${providerID}/${modelID}`;
27698
+ const cached2 = modelContextLimitsCache.get(key);
27699
+ if (cached2)
27700
+ return cached2;
27701
+ if (providerID === "anthropic" && anthropicContext1MEnabled && modelID.includes("sonnet")) {
27702
+ return 1e6;
27703
+ }
27704
+ return;
27705
+ };
27169
27706
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
27170
27707
  const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
27171
27708
  const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
@@ -27184,6 +27721,12 @@ var OhMyOpenCodePlugin = async (ctx) => {
27184
27721
  disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
27185
27722
  });
27186
27723
  const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx, { experimental: pluginConfig.experimental }) : null;
27724
+ const compactionContextInjector = createCompactionContextInjector();
27725
+ const preemptiveCompaction = createPreemptiveCompactionHook(ctx, {
27726
+ experimental: pluginConfig.experimental,
27727
+ onBeforeSummarize: compactionContextInjector,
27728
+ getModelLimit
27729
+ });
27187
27730
  const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
27188
27731
  const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx, {
27189
27732
  showStartupToast: isHookEnabled("startup-toast"),
@@ -27219,6 +27762,22 @@ var OhMyOpenCodePlugin = async (ctx) => {
27219
27762
  await emptyMessageSanitizer?.["experimental.chat.messages.transform"]?.(input, output);
27220
27763
  },
27221
27764
  config: async (config3) => {
27765
+ const providers = config3.provider;
27766
+ const anthropicBeta = providers?.anthropic?.options?.headers?.["anthropic-beta"];
27767
+ anthropicContext1MEnabled = anthropicBeta?.includes("context-1m") ?? false;
27768
+ if (providers) {
27769
+ for (const [providerID, providerConfig] of Object.entries(providers)) {
27770
+ const models = providerConfig?.models;
27771
+ if (models) {
27772
+ for (const [modelID, modelConfig] of Object.entries(models)) {
27773
+ const contextLimit = modelConfig?.limit?.context;
27774
+ if (contextLimit) {
27775
+ modelContextLimitsCache.set(`${providerID}/${modelID}`, contextLimit);
27776
+ }
27777
+ }
27778
+ }
27779
+ }
27780
+ }
27222
27781
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config3.model);
27223
27782
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
27224
27783
  const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
@@ -27315,6 +27874,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
27315
27874
  await rulesInjector?.event(input);
27316
27875
  await thinkMode?.event(input);
27317
27876
  await anthropicAutoCompact?.event(input);
27877
+ await preemptiveCompaction?.event(input);
27318
27878
  await agentUsageReminder?.event(input);
27319
27879
  await interactiveBashSession?.event(input);
27320
27880
  const { event } = input;
@@ -27356,6 +27916,9 @@ var OhMyOpenCodePlugin = async (ctx) => {
27356
27916
  await claudeCodeHooks["tool.execute.before"](input, output);
27357
27917
  await nonInteractiveEnv?.["tool.execute.before"](input, output);
27358
27918
  await commentChecker?.["tool.execute.before"](input, output);
27919
+ await directoryAgentsInjector?.["tool.execute.before"]?.(input, output);
27920
+ await directoryReadmeInjector?.["tool.execute.before"]?.(input, output);
27921
+ await rulesInjector?.["tool.execute.before"]?.(input, output);
27359
27922
  if (input.tool === "task") {
27360
27923
  const args = output.args;
27361
27924
  const subagentType = args.subagent_type;