oh-my-opencode 2.3.1 → 2.4.1

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
1666
1676
 
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 |
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\` |
1672
1682
 
1673
- **Detection triggers**: File extension OR keywords (UI, UX, component, button, modal, animation, styling, responsive, layout)
1683
+ #### Step 2: Ask Yourself
1674
1684
 
1675
- **YOU CANNOT**: "Just quickly fix", "It's only one line", "Too simple to delegate"
1685
+ Before touching any frontend file, think:
1686
+ > "Is this change about **how it LOOKS** or **how it WORKS**?"
1676
1687
 
1677
- ALL frontend = DELEGATE to \`frontend-ui-ux-engineer\`. Period.
1688
+ - **LOOKS** (colors, sizes, positions, animations) \u2192 DELEGATE
1689
+ - **WORKS** (data flow, API integration, state) \u2192 Handle directly
1690
+
1691
+ #### Quick Reference Examples
1692
+
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.
2419
2449
 
2420
- ## Frontend Aesthetics Guidelines
2450
+ ---
2451
+
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,40 +3258,41 @@ 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
+ const { model: _, ...restOverride } = override;
3275
+ config = mergeAgentConfig(config, restOverride);
3236
3276
  }
3277
+ result[name] = config;
3237
3278
  }
3238
3279
  return result;
3239
3280
  }
3240
3281
  // src/hooks/todo-continuation-enforcer.ts
3241
- import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
3282
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
3242
3283
  import { join as join6 } from "path";
3243
3284
 
3285
+ // src/features/claude-code-session-state/state.ts
3286
+ var subagentSessions = new Set;
3287
+ var mainSessionID;
3288
+ function setMainSession(id) {
3289
+ mainSessionID = id;
3290
+ }
3291
+ function getMainSessionID() {
3292
+ return mainSessionID;
3293
+ }
3244
3294
  // src/features/hook-message-injector/injector.ts
3245
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
3295
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
3246
3296
  import { join as join5 } from "path";
3247
3297
 
3248
3298
  // src/features/hook-message-injector/constants.ts
@@ -3284,16 +3334,16 @@ function generatePartId() {
3284
3334
  return `prt_${timestamp}${random}`;
3285
3335
  }
3286
3336
  function getOrCreateMessageDir(sessionID) {
3287
- if (!existsSync3(MESSAGE_STORAGE)) {
3337
+ if (!existsSync4(MESSAGE_STORAGE)) {
3288
3338
  mkdirSync(MESSAGE_STORAGE, { recursive: true });
3289
3339
  }
3290
3340
  const directPath = join5(MESSAGE_STORAGE, sessionID);
3291
- if (existsSync3(directPath)) {
3341
+ if (existsSync4(directPath)) {
3292
3342
  return directPath;
3293
3343
  }
3294
3344
  for (const dir of readdirSync(MESSAGE_STORAGE)) {
3295
3345
  const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
3296
- if (existsSync3(sessionPath)) {
3346
+ if (existsSync4(sessionPath)) {
3297
3347
  return sessionPath;
3298
3348
  }
3299
3349
  }
@@ -3348,7 +3398,7 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3348
3398
  try {
3349
3399
  writeFileSync(join5(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3350
3400
  const partDir = join5(PART_STORAGE, messageID);
3351
- if (!existsSync3(partDir)) {
3401
+ if (!existsSync4(partDir)) {
3352
3402
  mkdirSync(partDir, { recursive: true });
3353
3403
  }
3354
3404
  writeFileSync(join5(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
@@ -3367,14 +3417,14 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
3367
3417
  - Mark each task complete when finished
3368
3418
  - Do not stop until all tasks are done`;
3369
3419
  function getMessageDir(sessionID) {
3370
- if (!existsSync4(MESSAGE_STORAGE))
3420
+ if (!existsSync5(MESSAGE_STORAGE))
3371
3421
  return null;
3372
3422
  const directPath = join6(MESSAGE_STORAGE, sessionID);
3373
- if (existsSync4(directPath))
3423
+ if (existsSync5(directPath))
3374
3424
  return directPath;
3375
3425
  for (const dir of readdirSync2(MESSAGE_STORAGE)) {
3376
3426
  const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
3377
- if (existsSync4(sessionPath))
3427
+ if (existsSync5(sessionPath))
3378
3428
  return sessionPath;
3379
3429
  }
3380
3430
  return null;
@@ -3399,12 +3449,14 @@ function detectInterrupt(error) {
3399
3449
  }
3400
3450
  return false;
3401
3451
  }
