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/README.ja.md +67 -23
- package/README.ko.md +69 -25
- package/README.md +67 -23
- package/README.zh-cn.md +66 -22
- package/dist/agents/oracle.d.ts +1 -0
- package/dist/agents/sisyphus.d.ts +1 -0
- package/dist/agents/types.d.ts +2 -0
- package/dist/agents/utils.test.d.ts +1 -0
- package/dist/config/schema.d.ts +26 -23
- package/dist/hooks/anthropic-auto-compact/types.d.ts +1 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +7 -0
- package/dist/hooks/auto-update-checker/index.d.ts +1 -1
- package/dist/hooks/auto-update-checker/types.d.ts +1 -0
- package/dist/hooks/compaction-context-injector/index.d.ts +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/preemptive-compaction/constants.d.ts +3 -0
- package/dist/hooks/preemptive-compaction/index.d.ts +24 -0
- package/dist/hooks/preemptive-compaction/types.d.ts +17 -0
- package/dist/index.js +1115 -511
- package/dist/shared/config-path.d.ts +4 -1
- package/dist/tools/ast-grep/index.d.ts +22 -22
- package/dist/tools/ast-grep/tools.d.ts +22 -22
- package/dist/tools/index.d.ts +22 -22
- package/package.json +3 -2
- package/dist/agents/build.d.ts +0 -1
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+
|
|
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
|
-
###
|
|
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
|
-
|
|
|
1668
|
-
|
|
1669
|
-
|
|
|
1670
|
-
|
|
|
1671
|
-
|
|
|
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
|
-
|
|
1683
|
+
#### Step 2: Ask Yourself
|
|
1674
1684
|
|
|
1675
|
-
|
|
1685
|
+
Before touching any frontend file, think:
|
|
1686
|
+
> "Is this change about **how it LOOKS** or **how it WORKS**?"
|
|
1676
1687
|
|
|
1677
|
-
|
|
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\` |
|
|
1685
|
-
| Librarian | \`librarian\` | Unfamiliar packages /
|
|
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
|
|
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** |
|
|
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
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
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
|
|
1937
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2402
|
+
---
|
|
2363
2403
|
|
|
2364
|
-
|
|
2365
|
-
**Never compromise on task completion. What you commit to, you deliver.**
|
|
2404
|
+
# Work Principles
|
|
2366
2405
|
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
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
|
-
|
|
2374
|
-
**Approach every codebase with the mindset of a student, always ready to learn.**
|
|
2412
|
+
---
|
|
2375
2413
|
|
|
2376
|
-
|
|
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
|
-
|
|
2381
|
-
**Respect the existing codebase. Your code should blend seamlessly.**
|
|
2416
|
+
Before coding, commit to a **BOLD aesthetic direction**:
|
|
2382
2417
|
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
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
|
-
|
|
2390
|
-
**Keep everyone informed. Hide nothing.**
|
|
2423
|
+
**Key**: Choose a clear direction and execute with precision. Intentionality > intensity.
|
|
2391
2424
|
|
|
2392
|
-
|
|
2393
|
-
-
|
|
2394
|
-
-
|
|
2395
|
-
-
|
|
2396
|
-
|
|
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
|
-
|
|
2431
|
+
---
|
|
2399
2432
|
|
|
2400
|
-
|
|
2433
|
+
# Aesthetic Guidelines
|
|
2401
2434
|
|
|
2402
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
2407
|
-
-
|
|
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
|
-
|
|
2444
|
+
## Spatial Composition
|
|
2445
|
+
Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
|
|
2413
2446
|
|
|
2414
|
-
|
|
2415
|
-
|
|
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
|
-
|
|
2450
|
+
---
|
|
2451
|
+
|
|
2452
|
+
# Anti-Patterns (NEVER)
|
|
2421
2453
|
|
|
2422
|
-
|
|
2423
|
-
-
|
|
2424
|
-
-
|
|
2425
|
-
-
|
|
2426
|
-
-
|
|
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
|
-
|
|
2460
|
+
---
|
|
2430
2461
|
|
|
2431
|
-
|
|
2462
|
+
# Execution
|
|
2432
2463
|
|
|
2433
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
3171
|
-
Sisyphus:
|
|
3172
|
-
oracle:
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3234
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
3337
|
+
if (!existsSync4(MESSAGE_STORAGE)) {
|
|
3288
3338
|
mkdirSync(MESSAGE_STORAGE, { recursive: true });
|
|
3289
3339
|
}
|
|
3290
3340
|
const directPath = join5(MESSAGE_STORAGE, sessionID);
|
|
3291
|
-
if (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
3420
|
+
if (!existsSync5(MESSAGE_STORAGE))
|
|
3371
3421
|
return null;
|
|
3372
3422
|
const directPath = join6(MESSAGE_STORAGE, sessionID);
|
|
3373
|
-
if (
|
|
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 (
|
|
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
|
|
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
|
|
3426
|
-
if (
|
|
3427
|
-
|
|
3428
|
-
|
|
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
|
|
3439
|
-
if (
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
}
|
|
3443
|
-
const
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
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
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
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
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
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
|
-
}
|
|
3520
|
-
|
|
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
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
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
|
|
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
|
|
3547
|
-
if (
|
|
3548
|
-
|
|
3549
|
-
|
|
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
|
|
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 (!
|
|
3984
|
+
if (!existsSync6(MESSAGE_STORAGE2))
|
|
3903
3985
|
return "";
|
|
3904
3986
|
const directPath = join8(MESSAGE_STORAGE2, sessionID);
|
|
3905
|
-
if (
|
|
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 (
|
|
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 || !
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
4345
|
-
import * as
|
|
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
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
4578
|
+
if (existsSync7(archivePath)) {
|
|
4457
4579
|
unlinkSync2(archivePath);
|
|
4458
4580
|
}
|
|
4459
|
-
if (process.platform !== "win32" &&
|
|
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
|
-
|
|
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 (
|
|
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 &&
|
|
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 (!
|
|
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
|
|
4595
|
-
import { existsSync as
|
|
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
|
-
|
|
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 || !
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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
|
|
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" && !
|
|
5357
|
+
if (process.platform === "darwin" && !existsSync14(OPENCODE_STORAGE5)) {
|
|
5226
5358
|
const localShare = join18(homedir5(), ".local", "share", "opencode", "storage");
|
|
5227
|
-
if (
|
|
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 (!
|
|
5367
|
+
if (!existsSync14(MESSAGE_STORAGE3))
|
|
5236
5368
|
return "";
|
|
5237
5369
|
const directPath = join18(MESSAGE_STORAGE3, sessionID);
|
|
5238
|
-
if (
|
|
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 (
|
|
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 || !
|
|
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 (!
|
|
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
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
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
|
-
}
|
|
5485
|
-
return false;
|
|
5623
|
+
}
|
|
5486
5624
|
}
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
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 ${
|
|
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 (
|
|
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
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
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
|
|
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 &&
|
|
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 (
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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
|
|
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 (!
|
|
7878
|
+
if (!fs5.existsSync(configPath))
|
|
7525
7879
|
continue;
|
|
7526
|
-
const content =
|
|
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 =
|
|
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 (
|
|
7904
|
+
if (fs5.existsSync(pkgPath)) {
|
|
7551
7905
|
try {
|
|
7552
|
-
const content =
|
|
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 =
|
|
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 (!
|
|
7938
|
+
if (!fs5.existsSync(configPath))
|
|
7585
7939
|
continue;
|
|
7586
|
-
const content =
|
|
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 (
|
|
7608
|
-
const content =
|
|
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 =
|
|
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
|
|
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 (!
|
|
8051
|
+
if (!fs6.existsSync(lockPath))
|
|
7684
8052
|
return false;
|
|
7685
8053
|
try {
|
|
7686
|
-
const content =
|
|
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
|
-
|
|
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 (
|
|
7714
|
-
|
|
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 (
|
|
7719
|
-
const content =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
7775
|
-
const
|
|
7776
|
-
|
|
7777
|
-
|
|
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
|
-
|
|
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 (
|
|
7785
|
-
|
|
7786
|
-
if (showStartupToast) {
|
|
7787
|
-
await showVersionToast(result.currentVersion);
|
|
7788
|
-
}
|
|
7789
|
-
return;
|
|
8142
|
+
if (showStartupToast) {
|
|
8143
|
+
showVersionToast(ctx, displayVersion, getToastMessage(false)).catch(() => {});
|
|
7790
8144
|
}
|
|
7791
|
-
|
|
7792
|
-
log("[auto-update-checker]
|
|
7793
|
-
|
|
7794
|
-
|
|
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
|
|
8261
|
+
existsSync as existsSync22,
|
|
7836
8262
|
mkdirSync as mkdirSync8,
|
|
7837
8263
|
readFileSync as readFileSync13,
|
|
7838
|
-
writeFileSync as
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
8335
|
+
writeFileSync10(filePath, JSON.stringify(state2, null, 2));
|
|
7910
8336
|
}
|
|
7911
8337
|
function clearAgentUsageState(sessionID) {
|
|
7912
8338
|
const filePath = getStoragePath4(sessionID);
|
|
7913
|
-
if (
|
|
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
|
-
|
|
8467
|
+
ANALYSIS MODE. Gather context before diving deep:
|
|
8034
8468
|
|
|
8035
|
-
|
|
8036
|
-
-
|
|
8037
|
-
-
|
|
8038
|
-
-
|
|
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
|
-
|
|
8041
|
-
-
|
|
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
|
|
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
|
|
8566
|
+
existsSync as existsSync23,
|
|
8121
8567
|
mkdirSync as mkdirSync9,
|
|
8122
8568
|
readFileSync as readFileSync14,
|
|
8123
|
-
writeFileSync as
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
8617
|
+
writeFileSync11(filePath, JSON.stringify(serialized, null, 2));
|
|
8172
8618
|
}
|
|
8173
8619
|
function clearInteractiveBashSessionState(sessionID) {
|
|
8174
8620
|
const filePath = getStoragePath5(sessionID);
|
|
8175
|
-
if (
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
-
".
|
|
10593
|
-
".
|
|
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
|
-
".
|
|
10597
|
-
".
|
|
10598
|
-
".
|
|
10599
|
-
".
|
|
10600
|
-
".
|
|
10601
|
-
".
|
|
10602
|
-
".
|
|
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
|
-
".
|
|
10641
|
-
".
|
|
10642
|
-
".tfvars": "terraform"
|
|
11190
|
+
".graphql": "graphql",
|
|
11191
|
+
".gql": "graphql"
|
|
10643
11192
|
};
|
|
10644
11193
|
// src/tools/lsp/config.ts
|
|
10645
|
-
import { existsSync as
|
|
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 (!
|
|
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
|
|
11304
|
+
const pathSeparator = isWindows2 ? ";" : ":";
|
|
11305
|
+
const paths = pathEnv.split(pathSeparator);
|
|
10754
11306
|
for (const p of paths) {
|
|
10755
|
-
if (
|
|
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
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
24940
|
+
if (existsSync30(archivePath)) {
|
|
24375
24941
|
unlinkSync9(archivePath);
|
|
24376
24942
|
}
|
|
24377
|
-
if (process.platform !== "win32" &&
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
|
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 &&
|
|
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 &&
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
25513
|
-
const scripts =
|
|
25514
|
-
const assets =
|
|
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 (!
|
|
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
|
|
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 (!
|
|
26914
|
+
if (!existsSync37(MESSAGE_STORAGE))
|
|
26349
26915
|
return null;
|
|
26350
26916
|
const directPath = join44(MESSAGE_STORAGE, sessionID);
|
|
26351
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
26827
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
26969
|
-
const content =
|
|
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;
|