3452
+ var COUNTDOWN_SECONDS = 2;
3453
+ var TOAST_DURATION_MS = 900;
3402
3454
  function createTodoContinuationEnforcer(ctx) {
3403
3455
  const remindedSessions = new Set;
3404
3456
  const interruptedSessions = new Set;
3405
3457
  const errorSessions = new Set;
3406
3458
  const recoveringSessions = new Set;
3407
- const pendingTimers = new Map;
3459
+ const pendingCountdowns = new Map;
3408
3460
  const markRecovering = (sessionID) => {
3409
3461
  recoveringSessions.add(sessionID);
3410
3462
  };
@@ -3422,10 +3474,10 @@ function createTodoContinuationEnforcer(ctx) {
3422
3474
  interruptedSessions.add(sessionID);
3423
3475
  }
3424
3476
  log(`[${HOOK_NAME}] session.error received`, { sessionID, isInterrupt, error: props?.error });
3425
- const timer = pendingTimers.get(sessionID);
3426
- if (timer) {
3427
- clearTimeout(timer);
3428
- pendingTimers.delete(sessionID);
3477
+ const countdown = pendingCountdowns.get(sessionID);
3478
+ if (countdown) {
3479
+ clearInterval(countdown.intervalId);
3480
+ pendingCountdowns.delete(sessionID);
3429
3481
  }
3430
3482
  }
3431
3483
  return;
@@ -3435,57 +3487,78 @@ function createTodoContinuationEnforcer(ctx) {
3435
3487
  if (!sessionID)
3436
3488
  return;
3437
3489
  log(`[${HOOK_NAME}] session.idle received`, { sessionID });
3438
- const existingTimer = pendingTimers.get(sessionID);
3439
- if (existingTimer) {
3440
- clearTimeout(existingTimer);
3441
- log(`[${HOOK_NAME}] Cancelled existing timer`, { sessionID });
3442
- }
3443
- const timer = setTimeout(async () => {
3444
- pendingTimers.delete(sessionID);
3445
- log(`[${HOOK_NAME}] Timer fired, checking conditions`, { sessionID });
3446
- if (recoveringSessions.has(sessionID)) {
3447
- log(`[${HOOK_NAME}] Skipped: session in recovery mode`, { sessionID });
3448
- return;
3449
- }
3450
- const shouldBypass = interruptedSessions.has(sessionID) || errorSessions.has(sessionID);
3490
+ const mainSessionID2 = getMainSessionID();
3491
+ if (mainSessionID2 && sessionID !== mainSessionID2) {
3492
+ log(`[${HOOK_NAME}] Skipped: not main session`, { sessionID, mainSessionID: mainSessionID2 });
3493
+ return;
3494
+ }
3495
+ const existingCountdown = pendingCountdowns.get(sessionID);
3496
+ if (existingCountdown) {
3497
+ clearInterval(existingCountdown.intervalId);
3498
+ pendingCountdowns.delete(sessionID);
3499
+ log(`[${HOOK_NAME}] Cancelled existing countdown`, { sessionID });
3500
+ }
3501
+ if (recoveringSessions.has(sessionID)) {
3502
+ log(`[${HOOK_NAME}] Skipped: session in recovery mode`, { sessionID });
3503
+ return;
3504
+ }
3505
+ const shouldBypass = interruptedSessions.has(sessionID) || errorSessions.has(sessionID);
3506
+ if (shouldBypass) {
3451
3507
  interruptedSessions.delete(sessionID);
3452
3508
  errorSessions.delete(sessionID);
3453
- if (shouldBypass) {
3454
- log(`[${HOOK_NAME}] Skipped: error/interrupt bypass`, { sessionID });
3455
- return;
3456
- }
3457
- if (remindedSessions.has(sessionID)) {
3458
- log(`[${HOOK_NAME}] Skipped: already reminded this session`, { sessionID });
3459
- return;
3460
- }
3461
- let todos = [];
3462
- try {
3463
- log(`[${HOOK_NAME}] Fetching todos for session`, { sessionID });
3464
- const response = await ctx.client.session.todo({
3465
- path: { id: sessionID }
3466
- });
3467
- todos = response.data ?? response;
3468
- log(`[${HOOK_NAME}] Todo API response`, { sessionID, todosCount: todos?.length ?? 0 });
3469
- } catch (err) {
3470
- log(`[${HOOK_NAME}] Todo API error`, { sessionID, error: String(err) });
3471
- return;
3472
- }
3473
- if (!todos || todos.length === 0) {
3474
- log(`[${HOOK_NAME}] No todos found`, { sessionID });
3509
+ log(`[${HOOK_NAME}] Skipped: error/interrupt bypass`, { sessionID });
3510
+ return;
3511
+ }
3512
+ if (remindedSessions.has(sessionID)) {
3513
+ log(`[${HOOK_NAME}] Skipped: already reminded this session`, { sessionID });
3514
+ return;
3515
+ }
3516
+ let todos = [];
3517
+ try {
3518
+ log(`[${HOOK_NAME}] Fetching todos for session`, { sessionID });
3519
+ const response = await ctx.client.session.todo({
3520
+ path: { id: sessionID }
3521
+ });
3522
+ todos = response.data ?? response;
3523
+ log(`[${HOOK_NAME}] Todo API response`, { sessionID, todosCount: todos?.length ?? 0 });
3524
+ } catch (err) {
3525
+ log(`[${HOOK_NAME}] Todo API error`, { sessionID, error: String(err) });
3526
+ return;
3527
+ }
3528
+ if (!todos || todos.length === 0) {
3529
+ log(`[${HOOK_NAME}] No todos found`, { sessionID });
3530
+ return;
3531
+ }
3532
+ const incomplete = todos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
3533
+ if (incomplete.length === 0) {
3534
+ log(`[${HOOK_NAME}] All todos completed`, { sessionID, total: todos.length });
3535
+ return;
3536
+ }
3537
+ log(`[${HOOK_NAME}] Found incomplete todos, starting countdown`, { sessionID, incomplete: incomplete.length, total: todos.length });
3538
+ const showCountdownToast = async (seconds) => {
3539
+ await ctx.client.tui.showToast({
3540
+ body: {
3541
+ title: "Todo Continuation",
3542
+ message: `Resuming in ${seconds}s... (${incomplete.length} tasks remaining)`,
3543
+ variant: "warning",
3544
+ duration: TOAST_DURATION_MS
3545
+ }
3546
+ }).catch(() => {});
3547
+ };
3548
+ const executeAfterCountdown = async () => {
3549
+ pendingCountdowns.delete(sessionID);
3550
+ log(`[${HOOK_NAME}] Countdown finished, executing continuation`, { sessionID });
3551
+ if (recoveringSessions.has(sessionID)) {
3552
+ log(`[${HOOK_NAME}] Abort: session entered recovery mode during countdown`, { sessionID });
3475
3553
  return;
3476
3554
  }
3477
- const incomplete = todos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
3478
- if (incomplete.length === 0) {
3479
- log(`[${HOOK_NAME}] All todos completed`, { sessionID, total: todos.length });
3555
+ if (interruptedSessions.has(sessionID) || errorSessions.has(sessionID)) {
3556
+ log(`[${HOOK_NAME}] Abort: error/interrupt occurred during countdown`, { sessionID });
3557
+ interruptedSessions.delete(sessionID);
3558
+ errorSessions.delete(sessionID);
3480
3559
  return;
3481
3560
  }
3482
- log(`[${HOOK_NAME}] Found incomplete todos`, { sessionID, incomplete: incomplete.length, total: todos.length });
3483
3561
  remindedSessions.add(sessionID);
3484
- if (interruptedSessions.has(sessionID) || errorSessions.has(sessionID) || recoveringSessions.has(sessionID)) {
3485
- log(`[${HOOK_NAME}] Abort occurred during delay/fetch`, { sessionID });
3486
- remindedSessions.delete(sessionID);
3487
- return;
3488
- }
3489
3562
  try {
3490
3563
  const messageDir = getMessageDir(sessionID);
3491
3564
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
@@ -3516,24 +3589,44 @@ function createTodoContinuationEnforcer(ctx) {
3516
3589
  log(`[${HOOK_NAME}] Prompt injection failed`, { sessionID, error: String(err) });
3517
3590
  remindedSessions.delete(sessionID);
3518
3591
  }
3519
- }, 5000);
3520
- pendingTimers.set(sessionID, timer);
3592
+ };
3593
+ let secondsRemaining = COUNTDOWN_SECONDS;
3594
+ showCountdownToast(secondsRemaining).catch(() => {});
3595
+ const intervalId = setInterval(() => {
3596
+ secondsRemaining--;
3597
+ if (secondsRemaining <= 0) {
3598
+ clearInterval(intervalId);
3599
+ pendingCountdowns.delete(sessionID);
3600
+ executeAfterCountdown();
3601
+ return;
3602
+ }
3603
+ const countdown = pendingCountdowns.get(sessionID);
3604
+ if (!countdown) {
3605
+ clearInterval(intervalId);
3606
+ return;
3607
+ }
3608
+ countdown.secondsRemaining = secondsRemaining;
3609
+ showCountdownToast(secondsRemaining).catch(() => {});
3610
+ }, 1000);
3611
+ pendingCountdowns.set(sessionID, { secondsRemaining, intervalId });
3521
3612
  }
3522
3613
  if (event.type === "message.updated") {
3523
3614
  const info = props?.info;
3524
3615
  const sessionID = info?.sessionID;
3525
- log(`[${HOOK_NAME}] message.updated received`, { sessionID, role: info?.role });
3526
- if (sessionID && info?.role === "user") {
3527
- const timer = pendingTimers.get(sessionID);
3528
- if (timer) {
3529
- clearTimeout(timer);
3530
- pendingTimers.delete(sessionID);
3531
- log(`[${HOOK_NAME}] Cancelled pending timer on user message`, { sessionID });
3532
- }
3533
- }
3534
- if (sessionID && info?.role === "assistant" && remindedSessions.has(sessionID)) {
3616
+ const role = info?.role;
3617
+ const finish = info?.finish;
3618
+ log(`[${HOOK_NAME}] message.updated received`, { sessionID, role, finish });
3619
+ if (sessionID && role === "user") {
3620
+ const countdown = pendingCountdowns.get(sessionID);
3621
+ if (countdown) {
3622
+ clearInterval(countdown.intervalId);
3623
+ pendingCountdowns.delete(sessionID);
3624
+ log(`[${HOOK_NAME}] Cancelled countdown on user message`, { sessionID });
3625
+ }
3626
+ }
3627
+ if (sessionID && role === "assistant" && finish) {
3535
3628
  remindedSessions.delete(sessionID);
3536
- log(`[${HOOK_NAME}] Cleared remindedSessions on assistant response`, { sessionID });
3629
+ log(`[${HOOK_NAME}] Cleared reminded state on assistant finish`, { sessionID });
3537
3630
  }
3538
3631
  }
3539
3632
  if (event.type === "session.deleted") {
@@ -3543,10 +3636,10 @@ function createTodoContinuationEnforcer(ctx) {
3543
3636
  interruptedSessions.delete(sessionInfo.id);
3544
3637
  errorSessions.delete(sessionInfo.id);
3545
3638
  recoveringSessions.delete(sessionInfo.id);
3546
- const timer = pendingTimers.get(sessionInfo.id);
3547
- if (timer) {
3548
- clearTimeout(timer);
3549
- pendingTimers.delete(sessionInfo.id);
3639
+ const countdown = pendingCountdowns.get(sessionInfo.id);
3640
+ if (countdown) {
3641
+ clearInterval(countdown.intervalId);
3642
+ pendingCountdowns.delete(sessionInfo.id);
3550
3643
  }
3551
3644
  }
3552
3645
  }
@@ -3616,17 +3709,6 @@ ${CONTEXT_REMINDER}
3616
3709
  }
3617
3710
  // src/hooks/session-notification.ts
3618
3711
  import { platform } from "os";
3619
-
3620
- // src/features/claude-code-session-state/state.ts
3621
- var subagentSessions = new Set;
3622
- var mainSessionID;
3623
- function setMainSession(id) {
3624
- mainSessionID = id;
3625
- }
3626
- function getMainSessionID() {
3627
- return mainSessionID;
3628
- }
3629
- // src/hooks/session-notification.ts
3630
3712
  function detectPlatform() {
3631
3713
  const p = platform();
3632
3714
  if (p === "darwin" || p === "linux" || p === "win32")
@@ -3859,7 +3941,7 @@ function createSessionNotification(ctx, config = {}) {
3859
3941
  };
3860
3942
  }
3861
3943
  // src/hooks/session-recovery/storage.ts
3862
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3944
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3863
3945
  import { join as join8 } from "path";
3864
3946
 
3865
3947
  // src/hooks/session-recovery/constants.ts
@@ -3899,15 +3981,15 @@ function generatePartId2() {
3899
3981
  return `prt_${timestamp}${random}`;
3900
3982
  }
3901
3983
  function getMessageDir2(sessionID) {
3902
- if (!existsSync5(MESSAGE_STORAGE2))
3984
+ if (!existsSync6(MESSAGE_STORAGE2))
3903
3985
  return "";
3904
3986
  const directPath = join8(MESSAGE_STORAGE2, sessionID);
3905
- if (existsSync5(directPath)) {
3987
+ if (existsSync6(directPath)) {
3906
3988
  return directPath;
3907
3989
  }
3908
3990
  for (const dir of readdirSync3(MESSAGE_STORAGE2)) {
3909
3991
  const sessionPath = join8(MESSAGE_STORAGE2, dir, sessionID);
3910
- if (existsSync5(sessionPath)) {
3992
+ if (existsSync6(sessionPath)) {
3911
3993
  return sessionPath;
3912
3994
  }
3913
3995
  }
@@ -3915,7 +3997,7 @@ function getMessageDir2(sessionID) {
3915
3997
  }
3916
3998
  function readMessages(sessionID) {
3917
3999
  const messageDir = getMessageDir2(sessionID);
3918
- if (!messageDir || !existsSync5(messageDir))
4000
+ if (!messageDir || !existsSync6(messageDir))
3919
4001
  return [];
3920
4002
  const messages = [];
3921
4003
  for (const file of readdirSync3(messageDir)) {
@@ -3938,7 +4020,7 @@ function readMessages(sessionID) {
3938
4020
  }
3939
4021
  function readParts(messageID) {
3940
4022
  const partDir = join8(PART_STORAGE2, messageID);
3941
- if (!existsSync5(partDir))
4023
+ if (!existsSync6(partDir))
3942
4024
  return [];
3943
4025
  const parts = [];
3944
4026
  for (const file of readdirSync3(partDir)) {
@@ -3976,7 +4058,7 @@ function messageHasContent(messageID) {
3976
4058
  }
3977
4059
  function injectTextPart(sessionID, messageID, text) {
3978
4060
  const partDir = join8(PART_STORAGE2, messageID);
3979
- if (!existsSync5(partDir)) {
4061
+ if (!existsSync6(partDir)) {
3980
4062
  mkdirSync2(partDir, { recursive: true });
3981
4063
  }
3982
4064
  const partId = generatePartId2();
@@ -4005,6 +4087,19 @@ function findEmptyMessages(sessionID) {
4005
4087
  }
4006
4088
  return emptyIds;
4007
4089
  }
4090
+ function findEmptyMessageByIndex(sessionID, targetIndex) {
4091
+ const messages = readMessages(sessionID);
4092
+ const indicesToTry = [targetIndex, targetIndex - 1, targetIndex - 2];
4093
+ for (const idx of indicesToTry) {
4094
+ if (idx < 0 || idx >= messages.length)
4095
+ continue;
4096
+ const targetMsg = messages[idx];
4097
+ if (!messageHasContent(targetMsg.id)) {
4098
+ return targetMsg.id;
4099
+ }
4100
+ }
4101
+ return null;
4102
+ }
4008
4103
  function findMessagesWithThinkingBlocks(sessionID) {
4009
4104
  const messages = readMessages(sessionID);
4010
4105
  const result = [];
@@ -4040,7 +4135,7 @@ function findMessagesWithOrphanThinking(sessionID) {
4040
4135
  }
4041
4136
  function prependThinkingPart(sessionID, messageID) {
4042
4137
  const partDir = join8(PART_STORAGE2, messageID);
4043
- if (!existsSync5(partDir)) {
4138
+ if (!existsSync6(partDir)) {
4044
4139
  mkdirSync2(partDir, { recursive: true });
4045
4140
  }
4046
4141
  const partId = `prt_0000000000_thinking`;
@@ -4061,7 +4156,7 @@ function prependThinkingPart(sessionID, messageID) {
4061
4156
  }
4062
4157
  function stripThinkingParts(messageID) {
4063
4158
  const partDir = join8(PART_STORAGE2, messageID);
4064
- if (!existsSync5(partDir))
4159
+ if (!existsSync6(partDir))
4065
4160
  return false;
4066
4161
  let anyRemoved = false;
4067
4162
  for (const file of readdirSync3(partDir)) {
@@ -4081,6 +4176,33 @@ function stripThinkingParts(messageID) {
4081
4176
  }
4082
4177
  return anyRemoved;
4083
4178
  }
4179
+ function replaceEmptyTextParts(messageID, replacementText) {
4180
+ const partDir = join8(PART_STORAGE2, messageID);
4181
+ if (!existsSync6(partDir))
4182
+ return false;
4183
+ let anyReplaced = false;
4184
+ for (const file of readdirSync3(partDir)) {
4185
+ if (!file.endsWith(".json"))
4186
+ continue;
4187
+ try {
4188
+ const filePath = join8(partDir, file);
4189
+ const content = readFileSync3(filePath, "utf-8");
4190
+ const part = JSON.parse(content);
4191
+ if (part.type === "text") {
4192
+ const textPart = part;
4193
+ if (!textPart.text?.trim()) {
4194
+ textPart.text = replacementText;
4195
+ textPart.synthetic = true;
4196
+ writeFileSync2(filePath, JSON.stringify(textPart, null, 2));
4197
+ anyReplaced = true;
4198
+ }
4199
+ }
4200
+ } catch {
4201
+ continue;
4202
+ }
4203
+ }
4204
+ return anyReplaced;
4205
+ }
4084
4206
  function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
4085
4207
  const messages = readMessages(sessionID);
4086
4208
  if (targetIndex < 0 || targetIndex >= messages.length)
@@ -4341,13 +4463,13 @@ function createSessionRecoveryHook(ctx, options) {
4341
4463
  var {spawn: spawn3 } = globalThis.Bun;
4342
4464
  import { createRequire as createRequire2 } from "module";
4343
4465
  import { dirname, join as join10 } from "path";
4344
- import { existsSync as existsSync7 } from "fs";
4345
- import * as fs2 from "fs";
4466
+ import { existsSync as existsSync8 } from "fs";
4467
+ import * as fs3 from "fs";
4346
4468
  import { tmpdir as tmpdir3 } from "os";
4347
4469
 
4348
4470
  // src/hooks/comment-checker/downloader.ts
4349
4471
  var {spawn: spawn2 } = globalThis.Bun;
4350
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
4472
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
4351
4473
  import { join as join9 } from "path";
4352
4474
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
4353
4475
  import { createRequire } from "module";
@@ -4378,7 +4500,7 @@ function getBinaryName() {
4378
4500
  }
4379
4501
  function getCachedBinaryPath() {
4380
4502
  const binaryPath = join9(getCacheDir(), getBinaryName());
4381
- return existsSync6(binaryPath) ? binaryPath : null;
4503
+ return existsSync7(binaryPath) ? binaryPath : null;
4382
4504
  }
4383
4505
  function getPackageVersion() {
4384
4506
  try {
@@ -4426,7 +4548,7 @@ async function downloadCommentChecker() {
4426
4548
  const cacheDir = getCacheDir();
4427
4549
  const binaryName = getBinaryName();
4428
4550
  const binaryPath = join9(cacheDir, binaryName);
4429
- if (existsSync6(binaryPath)) {
4551
+ if (existsSync7(binaryPath)) {
4430
4552
  debugLog("Binary already cached at:", binaryPath);
4431
4553
  return binaryPath;
4432
4554
  }
@@ -4437,7 +4559,7 @@ async function downloadCommentChecker() {
4437
4559
  debugLog(`Downloading from: ${downloadUrl}`);
4438
4560
  console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
4439
4561
  try {
4440
- if (!existsSync6(cacheDir)) {
4562
+ if (!existsSync7(cacheDir)) {
4441
4563
  mkdirSync3(cacheDir, { recursive: true });
4442
4564
  }
4443
4565
  const response = await fetch(downloadUrl, { redirect: "follow" });
@@ -4453,10 +4575,10 @@ async function downloadCommentChecker() {
4453
4575
  } else {
4454
4576
  await extractZip(archivePath, cacheDir);
4455
4577
  }
4456
- if (existsSync6(archivePath)) {
4578
+ if (existsSync7(archivePath)) {
4457
4579
  unlinkSync2(archivePath);
4458
4580
  }
4459
- if (process.platform !== "win32" && existsSync6(binaryPath)) {
4581
+ if (process.platform !== "win32" && existsSync7(binaryPath)) {
4460
4582
  chmodSync(binaryPath, 493);
4461
4583
  }
4462
4584
  debugLog(`Successfully downloaded binary to: ${binaryPath}`);
@@ -4485,7 +4607,7 @@ function debugLog2(...args) {
4485
4607
  if (DEBUG2) {
4486
4608
  const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
4487
4609
  `;
4488
- fs2.appendFileSync(DEBUG_FILE2, msg);
4610
+ fs3.appendFileSync(DEBUG_FILE2, msg);
4489
4611
  }
4490
4612
  }
4491
4613
  function getBinaryName2() {
@@ -4498,7 +4620,7 @@ function findCommentCheckerPathSync() {
4498
4620
  const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
4499
4621
  const cliDir = dirname(cliPkgPath);
4500
4622
  const binaryPath = join10(cliDir, "bin", binaryName);
4501
- if (existsSync7(binaryPath)) {
4623
+ if (existsSync8(binaryPath)) {
4502
4624
  debugLog2("found binary in main package:", binaryPath);
4503
4625
  return binaryPath;
4504
4626
  }
@@ -4524,7 +4646,7 @@ async function getCommentCheckerPath() {
4524
4646
  }
4525
4647
  initPromise = (async () => {
4526
4648
  const syncPath = findCommentCheckerPathSync();
4527
- if (syncPath && existsSync7(syncPath)) {
4649
+ if (syncPath && existsSync8(syncPath)) {
4528
4650
  resolvedCliPath = syncPath;
4529
4651
  debugLog2("using sync-resolved path:", syncPath);
4530
4652
  return syncPath;
@@ -4558,7 +4680,7 @@ async function runCommentChecker(input, cliPath) {
4558
4680
  debugLog2("comment-checker binary not found");
4559
4681
  return { hasComments: false, message: "" };
4560
4682
  }
4561
- if (!existsSync7(binaryPath)) {
4683
+ if (!existsSync8(binaryPath)) {
4562
4684
  debugLog2("comment-checker binary does not exist:", binaryPath);
4563
4685
  return { hasComments: false, message: "" };
4564
4686
  }
@@ -4591,8 +4713,8 @@ async function runCommentChecker(input, cliPath) {
4591
4713
  }
4592
4714
 
4593
4715
  // src/hooks/comment-checker/index.ts
4594
- import * as fs3 from "fs";
4595
- import { existsSync as existsSync8 } from "fs";
4716
+ import * as fs4 from "fs";
4717
+ import { existsSync as existsSync9 } from "fs";
4596
4718
  import { tmpdir as tmpdir4 } from "os";
4597
4719
  import { join as join11 } from "path";
4598
4720
  var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
@@ -4601,7 +4723,7 @@ function debugLog3(...args) {
4601
4723
  if (DEBUG3) {
4602
4724
  const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
4603
4725
  `;
4604
- fs3.appendFileSync(DEBUG_FILE3, msg);
4726
+ fs4.appendFileSync(DEBUG_FILE3, msg);
4605
4727
  }
4606
4728
  }
4607
4729
  var pendingCalls = new Map;
@@ -4672,7 +4794,7 @@ function createCommentCheckerHooks() {
4672
4794
  }
4673
4795
  try {
4674
4796
  const cliPath = await cliPathPromise;
4675
- if (!cliPath || !existsSync8(cliPath)) {
4797
+ if (!cliPath || !existsSync9(cliPath)) {
4676
4798
  debugLog3("CLI not available, skipping comment check");
4677
4799
  return;
4678
4800
  }
@@ -4743,12 +4865,12 @@ function createToolOutputTruncatorHook(ctx) {
4743
4865
  };
4744
4866
  }
4745
4867
  // src/hooks/directory-agents-injector/index.ts
4746
- import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
4868
+ import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
4747
4869
  import { dirname as dirname2, join as join14, resolve as resolve2 } from "path";
4748
4870
 
4749
4871
  // src/hooks/directory-agents-injector/storage.ts
4750
4872
  import {
4751
- existsSync as existsSync9,
4873
+ existsSync as existsSync10,
4752
4874
  mkdirSync as mkdirSync4,
4753
4875
  readFileSync as readFileSync4,
4754
4876
  writeFileSync as writeFileSync3,
@@ -4768,7 +4890,7 @@ function getStoragePath(sessionID) {
4768
4890
  }
4769
4891
  function loadInjectedPaths(sessionID) {
4770
4892
  const filePath = getStoragePath(sessionID);
4771
- if (!existsSync9(filePath))
4893
+ if (!existsSync10(filePath))
4772
4894
  return new Set;
4773
4895
  try {
4774
4896
  const content = readFileSync4(filePath, "utf-8");
@@ -4779,7 +4901,7 @@ function loadInjectedPaths(sessionID) {
4779
4901
  }
4780
4902
  }
4781
4903
  function saveInjectedPaths(sessionID, paths) {
4782
- if (!existsSync9(AGENTS_INJECTOR_STORAGE)) {
4904
+ if (!existsSync10(AGENTS_INJECTOR_STORAGE)) {
4783
4905
  mkdirSync4(AGENTS_INJECTOR_STORAGE, { recursive: true });
4784
4906
  }
4785
4907
  const data = {
@@ -4791,7 +4913,7 @@ function saveInjectedPaths(sessionID, paths) {
4791
4913
  }
4792
4914
  function clearInjectedPaths(sessionID) {
4793
4915
  const filePath = getStoragePath(sessionID);
4794
- if (existsSync9(filePath)) {
4916
+ if (existsSync10(filePath)) {
4795
4917
  unlinkSync3(filePath);
4796
4918
  }
4797
4919
  }
@@ -4817,7 +4939,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
4817
4939
  let current = startDir;
4818
4940
  while (true) {
4819
4941
  const agentsPath = join14(current, AGENTS_FILENAME);
4820
- if (existsSync10(agentsPath)) {
4942
+ if (existsSync11(agentsPath)) {
4821
4943
  found.push(agentsPath);
4822
4944
  }
4823
4945
  if (current === ctx.directory)
@@ -4884,12 +5006,12 @@ ${content}`;
4884
5006
  };
4885
5007
  }
4886
5008
  // src/hooks/directory-readme-injector/index.ts
4887
- import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
5009
+ import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
4888
5010
  import { dirname as dirname3, join as join17, resolve as resolve3 } from "path";
4889
5011
 
4890
5012
  // src/hooks/directory-readme-injector/storage.ts
4891
5013
  import {
4892
- existsSync as existsSync11,
5014
+ existsSync as existsSync12,
4893
5015
  mkdirSync as mkdirSync5,
4894
5016
  readFileSync as readFileSync6,
4895
5017
  writeFileSync as writeFileSync4,
@@ -4909,7 +5031,7 @@ function getStoragePath2(sessionID) {
4909
5031
  }
4910
5032
  function loadInjectedPaths2(sessionID) {
4911
5033
  const filePath = getStoragePath2(sessionID);
4912
- if (!existsSync11(filePath))
5034
+ if (!existsSync12(filePath))
4913
5035
  return new Set;
4914
5036
  try {
4915
5037
  const content = readFileSync6(filePath, "utf-8");
@@ -4920,7 +5042,7 @@ function loadInjectedPaths2(sessionID) {
4920
5042
  }
4921
5043
  }
4922
5044
  function saveInjectedPaths2(sessionID, paths) {
4923
- if (!existsSync11(README_INJECTOR_STORAGE)) {
5045
+ if (!existsSync12(README_INJECTOR_STORAGE)) {
4924
5046
  mkdirSync5(README_INJECTOR_STORAGE, { recursive: true });
4925
5047
  }
4926
5048
  const data = {
@@ -4932,7 +5054,7 @@ function saveInjectedPaths2(sessionID, paths) {
4932
5054
  }
4933
5055
  function clearInjectedPaths2(sessionID) {
4934
5056
  const filePath = getStoragePath2(sessionID);
4935
- if (existsSync11(filePath)) {
5057
+ if (existsSync12(filePath)) {
4936
5058
  unlinkSync4(filePath);
4937
5059
  }
4938
5060
  }
@@ -4958,7 +5080,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
4958
5080
  let current = startDir;
4959
5081
  while (true) {
4960
5082
  const readmePath = join17(current, README_FILENAME);
4961
- if (existsSync12(readmePath)) {
5083
+ if (existsSync13(readmePath)) {
4962
5084
  found.push(readmePath);
4963
5085
  }
4964
5086
  if (current === ctx.directory)
@@ -5063,6 +5185,7 @@ var TOKEN_LIMIT_KEYWORDS = [
5063
5185
  "too many tokens",
5064
5186
  "non-empty content"
5065
5187
  ];
5188
+ var MESSAGE_INDEX_PATTERN = /messages\.(\d+)/;
5066
5189
  function extractTokensFromMessage(message) {
5067
5190
  for (const pattern of TOKEN_LIMIT_PATTERNS) {
5068
5191
  const match = message.match(pattern);
@@ -5074,6 +5197,13 @@ function extractTokensFromMessage(message) {
5074
5197
  }
5075
5198
  return null;
5076
5199
  }
5200
+ function extractMessageIndex2(text) {
5201
+ const match = text.match(MESSAGE_INDEX_PATTERN);
5202
+ if (match) {
5203
+ return parseInt(match[1], 10);
5204
+ }
5205
+ return;
5206
+ }
5077
5207
  function isTokenLimitError(text) {
5078
5208
  const lower = text.toLowerCase();
5079
5209
  return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase()));
@@ -5084,7 +5214,8 @@ function parseAnthropicTokenLimitError(err) {
5084
5214
  return {
5085
5215
  currentTokens: 0,
5086
5216
  maxTokens: 0,
5087
- errorType: "non-empty content"
5217
+ errorType: "non-empty content",
5218
+ messageIndex: extractMessageIndex2(err)
5088
5219
  };
5089
5220
  }
5090
5221
  if (isTokenLimitError(err)) {
@@ -5186,7 +5317,8 @@ function parseAnthropicTokenLimitError(err) {
5186
5317
  return {
5187
5318
  currentTokens: 0,
5188
5319
  maxTokens: 0,
5189
- errorType: "non-empty content"
5320
+ errorType: "non-empty content",
5321
+ messageIndex: extractMessageIndex2(combinedText)
5190
5322
  };
5191
5323
  }
5192
5324
  if (isTokenLimitError(combinedText)) {
@@ -5218,13 +5350,13 @@ var TRUNCATE_CONFIG = {
5218
5350
  };
5219
5351
 
5220
5352
  // src/hooks/anthropic-auto-compact/storage.ts
5221
- import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5353
+ import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5222
5354
  import { homedir as homedir5 } from "os";
5223
5355
  import { join as join18 } from "path";
5224
5356
  var OPENCODE_STORAGE5 = join18(xdgData2 ?? "", "opencode", "storage");
5225
- if (process.platform === "darwin" && !existsSync13(OPENCODE_STORAGE5)) {
5357
+ if (process.platform === "darwin" && !existsSync14(OPENCODE_STORAGE5)) {
5226
5358
  const localShare = join18(homedir5(), ".local", "share", "opencode", "storage");
5227
- if (existsSync13(localShare)) {
5359
+ if (existsSync14(localShare)) {
5228
5360
  OPENCODE_STORAGE5 = localShare;
5229
5361
  }
5230
5362
  }
@@ -5232,15 +5364,15 @@ var MESSAGE_STORAGE3 = join18(OPENCODE_STORAGE5, "message");
5232
5364
  var PART_STORAGE3 = join18(OPENCODE_STORAGE5, "part");
5233
5365
  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.]";
5234
5366
  function getMessageDir3(sessionID) {
5235
- if (!existsSync13(MESSAGE_STORAGE3))
5367
+ if (!existsSync14(MESSAGE_STORAGE3))
5236
5368
  return "";
5237
5369
  const directPath = join18(MESSAGE_STORAGE3, sessionID);
5238
- if (existsSync13(directPath)) {
5370
+ if (existsSync14(directPath)) {
5239
5371
  return directPath;
5240
5372
  }
5241
5373
  for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
5242
5374
  const sessionPath = join18(MESSAGE_STORAGE3, dir, sessionID);
5243
- if (existsSync13(sessionPath)) {
5375
+ if (existsSync14(sessionPath)) {
5244
5376
  return sessionPath;
5245
5377
  }
5246
5378
  }
@@ -5248,7 +5380,7 @@ function getMessageDir3(sessionID) {
5248
5380
  }
5249
5381
  function getMessageIds(sessionID) {
5250
5382
  const messageDir = getMessageDir3(sessionID);
5251
- if (!messageDir || !existsSync13(messageDir))
5383
+ if (!messageDir || !existsSync14(messageDir))
5252
5384
  return [];
5253
5385
  const messageIds = [];
5254
5386
  for (const file of readdirSync4(messageDir)) {
@@ -5264,7 +5396,7 @@ function findToolResultsBySize(sessionID) {
5264
5396
  const results = [];
5265
5397
  for (const messageID of messageIds) {
5266
5398
  const partDir = join18(PART_STORAGE3, messageID);
5267
- if (!existsSync13(partDir))
5399
+ if (!existsSync14(partDir))
5268
5400
  continue;
5269
5401
  for (const file of readdirSync4(partDir)) {
5270
5402
  if (!file.endsWith(".json"))
@@ -5469,32 +5601,59 @@ function clearSessionState(autoCompactState, sessionID) {
5469
5601
  function getOrCreateEmptyContentAttempt(autoCompactState, sessionID) {
5470
5602
  return autoCompactState.emptyContentAttemptBySession.get(sessionID) ?? 0;
5471
5603
  }
5472
- async function fixEmptyMessages(sessionID, autoCompactState, client) {
5604
+ async function fixEmptyMessages(sessionID, autoCompactState, client, messageIndex) {
5473
5605
  const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
5474
5606
  autoCompactState.emptyContentAttemptBySession.set(sessionID, attempt + 1);
5475
- const emptyMessageIds = findEmptyMessages(sessionID);
5476
- if (emptyMessageIds.length === 0) {
5477
- await client.tui.showToast({
5478
- body: {
5479
- title: "Empty Content Error",
5480
- message: "No empty messages found in storage. Cannot auto-recover.",
5481
- variant: "error",
5482
- duration: 5000
5607
+ let fixed = false;
5608
+ const fixedMessageIds = [];
5609
+ if (messageIndex !== undefined) {
5610
+ const targetMessageId = findEmptyMessageByIndex(sessionID, messageIndex);
5611
+ if (targetMessageId) {
5612
+ const replaced = replaceEmptyTextParts(targetMessageId, "[user interrupted]");
5613
+ if (replaced) {
5614
+ fixed = true;
5615
+ fixedMessageIds.push(targetMessageId);
5616
+ } else {
5617
+ const injected = injectTextPart(sessionID, targetMessageId, "[user interrupted]");
5618
+ if (injected) {
5619
+ fixed = true;
5620
+ fixedMessageIds.push(targetMessageId);
5621
+ }
5483
5622
  }
5484
- }).catch(() => {});
5485
- return false;
5623
+ }
5486
5624
  }
5487
- let fixed = false;
5488
- for (const messageID of emptyMessageIds) {
5489
- const success = injectTextPart(sessionID, messageID, "[user interrupted]");
5490
- if (success)
5491
- fixed = true;
5625
+ if (!fixed) {
5626
+ const emptyMessageIds = findEmptyMessages(sessionID);
5627
+ if (emptyMessageIds.length === 0) {
5628
+ await client.tui.showToast({
5629
+ body: {
5630
+ title: "Empty Content Error",
5631
+ message: "No empty messages found in storage. Cannot auto-recover.",
5632
+ variant: "error",
5633
+ duration: 5000
5634
+ }
5635
+ }).catch(() => {});
5636
+ return false;
5637
+ }
5638
+ for (const messageID of emptyMessageIds) {
5639
+ const replaced = replaceEmptyTextParts(messageID, "[user interrupted]");
5640
+ if (replaced) {
5641
+ fixed = true;
5642
+ fixedMessageIds.push(messageID);
5643
+ } else {
5644
+ const injected = injectTextPart(sessionID, messageID, "[user interrupted]");
5645
+ if (injected) {
5646
+ fixed = true;
5647
+ fixedMessageIds.push(messageID);
5648
+ }
5649
+ }
5650
+ }
5492
5651
  }
5493
5652
  if (fixed) {
5494
5653
  await client.tui.showToast({
5495
5654
  body: {
5496
5655
  title: "Session Recovery",
5497
- message: `Fixed ${emptyMessageIds.length} empty messages. Retrying...`,
5656
+ message: `Fixed ${fixedMessageIds.length} empty message(s). Retrying...`,
5498
5657
  variant: "warning",
5499
5658
  duration: 3000
5500
5659
  }
@@ -5603,10 +5762,10 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
5603
5762
  }
5604
5763
  }
5605
5764
  const retryState = getOrCreateRetryState(autoCompactState, sessionID);
5606
- if (experimental?.empty_message_recovery && errorData?.errorType?.includes("non-empty content")) {
5765
+ if (errorData?.errorType?.includes("non-empty content")) {
5607
5766
  const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
5608
5767
  if (attempt < 3) {
5609
- const fixed = await fixEmptyMessages(sessionID, autoCompactState, client);
5768
+ const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
5610
5769
  if (fixed) {
5611
5770
  autoCompactState.compactionInProgress.delete(sessionID);
5612
5771
  setTimeout(() => {
@@ -5710,12 +5869,16 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
5710
5869
  });
5711
5870
  fallbackState.revertAttempt++;
5712
5871
  fallbackState.lastRevertedMessageID = pair.userMessageID;
5713
- retryState.attempt = 0;
5714
- truncateState.truncateAttempt = 0;
5715
- autoCompactState.compactionInProgress.delete(sessionID);
5716
- setTimeout(() => {
5717
- executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
5718
- }, 1000);
5872
+ clearSessionState(autoCompactState, sessionID);
5873
+ setTimeout(async () => {
5874
+ try {
5875
+ await client.session.prompt_async({
5876
+ path: { sessionID },
5877
+ body: { parts: [{ type: "text", text: "Continue" }] },
5878
+ query: { directory }
5879
+ });
5880
+ } catch {}
5881
+ }, 500);
5719
5882
  return;
5720
5883
  } catch {}
5721
5884
  } else {
@@ -5845,6 +6008,200 @@ function createAnthropicAutoCompactHook(ctx, options) {
5845
6008
  event: eventHandler
5846
6009
  };
5847
6010
  }
6011
+ // src/hooks/preemptive-compaction/constants.ts
6012
+ var DEFAULT_THRESHOLD = 0.85;
6013
+ var MIN_TOKENS_FOR_COMPACTION = 50000;
6014
+ var COMPACTION_COOLDOWN_MS = 60000;
6015
+
6016
+ // src/hooks/preemptive-compaction/index.ts
6017
+ var CLAUDE_MODEL_PATTERN = /claude-(opus|sonnet|haiku)/i;
6018
+ var CLAUDE_DEFAULT_CONTEXT_LIMIT = 200000;
6019
+ function isSupportedModel(modelID) {
6020
+ return CLAUDE_MODEL_PATTERN.test(modelID);
6021
+ }
6022
+ function createState() {
6023
+ return {
6024
+ lastCompactionTime: new Map,
6025
+ compactionInProgress: new Set
6026
+ };
6027
+ }
6028
+ function createPreemptiveCompactionHook(ctx, options) {
6029
+ const experimental = options?.experimental;
6030
+ const onBeforeSummarize = options?.onBeforeSummarize;
6031
+ const getModelLimit = options?.getModelLimit;
6032
+ const enabled = experimental?.preemptive_compaction !== false;
6033
+ const threshold = experimental?.preemptive_compaction_threshold ?? DEFAULT_THRESHOLD;
6034
+ if (!enabled) {
6035
+ return { event: async () => {} };
6036
+ }
6037
+ const state2 = createState();
6038
+ const checkAndTriggerCompaction = async (sessionID, lastAssistant) => {
6039
+ if (state2.compactionInProgress.has(sessionID))
6040
+ return;
6041
+ const lastCompaction = state2.lastCompactionTime.get(sessionID) ?? 0;
6042
+ if (Date.now() - lastCompaction < COMPACTION_COOLDOWN_MS)
6043
+ return;
6044
+ if (lastAssistant.summary === true)
6045
+ return;
6046
+ const tokens = lastAssistant.tokens;
6047
+ if (!tokens)
6048
+ return;
6049
+ const modelID = lastAssistant.modelID ?? "";
6050
+ const providerID = lastAssistant.providerID ?? "";
6051
+ if (!isSupportedModel(modelID)) {
6052
+ log("[preemptive-compaction] skipping unsupported model", { modelID });
6053
+ return;
6054
+ }
6055
+ const configLimit = getModelLimit?.(providerID, modelID);
6056
+ const contextLimit = configLimit ?? CLAUDE_DEFAULT_CONTEXT_LIMIT;
6057
+ const totalUsed = tokens.input + tokens.cache.read + tokens.output;
6058
+ if (totalUsed < MIN_TOKENS_FOR_COMPACTION)
6059
+ return;
6060
+ const usageRatio = totalUsed / contextLimit;
6061
+ log("[preemptive-compaction] checking", {
6062
+ sessionID,
6063
+ totalUsed,
6064
+ contextLimit,
6065
+ usageRatio: usageRatio.toFixed(2),
6066
+ threshold
6067
+ });
6068
+ if (usageRatio < threshold)
6069
+ return;
6070
+ state2.compactionInProgress.add(sessionID);
6071
+ state2.lastCompactionTime.set(sessionID, Date.now());
6072
+ if (!providerID || !modelID) {
6073
+ state2.compactionInProgress.delete(sessionID);
6074
+ return;
6075
+ }
6076
+ await ctx.client.tui.showToast({
6077
+ body: {
6078
+ title: "Preemptive Compaction",
6079
+ message: `Context at ${(usageRatio * 100).toFixed(0)}% - compacting to prevent overflow...`,
6080
+ variant: "warning",
6081
+ duration: 3000
6082
+ }
6083
+ }).catch(() => {});
6084
+ log("[preemptive-compaction] triggering compaction", { sessionID, usageRatio });
6085
+ try {
6086
+ if (onBeforeSummarize) {
6087
+ await onBeforeSummarize({
6088
+ sessionID,
6089
+ providerID,
6090
+ modelID,
6091
+ usageRatio,
6092
+ directory: ctx.directory
6093
+ });
6094
+ }
6095
+ await ctx.client.session.summarize({
6096
+ path: { id: sessionID },
6097
+ body: { providerID, modelID },
6098
+ query: { directory: ctx.directory }
6099
+ });
6100
+ await ctx.client.tui.showToast({
6101
+ body: {
6102
+ title: "Compaction Complete",
6103
+ message: "Session compacted successfully",
6104
+ variant: "success",
6105
+ duration: 2000
6106
+ }
6107
+ }).catch(() => {});
6108
+ } catch (err) {
6109
+ log("[preemptive-compaction] compaction failed", { sessionID, error: err });
6110
+ } finally {
6111
+ state2.compactionInProgress.delete(sessionID);
6112
+ }
6113
+ };
6114
+ const eventHandler = async ({ event }) => {
6115
+ const props = event.properties;
6116
+ if (event.type === "session.deleted") {
6117
+ const sessionInfo = props?.info;
6118
+ if (sessionInfo?.id) {
6119
+ state2.lastCompactionTime.delete(sessionInfo.id);
6120
+ state2.compactionInProgress.delete(sessionInfo.id);
6121
+ }
6122
+ return;
6123
+ }
6124
+ if (event.type === "message.updated") {
6125
+ const info = props?.info;
6126
+ if (!info)
6127
+ return;
6128
+ if (info.role !== "assistant" || !info.finish)
6129
+ return;
6130
+ const sessionID = info.sessionID;
6131
+ if (!sessionID)
6132
+ return;
6133
+ await checkAndTriggerCompaction(sessionID, info);
6134
+ return;
6135
+ }
6136
+ if (event.type === "session.idle") {
6137
+ const sessionID = props?.sessionID;
6138
+ if (!sessionID)
6139
+ return;
6140
+ try {
6141
+ const resp = await ctx.client.session.messages({
6142
+ path: { id: sessionID },
6143
+ query: { directory: ctx.directory }
6144
+ });
6145
+ const messages = resp.data ?? resp;
6146
+ const assistants = messages.filter((m) => m.info.role === "assistant").map((m) => m.info);
6147
+ if (assistants.length === 0)
6148
+ return;
6149
+ const lastAssistant = assistants[assistants.length - 1];
6150
+ await checkAndTriggerCompaction(sessionID, lastAssistant);
6151
+ } catch {}
6152
+ }
6153
+ };
6154
+ return {
6155
+ event: eventHandler
6156
+ };
6157
+ }
6158
+ // src/hooks/compaction-context-injector/index.ts
6159
+ var SUMMARIZE_CONTEXT_PROMPT = `[COMPACTION CONTEXT INJECTION]
6160
+
6161
+ When summarizing this session, you MUST include the following sections in your summary:
6162
+
6163
+ ## 1. User Requests (As-Is)
6164
+ - List all original user requests exactly as they were stated
6165
+ - Preserve the user's exact wording and intent
6166
+
6167
+ ## 2. Final Goal
6168
+ - What the user ultimately wanted to achieve
6169
+ - The end result or deliverable expected
6170
+
6171
+ ## 3. Work Completed
6172
+ - What has been done so far
6173
+ - Files created/modified
6174
+ - Features implemented
6175
+ - Problems solved
6176
+
6177
+ ## 4. Remaining Tasks
6178
+ - What still needs to be done
6179
+ - Pending items from the original request
6180
+ - Follow-up tasks identified during the work
6181
+
6182
+ ## 5. MUST NOT Do (Critical Constraints)
6183
+ - Things that were explicitly forbidden
6184
+ - Approaches that failed and should not be retried
6185
+ - User's explicit restrictions or preferences
6186
+ - Anti-patterns identified during the session
6187
+
6188
+ This context is critical for maintaining continuity after compaction.
6189
+ `;
6190
+ function createCompactionContextInjector() {
6191
+ return async (ctx) => {
6192
+ log("[compaction-context-injector] injecting context", { sessionID: ctx.sessionID });
6193
+ const success = injectHookMessage(ctx.sessionID, SUMMARIZE_CONTEXT_PROMPT, {
6194
+ agent: "general",
6195
+ model: { providerID: ctx.providerID, modelID: ctx.modelID },
6196
+ path: { cwd: ctx.directory }
6197
+ });
6198
+ if (success) {
6199
+ log("[compaction-context-injector] context injected", { sessionID: ctx.sessionID });
6200
+ } else {
6201
+ log("[compaction-context-injector] injection failed", { sessionID: ctx.sessionID });
6202
+ }
6203
+ };
6204
+ }
5848
6205
  // src/hooks/think-mode/detector.ts
5849
6206
  var ENGLISH_PATTERNS = [/\bultrathink\b/i, /\bthink\b/i];
5850
6207
  var MULTILINGUAL_KEYWORDS = [
@@ -6114,7 +6471,7 @@ function createThinkModeHook() {
6114
6471
  // src/hooks/claude-code-hooks/config.ts
6115
6472
  import { homedir as homedir6 } from "os";
6116
6473
  import { join as join19 } from "path";
6117
- import { existsSync as existsSync14 } from "fs";
6474
+ import { existsSync as existsSync15 } from "fs";
6118
6475
  function normalizeHookMatcher(raw) {
6119
6476
  return {
6120
6477
  matcher: raw.matcher ?? raw.pattern ?? "*",
@@ -6143,7 +6500,7 @@ function getClaudeSettingsPaths(customPath) {
6143
6500
  join19(process.cwd(), ".claude", "settings.json"),
6144
6501
  join19(process.cwd(), ".claude", "settings.local.json")
6145
6502
  ];
6146
- if (customPath && existsSync14(customPath)) {
6503
+ if (customPath && existsSync15(customPath)) {
6147
6504
  paths.unshift(customPath);
6148
6505
  }
6149
6506
  return paths;
@@ -6167,7 +6524,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6167
6524
  const paths = getClaudeSettingsPaths(customSettingsPath);
6168
6525
  let mergedConfig = {};
6169
6526
  for (const settingsPath of paths) {
6170
- if (existsSync14(settingsPath)) {
6527
+ if (existsSync15(settingsPath)) {
6171
6528
  try {
6172
6529
  const content = await Bun.file(settingsPath).text();
6173
6530
  const settings = JSON.parse(content);
@@ -6184,7 +6541,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6184
6541
  }
6185
6542
 
6186
6543
  // src/hooks/claude-code-hooks/config-loader.ts
6187
- import { existsSync as existsSync15 } from "fs";
6544
+ import { existsSync as existsSync16 } from "fs";
6188
6545
  import { homedir as homedir7 } from "os";
6189
6546
  import { join as join20 } from "path";
6190
6547
  var USER_CONFIG_PATH = join20(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
@@ -6192,7 +6549,7 @@ function getProjectConfigPath() {
6192
6549
  return join20(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6193
6550
  }
6194
6551
  async function loadConfigFromPath(path4) {
6195
- if (!existsSync15(path4)) {
6552
+ if (!existsSync16(path4)) {
6196
6553
  return null;
6197
6554
  }
6198
6555
  try {
@@ -6372,7 +6729,7 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
6372
6729
 
6373
6730
  // src/hooks/claude-code-hooks/transcript.ts
6374
6731
  import { join as join21 } from "path";
6375
- import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync16, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6732
+ import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync17, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6376
6733
  import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
6377
6734
  import { randomUUID } from "crypto";
6378
6735
  var TRANSCRIPT_DIR = join21(homedir8(), ".claude", "transcripts");
@@ -6380,7 +6737,7 @@ function getTranscriptPath(sessionId) {
6380
6737
  return join21(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6381
6738
  }
6382
6739
  function ensureTranscriptDir() {
6383
- if (!existsSync16(TRANSCRIPT_DIR)) {
6740
+ if (!existsSync17(TRANSCRIPT_DIR)) {
6384
6741
  mkdirSync6(TRANSCRIPT_DIR, { recursive: true });
6385
6742
  }
6386
6743
  }
@@ -7041,7 +7398,7 @@ import { relative as relative3, resolve as resolve4 } from "path";
7041
7398
 
7042
7399
  // src/hooks/rules-injector/finder.ts
7043
7400
  import {
7044
- existsSync as existsSync17,
7401
+ existsSync as existsSync18,
7045
7402
  readdirSync as readdirSync5,
7046
7403
  realpathSync,
7047
7404
  statSync as statSync2
@@ -7079,7 +7436,7 @@ function findProjectRoot(startPath) {
7079
7436
  while (true) {
7080
7437
  for (const marker of PROJECT_MARKERS) {
7081
7438
  const markerPath = join24(current, marker);
7082
- if (existsSync17(markerPath)) {
7439
+ if (existsSync18(markerPath)) {
7083
7440
  return current;
7084
7441
  }
7085
7442
  }
@@ -7091,7 +7448,7 @@ function findProjectRoot(startPath) {
7091
7448
  }
7092
7449
  }
7093
7450
  function findRuleFilesRecursive(dir, results) {
7094
- if (!existsSync17(dir))
7451
+ if (!existsSync18(dir))
7095
7452
  return;
7096
7453
  try {
7097
7454
  const entries = readdirSync5(dir, { withFileTypes: true });
@@ -7335,7 +7692,7 @@ function mergeGlobs(existing, newValue) {
7335
7692
 
7336
7693
  // src/hooks/rules-injector/storage.ts
7337
7694
  import {
7338
- existsSync as existsSync18,
7695
+ existsSync as existsSync19,
7339
7696
  mkdirSync as mkdirSync7,
7340
7697
  readFileSync as readFileSync9,
7341
7698
  writeFileSync as writeFileSync7,
@@ -7347,7 +7704,7 @@ function getStoragePath3(sessionID) {
7347
7704
  }
7348
7705
  function loadInjectedRules(sessionID) {
7349
7706
  const filePath = getStoragePath3(sessionID);
7350
- if (!existsSync18(filePath))
7707
+ if (!existsSync19(filePath))
7351
7708
  return { contentHashes: new Set, realPaths: new Set };
7352
7709
  try {
7353
7710
  const content = readFileSync9(filePath, "utf-8");
@@ -7361,7 +7718,7 @@ function loadInjectedRules(sessionID) {
7361
7718
  }
7362
7719
  }
7363
7720
  function saveInjectedRules(sessionID, data) {
7364
- if (!existsSync18(RULES_INJECTOR_STORAGE)) {
7721
+ if (!existsSync19(RULES_INJECTOR_STORAGE)) {
7365
7722
  mkdirSync7(RULES_INJECTOR_STORAGE, { recursive: true });
7366
7723
  }
7367
7724
  const storageData = {
@@ -7374,7 +7731,7 @@ function saveInjectedRules(sessionID, data) {
7374
7731
  }
7375
7732
  function clearInjectedRules(sessionID) {
7376
7733
  const filePath = getStoragePath3(sessionID);
7377
- if (existsSync18(filePath)) {
7734
+ if (existsSync19(filePath)) {
7378
7735
  unlinkSync6(filePath);
7379
7736
  }
7380
7737
  }
@@ -7474,7 +7831,7 @@ function createBackgroundNotificationHook(manager) {
7474
7831
  };
7475
7832
  }
7476
7833
  // src/hooks/auto-update-checker/checker.ts
7477
- import * as fs4 from "fs";
7834
+ import * as fs5 from "fs";
7478
7835
  import * as path5 from "path";
7479
7836
  import { fileURLToPath } from "url";
7480
7837
 
@@ -7504,9 +7861,6 @@ var USER_OPENCODE_CONFIG = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jso
7504
7861
  var USER_OPENCODE_CONFIG_JSONC = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
7505
7862
 
7506
7863
  // src/hooks/auto-update-checker/checker.ts
7507
- function isLocalDevMode(directory) {
7508
- return getLocalDevPath(directory) !== null;
7509
- }
7510
7864
  function stripJsonComments(json) {
7511
7865
  return json.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g) => g ? "" : m).replace(/,(\s*[}\]])/g, "$1");
7512
7866
  }
@@ -7521,9 +7875,9 @@ function getConfigPaths(directory) {
7521
7875
  function getLocalDevPath(directory) {
7522
7876
  for (const configPath of getConfigPaths(directory)) {
7523
7877
  try {
7524
- if (!fs4.existsSync(configPath))
7878
+ if (!fs5.existsSync(configPath))
7525
7879
  continue;
7526
- const content = fs4.readFileSync(configPath, "utf-8");
7880
+ const content = fs5.readFileSync(configPath, "utf-8");
7527
7881
  const config = JSON.parse(stripJsonComments(content));
7528
7882
  const plugins = config.plugin ?? [];
7529
7883
  for (const entry of plugins) {
@@ -7543,13 +7897,13 @@ function getLocalDevPath(directory) {
7543
7897
  }
7544
7898
  function findPackageJsonUp(startPath) {
7545
7899
  try {
7546
- const stat = fs4.statSync(startPath);
7900
+ const stat = fs5.statSync(startPath);
7547
7901
  let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
7548
7902
  for (let i = 0;i < 10; i++) {
7549
7903
  const pkgPath = path5.join(dir, "package.json");
7550
- if (fs4.existsSync(pkgPath)) {
7904
+ if (fs5.existsSync(pkgPath)) {
7551
7905
  try {
7552
- const content = fs4.readFileSync(pkgPath, "utf-8");
7906
+ const content = fs5.readFileSync(pkgPath, "utf-8");
7553
7907
  const pkg = JSON.parse(content);
7554
7908
  if (pkg.name === PACKAGE_NAME)
7555
7909
  return pkgPath;
@@ -7571,7 +7925,7 @@ function getLocalDevVersion(directory) {
7571
7925
  const pkgPath = findPackageJsonUp(localPath);
7572
7926
  if (!pkgPath)
7573
7927
  return null;
7574
- const content = fs4.readFileSync(pkgPath, "utf-8");
7928
+ const content = fs5.readFileSync(pkgPath, "utf-8");
7575
7929
  const pkg = JSON.parse(content);
7576
7930
  return pkg.version ?? null;
7577
7931
  } catch {
@@ -7581,19 +7935,19 @@ function getLocalDevVersion(directory) {
7581
7935
  function findPluginEntry(directory) {
7582
7936
  for (const configPath of getConfigPaths(directory)) {
7583
7937
  try {
7584
- if (!fs4.existsSync(configPath))
7938
+ if (!fs5.existsSync(configPath))
7585
7939
  continue;
7586
- const content = fs4.readFileSync(configPath, "utf-8");
7940
+ const content = fs5.readFileSync(configPath, "utf-8");
7587
7941
  const config = JSON.parse(stripJsonComments(content));
7588
7942
  const plugins = config.plugin ?? [];
7589
7943
  for (const entry of plugins) {
7590
7944
  if (entry === PACKAGE_NAME) {
7591
- return { entry, isPinned: false, pinnedVersion: null };
7945
+ return { entry, isPinned: false, pinnedVersion: null, configPath };
7592
7946
  }
7593
7947
  if (entry.startsWith(`${PACKAGE_NAME}@`)) {
7594
7948
  const pinnedVersion = entry.slice(PACKAGE_NAME.length + 1);
7595
7949
  const isPinned = pinnedVersion !== "latest";
7596
- return { entry, isPinned, pinnedVersion: isPinned ? pinnedVersion : null };
7950
+ return { entry, isPinned, pinnedVersion: isPinned ? pinnedVersion : null, configPath };
7597
7951
  }
7598
7952
  }
7599
7953
  } catch {
@@ -7604,8 +7958,8 @@ function findPluginEntry(directory) {
7604
7958
  }
7605
7959
  function getCachedVersion() {
7606
7960
  try {
7607
- if (fs4.existsSync(INSTALLED_PACKAGE_JSON)) {
7608
- const content = fs4.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
7961
+ if (fs5.existsSync(INSTALLED_PACKAGE_JSON)) {
7962
+ const content = fs5.readFileSync(INSTALLED_PACKAGE_JSON, "utf-8");
7609
7963
  const pkg = JSON.parse(content);
7610
7964
  if (pkg.version)
7611
7965
  return pkg.version;
@@ -7615,7 +7969,7 @@ function getCachedVersion() {
7615
7969
  const currentDir = path5.dirname(fileURLToPath(import.meta.url));
7616
7970
  const pkgPath = findPackageJsonUp(currentDir);
7617
7971
  if (pkgPath) {
7618
- const content = fs4.readFileSync(pkgPath, "utf-8");
7972
+ const content = fs5.readFileSync(pkgPath, "utf-8");
7619
7973
  const pkg = JSON.parse(content);
7620
7974
  if (pkg.version)
7621
7975
  return pkg.version;
@@ -7625,6 +7979,48 @@ function getCachedVersion() {
7625
7979
  }
7626
7980
  return null;
7627
7981
  }
7982
+ function updatePinnedVersion(configPath, oldEntry, newVersion) {
7983
+ try {
7984
+ const content = fs5.readFileSync(configPath, "utf-8");
7985
+ const newEntry = `${PACKAGE_NAME}@${newVersion}`;
7986
+ const pluginMatch = content.match(/"plugin"\s*:\s*\[/);
7987
+ if (!pluginMatch || pluginMatch.index === undefined) {
7988
+ log(`[auto-update-checker] No "plugin" array found in ${configPath}`);
7989
+ return false;
7990
+ }
7991
+ const startIdx = pluginMatch.index + pluginMatch[0].length;
7992
+ let bracketCount = 1;
7993
+ let endIdx = startIdx;
7994
+ for (let i = startIdx;i < content.length && bracketCount > 0; i++) {
7995
+ if (content[i] === "[")
7996
+ bracketCount++;
7997
+ else if (content[i] === "]")
7998
+ bracketCount--;
7999
+ endIdx = i;
8000
+ }
8001
+ const before = content.slice(0, startIdx);
8002
+ const pluginArrayContent = content.slice(startIdx, endIdx);
8003
+ const after = content.slice(endIdx);
8004
+ const escapedOldEntry = oldEntry.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8005
+ const regex = new RegExp(`["']${escapedOldEntry}["']`);
8006
+ if (!regex.test(pluginArrayContent)) {
8007
+ log(`[auto-update-checker] Entry "${oldEntry}" not found in plugin array of ${configPath}`);
8008
+ return false;
8009
+ }
8010
+ const updatedPluginArray = pluginArrayContent.replace(regex, `"${newEntry}"`);
8011
+ const updatedContent = before + updatedPluginArray + after;
8012
+ if (updatedContent === content) {
8013
+ log(`[auto-update-checker] No changes made to ${configPath}`);
8014
+ return false;
8015
+ }
8016
+ fs5.writeFileSync(configPath, updatedContent, "utf-8");
8017
+ log(`[auto-update-checker] Updated ${configPath}: ${oldEntry} \u2192 ${newEntry}`);
8018
+ return true;
8019
+ } catch (err) {
8020
+ log(`[auto-update-checker] Failed to update config file ${configPath}:`, err);
8021
+ return false;
8022
+ }
8023
+ }
7628
8024
  async function getLatestVersion() {
7629
8025
  const controller = new AbortController;
7630
8026
  const timeoutId = setTimeout(() => controller.abort(), NPM_FETCH_TIMEOUT);
@@ -7643,47 +8039,19 @@ async function getLatestVersion() {
7643
8039
  clearTimeout(timeoutId);
7644
8040
  }
7645
8041
  }
7646
- async function checkForUpdate(directory) {
7647
- if (isLocalDevMode(directory)) {
7648
- log("[auto-update-checker] Local dev mode detected, skipping update check");
7649
- return { needsUpdate: false, currentVersion: null, latestVersion: null, isLocalDev: true, isPinned: false };
7650
- }
7651
- const pluginInfo = findPluginEntry(directory);
7652
- if (!pluginInfo) {
7653
- log("[auto-update-checker] Plugin not found in config");
7654
- return { needsUpdate: false, currentVersion: null, latestVersion: null, isLocalDev: false, isPinned: false };
7655
- }
7656
- if (pluginInfo.isPinned) {
7657
- log(`[auto-update-checker] Version pinned to ${pluginInfo.pinnedVersion}, skipping update check`);
7658
- return { needsUpdate: false, currentVersion: pluginInfo.pinnedVersion, latestVersion: null, isLocalDev: false, isPinned: true };
7659
- }
7660
- const currentVersion = getCachedVersion();
7661
- if (!currentVersion) {
7662
- log("[auto-update-checker] No cached version found");
7663
- return { needsUpdate: false, currentVersion: null, latestVersion: null, isLocalDev: false, isPinned: false };
7664
- }
7665
- const latestVersion = await getLatestVersion();
7666
- if (!latestVersion) {
7667
- log("[auto-update-checker] Failed to fetch latest version");
7668
- return { needsUpdate: false, currentVersion, latestVersion: null, isLocalDev: false, isPinned: false };
7669
- }
7670
- const needsUpdate = currentVersion !== latestVersion;
7671
- log(`[auto-update-checker] Current: ${currentVersion}, Latest: ${latestVersion}, NeedsUpdate: ${needsUpdate}`);
7672
- return { needsUpdate, currentVersion, latestVersion, isLocalDev: false, isPinned: false };
7673
- }
7674
8042
 
7675
8043
  // src/hooks/auto-update-checker/cache.ts
7676
- import * as fs5 from "fs";
8044
+ import * as fs6 from "fs";
7677
8045
  import * as path6 from "path";
7678
8046
  function stripTrailingCommas(json) {
7679
8047
  return json.replace(/,(\s*[}\]])/g, "$1");
7680
8048
  }
7681
8049
  function removeFromBunLock(packageName) {
7682
8050
  const lockPath = path6.join(CACHE_DIR, "bun.lock");
7683
- if (!fs5.existsSync(lockPath))
8051
+ if (!fs6.existsSync(lockPath))
7684
8052
  return false;
7685
8053
  try {
7686
- const content = fs5.readFileSync(lockPath, "utf-8");
8054
+ const content = fs6.readFileSync(lockPath, "utf-8");
7687
8055
  const lock = JSON.parse(stripTrailingCommas(content));
7688
8056
  let modified = false;
7689
8057
  if (lock.workspaces?.[""]?.dependencies?.[packageName]) {
@@ -7695,7 +8063,7 @@ function removeFromBunLock(packageName) {
7695
8063
  modified = true;
7696
8064
  }
7697
8065
  if (modified) {
7698
- fs5.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
8066
+ fs6.writeFileSync(lockPath, JSON.stringify(lock, null, 2));
7699
8067
  log(`[auto-update-checker] Removed from bun.lock: ${packageName}`);
7700
8068
  }
7701
8069
  return modified;
@@ -7710,17 +8078,17 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
7710
8078
  let packageRemoved = false;
7711
8079
  let dependencyRemoved = false;
7712
8080
  let lockRemoved = false;
7713
- if (fs5.existsSync(pkgDir)) {
7714
- fs5.rmSync(pkgDir, { recursive: true, force: true });
8081
+ if (fs6.existsSync(pkgDir)) {
8082
+ fs6.rmSync(pkgDir, { recursive: true, force: true });
7715
8083
  log(`[auto-update-checker] Package removed: ${pkgDir}`);
7716
8084
  packageRemoved = true;
7717
8085
  }
7718
- if (fs5.existsSync(pkgJsonPath)) {
7719
- const content = fs5.readFileSync(pkgJsonPath, "utf-8");
8086
+ if (fs6.existsSync(pkgJsonPath)) {
8087
+ const content = fs6.readFileSync(pkgJsonPath, "utf-8");
7720
8088
  const pkgJson = JSON.parse(content);
7721
8089
  if (pkgJson.dependencies?.[packageName]) {
7722
8090
  delete pkgJson.dependencies[packageName];
7723
- fs5.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
8091
+ fs6.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
7724
8092
  log(`[auto-update-checker] Dependency removed from package.json: ${packageName}`);
7725
8093
  dependencyRemoved = true;
7726
8094
  }
@@ -7739,7 +8107,7 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
7739
8107
 
7740
8108
  // src/hooks/auto-update-checker/index.ts
7741
8109
  function createAutoUpdateCheckerHook(ctx, options = {}) {
7742
- const { showStartupToast = true, isSisyphusEnabled = false } = options;
8110
+ const { showStartupToast = true, isSisyphusEnabled = false, autoUpdate = true } = options;
7743
8111
  const getToastMessage = (isUpdate, latestVersion) => {
7744
8112
  if (isSisyphusEnabled) {
7745
8113
  return isUpdate ? `Sisyphus on steroids is steering OpenCode.
@@ -7748,21 +8116,9 @@ v${latestVersion} available. Restart to apply.` : `Sisyphus on steroids is steer
7748
8116
  return isUpdate ? `OpenCode is now on Steroids. oMoMoMoMo...
7749
8117
  v${latestVersion} available. Restart OpenCode to apply.` : `OpenCode is now on Steroids. oMoMoMoMo...`;
7750
8118
  };
7751
- const showVersionToast = async (version) => {
7752
- const displayVersion = version ?? "unknown";
7753
- await ctx.client.tui.showToast({
7754
- body: {
7755
- title: `OhMyOpenCode ${displayVersion}`,
7756
- message: getToastMessage(false),
7757
- variant: "info",
7758
- duration: 5000
7759
- }
7760
- }).catch(() => {});
7761
- log(`[auto-update-checker] Startup toast shown: v${displayVersion}`);
7762
- };
7763
8119
  let hasChecked = false;
7764
8120
  return {
7765
- event: async ({ event }) => {
8121
+ event: ({ event }) => {
7766
8122
  if (event.type !== "session.created")
7767
8123
  return;
7768
8124
  if (hasChecked)
@@ -7771,47 +8127,69 @@ v${latestVersion} available. Restart OpenCode to apply.` : `OpenCode is now on S
7771
8127
  if (props?.info?.parentID)
7772
8128
  return;
7773
8129
  hasChecked = true;
7774
- try {
7775
- const result = await checkForUpdate(ctx.directory);
7776
- if (result.isLocalDev) {
7777
- log("[auto-update-checker] Skipped: local development mode");
8130
+ setTimeout(() => {
8131
+ const cachedVersion = getCachedVersion();
8132
+ const localDevVersion = getLocalDevVersion(ctx.directory);
8133
+ const displayVersion = localDevVersion ?? cachedVersion;
8134
+ showConfigErrorsIfAny(ctx).catch(() => {});
8135
+ if (localDevVersion) {
7778
8136
  if (showStartupToast) {
7779
- const version = getLocalDevVersion(ctx.directory) ?? getCachedVersion();
7780
- await showVersionToast(version);
8137
+ showLocalDevToast(ctx, displayVersion, isSisyphusEnabled).catch(() => {});
7781
8138
  }
8139
+ log("[auto-update-checker] Local development mode");
7782
8140
  return;
7783
8141
  }
7784
- if (result.isPinned) {
7785
- log(`[auto-update-checker] Skipped: version pinned to ${result.currentVersion}`);
7786
- if (showStartupToast) {
7787
- await showVersionToast(result.currentVersion);
7788
- }
7789
- return;
8142
+ if (showStartupToast) {
8143
+ showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {});
7790
8144
  }
7791
- if (!result.needsUpdate) {
7792
- log("[auto-update-checker] No update needed");
7793
- if (showStartupToast) {
7794
- await showVersionToast(result.currentVersion);
7795
- }
7796
- return;
7797
- }
7798
- invalidatePackage(PACKAGE_NAME);
7799
- await ctx.client.tui.showToast({
7800
- body: {
7801
- title: `OhMyOpenCode ${result.latestVersion}`,
7802
- message: getToastMessage(true, result.latestVersion ?? undefined),
7803
- variant: "info",
7804
- duration: 8000
7805
- }
7806
- }).catch(() => {});
7807
- log(`[auto-update-checker] Update notification sent: v${result.currentVersion} \u2192 v${result.latestVersion}`);
7808
- } catch (err) {
7809
- log("[auto-update-checker] Error during update check:", err);
7810
- }
7811
- await showConfigErrorsIfAny(ctx);
8145
+ runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage).catch((err) => {
8146
+ log("[auto-update-checker] Background update check failed:", err);
8147
+ });
8148
+ }, 0);
7812
8149
  }
7813
8150
  };
7814
8151
  }
8152
+ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
8153
+ const pluginInfo = findPluginEntry(ctx.directory);
8154
+ if (!pluginInfo) {
8155
+ log("[auto-update-checker] Plugin not found in config");
8156
+ return;
8157
+ }
8158
+ const cachedVersion = getCachedVersion();
8159
+ const currentVersion = cachedVersion ?? pluginInfo.pinnedVersion;
8160
+ if (!currentVersion) {
8161
+ log("[auto-update-checker] No version found (cached or pinned)");
8162
+ return;
8163
+ }
8164
+ const latestVersion = await getLatestVersion();
8165
+ if (!latestVersion) {
8166
+ log("[auto-update-checker] Failed to fetch latest version");
8167
+ return;
8168
+ }
8169
+ if (currentVersion === latestVersion) {
8170
+ log("[auto-update-checker] Already on latest version");
8171
+ return;
8172
+ }
8173
+ log(`[auto-update-checker] Update available: ${currentVersion} \u2192 ${latestVersion}`);
8174
+ if (!autoUpdate) {
8175
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8176
+ log("[auto-update-checker] Auto-update disabled, notification only");
8177
+ return;
8178
+ }
8179
+ if (pluginInfo.isPinned) {
8180
+ const updated = updatePinnedVersion(pluginInfo.configPath, pluginInfo.entry, latestVersion);
8181
+ if (updated) {
8182
+ invalidatePackage(PACKAGE_NAME);
8183
+ await showAutoUpdatedToast(ctx, currentVersion, latestVersion);
8184
+ log(`[auto-update-checker] Config updated: ${pluginInfo.entry} \u2192 ${PACKAGE_NAME}@${latestVersion}`);
8185
+ } else {
8186
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8187
+ }
8188
+ } else {
8189
+ invalidatePackage(PACKAGE_NAME);
8190
+ await showUpdateAvailableToast(ctx, latestVersion, getToastMessage);
8191
+ }
8192
+ }
7815
8193
  async function showConfigErrorsIfAny(ctx) {
7816
8194
  const errors = getConfigLoadErrors();
7817
8195
  if (errors.length === 0)
@@ -7830,12 +8208,60 @@ ${errorMessages}`,
7830
8208
  log(`[auto-update-checker] Config load errors shown: ${errors.length} error(s)`);
7831
8209
  clearConfigLoadErrors();
7832
8210
  }
8211
+ async function showVersionToast(ctx, version, message) {
8212
+ const displayVersion = version ?? "unknown";
8213
+ await ctx.client.tui.showToast({
8214
+ body: {
8215
+ title: `OhMyOpenCode ${displayVersion}`,
8216
+ message,
8217
+ variant: "info",
8218
+ duration: 5000
8219
+ }
8220
+ }).catch(() => {});
8221
+ log(`[auto-update-checker] Startup toast shown: v${displayVersion}`);
8222
+ }
8223
+ async function showUpdateAvailableToast(ctx, latestVersion, getToastMessage) {
8224
+ await ctx.client.tui.showToast({
8225
+ body: {
8226
+ title: `OhMyOpenCode ${latestVersion}`,
8227
+ message: getToastMessage(true, latestVersion),
8228
+ variant: "info",
8229
+ duration: 8000
8230
+ }
8231
+ }).catch(() => {});
8232
+ log(`[auto-update-checker] Update available toast shown: v${latestVersion}`);
8233
+ }
8234
+ async function showAutoUpdatedToast(ctx, oldVersion, newVersion) {
8235
+ await ctx.client.tui.showToast({
8236
+ body: {
8237
+ title: `OhMyOpenCode Updated!`,
8238
+ message: `v${oldVersion} \u2192 v${newVersion}
8239
+ Restart OpenCode to apply.`,
8240
+ variant: "success",
8241
+ duration: 8000
8242
+ }
8243
+ }).catch(() => {});
8244
+ log(`[auto-update-checker] Auto-updated toast shown: v${oldVersion} \u2192 v${newVersion}`);
8245
+ }
8246
+ async function showLocalDevToast(ctx, version, isSisyphusEnabled) {
8247
+ const displayVersion = version ?? "dev";
8248
+ const message = isSisyphusEnabled ? "Sisyphus running in local development mode." : "Running in local development mode. oMoMoMo...";
8249
+ await ctx.client.tui.showToast({
8250
+ body: {
8251
+ title: `OhMyOpenCode ${displayVersion} (dev)`,
8252
+ message,
8253
+ variant: "warning",
8254
+ duration: 5000
8255
+ }
8256
+ }).catch(() => {});
8257
+ log(`[auto-update-checker] Local dev toast shown: v${displayVersion}`);
8258
+ }
7833
8259
  // src/hooks/agent-usage-reminder/storage.ts
7834
8260
  import {
7835
- existsSync as existsSync21,
8261
+ existsSync as existsSync22,
7836
8262
  mkdirSync as mkdirSync8,
7837
8263
  readFileSync as readFileSync13,
7838
- writeFileSync as writeFileSync9,
8264
+ writeFileSync as writeFileSync10,
7839
8265
  unlinkSync as unlinkSync7
7840
8266
  } from "fs";
7841
8267
  import { join as join30 } from "path";
@@ -7892,7 +8318,7 @@ function getStoragePath4(sessionID) {
7892
8318
  }
7893
8319
  function loadAgentUsageState(sessionID) {
7894
8320
  const filePath = getStoragePath4(sessionID);
7895
- if (!existsSync21(filePath))
8321
+ if (!existsSync22(filePath))
7896
8322
  return null;
7897
8323
  try {
7898
8324
  const content = readFileSync13(filePath, "utf-8");
@@ -7902,15 +8328,15 @@ function loadAgentUsageState(sessionID) {
7902
8328
  }
7903
8329
  }
7904
8330
  function saveAgentUsageState(state2) {
7905
- if (!existsSync21(AGENT_USAGE_REMINDER_STORAGE)) {
8331
+ if (!existsSync22(AGENT_USAGE_REMINDER_STORAGE)) {
7906
8332
  mkdirSync8(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
7907
8333
  }
7908
8334
  const filePath = getStoragePath4(state2.sessionID);
7909
- writeFileSync9(filePath, JSON.stringify(state2, null, 2));
8335
+ writeFileSync10(filePath, JSON.stringify(state2, null, 2));
7910
8336
  }
7911
8337
  function clearAgentUsageState(sessionID) {
7912
8338
  const filePath = getStoragePath4(sessionID);
7913
- if (existsSync21(filePath)) {
8339
+ if (existsSync22(filePath)) {
7914
8340
  unlinkSync7(filePath);
7915
8341
  }
7916
8342
  }
@@ -8012,6 +8438,14 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
8012
8438
  3. Always Use Plan agent with gathered context to create detailed work breakdown
8013
8439
  4. Execute with continuous verification against original requirements
8014
8440
 
8441
+ ## ZERO TOLERANCE FAILURES
8442
+ - **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
8443
+ - **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
8444
+ - **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
8445
+ - **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
8446
+
8447
+ THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT.
8448
+
8015
8449
  </ultrawork-mode>
8016
8450
 
8017
8451
  ---
@@ -8030,18 +8464,17 @@ NEVER stop at first result - be exhaustive.`
8030
8464
  {
8031
8465
  pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
8032
8466
  message: `[analyze-mode]
8033
- DEEP ANALYSIS MODE. Execute in phases:
8467
+ ANALYSIS MODE. Gather context before diving deep:
8034
8468
 
8035
- PHASE 1 - GATHER CONTEXT (10+ agents parallel):
8036
- - 3+ explore agents (codebase structure, patterns, implementations)
8037
- - 3+ librarian agents (official docs, best practices, examples)
8038
- - 2+ general agents (different analytical perspectives)
8469
+ CONTEXT GATHERING (parallel):
8470
+ - 1-2 explore agents (codebase patterns, implementations)
8471
+ - 1-2 librarian agents (if external library involved)
8472
+ - Direct tools: Grep, AST-grep, LSP for targeted searches
8039
8473
 
8040
- PHASE 2 - EXPERT CONSULTATION (after Phase 1):
8041
- - 3+ oracle agents in parallel with gathered context
8042
- - Each oracle: different angle (architecture, performance, edge cases)
8474
+ IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
8475
+ - Consult oracle for strategic guidance
8043
8476
 
8044
- SYNTHESIZE: Cross-reference findings, identify consensus & contradictions.`
8477
+ SYNTHESIZE findings before proceeding.`
8045
8478
  }
8046
8479
  ];
8047
8480
 
@@ -8058,9 +8491,16 @@ function extractPromptText2(parts) {
8058
8491
  }
8059
8492
 
8060
8493
  // src/hooks/keyword-detector/index.ts
8494
+ var sessionFirstMessageProcessed2 = new Set;
8061
8495
  function createKeywordDetectorHook() {
8062
8496
  return {
8063
8497
  "chat.message": async (input, output) => {
8498
+ const isFirstMessage = !sessionFirstMessageProcessed2.has(input.sessionID);
8499
+ sessionFirstMessageProcessed2.add(input.sessionID);
8500
+ if (isFirstMessage) {
8501
+ log("Skipping keyword detection on first message for title generation", { sessionID: input.sessionID });
8502
+ return;
8503
+ }
8064
8504
  const promptText = extractPromptText2(output.parts);
8065
8505
  const messages = detectKeywords(promptText);
8066
8506
  if (messages.length === 0) {
@@ -8090,7 +8530,13 @@ var NON_INTERACTIVE_ENV = {
8090
8530
  DEBIAN_FRONTEND: "noninteractive",
8091
8531
  GIT_TERMINAL_PROMPT: "0",
8092
8532
  GCM_INTERACTIVE: "never",
8093
- HOMEBREW_NO_AUTO_UPDATE: "1"
8533
+ HOMEBREW_NO_AUTO_UPDATE: "1",
8534
+ GIT_EDITOR: "true",
8535
+ EDITOR: "true",
8536
+ VISUAL: "true",
8537
+ GIT_SEQUENCE_EDITOR: "true",
8538
+ GIT_PAGER: "cat",
8539
+ PAGER: "cat"
8094
8540
  };
8095
8541
 
8096
8542
  // src/hooks/non-interactive-env/index.ts
@@ -8117,10 +8563,10 @@ function createNonInteractiveEnvHook(_ctx) {
8117
8563
  }
8118
8564
  // src/hooks/interactive-bash-session/storage.ts
8119
8565
  import {
8120
- existsSync as existsSync22,
8566
+ existsSync as existsSync23,
8121
8567
  mkdirSync as mkdirSync9,
8122
8568
  readFileSync as readFileSync14,
8123
- writeFileSync as writeFileSync10,
8569
+ writeFileSync as writeFileSync11,
8124
8570
  unlinkSync as unlinkSync8
8125
8571
  } from "fs";
8126
8572
  import { join as join32 } from "path";
@@ -8144,7 +8590,7 @@ function getStoragePath5(sessionID) {
8144
8590
  }
8145
8591
  function loadInteractiveBashSessionState(sessionID) {
8146
8592
  const filePath = getStoragePath5(sessionID);
8147
- if (!existsSync22(filePath))
8593
+ if (!existsSync23(filePath))
8148
8594
  return null;
8149
8595
  try {
8150
8596
  const content = readFileSync14(filePath, "utf-8");
@@ -8159,7 +8605,7 @@ function loadInteractiveBashSessionState(sessionID) {
8159
8605
  }
8160
8606
  }
8161
8607
  function saveInteractiveBashSessionState(state2) {
8162
- if (!existsSync22(INTERACTIVE_BASH_SESSION_STORAGE)) {
8608
+ if (!existsSync23(INTERACTIVE_BASH_SESSION_STORAGE)) {
8163
8609
  mkdirSync9(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
8164
8610
  }
8165
8611
  const filePath = getStoragePath5(state2.sessionID);
@@ -8168,11 +8614,11 @@ function saveInteractiveBashSessionState(state2) {
8168
8614
  tmuxSessions: Array.from(state2.tmuxSessions),
8169
8615
  updatedAt: state2.updatedAt
8170
8616
  };
8171
- writeFileSync10(filePath, JSON.stringify(serialized, null, 2));
8617
+ writeFileSync11(filePath, JSON.stringify(serialized, null, 2));
8172
8618
  }
8173
8619
  function clearInteractiveBashSessionState(sessionID) {
8174
8620
  const filePath = getStoragePath5(sessionID);
8175
- if (existsSync22(filePath)) {
8621
+ if (existsSync23(filePath)) {
8176
8622
  unlinkSync8(filePath);
8177
8623
  }
8178
8624
  }
@@ -10116,11 +10562,11 @@ async function createGoogleAntigravityAuthPlugin({
10116
10562
  };
10117
10563
  }
10118
10564
  // src/features/claude-code-command-loader/loader.ts
10119
- import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
10565
+ import { existsSync as existsSync24, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
10120
10566
  import { homedir as homedir12 } from "os";
10121
10567
  import { join as join33, basename } from "path";
10122
10568
  function loadCommandsFromDir(commandsDir, scope) {
10123
- if (!existsSync23(commandsDir)) {
10569
+ if (!existsSync24(commandsDir)) {
10124
10570
  return [];
10125
10571
  }
10126
10572
  const entries = readdirSync6(commandsDir, { withFileTypes: true });
@@ -10191,11 +10637,11 @@ function loadOpencodeProjectCommands() {
10191
10637
  return commandsToRecord(commands);
10192
10638
  }
10193
10639
  // src/features/claude-code-skill-loader/loader.ts
10194
- import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
10640
+ import { existsSync as existsSync25, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
10195
10641
  import { homedir as homedir13 } from "os";
10196
10642
  import { join as join34 } from "path";
10197
10643
  function loadSkillsFromDir(skillsDir, scope) {
10198
- if (!existsSync24(skillsDir)) {
10644
+ if (!existsSync25(skillsDir)) {
10199
10645
  return [];
10200
10646
  }
10201
10647
  const entries = readdirSync7(skillsDir, { withFileTypes: true });
@@ -10208,7 +10654,7 @@ function loadSkillsFromDir(skillsDir, scope) {
10208
10654
  continue;
10209
10655
  const resolvedPath = resolveSymlink(skillPath);
10210
10656
  const skillMdPath = join34(resolvedPath, "SKILL.md");
10211
- if (!existsSync24(skillMdPath))
10657
+ if (!existsSync25(skillMdPath))
10212
10658
  continue;
10213
10659
  try {
10214
10660
  const content = readFileSync16(skillMdPath, "utf-8");
@@ -10261,7 +10707,7 @@ function loadProjectSkillsAsCommands() {
10261
10707
  }, {});
10262
10708
  }
10263
10709
  // src/features/claude-code-agent-loader/loader.ts
10264
- import { existsSync as existsSync25, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
10710
+ import { existsSync as existsSync26, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
10265
10711
  import { homedir as homedir14 } from "os";
10266
10712
  import { join as join35, basename as basename2 } from "path";
10267
10713
  function parseToolsConfig(toolsStr) {
@@ -10277,7 +10723,7 @@ function parseToolsConfig(toolsStr) {
10277
10723
  return result;
10278
10724
  }
10279
10725
  function loadAgentsFromDir(agentsDir, scope) {
10280
- if (!existsSync25(agentsDir)) {
10726
+ if (!existsSync26(agentsDir)) {
10281
10727
  return [];
10282
10728
  }
10283
10729
  const entries = readdirSync8(agentsDir, { withFileTypes: true });
@@ -10333,7 +10779,7 @@ function loadProjectAgents() {
10333
10779
  return result;
10334
10780
  }
10335
10781
  // src/features/claude-code-mcp-loader/loader.ts
10336
- import { existsSync as existsSync26 } from "fs";
10782
+ import { existsSync as existsSync27 } from "fs";
10337
10783
  import { homedir as homedir15 } from "os";
10338
10784
  import { join as join36 } from "path";
10339
10785
 
@@ -10410,7 +10856,7 @@ function getMcpConfigPaths() {
10410
10856
  ];
10411
10857
  }
10412
10858
  async function loadMcpConfigFile(filePath) {
10413
- if (!existsSync26(filePath)) {
10859
+ if (!existsSync27(filePath)) {
10414
10860
  return null;
10415
10861
  }
10416
10862
  try {
@@ -10505,6 +10951,32 @@ var BUILTIN_SERVERS = {
10505
10951
  command: ["vscode-eslint-language-server", "--stdio"],
10506
10952
  extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue"]
10507
10953
  },
10954
+ oxlint: {
10955
+ command: ["oxlint", "--lsp"],
10956
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".vue", ".astro", ".svelte"]
10957
+ },
10958
+ biome: {
10959
+ command: ["biome", "lsp-proxy", "--stdio"],
10960
+ extensions: [
10961
+ ".ts",
10962
+ ".tsx",
10963
+ ".js",
10964
+ ".jsx",
10965
+ ".mjs",
10966
+ ".cjs",
10967
+ ".mts",
10968
+ ".cts",
10969
+ ".json",
10970
+ ".jsonc",
10971
+ ".vue",
10972
+ ".astro",
10973
+ ".svelte",
10974
+ ".css",
10975
+ ".graphql",
10976
+ ".gql",
10977
+ ".html"
10978
+ ]
10979
+ },
10508
10980
  gopls: {
10509
10981
  command: ["gopls"],
10510
10982
  extensions: [".go"]
@@ -10521,6 +10993,10 @@ var BUILTIN_SERVERS = {
10521
10993
  command: ["pyright-langserver", "--stdio"],
10522
10994
  extensions: [".py", ".pyi"]
10523
10995
  },
10996
+ ty: {
10997
+ command: ["ty", "server"],
10998
+ extensions: [".py", ".pyi"]
10999
+ },
10524
11000
  ruff: {
10525
11001
  command: ["ruff", "server"],
10526
11002
  extensions: [".py", ".pyi"]
@@ -10537,6 +11013,10 @@ var BUILTIN_SERVERS = {
10537
11013
  command: ["csharp-ls"],
10538
11014
  extensions: [".cs"]
10539
11015
  },
11016
+ fsharp: {
11017
+ command: ["fsautocomplete"],
11018
+ extensions: [".fs", ".fsi", ".fsx", ".fsscript"]
11019
+ },
10540
11020
  "sourcekit-lsp": {
10541
11021
  command: ["sourcekit-lsp"],
10542
11022
  extensions: [".swift", ".objc", ".objcpp"]
@@ -10580,26 +11060,125 @@ var BUILTIN_SERVERS = {
10580
11060
  dart: {
10581
11061
  command: ["dart", "language-server", "--lsp"],
10582
11062
  extensions: [".dart"]
11063
+ },
11064
+ "terraform-ls": {
11065
+ command: ["terraform-ls", "serve"],
11066
+ extensions: [".tf", ".tfvars"]
10583
11067
  }
10584
11068
  };
10585
11069
  var EXT_TO_LANG = {
11070
+ ".abap": "abap",
11071
+ ".bat": "bat",
11072
+ ".bib": "bibtex",
11073
+ ".bibtex": "bibtex",
11074
+ ".clj": "clojure",
11075
+ ".cljs": "clojure",
11076
+ ".cljc": "clojure",
11077
+ ".edn": "clojure",
11078
+ ".coffee": "coffeescript",
11079
+ ".c": "c",
11080
+ ".cpp": "cpp",
11081
+ ".cxx": "cpp",
11082
+ ".cc": "cpp",
11083
+ ".c++": "cpp",
11084
+ ".cs": "csharp",
11085
+ ".css": "css",
11086
+ ".d": "d",
11087
+ ".pas": "pascal",
11088
+ ".pascal": "pascal",
11089
+ ".diff": "diff",
11090
+ ".patch": "diff",
11091
+ ".dart": "dart",
11092
+ ".dockerfile": "dockerfile",
11093
+ ".ex": "elixir",
11094
+ ".exs": "elixir",
11095
+ ".erl": "erlang",
11096
+ ".hrl": "erlang",
11097
+ ".fs": "fsharp",
11098
+ ".fsi": "fsharp",
11099
+ ".fsx": "fsharp",
11100
+ ".fsscript": "fsharp",
11101
+ ".gitcommit": "git-commit",
11102
+ ".gitrebase": "git-rebase",
11103
+ ".go": "go",
11104
+ ".groovy": "groovy",
11105
+ ".gleam": "gleam",
11106
+ ".hbs": "handlebars",
11107
+ ".handlebars": "handlebars",
11108
+ ".hs": "haskell",
11109
+ ".html": "html",
11110
+ ".htm": "html",
11111
+ ".ini": "ini",
11112
+ ".java": "java",
11113
+ ".js": "javascript",
11114
+ ".jsx": "javascriptreact",
11115
+ ".json": "json",
11116
+ ".jsonc": "jsonc",
11117
+ ".tex": "latex",
11118
+ ".latex": "latex",
11119
+ ".less": "less",
11120
+ ".lua": "lua",
11121
+ ".makefile": "makefile",
11122
+ makefile: "makefile",
11123
+ ".md": "markdown",
11124
+ ".markdown": "markdown",
11125
+ ".m": "objective-c",
11126
+ ".mm": "objective-cpp",
11127
+ ".pl": "perl",
11128
+ ".pm": "perl",
11129
+ ".pm6": "perl6",
11130
+ ".php": "php",
11131
+ ".ps1": "powershell",
11132
+ ".psm1": "powershell",
11133
+ ".pug": "jade",
11134
+ ".jade": "jade",
10586
11135
  ".py": "python",
10587
11136
  ".pyi": "python",
11137
+ ".r": "r",
11138
+ ".cshtml": "razor",
11139
+ ".razor": "razor",
11140
+ ".rb": "ruby",
11141
+ ".rake": "ruby",
11142
+ ".gemspec": "ruby",
11143
+ ".ru": "ruby",
11144
+ ".erb": "erb",
11145
+ ".html.erb": "erb",
11146
+ ".js.erb": "erb",
11147
+ ".css.erb": "erb",
11148
+ ".json.erb": "erb",
11149
+ ".rs": "rust",
11150
+ ".scss": "scss",
11151
+ ".sass": "sass",
11152
+ ".scala": "scala",
11153
+ ".shader": "shaderlab",
11154
+ ".sh": "shellscript",
11155
+ ".bash": "shellscript",
11156
+ ".zsh": "shellscript",
11157
+ ".ksh": "shellscript",
11158
+ ".sql": "sql",
11159
+ ".svelte": "svelte",
11160
+ ".swift": "swift",
10588
11161
  ".ts": "typescript",
10589
11162
  ".tsx": "typescriptreact",
10590
11163
  ".mts": "typescript",
10591
11164
  ".cts": "typescript",
10592
- ".js": "javascript",
10593
- ".jsx": "javascriptreact",
11165
+ ".mtsx": "typescriptreact",
11166
+ ".ctsx": "typescriptreact",
11167
+ ".xml": "xml",
11168
+ ".xsl": "xsl",
11169
+ ".yaml": "yaml",
11170
+ ".yml": "yaml",
10594
11171
  ".mjs": "javascript",
10595
11172
  ".cjs": "javascript",
10596
- ".go": "go",
10597
- ".rs": "rust",
10598
- ".c": "c",
10599
- ".cpp": "cpp",
10600
- ".cc": "cpp",
10601
- ".cxx": "cpp",
10602
- ".c++": "cpp",
11173
+ ".vue": "vue",
11174
+ ".zig": "zig",
11175
+ ".zon": "zig",
11176
+ ".astro": "astro",
11177
+ ".ml": "ocaml",
11178
+ ".mli": "ocaml",
11179
+ ".tf": "terraform",
11180
+ ".tfvars": "terraform-vars",
11181
+ ".hcl": "hcl",
10603
11182
  ".h": "c",
10604
11183
  ".hpp": "cpp",
10605
11184
  ".hh": "cpp",
@@ -10607,46 +11186,16 @@ var EXT_TO_LANG = {
10607
11186
  ".h++": "cpp",
10608
11187
  ".objc": "objective-c",
10609
11188
  ".objcpp": "objective-cpp",
10610
- ".java": "java",
10611
- ".rb": "ruby",
10612
- ".rake": "ruby",
10613
- ".gemspec": "ruby",
10614
- ".ru": "ruby",
10615
- ".lua": "lua",
10616
- ".swift": "swift",
10617
- ".cs": "csharp",
10618
- ".php": "php",
10619
- ".dart": "dart",
10620
- ".ex": "elixir",
10621
- ".exs": "elixir",
10622
- ".zig": "zig",
10623
- ".zon": "zig",
10624
- ".vue": "vue",
10625
- ".svelte": "svelte",
10626
- ".astro": "astro",
10627
- ".yaml": "yaml",
10628
- ".yml": "yaml",
10629
- ".json": "json",
10630
- ".jsonc": "jsonc",
10631
- ".html": "html",
10632
- ".htm": "html",
10633
- ".css": "css",
10634
- ".scss": "scss",
10635
- ".less": "less",
10636
- ".sh": "shellscript",
10637
- ".bash": "shellscript",
10638
- ".zsh": "shellscript",
10639
11189
  ".fish": "fish",
10640
- ".md": "markdown",
10641
- ".tf": "terraform",
10642
- ".tfvars": "terraform"
11190
+ ".graphql": "graphql",
11191
+ ".gql": "graphql"
10643
11192
  };
10644
11193
  // src/tools/lsp/config.ts
10645
- import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
11194
+ import { existsSync as existsSync28, readFileSync as readFileSync18 } from "fs";
10646
11195
  import { join as join37 } from "path";
10647
11196
  import { homedir as homedir16 } from "os";
10648
11197
  function loadJsonFile(path7) {
10649
- if (!existsSync27(path7))
11198
+ if (!existsSync28(path7))
10650
11199
  return null;
10651
11200
  try {
10652
11201
  return JSON.parse(readFileSync18(path7, "utf-8"));
@@ -10749,10 +11298,27 @@ function isServerInstalled(command) {
10749
11298
  if (command.length === 0)
10750
11299
  return false;
10751
11300
  const cmd = command[0];
11301
+ const isWindows2 = process.platform === "win32";
11302
+ const ext = isWindows2 ? ".exe" : "";
10752
11303
  const pathEnv = process.env.PATH || "";
10753
- const paths = pathEnv.split(":");
11304
+ const pathSeparator = isWindows2 ? ";" : ":";
11305
+ const paths = pathEnv.split(pathSeparator);
10754
11306
  for (const p of paths) {
10755
- if (existsSync27(join37(p, cmd))) {
11307
+ if (existsSync28(join37(p, cmd)) || existsSync28(join37(p, cmd + ext))) {
11308
+ return true;
11309
+ }
11310
+ }
11311
+ const cwd = process.cwd();
11312
+ const additionalPaths = [
11313
+ join37(cwd, "node_modules", ".bin", cmd),
11314
+ join37(cwd, "node_modules", ".bin", cmd + ext),
11315
+ join37(homedir16(), ".config", "opencode", "bin", cmd),
11316
+ join37(homedir16(), ".config", "opencode", "bin", cmd + ext),
11317
+ join37(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd),
11318
+ join37(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
11319
+ ];
11320
+ for (const p of additionalPaths) {
11321
+ if (existsSync28(p)) {
10756
11322
  return true;
10757
11323
  }
10758
11324
  }
@@ -11347,16 +11913,16 @@ ${msg}`);
11347
11913
  }
11348
11914
  // src/tools/lsp/utils.ts
11349
11915
  import { extname as extname2, resolve as resolve6 } from "path";
11350
- import { existsSync as existsSync28, readFileSync as readFileSync20, writeFileSync as writeFileSync11 } from "fs";
11916
+ import { existsSync as existsSync29, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
11351
11917
  function findWorkspaceRoot(filePath) {
11352
11918
  let dir = resolve6(filePath);
11353
- if (!existsSync28(dir) || !__require("fs").statSync(dir).isDirectory()) {
11919
+ if (!existsSync29(dir) || !__require("fs").statSync(dir).isDirectory()) {
11354
11920
  dir = __require("path").dirname(dir);
11355
11921
  }
11356
11922
  const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
11357
11923
  while (dir !== "/") {
11358
11924
  for (const marker of markers) {
11359
- if (existsSync28(__require("path").join(dir, marker))) {
11925
+ if (existsSync29(__require("path").join(dir, marker))) {
11360
11926
  return dir;
11361
11927
  }
11362
11928
  }
@@ -11539,7 +12105,7 @@ function applyTextEditsToFile(filePath, edits) {
11539
12105
  `));
11540
12106
  }
11541
12107
  }
11542
- writeFileSync11(filePath, lines.join(`
12108
+ writeFileSync12(filePath, lines.join(`
11543
12109
  `), "utf-8");
11544
12110
  return { success: true, editCount: edits.length };
11545
12111
  } catch (err) {
@@ -11570,7 +12136,7 @@ function applyWorkspaceEdit(edit) {
11570
12136
  if (change.kind === "create") {
11571
12137
  try {
11572
12138
  const filePath = change.uri.replace("file://", "");
11573
- writeFileSync11(filePath, "", "utf-8");
12139
+ writeFileSync12(filePath, "", "utf-8");
11574
12140
  result.filesModified.push(filePath);
11575
12141
  } catch (err) {
11576
12142
  result.success = false;
@@ -11581,7 +12147,7 @@ function applyWorkspaceEdit(edit) {
11581
12147
  const oldPath = change.oldUri.replace("file://", "");
11582
12148
  const newPath = change.newUri.replace("file://", "");
11583
12149
  const content = readFileSync20(oldPath, "utf-8");
11584
- writeFileSync11(newPath, content, "utf-8");
12150
+ writeFileSync12(newPath, content, "utf-8");
11585
12151
  __require("fs").unlinkSync(oldPath);
11586
12152
  result.filesModified.push(newPath);
11587
12153
  } catch (err) {
@@ -24282,11 +24848,11 @@ var lsp_code_action_resolve = tool({
24282
24848
  // src/tools/ast-grep/constants.ts
24283
24849
  import { createRequire as createRequire4 } from "module";
24284
24850
  import { dirname as dirname6, join as join39 } from "path";
24285
- import { existsSync as existsSync30, statSync as statSync4 } from "fs";
24851
+ import { existsSync as existsSync31, statSync as statSync4 } from "fs";
24286
24852
 
24287
24853
  // src/tools/ast-grep/downloader.ts
24288
24854
  var {spawn: spawn5 } = globalThis.Bun;
24289
- import { existsSync as existsSync29, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24855
+ import { existsSync as existsSync30, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
24290
24856
  import { join as join38 } from "path";
24291
24857
  import { homedir as homedir17 } from "os";
24292
24858
  import { createRequire as createRequire3 } from "module";
@@ -24325,7 +24891,7 @@ function getBinaryName3() {
24325
24891
  }
24326
24892
  function getCachedBinaryPath2() {
24327
24893
  const binaryPath = join38(getCacheDir3(), getBinaryName3());
24328
- return existsSync29(binaryPath) ? binaryPath : null;
24894
+ return existsSync30(binaryPath) ? binaryPath : null;
24329
24895
  }
24330
24896
  async function extractZip2(archivePath, destDir) {
24331
24897
  const proc = process.platform === "win32" ? spawn5([
@@ -24352,7 +24918,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24352
24918
  const cacheDir = getCacheDir3();
24353
24919
  const binaryName = getBinaryName3();
24354
24920
  const binaryPath = join38(cacheDir, binaryName);
24355
- if (existsSync29(binaryPath)) {
24921
+ if (existsSync30(binaryPath)) {
24356
24922
  return binaryPath;
24357
24923
  }
24358
24924
  const { arch, os: os5 } = platformInfo;
@@ -24360,7 +24926,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24360
24926
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
24361
24927
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
24362
24928
  try {
24363
- if (!existsSync29(cacheDir)) {
24929
+ if (!existsSync30(cacheDir)) {
24364
24930
  mkdirSync10(cacheDir, { recursive: true });
24365
24931
  }
24366
24932
  const response2 = await fetch(downloadUrl, { redirect: "follow" });
@@ -24371,10 +24937,10 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
24371
24937
  const arrayBuffer = await response2.arrayBuffer();
24372
24938
  await Bun.write(archivePath, arrayBuffer);
24373
24939
  await extractZip2(archivePath, cacheDir);
24374
- if (existsSync29(archivePath)) {
24940
+ if (existsSync30(archivePath)) {
24375
24941
  unlinkSync9(archivePath);
24376
24942
  }
24377
- if (process.platform !== "win32" && existsSync29(binaryPath)) {
24943
+ if (process.platform !== "win32" && existsSync30(binaryPath)) {
24378
24944
  chmodSync2(binaryPath, 493);
24379
24945
  }
24380
24946
  console.log(`[oh-my-opencode] ast-grep binary ready.`);
@@ -24426,7 +24992,7 @@ function findSgCliPathSync() {
24426
24992
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
24427
24993
  const cliDir = dirname6(cliPkgPath);
24428
24994
  const sgPath = join39(cliDir, binaryName);
24429
- if (existsSync30(sgPath) && isValidBinary(sgPath)) {
24995
+ if (existsSync31(sgPath) && isValidBinary(sgPath)) {
24430
24996
  return sgPath;
24431
24997
  }
24432
24998
  } catch {}
@@ -24438,7 +25004,7 @@ function findSgCliPathSync() {
24438
25004
  const pkgDir = dirname6(pkgPath);
24439
25005
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
24440
25006
  const binaryPath = join39(pkgDir, astGrepName);
24441
- if (existsSync30(binaryPath) && isValidBinary(binaryPath)) {
25007
+ if (existsSync31(binaryPath) && isValidBinary(binaryPath)) {
24442
25008
  return binaryPath;
24443
25009
  }
24444
25010
  } catch {}
@@ -24446,7 +25012,7 @@ function findSgCliPathSync() {
24446
25012
  if (process.platform === "darwin") {
24447
25013
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
24448
25014
  for (const path7 of homebrewPaths) {
24449
- if (existsSync30(path7) && isValidBinary(path7)) {
25015
+ if (existsSync31(path7) && isValidBinary(path7)) {
24450
25016
  return path7;
24451
25017
  }
24452
25018
  }
@@ -24502,11 +25068,11 @@ var DEFAULT_MAX_MATCHES = 500;
24502
25068
 
24503
25069
  // src/tools/ast-grep/cli.ts
24504
25070
  var {spawn: spawn6 } = globalThis.Bun;
24505
- import { existsSync as existsSync31 } from "fs";
25071
+ import { existsSync as existsSync32 } from "fs";
24506
25072
  var resolvedCliPath3 = null;
24507
25073
  var initPromise2 = null;
24508
25074
  async function getAstGrepPath() {
24509
- if (resolvedCliPath3 !== null && existsSync31(resolvedCliPath3)) {
25075
+ if (resolvedCliPath3 !== null && existsSync32(resolvedCliPath3)) {
24510
25076
  return resolvedCliPath3;
24511
25077
  }
24512
25078
  if (initPromise2) {
@@ -24514,7 +25080,7 @@ async function getAstGrepPath() {
24514
25080
  }
24515
25081
  initPromise2 = (async () => {
24516
25082
  const syncPath = findSgCliPathSync();
24517
- if (syncPath && existsSync31(syncPath)) {
25083
+ if (syncPath && existsSync32(syncPath)) {
24518
25084
  resolvedCliPath3 = syncPath;
24519
25085
  setSgCliPath(syncPath);
24520
25086
  return syncPath;
@@ -24548,7 +25114,7 @@ async function runSg(options) {
24548
25114
  const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
24549
25115
  args.push(...paths);
24550
25116
  let cliPath = getSgCliPath();
24551
- if (!existsSync31(cliPath) && cliPath !== "sg") {
25117
+ if (!existsSync32(cliPath) && cliPath !== "sg") {
24552
25118
  const downloadedPath = await getAstGrepPath();
24553
25119
  if (downloadedPath) {
24554
25120
  cliPath = downloadedPath;
@@ -24812,12 +25378,12 @@ var ast_grep_replace = tool({
24812
25378
  var {spawn: spawn7 } = globalThis.Bun;
24813
25379
 
24814
25380
  // src/tools/grep/constants.ts
24815
- import { existsSync as existsSync33 } from "fs";
25381
+ import { existsSync as existsSync34 } from "fs";
24816
25382
  import { join as join41, dirname as dirname7 } from "path";
24817
25383
  import { spawnSync } from "child_process";
24818
25384
 
24819
25385
  // src/tools/grep/downloader.ts
24820
- import { existsSync as existsSync32, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
25386
+ import { existsSync as existsSync33, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
24821
25387
  import { join as join40 } from "path";
24822
25388
  function getInstallDir() {
24823
25389
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
@@ -24829,7 +25395,7 @@ function getRgPath() {
24829
25395
  }
24830
25396
  function getInstalledRipgrepPath() {
24831
25397
  const rgPath = getRgPath();
24832
- return existsSync32(rgPath) ? rgPath : null;
25398
+ return existsSync33(rgPath) ? rgPath : null;
24833
25399
  }
24834
25400
 
24835
25401
  // src/tools/grep/constants.ts
@@ -24858,7 +25424,7 @@ function getOpenCodeBundledRg() {
24858
25424
  join41(execDir, "..", "libexec", rgName)
24859
25425
  ];
24860
25426
  for (const candidate of candidates) {
24861
- if (existsSync33(candidate)) {
25427
+ if (existsSync34(candidate)) {
24862
25428
  return candidate;
24863
25429
  }
24864
25430
  }
@@ -25261,11 +25827,11 @@ var glob = tool({
25261
25827
  }
25262
25828
  });
25263
25829
  // src/tools/slashcommand/tools.ts
25264
- import { existsSync as existsSync34, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
25830
+ import { existsSync as existsSync35, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
25265
25831
  import { homedir as homedir18 } from "os";
25266
25832
  import { join as join42, basename as basename3, dirname as dirname8 } from "path";
25267
25833
  function discoverCommandsFromDir(commandsDir, scope) {
25268
- if (!existsSync34(commandsDir)) {
25834
+ if (!existsSync35(commandsDir)) {
25269
25835
  return [];
25270
25836
  }
25271
25837
  const entries = readdirSync10(commandsDir, { withFileTypes: true });
@@ -25436,7 +26002,7 @@ var SkillFrontmatterSchema = exports_external.object({
25436
26002
  metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
25437
26003
  });
25438
26004
  // src/tools/skill/tools.ts
25439
- import { existsSync as existsSync35, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
26005
+ import { existsSync as existsSync36, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
25440
26006
  import { homedir as homedir19 } from "os";
25441
26007
  import { join as join43, basename as basename4 } from "path";
25442
26008
  function parseSkillFrontmatter(data) {
@@ -25449,7 +26015,7 @@ function parseSkillFrontmatter(data) {
25449
26015
  };
25450
26016
  }
25451
26017
  function discoverSkillsFromDir(skillsDir, scope) {
25452
- if (!existsSync35(skillsDir)) {
26018
+ if (!existsSync36(skillsDir)) {
25453
26019
  return [];
25454
26020
  }
25455
26021
  const entries = readdirSync11(skillsDir, { withFileTypes: true });
@@ -25461,7 +26027,7 @@ function discoverSkillsFromDir(skillsDir, scope) {
25461
26027
  if (entry.isDirectory() || entry.isSymbolicLink()) {
25462
26028
  const resolvedPath = resolveSymlink(skillPath);
25463
26029
  const skillMdPath = join43(resolvedPath, "SKILL.md");
25464
- if (!existsSync35(skillMdPath))
26030
+ if (!existsSync36(skillMdPath))
25465
26031
  continue;
25466
26032
  try {
25467
26033
  const content = readFileSync22(skillMdPath, "utf-8");
@@ -25491,7 +26057,7 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
25491
26057
  async function parseSkillMd(skillPath) {
25492
26058
  const resolvedPath = resolveSymlink(skillPath);
25493
26059
  const skillMdPath = join43(resolvedPath, "SKILL.md");
25494
- if (!existsSync35(skillMdPath)) {
26060
+ if (!existsSync36(skillMdPath)) {
25495
26061
  return null;
25496
26062
  }
25497
26063
  try {
@@ -25509,9 +26075,9 @@ async function parseSkillMd(skillPath) {
25509
26075
  const referencesDir = join43(resolvedPath, "references");
25510
26076
  const scriptsDir = join43(resolvedPath, "scripts");
25511
26077
  const assetsDir = join43(resolvedPath, "assets");
25512
- const references = existsSync35(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
25513
- const scripts = existsSync35(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
25514
- const assets = existsSync35(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
26078
+ const references = existsSync36(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
26079
+ const scripts = existsSync36(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
26080
+ const assets = existsSync36(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
25515
26081
  return {
25516
26082
  name: metadata.name,
25517
26083
  path: resolvedPath,
@@ -25527,7 +26093,7 @@ async function parseSkillMd(skillPath) {
25527
26093
  }
25528
26094
  }
25529
26095
  async function discoverSkillsFromDirAsync(skillsDir) {
25530
- if (!existsSync35(skillsDir)) {
26096
+ if (!existsSync36(skillsDir)) {
25531
26097
  return [];
25532
26098
  }
25533
26099
  const entries = readdirSync11(skillsDir, { withFileTypes: true });
@@ -26342,17 +26908,17 @@ var builtinTools = {
26342
26908
  skill
26343
26909
  };
26344
26910
  // src/features/background-agent/manager.ts
26345
- import { existsSync as existsSync36, readdirSync as readdirSync12 } from "fs";
26911
+ import { existsSync as existsSync37, readdirSync as readdirSync12 } from "fs";
26346
26912
  import { join as join44 } from "path";
26347
26913
  function getMessageDir4(sessionID) {
26348
- if (!existsSync36(MESSAGE_STORAGE))
26914
+ if (!existsSync37(MESSAGE_STORAGE))
26349
26915
  return null;
26350
26916
  const directPath = join44(MESSAGE_STORAGE, sessionID);
26351
- if (existsSync36(directPath))
26917
+ if (existsSync37(directPath))
26352
26918
  return directPath;
26353
26919
  for (const dir of readdirSync12(MESSAGE_STORAGE)) {
26354
26920
  const sessionPath = join44(MESSAGE_STORAGE, dir, sessionID);
26355
- if (existsSync36(sessionPath))
26921
+ if (existsSync37(sessionPath))
26356
26922
  return sessionPath;
26357
26923
  }
26358
26924
  return null;
@@ -26823,8 +27389,9 @@ var SisyphusAgentConfigSchema = exports_external.object({
26823
27389
  });
26824
27390
  var ExperimentalConfigSchema = exports_external.object({
26825
27391
  aggressive_truncation: exports_external.boolean().optional(),
26826
- empty_message_recovery: exports_external.boolean().optional(),
26827
- auto_resume: exports_external.boolean().optional()
27392
+ auto_resume: exports_external.boolean().optional(),
27393
+ preemptive_compaction: exports_external.boolean().optional(),
27394
+ preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional()
26828
27395
  });
26829
27396
  var OhMyOpenCodeConfigSchema = exports_external.object({
26830
27397
  $schema: exports_external.string().optional(),
@@ -26835,7 +27402,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
26835
27402
  claude_code: ClaudeCodeConfigSchema.optional(),
26836
27403
  google_auth: exports_external.boolean().optional(),
26837
27404
  sisyphus_agent: SisyphusAgentConfigSchema.optional(),
26838
- experimental: ExperimentalConfigSchema.optional()
27405
+ experimental: ExperimentalConfigSchema.optional(),
27406
+ auto_update: exports_external.boolean().optional()
26839
27407
  });
26840
27408
  // src/agents/plan-prompt.ts
26841
27409
  var PLAN_SYSTEM_PROMPT = `<system-reminder>
@@ -26909,7 +27477,7 @@ var PLAN_PERMISSION = {
26909
27477
  };
26910
27478
 
26911
27479
  // src/index.ts
26912
- import * as fs6 from "fs";
27480
+ import * as fs7 from "fs";
26913
27481
  import * as path7 from "path";
26914
27482
  var AGENT_NAME_MAP = {
26915
27483
  omo: "Sisyphus",
@@ -26954,7 +27522,7 @@ function migrateConfigFile(configPath, rawConfig) {
26954
27522
  }
26955
27523
  if (needsWrite) {
26956
27524
  try {
26957
- fs6.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + `
27525
+ fs7.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + `
26958
27526
  `, "utf-8");
26959
27527
  log(`Migrated config file: ${configPath} (OmO \u2192 Sisyphus)`);
26960
27528
  } catch (err) {
@@ -26965,8 +27533,8 @@ function migrateConfigFile(configPath, rawConfig) {
26965
27533
  }
26966
27534
  function loadConfigFromPath2(configPath) {
26967
27535
  try {
26968
- if (fs6.existsSync(configPath)) {
26969
- const content = fs6.readFileSync(configPath, "utf-8");
27536
+ if (fs7.existsSync(configPath)) {
27537
+ const content = fs7.readFileSync(configPath, "utf-8");
26970
27538
  const rawConfig = JSON.parse(content);
26971
27539
  migrateConfigFile(configPath, rawConfig);
26972
27540
  const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
@@ -27033,6 +27601,18 @@ var OhMyOpenCodePlugin = async (ctx) => {
27033
27601
  const pluginConfig = loadPluginConfig(ctx.directory);
27034
27602
  const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
27035
27603
  const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
27604
+ const modelContextLimitsCache = new Map;
27605
+ let anthropicContext1MEnabled = false;
27606
+ const getModelLimit = (providerID, modelID) => {
27607
+ const key = `${providerID}/${modelID}`;
27608
+ const cached2 = modelContextLimitsCache.get(key);
27609
+ if (cached2)
27610
+ return cached2;
27611
+ if (providerID === "anthropic" && anthropicContext1MEnabled && modelID.includes("sonnet")) {
27612
+ return 1e6;
27613
+ }
27614
+ return;
27615
+ };
27036
27616
  const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
27037
27617
  const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
27038
27618
  const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
@@ -27051,10 +27631,17 @@ var OhMyOpenCodePlugin = async (ctx) => {
27051
27631
  disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
27052
27632
  });
27053
27633
  const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx, { experimental: pluginConfig.experimental }) : null;
27634
+ const compactionContextInjector = createCompactionContextInjector();
27635
+ const preemptiveCompaction = createPreemptiveCompactionHook(ctx, {
27636
+ experimental: pluginConfig.experimental,
27637
+ onBeforeSummarize: compactionContextInjector,
27638
+ getModelLimit
27639
+ });
27054
27640
  const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
27055
27641
  const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx, {
27056
27642
  showStartupToast: isHookEnabled("startup-toast"),
27057
- isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true
27643
+ isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true,
27644
+ autoUpdate: pluginConfig.auto_update ?? true
27058
27645
  }) : null;
27059
27646
  const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook() : null;
27060
27647
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
@@ -27085,6 +27672,22 @@ var OhMyOpenCodePlugin = async (ctx) => {
27085
27672
  await emptyMessageSanitizer?.["experimental.chat.messages.transform"]?.(input, output);
27086
27673
  },
27087
27674
  config: async (config3) => {
27675
+ const providers = config3.provider;
27676
+ const anthropicBeta = providers?.anthropic?.options?.headers?.["anthropic-beta"];
27677
+ anthropicContext1MEnabled = anthropicBeta?.includes("context-1m") ?? false;
27678
+ if (providers) {
27679
+ for (const [providerID, providerConfig] of Object.entries(providers)) {
27680
+ const models = providerConfig?.models;
27681
+ if (models) {
27682
+ for (const [modelID, modelConfig] of Object.entries(models)) {
27683
+ const contextLimit = modelConfig?.limit?.context;
27684
+ if (contextLimit) {
27685
+ modelContextLimitsCache.set(`${providerID}/${modelID}`, contextLimit);
27686
+ }
27687
+ }
27688
+ }
27689
+ }
27690
+ }
27088
27691
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config3.model);
27089
27692
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
27090
27693
  const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
@@ -27181,6 +27784,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
27181
27784
  await rulesInjector?.event(input);
27182
27785
  await thinkMode?.event(input);
27183
27786
  await anthropicAutoCompact?.event(input);
27787
+ await preemptiveCompaction?.event(input);
27184
27788
  await agentUsageReminder?.event(input);
27185
27789
  await interactiveBashSession?.event(input);
27186
27790
  const { event } = input;